├── debugger_ss.jpg ├── .gitignore ├── src ├── src.vcxproj.user ├── main.cpp ├── edmapper.hpp ├── src.vcxproj.filters ├── hook │ ├── shellcode.hpp │ └── iat_hook.hpp ├── memory │ └── memory_handlers.hpp ├── edmapper.cpp ├── src.vcxproj └── pe │ └── portable_executable.hpp ├── EDMapper.sln └── README.md /debugger_ss.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeroaceee/EDMapper/HEAD/debugger_ss.jpg -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # useless visual studio files , executables and objects 2 | /.vs 3 | /x64 4 | /src/x64 -------------------------------------------------------------------------------- /src/src.vcxproj.user: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "edmapper.hpp" 2 | 3 | int main(int argc, char** argv) 4 | { 5 | // check if file exists. 6 | 7 | if (!std::filesystem::exists(argv[2])) 8 | { 9 | std::cerr << "[-]file path is invalid" << '\n'; 10 | return -1; 11 | } 12 | 13 | // check if its a dll file 14 | if (std::filesystem::path(argv[2]).extension().string().compare(".dll") == -1) { 15 | std::cerr << "[-]file is not a dll" << '\n'; 16 | return -1; 17 | } 18 | 19 | std::unique_ptr dll = std::make_unique(); 20 | 21 | if (!dll->map_dll(argv[1], argv[2], argv[3])) 22 | { 23 | std::cerr << "[-]Failed to map dll." << '\n'; 24 | return -1; 25 | } 26 | 27 | std::printf("DLL mapped!\n"); 28 | 29 | std::cin.get(); 30 | } -------------------------------------------------------------------------------- /src/edmapper.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "memory/memory_handlers.hpp" 7 | #include "pe/portable_executable.hpp" 8 | #include "hook/iat_hook.hpp" 9 | #include "hook/shellcode.hpp" 10 | 11 | 12 | 13 | 14 | namespace Edmapper { 15 | 16 | class dll_map 17 | { 18 | // private by default 19 | std::uint32_t process_id = 0; 20 | std::uint8_t* rawDll_data = nullptr; 21 | std::size_t rawDll_dataSize = 0; 22 | PIMAGE_NT_HEADERS pnt_headers = nullptr; 23 | PVOID m_image = nullptr; 24 | PVOID l_image = nullptr; 25 | PVOID pShellCode = nullptr; 26 | public: 27 | dll_map() = default; 28 | ~dll_map(); 29 | bool map_dll(const std::string_view proccess_name, const std::string_view dll_path,const std::string_view iat_functionName_to_hijack); 30 | }; 31 | } 32 | 33 | -------------------------------------------------------------------------------- /EDMapper.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30204.135 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "src", "src\src.vcxproj", "{99331F9D-CD24-4E6E-AB14-46A7D4B1F60F}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|x64 = Debug|x64 11 | Debug|x86 = Debug|x86 12 | Release|x64 = Release|x64 13 | Release|x86 = Release|x86 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {99331F9D-CD24-4E6E-AB14-46A7D4B1F60F}.Debug|x64.ActiveCfg = Debug|x64 17 | {99331F9D-CD24-4E6E-AB14-46A7D4B1F60F}.Debug|x64.Build.0 = Debug|x64 18 | {99331F9D-CD24-4E6E-AB14-46A7D4B1F60F}.Debug|x86.ActiveCfg = Debug|Win32 19 | {99331F9D-CD24-4E6E-AB14-46A7D4B1F60F}.Debug|x86.Build.0 = Debug|Win32 20 | {99331F9D-CD24-4E6E-AB14-46A7D4B1F60F}.Release|x64.ActiveCfg = Release|x64 21 | {99331F9D-CD24-4E6E-AB14-46A7D4B1F60F}.Release|x64.Build.0 = Release|x64 22 | {99331F9D-CD24-4E6E-AB14-46A7D4B1F60F}.Release|x86.ActiveCfg = Release|Win32 23 | {99331F9D-CD24-4E6E-AB14-46A7D4B1F60F}.Release|x86.Build.0 = Release|Win32 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {42FC91DF-D4C7-4BA2-810E-81AB4B3F535E} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /src/src.vcxproj.filters: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;c++;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | {8a862081-6242-49f0-b106-69d8dee90bfd} 18 | 19 | 20 | {ca1c1df1-c158-4a5b-bf75-9a9fe7a29263} 21 | 22 | 23 | {e09b62a3-1fc3-4e1e-90fc-d7dcc55126df} 24 | 25 | 26 | 27 | 28 | Source Files 29 | 30 | 31 | Source Files 32 | 33 | 34 | 35 | 36 | Source Files\memory 37 | 38 | 39 | Source Files\PortableExe 40 | 41 | 42 | Header Files 43 | 44 | 45 | Source Files\hook 46 | 47 | 48 | Source Files\hook 49 | 50 | 51 | -------------------------------------------------------------------------------- /src/hook/shellcode.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | namespace assembly{ 5 | 6 | inline byte shellcode[] = 7 | { 8 | 0x9C, // pushf (save flags) 9 | 0x50, // push rax 10 | 0x53, // push rbx 11 | 0x51, // push rcx 12 | 0x52, // push rdx 13 | 0x56, // push rsi 14 | 0x57, // push rdi 15 | 0x41, 0x50, // push r8 16 | 0x41, 0x51, // push r9 17 | 0x41, 0x52, // push r10 18 | 0x41, 0x53, // push r11 19 | 0x41, 0x54, // push r12 20 | 0x41, 0x55, // push r13 21 | 0x41, 0x56, // push r14 22 | 0x41, 0x57, // push r15 23 | 24 | 25 | 0x48, 0xB8, 0xFF, 0x00, 0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0xFF,// mov rax,0xff00efbeadde00ff [entry-address-place-holder] 26 | 0x48, 0x31, 0xD2, // xor rdx,rdx (clean register) 27 | 0x48, 0x83, 0xC2, 0x01, // add rdx,byte 28 | 0x48, 0x83, 0xEC, 0x28, // sub rsp,0x28 (align the stack and shadow space allocation) 29 | 0xFF, 0xD0, // call rax 30 | 0x48, 0x83, 0xC4, 0x28, // add rsp,0x28 31 | 0x48, 0xB8, 0xFF, 0x00, 0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0xFF, // mov rax,0xff00efbeadde00ff [original-iat-ptr-address-place-holder] 32 | 0x48, 0xBA, 0xFF, 0x00, 0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0xFF, // mov rdx,0xff00efbeadde00ff [iat-func-address-place-holder] 33 | 0x48, 0x89, 0x10, // mov [rax],rdx (write address from rdx to memory at rax) 34 | 0xC6, 0x05, 0x0D, 0x00, 0x00, 0x00, 0x01, // mov byte ptr [rip+0xd], 1 (0xd == dec 13) 35 | 0x48, 0x8D, 0x05, 0x00, 0x00, 0x00, 0x00, // lea rax,[rip] 36 | 0x48, 0x8D, 0x40, 0x08, // lea rax,[rax+8] 37 | 0xFF, 0xE0, // jmp rax 38 | 0x00, 0x00, // add BYTE PTR [rax],al 39 | 40 | 41 | 0x41, 0x5F, // pop r15 42 | 0x41, 0x5E, // pop r14 43 | 0x41, 0x5D, // pop r13 44 | 0x41, 0x5C, // pop r12 45 | 0x41, 0x5B, // pop r11 46 | 0x41, 0x5A, // pop r10 47 | 0x41, 0x59, // pop r9 48 | 0x41, 0x58, // pop r8 49 | 0x5F, // pop rdi 50 | 0x5E, // pop rsi 51 | 0x5A, // pop rdx 52 | 0x59, // pop rcx 53 | 0x5B, // pop rbx 54 | 0x58, // pop rax 55 | 0x9D, // popf (restore flags) 56 | 57 | 58 | // call original function? 59 | 0x48, 0x89, 0xE5, // mov rbp,rsp 60 | 0x48, 0x8B, 0x6D, 0x00, // mov rbp,[rbp] 61 | 0x48, 0x8D, 0x6D, 0xFA, // lea rbp,[rbp-6] <- this has our call instruction 62 | 0xFF, 0xD5, // call rbp 63 | 0xC3 // ret 64 | }; 65 | 66 | // this is treated as an int 67 | enum Shell_Address_Location 68 | { 69 | DLL_ENTRY_POINT = 25, 70 | IAT_FUNCTION_PTR = 52, 71 | IAT_ORIGINAL_FUNCTION_ADDRESS = 62, 72 | SIGNAL_BYTE_OFFSET = 93 73 | }; 74 | 75 | template 76 | inline void shellcode_insert_address(Shell_Address_Location location, T address) noexcept 77 | { 78 | *reinterpret_cast(shellcode + location) = address; 79 | } 80 | 81 | inline std::uintptr_t signal_byte_offset(void* shellcode_ptr_address) noexcept 82 | { 83 | return reinterpret_cast(shellcode_ptr_address) + SIGNAL_BYTE_OFFSET; 84 | } 85 | 86 | }; 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /src/memory/memory_handlers.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #pragma warning( disable : 6289) 11 | #pragma warning( disable : 4996) 12 | 13 | 14 | namespace memory{ 15 | inline bool GetProcessID(const std::string_view process_name); 16 | inline std::uintptr_t GetModuleBase(const std::string_view module_name); 17 | inline bool GetRawDataFromFile(const std::string_view file_name,std::uint8_t* &raw_data,std::size_t &sizeOfRawData); 18 | 19 | 20 | 21 | namespace { 22 | // this struct will get called whenever a handle needs to be closed. 23 | struct close_handle { 24 | using pointer = HANDLE; 25 | void operator()(HANDLE handle) 26 | { 27 | if (handle) 28 | CloseHandle(handle); 29 | } 30 | }; 31 | 32 | std::uint32_t process_id; 33 | using process_handle = std::unique_ptr; 34 | std::unique_ptr proc_handle; 35 | } 36 | 37 | // what is inline here? 38 | // https://stackoverflow.com/questions/22102919/static-vs-inline-for-functions-implemented-in-header-files 39 | inline HANDLE get_handle() 40 | { 41 | return proc_handle.get(); 42 | } 43 | 44 | 45 | inline std::uint32_t return_processid() 46 | { 47 | return process_id; 48 | } 49 | 50 | inline void set_processid(std::uint32_t pid) 51 | { 52 | process_id = pid; 53 | } 54 | 55 | inline bool OpenProcessHandle(const std::uint32_t process_id) 56 | { 57 | if (process_id == 0) 58 | return false; 59 | 60 | process_handle handle(OpenProcess(PROCESS_VM_WRITE | PROCESS_VM_READ | PROCESS_VM_OPERATION | PROCESS_CREATE_THREAD, false, process_id)); 61 | 62 | if (handle.get() == nullptr) 63 | return false; 64 | 65 | // move ownership of object to ours. 66 | proc_handle = std::move(handle); 67 | 68 | return true; 69 | } 70 | 71 | 72 | inline bool Read(std::uintptr_t address, void* buffer, size_t size) 73 | { 74 | return ReadProcessMemory(memory::get_handle(), reinterpret_cast(address), buffer, size, nullptr); 75 | } 76 | 77 | inline bool Write(std::uintptr_t address, void* buffer, size_t size) 78 | { 79 | return WriteProcessMemory(memory::get_handle(), reinterpret_cast(address), buffer, size, nullptr); 80 | } 81 | 82 | inline bool VirtualprotectExPage(std::uintptr_t address, size_t size,DWORD protection,PDWORD old_protection) 83 | { 84 | return VirtualProtectEx(memory::get_handle(), reinterpret_cast(address), size, protection, old_protection); 85 | } 86 | 87 | inline std::size_t VirtualQueryExPage(std::uintptr_t address,MEMORY_BASIC_INFORMATION &mb) 88 | { 89 | return VirtualQueryEx(memory::get_handle(), reinterpret_cast(address), &mb,sizeof(mb)); 90 | } 91 | } 92 | 93 | 94 | bool memory::GetProcessID(const std::string_view process_name) { 95 | PROCESSENTRY32 processentry; 96 | 97 | const std::unique_ptr 98 | snapshot_handle(CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)); 99 | 100 | if (snapshot_handle.get() == INVALID_HANDLE_VALUE) 101 | return false; 102 | 103 | processentry.dwSize = sizeof(PROCESSENTRY32); 104 | 105 | while (Process32Next(snapshot_handle.get(), &processentry) == TRUE) { 106 | if (process_name.compare(processentry.szExeFile) == 0) 107 | { 108 | memory::set_processid(processentry.th32ProcessID); 109 | return true; 110 | } 111 | } 112 | return false; 113 | } 114 | 115 | 116 | std::uintptr_t memory::GetModuleBase(const std::string_view module_name) 117 | { 118 | const std::unique_ptr 119 | snapshot_handle(CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, memory::return_processid())); 120 | 121 | MODULEENTRY32 entry; 122 | entry.dwSize = sizeof(MODULEENTRY32); 123 | 124 | while (Module32Next(snapshot_handle.get(), &entry)) { 125 | if (!strcmp(entry.szModule, module_name.data())) 126 | return reinterpret_cast(entry.modBaseAddr); 127 | } 128 | return 0; 129 | } 130 | 131 | 132 | 133 | bool memory::GetRawDataFromFile(const std::string_view file_name, std::uint8_t* &raw_data, std::size_t &sizeOfRawData) 134 | { 135 | std::ifstream file(file_name.data(), std::ifstream::binary); 136 | 137 | if (file) 138 | { 139 | file.seekg(0, file.end); 140 | sizeOfRawData = file.tellg(); 141 | file.seekg(0, file.beg); 142 | 143 | raw_data = new std::uint8_t[sizeOfRawData]; 144 | 145 | if (!raw_data) 146 | return false; 147 | 148 | file.read(reinterpret_cast(raw_data), sizeOfRawData); 149 | 150 | // fstream already closes our file when it goes out of scoop 151 | // if we tried to close it we will get an exception 152 | return true; 153 | } 154 | else 155 | return false; 156 | } 157 | 158 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Easy DLL Mapper 2 | is a dll manual mapper the goal of this project is to make use of the modern 3 | c++ language and to explain how does manual mapping works step by step. 4 | 5 | ## example usage 6 | open cmd navigate to where the EDMapper.exe is located and type 7 | `EDMapper.exe notepad.exe C:\\test.dll MessageBoxA` 8 | * note that you need to specify a function that get's called frequently 9 | in order for this injector to work. 10 | 11 | ## assembly ref 12 | - https://defuse.ca/online-x86-assembler.htm#disassembly 13 | - https://stackoverflow.com/questions/30190132/what-is-the-shadow-space-in-x64-assembly 14 | - https://forum.nasm.us/index.php?topic=2309.0 15 | 16 | ## general pe-format 17 | - https://docs.microsoft.com/en-us/previous-versions/ms809762(v=msdn.10)?redirectedfrom=MSDN 18 | - https://www.codeproject.com/Articles/36928/Parse-a-PE-EXE-DLL-OCX-Files-and-New-Dependency-Wa 19 | 20 | ## .idata section 21 | - https://stackoverflow.com/questions/7673754/pe-format-iat-questions 22 | - https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#the-idata-section 23 | - https://docs.microsoft.com/en-us/previous-versions/ms809762(v=msdn.10)?redirectedfrom=MSDN#pe-file-imports 24 | - https://stackoverflow.com/questions/42413937/why-pe-need-original-first-thunkoft 25 | 26 | ## .reloc section 27 | - https://stackoverflow.com/questions/24821910/whats-the-meaning-of-highlow-in-a-disassembled-binary-file/24823931 28 | - https://docs.microsoft.com/en-us/previous-versions/ms809762(v=msdn.10)?redirectedfrom=MSDN#pe-file-base-relocations 29 | - https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#the-reloc-section-image-only 30 | - https://docs.microsoft.com/en-us/archive/msdn-magazine/2002/march/inside-windows-an-in-depth-look-into-the-win32-portable-executable-file-format-part-2 31 | - https://research32.blogspot.com/2015/01/base-relocation-table.html 32 | 33 | 34 | ## useful links for bitwise operators 35 | - https://stackoverflow.com/questions/3270307/how-do-i-get-the-lower-8-bits-of-int 36 | - https://en.wikipedia.org/wiki/Bitwise_operations_in_C#Bitwise_operators 37 | - https://stackoverflow.com/questions/10493411/what-is-bit-masking 38 | 39 | 40 | ## few notes 41 | when we try to manual map a dll that uses `MessageBoxA` for example the Target 42 | process must have it in its (import table) `.idata` section or else we will crash. 43 | 44 | 45 | ## Shellcode explanation 46 | ``` c++ 47 | // hardcoded shellcode 48 | BYTE shellcode[] = { 49 | 0x50, // push rax save old register so we don't corrupt it 50 | 0x48, 0xB8, 0xFF, 0x00, 0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0xFF, // mov rax,0xff00efbeadde00ff <- this value is just a place that will get replaced by our entrypoint pointer 51 | 0x52, // push rdx save old register so we don't corrupt it 52 | 0x48, 0x31, 0xD2, // xor rdx,rdx 53 | 0x48, 0x83, 0xC2, 0x01, // add rdx,byte +0x0 (add 1 bit to rdx) 54 | 0x48, 0x83, 0xEC, 0x28, // sub rsp,0x28 (align the stack and shadow space allocation) 55 | 0xFF, 0xD0, // call rax 56 | 0x48, 0x83, 0xC4, 0x28, // add rsp,0x28 57 | 0x58, // pop rax (restore rax) 58 | 0x5A, // pop rdx (restore rdx) 59 | 0xC3 // ret (return) 60 | }; 61 | 62 | // note 0x1020 is an RVA to where the location that i want to jmp to. to get there we need to add image base + rva 63 | *(std::uintptr_t*)(shellcode + 3) = (std::uintptr_t)m_image + 0x1020; // Hardcoded offset 64 | ``` 65 | 66 | first please see assembly ref and read on x64 shadow space before reading this so you have a better understanding about why we are reserving space in the stack 67 | 68 | before i start talking about what does this shellcode does please note that you can use normal functions to achieve this exact way but am using assembly since it gives you more control on whats happening when executing it 69 | 70 | 71 | now lets start with this shellcode first we need to put our offset in some place well we use the registery `rax` but before we use it we push it to the stack to save its old state then we can move our offset into it after that we push `rdx` to stack too. then we XOR rdx,rdx to zero out any garbage data that was in there before .then we add 1 bit to rdx .then we subtract some space for x64 shadowing and re-align the stack. then we `call rax` which will jmp to our address after finishing we clean up the stack by adding the same amount we subtracted before then we `pop rax` and `pop rdx` then we return from our shellcode to let the target process continue execution. 72 | 73 | where did the hardcoded offset came from? shouldn't we call our dllEntry point. 74 | 75 | - the answer is yes but for me there was a `cmp` instruction which compared edx with 1 and if edx wasn't `1` it will jmp and never execute my code. and to fix that problem i just hardcoded this shellcode + offset where the cmp instruction is so i can call my code you can see from the screenshot below : 76 | 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /src/edmapper.cpp: -------------------------------------------------------------------------------- 1 | #include "edmapper.hpp" 2 | 3 | 4 | Edmapper::dll_map::~dll_map() 5 | { 6 | if(this->rawDll_data) 7 | delete[] this->rawDll_data; // free allocated raw dll data 8 | 9 | if(this->l_image) 10 | VirtualFree(this->l_image, 0, MEM_RELEASE); // free local image 11 | 12 | if(this->m_image) 13 | VirtualFreeEx(memory::get_handle(), this->m_image, 0, MEM_RELEASE); // free mapped image 14 | 15 | if (this->pShellCode) 16 | VirtualFreeEx(memory::get_handle(), pShellCode, 0, MEM_RELEASE); // free mapped shellcode memory 17 | } 18 | 19 | 20 | bool Edmapper::dll_map::map_dll(const std::string_view proccess_name, const std::string_view dll_path, const std::string_view iat_functionName_to_hijack) 21 | { 22 | // get process id 23 | 24 | if (!memory::GetProcessID(proccess_name)) 25 | { 26 | std::cerr << "[-] couldn't obtain process id aborting." << '\n'; 27 | return false; 28 | } 29 | 30 | this->process_id = memory::return_processid(); 31 | 32 | // open handle to process 33 | 34 | if (!memory::OpenProcessHandle(this->process_id)) 35 | { 36 | std::cerr << "[-] failed to open handle to process." << '\n'; 37 | return false; 38 | } 39 | 40 | 41 | // open our dll file & get data 42 | if (!memory::GetRawDataFromFile(dll_path, this->rawDll_data, this->rawDll_dataSize)) 43 | { 44 | std::cerr << "[-] failed to open file." << '\n'; 45 | return false; 46 | } 47 | 48 | 49 | // validate image 50 | this->pnt_headers = portable_exe::IsValidImage(this->rawDll_data); 51 | 52 | if (this->pnt_headers == nullptr) 53 | return false; 54 | else 55 | std::printf("[+] image is valid.\n"); 56 | 57 | 58 | // allocate local image 59 | const auto image_size = this->pnt_headers->OptionalHeader.SizeOfImage; 60 | 61 | this->l_image = VirtualAlloc(nullptr, image_size, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE); 62 | 63 | if (!this->l_image) 64 | { 65 | std::cerr << "[-] failed allocate local image memory." << '\n'; 66 | return false; 67 | } 68 | 69 | 70 | // copy all headers from our dll image. 71 | std::memcpy(l_image, rawDll_data, this->pnt_headers->OptionalHeader.SizeOfHeaders); 72 | 73 | 74 | // copy sections into local image. 75 | portable_exe::CopyImageSections(this->l_image, this->pnt_headers, this->rawDll_data); 76 | 77 | std::printf("[+] copied sections.\n"); 78 | 79 | // fix imports 80 | 81 | if (!portable_exe::FixImageImports(this->l_image, this->pnt_headers, this->rawDll_data)) 82 | { 83 | std::cerr << "[-] couldn't fix image imports." << '\n'; 84 | return false; 85 | } 86 | 87 | std::printf("[+] fixed imports.\n"); 88 | 89 | // allocate image in target process 90 | 91 | const auto image_base = this->pnt_headers->OptionalHeader.ImageBase; 92 | 93 | // first try to allocate at prefered load address 94 | this->m_image = VirtualAllocEx(memory::get_handle(), reinterpret_cast(image_base), image_size, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE); 95 | 96 | if (!this->m_image) 97 | { 98 | // if we couldn't allocate at prefered address then just allocate memory on any random place in memory 99 | // but we will need to fix relocation of image 100 | this->m_image = VirtualAllocEx(memory::get_handle(), nullptr, image_size, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE); 101 | 102 | if (!this->m_image) 103 | { 104 | std::cerr << "[-] couldn't allocate memory in target process." << '\n'; 105 | return false; 106 | } 107 | 108 | // fix relocation 109 | portable_exe::FixImageRelocations(this->m_image, this->l_image, this->pnt_headers, this->rawDll_data); 110 | 111 | std::printf("[+] Fixed relocations!\n"); 112 | } 113 | 114 | // no need to fix relocations since we loaded at prefered base address. 115 | 116 | // write content of our dll aka local image into the allocated memory in target process 117 | if (!memory::Write(reinterpret_cast(this->m_image), this->l_image, image_size)) 118 | { 119 | std::cerr << "[-] couldn't copy image to target process." << '\n'; 120 | return false; 121 | } 122 | 123 | std::printf("[+] Wrote image to target process.\n"); 124 | 125 | 126 | // allocate memory for our shellcode inside target process 127 | this->pShellCode = VirtualAllocEx(memory::get_handle(), nullptr, sizeof(assembly::shellcode), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); 128 | if (!this->pShellCode) 129 | { 130 | std::cerr << "[-] failed to allocate memory for shellcode in target process." << '\n'; 131 | return false; 132 | } 133 | 134 | std::printf("[+] Allocated memory for shellcode at : %p \n", this->pShellCode); 135 | 136 | auto iat_func_ptr = get_ptr_to_iatfunc(memory::GetModuleBase(proccess_name.data()), iat_functionName_to_hijack.data()); 137 | if (!iat_func_ptr) 138 | return false; 139 | 140 | auto entrypoint_jmp = reinterpret_cast(this->m_image) + 0x1020; 141 | 142 | std::uintptr_t iat_func_address = 0; 143 | if (!memory::Read(iat_func_ptr, &iat_func_address, sizeof(std::uintptr_t))) 144 | return false; 145 | 146 | 147 | // init shellcode 148 | assembly::shellcode_insert_address(assembly::DLL_ENTRY_POINT, entrypoint_jmp); 149 | assembly::shellcode_insert_address(assembly::IAT_FUNCTION_PTR, iat_func_ptr); 150 | assembly::shellcode_insert_address(assembly::IAT_ORIGINAL_FUNCTION_ADDRESS, iat_func_address); 151 | 152 | // write our shellcode into memory 153 | if (!memory::Write((std::uintptr_t)this->pShellCode, assembly::shellcode, sizeof(assembly::shellcode))) 154 | { 155 | std::cerr << "[-] failed to write shellcode to memory" << '\n'; 156 | return false; 157 | } 158 | 159 | if (!hook_iat_function(iat_func_ptr, this->pShellCode)) 160 | return false; 161 | 162 | std::printf("[+] shellcode executed successfully.\n"); 163 | 164 | // free shellcode in our target process 165 | VirtualFreeEx(memory::get_handle(), this->pShellCode, 0, MEM_RELEASE); 166 | // free local image 167 | VirtualFree(this->l_image, 0, MEM_RELEASE); 168 | // free our raw dll data 169 | delete[] this->rawDll_data; 170 | 171 | return true; 172 | } -------------------------------------------------------------------------------- /src/hook/iat_hook.hpp: -------------------------------------------------------------------------------- 1 | #include "../memory/memory_handlers.hpp" 2 | #include "shellcode.hpp" 3 | #include 4 | #include 5 | 6 | 7 | inline std::uint64_t get_ptr_to_iatfunc(std::uintptr_t process_base_address, const std::string_view import_name) 8 | { 9 | // check if we got valid args 10 | if (import_name.empty() || !process_base_address) 11 | { 12 | std::cerr << "get_iat_function_pointer : invalid args passed." << '\n'; 13 | return 0; 14 | } 15 | 16 | IMAGE_DOS_HEADER dos_header = { 0 }; 17 | 18 | if (!memory::Read(process_base_address, &dos_header, sizeof(dos_header))) 19 | return 0; 20 | 21 | // check if we got valid PE file 22 | if (dos_header.e_magic != IMAGE_DOS_SIGNATURE) 23 | return 0; 24 | 25 | IMAGE_NT_HEADERS nt_headers = { 0 }; 26 | 27 | if (!memory::Read(process_base_address + dos_header.e_lfanew, &nt_headers, sizeof(nt_headers))) 28 | return 0; 29 | 30 | // check if we got valid nt headers 31 | if (nt_headers.Signature != IMAGE_NT_SIGNATURE) 32 | return 0; 33 | 34 | // check if we got a 64 bit image 35 | if (nt_headers.OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR64_MAGIC) 36 | return 0; 37 | 38 | IMAGE_IMPORT_DESCRIPTOR import_desc = { 0 }; 39 | 40 | const auto rva_to_iat = nt_headers.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress; 41 | const auto iat_size = nt_headers.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size; 42 | 43 | int i = 0; 44 | char ModuleName[100]; // set this to 100 so it can hold 100 char's i don't think that dll names would require more than that. 45 | char import_function_name[200]; // holds imported function names 46 | 47 | do 48 | { 49 | // read offset to our .idata section 50 | // we need to loop through all imported dll's each dll has its own struct which is the IMAGE_IMPORT_DESCRIPTOR struct 51 | // to read all structs we need to advance to next struct each time we finish reading imports from a specific dll 52 | // todo that we use this calculation where var i = 0 which means read the first struct in memory 53 | // after that var (i) will be 1 so 1 * the size of the struct means go to next struct of memory 54 | // because when we add the size of struct 1 time we are saying like skip this struct and go to the other in memory i hope it makes since 55 | // because they are in contiguous memory meaning that they are after each other in memory 56 | if (!memory::Read(process_base_address + rva_to_iat + i * sizeof(IMAGE_IMPORT_DESCRIPTOR), &import_desc, sizeof(import_desc))) 57 | return 0; 58 | 59 | // read current imported dll name 60 | if (!memory::Read(process_base_address + import_desc.Name,&ModuleName,sizeof(ModuleName))) 61 | return 0; 62 | 63 | 64 | auto ModuleBase = LoadLibraryA(ModuleName); 65 | if (!ModuleBase) return 0; 66 | 67 | int func_index = 0; 68 | 69 | IMAGE_THUNK_DATA originalfirst_thunk = { 0 }; 70 | if (!memory::Read(process_base_address + import_desc.OriginalFirstThunk, &originalfirst_thunk, sizeof(originalfirst_thunk))) 71 | return 0; 72 | 73 | int n = 1; // since we already read the first struct in line 69 & 72 we set this to start looping from the second struct which is 1 74 | // everytime this loops strats n will be set to 1. 75 | while (originalfirst_thunk.u1.AddressOfData != NULL) 76 | { 77 | // if import by ordinal then go to next import and don't try to read the name of this since its ordinal 78 | if (IMAGE_SNAP_BY_ORDINAL(originalfirst_thunk.u1.Ordinal)) 79 | goto nextstruct; 80 | 81 | // read function name 82 | if (!memory::Read(process_base_address + originalfirst_thunk.u1.AddressOfData + FIELD_OFFSET(IMAGE_IMPORT_BY_NAME, Name),&import_function_name,sizeof(import_function_name))) 83 | return 0; 84 | 85 | // std::cout << "Module :" << ModuleName << " " << "Function ->" << "[" << import_function_name << "]" << '\n'; 86 | 87 | /* 88 | what are we doing here? 89 | basically if we read the memory at : process_base_address + import_desc.FirstThunk 90 | we will be dereferencing the pointer that points to the (iat function ADDRESS) and will give us 91 | the address to the first instruction inside the function but since we don't want to hook there (although we could) 92 | but its going to be messy since we need a fixed amount of bytes to replace and it changes from 1 function to another 93 | instead what we will do is return the pointer address that POINTS to iat function address and we will replace it to point to our OWN function address 94 | how can we do that? 95 | simple just don't read process_base_address + import_desc.FirstThunk from memory(aka derf it) just get the pointer address as it is 96 | */ 97 | 98 | // NOTE : this ptr is placed in .rdata (aka readonly) section 99 | 100 | // don't let this confuse you this checks if strcmp will return 0 meaning that our string matched with the function name! 101 | if (!strcmp(import_function_name, import_name.data())) 102 | return process_base_address + import_desc.FirstThunk + func_index * sizeof(ULONGLONG); 103 | 104 | 105 | // go to next function aka next IMAGE_IMPORT_BY_NAME struct in memory 106 | nextstruct: 107 | if (!memory::Read(process_base_address + import_desc.OriginalFirstThunk + n * sizeof(IMAGE_THUNK_DATA),&originalfirst_thunk,sizeof(originalfirst_thunk))) 108 | break; 109 | 110 | n++; 111 | } 112 | 113 | i++; // advance to second struct that contains the second imported dll. 114 | } while (import_desc.Name != NULL); 115 | 116 | return 0; 117 | } 118 | 119 | 120 | 121 | inline bool hook_iat_function(std::uint64_t iat_function_ptr, void* ptr_to_shellcode) 122 | { 123 | if (!iat_function_ptr || ptr_to_shellcode == nullptr) 124 | return false; 125 | 126 | MEMORY_BASIC_INFORMATION mb = { 0 }; 127 | if (!memory::VirtualQueryExPage(iat_function_ptr, mb)) 128 | return false; 129 | 130 | 131 | DWORD old_protection = 0; 132 | 133 | if (mb.Protect == PAGE_EXECUTE_READ || mb.Protect == PAGE_READONLY) 134 | { 135 | // if DEP is enabled we are going to crash with access violation 136 | // change protection of page to read/write so we can change function ptr with WriteprocessMemory 137 | if (!memory::VirtualprotectExPage(iat_function_ptr,sizeof(std::uint64_t), PAGE_READWRITE,&old_protection)) 138 | return false; 139 | } 140 | 141 | // save old function ptr 142 | const auto original_func_ptr = iat_function_ptr; 143 | 144 | std::printf("[+] original function ptr : %p \n", (void*)original_func_ptr); 145 | 146 | // write func ptr and wait for shellcode to finish 147 | if (!memory::Write(iat_function_ptr, &ptr_to_shellcode, sizeof(void*))) 148 | return false; 149 | 150 | // check if shellcode finished executing 151 | byte signal = 0; 152 | const auto offset_byte = assembly::signal_byte_offset(ptr_to_shellcode); 153 | 154 | std::printf("[+] byte address : %p \n", (void*)offset_byte); 155 | 156 | for (;;) 157 | { 158 | if (!memory::Read(offset_byte, &signal, sizeof(byte))) 159 | { 160 | std::cerr << "failed to read signal byte." << '\n'; 161 | return false; // failed to read from address 162 | } 163 | 164 | // signaled. 165 | if (signal == 1) 166 | break; 167 | } 168 | 169 | // restore protection to iat_func_ptr in .rdata section 170 | if (!memory::VirtualprotectExPage(iat_function_ptr, sizeof(std::uint64_t), old_protection, &old_protection)) 171 | return false; 172 | 173 | return true; 174 | } -------------------------------------------------------------------------------- /src/src.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 16.0 23 | Win32Proj 24 | {99331f9d-cd24-4e6e-ab14-46a7d4b1f60f} 25 | src 26 | 10.0 27 | EDMapper 28 | 29 | 30 | 31 | Application 32 | true 33 | v142 34 | Unicode 35 | 36 | 37 | Application 38 | false 39 | v142 40 | true 41 | Unicode 42 | 43 | 44 | Application 45 | true 46 | v142 47 | MultiByte 48 | 49 | 50 | Application 51 | false 52 | v142 53 | true 54 | MultiByte 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | true 76 | 77 | 78 | false 79 | 80 | 81 | true 82 | 83 | 84 | false 85 | 86 | 87 | 88 | Level3 89 | true 90 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 91 | true 92 | 93 | 94 | Console 95 | true 96 | 97 | 98 | 99 | 100 | Level3 101 | true 102 | true 103 | true 104 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 105 | true 106 | 107 | 108 | Console 109 | true 110 | true 111 | true 112 | 113 | 114 | 115 | 116 | Level3 117 | true 118 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions) 119 | true 120 | stdcpplatest 121 | 122 | 123 | Console 124 | true 125 | 126 | 127 | 128 | 129 | Level3 130 | true 131 | true 132 | true 133 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 134 | true 135 | stdcpplatest 136 | 137 | 138 | Console 139 | true 140 | true 141 | true 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | -------------------------------------------------------------------------------- /src/pe/portable_executable.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include "../edmapper.hpp" 4 | 5 | 6 | namespace portable_exe{ 7 | inline PIMAGE_NT_HEADERS IsValidImage(std::uint8_t* &rawdll_image); 8 | inline void CopyImageSections(const void* image,const PIMAGE_NT_HEADERS pnt_headers, std::uint8_t* &rawdll_image); 9 | inline bool FixImageImports(const void* image, const PIMAGE_NT_HEADERS pnt_headers, std::uint8_t* &rawdll_image); 10 | inline void FixImageRelocations(void* mapped_image,void* local_image, const PIMAGE_NT_HEADERS pnt_headers, std::uint8_t* &rawdll_image); 11 | 12 | // L l = L() basically because we are doing default params and we need to initialzie it to std::less 13 | // how we are not passing third param? to l(a,b) because its already in l == std::less 14 | template> 15 | inline bool CheckHigher_addressInMem(const A a,const B b,L l = L()) { 16 | return l(a, b); // call std::less to do comparing for us 17 | } 18 | } 19 | 20 | 21 | PIMAGE_NT_HEADERS portable_exe::IsValidImage(std::uint8_t* &rawdll_image) 22 | { 23 | const auto p_dosHeader = reinterpret_cast(rawdll_image); 24 | 25 | // check the "MZ" chars to check if its a valid PE file. 26 | if (p_dosHeader->e_magic != IMAGE_DOS_SIGNATURE) 27 | { 28 | std::printf("[-]Invalid Image type.\n"); 29 | return nullptr; 30 | } 31 | 32 | const auto p_ntHeaders = reinterpret_cast(rawdll_image + p_dosHeader->e_lfanew); 33 | 34 | // check if our nt headers is valid or not by checking its signature 35 | if (p_ntHeaders->Signature != IMAGE_NT_SIGNATURE) 36 | { 37 | std::printf("[-]Invalid nt_headers signature.\n"); 38 | return nullptr; 39 | } 40 | 41 | // check if image is 64 bit 42 | if (p_ntHeaders->OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR64_MAGIC) 43 | { 44 | std::printf("[-]Image is not 64 bit.\n"); 45 | return nullptr; 46 | } 47 | 48 | return p_ntHeaders; 49 | } 50 | 51 | 52 | void portable_exe::CopyImageSections(const void* image, const PIMAGE_NT_HEADERS pnt_headers, std::uint8_t* &rawdll_image) 53 | { 54 | // Immediately following the PE header in memory is an array of IMAGE_SECTION_HEADERs. The number of elements in this array is given in the PE header (the IMAGE_NT_HEADER.FileHeader.NumberOfSections field). 55 | // basically this will return a pointer to an array 56 | // then we need to index it to get to our structs. 57 | PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION(pnt_headers); 58 | 59 | // loop through each section until we get to the last one 60 | for (size_t i = 0; i < pnt_headers->FileHeader.NumberOfSections; i++, pSection++) 61 | { 62 | // get pointer to where we want to copy the sections to which will be at location where our section offset start from. 63 | auto dest = reinterpret_cast((reinterpret_cast(image) + pSection->VirtualAddress)); 64 | // get pointer to our section data 65 | const auto src = rawdll_image + pSection->PointerToRawData; 66 | // get the size of the section data 67 | const auto size = pSection->SizeOfRawData; 68 | // copy sections from our dll image to our local image 1 by 1 69 | std::memcpy(dest,src , size); 70 | } 71 | } 72 | 73 | 74 | bool portable_exe::FixImageImports(const void* image, const PIMAGE_NT_HEADERS pnt_headers, std::uint8_t* &rawdll_image) 75 | { 76 | PIMAGE_IMPORT_DESCRIPTOR pImportDesc = nullptr; 77 | 78 | // we can use this offset to our .idata section because we have the same exact one that we copied from our 79 | // dll so basically adding our image + our dll idata section is like doing it from our (copied image) headers if we wanted to. 80 | // this will return a pointer to an array of structs 81 | pImportDesc = reinterpret_cast( 82 | reinterpret_cast(image) + pnt_headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress); 83 | 84 | // if we couldn't get the address of our .idata section then return 85 | if (!pImportDesc) 86 | return false; 87 | 88 | // loop through all imported dll's until we get to the last one 89 | while (pImportDesc->Name != NULL) 90 | { 91 | // get the imported dll name 92 | const auto ModuleName = reinterpret_cast( 93 | reinterpret_cast(image) + pImportDesc->Name); 94 | 95 | // load the imported dll into our process to get the base address of it 96 | auto ModuleBase = LoadLibraryA(ModuleName); 97 | 98 | // if we couldn't obatin base of MODULE DLL return false 99 | if (!ModuleBase) 100 | return false; 101 | 102 | 103 | PIMAGE_THUNK_DATA pFirst_thunkData = nullptr; 104 | PIMAGE_THUNK_DATA pOriginalFirst_thunkData = nullptr; 105 | 106 | 107 | // get address of FirstThunk pointer to PIMAGE_THUNK_DATA 108 | pFirst_thunkData = reinterpret_cast( 109 | reinterpret_cast(image) + pImportDesc->FirstThunk); 110 | 111 | // get address of OriginalFirstThunk pointer to PIMAGE_THUNK_DATA 112 | pOriginalFirst_thunkData = reinterpret_cast( 113 | reinterpret_cast(image) + pImportDesc->OriginalFirstThunk); 114 | 115 | // ^^ these array's points to structs each struct contains either an ordinal number for an imported function name. 116 | // difference is FirstThunk get overwritten by windows loader while OriginalFirstThunk does not and contains original information about imported functions. 117 | 118 | // if not found return 119 | if (!pFirst_thunkData && !pOriginalFirst_thunkData) 120 | return false; 121 | 122 | std::uintptr_t Function_address = 0; 123 | 124 | // again we are using OriginalThunk data since its the one that guarantee us that we will have original information about this dll idata section. 125 | // each u1.AddressOfData will point to a struct in memory called PIMAGE_IMPORT_BY_NAME 126 | // each struct of type PIMAGE_IMPORT_BY_NAME contains an imported function name for example (MessageBoxA inside user32.dll) 127 | // if we did not loop through all imported functions we will only get the first one and other ones will be ignored which is not what we want. 128 | // so that's why we do this. 129 | while (pOriginalFirst_thunkData->u1.AddressOfData != NULL) 130 | { 131 | // IMAGE_SNAP_BY_ORDINAL is a marco that checks if the highest bit is set or not in 132 | // PIMAGE_THUNK_DATA which is (u1.Ordinal) 133 | 134 | // little note we can import using 2 ways 135 | // 1 - [ordinal] number is just a number that the windows loader use to find an import function 136 | // 2 - by Function Name you can see how both works below 137 | 138 | 139 | // if set then import by ordinal otherwise import by name. 140 | if (IMAGE_SNAP_BY_ORDINAL(pOriginalFirst_thunkData->u1.Ordinal)) 141 | { 142 | // LOWORD marco gets the low-order word from the specified value 143 | // read about GetProcAddress to understand more on msdn. 144 | // then we cast it to a string to find our function address. 145 | Function_address = reinterpret_cast(GetProcAddress(ModuleBase, reinterpret_cast(LOWORD(pOriginalFirst_thunkData->u1.Ordinal)))); 146 | } 147 | else 148 | { 149 | // pOriginalFirst_thunkData->u1.AddressOfData : is basically an rva to PIMAGE_IMPORT_BY_NAME struct inside 150 | // union PIMAGE_THUNK_DATA 151 | // we can do the same thing using FirstThunk but i prefered to use OriginalFirstThunk cuz that's what its for (INT) 152 | // Import Name Table 153 | PIMAGE_IMPORT_BY_NAME pImport = reinterpret_cast( 154 | reinterpret_cast(image) + pOriginalFirst_thunkData->u1.AddressOfData); 155 | 156 | // get the current function address from current imported dll 157 | Function_address = reinterpret_cast(GetProcAddress(ModuleBase, pImport->Name)); 158 | } 159 | 160 | // couldn't find any explanation about this but 161 | // pFirst_thunkData->u1.Function : is the address of the function that we are currently importing 162 | // if we tried to call it , it will crash so we need to add base address of MODULE DLL + Function offset 163 | // so it can get called normally. 164 | 165 | // why storing result address in FirstThunk not OriginalFirstThunk since FirstThunk is the one that gets overwritten by windows loader 166 | if (Function_address) 167 | pFirst_thunkData->u1.Function = Function_address; 168 | else 169 | return false; 170 | 171 | // advance to second struct in our array to get imported function in current module. 172 | pOriginalFirst_thunkData++; 173 | pFirst_thunkData++; 174 | } 175 | 176 | // go to next imported dll in our array using pointer Arithmetic 177 | pImportDesc++; 178 | } 179 | 180 | return true; 181 | } 182 | 183 | 184 | void portable_exe::FixImageRelocations(void* mapped_image, void* local_image, const PIMAGE_NT_HEADERS pnt_headers, std::uint8_t* &rawdll_image) 185 | { 186 | // get relocation directory pointer by adding imageBase + RVA (AKA the struct that has our .reloc info) 187 | auto pRelocation_dir = reinterpret_cast( 188 | reinterpret_cast(local_image) + pnt_headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress); 189 | 190 | if (!pRelocation_dir) 191 | return; 192 | 193 | 194 | // we can't compare address's using < or > operators since we will get undefined behavior 195 | // since both of them doesn't belong to each other or they are not pointing to the same object/array etc.. 196 | // so we use std::less to compare 2 void pointers 197 | const auto isLess = 198 | CheckHigher_addressInMem(mapped_image, reinterpret_cast(pnt_headers->OptionalHeader.ImageBase)); 199 | 200 | // calculate delta 201 | // https://sciencing.com/calculate-delta-between-two-numbers-5893964.html 202 | 203 | ULONGLONG delta = 0; 204 | if (isLess) 205 | { 206 | // cast to ULONGLONG to perform calculations 207 | delta = pnt_headers->OptionalHeader.ImageBase - reinterpret_cast(mapped_image); 208 | } 209 | else 210 | { 211 | delta = reinterpret_cast(mapped_image) - pnt_headers->OptionalHeader.ImageBase; 212 | } 213 | 214 | 215 | // we will keep looping through pages until we encounter a null pointer which then means we are done and got to the end of it. 216 | while (pRelocation_dir->VirtualAddress) 217 | { 218 | // get amount of entries basically 219 | // this represent a location 220 | // (offset within the 4 KB page if 32bit or 8 KB page if on 64bit pointed out by the VirtualAddress member in the IMAGE_BASE_RELOCATION struct) 221 | // which needs to be fixed up 222 | 223 | // The .reloc section contains a serie of blocks 224 | // There is one block for each 4 KB page if 32bit or 8 KB page if on 64bit page that contains virtual addresses 225 | // The SizeOfBlock holds the size of the block in bytes (including the size of the IMAGE_BASE_RELOCATION struct) 226 | // so to get how many entries in our block we do this calculation 227 | 228 | // sizeOfBlock has the size of our block + size of IMAGE_BASE_RELOCATION struct 229 | // so we need to subtract 8 bytes aka (size of IMAGE_BASE_RELOCATION) then divide by 2 which is size WORD 230 | // since each entry is 2 bytes 231 | 232 | // why we doing this inside our loop? 233 | // because we will be advancing to the second block and we need to get new block size of our second page in memory 234 | const auto amountof_entries = pRelocation_dir->SizeOfBlock - sizeof(PIMAGE_BASE_RELOCATION) / sizeof(WORD); 235 | 236 | // Immediately following the IMAGE_BASE_RELOCATION structure is a variable number of WORD values 237 | // AKA an array of WORD values 238 | // we go 1 more byte after our relocation directory to get to it 239 | auto entry = reinterpret_cast(pRelocation_dir + 1); 240 | 241 | // loop through all entries (aka where we should apply our relocations) in our block of relocation 242 | for (size_t i = 0; i < amountof_entries; i++) 243 | { 244 | // we are doing a right-shift operation to get to the higher 4 bits inside our current WORD value 245 | // note that doing bitwise operations like this won't change the value of our WORD var unless we assign it with the assignment operator "=" 246 | // we go 12 bits to the right to get the higher 4 bits in our WORD var 247 | // for IA-64 executables the relocation type is seem to be always type of IMAGE_REL_BASED_DIR64 248 | // which basically means write our (whole) delta value to where the relocation should be applied 249 | 250 | // note we can use both hex or dec with bitwise operators the result will be the same 251 | // 0x0C = 12(dec) 252 | // 0xFFF = 4095(dec) 253 | if (entry[i] >> 0x0C == IMAGE_REL_BASED_DIR64) 254 | { 255 | // get an offset pointer to the address that needs to be relocated 256 | // we need this to be a pointer to std::uintptr_t so we can modify its contents 257 | 258 | // (entry[i] & 0xFFF) = lowest 12 bits which is an RVA to the address where we want to apply relocations 259 | // this might be confusing but i will explain it 260 | // when we want to extract specific bits from a variable 261 | // we can compare it with another value that has x amount of bits set and nothing more 262 | // so 0xFFF in binary is (1111 1111 1111) <- 12 bits 263 | // and when we do AND operation on it 264 | // this is what happens ex 265 | 266 | /* 267 | USING THE (&) OPERATOR 268 | 1111 1101 1001 0101 // lets say this is our WORD value in memory (16 bits) 269 | 0000 1111 1111 1111 // and this is 0xFFF bitmask 270 | ---- ---- ---- ---- 271 | 0000 1101 1001 0101 // <- see only the 8 bits are left and nothing more 272 | 273 | by doing this too it won't change our WORD value only gets the lowest 12 bits 274 | // unless we assign it to it. 275 | 276 | why use & operator since the & operator won't change any data or won't result in any change 277 | after the operation is done if a bit is 1 it will stay the same if 0 it will be 0 simple 278 | its literally grabbing values from our WORD variable 279 | */ 280 | // this is called bit-masking ^^ 281 | 282 | auto offset_to_relocation = reinterpret_cast( 283 | reinterpret_cast(local_image) + pRelocation_dir->VirtualAddress + (entry[i] & 0xFFF)); 284 | 285 | // derf and add delta value 286 | *offset_to_relocation += delta; 287 | } 288 | // advance to the next WORD value 289 | entry++; 290 | } 291 | 292 | /* 293 | go to the next block of memory 294 | we add SizeOfBlock which contains the IMAGE_BASE_RELOCATION struct + entries to our original pointer to old block of relocation 295 | so we can advance to the second block of reloctaion. 296 | */ 297 | 298 | pRelocation_dir = reinterpret_cast(reinterpret_cast(pRelocation_dir + pRelocation_dir->SizeOfBlock)); 299 | } 300 | } --------------------------------------------------------------------------------