如何在 C# 程序中注入恶意 DLL ?

一:背景前段时间在训练营上课的时候就有朋友提到一个问题 , 为什么 Windbg 附加到 C# 程序后 , 程序就处于中断状态了?它到底是如何实现的?其实简而言之就是线程的远程注入 , 这一篇就展开说一下 。
 
二:实现原理1. 基本思路WinDbg 在附加进程的时候 , 会注入一个线程到 C# 进程中 , 注入成功后 , 会执行一个DbgBreakPoint函数 , 其实就是int 3 , 这时候 CPU 就会执行 3 号中断函数 , 将当前进程的所有线程进行暂停 , 文字不好理解的话 , 画一个图大概就是这样 。

如何在 C# 程序中注入恶意 DLL ?

文章插图
口说无凭 , 接下来用上一个简单案例演示一下 。
 
2. 案例演示首先写一个简单的 C# 程序 , 不断的输出时间和标号 , 代码如下:
internal class Program{static void Main(string[] args){for (int i = 0; i < 10000; i++){Console.WriteLine($"{DateTime.Now},i={i}");Thread.Sleep(1000);}}}把程序跑起来后 , 使用 WinDbg 附加 , 你可以发现 Command 自动切换到了 8号线程 , 通过 k 命令可以看到最上面是一个 int 3 中断 , 截图如下:
如何在 C# 程序中注入恶意 DLL ?

文章插图
这里就有一个想法了 , 既然 WinDbg 可以注入 , 为何我的程序就注入不得呢?既然我的程序可以注入 , 那就可以做一些我想做的事情 。
 
3. 自定义注入有了自定义注入的想法 , 接下来的实现步骤大概是这样的 。
  1. 注入一个线程到 C# 程序中 。
  2. 让程序加载一个 dll 文件 。
  3. 在 dll 中做一些我想做的业务逻辑 。
接下来新建一个 C++ 的动态链接库 , 在 DLLMain 入口函数的 DLL_PROCESS_ATTACH事件中写一个 printf 函数 , 如果在 C# 程序中输出来了 , 就算成功注入了 , 参考代码如下:
【如何在 C# 程序中注入恶意 DLL ?】#include <windows.h>#include <stdio.h>BOOL APIENTRY DllMain(HMODULE hModule,Dword ul_reason_for_call,LPVOID lpReserved){switch (ul_reason_for_call){case DLL_PROCESS_ATTACH:printf(" 总部 , 总部 , 我已经成功打入内部!ul_reason_for_call=%dn ", ul_reason_for_call);break;}return TRUE;}要被加载的 MyInject.dll已经构建完毕 , 接下来就用 Win32 API 的CreateRemoteThread实现远程注入 , 但注入之前需要做三件事情 。
  1. 获取 C# 程序的 进程句柄 。
  2. 在 C# 进程中申请一块内存空间 , 存放加载的 path 路径 。
  3. 调用 LoadLibraryW 函数在 C# 进程中实现 dll 加载 。
过程有了 , 新建一个 C++ 控制台程序 ConsoleApplication1.exe ,  整体的参考代码如下:
#include <IOStream>#include <Windows.h>#include <stdio.h>#include <stdlib.h>#include <Tlhelp32.h>DWORD GetPid(const WCHAR* szName){HANDLE hprocessSnap = ;PROCESSENTRY32 pe32 = { 0 };hprocessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);pe32.dwSize = sizeof(PROCESSENTRY32);if (Process32First(hprocessSnap, &pe32)){do {if (!wcscmp(szName, pe32.szExeFile)) {return (int)pe32.th32ProcessID;}} while (Process32Next(hprocessSnap, &pe32));}else{CloseHandle(hprocessSnap);}return 0;}int main{const wchar_t* path = L"D:.NET6\ConsoleApp1\x64\Debug\MyInject.dll"; //要注入的dll文件地址//1. 获取进程IDDWORD procID = GetPid(L"ConsoleApp4.exe");//2. 获取进程句柄HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, procID);//3. 在目标进程中开辟一块空间LPVOID pRemoteAdress = VirtualAllocEx(hProcess, , wcslen(path) * 2, MEM_COMMIT, PAGE_READWRITE);//4. 将 path 写入到这块空间中BOOL bRet = WriteProcessMemory(hProcess, pRemoteAdress, path, wcslen(path) * 2, );//5. 让目标线程调用 LoadLibraryW 加载我们注入的 dllHMODULE hModule = GetModuleHandle(L"kernel32.dll");LPTHREAD_START_ROUTINE dwLoadAddr = (LPTHREAD_START_ROUTINE)GetProcAddress(hModule, "LoadLibraryW");HANDLE hThread = CreateRemoteThread(hProcess,,0,(LPTHREAD_START_ROUTINE)dwLoadAddr,pRemoteAdress,,);//6. 函数执行完后 , 释放这块空间 。WaitForSingleObject(hThread, -1);VirtualFreeEx(hProcess, pRemoteAdress, 1, MEM_DECOMMIT);system("pause");return 0;}


推荐阅读