C/C++ 实现常用的线程注入

简单编写DLL文件:

#include <Windows.h>

extern "C" __declspec(dllexport) void MsgBox(LPCWSTR szMsg, LPCWSTR Title)
{
	MessageBox(NULL, szMsg, Title, MB_OK);
}

BOOL APIENTRY DllMain(HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
	switch (ul_reason_for_call)
	{
	case DLL_PROCESS_ATTACH:
		// 进程被加载后执行
		break;
	case DLL_THREAD_ATTACH:
		// 线程被创建后加载
		break;
	case DLL_THREAD_DETACH:
		// 正常退出执行的代码
		break;
	case DLL_PROCESS_DETACH:
		// 进程卸载本Dll后执行的代码
		break;
	}
	return TRUE;
}
#include <Windows.h>
#include <iostream>

typedef VOID(*PFUNMSG)(LPCWSTR szMsg, LPCWSTR Title);

int main(int argc, char *argv[])
{

	HMODULE hModule = LoadLibrary("./hook.dll");

	if (hModule != NULL)
	{
		PFUNMSG pMsgBox = (PFUNMSG)GetProcAddress(hModule, "MsgBox");
		pMsgBox(L"hello lyshark", L"msgbox");
	}

	system("pause");
	return 0;
}

x86 实现远程线程注入: 注入原理是利用了Windows系统中提供的CreateRemoteThread()这个API函数,该函数第四个参数是准备运行的线程,我们将LoadLibrary()函数填入其中,这样就可以执行远程进程中的LoadLibrary()函数,进而将我们自己准备的DLL加载到远程进程空间中执行,DLL在被装载后则会自动执行初始化部分,X86注入代码如下.

#include <windows.h>
#include <stdio.h>

// 使用 CreateRemoteThread 实现远线程注入
BOOL CreateRemoteThreadInjectDll(DWORD Pid, char *DllName)
{
	HANDLE hProcess = NULL;
	SIZE_T dwSize = 0;
	LPVOID pDllAddr = NULL;
	FARPROC pFuncProcAddr = NULL;

	// 打开注入进程,获取进程句柄
	hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, Pid);
	if (NULL == hProcess)
	{
		return FALSE;
	}
	// 计算欲注入 DLL 文件完整路径的长度
	dwSize = sizeof(char)+lstrlen(DllName);

	//  在目标进程申请一块长度为 nDllLen 大小的内存空间
	pDllAddr = VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT, PAGE_READWRITE);
	if (NULL == pDllAddr)
	{
		return FALSE;
	}
	//  将欲注入 DLL 文件的完整路径写入在目标进程中申请的空间内
	if (FALSE == WriteProcessMemory(hProcess, pDllAddr, DllName, dwSize, NULL))
	{
		return FALSE;
	}
	// 获得 LoadLibraryA()函数的地址
	pFuncProcAddr = GetProcAddress(GetModuleHandle("kernel32.dll"), "LoadLibraryA");
	if (NULL == pFuncProcAddr)
	{
		return FALSE;
	}
	// 使用 CreateRemoteThread 创建远线程, 实现 DLL 注入
	HANDLE hRemoteThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pFuncProcAddr, pDllAddr, 0, NULL);
	if (NULL == hRemoteThread)
	{
		return FALSE;
	}
	// 关闭句柄
	CloseHandle(hProcess);
	return TRUE;
}

int main(int argc, char *argv[])
{
	CreateRemoteThreadInjectDll(4668, "./x86.dll");
	system("pause");
	return 0;
}

x64 实现远程线程注入: 如果想要注入X64程序,则需要在编译时指定为64位编译模式,并且使用LoadLibraryW()来加载动态链接库,我们只需要在上面代码的基础上稍加改进就可以实现64位进程的注入了.

#include <windows.h>
#include <stdio.h>

// 使用 CreateRemoteThread 实现远线程注入
BOOL CreateRemoteThreadInjectDll(DWORD Pid, PCWSTR DllName)
{
	BOOL ret = FALSE;
	HANDLE hProcess, hThread = NULL;
	FARPROC pfnThreadRtn = NULL;
	PWSTR pwszPara = NULL;

	// 打开注入进程,获取进程句柄
	hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, Pid);
	if (NULL == hProcess)
	{
		return FALSE;
	}

	// 计算欲注入 DLL 文件完整路径的长度
	size_t iProxyFileLen = wcslen(DllName) * sizeof(WCHAR);

	//  在目标进程申请一块长度为 nDllLen 大小的内存空间
	pwszPara = (PWSTR)VirtualAllocEx(hProcess, NULL, iProxyFileLen, MEM_COMMIT, PAGE_READWRITE);
	if (NULL == pwszPara)
	{
		return FALSE;
	}

	//  将欲注入 DLL 文件的完整路径写入在目标进程中申请的空间内
	if (FALSE == WriteProcessMemory(hProcess, pwszPara, (PVOID)DllName, iProxyFileLen, NULL))
	{
		return FALSE;
	}

	// 获得 LoadLibraryW()函数的地址
	pfnThreadRtn = GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryW");
	if (NULL == pfnThreadRtn)
	{
		return FALSE;
	}

	// 使用 CreateRemoteThread 创建远线程, 实现 DLL 注入
	hThread = CreateRemoteThread(hProcess, NULL, 1024, (LPTHREAD_START_ROUTINE)pfnThreadRtn, pwszPara, 0, NULL);
	WaitForSingleObject(hThread, INFINITE);
	if (NULL != hThread)
	{
		CloseHandle(hThread);
		CloseHandle(hProcess);
		VirtualFreeEx(hProcess, pwszPara, 0, MEM_RELEASE);
		return TRUE;
	}
	return FALSE;
}

int main(int argc, char *argv[])
{
	CreateRemoteThreadInjectDll(8224, L"./x64.dll");
	system("pause");
	return 0;
}

实现普通消息钩子注入: Windows提供的钩子类型非常多,其中一种类型的钩子非常实用,那就是WH_GETME SSAGE钩子,它可以很方便地将DLL文件注入到所有的基于消息机制的目标程序中,代码非常简单,这里直接给出DLL文件的代码,具体如下:

#include <windows.h>

extern "C" __declspec(dllexport) VOID SetHookOn();
extern "C" __declspec(dllexport) VOID SetHookOff();

HHOOK g_HHook = NULL;
HINSTANCE g_hInst = NULL;

VOID DoSomeThing()
{
	MessageBoxA(0, "hello lyshark", 0, 0);
}

BOOL WINAPI DllMain(HINSTANCE hinstDLL,DWORD fdwReason,LPVOID lpvReserved)
{
	switch (fdwReason)
	{
		case DLL_PROCESS_ATTACH:
		{
			g_hInst = hinstDLL;
			DoSomeThing();
			break;
		}
	}
	return TRUE;
}

LRESULT CALLBACK GetMsgProc(int code,WPARAM wParam,LPARAM lParam)
{
	return CallNextHookEx(g_HHook, code, wParam, lParam);
}

VOID SetHookOn()
{
	g_HHook = SetWindowsHookEx(WH_GETMESSAGE, GetMsgProc, g_hInst, 0);
}

VOID SetHookOff()
{
	UnhookWindowsHookEx(g_HHook);
}

实现全局消息钩子注入: 该注入的原理是利用系统中的SetWindowHookEx()这个API函数,该函数可以拦截目标进程的消息到指定DLL中的导出函数上,利用这个特性,我们可以将DLL注入到全局进程中,但是在使用SetWindowsHookEx()之前首先需要将HOOK的DLL加载到本身的进程中,以此得到DLL的模块句柄,再使用GetProcAddress()得到DLL中公开的函数地址,最后遍历出待注入进程的线程ID,这样SetWindowHookEx()就可以利用这些参数进行HOOK了.

我们先来编写DLL文件,创建Dll工程hook.cpp然后将SetHook()函数导出,由于该注入方式是全局注入,所以如果我们想要注入到指定进程中,则需要在DllMain()也就是动态链接库开头位置进行判断,如果是我们需要Hook的进程,则加载Dll到指定进程中,如果不是则不执行任何操作,这样一来即可实现指定进程注入.

#include <windows.h>
HHOOK Global_Hook;

// 设置全局消息回调函数
LRESULT CALLBACK MyProc(int nCode, WPARAM wParam, LPARAM lParam)
{
	return CallNextHookEx(Global_Hook, nCode, wParam, lParam);
}
// 安装全局钩子,此处的hook.dll可以是外部其他的dll
extern "C" __declspec(dllexport) void SetHook()
{
	Global_Hook = SetWindowsHookEx(WH_CBT, MyProc, GetModuleHandle(TEXT("hook.dll")), 0);
}
// 卸载全局钩子
extern "C" __declspec(dllexport) void UnHook()
{
	if(Global_Hook)
		UnhookWindowsHookEx(Global_Hook);
}

bool APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
	HWND hwnd = FindWindowW(L"valve001",NULL);
	DWORD pid;
	GetWindowThreadProcessId(hwnd, &pid);
	if (GetCurrentProcessId() == pid)
	{
		MessageBox(hwnd, TEXT("hello lyshark"), 0, 0);
	}
	return true;
}

调用代码:注意必须将上方编译好的hook.dll与下方工程放到同一个目录下,通过LoadLibrary()函数获取到模块句柄,然后通过GetProcAddress()获取到导出函数地址,并通过函数指针调用,由于全局注入依赖于父进程,所以下面的代码必须一直运行.

#include <windows.h>

int main(int argc, char *argv[])
{
	HMODULE hMod = LoadLibrary(TEXT("hook.dll"));

	typedef void(*pSetHook)(void);
	pSetHook SetHook = (pSetHook)GetProcAddress(hMod, "SetHook");
	SetHook();
	while (1)
	{
		Sleep(1000);
	}
	return 0;
}

APC应用层异步注入: APC 是异步过程调用,在Windows下每个线程在可被唤醒时在其APC链中的函数将有机会执行被执行,每一个线程都具有一个APC链,那么只要在可以在APC链中添加一个APC,就可以完成我们所需要的DLL注入的功能.

1.将需要加载的DLL的完整路径写入目标进程空间.
2.获得LoadLibraryA()函数的地址,当然也可以是LoadLibraryW()函数的地址.
3.枚举目标进程中的所有线程,为每个线程添加一个APC函数,这样增加了注入成功的机会.

该注入的原理是利用当线程被唤醒时APC中的注册函数会被执行的机制,并以此去执行我们的DLL加载代码,进而完成DLL注入的目的,通过APC注入的流程步骤大致如下

1.当EXE里某个线程执行到SleepEx()或者WaitForSingleObjectEx()时,系统就会产生一个软中断.
2.当线程再次被唤醒时,此线程会首先执行APC队列中的被注册的函数.
3.利用QueueUserAPC()这个API可以在软中断时向线程的APC队列插入一个函数指针,如果我们插入的是Loadlibrary()执行函数的话,就能达到注入DLL的目的,不论如何目标程序必须有执行SleepEx()或者WaitForSingleObjectEx()否则DLL不会加载.

#include <windows.h>
#include <tlhelp32.h>
#include <stdio.h>

// APC注入
BOOL ApcInjectDll(DWORD dwPid, char * szDllName)
{
	// 计算欲注入 DLL 文件完整路径的长度
	int nDllLen = lstrlen(szDllName) + sizeof(char);

	// 打开目标进程
	HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS,FALSE, dwPid);
	if (hProcess == NULL)
	{
		return FALSE;
	}
	// 在目标进程申请一块长度为 nDllLen 大小的内存空间
	PVOID pDllAddr = VirtualAllocEx(hProcess,NULL, nDllLen,MEM_COMMIT,PAGE_READWRITE);
	if (pDllAddr == NULL)
	{
		CloseHandle(hProcess);
		return FALSE;
	}
	DWORD dwWriteNum = 0;
	// 将欲注入 DLL 文件的完整路径写入在目标进程中申请的空间内
	WriteProcessMemory(hProcess, pDllAddr, szDllName,nDllLen, &dwWriteNum);
	CloseHandle(hProcess);

	THREADENTRY32 te = { 0 };
	te.dwSize = sizeof(THREADENTRY32);
	//得到线程快照
	HANDLE handleSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
	if (INVALID_HANDLE_VALUE == handleSnap)
	{
		CloseHandle(hProcess);
		return FALSE;
	}
	// 获得 LoadLibraryA()函数的地址
	FARPROC pFunAddr = GetProcAddress(GetModuleHandle("kernel32.dll"), "LoadLibraryA");
	DWORD dwRet = 0;
	//得到第一个线程
	if (Thread32First(handleSnap, &te))
	{
		do
		{
			//进行进程 ID 对比
			if (te.th32OwnerProcessID == dwPid)
			{
				//得到线程句柄
				HANDLE hThread = OpenThread(THREAD_ALL_ACCESS,FALSE,te.th32ThreadID);
				if (hThread)
				{
					//向线程插入 APC
					dwRet = QueueUserAPC((PAPCFUNC)pFunAddr,hThread,(ULONG_PTR)pDllAddr);
					//关闭句柄
					CloseHandle(hThread);
				}
			}
			//循环下一个线程
		} while (Thread32Next(handleSnap, &te));
	}
	CloseHandle(handleSnap);
	return TRUE;
}

int main(int argc, char *argv[])
{
	ApcInjectDll(9608, "c:/x86.dll");
	system("pause");
	return 0;
}

ZwCreateThreadEx强力注入: 在前面的注入方式中,我们使用了CreateRemoteThread()这个函数来完成线程注入,此方式可以注入普通的进程,但却无法注入到系统进程中,因为系统进程是处在SESSION0高权限级别的会话层.

由于CreateRemoteThread()底层会调用ZwCreateThreadEx()这个未公开的内核函数,所以我们必须手动调用ZwCreateThread()这一内核函数,将第七个参数设置为0即可,ZwCreateThreadEx函数在ntdll.dll中并未声明,所以必须手动使用GetProcAddress函数将其地址导出.

#include <windows.h>
#include <stdio.h>

// 使用 ZwCreateThreadEx 实现远线程注入
BOOL ZwCreateThreadExInjectDll(DWORD dwProcessId, char * pDllName)
{
	HANDLE hProcess = NULL;
	SIZE_T dwSize = 0;
	LPVOID pDllAddr = NULL;
	FARPROC pFuncProcAddr = NULL;
	HANDLE hRemoteThread = NULL;
	DWORD dwStatus = 0;

	// 打开注入进程,获取进程句柄
	hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);
	if (NULL == hProcess)
	{
		return FALSE;
	}
	// 在注入进程中申请内存
	dwSize = sizeof(char)+lstrlen(pDllName);
	pDllAddr = ::VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT, PAGE_READWRITE);
	if (NULL == pDllAddr)
	{
		return FALSE;
	}
	// 向申请的内存中写入数据
	if (FALSE == ::WriteProcessMemory(hProcess, pDllAddr, pDllName, dwSize, NULL))
	{
		return FALSE;
	}
	// 加载 ntdll.dll
	HMODULE hNtdllDll = ::LoadLibrary("ntdll.dll");
	if (NULL == hNtdllDll)
	{
		return FALSE;
	}
	// 获取LoadLibraryA函数地址
	pFuncProcAddr = ::GetProcAddress(::GetModuleHandle("Kernel32.dll"), "LoadLibraryA");
	if (NULL == pFuncProcAddr)
	{
		return FALSE;
	}
	// 获取ZwCreateThread函数地址
#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
	typedef_ZwCreateThreadEx ZwCreateThreadEx = (typedef_ZwCreateThreadEx)::GetProcAddress(hNtdllDll, "ZwCreateThreadEx");
	if (NULL == ZwCreateThreadEx)
	{
		return FALSE;
	}
	// 使用 ZwCreateThreadEx 创建远线程, 实现 DLL 注入
	dwStatus = ZwCreateThreadEx(&hRemoteThread, PROCESS_ALL_ACCESS, NULL, hProcess, 
		(LPTHREAD_START_ROUTINE)pFuncProcAddr, pDllAddr, 0, 0, 0, 0, NULL);
	if (NULL == hRemoteThread)
	{
		return FALSE;
	}
	// 关闭句柄
	::CloseHandle(hProcess);
	::FreeLibrary(hNtdllDll);
	return TRUE;
}

int main(int argc, char *argv[])
{
	BOOL bRet = ZwCreateThreadExInjectDll(2940, "hook.dll");

	system("pause");
	return 0;
}

强制卸载进程中的DLL:

#include <Windows.h>
#include <stdio.h>
#include <TlHelp32.h>

BOOL UnLoad_Module(DWORD dwPID, LPCTSTR szDllName)
{
	BOOL bMore = FALSE, bFound = FALSE;
	HANDLE hSnapshot, hProcess, hThread;
	HMODULE hModule = NULL;
	MODULEENTRY32 me = { sizeof(me) };
	LPTHREAD_START_ROUTINE pThreadProc;

	hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwPID);
	bMore = Module32First(hSnapshot, &me);
	for (; bMore; bMore = Module32Next(hSnapshot, &me))
	{
		if (!_tcsicmp((LPCTSTR)me.szModule, szDllName) || !_tcsicmp((LPCTSTR)me.szExePath, szDllName))
		{
			bFound = TRUE;
			break;
		}
	}
	if (!(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)))
		return FALSE;

	hModule = GetModuleHandle(L"kernel32.dll");
	pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hModule, "FreeLibrary");
	hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, me.modBaseAddr, 0, NULL);
	WaitForSingleObject(hThread, INFINITE);
	if (hThread != 0)
	{
		CloseHandle(hThread);
		CloseHandle(hProcess);
		CloseHandle(hSnapshot);
		return TRUE;
	}
	return FALSE;
}

int main(int argc, char *argv[])
{
	UnLoad_Module(2012, "lyshark.dll");

	system("pause");
	return 0;
}

纯汇编实现远程Dll注入:

.data
	szMyDll        db "\lyshark.dll",0h       ; 要注入的DLL
	szDllKernel    db "Kernel32.dll",0h
	szLoadLibrary  db "LoadLibraryA",0h
	lpFileName     db "Tutorial-i386",0h      ; 指定要注入进程
	lpDllName      dd ?
.data?
	szMyDllFull    db MAX_PATH dup (?)
	lpLoadLibrary  dd ?
	dwProcessID    dd ?     
	dwThreadID     dd ?
	hProcess       dd ?
.code
	main PROC
; 准备工作:获取dll的全路径文件名、获取LoadLibrary函数地址等
		invoke GetCurrentDirectory,MAX_PATH,addr szMyDllFull
		invoke lstrcat,addr szMyDllFull,addr szMyDll
		invoke GetModuleHandle,addr szDllKernel
		invoke GetProcAddress,eax,offset szLoadLibrary
		mov lpLoadLibrary,eax

; 查找文件管理器窗口并获取进程ID,然后打开进程
		invoke FindWindow,NULL,addr lpFileName
		invoke GetWindowThreadProcessId,eax,offset dwProcessID
  		mov dwThreadID,eax
  		invoke OpenProcess,PROCESS_ALL_ACCESS,FALSE,dwProcessID
		mov hProcess,eax

; 在进程中分配空间并将DLL文件名拷贝过去,然后创建一个LoadLibrary线程
		invoke VirtualAllocEx,hProcess,NULL,MAX_PATH,MEM_COMMIT,PAGE_READWRITE
		mov lpDllName,eax
		invoke WriteProcessMemory,hProcess,eax,offset szMyDllFull,MAX_PATH,NULL
		invoke CreateRemoteThread,hProcess,NULL,0,lpLoadLibrary,lpDllName,0,NULL
		ret
	main endp
end main
上一篇:《逆向工程核心原理》——DLL注入与卸载


下一篇:Vue.set、Vue.mixin