libPeConv
A library to load, manipulate, dump PE files.
Loading...
Searching...
No Matches
relocate.cpp
Go to the documentation of this file.
1#include "peconv/relocate.h"
2
4#include <stdio.h>
5#include "peconv/logger.h"
6
7using namespace peconv;
8
9#define RELOC_32BIT_FIELD 3
10#define RELOC_64BIT_FIELD 0xA
11
13{
14public:
15 ApplyRelocCallback(bool _is64bit, ULONGLONG _oldBase, ULONGLONG _newBase)
16 : RelocBlockCallback(_is64bit), oldBase(_oldBase), newBase(_newBase)
17 {
18 }
19
20 virtual bool processRelocField(ULONG_PTR relocField)
21 {
22 if (is64bit) {
23 ULONGLONG* relocateAddr = (ULONGLONG*)((ULONG_PTR)relocField);
24 ULONGLONG rva = (*relocateAddr) - oldBase;
25 (*relocateAddr) = rva + newBase;
26 }
27 else {
28 DWORD* relocateAddr = (DWORD*)((ULONG_PTR)relocField);
29 ULONGLONG rva = ULONGLONG(*relocateAddr) - oldBase;
30 (*relocateAddr) = static_cast<DWORD>(rva + newBase);
31 }
32 return true;
33 }
34
35protected:
36 ULONGLONG oldBase;
37 ULONGLONG newBase;
38};
39
40bool is_empty_reloc_block(BASE_RELOCATION_ENTRY *block, SIZE_T entriesNum, DWORD page, PVOID modulePtr, SIZE_T moduleSize)
41{
42 if (entriesNum == 0) {
43 return true; // nothing to process
44 }
45 BASE_RELOCATION_ENTRY* entry = block;
46 for (SIZE_T i = 0; i < entriesNum; i++) {
47 if (!validate_ptr(modulePtr, moduleSize, entry, sizeof(BASE_RELOCATION_ENTRY))) {
48 return false;
49 }
50 DWORD type = entry->Type;
51 if (type != 0) {
52 //non empty block found
53 return false;
54 }
55 entry = (BASE_RELOCATION_ENTRY*)((ULONG_PTR)entry + sizeof(WORD));
56 }
57 return true;
58}
59
60namespace {
61 bool validate_reloc_field(PVOID modulePtr, SIZE_T moduleSize, bool is64bit, const DWORD reloc_field)
62 {
63 const size_t field_width = is64bit ? sizeof(ULONGLONG) : sizeof(DWORD);
64 const ULONG_PTR reloc_ptr = (ULONG_PTR)modulePtr + reloc_field;
65 return peconv::validate_ptr(modulePtr, moduleSize, (LPVOID)reloc_ptr, field_width);
66 }
67};
68
69bool process_reloc_block(BASE_RELOCATION_ENTRY *block, SIZE_T entriesNum, DWORD page, PVOID modulePtr, SIZE_T moduleSize, bool is64bit, RelocBlockCallback *callback)
70{
71 if (entriesNum == 0) {
72 return true; // nothing to process
73 }
74 BASE_RELOCATION_ENTRY* entry = block;
75 SIZE_T i = 0;
76 for (i = 0; i < entriesNum; i++) {
77 if (!validate_ptr(modulePtr, moduleSize, entry, sizeof(BASE_RELOCATION_ENTRY))) {
78 break;
79 }
80 DWORD offset = entry->Offset;
81 DWORD type = entry->Type;
82 if (type == 0) {
83 entry = (BASE_RELOCATION_ENTRY*)((ULONG_PTR)entry + sizeof(WORD));
84 continue; // skip padding
85 }
86 if (type != RELOC_32BIT_FIELD && type != RELOC_64BIT_FIELD) {
87 if (callback) { //print debug messages only if the callback function was set
88 LOG_ERROR("Not supported relocation format at %d: %d.", (int)i, (int)type);
89 }
90 return false;
91 }
92 const DWORD reloc_field = page + offset;
93 if (!validate_reloc_field(modulePtr, moduleSize, is64bit, reloc_field)) {
94 if (callback) { //print debug messages only if the callback function was set
95 LOG_ERROR("Malformed reloc field: 0x%lx.", reloc_field);
96 }
97 return false;
98 }
99 if (callback) {
100 bool isOk = callback->processRelocField(((ULONG_PTR)modulePtr + reloc_field));
101 if (!isOk) {
102 LOG_ERROR("Failed processing reloc field at: 0x%lx.", reloc_field);
103 return false;
104 }
105 }
106 entry = (BASE_RELOCATION_ENTRY*)((ULONG_PTR)entry + sizeof(WORD));
107 }
108 return (i != 0);
109}
110
111bool peconv::process_relocation_table(IN PVOID modulePtr, IN SIZE_T moduleSize, IN RelocBlockCallback *callback)
112{
113 IMAGE_DATA_DIRECTORY* relocDir = peconv::get_directory_entry((const BYTE*)modulePtr, IMAGE_DIRECTORY_ENTRY_BASERELOC);
114 if (relocDir == NULL) {
115 LOG_DEBUG("No relocation table found.");
116 return false;
117 }
118 if (!validate_ptr(modulePtr, moduleSize, relocDir, sizeof(IMAGE_DATA_DIRECTORY))) {
119 LOG_ERROR("Invalid relocDir pointer.");
120 return false;
121 }
122 const DWORD maxSize = relocDir->Size;
123 const DWORD relocAddr = relocDir->VirtualAddress;
124 const bool is64b = is64bit((BYTE*)modulePtr);
125
126 IMAGE_BASE_RELOCATION* reloc = NULL;
127
128 DWORD parsedSize = 0;
129 while (parsedSize < maxSize) {
130 const ULONGLONG curr_reloc_addr = relocAddr + parsedSize;
131 reloc = (IMAGE_BASE_RELOCATION*)(curr_reloc_addr + (ULONG_PTR)modulePtr);
132 if (!validate_ptr(modulePtr, moduleSize, reloc, sizeof(IMAGE_BASE_RELOCATION))) {
133 LOG_WARNING("Invalid address of relocations, RVA: 0x%llx when moduleSize is: 0x%llx", curr_reloc_addr, (ULONGLONG)moduleSize);
134 return false;
135 }
136 if (reloc->SizeOfBlock < (2 * sizeof(DWORD))) {
137 LOG_WARNING("Malformed relocation block: SizeOfBlock too small.");
138 return false;
139 }
140 const size_t entriesNum = (reloc->SizeOfBlock - 2 * sizeof(DWORD)) / sizeof(WORD);
141 const DWORD page = reloc->VirtualAddress;
142
143 BASE_RELOCATION_ENTRY* block = (BASE_RELOCATION_ENTRY*)((ULONG_PTR)reloc + sizeof(DWORD) + sizeof(DWORD));
144 if (!validate_ptr(modulePtr, moduleSize, block, sizeof(BASE_RELOCATION_ENTRY))) {
145 LOG_WARNING("Invalid address of relocations block.");
146 return false;
147 }
148 if (!is_empty_reloc_block(block, entriesNum, page, modulePtr, moduleSize)) {
149 if (!process_reloc_block(block, entriesNum, page, modulePtr, moduleSize, is64b, callback)) {
150 // the block was malformed
151 LOG_WARNING("Malformed relocation block.");
152 return false;
153 }
154 }
155 const DWORD _newParsedSize = parsedSize + reloc->SizeOfBlock;
156 if (_newParsedSize < parsedSize) {
157 LOG_ERROR("Invalid SizeOfBlock: DWORD overflow.");
158 return false;
159 }
160 parsedSize = _newParsedSize;
161 }
162 return true;
163}
164
165bool apply_relocations(PVOID modulePtr, SIZE_T moduleSize, ULONGLONG newBase, ULONGLONG oldBase)
166{
167 const bool is64b = is64bit((BYTE*)modulePtr);
168 ApplyRelocCallback callback(is64b, oldBase, newBase);
169 return process_relocation_table(modulePtr, moduleSize, &callback);
170}
171
172bool peconv::relocate_module(IN PBYTE modulePtr, IN SIZE_T moduleSize, IN ULONGLONG newBase, IN ULONGLONG oldBase)
173{
174 if (!modulePtr || !moduleSize) {
175 return false;
176 }
177 if (oldBase == 0) {
178 oldBase = get_image_base(modulePtr);
179 }
180 LOG_DEBUG("New Base: 0x%llx Old Base: 0x%llx.", (unsigned long long)newBase, (unsigned long long)oldBase);
181 if (newBase == oldBase) {
182 LOG_DEBUG("Nothing to relocate: oldBase equals newBase.");
183 return true; //nothing to relocate
184 }
185 if (apply_relocations(modulePtr, moduleSize, newBase, oldBase)) {
186 return true;
187 }
188 LOG_ERROR("Could not relocate the module.");
189 return false;
190}
191
192bool peconv::has_valid_relocation_table(IN const PBYTE modulePtr, IN const size_t moduleSize)
193{
194 return process_relocation_table(modulePtr, moduleSize, nullptr);
195}
196
197namespace {
198 bool virtual_addr_to_rva_no_relocs(IN const BYTE* modulePtr, IN const size_t module_size, IN ULONGLONG callback_addr, OUT DWORD& callback_rva)
199 {
200 const ULONGLONG img_base = (ULONGLONG)modulePtr;
201 //check if VA:
202 if (callback_addr >= img_base && callback_addr < (img_base + module_size)) {
203 callback_rva = MASK_TO_DWORD(callback_addr - img_base);
204 return true;
205 }
206 if (callback_addr < (ULONGLONG)module_size) {
207 callback_rva = MASK_TO_DWORD(callback_addr);
208 return true;
209 }
210 // out of scope address
211 return false;
212 }
213}
214
215bool peconv::virtual_addr_to_rva(IN const PBYTE modulePtr, IN const size_t module_size, IN ULONGLONG callback_addr, OUT DWORD& callback_rva, IN std::unordered_set<ULONGLONG>* _relocs)
216{
217 if (!module_size || !callback_addr) return false;
218
219 const ULONGLONG img_base = (ULONGLONG)modulePtr;
220
221 std::unordered_set<ULONGLONG> local_relocs;
222 std::unordered_set<ULONGLONG>& reloc_values = _relocs ? (*_relocs) : local_relocs;
223 if (!_relocs && peconv::has_relocations(modulePtr)) {
224 // Collect relocations for VA detection
225 CollectRelocs callback(modulePtr, module_size, peconv::is64bit(modulePtr), reloc_values);
226 process_relocation_table(modulePtr, module_size, &callback);
227 }
228
229 // for files with no relocation table use simple heuristics:
230 if (reloc_values.empty()) {
231 return virtual_addr_to_rva_no_relocs(modulePtr, module_size, callback_addr, callback_rva);
232 }
233 // Helper to convert VA -> RVA if the address is in relocation table
234 auto _convert_va_to_rva = [&](ULONGLONG& addr, DWORD &rva) -> bool
235 {
236 if (reloc_values.find(addr) != reloc_values.end()) {
237 // found: input is a VA
238 if (addr < img_base) {
239 LOG_ERROR("Invalid VA: 0x%llx cannot convert safely", addr);
240 return false;
241 }
242 rva = static_cast<DWORD>(addr - img_base);
243 }
244 else {
245 // not found: input is a RVA
246 if (addr > module_size) {
247 return false;
248 }
249 rva = static_cast<DWORD>(addr);
250 }
251 return true;
252 };
253 return _convert_va_to_rva(callback_addr, callback_rva);
254}
255
#define MASK_TO_DWORD(val)
Definition: buffer_util.h:12
virtual bool processRelocField(ULONG_PTR relocField)
Definition: relocate.cpp:20
ApplyRelocCallback(bool _is64bit, ULONGLONG _oldBase, ULONGLONG _newBase)
Definition: relocate.cpp:15
ULONGLONG oldBase
Definition: relocate.cpp:36
ULONGLONG newBase
Definition: relocate.cpp:37
virtual bool processRelocField(ULONG_PTR relocField)=0
Compile-time configurable logging macros for peconv.
#define LOG_DEBUG(fmt,...)
Definition: logger.h:80
#define LOG_ERROR(fmt,...)
Definition: logger.h:60
#define LOG_WARNING(fmt,...)
Definition: logger.h:68
bool has_relocations(IN const BYTE *pe_buffer)
ULONGLONG get_image_base(IN const BYTE *pe_buffer)
bool virtual_addr_to_rva(IN const PBYTE imgBase, IN const size_t imgSize, IN ULONGLONG virtualAddr, OUT DWORD &outRVA, IN std::unordered_set< ULONGLONG > *relocs=nullptr)
Definition: relocate.cpp:215
bool validate_ptr(IN const void *buffer_bgn, IN size_t buffer_size, IN const void *field_bgn, IN size_t field_size)
Definition: buffer_util.cpp:9
bool process_relocation_table(IN PVOID modulePtr, IN SIZE_T moduleSize, IN RelocBlockCallback *callback)
Definition: relocate.cpp:111
bool is64bit(IN const BYTE *pe_buffer)
IMAGE_DATA_DIRECTORY * get_directory_entry(IN const BYTE *pe_buffer, IN DWORD dir_id, IN bool allow_empty=false)
bool relocate_module(IN PBYTE modulePtr, IN SIZE_T moduleSize, IN ULONGLONG newBase, IN ULONGLONG oldBase=0)
Definition: relocate.cpp:172
bool has_valid_relocation_table(IN const PBYTE modulePtr, IN const size_t moduleSize)
Definition: relocate.cpp:192
Wrappers over various fields in the PE header. Read, write, parse PE headers.
bool process_reloc_block(BASE_RELOCATION_ENTRY *block, SIZE_T entriesNum, DWORD page, PVOID modulePtr, SIZE_T moduleSize, bool is64bit, RelocBlockCallback *callback)
Definition: relocate.cpp:69
#define RELOC_64BIT_FIELD
Definition: relocate.cpp:10
bool apply_relocations(PVOID modulePtr, SIZE_T moduleSize, ULONGLONG newBase, ULONGLONG oldBase)
Definition: relocate.cpp:165
bool is_empty_reloc_block(BASE_RELOCATION_ENTRY *block, SIZE_T entriesNum, DWORD page, PVOID modulePtr, SIZE_T moduleSize)
Definition: relocate.cpp:40
#define RELOC_32BIT_FIELD
Definition: relocate.cpp:9
Operating on PE file's relocations table.