原理介绍
远程线程指的是跨进程,可以在别的进程里创建线程
用到的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);
效果演示
突破SESSION 0 远线程注入
在Windows 10 下,dll注入只能注入到一些普通的用户进程里,没办法注入到系统服务进程里,这是因为SESSION 0隔离机制所造成的,通过ZwCreateThreadEx函数可以突破SESSION 0 限制实现远线程注入
CreateRemoteThread函数在调用的时候,内部会调用ZwCreateThread来创建远程线程,其中第七个参数为1,导致线程创建完成之后处于挂起状态,无法恢复运行,我们直接调用这个函数然后把第七个参数设置为1即可让线程创建完成后就恢复运行,从而成功注入
关于这个参数的值,网上有个师傅说:
恰巧的是传入值为2的时候,可以绕过DLL_ATTACH_THREAD,大家都懂得。
另外就是传入值为4的时候,貌似OD里对这个线程里的代码下断会飞,似乎是hidefromdbgger的功能。
由于目前能力有限,先把这个信息留在这里,回头懂了再回来进行实验。
ZwCreateThreadEx 函数
利用这个函数和CreateRemoteThread相比,原理是相同的,但这个函数更为底层
由于ZwCreateThreadEx函数在ntdll.dll中没有定义,所以需要使用GetProcAddress从ntdll.dll中获取函数的导出地址
我们要在文件开头自己声明这个函数的参数:
#ifdef _WIN64
typedef DWORD(WINAPI* typedef_ZwCreateThreadEx)(
PHANDLE ThreadHandle,
ACCESS_MASK DesiredAccess,
LPVOID ObjectAttributes,
HANDLE ProcessHandle,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
ULONG CreateThreadFlags,
SIZE_T ZeroBits,
SIZE_T StackSize,
SIZE_T MaximumStackSize,
LPVOID pUnkown);
#else
typedef DWORD(WINAPI* typedef_ZwCreateThreadEx)(
PHANDLE ThreadHandle,
ACCESS_MASK DesiredAccess,
LPVOID ObjectAttributes,
HANDLE ProcessHandle,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
BOOL CreateSuspended,
DWORD dwStackSize,
DWORD dw1,
DWORD dw2,
LPVOID pUnkown);
#endif
这是个宏定义的函数,函数地址需要从GetProcAddress获得:
//加载ntdll.dll
HMODULE hNtdll = LoadLibraryW(L"ntdll.dll");
//获取ZwCreateThreadEx地址
typedef_ZwCreateThreadEx ZwCreateThreadEx = (typedef_ZwCreateThreadEx)GetProcAddress(hNtdll,"ZwCreateThreadEx");
获取到这个函数之后,直接调用即可实现注入:
HANDLE hRemoteThread = NULL;
DWORD dwStatus = ZwCreateThreadEx(&hRemoteThread,PROCESS_ALL_ACCESS,NULL,hProcess,(LPTHREAD_START_ROUTINE)pFunprocAddr,pDllAddr,0,0,0,0,NULL);
注入的话,最后5个参数全是0
代码示例:
BOOL ZwCreateThreadEx_InjectDLLW(DWORD dwPid,LPCWSTR pszDllFileName) {
//打开进程,获取进程句柄
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS,FALSE,dwPid);
if (hProcess == NULL) {
return FALSE;
}
//在注入进程中申请内存
DWORD dwSize = 1 + wcslen(pszDllFileName)*2;
LPVOID pDllAddr = VirtualAllocEx(hProcess,NULL,dwSize,MEM_COMMIT,PAGE_READWRITE);
if (pDllAddr == NULL) {
return FALSE;
}
//在目标内存写入数据
if (WriteProcessMemory(hProcess, pDllAddr, pszDllFileName, dwSize, NULL) == NULL) {
return FALSE;
}
//获取LoadLibraryA函数地址
FARPROC pFunprocAddr = GetProcAddress(GetModuleHandleW(L"kernel32.dll"),"LoadLibraryW");
if (pFunprocAddr == NULL) {
return FALSE;
}
//加载ntdll.dll
HMODULE hNtdll = LoadLibraryW(L"ntdll.dll");
if (hNtdll == NULL) {
return FALSE;
}
//获取ZwCreateThreadEx地址
typedef_ZwCreateThreadEx ZwCreateThreadEx = (typedef_ZwCreateThreadEx)GetProcAddress(hNtdll, "ZwCreateThreadEx");
if (ZwCreateThreadEx == NULL) {
return FALSE;
}
//注入
HANDLE hRemoteThread = NULL;
DWORD dwStatus = ZwCreateThreadEx(&hRemoteThread,PROCESS_ALL_ACCESS,NULL,hProcess,(LPTHREAD_START_ROUTINE)pFunprocAddr,pDllAddr,0,0,0,0,NULL);
if (hRemoteThread == NULL) {
return FALSE;
}
//关闭句柄
CloseHandle(hRemoteThread);
CloseHandle(hProcess);
return TRUE;
}
卸载注入的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);
}