33.在x86_64中不用call指令修改返回地址调用函数
在x86_64中不用call指令修改返回地址调用函数
修改返回地址调用函数hello
准备环境vs2022。
start.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;assembly.asm
extrn hello : PROC
.code
funnormal PROC; 正常调用函数
; shadow space 32 bytes
sub rsp, 40 ; 保持16字节对齐 + 预留 shadow space
mov ecx, 123 ; RCX = 第一个参数(Windows x64 ABI)
call hello ; 正常调用函数
add rsp, 40
xor eax, eax ; return 0
ret
funnormal ENDP
vulnerable PROC;修改返回地址调用函数
push rbp
mov rbp, rsp
; return address stored at [rbp+8]
; we replace it with address of hello
mov rax, QWORD PTR [rbp+8];
mov rcx, 321 ; 设置第一个参数
lea rdx, hello
mov QWORD PTR [rbp+8], rdx
pop rbp
ret
vulnerable ENDP
main proc
sub rsp, 40 ; 保持16字节对齐 + 预留 shadow space
call vulnerable ; 正常调用函数
add rsp, 40
ret
main endp
endhello.asm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20;assembly.asm
includelib ucrt.lib
includelib legacy_stdio_definitions.lib
extern printf:proc
.data
Format db '%d',0
.code
hello PROC
; RCX = int x (hello 的第一个参数)
; printf 可以用 C 版本,不必写 asm
sub rsp,28h
mov rdx,rcx
lea rcx,Format
call printf
add rsp,28h
ret
hello ENDP
END
修改返回地址调用函数CreateRemoteThread注入代码
- MessageBoxA在重启前的每个64位程序中的地址相同,可以在本地址得到弹窗函数地址后覆盖shellcode中间预留的8个字节的占位符(比如全
0xAA)。
准备环境vs2022
main.c
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
// 声明汇编函数
// 注意:我们需要传入所有的 7 个参数
extern HANDLE vulnerable_CRT(
HANDLE hProcess, // RCX
LPSECURITY_ATTRIBUTES lpAttr, // RDX
SIZE_T dwStackSize, // R8
LPTHREAD_START_ROUTINE lpAddr,// R9
LPVOID lpParam, // [Stack]
DWORD dwFlags, // [Stack]
LPDWORD lpThreadId // [Stack]
);
void InjectCode(DWORD dwProcId, LPVOID mFunc)
{
HANDLE hProcess, hThread;
LPVOID mFuncAddr;
DWORD NumberOfByte;
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcId);
mFuncAddr = VirtualAllocEx(hProcess, NULL, 1024, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
WriteProcessMemory(hProcess, mFuncAddr, mFunc, 1024, &NumberOfByte);
// --- 唯独这里改为汇编跳转方式 ---
hThread = vulnerable_CRT(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)mFuncAddr, NULL, 0, NULL);
WaitForSingleObject(hThread, INFINITE);
VirtualFreeEx(hProcess, mFuncAddr, 1024, MEM_RELEASE);
CloseHandle(hThread);
CloseHandle(hProcess);
}
void InjectCodec(DWORD dwProcId, LPVOID mFunc)
{
HANDLE hProcess, hThread;
LPVOID mFuncAddr, ParamAddr;
SIZE_T NumberOfByte;
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcId);
mFuncAddr = VirtualAllocEx(hProcess, NULL, 1024, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
WriteProcessMemory(hProcess, mFuncAddr, mFunc, 1024, &NumberOfByte);
hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)mFuncAddr, NULL, 0, &NumberOfByte);
WaitForSingleObject(hThread, INFINITE);
VirtualFreeEx(hProcess, mFuncAddr, 1024, MEM_RELEASE);
CloseHandle(hThread);
CloseHandle(hProcess);
}
// msg: "Hello", title: "Hi"
// 经过严格计算偏移的 Shellcode
unsigned char shellcode[] = {
0x55, // push rbp
0x48, 0x89, 0xE5, // mov rbp, rsp
0x48, 0x83, 0xEC, 0x30, // sub rsp, 30h (足够大的影子空间)
0x48, 0x83, 0xE4, 0xF0, // and rsp, -16 (对齐)
0x48, 0x31, 0xC9, // xor rcx, rcx
0x48, 0x8D, 0x15, 0x18, 0x00, 0x00, 0x00, // lea rdx, [rip+24] -> "Hello"
0x4C, 0x8D, 0x05, 0x18, 0x00, 0x00, 0x00, // lea r8, [rip+24] -> "Hi"
0x45, 0x31, 0xC9, // xor r9d, r9d
0x48, 0xB8, // mov rax, 0xAAAAAAAAAAAAAAAA
0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
0xFF, 0xD0, // call rax
0x48, 0x89, 0xEC, // mov rsp, rbp (恢复原始栈指针)
0x5D, // pop rbp
0xC3, // ret
'H', 'e', 'l', 'l', 'o', 0x00,
'H', 'i', 0x00
};
void PrepareShellcode(unsigned char* sc, size_t scSize)
{
// 1. 获取当前系统真实的 MessageBoxA 地址
// 注意:在所有 64 位进程中,这个地址在本次开机期间是固定的
void* realAddr = (void*)GetProcAddress(LoadLibraryA("user32.dll"), "MessageBoxA");
if (realAddr == NULL) {
printf("Error: Could not find MessageBoxA. Is user32.dll loaded?\n");
return;
}
printf("[+] Found MessageBoxA at: %p\n", realAddr);
// 2. 在 shellcode 数组中寻找占位符 0xAAAAAAAAAAAAAAAA 并替换
// 我们步进查找,确保不会越界
BOOL found = FALSE;
for (size_t i = 0; i < scSize - 8; i++)
{
// 检查连续的 8 个字节是否为我们的占位符
if (*(unsigned __int64*)(&sc[i]) == 0xAAAAAAAAAAAAAAAA)
{
*(unsigned __int64*)(&sc[i]) = (unsigned __int64)realAddr;
found = TRUE;
printf("[+] Placeholder replaced at offset: %zu\n", i);
break;
}
}
if (!found) {
printf("[-] Warning: Placeholder 0xAAAAAAAAAAAAAAAA not found in shellcode!\n");
}
}
int main()
{
PrepareShellcode(shellcode, sizeof(shellcode)); // 别忘了先替换地址
InjectCode(58344, shellcode);
return 0;
}vulnerable.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
37includelib ucrt.lib
includelib legacy_stdio_definitions.lib
extern CreateRemoteThread : proc
.code
vulnerable_CRT PROC
; 进入时:
; RCX, RDX, R8, R9 已经由 C 语言填好了前 4 个参数
; 此时栈顶 [rsp] 是返回到 InjectCode 的地址
push rbp
mov rbp, rsp
; --- 关键:手动处理第 5, 6, 7 个参数 ---
; C 语言调用本函数时,已经把 5, 6, 7 参数放到了它的影子空间上方
; 我们需要把它们挪到“发给 CreateRemoteThread 的影子空间”上方
; 1. 获取 C 语言传入的后三个参数(位于本函数的栈帧之上)
mov rax, qword ptr [rbp + 30h] ; 参数 5 (lpParam)
mov r10, qword ptr [rbp + 38h] ; 参数 6 (dwFlags)
mov r11, qword ptr [rbp + 40h] ; 参数 7 (lpThreadId)
; 2. 将它们布置在返回地址 [rbp+8] 之后的位置
; 栈布局应该是:[返回地址] [影子空间32字节] [参数5] [参数6] [参数7]
mov qword ptr [rbp + 28h], rax ; 第 5 参数放到 [rbp+40] 左右
mov qword ptr [rbp + 30h], r10 ; 第 6 参数
mov qword ptr [rbp + 38h], r11 ; 第 7 参数
; --- 劫持返回地址 ---
lea rax, CreateRemoteThread
mov qword ptr [rbp + 8], rax
; 恢复栈帧并跳转
pop rbp
ret
vulnerable_CRT ENDP
END
All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.
