前言

一些程序会利用CreateRemoteThread这个函数为其他进程创建一个线程,而传入的线程回调函数地址为kernel32.dllLoadLibraryA函数。
这个函数最原始的功能是用来加载动态库的,而且这个函数可以和最原始的线程回调
函数声明是相同的。因此一些Hack利用这个特性来完成一些跨进程dll注入。

我们首先观察下这个函数

//kernel32.dll 
HMODULE
WINAPI
LoadLibraryA(_In_ LPCSTR lpLibFileName);

在对比下最原始的线程回调函数

typedef DWORD (WINAPI *PTHREAD_START_ROUTINE)(
    LPVOID lpThreadParameter
    );

可以发现两个函数传入的参数是一样的(单个参数且都可以视为char*),所以我们利用这个特性完成一些奇淫技巧(传入的参数为dll地址那不就可以加载一个库了吗)。

我们首先定下一个目标程序给其注入一个dll,并打印一个弹出窗口。

目标程序这里选取的计算器
在这里插入图片描述
我们的dll代码如下:

//dll被加载的时候会回调这个函数
BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{

	OutputDebugString("Your was injected by test ");

    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
		MessageBox(NULL, "成功注入dll", "注入提示", MB_OK);
		break;
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}


预期效果如下:
在这里插入图片描述

我们首先看CreateRemoteThread文档
CreateRemoteThread 文档

HANDLE CreateRemoteThread(
  HANDLE                 hProcess,//指定为哪一个进程创建线程
  LPSECURITY_ATTRIBUTES  lpThreadAttributes,//安全属性
  SIZE_T                 dwStackSize, //指定栈大小
  LPTHREAD_START_ROUTINE lpStartAddress,//回调函数
  LPVOID                 lpParameter,//回调的参数
  DWORD                  dwCreationFlags,//创建一些标识符 我们传0即可
  LPDWORD                lpThreadId //线程id 可以传NULL
);

我们的大致流程:

  1. 寻找计算器的句柄
  2. 获取kernel32模块中的LoadLibraryA函数地址。
  3. 将dll字符串的内容拷贝到计算器进程
  4. 调用CreateRemoteThread为计算器进程创建线程,回调函数为LoadLibraryA,回调函数的传入参数为dll目录字符串地址
  5. 资源释放


int main()
{

	/**
	第一步:寻找计算器的句柄
	**/
	//返回窗口句柄
	HWND hWndCalc = FindWindow(NULL, "计算器");
	DWORD wdproId = 0;

	//获取窗口句柄对应的线程ID https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getwindowthreadprocessid
	GetWindowThreadProcessId(hWndCalc, &wdproId);

	//返回进程的句柄
	HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, wdproId);


	/**
	第二步: 获取`kernel32`模块中的`LoadLibraryA`函数地址
	**/
	//得到加载kernel32模块的句柄
	HMODULE hModker = GetModuleHandle("kernel32");

	//从指定的动态链接库 (DLL) 中检索导出函数或变量的地址。
	LPVOID pLoadLibrary = GetProcAddress(hModker, "LoadLibraryA");


	/**
	第三步:  将dll字符串的内容拷贝到`计算器`进程
	**/
	char  szDllPath[] = { "C:\\Users\\fmy\\source\\repos\\MyDllProject\\x64\\Debug\\MyDllProject.dll" };
	//https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualallocex
	LPVOID  pPathBuf = VirtualAllocEx(hProcess, NULL, sizeof(szDllPath), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);

	if (pPathBuf == NULL)
	{
		return 0;

	}
	SIZE_T writeWord;
	WriteProcessMemory(hProcess, pPathBuf, szDllPath, sizeof(szDllPath), &writeWord);
	/**
	第四步:  调用`CreateRemoteThread`为计算器进程创建线程,回调函数为`LoadLibraryA`,回调函数的传入参数为dll目录字符串地址
	**/
	HANDLE hTread = CreateRemoteThread(
		hProcess,
		NULL,
		0,
		(LPTHREAD_START_ROUTINE)pLoadLibrary,
		(LPVOID)pPathBuf,
		0,
		NULL
	);


	/**
	第五步:资源释放
	**/
	WaitForSingleObject(hTread, INFINITE);
	BOOL result = VirtualFreeEx(hProcess, pPathBuf, 0, MEM_RELEASE);
	CloseHandle(hTread);


	return EXIT_SUCCESS;
}

需要注意一点:你要特别留意目标进程和dll库以及注入进程的位数必须要相同。比如全部都是64位等

为什么需要第三步: 将dll字符串的内容拷贝到计算器进程 ?
因为这个字符串内容现在在你的进程内存中,而对于计算器进程来说是不可以访问到的

实操

一个小实验项目:我们给notepad++.exe注入一个DLL.这个DLL库会在notepad++.exe注入一个菜单按钮。
如下图所示:
在这里插入图片描述
在这里插入图片描述
上面有两个二级菜单:

  1. 弹出消息框:弹出一个信息框
  2. 卸载:卸载被注入的dll

上面我们要响应点击事件必然要监听消息分发。(替换窗口过程函数)

我们看下被注入dll源代码:

#include "pch.h"
#include <windows.h>
#include <strsafe.h>

//最原始窗口notepad++.过程函数
WNDPROC g_pfnOldProc = NULL;
//当前被注入的dll
HMODULE hModule;

//将要替换最原始的g_pfnOldProc 窗口函数的声明
LRESULT CALLBACK NewWndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam);


//执行注入代码
void InjectCalc() {
	//寻找窗口句柄
	HWND hWnd = ::FindWindow(NULL, "new 1 - Notepad++");
 
	if (hWnd == NULL)
	{
		MessageBox(NULL, "没有寻找到句柄", "注入提示3", MB_OK);
		return;
	}
    //获取菜单
	HMENU hmenu = ::GetMenu(hWnd);
	//创建一个新的菜单
	HMENU hNewMenu = ::CreatePopupMenu();//创建个菜单
	//插入菜单
	::InsertMenu(hmenu, 0, MF_BYPOSITION| MF_STRING| MF_POPUP, (UINT_PTR)hNewMenu, "注入菜单");
	::AppendMenu(hNewMenu, MF_STRING, 65534, "弹出消息框");//子菜单
	::AppendMenu(hNewMenu, MF_STRING, 65535, "卸载");//子菜单

	//替换过程函数,因为你需要监听菜单点击事件
	if (::IsWindowUnicode(hWnd))
	{
		//SetWindowLongW用户替换旧的窗口属性
		//GWLP_WNDPROC用于指定为替换旧的过程函数,然后返回旧的的窗口过程函数
		//返回旧的窗口函数地址
		g_pfnOldProc = (WNDPROC)::SetWindowLongPtrW(hWnd, GWLP_WNDPROC, (LONG_PTR)NewWndProc);
	}else
	{
		g_pfnOldProc = (WNDPROC)::SetWindowLongPtrA(hWnd, GWLP_WNDPROC, (LONG_PTR)NewWndProc);
	}

	MessageBox(NULL, "成功注入dll", "注入提示3", MB_OK);
}

//dll被加载的时候会回调
BOOL APIENTRY DllMain(HMODULE hModule,
	DWORD  ul_reason_for_call,
	LPVOID lpReserved
)
{
	switch (ul_reason_for_call)
	{
	case DLL_PROCESS_ATTACH:
		::hModule = hModule;
		MessageBox(NULL, "成功注入dll", "注入提示232", MB_OK);
		InjectCalc();
		break;
	case DLL_THREAD_ATTACH:
	case DLL_THREAD_DETACH:
	case DLL_PROCESS_DETACH:
		break;
	}
	return TRUE;
}

LRESULT CALLBACK NewWndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam) {

	if (nMsg == WM_COMMAND)
	{
		//卸载按钮点击
		if (LOWORD(wParam) == 65535)
		{
			MessageBox(NULL, "卸载模块成功", "注入提示", MB_OK);
			//替换回过程函数
			if (::IsWindowUnicode(hWnd))
			{
				//SetWindowLongW用户替换旧的窗口属性
				//GWLP_WNDPROC用于指定为替换旧的过程函数,然后返回旧的的窗口过程函数
				(WNDPROC)::SetWindowLongPtrW(hWnd, GWLP_WNDPROC, (LONG_PTR)g_pfnOldProc);
			}
			else
			{
			 (WNDPROC)::SetWindowLongPtrA(hWnd, GWLP_WNDPROC, (LONG_PTR)g_pfnOldProc);
			}
					
			//重画状态栏
			HMENU hmenu = ::GetMenu(hWnd);
			DeleteMenu(hmenu,0, MF_BYPOSITION);
			DrawMenuBar(hWnd);
			//释放module库,这里直接调用FreeLibrary(hModule)回导致后面的代码异常,因为你已经移除了module内存
			::CreateThread(NULL,0, (LPTHREAD_START_ROUTINE)FreeLibrary, hModule,0,NULL);
			//释放module
			//::FreeLibrary(hModule);
			return 0;
		}

		else if (LOWORD(wParam) == 65534)
		{
			MessageBox(NULL, "你好", "注入提示", MB_OK);

		}
	}

	return g_pfnOldProc(hWnd, nMsg, wParam, lParam);

}
Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐