libPeConv
A library to load, manipulate, dump PE files.
remote_pe_reader.cpp
Go to the documentation of this file.
2
3#include <iostream>
4
5#include "peconv/util.h"
7
8using namespace peconv;
9
10bool peconv::fetch_region_info(HANDLE processHandle, LPVOID moduleBase, MEMORY_BASIC_INFORMATION &page_info)
11{
12 memset(&page_info, 0, sizeof(MEMORY_BASIC_INFORMATION));
13 SIZE_T out = VirtualQueryEx(processHandle, moduleBase, &page_info, sizeof(page_info));
14 if (out != sizeof(page_info)) {
15 return false;
16 }
17 return true;
18}
19
20size_t _fetch_region_size(MEMORY_BASIC_INFORMATION &page_info, LPVOID moduleBase)
21{
22 if (page_info.Type == 0) {
23 return false; //invalid type, skip it
24 }
25 if ((BYTE*)page_info.BaseAddress > moduleBase) {
26 return 0; //should never happen
27 }
28 const size_t offset = (ULONG_PTR)moduleBase - (ULONG_PTR)page_info.BaseAddress;
29 const size_t area_size = page_info.RegionSize - offset;
30 return area_size;
31}
32
33size_t peconv::fetch_region_size(HANDLE processHandle, LPVOID moduleBase)
34{
35 MEMORY_BASIC_INFORMATION page_info = { 0 };
36 if (!peconv::fetch_region_info(processHandle, moduleBase, page_info)) {
37 return 0;
38 }
39 const size_t area_size = _fetch_region_size(page_info, moduleBase);
40 return area_size;
41}
42
43ULONGLONG peconv::fetch_alloc_base(HANDLE processHandle, LPVOID moduleBase)
44{
45 MEMORY_BASIC_INFORMATION page_info = { 0 };
46 if (!peconv::fetch_region_info(processHandle, moduleBase, page_info)) {
47 return 0;
48 }
49 if (page_info.Type == 0) {
50 return 0; //invalid type, skip it
51 }
52 return (ULONGLONG) page_info.AllocationBase;
53}
54
55namespace peconv {
61 SIZE_T _search_readable_size(HANDLE processHandle, LPVOID start_addr, OUT BYTE* buffer, const size_t buffer_size, const SIZE_T minimal_size)
62 {
63 if (!buffer || buffer_size == 0) {
64 return 0;
65 }
66 if ((buffer_size < minimal_size) || minimal_size == 0) {
67 return 0;
68 }
69 SIZE_T last_failed_size = buffer_size;
70 SIZE_T last_success_size = 0;
71
72 SIZE_T test_read_size = 0;
73 if (!ReadProcessMemory(processHandle, start_addr, buffer, minimal_size, &test_read_size)) {
74 //cannot read even the minimal size, quit trying
75 return test_read_size;
76 }
77 last_success_size = minimal_size;
78
79 SIZE_T read_size = 0;
80 SIZE_T to_read_size = buffer_size/2;
81
82 while (to_read_size > minimal_size && to_read_size < buffer_size)
83 {
84 read_size = 0;
85 if (ReadProcessMemory(processHandle, start_addr, buffer, to_read_size, &read_size)) {
86 last_success_size = to_read_size;
87 }
88 else {
89 last_failed_size = to_read_size;
90 }
91 const size_t delta = (last_failed_size - last_success_size) / 2;
92 if (delta == 0) break;
93 to_read_size = last_success_size + delta;
94 }
95 if (last_success_size) {
96 read_size = 0;
97 memset(buffer, 0, buffer_size);
98 ReadProcessMemory(processHandle, start_addr, buffer, last_success_size, &read_size);
99 return read_size;
100 }
101 return 0;
102 }
103};
104
105size_t peconv::read_remote_memory(HANDLE processHandle, LPVOID start_addr, OUT BYTE* buffer, const size_t buffer_size, const SIZE_T minimal_size)
106{
107 if (!buffer || buffer_size == 0) {
108 return 0;
109 }
110 memset(buffer, 0, buffer_size);
111
112 SIZE_T read_size = 0;
113 DWORD last_error = ERROR_SUCCESS;
114
115 while (buffer_size > 0)
116 {
117 if (ReadProcessMemory(processHandle, start_addr, buffer, buffer_size, &read_size)) {
118 break;
119 }
120 last_error = GetLastError();
121 if (last_error != ERROR_SUCCESS) {
122 if (read_size == 0 && (last_error != ERROR_PARTIAL_COPY)) {
123 break; // break
124 }
125 }
126 if (last_error == ERROR_PARTIAL_COPY) {
127 read_size = peconv::_search_readable_size(processHandle, start_addr, buffer, buffer_size, minimal_size);
128#ifdef _DEBUG
129 std::cout << "peconv::search_readable_size res: " << std::hex << read_size << std::endl;
130#endif
131 }
132 break;
133 }
134
135#ifdef _DEBUG
136 if (read_size == 0) {
137 std::cerr << "[WARNING] Cannot read memory. Last Error : " << last_error << std::endl;
138 }
139 else if (read_size < buffer_size) {
140 std::cerr << "[WARNING] Read size: " << std::hex << read_size
141 << " is smaller than the requested size: " << std::hex << buffer_size
142 << ". Last Error: " << last_error << std::endl;
143
144 }
145#endif
146 return static_cast<size_t>(read_size);
147}
148
149size_t peconv::read_remote_region(HANDLE processHandle, LPVOID start_addr, OUT BYTE* buffer, const size_t buffer_size, const bool force_access, const SIZE_T minimal_size)
150{
151 if (!buffer || buffer_size == 0) {
152 return 0;
153 }
154 MEMORY_BASIC_INFORMATION page_info = { 0 };
155 if (!peconv::fetch_region_info(processHandle, start_addr, page_info)) {
156 return 0;
157 }
158 if ((page_info.State & MEM_COMMIT) == 0) {
159 return 0;
160 }
161 size_t region_size = _fetch_region_size(page_info, start_addr);
162 if (region_size == 0) {
163 return 0;
164 }
165
166 const size_t size_to_read = (region_size > buffer_size) ? buffer_size : region_size;
167
168 const bool is_accessible = (page_info.Protect & PAGE_NOACCESS) == 0;
169 BOOL access_changed = FALSE;
170 DWORD oldProtect = 0;
171
172 // check the access right and eventually try to change it
173 if (force_access && !is_accessible) {
174 access_changed = VirtualProtectEx(processHandle, start_addr, region_size, PAGE_READONLY, &oldProtect);
175#ifdef _DEBUG
176 if (!access_changed) {
177 DWORD err = GetLastError();
178 if (err != ERROR_ACCESS_DENIED) {
179 std::cerr << "[!] " << std::hex << start_addr << " : " << region_size << " inaccessible area, changing page access failed: " << std::dec << err << "\n";
180 }
181 }
182#endif
183 }
184
185 size_t size_read = 0;
186 if (is_accessible || access_changed) {
187 size_read = peconv::read_remote_memory(processHandle, start_addr, buffer, size_to_read, minimal_size);
188 if ((size_read == 0) && (page_info.Protect & PAGE_GUARD)) {
189#ifdef _DEBUG
190 std::cout << "Warning: guarded page, trying to read again..." << std::endl;
191#endif
192 size_read = peconv::read_remote_memory(processHandle, start_addr, buffer, size_to_read, minimal_size);
193 }
194 }
195 // if the access rights were changed, change it back:
196 if (access_changed) {
197 VirtualProtectEx(processHandle, start_addr, region_size, oldProtect, &oldProtect);
198 }
199 return size_read;
200}
201
202size_t peconv::read_remote_area(HANDLE processHandle, LPVOID start_addr, OUT BYTE* buffer, const size_t buffer_size, const bool force_access, const SIZE_T minimal_size)
203{
204 if (!buffer || !start_addr || buffer_size == 0) {
205 return 0;
206 }
207 memset(buffer, 0, buffer_size);
208
209 size_t real_read = 0; //how many bytes has been realy read (not counting the skipped areas)
210 size_t total_read = 0;
211 for (total_read = 0; total_read < buffer_size; ) {
212 LPVOID remote_chunk = LPVOID((ULONG_PTR)start_addr + total_read);
213
214 MEMORY_BASIC_INFORMATION page_info = { 0 };
215 if (!peconv::fetch_region_info(processHandle, remote_chunk, page_info)) {
216 break;
217 }
218 const size_t region_size = _fetch_region_size(page_info, remote_chunk);
219 if (region_size == 0) {
220 break;
221 }
222
223 // read the memory:
224 const size_t read_chunk = read_remote_region(processHandle, remote_chunk, buffer + total_read, buffer_size - total_read, force_access, minimal_size);
225 if (read_chunk == 0) {
226 //skip the region that could not be read:
227 total_read += region_size;
228 continue;
229 }
230 real_read += read_chunk;
231 total_read += read_chunk;
232 }
233 if (real_read == 0) {
234 return 0;
235 }
236 return total_read;
237}
238
239bool peconv::read_remote_pe_header(HANDLE processHandle, LPVOID start_addr, OUT BYTE* buffer, const size_t buffer_size)
240{
241 if (buffer == nullptr) {
242 return false;
243 }
244 SIZE_T read_size = read_remote_memory(processHandle, start_addr, buffer, buffer_size);
245 if (read_size == 0) {
246 return false;
247 }
248 BYTE *nt_ptr = get_nt_hdrs(buffer, buffer_size);
249 if (nt_ptr == nullptr) {
250 return false;
251 }
252 const size_t nt_offset = nt_ptr - buffer;
253 const size_t nt_size = peconv::is64bit(buffer) ? sizeof(IMAGE_NT_HEADERS64) : sizeof(IMAGE_NT_HEADERS32);
254 const size_t min_size = nt_offset + nt_size;
255
256 if (read_size < min_size) {
257 std::cerr << "[-] [" << std::dec << get_process_id(processHandle)
258 << " ][" << std::hex << (ULONGLONG) start_addr
259 << "] Read size: " << std::hex << read_size
260 << " is smaller that the minimal size:" << get_hdrs_size(buffer)
261 << std::endl;
262 return false;
263 }
264 //reading succeeded and the header passed the checks:
265 return true;
266}
267
268namespace peconv {
269 inline size_t roundup_to_unit(size_t size, size_t unit)
270 {
271 if (unit == 0) {
272 return size;
273 }
274 size_t parts = size / unit;
275 if (size % unit) parts++;
276 return parts * unit;
277 }
278};
279
280peconv::UNALIGNED_BUF peconv::get_remote_pe_section(HANDLE processHandle, LPVOID start_addr, const size_t section_num, OUT size_t &section_size, bool roundup, bool force_access)
281{
282 BYTE header_buffer[MAX_HEADER_SIZE] = { 0 };
283
284 if (!read_remote_pe_header(processHandle, start_addr, header_buffer, MAX_HEADER_SIZE)) {
285 return NULL;
286 }
287 PIMAGE_SECTION_HEADER section_hdr = get_section_hdr(header_buffer, MAX_HEADER_SIZE, section_num);
288 if (section_hdr == NULL || section_hdr->Misc.VirtualSize == 0) {
289 return NULL;
290 }
291 size_t buffer_size = section_hdr->Misc.VirtualSize;
292 if (roundup) {
293 DWORD va = peconv::get_sec_alignment(header_buffer, false);
294 if (va == 0) va = PAGE_SIZE;
295 buffer_size = roundup_to_unit(section_hdr->Misc.VirtualSize, va);
296 }
297 UNALIGNED_BUF module_code = peconv::alloc_unaligned(buffer_size);
298 if (module_code == NULL) {
299 return NULL;
300 }
301 size_t read_size = peconv::read_remote_memory(processHandle, LPVOID((ULONG_PTR)start_addr + section_hdr->VirtualAddress), module_code, buffer_size);
302 if (read_size == 0) {
303 // this function is slower, so use it only if the normal read has failed:
304 read_size = read_remote_area(processHandle, LPVOID((ULONG_PTR)start_addr + section_hdr->VirtualAddress), module_code, buffer_size, force_access);
305 }
306 if (read_size == 0) {
307 peconv::free_unaligned(module_code);
308 return NULL;
309 }
310 section_size = buffer_size;
311 return module_code;
312}
313
314size_t peconv::read_remote_pe(const HANDLE processHandle, LPVOID start_addr, const size_t mod_size, OUT BYTE* buffer, const size_t bufferSize)
315{
316 if (buffer == nullptr) {
317 std::cerr << "[-] Invalid output buffer: NULL pointer" << std::endl;
318 return 0;
319 }
320 if (bufferSize < mod_size || bufferSize < MAX_HEADER_SIZE ) {
321 std::cerr << "[-] Invalid output buffer: too small size!" << std::endl;
322 return 0;
323 }
324 // read PE section by section
325 PBYTE hdr_buffer = buffer;
326 //try to read headers:
327 if (!read_remote_pe_header(processHandle, start_addr, hdr_buffer, MAX_HEADER_SIZE)) {
328 std::cerr << "[-] Failed to read the module header" << std::endl;
329 return 0;
330 }
332 std::cerr << "[-] Sections headers are invalid or atypically aligned" << std::endl;
333 return 0;
334 }
335 size_t sections_count = get_sections_count(hdr_buffer, MAX_HEADER_SIZE);
336#ifdef _DEBUG
337 std::cout << "Sections: " << sections_count << std::endl;
338#endif
339 size_t read_size = MAX_HEADER_SIZE;
340
341 for (size_t i = 0; i < sections_count; i++) {
342 PIMAGE_SECTION_HEADER hdr = get_section_hdr(hdr_buffer, MAX_HEADER_SIZE, i);
343 if (!hdr) {
344 std::cerr << "[-] Failed to read the header of section: " << i << std::endl;
345 break;
346 }
347 const DWORD sec_va = hdr->VirtualAddress;
348 const DWORD sec_vsize = get_virtual_sec_size(hdr_buffer, hdr, true);
349 if (sec_va + sec_vsize > bufferSize) {
350 std::cerr << "[-] No more space in the buffer!" << std::endl;
351 break;
352 }
353 if (sec_vsize > 0 && !read_remote_memory(processHandle, LPVOID((ULONG_PTR)start_addr + sec_va), buffer + sec_va, sec_vsize)) {
354 std::cerr << "[-] Failed to read the module section " << i <<" : at: " << std::hex << (ULONG_PTR)start_addr + sec_va << std::endl;
355 }
356 // update the end of the read area:
357 size_t new_end = sec_va + sec_vsize;
358 if (new_end > read_size) read_size = new_end;
359 }
360#ifdef _DEBUG
361 std::cout << "Total read size: " << read_size << std::endl;
362#endif
363 return read_size;
364}
365
366DWORD peconv::get_remote_image_size(IN const HANDLE processHandle, IN LPVOID start_addr)
367{
368 BYTE hdr_buffer[MAX_HEADER_SIZE] = { 0 };
369 if (!read_remote_pe_header(processHandle, start_addr, hdr_buffer, MAX_HEADER_SIZE)) {
370 return 0;
371 }
372 return peconv::get_image_size(hdr_buffer);
373}
374
375bool peconv::dump_remote_pe(IN const char *out_path,
376 IN const HANDLE processHandle,
377 IN LPVOID start_addr,
378 IN OUT t_pe_dump_mode &dump_mode,
379 IN OPTIONAL peconv::ExportsMapper* exportsMap)
380{
381 DWORD mod_size = get_remote_image_size(processHandle, start_addr);
382#ifdef _DEBUG
383 std::cout << "Module Size: " << mod_size << std::endl;
384#endif
385 if (mod_size == 0) {
386 return false;
387 }
388 BYTE* buffer = peconv::alloc_pe_buffer(mod_size, PAGE_READWRITE);
389 if (buffer == nullptr) {
390 std::cerr << "[-] Failed allocating buffer. Error: " << GetLastError() << std::endl;
391 return false;
392 }
393 //read the module that it mapped in the remote process:
394 const size_t read_size = read_remote_pe(processHandle, start_addr, mod_size, buffer, mod_size);
395 if (read_size == 0) {
396 std::cerr << "[-] Failed reading module. Error: " << GetLastError() << std::endl;
397 peconv::free_pe_buffer(buffer, mod_size);
398 buffer = nullptr;
399 return false;
400 }
401
402 const bool is_dumped = peconv::dump_pe(out_path,
403 buffer, mod_size,
404 reinterpret_cast<ULONGLONG>(start_addr),
405 dump_mode, exportsMap);
406
407 peconv::free_pe_buffer(buffer, mod_size);
408 buffer = nullptr;
409 return is_dumped;
410}
411
Functions and classes responsible for fixing Import Table. A definition of ImportedDllCoverage class.
DWORD get_process_id(HANDLE hProcess)
Definition: util.cpp:76
peconv::UNALIGNED_BUF get_remote_pe_section(HANDLE processHandle, LPVOID moduleBase, const size_t sectionNum, OUT size_t &sectionSize, bool roundup, bool force_access=false)
DWORD get_virtual_sec_size(IN const BYTE *pe_hdr, IN const PIMAGE_SECTION_HEADER sec_hdr, IN bool rounded)
bool is_valid_sections_hdr_offset(IN const BYTE *buffer, IN const size_t buffer_size)
bool dump_pe(IN const char *outputFilePath, IN OUT BYTE *buffer, IN size_t buffer_size, IN const ULONGLONG module_base, IN OUT t_pe_dump_mode &dump_mode, IN OPTIONAL const peconv::ExportsMapper *exportsMap=nullptr)
Definition: pe_dumper.cpp:26
UNALIGNED_BUF alloc_unaligned(size_t buf_size)
Definition: buffer_util.cpp:35
size_t read_remote_region(HANDLE processHandle, LPVOID start_addr, OUT BYTE *buffer, const size_t buffer_size, const bool force_access, const SIZE_T minimal_size=0x100)
PIMAGE_SECTION_HEADER get_section_hdr(IN const BYTE *pe_buffer, IN const size_t buffer_size, IN size_t section_num)
size_t read_remote_memory(HANDLE processHandle, LPVOID start_addr, OUT BYTE *buffer, const size_t buffer_size, const SIZE_T minimal_size=0x100)
bool fetch_region_info(HANDLE processHandle, LPVOID start_addr, MEMORY_BASIC_INFORMATION &page_info)
DWORD get_image_size(IN const BYTE *payload)
t_pe_dump_mode
Definition: pe_dumper.h:16
size_t read_remote_area(HANDLE processHandle, LPVOID start_addr, OUT BYTE *buffer, const size_t buffer_size, const bool force_access, const SIZE_T minimal_size=0x100)
bool free_pe_buffer(ALIGNED_BUF buffer, size_t buffer_size=0)
Definition: buffer_util.cpp:84
ALIGNED_BUF alloc_pe_buffer(size_t buffer_size, DWORD protect, ULONGLONG desired_base=NULL)
Definition: buffer_util.cpp:78
size_t roundup_to_unit(size_t size, size_t unit)
DWORD get_sec_alignment(IN const BYTE *modulePtr, IN bool is_raw)
bool is64bit(IN const BYTE *pe_buffer)
size_t get_sections_count(IN const BYTE *buffer, IN const size_t buffer_size)
SIZE_T _search_readable_size(HANDLE processHandle, LPVOID start_addr, OUT BYTE *buffer, const size_t buffer_size, const SIZE_T minimal_size)
bool dump_remote_pe(IN const char *outputFilePath, IN const HANDLE processHandle, IN LPVOID moduleBase, IN OUT t_pe_dump_mode &dump_mode, IN OPTIONAL peconv::ExportsMapper *exportsMap=nullptr)
const ULONGLONG MAX_HEADER_SIZE
DWORD get_hdrs_size(IN const BYTE *pe_buffer)
bool read_remote_pe_header(HANDLE processHandle, LPVOID moduleBase, OUT BYTE *buffer, const size_t bufferSize)
PBYTE UNALIGNED_BUF
Definition: buffer_util.h:36
BYTE * get_nt_hdrs(IN const BYTE *pe_buffer, IN OPTIONAL size_t buffer_size=0)
size_t read_remote_pe(const HANDLE processHandle, LPVOID moduleBase, const size_t moduleSize, OUT BYTE *buffer, const size_t bufferSize)
size_t fetch_region_size(HANDLE processHandle, LPVOID start_addr)
ULONGLONG fetch_alloc_base(HANDLE processHandle, LPVOID start_addr)
void free_unaligned(UNALIGNED_BUF section_buffer)
Definition: buffer_util.cpp:43
DWORD get_remote_image_size(IN const HANDLE processHandle, IN LPVOID start_addr)
#define PAGE_SIZE
size_t _fetch_region_size(MEMORY_BASIC_INFORMATION &page_info, LPVOID moduleBase)
Reading from a PE module that is loaded within a remote process.
Miscellaneous utility functions.