- 代码注入是一种向目标进程插入独立运行代码并使之运行的技术,它一般调用CreateRomoteThread() API以远程线程形式运行插入的代码,所以也被称为线程注入。

- 首先向目标进程target.exe插入代码与数据,在此过程中,代码以线程过程(Thread Procedure)形式插入,而代码中使用的数据则以线程参数的形式传入,也就是说,代码与数据是分别注入的。
DLL注入与代码注入
- 下面这段代码用来弹出Windows消息框:
DWORD WINAPI ThreadProc(LPVOID lParam)
{
MessageBoxA(NULL, "www.reversecore.com", "ReverseCore", MB_OK);
return 0;
}
- 若想使用DLL注入技术,则需要把上述代码放入某个DLL文件,然后再将整个DLL文件注入目标进程,采用该技术完成注入后,运行OllyDbg调试器,查看上述ThreadProc()代码区域。

- 10009290与1000929C分别指向DLL数据节区中的字符串(“ReverseCore"、“www.reversecore.com”),上面2条PUSH指令将MessageBoxA() API中要使用的字符串地址存储到栈,1000100E地址处由一条CALL DWORD PTR DS:[100080F0]指令,该指令即使调用user32!MessageBoxA() API的命令,转到100080F0地址处查看。

- 100080F0地址就是DLL的IAT区域,在其上方可用看到其他API的地址,向这样,DLL代码中使用的所有数据均位于DLL的数据区域,采用DLL注入技术,整个DLL会被插入目标进程,代码与数据共存于内存,所以代码能够正常运行,以此不同,代码注入仅向目标进程注入必要的代码,要想使注入的代码正常运行,还必须将代码中使用的数据一同注入。
- 使用代码注入的原因
- 占用内存少:如果要注入的代码与数据较少,那么就不需要将它们做成DLL的形式再注入,此时直接采用代码注入的方式同样能够获得与DLL注入相同的效果,且占用内存会更少。
- 难以查找痕迹:采用DLL注入方式会在目标进程的内存中留下相关痕迹,很容易让人判断出目标进程是否被执行注入操作,但采用代码注入方式几乎不会留下任何痕迹。
例子
- 运行notepad.exe,然后使用Process Explorer查看notepad.exe进程的PID。

- 运行如下命令
C:\Users\12586\Downloads\example\03\27\bin>CodeInjection.exe 1196
- notepad.exe进程中弹出一个消息框。

- CodeInjection.cpp
int main(int argc, char *argv[])
{
DWORD dwPID = 0;
if( argc != 2 )
{
printf("\n USAGE : %s <pid>\n", argv[0]);
return 1;
}
if( !SetPrivilege(SE_DEBUG_NAME, TRUE) )
return 1;
dwPID = (DWORD)atol(argv[1]);
InjectCode(dwPID);
return 0;
}
- main()函数用来调用InjectCode()函数,传入的函数参数为目标进程的PID。
- ThreadProc()函数即为要注入目标进程的代码。
typedef struct _THREAD_PARAM
{
FARPROC pFunc[2];
char szBuf[4][128];
} THREAD_PARAM, *PTHREAD_PARAM;
typedef HMODULE (WINAPI *PFLOADLIBRARYA)
(
LPCSTR lpLibFileName
);
typedef FARPROC (WINAPI *PFGETPROCADDRESS)
(
HMODULE hModule,
LPCSTR lpProcName
);
typedef int (WINAPI *PFMESSAGEBOXA)
(
HWND hWnd,
LPCSTR lpText,
LPCSTR lpCaption,
UINT uType
);
DWORD WINAPI ThreadProc(LPVOID lParam)
{
PTHREAD_PARAM pParam = (PTHREAD_PARAM)lParam;
HMODULE hMod = NULL;
FARPROC pFunc = NULL;
hMod = ((PFLOADLIBRARYA)pParam->pFunc[0])(pParam->szBuf[0]);
if( !hMod )
return 1;
pFunc = (FARPROC)((PFGETPROCADDRESS)pParam->pFunc[1])(hMod, pParam->szBuf[1]);
if( !pFunc )
return 1;
((PFMESSAGEBOXA)pFunc)(NULL, pParam->szBuf[2], pParam->szBuf[3], MB_OK);
return 0;
}
hMod = LoadLibraryA("user32.dll");
pFunc = GetProcAddress(hMod, "MessageBoxA");
pFunc = (NULL,"www.revercore.com","ReverseCore",MB_OK);
- 代码注入技术的核心内容是注入可独立运行的代码,为此,需要同时注入代码与数据,并且要保证代码能够准确引用注入的数据。
- 上述代码中的ThreadProc()函数可以看到,函数中并未直接调用相关API,也未直接定义使用字符串,它们都是通过THREAD_PARAM结构体以线程参数的形式传递使用。
- ThreadProc()函数中使用THREAD_PARAM结构体接收2个API地址与4个字符串数据,其中2个API分别为LoadLibraryA()与GetProcAddress(),只要有了这2个API,就能调用所有库函数。
00401000 /. 55 PUSH EBP
00401001 |. 8BEC MOV EBP,ESP
00401003 |. 56 PUSH ESI
00401004 |. 8B75 08 MOV ESI,DWORD PTR SS:[EBP+8]; [EBP+8] = IParam = address of THREAD_PARAM
00401007 |. 8B0E MOV ECX,DWORD PTR DS:[ESI]
00401009 |. 8D46 08 LEA EAX,DWORD PTR DS:[ESI+8] ; EAX = "user32.dll"
0040100C |. 50 PUSH EAX
0040100D |. FFD1 CALL ECX ; kernel32.LoadLibraryA()
0040100F |. 85C0 TEST EAX,EAX
00401011 |. 75 0A JNZ SHORT 0040101D ; 0040101D
00401013 |> B8 01000000 MOV EAX,1
00401018 |. 5E POP ESI
00401019 |. 5D POP EBP
0040101A |. C2 0400 RETN 4
0040101D |> 8D96 88000000 LEA EDX,DWORD PTR DS:[ESI+88]
00401023 |. 52 PUSH EDX ; EDX = "MessageBoxA"
00401024 |. 50 PUSH EAX ; EAX = hMod
00401025 |. 8B46 04 MOV EAX,DWORD PTR DS:[ESI+4]
00401028 |. FFD0 CALL EAX ;kernel32.GetProcAddress
0040102A |. 85C0 TEST EAX,EAX
0040102C |.^74 E5 JE SHORT 00401013 ; 00401013
0040102E |. 6A 00 PUSH 0
00401030 |. 8D8E 88010000 LEA ECX,DWORD PTR DS:[ESI+188]
00401036 |. 51 PUSH ECX ; ECX = "ReverseCore"
00401037 |. 81C6 08010000 ADD ESI,108
0040103D |. 56 PUSH ESI ; ESI = "www.reversecore.com"
0040103E |. 6A 00 PUSH 0
00401040 |. FFD0 CALL EAX ;user32.MessageBoxA()
00401042 |. 33C0 XOR EAX,EAX
00401044 |. 5E POP ESI
00401045 |. 5D POP EBP
00401046 \. C2 0400 RETN 4
- 所有重要数据都是从线程参数IParam[EBP+8]接收使用的,也就说,ThreadProc()函数是可以独立运行的代码。
- InjectCode()函数
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");
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");
if ( !(hProcess = OpenProcess(PROCESS_ALL_ACCESS,
FALSE,
dwPID)) )
{
printf("OpenProcess() fail : err_code = %d\n", GetLastError());
return FALSE;
}
dwSize = sizeof(THREAD_PARAM);
if( !(pRemoteBuf[0] = VirtualAllocEx(hProcess,
NULL,
dwSize,
MEM_COMMIT,
PAGE_READWRITE)) )
{
printf("VirtualAllocEx() fail : err_code = %d\n", GetLastError());
return FALSE;
}
if( !WriteProcessMemory(hProcess,
pRemoteBuf[0],
(LPVOID)¶m,
dwSize,
NULL) )
{
printf("WriteProcessMemory() fail : err_code = %d\n", GetLastError());
return FALSE;
}
dwSize = (DWORD)InjectCode - (DWORD)ThreadProc;
if( !(pRemoteBuf[1] = VirtualAllocEx(hProcess,
NULL,
dwSize,
MEM_COMMIT,
PAGE_EXECUTE_READWRITE)) )
{
printf("VirtualAllocEx() fail : err_code = %d\n", GetLastError());
return FALSE;
}
if( !WriteProcessMemory(hProcess,
pRemoteBuf[1],
(LPVOID)ThreadProc,
dwSize,
NULL) )
{
printf("WriteProcessMemory() fail : err_code = %d\n", GetLastError());
return FALSE;
}
if( !(hThread = CreateRemoteThread(hProcess,
NULL,
0,
(LPTHREAD_START_ROUTINE)pRemoteBuf[1],
pRemoteBuf[0],
0,
NULL)) )
{
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函数,API函数归纳如下:
OpenProcess();
VirtualAllocEx();
WriteProcessMemory();
VirtualAllocEx();
WriteProcessMemory();
CreateRemoteThread();
- 上述代码主要用在目标进程中分别为data与code分配内存,并将它们注入目标进程,最后调用CreateRemoteThread() API,执行远程线程。
代码注入调试练习
- 用Ollydbg开始调试notepad.exe文件,按F9运行键,是notepad.exe处于Runing状态。

- 设置OllyDbg的选项后,即可从注入的线程代码开始调试。

- 每当notepad.exe进程中生成新线程,调试器就暂停在线程函数开始的代码位置。
- 使用Process Explorer查看notepad.exe的进程PID。

- 在命令窗口输入PID作参数,运行CodeInjection.exe。
C:\Users\12586\Downloads\example\03\27\bin>CodeInjection.exe 13716
- 按F9继续运行调试器,代码注入成功后,调试器就会暂停在被注入的线程代码的开始位置,由此开始调试。

- 004E0004地址处的MOV ESI,DWORD PTR SS:[EBP+8],[EBP+8]地址就是ThreadProc()函数的IParam参数,而参数IParam则指向被一同注入的THREAD_PARAM结构体。
