原理介绍
远程线程指的是跨进程,可以在别的进程里创建线程
用到的API函数是 CreateRemoteThread
使用 CreateRemoteThread 创建远程线程的时候,需要提供目标进程的句柄,这个可以通过OpenProcess得到,然后还需要一个函数地址和一个参数地址
这个函数地址是在目标进程里创建线程执行的函数,而这个函数只能带有一个参数
说也巧,加载DLL的函数 LoadLibrary 刚好也只要一个参数,那就是DLL文件的路径
那么,我们只要找到 LoadLibrary 函数的地址,然后把DLL文件的路径写入到目标进程内存空间中去,得到参数的地址,就可以实现创建远程线程的DLL注入了
注意
这里先提一下一个可能会遇到的坑,DLL注入过程中,如果目标进程是64位的,那么我们写的程序也得是64位的,注入的DLL也必须是64位的,不然注入会失败!!
CreateRemoteThread 函数介绍
HANDLE
WINAPI
CreateRemoteThread(
_In_ HANDLE hProcess, //目标进程句柄
_In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes, //安全属性一般NULL
_In_ SIZE_T dwStackSize, //缺省堆栈大小 一般NULL
_In_ LPTHREAD_START_ROUTINE lpStartAddress, //函数地址指针
_In_opt_ LPVOID lpParameter, //函数参数
_In_ DWORD dwCreationFlags, //创建后线程的状态,0表示立即执行,暂停CREATE_SUSPENDED需要调用ResumeThread去恢复
_Out_opt_ LPDWORD lpThreadId //用来接收新创建线程的线程ID,可以为NULL
);
这个函数需要用到的进程句柄、函数地址、参数地址这三个参数需要我们提前去获得
获得进程ID
通过OpenProcess获得即可
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, Pid);
if (hProcess == NULL) {
return -1;
}
进程PID可以通过任务管理器自行查看
当然也可以通过代码来获取,比如通过FindWindow根据窗口名获取窗口句柄,然后使用GetWindowThreadProcessId来获取进程PID,这里就不细讲了
获取函数地址
由于kernel32.dll中的函数地址在windows上是固定的位置,所以在本进程中获取到的地址在别的进程中也适用:
//获取LoadLibraryA函数地址
PTHREAD_START_ROUTINE load_start_addr = (PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryA");
获取参数地址
这里需要打开进程,向目标进程申请一片地址空间,然后向这个空间写入dll文件的路径
//打开进程,获取进程句柄
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, Pid);
if (hProcess == NULL) {
return -1;
}
//向目标进程申请空间,返回首地址指针
int nDllSize = strlen(dll) + 1;
LPVOID pDllAddr = VirtualAllocEx(hProcess, NULL, nDllSize, MEM_COMMIT, PAGE_READWRITE);
if (pDllAddr == NULL) {
CloseHandle(hProcess);
return -1;
}
//向目标进程申请的空间写入dll绝对路径
DWORD dwWriteNum = 0;
WriteProcessMemory(hProcess, pDllAddr, dll, nDllSize, &dwWriteNum);
如果不知道dll路径有没有成功写入目标进程,可通过CE软件打开目标进程查看内存来判断
DLL注入
接下来调用CreateRemoteThread进行dll注入即可
//DLL注入
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, load_start_addr, pDllAddr, 0, NULL);
//等待目标dll的加载结束
WaitForSingleObject(hThread, INFINITE);
//关闭刚刚创建的句柄
CloseHandle(hThread);
CloseHandle(hProcess);
效果演示
卸载注入的DLL
卸载注入的DLL,和注入DLL的原理相同,都是通过创建远程线程去执行一个只有一个参数的函数来进行
卸载用到的函数是:FreeLibrary,参数为模块句柄,这个模块句柄可通过对目标进程创建快照,然后遍历其中的模块来获得,关于创建快照遍历模块相关内容,可从我以前的笔记中找到,这里就不再啰嗦了。
FreeLibrary 函数介绍
BOOL
WINAPI
FreeLibrary(
_In_ HMODULE hLibModule //模块句柄,可通过遍历获得
);
代码实现
void CInjectDllDlg::UnInjectDll(DWORD dwPid, LPCSTR szDllName)
{
// TODO: 在此处添加实现代码.
if (dwPid == 0 || strlen(szDllName) == 0)
{
return;
}
//创建DLL快照
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwPid);
MODULEENTRY32 me32;
me32.dwSize = sizeof(me32);
//这里寻找指定的dll信息,找到了后,结构体里的hModule参数里会存有dll模块句柄
BOOL bRet = Module32First(hSnap,&me32);
TCHAR szDllNameW[MAX_PATH] = { 0 };
MultiByteToWideChar(CP_ACP, NULL, szDllName, -1, szDllNameW, strlen(szDllName));
while (bRet) {
if (lstrcmp(_wcsupr(me32.szExePath), _wcsupr(szDllNameW)) == 0) {
break;
}
bRet = Module32Next(hSnap, &me32);
}
CloseHandle(hSnap);
char* pFunName = "FreeLibrary";
// 打开目标进程
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
if (hProcess == NULL)
{
return;
}
// 计算欲注入DLL文件完整路径的长度
int nDllLen = strlen(szDllName) + sizeof(TCHAR);
//获得函数的地址
FARPROC pFunAddr = GetProcAddress(GetModuleHandle(L"kernel32.dll"), pFunName);
// 创建远程线程
HANDLE hThread = CreateRemoteThread(hProcess,
NULL, 0,
(PTHREAD_START_ROUTINE)pFunAddr,//LoadLibraryA函数
me32.hModule, //参数是dll地址指针
0, NULL);
//WaitForSingleObject(hThread, INFINITE);//等待线程执行完毕
CloseHandle(hThread);
CloseHandle(hProcess);
}