Win32 利用远程线程注入dll

分手后的思念是犯贱 2022-10-08 05:50 354阅读 0赞

前言

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

我们首先观察下这个函数

  1. //kernel32.dll
  2. HMODULE
  3. WINAPI
  4. LoadLibraryA(_In_ LPCSTR lpLibFileName);

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

  1. typedef DWORD (WINAPI *PTHREAD_START_ROUTINE)(
  2. LPVOID lpThreadParameter
  3. );

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

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

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

  1. //dll被加载的时候会回调这个函数
  2. BOOL APIENTRY DllMain( HMODULE hModule,
  3. DWORD ul_reason_for_call,
  4. LPVOID lpReserved
  5. )
  6. {
  7. OutputDebugString("Your was injected by test ");
  8. switch (ul_reason_for_call)
  9. {
  10. case DLL_PROCESS_ATTACH:
  11. MessageBox(NULL, "成功注入dll", "注入提示", MB_OK);
  12. break;
  13. case DLL_THREAD_ATTACH:
  14. case DLL_THREAD_DETACH:
  15. case DLL_PROCESS_DETACH:
  16. break;
  17. }
  18. return TRUE;
  19. }

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

我们首先看CreateRemoteThread文档
CreateRemoteThread 文档

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

我们的大致流程:

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

    int main()
    {

  1. /**
  2. 第一步:寻找计算器的句柄
  3. **/
  4. //返回窗口句柄
  5. HWND hWndCalc = FindWindow(NULL, "计算器");
  6. DWORD wdproId = 0;
  7. //获取窗口句柄对应的线程ID https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getwindowthreadprocessid
  8. GetWindowThreadProcessId(hWndCalc, &wdproId);
  9. //返回进程的句柄
  10. HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, wdproId);
  11. /**
  12. 第二步: 获取`kernel32`模块中的`LoadLibraryA`函数地址
  13. **/
  14. //得到加载kernel32模块的句柄
  15. HMODULE hModker = GetModuleHandle("kernel32");
  16. //从指定的动态链接库 (DLL) 中检索导出函数或变量的地址。
  17. LPVOID pLoadLibrary = GetProcAddress(hModker, "LoadLibraryA");
  18. /**
  19. 第三步: 将dll字符串的内容拷贝到`计算器`进程
  20. **/
  21. char szDllPath[] = {
  22. "C:\\Users\\fmy\\source\\repos\\MyDllProject\\x64\\Debug\\MyDllProject.dll" };
  23. //https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualallocex
  24. LPVOID pPathBuf = VirtualAllocEx(hProcess, NULL, sizeof(szDllPath), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
  25. if (pPathBuf == NULL)
  26. {
  27. return 0;
  28. }
  29. SIZE_T writeWord;
  30. WriteProcessMemory(hProcess, pPathBuf, szDllPath, sizeof(szDllPath), &writeWord);
  31. /**
  32. 第四步: 调用`CreateRemoteThread`为计算器进程创建线程,回调函数为`LoadLibraryA`,回调函数的传入参数为dll目录字符串地址
  33. **/
  34. HANDLE hTread = CreateRemoteThread(
  35. hProcess,
  36. NULL,
  37. 0,
  38. (LPTHREAD_START_ROUTINE)pLoadLibrary,
  39. (LPVOID)pPathBuf,
  40. 0,
  41. NULL
  42. );
  43. /**
  44. 第五步:资源释放
  45. **/
  46. WaitForSingleObject(hTread, INFINITE);
  47. BOOL result = VirtualFreeEx(hProcess, pPathBuf, 0, MEM_RELEASE);
  48. CloseHandle(hTread);
  49. return EXIT_SUCCESS;
  50. }

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

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

实操

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

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

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

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

  1. #include "pch.h"
  2. #include <windows.h>
  3. #include <strsafe.h>
  4. //最原始窗口notepad++.过程函数
  5. WNDPROC g_pfnOldProc = NULL;
  6. //当前被注入的dll
  7. HMODULE hModule;
  8. //将要替换最原始的g_pfnOldProc 窗口函数的声明
  9. LRESULT CALLBACK NewWndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam);
  10. //执行注入代码
  11. void InjectCalc() {
  12. //寻找窗口句柄
  13. HWND hWnd = ::FindWindow(NULL, "new 1 - Notepad++");
  14. if (hWnd == NULL)
  15. {
  16. MessageBox(NULL, "没有寻找到句柄", "注入提示3", MB_OK);
  17. return;
  18. }
  19. //获取菜单
  20. HMENU hmenu = ::GetMenu(hWnd);
  21. //创建一个新的菜单
  22. HMENU hNewMenu = ::CreatePopupMenu();//创建个菜单
  23. //插入菜单
  24. ::InsertMenu(hmenu, 0, MF_BYPOSITION| MF_STRING| MF_POPUP, (UINT_PTR)hNewMenu, "注入菜单");
  25. ::AppendMenu(hNewMenu, MF_STRING, 65534, "弹出消息框");//子菜单
  26. ::AppendMenu(hNewMenu, MF_STRING, 65535, "卸载");//子菜单
  27. //替换过程函数,因为你需要监听菜单点击事件
  28. if (::IsWindowUnicode(hWnd))
  29. {
  30. //SetWindowLongW用户替换旧的窗口属性
  31. //GWLP_WNDPROC用于指定为替换旧的过程函数,然后返回旧的的窗口过程函数
  32. //返回旧的窗口函数地址
  33. g_pfnOldProc = (WNDPROC)::SetWindowLongPtrW(hWnd, GWLP_WNDPROC, (LONG_PTR)NewWndProc);
  34. }else
  35. {
  36. g_pfnOldProc = (WNDPROC)::SetWindowLongPtrA(hWnd, GWLP_WNDPROC, (LONG_PTR)NewWndProc);
  37. }
  38. MessageBox(NULL, "成功注入dll", "注入提示3", MB_OK);
  39. }
  40. //dll被加载的时候会回调
  41. BOOL APIENTRY DllMain(HMODULE hModule,
  42. DWORD ul_reason_for_call,
  43. LPVOID lpReserved
  44. )
  45. {
  46. switch (ul_reason_for_call)
  47. {
  48. case DLL_PROCESS_ATTACH:
  49. ::hModule = hModule;
  50. MessageBox(NULL, "成功注入dll", "注入提示232", MB_OK);
  51. InjectCalc();
  52. break;
  53. case DLL_THREAD_ATTACH:
  54. case DLL_THREAD_DETACH:
  55. case DLL_PROCESS_DETACH:
  56. break;
  57. }
  58. return TRUE;
  59. }
  60. LRESULT CALLBACK NewWndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam) {
  61. if (nMsg == WM_COMMAND)
  62. {
  63. //卸载按钮点击
  64. if (LOWORD(wParam) == 65535)
  65. {
  66. MessageBox(NULL, "卸载模块成功", "注入提示", MB_OK);
  67. //替换回过程函数
  68. if (::IsWindowUnicode(hWnd))
  69. {
  70. //SetWindowLongW用户替换旧的窗口属性
  71. //GWLP_WNDPROC用于指定为替换旧的过程函数,然后返回旧的的窗口过程函数
  72. (WNDPROC)::SetWindowLongPtrW(hWnd, GWLP_WNDPROC, (LONG_PTR)g_pfnOldProc);
  73. }
  74. else
  75. {
  76. (WNDPROC)::SetWindowLongPtrA(hWnd, GWLP_WNDPROC, (LONG_PTR)g_pfnOldProc);
  77. }
  78. //重画状态栏
  79. HMENU hmenu = ::GetMenu(hWnd);
  80. DeleteMenu(hmenu,0, MF_BYPOSITION);
  81. DrawMenuBar(hWnd);
  82. //释放module库,这里直接调用FreeLibrary(hModule)回导致后面的代码异常,因为你已经移除了module内存
  83. ::CreateThread(NULL,0, (LPTHREAD_START_ROUTINE)FreeLibrary, hModule,0,NULL);
  84. //释放module
  85. //::FreeLibrary(hModule);
  86. return 0;
  87. }
  88. else if (LOWORD(wParam) == 65534)
  89. {
  90. MessageBox(NULL, "你好", "注入提示", MB_OK);
  91. }
  92. }
  93. return g_pfnOldProc(hWnd, nMsg, wParam, lParam);
  94. }

发表评论

表情:
评论列表 (有 0 条评论,354人围观)

还没有评论,来说两句吧...

相关阅读

    相关 win32线

    线程是不能但对存在的,其必须存在在进程的地址空间中。一个线程在这段地址空间仅有两样东西 一个线程的内核对象,操作系统使用这个数据结构来管理线程。 一个线程栈,其中

    相关 Win32_1-线创建

    线程句柄与线程ID: 线程是由Windows内核负责创建与管理的,句柄相当于一个令牌,有了这个令牌就可以使用线程对象. 线程ID是身份证,唯一的,系统进行线程调度的时候要使