├── 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 | }
--------------------------------------------------------------------------------