代码注入

之前学习了DLL注入,实际上让进程执行我们自己的代码还有另一种方式,就是代码注入。
顾名思义,将代码注入进程,然后创建远程线程去执行。

和DLL注入的区别很明显:DLL注入是通过LoadLibrary自动执行DLL内的初始化函数从而实现执行我们的函数,而代码注入则是直接申请空间并写入函数机器码及对应传参,最后用CreateRemoteThread将函数首地址和传参首地址做参数来执行。

具体流程:

  • 获取目标进程句柄
  • 将参数打包为结构体
  • 将结构体写入目标进程
  • 将需要执行的函数写入目标进程
  • 创建远程线程执行

按书上的直接写结果会崩溃,调试了一下发现因为有跳转表之类的东西,所以地址不对应
在项目-属性-链接器-增量链接-禁用后
项目-属性-C/C++-代码生成-安全检查-禁用
项目-属性-C/C++-优化-内联函数扩展-禁用
项目-属性-C/C++-常规-支持仅我的代码调试-禁用

以上几个禁用后,可以在debug配置下缩小代码规模和防止出现奇怪地址的跳转

同时似乎函数位置不一定和写代码的时候的位置一样,所以要看一下

然后是导入表的问题,如果是直接使用库函数,会直接跳转到导入表条目,所以不能在代码中直接使用库函数,必须取得后传地址过去。

实践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
#include<iostream>
#include<Windows.h>

struct Data {
DWORD64 pLoadLibraryA;
DWORD64 pGetProcAddress;
DWORD64 pMassage;
}data;




LPVOID writeMemory(
HANDLE hProcess,
PVOID pReadyToWrite,
DWORD size)
{
LPVOID pMassage = VirtualAllocEx(hProcess, NULL, size, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (!pMassage)
{
std::cerr << "申请内存失败" << std::endl;
return 0;
}
size_t bytewritten;
WriteProcessMemory(hProcess, pMassage, pReadyToWrite, size, &bytewritten);
if (bytewritten != size)
{
std::cerr << "写入字节失败" << std::endl;
return 0;
}
std::cout << "[+] 写入成功" << std::endl;

return pMassage;
}





void code() {
MessageBoxA(NULL, "hello", "SUCCESS", NULL);
}




void targetCode(Data* data) {
char user32[] = { 'u', 's', 'e', 'r', '3', '2', '.', 'd', 'l', 'l', '\0' };
char messageBox[] = { 'M', 'e', 's', 's', 'a', 'g', 'e', 'B', 'o', 'x', 'A', '\0' };
HMODULE hModule = ((HMODULE(*)(char*))(data->pLoadLibraryA))(user32);
if (!hModule)
{
return;
}
LPVOID pMessagebox = ((FARPROC(*)(HMODULE, LPCSTR))(data->pGetProcAddress))(hModule, messageBox);
((int (*)(HWND, LPCSTR, LPCSTR, UINT))pMessagebox)(NULL, (char*)(data->pMassage), (char*)(data->pMassage), MB_OKCANCEL);
}


//获取目标句柄
//申请空间,写入参数
//申请空间写入函数
//远程线程执行


void injectCode(DWORD pid) {
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);

char message[] = "SUCCESS";
LPVOID pMessage = writeMemory(hProcess, message, sizeof(message) + 1);


data.pMassage = (DWORD64)pMessage;
data.pGetProcAddress = (DWORD64)GetProcAddress(GetModuleHandleA("kernel32"), "GetProcAddress");
data.pLoadLibraryA = (DWORD64)GetProcAddress(GetModuleHandleA("kernel32"), "LoadLibraryA");

LPVOID pData = writeMemory(hProcess, &data, sizeof(data) + 1);

uintptr_t funcSize = (uintptr_t)writeMemory - (uintptr_t)targetCode;

LPVOID pfunc = writeMemory(hProcess, (PVOID)targetCode, funcSize);


HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pfunc, pData, NULL, NULL);
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
CloseHandle(hProcess);

}



int main(int argc, char* argv[]) {
DWORD pid;
std::cin >> pid;

//pid = atol(argv[1]);
injectCode(pid);
return 0;
}

其中targetCode是写入进程的函数,
uintptr_t funcSize = (uintptr_t)writeMemory - (uintptr_t)targetCode;的原因是我编译之后看了一下反编译结果,发现是这两个函数挨在一起,那么首地址相减就是大小了。

实践ASM

实际上,一般来说,代码注入都是注入汇编代码,因为汇编代码可以更方便地和寄存器,内存空间进行交互

如果要直接用汇编来完成操作,步骤为:

将执行的函数写为shellcode,然后创建远程线程执行,这里可以直接把数据和代码写在一起传递

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
from keystone import *
from prism import *

code = '''
jmp code;
user32:
.asciz "user32.dll";
messageboxa:
.asciz "MessageBoxA";
message:
.asciz "SUCCESS";
pLoadLibraryA:
.quad 0x00007FFD20BC9460;
pGetProcessAddress:
.quad 0x00007FFD20BC3C30;
code:
push 0;
lea rcx,[rip + rip - user32];
mov rax,[rip + rip - pLoadLibraryA];
call rax;
mov rcx,rax;
lea rdx,[rip + rip - messageboxa];
mov rax,[rip + rip - pGetProcessAddress];
call rax;
lea rbx,[rip + rip - message];
mov r9,1;
mov r8,rbx;
mov rdx,rbx;
mov rcx, 0;
call rax;
ret;
'''


def pasm(code_list):
code_str = ''
code_str += '__asm{\n'
for num in code_list:
code_str += f"_emit 0x{hex(num)[2:].zfill(2)};\n"
code_str += '}'
print(code_str)



ks = Ks(KS_ARCH_X86, KS_MODE_64)
outcode,count = ks.asm(code)

phex(outcode)
# pasm(outcode)

这段汇编总能出现一些奇怪的bug,要注意栈平衡和调用函数时栈最后一位为0,lea和mov什么时候传值什么时候传地址,以及x64下的基本调用方式。不管我写没写后面的pop,程序都会崩溃不知道为什么,但是messagebox确实弹出了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void injectCodeAsm(DWORD pid) {
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);

BYTE code[] = { 0xeb, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x33, 0x32, 0x2e, 0x64, 0x6c, 0x6c, 0x00, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x42, 0x6f, 0x78, 0x41, 0x00, 0x53, 0x55, 0x43, 0x43, 0x45, 0x53, 0x53, 0x00, 0x60, 0x94, 0xbc, 0x20, 0xfd, 0x7f, 0x00, 0x00, 0x30, 0x3c, 0xbc, 0x20, 0xfd, 0x7f, 0x00, 0x00, 0x6a, 0x00, 0x48, 0x8d, 0x0d, 0xc8, 0xff, 0xff, 0xff, 0x48, 0x8b, 0x05, 0xe0, 0xff, 0xff, 0xff, 0xff, 0xd0, 0x48, 0x89, 0xc1, 0x48, 0x8d, 0x15, 0xc0, 0xff, 0xff, 0xff, 0x48, 0x8b, 0x05, 0xd5, 0xff, 0xff, 0xff, 0xff, 0xd0, 0x48, 0x8d, 0x1d, 0xbc, 0xff, 0xff, 0xff, 0x49, 0xc7, 0xc1, 0x01, 0x00, 0x00, 0x00, 0x49, 0x89, 0xd8, 0x48, 0x89, 0xda, 0x48, 0xc7, 0xc1, 0x00, 0x00, 0x00, 0x00, 0xff, 0xd0, 0xc3 };
LPVOID pcode = writeMemory(hProcess, code, sizeof(code));

HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pcode, NULL, NULL, NULL);
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
CloseHandle(hProcess);
}

int main(int argc, char* argv[]) {
DWORD pid;
std::cin >> pid;


//pid = atol(argv[1]);
injectCodeAsm(pid);
return 0;
}