代码注入

1、代码注入

代码注入是一种向目标进程插入独立运行代码并使之运行的技术,它一般调用CreateRemoteThread() API以远程线程形式运行插入的代码,所以也被称为线程注入。原理如图所示:
8vx46e.png
首先向目标进程插入代码于=与数据,在此过程中,代码以线程过程(Thread Procedure)形式插入,二代码中使用的数据则以线程参数的形式传入。也就是说,代码与数据是分别注入的。
使用代码注入的原因:
1、占用内存少
如果要注入的代码与数据较少,那么就不需要讲它们做成DLL的形式注入。采用直接代码注入的方式同样能够获得与DLL注入相同的效果,且占用的内存会更少。
2、难以查找痕迹
采用DLL注入方式会在目标进程的内存中留下相关的痕迹,采用代码注入方式几乎不会留下任何痕迹。
DLL注入技术主要用在代码量大且复杂的时候,而代码注入技术则适合用于代码量较少的情况。

2、注入示例

1、运行notepad.exe
首先运行notepad.exe,然后查看进程的PID,如图所示:
8xkgk4.png
2、运行CodeInjection.exe
在命令行窗口输入命令与参数(notepad.exe的PID),回车运行:
8xAHK0.png
3、弹出消息框
notepad.exe进程中弹出一个消息框,如图所示:
8xEeGd.png

3、代码

1、main()函数
首先看一下main()函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int main(int argc, char *argv[])
{
DWORD dwPID = 0;

if( argc != 2 )
{
printf("\n USAGE : %s <pid>\n", argv[0]);
return 1;
}

// change privilege
if( !SetPrivilege(SE_DEBUG_NAME, TRUE) )
return 1;

// code injection
dwPID = (DWORD)atol(argv[1]);
InjectCode(dwPID);

return 0;
}

main()函数用来调用InjectCode()函数,传入的函数参数为目标进程的PID。

2、ThreadProc()函数
该函数为注入目标进程的代码(线程函数)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
DWORD WINAPI ThreadProc(LPVOID lParam)
{
PTHREAD_PARAM pParam = (PTHREAD_PARAM)lParam;
HMODULE hMod = NULL;
FARPROC pFunc = NULL;

// LoadLibrary()
hMod = ((PFLOADLIBRARYA)pParam->pFunc[0])(pParam->szBuf[0]); // "user32.dll"
if( !hMod )
return 1;

// GetProcAddress()
pFunc = (FARPROC)((PFGETPROCADDRESS)pParam->pFunc[1])(hMod, pParam->szBuf[1]); // "MessageBoxA"
if( !pFunc )
return 1;

// MessageBoxA()
((PFMESSAGEBOXA)pFunc)(NULL, pParam->szBuf[2], pParam->szBuf[3], MB_OK);

return 0;
}

上述代码看起来比较复杂,其实等同于

1
2
3
hMod = LoadLibraryA("user32.dll");
pFunc = GetProcAddress(hMod, "MessageBoxA");
pFunc(NULL, "www.reversecore.com", "ReverseCore", MB_OK);

从上述代码中的ThreadProc()函数可以看到,函数中并未直接调用相关API,也未直接定义用字符串,它们都是通过THREAD_PARAM结构体以线程参数的形式传递使用。原因在于编译的过程中,编译器将会把调用API的地址写死,但是在目标进程中对应的地址并不一定存在该函数,导致代码不能正常工作。因此我们通过重定义API函数,使其不依赖动态库的调用。

3、InjectCode()函数
InjectCode()是代码注入的核心部分,以下是代码:

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
BOOL InjectCode(DWORD dwPID)
{
HMODULE hMod = NULL;
THREAD_PARAM param = {0,};
HANDLE hProcess = NULL;
HANDLE hThread = NULL;
LPVOID pRemoteBuf[2] = {0,};
DWORD dwSize = 0;

hMod = GetModuleHandleA("kernel32.dll");

// set THREAD_PARAM
param.pFunc[0] = GetProcAddress(hMod, "LoadLibraryA");
param.pFunc[1] = GetProcAddress(hMod, "GetProcAddress");
strcpy_s(param.szBuf[0], "user32.dll");
strcpy_s(param.szBuf[1], "MessageBoxA");
strcpy_s(param.szBuf[2], "www.reversecore.com");
strcpy_s(param.szBuf[3], "ReverseCore");

// Open Process
if ( !(hProcess = OpenProcess(PROCESS_ALL_ACCESS, // dwDesiredAccess
FALSE, // bInheritHandle
dwPID)) ) // dwProcessId
{
printf("OpenProcess() fail : err_code = %d\n", GetLastError());
return FALSE;
}

// Allocation for THREAD_PARAM
dwSize = sizeof(THREAD_PARAM);
if( !(pRemoteBuf[0] = VirtualAllocEx(hProcess, // hProcess
NULL, // lpAddress
dwSize, // dwSize
MEM_COMMIT, // flAllocationType
PAGE_READWRITE)) ) // flProtect
{
printf("VirtualAllocEx() fail : err_code = %d\n", GetLastError());
return FALSE;
}

if( !WriteProcessMemory(hProcess, // hProcess
pRemoteBuf[0], // lpBaseAddress
(LPVOID)&param, // lpBuffer
dwSize, // nSize
NULL) ) // [out] lpNumberOfBytesWritten
{
printf("WriteProcessMemory() fail : err_code = %d\n", GetLastError());
return FALSE;
}

// Allocation for ThreadProc()
dwSize = (DWORD)InjectCode - (DWORD)ThreadProc;
if( !(pRemoteBuf[1] = VirtualAllocEx(hProcess, // hProcess
NULL, // lpAddress
dwSize, // dwSize
MEM_COMMIT, // flAllocationType
PAGE_EXECUTE_READWRITE)) ) // flProtect
{
printf("VirtualAllocEx() fail : err_code = %d\n", GetLastError());
return FALSE;
}

if( !WriteProcessMemory(hProcess, // hProcess
pRemoteBuf[1], // lpBaseAddress
(LPVOID)ThreadProc, // lpBuffer
dwSize, // nSize
NULL) ) // [out] lpNumberOfBytesWritten
{
printf("WriteProcessMemory() fail : err_code = %d\n", GetLastError());
return FALSE;
}

if( !(hThread = CreateRemoteThread(hProcess, // hProcess
NULL, // lpThreadAttributes
0, // dwStackSize
(LPTHREAD_START_ROUTINE)pRemoteBuf[1], // dwStackSize
pRemoteBuf[0], // lpParameter
0, // dwCreationFlags
NULL)) ) // lpThreadId
{
printf("CreateRemoteThread() fail : err_code = %d\n", GetLastError());
return FALSE;
}

WaitForSingleObject(hThread, INFINITE);

CloseHandle(hThread);
CloseHandle(hProcess);

return TRUE;
}

InjectCode()函数的set THREAD_PARAM部分用来设置THREAD_PARAM结构体变量,它们会注入目标进程,并且以参数形式传递给ThreadProc()线程函数。
其核心API函数整理如下:

1
2
3
4
5
6
7
8
9
10
11
OpenProcess()

//data : THREAD_PARAM
VirtualAllocEx()
WriteProcessMemory()

//code : ThreadProc()
VirtualAllocEx()
WriteProcessMemory()

CreateRemoteThread()

上述代码主要用来在目标进程中分别为data和code分配内存,并将它们注入进程。最后调用CreteRemoteThread() API,执行远程线程。

3、调试

代码注入是一种向目标进程创建新线程的技术,在OD的Debugging options中将Events设置为Break on new thread。从现在开始,每当notepad.exe进程中生成新线程,调试器就暂停在线程函数开始的代码位置。
8xMfat.png