在上一篇博文中,介绍了远程线程的概念并进行了实践,普通的远程线程仅可以运行目标进程中的函数,如果我们想要在目标进程中执行我们自己的函数,我们就必须要了解注入技术
1 注入
所谓注入就是在第三方进程不知道或者不允许的情况下将模块或者代码写入对方进程空间,并设法执行的技术
在安全领域,“注入”是非常重要的一种技术手段,注入与反注入也一直处于不断变化的,而且也愈来愈激烈的对抗当中
已知的注入方式:
远程线程注入、APC注入、消息钩子注入、注册表注入、导入表注入、输入法注入等
2 远程线程注入
在windows环境下,只要形如以下函数的代码,我们都可以使用createRemoteThread函数来远程创建线程
DWORD WINAPI ThreadProc(
LPVOID lpParameter
);
并且我们发现LoadLibrary函数就是这种形式的,因此会产生一种想法,先利用LoadLibrary函数在目标进程中加载我们自己编写的dll文件,之后便可以运行我们自己的代码了
HMODULE LoadLibraryA(
LPCSTR lpLibFileName
);
3 实验
我们编写目标进程如下:
#include <iostream>
#include<Windows.h>
void myFun() {
for (int i = 0; i < 10; i++) {
Sleep(1000);
printf("目标进程运行\n");
}
}
DWORD WINAPI ThreadProc(LPVOID IpParameter)
{
myFun();
return 0;
}
int main()
{
HANDLE hThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
getchar();
}
我们编写dll文件如下:
void myFun() {
printf("代码注入成功!!!");
}
DWORD WINAPI ThreadProc(LPVOID lpParameter) {
myFun();
return 0;
}
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadProc, NULL, 0, NULL);
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
我们编写远程注入代码如下:
#include <iostream>
#include <Windows.h>
DWORD Load(DWORD pid, char* DllName) {
HANDLE hproc = NULL;
DWORD dllLen = 0;
LPVOID parameterAddr = NULL;
DWORD wpFlag = 0;
HMODULE hModule = NULL;
DWORD loadAddr = 0;
HANDLE hThread = NULL;
//1.获得目标进程句柄
hproc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
if (hproc == NULL) {
OutputDebugString("OpenProcess ERROR!!!\n");
return 1;
}
//2.计算dll文件名的长度,记得加0
dllLen = strlen(DllName) + 1;
//3.在目标进程中分配内存
parameterAddr = VirtualAllocEx(hproc, NULL, dllLen, MEM_COMMIT, PAGE_READWRITE);
if (parameterAddr == NULL) {
OutputDebugString("VirtualAllocEx ERROR!!!\n");
CloseHandle(hproc);
return 2;
}
//4.将参数移动到分配的内存中
wpFlag = WriteProcessMemory(hproc, parameterAddr, DllName, dllLen, NULL);
if (wpFlag == 0) {
OutputDebugString("WriteProcessMemory ERROR!!!\n");
CloseHandle(hproc);
return 3;
}
//5.获得模块的地址
hModule = GetModuleHandle("kernel32.dll");
if (hModule == NULL) {
OutputDebugString("GetModuleHandle ERROR!!!\n");
CloseHandle(hproc);
return 4;
}
//6.获得LoadLibraryA的地址
loadAddr = (DWORD)GetProcAddress(hModule, "LoadLibraryA");
if (!loadAddr) {
OutputDebugString("GetProcAddress ERROR!!!\n");
CloseHandle(hproc);
CloseHandle(hModule);
return 5;
}
//7.创建远程线程来加载dll
hThread = CreateRemoteThread(hproc, NULL, 0, (LPTHREAD_START_ROUTINE)loadAddr, parameterAddr, 0, NULL);
if (hThread == NULL) {
OutputDebugString("CreateRemoteThread ERROR!!!\n");
CloseHandle(hproc);
CloseHandle(hModule);
return 6;
}
return 0;
}
int main() {
Load(3752, "Dll1.dll");
return 0;
}
实验开始,首先我们运行目标进程
将PID和dll文件的地址填入远程注入代码中,运行远程注入代码,可以发现成功注入
4 代码理解
在这一节中,我们主要来理解远程注入代码
在1中提到我们可以利用LoadLibraryA来加载我们自己的dll文件,但是其中有一个问题,LoadLibraryA需要一个参数,我们无法将参数传送给目标进程,因此需要在目标进程中开辟一段新的内存空间用来提前写入参数,win32的api恰恰提供了这些函数:
LPVOID VirtualAllocEx(
HANDLE hProcess,
LPVOID lpAddress,
SIZE_T dwSize,
DWORD flAllocationType,
DWORD flProtect
);
BOOL WriteProcessMemory(
HANDLE hProcess,
LPVOID lpBaseAddress,
LPCVOID lpBuffer,
SIZE_T nSize,
SIZE_T *lpNumberOfBytesWritten
);
前者可以为一个进程分配内存空间,后者可以向一片内存空间中写入数据,具体参数解释请前往微软文档查看,在远程注入代码的注释1、2、3、4中实现了这一点
而在注释5和6中,我们获取了LoadLibraryA的函数地址,请注意此处获取的是远程注入代码本身的LoadLibraryA地址,而不是目标进程中的LoadLibraryA地址。
为什么这样还会生效呢?
因为LoadLibraryA函数属于kernel32.dll,而该dll每个进程都会加载,在每个进程中LoadLibraryA函数的地址都是相同的(只要它们都运行在同一个操作系统中)
至于注释7很简单了,请参照我的“初探远程线程”