Windows x64 ShellCode入门教程 05
# Windows x64 ShellCode入门教程 05
在这篇文章中,我们要做如下事情:
顺便说一句,我们的代码不会包含任何NULL值。
- 定位Kernel32模块并收集PE导出表信息
- 动态定位GetProcAddress函数
- 使用GetProcAddress的句柄来定位LoadLibraryA函数的地址
- 使用GetProcAddress的句柄来定位ExitProcess函数
- 使用LoadLibraryA函数的句柄加载user32.dll
- 使用user32.dll的句柄,通过GetProcAddress在user32.dll中定位MessageBoxA的地址
- 弹出messagebox消息框
- 调用ExitProcess以正常退出程序
这些内容是我们在前面的系列文章中承诺给读者的。
在深入探讨之前,我想指出在重新学习x64汇编时发现的一点。也许只是我的个人感受,但在查看Windows 11的PE导出表时,名称索引的地址似乎与函数地址是对应的。这样一来,我们就不再需要借助名称索引的地址来获取序号了。我可能搞错了,但我在两台不同的机器上的Windows 11系统上进行了测试,结果都是一样的。我完全跳过了序号查找这一步,直接将从名称查找地址中获取的索引代入函数查找地址,并没有出现任何问题。这值得思考一下。也许这一点应该由比我更了解Windows内部机制的人来进一步详细阐述。先把这个小想法放在一边,我们继续往下说。
今天的代码将是本系列中迄今为止最具挑战性的,所以先提前提醒一下。内容很多,但我相信你能做好。我会借鉴上面的要点,将其分成几个部分来讲解。
# 序言 - 定位Kernel32并收集PE导出表信息
;nasm -fwin64 [x64findkernel32.asm]
;ld -m i386pep -o x64findkernel32.exe x64findkernel32.obj
BITS 64
SECTION .text
global main
main:
sub rsp, 0x28
and rsp, 0xFFFFFFFFFFFFFFF0
xor rcx, rcx ; RCX = 0
mov rax, [gs:rcx + 0x60] ; RAX = PEB
mov rax, [rax + 0x18] ; RAX = PEB->Ldr
mov rsi,[rax+0x10] ; PEB.Ldr->InLoadOrderModuleList
mov rsi, [rsi]
mov rsi,[rsi]
mov rbx, [rsi+0x30] ; kernel32.dll base address
mov r8, rbx ; mov kernel32.dll base addr into r8
xor rcx, rcx ; Avoid null bytes from mov edx,[rbx+0x88] by using rcx register to add
add cx, 0x88ff
shr rcx, 0x8 ; RCX = 0x88ff --> 0x88
mov edx, [rbx+rcx] ; EDX = [&NewEXEHeader + Offset RVA ExportTable] = RVA ExportTable
add rdx, r8 ; RDX = kernel32.dll + RVA ExportTable = ExportTable Address
mov r10d, [rdx+0x14] ; Number of functions
xor r11, r11 ; Zero R11 before use
mov r11d, [rdx+0x20] ; AddressOfNames RVA
add r11, r8 ; AddressOfNames VMA
mov rcx, r10 ; store number of functions for future use
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
# 动态定位GetProcAddress
mov rax, 0x9090737365726464 ; 'ddress'
shl rax, 0x10 ; 7373657264640000
shr rax, 0x10 ; 0000737365726464 terminate our string w/ no nulls present in our shellcode!
push rax
mov rax, 0x41636F7250746547 ; GetProcA
push rax
mov rax, rsp
kernel32findfunction: ; Loop over Export Address Table to find WinApi names
jecxz FunctionNameNotFound ; Loop around this function until we find GetProcAddress
xor ebx,ebx ; Zero EBX for use
mov ebx, [r11+rcx*4] ; EBX = RVA for first AddressOfName
add rbx, r8 ; RBX = Function name VMA / add kernel32 base address to RVA and get WinApi name
dec rcx ; Decrement our loop by one, this goes from Z to A
mov r9, qword [rax] ; R9 = "GetProcA"
cmp [rbx], r9 ; Compare first 8 bytes
jnz kernel32findfunction ; If not equal, continue loop
mov r9d, dword [rax + 8] ; R9 = "ddress"
cmp [rbx + 8], r9d ; Compare remaining part
jz FunctionNameFound ; If match, function found
jnz kernel32findfunction
FunctionNameNotFound:
int3
FunctionNameFound:
push rcx
pop r15 ; getprocaddress position
inc r15
xor r11, r11
mov r11d, [rdx+0x1c] ; AddressOfFunctions RVA
add r11, r8 ; AddressOfFunctions VMA in R11. Kernel32+RVA for addressoffunctions
mov eax, [r11+r15*4] ; Get the function RVA.
add rax, r8 ; Found the GetProcAddress WinApi!!!
push rax ; push GetProcAddress temporarily to be used by next segment
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
我真的希望不需要这么多行代码就能动态定位kernel32以及你最初的API,但看起来这种情况已经持续很久了。不过值得庆幸的是,代码的其他部分要短得多,也更容易理解,至少在我看来是这样。
# 使用GetProcAddress句柄找到LoadLibraryA的地址
; Prepare arguments for getting handle to LoadLibraryA:
pop r15 ; temporary use
mov r12, r15 ; save copy of GetProcAddress for future use
mov rdi, r8 ; make a copy of kernel32 base address for future use
mov rcx, r8 ; RCX = handle to kernel32.dll (first argument)
; Load "LoadLibraryA" onto the stack
mov rax, 0x41797261 ; aryA
push rax
mov rax, 0x7262694C64616F4C ; LoadLibr
push rax
mov rdx, rsp ; RDX points to "LoadLibraryA" (second argument)
sub rsp, 0x30 ; decimal 48 ( 3 x 16 bytes)
call r15 ; Call GetProcAddress
add rsp, 0x30 ; alignmnent/shadow space adjustments
mov r15, rax ; holds LoadLibraryA!
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 使用GetProcAddress句柄查找ExitProcess的地址
;getexitprocess
mov r14, r12 ; temporary assignment of GetProcess handle
mov rcx, rdi ; RCX = handle to kernel32.dll (first argument)
; Load "ExitProcess" onto the stack
mov rax, 0x90737365 ; 'ess'
shl eax, 0x8 ; 0000000073736500
shr eax, 0x8 ; 0000000000737365 terminate our string w/ no nulls present in our shellcode!
push rax
mov rax, 0x636F725074697845 ; ExitProc
push rax
mov rdx, rsp ; RDX points to "ExitProcess" (second argument)
sub rsp, 0x30
call r14 ; Call GetProcAddress
add rsp, 0x30
mov r14, rax ; holds ExitProcess!
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 使用LoadLibraryA句柄定位user32.dll
;locate user32.dll
mov rax, 0x90906C6C ; add "ll" string to RAX
shl eax, 0x10 ; 000000006C6C0000
shr eax, 0x10 ; 0000000000006C6C
push rax ; push RAX to stack
mov rax, 0x642E323372657375 ; Add "user32.d" string to RAX.
push rax ; Push RAX to stack
mov rcx, rsp ; Move a pointer to User32.dll into RCX.
sub rsp, 0x30
call r15 ; Call LoadLibraryA("user32.dll")
mov rdi, rax ; holds User32.dll address
2
3
4
5
6
7
8
9
10
11
# 使用user32.dll句柄和GetProcAddress定位MessageBoxA地址
; Prepare arguments for GetProcAddress for MessageBoxA:
mov rcx, rdi ; RCX = handle to user32.dll (first argument)
mov rax, 0x9041786F ; Load "oxA" into RAX
shl eax, 0x8 ; 0000000041786F00
shr eax, 0x8 ; 000000000041786F
push rax
mov rax, 0x426567617373654D ; Load "MessageB" into RAX
push rax
mov rdx, rsp ; RDX points to "MessageBoxA" (second argument)
sub rsp, 0x30
call r12 ; Call GetProcAddress
mov r15, rax ; store MessageBoxA
2
3
4
5
6
7
8
9
10
11
12
# 弹出MessageBox消息框
;messageboxfinally:
xor rcx, rcx ; hWnd = NULL (no owner window)
mov rax, 0x9090906D ; m, 0
shl eax, 24 ; 000000006D000000
shr eax, 24 ; 000000000000006D
push rax
mov rax, 0x3374737973743367 ; g3tsyst3
push rax
mov rdx, rsp ; lpText = pointer to message
mov r8, rsp ; lpCaption = pointer to title
xor r9d, r9d ; uType = MB_OK (OK button only)
sub rsp, 0x30
call r15 ; Call MessageBoxA
add rsp, 0x30
2
3
4
5
6
7
8
9
10
11
12
13
14
# 调用ExitProcess
;exitcleanly:
xor ecx, ecx
call r14 ;ExitProcess
2
3
就是这样!好了,现在我们继续获取我们的shellcode(机器码):

unsigned char shellcode[] =
"\x48\x83\xec\x28\x48\x83\xe4\xf0\x48\x31\xc9\x65\x48\x8b\x41\x60\x48\x8b\x40\x18\x48\x8b"
"\x70\x10\x48\x8b\x36\x48\x8b\x36\x48\x8b\x5e\x30\x49\x89\xd8\x8b\x5b\x3c\x4c\x01\xc3\x48"
"\x31\xc9\x66\x81\xc1\xff\x88\x48\xc1\xe9\x08\x8b\x14\x0b\x4c\x01\xc2\x44\x8b\x52\x14\x4d"
"\x31\xdb\x44\x8b\x5a\x20\x4d\x01\xc3\x4c\x89\xd1\x48\xb8\x64\x64\x72\x65\x73\x73\x90\x90"
"\x48\xc1\xe0\x10\x48\xc1\xe8\x10\x50\x48\xb8\x47\x65\x74\x50\x72\x6f\x63\x41\x50\x48\x89"
"\xe0\x67\xe3\x20\x31\xdb\x41\x8b\x1c\x8b\x4c\x01\xc3\x48\xff\xc9\x4c\x8b\x08\x4c\x39\x0b"
"\x75\xe9\x44\x8b\x48\x08\x44\x39\x4b\x08\x74\x03\x75\xdd\xcc\x51\x41\x5f\x49\xff\xc7\x4d"
"\x31\xdb\x44\x8b\x5a\x1c\x4d\x01\xc3\x43\x8b\x04\xbb\x4c\x01\xc0\x50\x41\x5f\x4d\x89\xfc"
"\x4c\x89\xc7\x4c\x89\xc1\xb8\x61\x72\x79\x41\x50\x48\xb8\x4c\x6f\x61\x64\x4c\x69\x62\x72"
"\x50\x48\x89\xe2\x48\x83\xec\x30\x41\xff\xd7\x48\x83\xc4\x30\x49\x89\xc7\x4d\x89\xe6\x48"
"\x89\xf9\xb8\x65\x73\x73\x90\xc1\xe0\x08\xc1\xe8\x08\x50\x48\xb8\x45\x78\x69\x74\x50\x72"
"\x6f\x63\x50\x48\x89\xe2\x48\x83\xec\x30\x41\xff\xd6\x48\x83\xc4\x30\x49\x89\xc6\xb8\x6c"
"\x6c\x90\x90\xc1\xe0\x10\xc1\xe8\x10\x50\x48\xb8\x75\x73\x65\x72\x33\x32\x2e\x64\x50\x48"
"\x89\xe1\x48\x83\xec\x30\x41\xff\xd7\x48\x89\xc7\x48\x89\xf9\xb8\x6f\x78\x41\x90\xc1\xe0"
"\x08\xc1\xe8\x08\x50\x48\xb8\x4d\x65\x73\x73\x61\x67\x65\x42\x50\x48\x89\xe2\x48\x83\xec"
"\x30\x41\xff\xd4\x49\x89\xc7\x48\x31\xc9\xb8\x6d\x90\x90\x90\xc1\xe0\x18\xc1\xe8\x18\x50"
"\x48\xb8\x67\x33\x74\x73\x79\x73\x74\x33\x50\x48\x89\xe2\x49\x89\xe0\x45\x31\xc9\x48\x83"
"\xec\x30\x41\xff\xd7\x48\x83\xc4\x30\x31\xc9\x41\xff\xd6";
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
将shellcode放到我们的C++程序中:
#include <windows.h>
#include <iostream>
// Shellcode (as given, formatted for clarity)
unsigned char shellcode[] =
"\x48\x83\xec\x28\x48\x83\xe4\xf0\x48\x31\xc9\x65\x48\x8b\x41\x60\x48\x8b\x40\x18\x48\x8b"
"\x70\x10\x48\x8b\x36\x48\x8b\x36\x48\x8b\x5e\x30\x49\x89\xd8\x8b\x5b\x3c\x4c\x01\xc3\x48"
"\x31\xc9\x66\x81\xc1\xff\x88\x48\xc1\xe9\x08\x8b\x14\x0b\x4c\x01\xc2\x44\x8b\x52\x14\x4d"
"\x31\xdb\x44\x8b\x5a\x20\x4d\x01\xc3\x4c\x89\xd1\x48\xb8\x64\x64\x72\x65\x73\x73\x90\x90"
"\x48\xc1\xe0\x10\x48\xc1\xe8\x10\x50\x48\xb8\x47\x65\x74\x50\x72\x6f\x63\x41\x50\x48\x89"
"\xe0\x67\xe3\x20\x31\xdb\x41\x8b\x1c\x8b\x4c\x01\xc3\x48\xff\xc9\x4c\x8b\x08\x4c\x39\x0b"
"\x75\xe9\x44\x8b\x48\x08\x44\x39\x4b\x08\x74\x03\x75\xdd\xcc\x51\x41\x5f\x49\xff\xc7\x4d"
"\x31\xdb\x44\x8b\x5a\x1c\x4d\x01\xc3\x43\x8b\x04\xbb\x4c\x01\xc0\x50\x41\x5f\x4d\x89\xfc"
"\x4c\x89\xc7\x4c\x89\xc1\xb8\x61\x72\x79\x41\x50\x48\xb8\x4c\x6f\x61\x64\x4c\x69\x62\x72"
"\x50\x48\x89\xe2\x48\x83\xec\x30\x41\xff\xd7\x48\x83\xc4\x30\x49\x89\xc7\x4d\x89\xe6\x48"
"\x89\xf9\xb8\x65\x73\x73\x90\xc1\xe0\x08\xc1\xe8\x08\x50\x48\xb8\x45\x78\x69\x74\x50\x72"
"\x6f\x63\x50\x48\x89\xe2\x48\x83\xec\x30\x41\xff\xd6\x48\x83\xc4\x30\x49\x89\xc6\xb8\x6c"
"\x6c\x90\x90\xc1\xe0\x10\xc1\xe8\x10\x50\x48\xb8\x75\x73\x65\x72\x33\x32\x2e\x64\x50\x48"
"\x89\xe1\x48\x83\xec\x30\x41\xff\xd7\x48\x89\xc7\x48\x89\xf9\xb8\x6f\x78\x41\x90\xc1\xe0"
"\x08\xc1\xe8\x08\x50\x48\xb8\x4d\x65\x73\x73\x61\x67\x65\x42\x50\x48\x89\xe2\x48\x83\xec"
"\x30\x41\xff\xd4\x49\x89\xc7\x48\x31\xc9\xb8\x6d\x90\x90\x90\xc1\xe0\x18\xc1\xe8\x18\x50"
"\x48\xb8\x67\x33\x74\x73\x79\x73\x74\x33\x50\x48\x89\xe2\x49\x89\xe0\x45\x31\xc9\x48\x83"
"\xec\x30\x41\xff\xd7\x48\x83\xc4\x30\x31\xc9\x41\xff\xd6";
int main() {
// Allocate executable memory
void* exec_mem = VirtualAlloc(0, sizeof(shellcode), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (exec_mem == nullptr) {
std::cerr << "Memory allocation failed\n";
return -1;
}
// Copy shellcode to the allocated memory
memcpy(exec_mem, shellcode, sizeof(shellcode));
// Create a function pointer to the shellcode
auto shellcode_func = reinterpret_cast<void(*)()>(exec_mem);
// Execute the shellcode
shellcode_func();
// Free the allocated memory
VirtualFree(exec_mem, 0, MEM_RELEASE);
return 0;
}
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
执行效果:

虽然这看起来像是一段不必要的大量代码,但从整体来看还不算太糟。我在Notepad++中显示的代码总共有132行。如果我们不需要考虑空值的话,代码会少很多。但是,对于加载一个消息框所需的代码量来说,这真的不算多了😸 不过,当所有部分都整合在一起时,那种感觉确实很棒。接下来是套接字和反向shell的内容!请继续关注,感谢阅读。我真的很感谢大家的支持以及从这些帖子中提出的问题。请继续提问,我们很快再见!