#define WIN32_LEAN_AND_MEAN #include #include #include #include #include #include #include static std::vector read_file(const std::string& path) { std::ifstream f(path, std::ios::binary | std::ios::ate); if (!f) return {}; auto sz = (size_t)f.tellg(); f.seekg(0, std::ios::beg); std::vector buf(sz); if (!f.read((char*)buf.data(), sz)) return {}; return buf; } static uintptr_t get_local_module_base(const char* name) { HMODULE h = GetModuleHandleA(name); return (uintptr_t)h; } std::string wchar2string(const wchar_t* wideString) { if (!wideString) { return ""; } int sizeNeeded = WideCharToMultiByte(CP_UTF8, 0, wideString, -1, nullptr, 0, nullptr, nullptr); if (sizeNeeded <= 0) { return ""; } std::string ret(sizeNeeded - 1, 0); WideCharToMultiByte(CP_UTF8, 0, wideString, -1, &ret[0], sizeNeeded, nullptr, nullptr); return ret; } // find module base in remote process using Toolhelp snapshot static uintptr_t get_remote_module_base(DWORD pid, const char* modulename) { uintptr_t base = 0; HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, pid); if (snap == INVALID_HANDLE_VALUE) return 0; MODULEENTRY32 me; me.dwSize = sizeof(me); if (Module32First(snap, &me)) { do { std::string name = wchar2string(me.szModule); if (_stricmp(name.c_str(), modulename) == 0) { base = (uintptr_t)me.modBaseAddr; break; } } while (Module32Next(snap, &me)); } CloseHandle(snap); return base; } // helper: write data to remote and check static bool write_remote(HANDLE hProc, LPVOID remote, const void* data, SIZE_T size) { SIZE_T wrote = 0; bool ret = WriteProcessMemory(hProc, remote, data, size, &wrote) && wrote == size; FlushInstructionCache(hProc, remote, size); return ret; } // use CreateRemoteThread to call LoadLibraryA in target to ensure a module is loaded static uintptr_t remote_load_library(HANDLE hProc, DWORD pid, const char* dllName) { size_t nameLen = strlen(dllName) + 1; LPVOID remoteStr = VirtualAllocEx(hProc, NULL, nameLen, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); if (!remoteStr) return 0; if (!write_remote(hProc, remoteStr, dllName, nameLen)) { VirtualFreeEx(hProc, remoteStr, 0, MEM_RELEASE); return 0; } // get local LoadLibraryA address FARPROC ll = GetProcAddress(GetModuleHandleA("kernel32.dll"), "LoadLibraryA"); if (!ll) { VirtualFreeEx(hProc, remoteStr, 0, MEM_RELEASE); return 0; } HANDLE hThr = CreateRemoteThread(hProc, NULL, 0, (LPTHREAD_START_ROUTINE)ll, remoteStr, 0, NULL); if (!hThr) { VirtualFreeEx(hProc, remoteStr, 0, MEM_RELEASE); return 0; } WaitForSingleObject(hThr, INFINITE); CloseHandle(hThr); // cleanup remote string VirtualFreeEx(hProc, remoteStr, 0, MEM_RELEASE); // now try to look up module base via snapshot (may require a short delay; we do a quick retry) uintptr_t base = 0; for (int i = 0; i < 10 && !base; i++) { Sleep(10); base = get_remote_module_base(pid, dllName); } return base; } int main(int argc, char** argv) { if (argc != 3) { std::cout << "Usage: " << argv[0] << " \n"; return 1; } std::string dllPath = argv[1]; DWORD pid = (DWORD)atoi(argv[2]); // read dll bytes auto dll = read_file(dllPath); if (dll.empty()) { std::cerr << "Failed to read DLL\n"; return 2; } // basic PE checks if (dll.size() < sizeof(IMAGE_DOS_HEADER)) { std::cerr << "Not a PE\n"; return 3; } IMAGE_DOS_HEADER* dos = (IMAGE_DOS_HEADER*)dll.data(); if (dos->e_magic != IMAGE_DOS_SIGNATURE) { std::cerr << "Bad DOS sig\n"; return 4; } IMAGE_NT_HEADERS64* nth = (IMAGE_NT_HEADERS64*)(dll.data() + dos->e_lfanew); if (nth->Signature != IMAGE_NT_SIGNATURE) { std::cerr << "Bad NT sig\n"; return 5; } if (nth->OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR64_MAGIC) { std::cerr << "Not x64 DLL (this injector is x64-only)\n"; return 6; } SIZE_T imageSize = nth->OptionalHeader.SizeOfImage; SIZE_T headersSize = nth->OptionalHeader.SizeOfHeaders; uint64_t preferredBase = nth->OptionalHeader.ImageBase; HANDLE hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid); if (!hProc) { std::cerr << "OpenProcess failed: " << GetLastError() << "\n"; return 7; } // allocate remote image memory (we choose any address; relocations will be applied) LPVOID remoteImage = VirtualAllocEx(hProc, NULL, imageSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); if (!remoteImage) { std::cerr << "VirtualAllocEx failed: " << GetLastError() << "\n"; CloseHandle(hProc); return 8; } // prepare a local working copy to apply relocations & fill IAT with *remote* addresses std::vector localImage(imageSize); memset(localImage.data(), 0, imageSize); // copy headers memcpy(localImage.data(), dll.data(), headersSize); // copy sections IMAGE_SECTION_HEADER* sect = IMAGE_FIRST_SECTION(nth); for (unsigned i = 0; i < nth->FileHeader.NumberOfSections; ++i) { const IMAGE_SECTION_HEADER& s = sect[i]; if (s.SizeOfRawData > 0 && s.PointerToRawData + s.SizeOfRawData <= dll.size()) { memcpy(localImage.data() + s.VirtualAddress, dll.data() + s.PointerToRawData, s.SizeOfRawData); } else { // zero virtual size if (s.Misc.VirtualSize) memset(localImage.data() + s.VirtualAddress, 0, s.Misc.VirtualSize); } } // 1) Relocations: apply relocations in localImage for target base = remoteImage intptr_t delta = (intptr_t)((uintptr_t)remoteImage - (intptr_t)preferredBase); if (delta != 0 && nth->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size) { auto relocDirRVA = nth->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress; auto reloc = (IMAGE_BASE_RELOCATION*)(localImage.data() + relocDirRVA); uint64_t iatVal = 0; ReadProcessMemory(hProc, (uint8_t*)remoteImage + relocDirRVA, &iatVal, sizeof(iatVal), nullptr); printf("IAT[0] = 0x%llX\n", iatVal); SIZE_T processed = 0; SIZE_T relocSize = nth->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size; while (processed < relocSize && reloc->SizeOfBlock) { DWORD entries = (reloc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD); WORD* list = (WORD*)((uint8_t*)reloc + sizeof(IMAGE_BASE_RELOCATION)); for (DWORD i = 0; i < entries; ++i) { WORD e = list[i]; WORD type = e >> 12; WORD offset = e & 0x0FFF; uint8_t* where = localImage.data() + reloc->VirtualAddress + offset; if (type == IMAGE_REL_BASED_DIR64) { uint64_t* p = (uint64_t*)where; *p = (uint64_t)((intptr_t)(*p) + delta); } else if (type == IMAGE_REL_BASED_HIGHLOW) { uint32_t* p = (uint32_t*)where; *p = (uint32_t)((intptr_t)(*p) + (uint32_t)delta); } // other types ignored } processed += reloc->SizeOfBlock; reloc = (IMAGE_BASE_RELOCATION*)((uint8_t*)reloc + reloc->SizeOfBlock); } } // 2) Resolve imports: for each imported DLL, ensure it is loaded in remote (or load it), then write remote function addresses into IAT auto importDir = nth->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]; if (importDir.Size) { auto importDesc = (IMAGE_IMPORT_DESCRIPTOR*)(localImage.data() + importDir.VirtualAddress); while (importDesc->Name) { char* dllName = (char*)(localImage.data() + importDesc->Name); // ensure remote has this module, otherwise load it remotely uintptr_t remoteModBase = get_remote_module_base(pid, dllName); if (remoteModBase == 0) { // attempt remote LoadLibraryA remoteModBase = remote_load_library(hProc, pid, dllName); if (remoteModBase == 0) { std::cerr << "Failed to ensure remote module loaded: " << dllName << "\n"; VirtualFreeEx(hProc, remoteImage, 0, MEM_RELEASE); CloseHandle(hProc); return 9; } std::cout << "Loaded remote module " << dllName << " at " << std::hex << remoteModBase << std::dec << "\n"; } // local handle for computing offset HMODULE localHM = LoadLibraryA(dllName); // should succeed (we are likely to have same system DLLs) if (!localHM) { std::cerr << "LoadLibraryA locally failed for " << dllName << "\n"; VirtualFreeEx(hProc, remoteImage, 0, MEM_RELEASE); CloseHandle(hProc); return 10; } uintptr_t localBase = (uintptr_t)localHM; IMAGE_THUNK_DATA64* thunkILT = (IMAGE_THUNK_DATA64*)(localImage.data() + importDesc->OriginalFirstThunk); IMAGE_THUNK_DATA64* thunkIAT = (IMAGE_THUNK_DATA64*)(localImage.data() + importDesc->FirstThunk); if (!importDesc->OriginalFirstThunk) thunkILT = thunkIAT; while (thunkILT->u1.AddressOfData) { uintptr_t funcAddrRemote = 0; if (thunkILT->u1.Ordinal & IMAGE_ORDINAL_FLAG64) { // import by ordinal WORD ord = IMAGE_ORDINAL64(thunkILT->u1.Ordinal); FARPROC localFunc = GetProcAddress(localHM, (LPCSTR)(uintptr_t)ord); if (!localFunc) { std::cerr << "GetProcAddress local by ordinal failed\n"; VirtualFreeEx(hProc, remoteImage, 0, MEM_RELEASE); CloseHandle(hProc); return 11; } uintptr_t localFuncAddr = (uintptr_t)localFunc; uintptr_t offset = localFuncAddr - localBase; funcAddrRemote = remoteModBase + offset; } else { IMAGE_IMPORT_BY_NAME* ibn = (IMAGE_IMPORT_BY_NAME*)(localImage.data() + thunkILT->u1.AddressOfData); const char* name = (const char*)ibn->Name; FARPROC localFunc = GetProcAddress(localHM, name); if (!localFunc) { std::cerr << "GetProcAddress local failed for " << name << "\n"; VirtualFreeEx(hProc, remoteImage, 0, MEM_RELEASE); CloseHandle(hProc); return 12; } uintptr_t localFuncAddr = (uintptr_t)localFunc; uintptr_t offset = localFuncAddr - localBase; funcAddrRemote = remoteModBase + offset; } // write the remote function pointer into the IAT entry in localImage thunkIAT->u1.Function = (ULONGLONG)funcAddrRemote; ++thunkILT; ++thunkIAT; } ++importDesc; } } // 3) write the prepared localImage into remote memory if (!write_remote(hProc, remoteImage, localImage.data(), imageSize)) { std::cerr << "WriteProcessMemory image failed: " << GetLastError() << "\n"; VirtualFreeEx(hProc, remoteImage, 0, MEM_RELEASE); CloseHandle(hProc); return 13; } // 4) set proper protections for sections in remote (iterate section headers and call VirtualProtectEx) for (unsigned i = 0; i < nth->FileHeader.NumberOfSections; ++i) { const IMAGE_SECTION_HEADER& s = sect[i]; LPVOID secAddr = (LPVOID)((uintptr_t)remoteImage + s.VirtualAddress); SIZE_T secSize = s.Misc.VirtualSize ? s.Misc.VirtualSize : s.SizeOfRawData; if (secSize == 0) continue; DWORD protect = PAGE_NOACCESS; bool isExec = (s.Characteristics & IMAGE_SCN_MEM_EXECUTE) != 0; bool isRead = (s.Characteristics & IMAGE_SCN_MEM_READ) != 0; bool isWrite = (s.Characteristics & IMAGE_SCN_MEM_WRITE) != 0; if (isExec) { if (isRead) protect = isWrite ? PAGE_EXECUTE_READWRITE : PAGE_EXECUTE_READ; else protect = isWrite ? PAGE_EXECUTE_WRITECOPY : PAGE_EXECUTE; } else { if (isRead) protect = isWrite ? PAGE_READWRITE : PAGE_READONLY; else protect = isWrite ? PAGE_WRITECOPY : PAGE_NOACCESS; } DWORD old = 0; if (!VirtualProtectEx(hProc, secAddr, secSize, protect, &old)) { // not fatal; continue } } // 5) Build a small x64 trampoline that calls DllMain(remoteImage, DLL_PROCESS_ATTACH, NULL) // trampoline (assembly sequence): // mov rcx, ; rcx = hModule // mov rdx, 2 ; rdx = DLL_PROCESS_ATTACH // xor r8, r8 ; r8 = NULL // mov rax, ; rax = entry // call rax // xor ecx, ecx ; set return 0 // ret // // Machine bytes assembled at runtime (little-endian imm64) uint64_t remoteBaseAddr = (uint64_t)remoteImage; uint64_t entryRVA = (uint64_t)nth->OptionalHeader.AddressOfEntryPoint; uint64_t remoteEntry = remoteBaseAddr + entryRVA; std::vector tramp; // mov rcx, imm64 tramp.push_back(0x48); tramp.push_back(0xB9); for (int i = 0; i < 8; i++) tramp.push_back((uint8_t)((remoteBaseAddr >> (8 * i)) & 0xFF)); // mov rdx, imm64 (2) tramp.push_back(0x48); tramp.push_back(0xBA); uint64_t dword2 = 2; for (int i = 0; i < 8; i++) tramp.push_back((uint8_t)((dword2 >> (8 * i)) & 0xFF)); // xor r8, r8 tramp.push_back(0x4D); tramp.push_back(0x31); tramp.push_back(0xC0); // mov rax, imm64 (entry) tramp.push_back(0x48); tramp.push_back(0xB8); for (int i = 0; i < 8; i++) tramp.push_back((uint8_t)((remoteEntry >> (8 * i)) & 0xFF)); // call rax tramp.push_back(0xFF); tramp.push_back(0xD0); // xor eax,eax tramp.push_back(0x33); tramp.push_back(0xC0); // ret tramp.push_back(0xC3); // write trampoline to remote LPVOID remoteTramp = VirtualAllocEx(hProc, NULL, tramp.size(), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); if (!remoteTramp) { std::cerr << "VirtualAllocEx tramp failed\n"; VirtualFreeEx(hProc, remoteImage, 0, MEM_RELEASE); CloseHandle(hProc); return 14; } if (!write_remote(hProc, remoteTramp, tramp.data(), tramp.size())) { std::cerr << "WriteProcessMemory tramp failed: " << GetLastError() << "\n"; VirtualFreeEx(hProc, remoteTramp, 0, MEM_RELEASE); VirtualFreeEx(hProc, remoteImage, 0, MEM_RELEASE); CloseHandle(hProc); return 15; } std::cout << "Press Enter to execute remote DllMain..."; std::cin.get(); // execute remote trampoline HANDLE hThread = CreateRemoteThread(hProc, NULL, 0, (LPTHREAD_START_ROUTINE)remoteTramp, NULL, 0, NULL); if (!hThread) { std::cerr << "CreateRemoteThread failed: " << GetLastError() << "\n"; VirtualFreeEx(hProc, remoteTramp, 0, MEM_RELEASE); VirtualFreeEx(hProc, remoteImage, 0, MEM_RELEASE); CloseHandle(hProc); return 16; } WaitForSingleObject(hThread, INFINITE); DWORD exitCode = 0; GetExitCodeThread(hThread, &exitCode); std::cout << "Remote DllMain returned: 0x" << std::hex << exitCode << "\n"; // cleanup trampoline (optionally keep it) VirtualFreeEx(hProc, remoteTramp, 0, MEM_RELEASE); CloseHandle(hThread); CloseHandle(hProc); std::cout << "Done. Remote image at " << remoteImage << "\n"; return 0; }