代码注入
@author: dlive
0x01 代码注入 VS DLL注入
代码注入相比于DLL注入的有点:
- 占用内存少,如果要注入的代码与数据较少,那么就不需要将它们做成DLL的形式注入,此时代码注入的方式占用的内存会更少
- 难以查找痕迹,DLL注入的方式会在目标内存中留下相关痕迹,很容易让人判断出目标进程是否被执行过注入操作,代码注入更难以查找痕迹
- 其他,不需要另外的DLL文件,只要有代码注入程序即可。
0x02 代码分析
CodeInjection.cpp
typedef struct _THREAD_PARAM
{
FARPROC pFunc[2]; // LoadLibraryA(), GetProcAddress()
char szBuf[4][128]; // "user32.dll", "MessageBoxA", "www.reversecore.com", "ReverseCore"
} 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;
// 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");
// 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)¶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;
}
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;
}
代码比较简单易懂,需要注意的一点是,代码注入时需要同时注入代码与代码中引用的数据,并且要保证代码能够正确引用注入的数据。从ThreadProc的代码中也可以看出,函数中并未直接调用相关API,也未直接定义使用字符串,它们都通过THREAD_PARAM结构体以线程参数的形式传递使用。
ThreadProc的参数中传递了LoadLibrary和GetProcAddress的地址以及参数,并使用LoadLibrary加载user32.dll,使用GetProcAddress获取MessagaBox的地址。
这里根据代码注入的原理做一个猜想,不传递LoadLibrary和GetProcAddress的地址而是直接在ThreadProc中直接调用这两个函数应该也是可以的,因为注入程序和被注入程序在同一个运行环境下的Loadlibrary和GetProcAddress函数的地址是一样的。问了一下大佬,大佬说这个猜想不对,如果直接调用LoadLibrary或GetProcAddress会经过IAT这个过程,call函数的时候会call 到IAT的空间,然后做一个跳转调到真正的函数地址。所以注入程序里如果ThreadProc直接调用loadlibrary,那ThreadProc的代码注入到其他程序里是不能用的。而通过GetProcAddress获得的是函数真正的地址,所以注入到别的程序中是可以正常运行的。
在函数大小的计算时使用了如下代码
dwSize = (DWORD)InjectCode - (DWORD)ThreadProc;
能这么计算,是因为MS VC++中使用Release模式编译程序代码后,源代码中函数顺序和二进制代码中的顺序一致。InjectCode函数定义在ThreadProc之后,函数名就是函数地址,所以直接将地址相减即可。
0x03 代码注入调试
首先使用OD打开notepad.exe进行调试
设置调试选项->事件,勾选中断于新线程
运行注入命令,OD在新线程运行时断下