-
Notifications
You must be signed in to change notification settings - Fork 171
/
abusing_token_eop_1.0.txt
1140 lines (924 loc) · 56.4 KB
/
abusing_token_eop_1.0.txt
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
|=-----------------------------------------------------------------------=|
|=----------------=[ Abusing Token Privileges For LPE]=------------------=|
|=-----------------------------------------------------------------------=|
|=----------------------=[ drone <@dronesec> ]=--------------------------=|
|=----------------=[ breenmachine <@breenmachine>]=----------------------=|
|=-----------------------------------------------------------------------=|
--[ Table of contents
0 - Introduction
1 - Token Overview
1.1 - Windows Privilege Model
1.2 - Token Structure and Privileges
1.3 - Token Impersonation
2 - Modern Mitigations and Techniques
2.1 - Modern Windows Kernel Mitigations
2.2 - Relevant Exploitation Strategies
3 - Abusing Token Privileges
3.1 - Exploitable Privileges
3.2 - Exploiting Partial Writes
3.3 - Abusing Existing Service Accounts
4 - Case Studies
4.1 - MS16-135
4.2 - MS15-061
4.3 - HEVD
5 - Conclusions
6 - Greets
7 - References
--[ 0 - Introduction
Windows kernel exploitation has grown increasingly complex over the last
several years, particularly with the release of Windows 10 and its
successive core updates. Techniques have been born and died over the
course of months, new ones quickly replacing them. Techniques themselves
have grown hyper specific or brittle, fitting only a particular pattern or
space.
Exploitation has often relied on obtaining some form of arbitrary code
execution. Obtaining a read/write primitive, stacking strategies to evade
mitigations, and eventually obtaining the execution of a privileged action.
Recently we've seen a trend towards logic basic actions, such as token
stealing, enabling god mode bits, or NULLing out token security descriptors
[0]. These actions delegate the theft of privileges to userland, freeing
the exploit dev from the confines of kernel hell.
The token stealing shellcode popularized by many exploit developers and
malware authors over the years is not one of chance. It's been an
extremely reliable technique offering stable, simple shellcode and, until
recently, acceptable behavior. Microsoft has implemented detection
heuristics within its Advanced Threat Protection platform [2], but as of
yet not implemented anything within the Windows kernel itself. For malware
writers and skiddies, this might be tolerable, but for advanced adversaries
or red teamers, it is not.
This paper aims to discuss one such logic-based technique that we've
refined over the last several months and exploits. Although the techniques
themselves are not new [2], we hope to present new approaches and ideas
that may aid in the further refinement of this technique and others.
In addition to kernel exploitation, token privileges can be abused in other
less exotic ways. In situations where a service account is compromised,
which has non-standard privileges enabled, they can often be leveraged to
gain elevation of privilege (EoP). The methods for doing this are specific
to each privilege, often undocumented, and in many cases non-trivial. In
section 3.3 of this paper we demonstrate how many of these privileges can be
abused for EoP in common penetration testing and red teaming scenarios.
We aim to consolidate disparate sources and provide reference for future
work. We acknowledge the time and efforts of other researches within the
same space, and hope to give something meaningful back to the community at
large.
--[ 1 - Token Overview
The basis of our strategy again stems from the very core of the object
access model within Windows. Windows uses token objects to describe the
security context of a particular thread or process. These token objects,
represented by the nt!_TOKEN structure, contain a vast swath of security
and referential information, including integrity level, privileges, groups,
and more. Our focus lies on the privileges contained within these tokens.
----[ 1.1 - Windows Privilege Model
We'll briefly describe the Windows privilege model as it relates to process
and thread tokens. If you'd like an in-depth explanation, the authors
recommend Windows Internals Part 1 or spending some time in windbg.
Each process on the system holds a token object reference within its
EPROCESS structure which is used during object access negotiations or
privileged system tasks. This token is granted via LSASS during the logon
process, and thus all processes within a session run under the same token,
initially.
A process holds a primary token and threads executing within the process
inherit this same token. When a thread needs to access an object using a
different set of credentials, it can use an impersonation token. Using an
impersonation token does not impact the primary token or other threads, but
only execution in the context of the impersonating thread. These
impersonation tokens can be obtained via a number of different APIs
provided by the kernel.
The token serves as a processes access ticket, which must be presented to
the various gatekeepers within Windows; it's evaluated via SeAccessCheck on
object access and by SeSinglePrivilegeCheck during privileged operations.
When a process requests write access to a file, for example, SeAccessCheck
will evaluate the tokens integrity level followed by an evaluation of its
Discretionary Access Control List (DACL). When a process attempts to
shutdown a system via NtShutdownSystem, the kernel will evaluate whether or
not the requesting process token has SeShutdownPrivilege enabled.
----[ 1.2 - Token Structure and Privileges
As mentioned, the _TOKEN structure primarily contains security context
information about a process or thread. The relevant entry for our purposes
is _SEP_TOKEN_PRIVILEGES, located at offset 0x40, containing token
privilege information:
kd> dt nt!_SEP_TOKEN_PRIVILEGES c5d39c30+40
+0x000 Present : 0x00000006`02880000
+0x008 Enabled : 0x800000
+0x010 EnabledByDefault : 0x800000
The Present entry represents an unsigned long long containing the present
privileges on the token. This does not mean that they are enabled or
disabled, but only that they exist on the token. Once a token is created,
you cannot add privileges to it; you may only enable or disable existing
ones found in this field. The second field, Enabled, represents an
unsigned long long containing all enabled privileges on the token.
Privileges must be enabled in this bitmask to pass the
SeSinglePrivilegeCheck. The final field, EnabledByDefault, represents the
initial state of the token at the moment of conception.
Privileges can be enabled or disabled by twiddling specific bits within
these fields. For example, to enable SeCreateTokenPrivilege, one would
simply need to execute: _SEP_TOKEN_PRIVILEGES+0x44 |= 1 << 0x000000002.
Disabling the privilege would be the inverse: _SEP_TOKEN_PRIVILEGES+0x44 &=
~(1 << 0x000000002). A Pykd helper script can be found here [3].
Up until recently, one must only set bits within the Enabled field to
actually toggle privileges within a token. This means that a single write,
partial or otherwise, is enough to enable privileges. With the release of
Windows 10 v1607, however, the kernel now verifies that the enabled bits
are also flipped in the Present field [4].
Although on the surface the tokens security model of defining specific
privileges for various tasks appears to allow for the implementation of
service specific, fine-grained access controls, a closer look reveals a
more complex situation. Many of the privileges, when enabled, allow the
user to perform privileged actions that can result in elevation of
privilege. This effectively destroys the "fine-grain" access control
structure and can provide a false sense of security.
----[ 1.3 - Token Impersonation
Before diving into specific privileges, it will be beneficial to describe
the Windows mechanism for determining whether a specific thread can make
use of a given token. Any user may be able to obtain a handle to a
privileged token, but being able to actually use it is another matter.
In Windows, “Token Impersonation” is when a new token is assigned to a
thread that is different from the parent process's token. Although the word
impersonation implies that one user is using a token belonging to a
different user, this is not always the case. A user may impersonate a token
that belongs to them but simply has a different set of privileges or some
other modifications.
One of the fields specified in every token is the tokens impersonation
level. This field controls whether that token can be used for impersonation
purposes and to what degree. The four impersonation levels are as follows:
SecurityAnonymous - The server cannot impersonate or identify the client.
SecurityIdentification - The server can get the identity and privileges of
the client, but cannot impersonate the client.
SecurityImpersonation - The server can impersonate the client's security
context on the local system.
SecurityDelegation - The server can impersonate the client's security
context on remote systems.
SecurityImpersonation and SecurityDelegation are the most interesting cases
for us. Identification tokens and lower can not be used to run code.
Whether or not a given user is allowed to impersonate a specific token can
be determined as follows:
IF the token level < Impersonate THEN allow (such tokens are called
“Identification” level and can not be used for privileged actions).
IF the process has “Impersonate” privilege THEN allow.
IF the process integrity level <= the token integrity level AND
the process user == token user THEN allow ELSE restrict the
token to “Identification” level (no privileged actions possible).
--[ 2 - Modern Mitigations and Techniques
Windows 10 significantly improves upon the security of the Windows kernel,
both in overall reducing the attack surface and improving existing
defenses. Microsoft continues to iterate on defensive mechanisms: KASLR
continues to receive much needed improvements (still many leaks), hardening
of often exploited structures for read/write primitives (tagWND), and
mitigating the revered NULL SecurityDescriptor technique [5]. As attackers
have demonstrated over the years, squashing individual strategies only
gives birth to newer ones, and the cycle continues.
In order to provide some context on the topics discussed, a brief
description of modern kernel mitigations and exploitation techniques
follows. These sections are by no means authoritative or comprehensive,
and are meant only to reinforce the overall complexity and upfront
investment cost of modern kernel exploitation. Go ask Ionescu if you have
questions.
----[ 2.1 - Modern Windows Kernel Mitigations
Windows 10 and successive updates (Anniversary/Creators) have included a
number of exploit mitigation improvements, detailed by Skape et al. at
Blackhat 2016 [8]. We recommend reviewing the referenced slides for
further details and statistics on general Windows mitigation strategies.
We will focus on mitigations relevant to the topic at hand.
Kernel ASLR has seen improvement by enabling ASLR on various regions and
structures within the kernel. Though this is a strong mitigation strategy
for remote kernel exploits, there exists several public and private
strategies for leaking object addresses in the kernel [9], and thus does
not pose much of a threat for the technique detailed here. Core data
structures and memory regions once used for KASLR bypasses, such as the
HAL dispatch table, are now fully randomized.
The AppContainer, first introduced in Windows 8, provides sandboxing
capabilities for userland applications. This control has been expanded upon in
Windows 10 to include win32k system call filtering which restricts the
process from abusing various win32k syscalls. This is currently only
(officially) enabled for the Edge browser, and thus is not very interesting.
A common kernel read/write primitive is the tagWND structure, which when
corrupted allows for arbitrary read/writes via
InternalGetWindowText/NtUserDefSetText. Exploits for MS15-061 and more
recently MS16-135 were found to be taking advantage of this technique. The
Anniversary Update now provides additional bounds checks on this specific
object, rendering it useless. Though this form of technique squashing is
rudimentary, a mere annoyance in having to find other rw primitives, it
can be effective for breaking already deployed/developed chains.
The introduction of SMEP in Windows 8 means we can no longer ask the kernel
to execute userland shellcode for us. Many bypasses have been used over
the years, some still effective, most not. Evasion generally involves
disabling some principle of mitigation or getting code into the kernel (via
RWX regions or KROP). Because we're not looking to execute any shellcode
within the kernel or even in userland, this mitigation does not apply.
Another interesting mitigation is the mythical Patchguard, formerly known
as Kernel Patch Protection, introduced many moons ago to the x64 NT kernel.
If you're unfamiliar with this mitigation, know that it is the kernel
boogeyman. It monitors a random subset of checks, at random times, for
random reasons. Its initialization and runtime behavior are obfuscated.
Its source code and behavior are obfuscated from even internal Windows
developers. Skywing and Skape have previously done quite a bit of work
reversing and documenting portions of it, and should be referenced for
further tales of intrigue [18].
As of Windows 10, there are 44 different checks in Patchguard (visible via
!analyze -show 109), and while the authors are suspicious of the lists
completeness, there is no protection of process tokens.
----[ 2.2 - Relevant Exploitation Strategies
Previous, related work that provided influence and indirect guidance for this
article's strategy is presented here. These related techniques are briefly
detailed to provide background and to pay homage to those who came before
us.
Cesar Cerrudos Easy Local Windows Kernel Exploitation paper released at
Blackhat 2012 [1] introduced three different privilege escalation
strategies, and pointed many exploit devs towards the power of abusing
process tokens. The first technique demonstrated in the paper details the
NULL ACL strategy, now partially mitigated, in which an arbitrary
write could be leveraged to NULL a privileged object's ACL. This was and is a
very common strategy for effectively migrating into more privileged
processes.
The second Cerrudos strategy is a carpet bombing version of ours, in which an
arbitrary write could enable all privileges in a process token. With these
privileges enabled, one could exploit SeDebugPrivilege and migrate into a
more privileged process, create tokens with SeCreateTokenPrivilege, or load
kernel drivers with SeLoadDriverPrivilege.
The third and final Cerrudos strategy is another technique very popular in
modern EoP exploits, and involves replacing a process token with a SYSTEM
token. This has been widely detailed from various perspectives elsewhere,
and will not be repeated here.
Yin Liang and Zhou Li of Tencent conducted and released similar research at
Blackhat Europe 2016, in which they demonstrated abusing partial writes
with window objects in order to obtain rw primitives [11]. Much like our
work, they focused on partially controlled, limited write bugs such as
MS16-135 and CVE-2016-0174 in which the target of an OR or DEC may be
controlled. In these cases, particularly those involving win32k, our
strategy obviates the need to obtain a primitive.
Moritz Jodeit published a great article on CVE-2014-4113, in which he
targeted the _SEP_TOKEN_PRIVILEGES structure with an uncontrolled write in
order to enable additional token privileges [12]. His technique is of
interest because it demonstrated, at the time, modern mitigation evasion
without any pointer overwrites or the execution of shellcode, with the
added benefit of a vastly simplified exploitation process.
--[ 3 - Abusing Token Privileges
----[ 3.1 - Exploitable Privileges
For the purpose of this paper, we will define an “exploitable” privilege as
any token privilege that can be used alone to gain “NT AUTHORITY\SYSTEM”
level access to the target system. The term “exploit” is used loosely here,
in most cases these exploits are intended, albeit undocumented, behavior.
As was mentioned in section 1.2, the nt!_SEP_TOKEN_PRIVILEGES structure is
a binary field in the token where each bit determines whether a given
privilege is present or enabled in the token. The exact structure of this
bitmask, which bits correspond to which privileges, can be found in the
code released with this project, specifically the pykd script -
“tokenum.py”.
The remainder of this section deals with the details of each privilege we
were able to successfully abuse to gain elevated privileges. Code samples
to take advantage of each of these privileges are included with this
project.
------ [ 3.1.1 - SeImpersonatePrivilege
The SeImpersonatePrivilege is described on MSDN as “User Right: Impersonate
a client after authentication.” This is the privilege referred to in
section 1.4, in the second step, when checking whether a specific process can
impersonate a given token. Any process holding this privilege can
impersonate any token for which it is able to get a handle. It should be
noted that this privilege does not allow for the creation of new tokens.
This particular privilege is quite interesting because it is required by a
number of common Windows service accounts, such as LocalService and those
for MSSQL and IIS. An “exploit” for this privilege then would also yield
elevation of privilege if any such accounts were compromised. This was the
subject of previous work by the authors [6].
In [20], a method to gain a handle to a token for the “NT AUTHORITY\SYSTEM”
account is described. Basically a Windows service (DCOM) is passed a
specially crafted object that contains a reference to an attacker
controlled TCP listener on the local machine. When Windows tries to resolve
this reference, the attacker requests NTLM authentication and then relays
the NTLM authentication sent to the listener to create a new token on the
local machine. This token will be for the user “NT AUTHORITY\SYSTEM” and
have full administrative privilege.
Any user can perform the previously described procedure to gain a handle to
a token for the “NT AUTHORITY\SYSTEM” user, however in order to make use of
this handle, the ability to impersonate is required; the
SeImpersonatePrivilege allows us to do just that. All that is required to
spawn a new process with the elevated token is to call the
CreateProcessWithToken function, passing the new token as the first
argument.
------ [ 3.1.2 - SeAssignPrimaryPrivilege
The SeAssignPrimaryPrivilege is offensively very similar to the previously
discussed SeImpersonatePrivilege. It is needed to “assign the primary token
of a process”. Our strategy will be to spawn a new process using an
elevated token.
In order to create a new process with a privileged token, we will first
need to get a handle to such a token. To do this, we follow the procedure
described in [20] and briefly outlined in section 3.1.1.
As the name of this privilege implies, it allows us to assign a primary
token to a new or suspended process. Using the strategy outlined in 3.1.1
to obtain a token, we find ourselves with a privileged impersonation token,
and thus will need to first derive a primary token from it. This can be
accomplished via the DuplicateTokenEx function:
DuplicateTokenEx(hClientToken, TOKEN_ALL_ACCESS,
NULL, SecurityAnonymous,
TokenPrimary, &hDupedToken);
With a privileged primary token in hand, we now have a few options.
Unfortunately, we cannot simply swap the token of our currently running
process for the elevated one, as changing primary tokens on running
processes is not supported behavior. This is controlled by the
PrimaryTokenFrozen field in the EPROCESS structure.
The simplest option is to make a call to “CreateProcessAsUser” using the
new token as an argument to create a new, highly privileged process.
Alternatively, we can spawn a new process in the suspended state and
perform the same operation as above. When a new processes is created
with a call to CreateProcess(..., CREATE_SUSPENDED, ...), the value for
PrimaryTokenFrozen is not yet set, allowing the token to be swapped.
A corollary to the previous point is that in some partial write
exploitation scenarios, we can actually swap the token of the currently
running process for an elevated one. If we can cause the PrimaryTokenFrozen
field to be unset through our partial write, assuming we possess, or can
gain SeAssignPrimaryTokenPrivilege, we can then call
NtSetInformationProcess to swap the old token for the new one. Since
PrimaryTokenFrozen is a single bit field, the surrounding fields are
relevant because they will likely be trashed by any sort of partial write:
+0x0c8 RefTraceEnabled : Pos 9, 1 Bit
+0x0c8 DisableDynamicCode : Pos 10, 1 Bit
+0x0c8 EmptyJobEvaluated : Pos 11, 1 Bit
+0x0c8 DefaultPagePriority : Pos 12, 3 Bits
+0x0c8 PrimaryTokenFrozen : Pos 15, 1 Bit
+0x0c8 ProcessVerifierTarget : Pos 16, 1 Bit
+0x0c8 StackRandomizationDisabled : Pos 17, 1 Bit
If for example you've got an arbitrary decrement, as in MS15-061, you can
unset the TokenPrimaryFrozen bit and swap your process token out:
NtSetInformationProcess(hCurrentProcess,
(PROCESS_INFORMATION_CLASS)0x09,
&hElevatedPrimaryToken, 8);
------ [ 3.1.3 - SeTcbPrivilege
SeTcbPrivilege is quite interesting, MSDN describes it as “This privilege
identifies its holder as part of the trusted computer base. Some trusted
protected subsystems are granted this privilege.” In addition to this, a
number of books, articles, and forum posts describe the TCB privilege as
being equivalent to fully privileged access to the machine. However,
despite all this, no publicly available resources seem to indicate HOW the
SeTcbPrivilege can be used, on its own, to perform privileged operations.
We began by analyzing MSDN documentation, trying to find which Windows API
calls were restricted to accounts with SeTcbPrivilge. In the documentation
for the LsaLogonUser function, we find the following as the first
parameter:
LsaHandle [in] A handle obtained from a previous call to
LsaRegisterLogonProcess. The caller is required to have SeTcbPrivilege
only if one or more of the following is true:
+ A Subauthentication package is used.
+ KERB_S4U_LOGON is used, and the caller requests an impersonation token.
+ The LocalGroups parameter is not NULL.
If SeTcbPrivilege is not required, call LsaConnectUntrusted to obtain
the handle.
Normally the LsaLogonUser API call is used to authenticate a user with some
form of credentials, however we are assuming that we have no knowledge of
the target system and its users. Further, which user will we attempt to
logon? Finally, how will we impersonate the resulting token since we do not
have SeImpersonatePrivilege?
Thankfully James Forshaw chimed in with a very useful <140 character
message:
“you could use LsaLogonUser to add admin group to a token of your own user,
then impersonate.”
There is an interesting logon type in Windows known as S4U logon (and
referenced above as KERB_S4U_LOGON). It is effectively described in an MSDN
blog post [19] as follows:
“In Windows, it is possible to logon as a different domain user without any
credentials. This is known as a S4U or a Service For User Logon. This is
a Microsoft Extension to Kerberos introduced with Windows Server 2003.”
This seems to fit what we are trying to do perfectly; using the S4U logon
type, we can obtain a token for any user. Referring back to the
documentation for the LsaHandle parameter above, if we have SeTcbPrivilege,
apparently the resulting token can be an “impersonation” token, meaning we
can assign it to a thread.
Referring again to the LsaHandle parameter, the last bullet point implies
that we can call LsaLogonUser with SeTcbPrivilege and add arbitrary groups
to the resulting token returned by this call. We will add the group SID
“S-1-5-18” to the token, this is the SID for the Local System account and
if we are using a token that possesses it, we will have full privilege on
the system. Adding the SYSTEM SID is quite straightforward:
WCHAR systemSID[] = L"S-1-5-18";
ConvertStringSidToSid(systemSID, &pExtraSid);
pGroups->Groups[pGroups->GroupCount].Attributes =
SE_GROUP_ENABLED | SE_GROUP_MANDATORY;
pGroups->Groups[pGroups->GroupCount].Sid = pExtraSid;
pGroups->GroupCount++;
The only piece remaining in this puzzle is how we will use the resulting
impersonation token, since we are assuming that we have SeTcbPrivilege, but
no other impersonation related privileges. Referring back to section 1.4 on
the rules related to token impersonation, we can see that we should be able
to impersonate the token without any special privileges as long as the
token is for our current user and the integrity level of the token is
“Medium”. So using the token returned by LsaLogonUser, we simply set the
integrity level to “Medium” and then call SetThreadToken to replace our
current thread's token with the new one.
------ [ 3.1.4 - SeBackupPrivilege
The SeBackupPrivilege is described on MSDN as follows:
“Required to perform backup operations. This privilege causes the system to
grant all read access control to any file, regardless of the access control
list (ACL) specified for the file. Any access request other than read is
still evaluated with the ACL. This privilege is required by the RegSaveKey
and RegSaveKeyExfunctions. The following access rights are granted if this
privilege is held: -READ_CONTROL -ACCESS_SYSTEM_SECURITY -FILE_GENERIC_READ
-FILE_TRAVERSE”
To use this privilege for EoP, we read the password hashes of local
Administrator accounts from the registry and then pass these to a local
service from which we can get code execution, the most popular technique
here would simply be passing the hash with “psexec” or “wmiexec”.
Unfortunately for us there are two caveats in this scenario. First, many
organizations over the past several years have begun to disable local
administrator accounts. Second, even in cases where some local
administrator accounts remain enabled, Microsoft implemented a change in
Windows Vista and newer where only the RID 500 (the default local
Administrator) account can administer the machine remotely by default:
When a user who is a member of the local administrators group on the
target remote computer establishes a remote administrative
connection…they will not connect as a full administrator. The user has
no elevation potential on the remote computer, and the user cannot
perform administrative tasks.
In our experience however, it is still fairly common in enterprise
environments to find the RID 500 local administrator account enabled.
------ [ 3.1.5 - SeRestorePrivilege
The restore privilege is described as being “required to perform restore
operations”, and causes the system to grant all write access control to any
file on the system, regardless of the files ACL. Additionally, this
privilege allows the holding process or thread to change the owner of a
file. The implications of obtaining this privilege should be obvious.
In order to use this privilege, one must supply the
FILE_FLAG_BACKUP_SEMANTICS flag to supporting APIs. This tips the kernel
off that the requesting process might have SeBackupPrivilege or
SeRestorePrivilege enabled, and to check for it before short circuiting the
DACL check. Keen observers may infer from the terse description of the
privilege on MSDN that this also allows for the creation or modification of
registry keys.
Arbitrary writes to HKLM opens up infinite potential for privilege
escalation. We chose to use the Image File Execution Options key, used for
debugging software on a system. When a system binary is launched, if an
HKLM entry exists for it at HKLM\SOFTWARE\Microsoft\Windows
NT\CurrentVersion\Image File Execution Options and it contains a Debugger
key, it will execute the set entry. This technique is fairly common for
malware persistence and privilege escalation vulnerabilities [7].
Exploitation simply requires opening the registry, specifying the backup
flag, and creating the key:
RegCreateKeyExA(
HKEY_LOCAL_MACHINE,
“HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File
Execution Options\wsqmcons.exe”,
0, NULL,
REG_OPTION_BACKUP_RESTORE,
KEY_SET_VALUE,
NULL,
&hkReg,
NULL);
RegSetValueExA(hkReg,
"Debugger", 0, REG_SZ,
(const BYTE*)regentry,
strlen(regentry) + 1);
In the above sample we used wsqmcons, a consolidator service that runs as
SYSTEM and is triggerable by a standard user on the system.
In addition to adding registry entries, one could also drop DLLs into
system folders for DLL hijacking, overwrite critical system resources, or
modify other services.
------ [ 3.1.6 - SeCreateTokenPrivilege
The “SeCreateTokenPrivilege” allows users to create primary tokens via the
ZwCreateToken API. Unfortunately, this right alone does not allow the user
to use the token they have just created. Therefore naive attempts to create
and use tokens for high privileged users such as “NT AUTHORITY\SYSTEM” will
not succeed.
Recall the rules for token impersonation from section 1.4. A user is
allowed to impersonate a token even without SeImpersonatePrivilege so long
as the token is for the same user and the integrity level is less than or
equal to the current process integrity level.
In order to leverage SeCreateTokenPrivilege, we need only craft a new
impersonation token that matches the requesting token with the addition of
a privileged group SID. This is in theory pretty simple to do, but the API
for these interfaces and ZwCreateToken is not very friendly. Most of the
fields we can query from our executing token via GetTokenInformation, with
only a few exceptions.
As mentioned, we want to enable the local administrator group on the token.
To do this, we build a SID using the RID of the group:
SID_BUILTIN SIDLocalAdminGroup = { 1, 2, { 0, 0, 0, 0, 0, 5 }, { 32,
DOMAIN_ALIAS_RID_ADMINS } };
We then iterate over the token's groups and elevate it from user to
administrative membership:
for (int i = 0; i < groups->GroupCount; ++i, pSid++) {
PISID piSid = PISID)pSid->Sid;
if (piSid->SubAuthority[piSid->SubAuthorityCount - 1] ==
DOMAIN_ALIAS_RID_USERS){
memcpy(piSid, &TkSidLocalAdminGroup, sizeof(TkSidLocalAdminGroup));
pSid->Attributes = SE_GROUP_ENABLED;
}
}
The final change is ensuring that we're building a TokenImpersonation
token. This can be set in the token's object attributes:
SECURITY_QUALITY_OF_SERVICE sqos = { sizeof(sqos),
SecurityImpersonation,
SECURITY_STATIC_TRACKING, FALSE };
OBJECT_ATTRIBUTES oa = { sizeof(oa), 0, 0, 0, 0, &sqos };
Provided we maintain the token user and integrity level, we can finally use
the token and impersonate the executing thread.
------ [ 3.1.7 - SeLoadDriverPrivilege
The LoadDriver privilege is described by Microsoft as “User Right: Load and
unload device drivers”. The fact that device drivers run in the kernel
makes this a very desirable privilege.
Our goal then is to execute arbitrary code in the kernel given only
SeLoadDriverPrivilege and to bypass any driver signing requirements in the
process; a tall order. Most documentation on this privilege and the
associated “NtLoadDriver” Windows API call assumes that the caller has
additional privilege on the system, specifically the ability to write to
the HKLM (HKEY_LOCAL_MACHINE) section of the Windows registry.
The format of the Windows API call to load a driver is as follows:
NTSTATUS NtLoadDriver( _In_ PUNICODE_STRING DriverServiceName);
DriverServiceName [in] Pointer to a counted Unicode string that specifies a
path to the driver's registry key,
\Registry\Machine\System\CurrentControlSet\Services\DriverName, where
DriverName is the name of the driver.
The DriverServiceName parameter is a pointer to a registry location. Under
the “DriverName” key, there should be at least the following two values:
+ ImagePath - A string in the format “\??\C:\path\to\driver.sys”
+ Type - A DWORD that should be set to “1”
Notice the “DriverServiceName” parameter format supposedly (according to
the documentation) must begin with “\Registry\Machine” which is a reference
to the HKLM registry key for which we are assuming we do not have access.
In order to circumvent this obstacle, we can actually use a path that
points to HKCU (HKEY_CURRENT_USER) instead such as
“\Registry\User\S-1-5-21-582075628-3447520101-2530640108-1003\”. Here the
numeric ID in the string is the RID for our current user. We must do this
because the kernel doesn't know what HKCU is, as it's purely a userland
helper that points into the HKLM hive.
Since “System\CurrentControlSet\Services\DriverName” does not exist under
this path we must create it, which we can do since it is the current user's
registry hive. The format for loading a simple driver is fairly
straightforward. We must, at minimum, define two things:
+ REG_DWORD Type
+ REG_SZ ImagePath
The type defines the service, as listed in wdm.h:
#define SERVICE_KERNEL_DRIVER 0x00000001
#define SERVICE_FILE_SYSTEM_DRIVER 0x00000002
#define SERVICE_ADAPTER 0x00000004
#define SERVICE_RECOGNIZER_DRIVER 0x00000008
[....]
In our case, we'll use the SERVICE_KERNEL_DRIVER type. Once these values
are set, we can then invoke NtLoadDriver with a path to our registry key
and load the driver into the kernel.
There are several other strategies that can be used to load kernel mode
drivers, such as the FltMgr or condrv key, but these cannot be used without
administrative privileges in the first place.
------ [ 3.1.8 - SeTakeOwnershipPrivilege
This privilege is, offensively, similar to SeRestorePrivilege. According
to MSDN, it allows a process to “take ownership of an object without being
granted discretionary access” by granting the WRITE_OWNER access right.
Exploiting this privilege is very similar to SeRestorePrivilege, except we
first need to take ownership of the registry key we want to write to.
Taking ownership of the registry key requires building an ACL with updated
ownership, then modifying the DACL so that we can write into it. Building
an ACL requires the construction of an EXPLICIT_ACCESS object:
ea[0].grfAccessPermissions = KEY_ALL_ACCESS;
ea[0].grfAccessMode = SET_ACCESS;
ea[0].grfInheritance = SUB_CONTAINERS_AND_OBJECTS_INHERIT;
ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID;
ea[0].Trustee.TrusteeType = TRUSTEE_IS_USER;
ea[0].Trustee.ptstrName = (LPTSTR)user->User.Sid; // owner
We can then use SetEntriesInAcl to build the ACL object. Once the ACL
object is composed, we take ownership of the registry path:
SetNamedSecurityInfo(
_TEXT("MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion
\\Image File Execution Options"),
SE_REGISTRY_KEY,
OWNER_SECURITY_INFORMATION,
user->User.Sid,
NULL, NULL, NULL);
Once we own the registry key, we make one last call to SetNamedSecurityInfo
to enable the previously composed ACL and gain write privileges to the
entry:
SetNamedSecurityInfo(_TEXT("MACHINE\\SOFTWARE\\Microsoft\\Windows
NT\\CurrentVersion\\Image File Execution Options"),
SE_REGISTRY_KEY, // type of object
DACL_SECURITY_INFORMATION, // change DACL
NULL, NULL, // do not change owner or group
pACL, // DACL specified
NULL);
At this point we can follow the same steps taken via the SeRestorePrivilege
method, making sure to restore registry ownership once we're done.
Much like SeRestorePrivilege, we can also take control of critical system
files or folders to abuse DLL load order or other such techniques.
------ [ 3.1.9 - SeDebugPrivilege
SeDebugPrivilege is very powerful, it allows the holder to debug another
process, this includes reading and writing to that process' memory. This
privilege has been widely abused for years by malware authors and exploit
developers, and therefore many of the techniques that one would use to gain
EoP through this privilege will be flagged by modern endpoint protection
solutions.
There are a host of various memory injection strategies that can be used
with this privilege that evade a majority of AV/HIPS solutions. Finding
these is left as an exercise for the reader.
----[ 3.2 - Exploiting Partial Writes
With an understanding of how individual privileges can be exploited, we can
now begin to demonstrate how and why these might be useful to us. Case
studies will be discussed further in section 4.
This technique was born from attempts at evading various kernel and
userland mitigations via partial write exploits. To establish a familiar
vernacular, a partial write is an instruction in which the destination is
controlled, but the value written may not be. Or the value may be a single
bit or byte modification, such as a decrement or addition. Take for
example a DEC operation; if the destination address can be controlled, then
we've got the ability to arbitrarily decrement any address by one. The OR
instruction is another example; while we may control the destination in
which we perform the OR operation, we might not control the value in which
we OR it with: OR DWORD PTR[controlled], 4.
Commonly, exploiting these would require modifying object fields, such as a
length, in order to obtain an arbitrary or partial read/write primitive.
Others may make modifications to page tables or mangle other kernel mode
data structures. These are all pieces to a larger exploit chain that
generally ends in NULLing an ACL, swapping process tokens, or executing a
privileged usermode process. Privileged execution (see:payload) changes
with the mitigation landscape.
Instead of trying to execute ROP in the kernel or mangle kernel objects,
why not delegate the escalation of privileges to userland? Much like the
process swapping strategy, if we can enable elevated privileges in
userland, we stand to benefit from increased reliability, trivial
sidestepping of various kernel mitigations, and increased flexibility in
the deliverance of some malicious payload. We want to persist on a system
unimpeded, and to do so we must remain clandestine.
As discussed in section 1.2, each token has a _SEP_TOKEN_PRIVILEGES
structure containing what privileges a token currently holds. Our path
towards elevation should be pretty obvious: by abusing a partial write and
a userland information leak, we can flip a few bits in our process token
and obtain access to administrative privileges. Because we often cannot
control the value being written, it's important that all possible values
allow for elevation. We've identified three bytes in which do not grant
administrative privileges: 0x00, 0x40, 0x41. That is, if you were to
obtain a MOV BYTE PTR[controlled], 0x41 primitive, you would not be able to
enable an exploitable privilege. This assumes a MOV operation on default
privilege bitmasks; other operations or combinations of privileges may
grant elevated privileges. All other values provide access to an
exploitable privilege.
In order to target this process token, we must be able to obtain the
address of a controlled processes token. As we're targeting privilege
escalation vulnerabilities with this strategy, we can use and continue to
use (as of v1703) the common NtQuerySystemInformation API to leak our
process token address, as demonstrated below:
NtQuerySystemInformation(16, bHandleInfo,
sizeof(bHandleInfo),
&BytesReturned));
PSYSTEM_HANDLE_INFORMATION shiHandleInfo =
(PSYSTEM_HANDLE_INFORMATION)bHandleInfo;
PSYSTEM_HANDLE_TABLE_ENTRY_INFO hteCurrent= &shiHandleInfo>Handles[0];
for (i = 0; i<shiHandleInfo>NumberOfHandles; hteCurrent++, i++) {
if(hteCurrent>UniqueProcessId == dwPid &&
hteCurrent>HandleValue == (USHORT)hToken)
return hteCurrent>Object;
}
The dwPid value is the process in which the token exists, and the hToken is
a handle to the process token. Note that, as of Windows 8.1 this strategy
no longer works under Low integrity processes, and thus other methods will
need to be employed. For brevity and clarity, we will work under the
assumption that we're targeting privilege escalation from a Medium
integrity process (default IL for users) and rely on the above technique.
----[ 3.3 - Abusing Existing Service Accounts
In addition to being useful for local privilege escalation through
arbitrary write primitives in the kernel, these same techniques can be of
use in the more common scenario where an attacker is accessing the machine
as a local service account.
There are a number of common scenarios where an attacker is able to execute
code in the context of a service account on a target machine, including the
following:
+ The service itself is compromised through some vulnerability. Typical
scenarios include web application vulnerabilities which allow execution
in the context of the account running IIS, and SQL injection
vulnerabilities where XP_CMDSHELL can be used to run code in the
context of the SQL service account.
+ Service account credentials are leaked in some way.
+ Kerberoast style attacks. A Kerberos ticket is requested for the target
account from the domain controller. Part of this ticket is encrypted
using the target account's password hash. This can be efficiently
cracked offline to yield the account password.
In any of these scenarios, if the service account happens to have one of
the privileges outlined in the previous section, it is possible to gain
local privilege escalation simply by leveraging the corresponding module
from this project.
----[ 3.3.1 - Common Service Accounts
In this section, we will give brief examples of some common service
accounts that can be abused for EoP due to their default token privileges.
----[ 3.3.1.2 - MSSQL / IIS
If we examine the default privileges assigned to the MSSQL and IIS service
accounts using the “AccessChk” tool from Sysinternals, we find the
following:
+ IIS - SeImpersonatePrivilege - BUILTIN\IIS_IUSRS
+ MSSQL - SeAssignPrimaryTokenPrivilege -
NT SERVICE\SQLAgent$SQLEXPRESS,
NT SERVICE\MSSQLLaunchpad$SQLEXPRESS,
NT SERVICE\MSSQL$SQLEXPRESS
These privileges are sufficient for EoP by leveraging the modules in this
project. Compromise of these accounts is a very common penetration testing
scenario. Any time SQL injection in MSSQL, or a web application
vulnerability in IIS is exploited to gain command execution, the attackers
end up with these privileges. Traditionally, this was considered a limiting
scenario with a restricted local account and an attacker would need to
resort to another method for EoP. Using the techniques outlined in this
paper, it is simply a matter of abusing existing token privileges.
----[ 3.3.1.3 - Backup Products
Every commercial backup product on the market will run with some sort of
elevated privilege. In many cases, the backup service account will run with
SYSTEM privileges, making EoP unnecessary. Where administrators have
started to smarten up however, we are starting to see the privileges on
these accounts become more restricted.
The following are the minimum privileges required by the Veritas NetBackup
solution, shamelessly borrowed from their website
(https://www.veritas.com/support/en_US/article.TECH36718):
+ *Act as a part of Operating System ( Only for Windows Server 2000 ).
+ *Create a token object.
+ Log on as a service.
+ Logon as a batch job.
+ Manage auditing and security log.
+ *Backup files and directories.
+ *Restore files and directories.
Note the 4 items in the list that we've marked with an asterisk (*). Any
one of these privileges alone can be leveraged for EoP given one of the
techniques described in this project.
----[ 3.3.1.4 - Local Service Accounts
There are also pre-defined service accounts on every Windows machine that
contain privileges that can be leveraged for EoP. These are
“NT AUTHORITY\SERVICE”, “NT AUTHORITY\NETWORK SERVICE”, and
“NT AUTHORITY\LOCAL SERVICE”.
Each of these has slightly different privileges, some contain multiple
exploitable privileges, however they all have access to the exploitable
SeImpersonatePrivilege.
If an attacker is somehow able to gain access to the system under the
context of one of these limited local accounts, they can trivially elevate
their privileges to “NT AUTHORITY\SYSTEM” using the techniques outlined
above.
--[ 4 - Kernel Exploit Development Case Studies
We'll now detail several case studies that demonstrate how and why we may
want to delegate privileged exploitation to userland. Note that our
strategy applies towards elevation of privilege; it cannot, in its current
state, be used remotely.
----[ 4.1 - MS16-135
MS16-135 is the bug that we first wrote an exploit for using this strategy,
and proves to be a fantastic case study. Initially released by Google
after identifying active exploitation in the wild [13], a trigger was
quickly released and the race for weaponization began.
One of the first public demonstrations of exploitability was by Enrique
Nissim at Zero Nights 2016 [14], in which he used the bug to demonstrate
PML4 randomization weaknesses. Several other proof of concepts followed in
suit, most abusing the PML4 strategy, others using the pvscan0 technique.
The bug can be triggered via SetWindowLongPtr with a specifically crafted
window and index value. The result is a controlled OR operation: OR DWORD
PTR[controlled], 4. The first step is identifying the address of
_SEP_TOKEN_PRIVILEGES. This can be accomplished using the following:
OpenProcessToken(OpenProcess(PROCESS_QUERY_INFORMATION,
1, GetParentProcessId()),
TOKEN_QUERY | TOKEN_QUERY_SOURCE,
¤t_token);
dwToken = (UINT)current_token & 0xffff;
_TOKEN = GetHandleAddress(GetCurrentProcessId(), dwToken);
startTokenOffset = (UINT)_TOKEN + 0x40;
We first open a handle to our parent process token, then use a
NtQuerySystemInformation wrapper function to fetch the actual address of
the token. The structure we're after lies 0x40 bytes ahead. Note that the
NtQuerySystemInformation leak only works from medium integrity processes on
Windows 8.1+. In order to exploit this from a low integrity process, we
will need to abuse a different leak [9].
With the token address, we now adjust the offset to the enabled bitmask and
invoke:
ULONG enabled_create_token = startTokenOffset + 0xa;
SetWindowLongPtr(childWnd, GWLP_ID, (LONG)(enabled_create_token - 0x14));
Should we trigger this bug multiple times while shifting the offset, we can
hit each byte in the Enabled bitmask. This enables the following
privileges:
02 0x000000002 SeCreateTokenPrivilege Attributes - Enabled
10 0x00000000a SeLoadDriverPrivilege Attributes - Enabled
18 0x000000012 SeRestorePrivilege Attributes - Enabled
23 0x000000017 SeChangeNotifyPrivilege Attributes - Enabled
As we've seen, three of the four privileges enabled are trivially
exploitable. Our proof of concept code opts to abuse the
SeRestorePrivilege, and can be found in the project git repository [3].
Though many of the public exploits for this bug were written in such a way
to demonstrate a technique, we find that the presented example highlights
the simplicity and reliability of our strategy: it relies only on the
external need to leak a token address from the kernel. Public proof of
concepts are much more complicated and require a variety of primitive
grooming and kernel dancing.
----[ 4.2 - MS15-061
This was another fantastic bug observed in the wild during the RussianDoll
campaigns and was quickly reversed and weaponized within the community.
Much like MS16-135, this is a partial write that allows for the decrement
of a controlled address. The bug was a use after free in win32k,
specifically yet another issue with usermode callbacks emanating from
within win32k [15].
Our strategy here does not differ much from the example in 4.1: we identify
our token address, groom the heap to obtain our arbitrary decrement, and
trigger it a few times to cover the Enabled and Present bitmasks. This
enables a whole host of different privileges, namely:
07 0x000000007 SeTcbPrivilege Attributes - Enabled
09 0x000000009 SeTakeOwnershipPrivilege Attributes - Enabled
10 0x00000000a SeLoadDriverPrivilege Attributes - Enabled
17 0x000000011 SeBackupPrivilege Attributes - Enabled
18 0x000000012 SeRestorePrivilege Attributes - Enabled