代码注入概念
代码注入是一种向目标进程插入独立运行代码并使之运行的技术,其一般调用CreateRemoteThread() API以远程线程的形式运行插入的代码,亦称为线程注入。代码以线程过程(ThreadProcedure)形式插入,而代码中使用的数据则以线程参数的形式插入,即代码和数据是分别注入的。
代码注入与DLL注入的区别
DLL注入适用于代码量大且复杂的情况,而代码注入适用于代码量少且简单的情况。
DLL注入需要先把注入代码放入某个DLL文件,再将整个DLL文件注入目标进程。DLL代码中使用的所有数据位于DLL的数据区域,整个DLL插入目标进程时,代码和数据是共存于内存,因而代码能够正常执行。
代码注入仅向目标进程注入必要的代码,要想使注入代码正常运行,还必须将代码中使用的数据一同注入。
另外,代码注入的优点为:占用内存少;难以查找痕迹;无需另外的DLL文件。
DLL注入示例
在之前写的文章《DLL注入》中有简单的讲解。这里先看看DLL注入的常规做法,即将代码写入DLL文件,然后将DLL注入到目标进程后会执行DLL中的程序,这里以MsgBox.dll注入到notepad进程中弹框为例。
MsgBox.cpp
// MsgBox.cpp : 定义 DLL 应用程序的导出函数。
//
#include "stdafx.h"
#include "windows.h"
DWORD WINAPI ThreadProc(LPVOID lParam){
MessageBoxA(NULL, "DLL注入 By SKI12", "blog.csdn.net/ski_12", MB_OK);
return 0;
}
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved){
switch( fdwReason ){
case DLL_PROCESS_ATTACH :
CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
break;
}
return TRUE;
}
如果在编译中出现如下错误时,只需到dllmain.cpp中删除DllMain()函数即可:
生成成功后,将得到MsgBox.dll文件。
另外还需要编写DLL注入的程序,如之前《DLL注入》文章中所示,这里不再累赘。
打开notepad程序,查看其PID,再到cmd中进行DLL注入,:
接着跟踪调试。在完成MsgBox.dll注入notepad进程后,用OllyDbg查看DLL注入代码:
可以看到两条PUSH指令中两个地址为MessageBoxA()要使用的两个字符串参数,将其存储在栈中。
接着call指令调用的MessageBoxA()函数的地址如下:
可以看到62B82080地址为DLL的IAT区域,DLL代码中使用的所有数据都位于DLL的数据区域。
小结一下,DLL注入就是把代码和数据都写到DLL文件中,再将该DLL文件注入到目标进程中,从而代码与数据共存于内存,使得代码可以正常运行。
代码注入示例
编写实现和上述DLL注入一样弹框功能的代码,只是改为用代码注入的方式进行。
CodeInject.cpp
// CodeInject.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include "windows.h"
#include "stdio.h"
//该结构体用于接收API和4个字符串
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;
//未直接调用相关API和未直接定义使用字符串,而通过THREAD_PARAM结构体以线程参数的形式传递使用
// 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;
}
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");
//设置THREAD_PARAM结构体
//加载到所有进程的kernel32.dll的地址都相同,因此从本进程获取的API与在notepad进程中获取的API地址是一样的
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], "代码注入 By SKI12");
strcpy_s(param.szBuf[3], "blog.csdn.net/ski_12");
// 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;
}
//param参数值为数据
if( !WriteProcessMemory(hProcess, // hProcess
pRemoteBuf[0], // lpBaseAddress
(LPVOID)¶m, // 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;
}
//ThreadProc()函数为操作代码
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;
}
//将数据与代码注入到进程内存后,调用CreateRemoteThread()执行远程线程
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;
}
BOOL SetPrivilege(LPCTSTR lpszPrivilege, BOOL bEnablePrivilege) {
TOKEN_PRIVILEGES tp;
HANDLE hToken;
LUID luid;
if( !OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken) ){
printf("OpenProcessToken error: %u\n", GetLastError());
return FALSE;
}
if( !LookupPrivilegeValue(NULL, // lookup privilege on local system
lpszPrivilege, // privilege to lookup
&luid) ) // receives LUID of privilege
{
printf("LookupPrivilegeValue error: %u\n", GetLastError() );
return FALSE;
}
tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = luid;
if( bEnablePrivilege )
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
else
tp.Privileges[0].Attributes = 0;
// Enable the privilege or disable all privileges.
if( !AdjustTokenPrivileges(hToken,
FALSE,
&tp,
sizeof(TOKEN_PRIVILEGES),
(PTOKEN_PRIVILEGES) NULL,
(PDWORD) NULL) )
{
printf("AdjustTokenPrivileges error: %u\n", GetLastError() );
return FALSE;
}
if( GetLastError() == ERROR_NOT_ALL_ASSIGNED ){
printf("The token does not have the specified privilege. \n");
return FALSE;
}
return TRUE;
}
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;
}
编译生成exe文件后,同样运行notepad进程,查看进程PID,在cmd中进行代码注入,可以看到和上述DLL注入一样的效果:
接着使用OllyDbg跟踪调试。
因为代码注入是通过线程的方式实现注入的,因此先设置注入新线程时进行断点:
命令行进行代码注入后,停留在被注入线程代码的起始位置,即ThreadProc()函数:
可以看到002C0004地址处的MOV指令中,[EBP+8]地址就是ThreadProc()函数的lParam参数,而参数lParam则指向被一同注入的THREAD_PARAM结构体:
小结一下,代码注入即线程注入,先注入数据与代码到进程的内存再通过调动CreateRemoteThread()函数来执行远程线程,其中代码和数据都是通过THREAD_PARAM结构体以线程参数的形式传递使用的。