libPeConv
A library to load, manipulate, dump PE files.
Loading...
Searching...
No Matches
remote_pe_reader.cpp
Go to the documentation of this file.
2
3#include "peconv/logger.h"
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 0; //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 if (ReadProcessMemory(processHandle, start_addr, buffer, last_success_size, &read_size)) {
99 return read_size;
100 }
101 }
102 return 0;
103 }
104};
105
106size_t peconv::read_remote_memory(HANDLE processHandle, LPVOID start_addr, OUT BYTE* buffer, const size_t buffer_size, const SIZE_T minimal_size)
107{
108 if (!buffer || buffer_size == 0) {
109 return 0;
110 }
111 memset(buffer, 0, buffer_size);
112 DWORD last_error = ERROR_SUCCESS;
113
114 SIZE_T read_size = 0;
115 if (!ReadProcessMemory(processHandle, start_addr, buffer, buffer_size, &read_size)) {
116 last_error = GetLastError();
117 if (last_error == ERROR_PARTIAL_COPY) {
118 read_size = peconv::_search_readable_size(processHandle, start_addr, buffer, buffer_size, minimal_size);
119 LOG_DEBUG("peconv::search_readable_size res: 0x%zx.", read_size);
120 }
121 }
122
123 if (read_size == 0) {
124 LOG_WARNING("Cannot read memory. Last Error: %lu.", last_error);
125 } else if (read_size < buffer_size) {
126 LOG_WARNING("Read size: 0x%zx is smaller than requested: 0x%zx. Last Error: %lu.", (size_t)read_size, buffer_size, last_error);
127 }
128 return static_cast<size_t>(read_size);
129}
130
131size_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)
132{
133 if (!buffer || buffer_size == 0) {
134 return 0;
135 }
136 MEMORY_BASIC_INFORMATION page_info = { 0 };
137 if (!peconv::fetch_region_info(processHandle, start_addr, page_info)) {
138 return 0;
139 }
140 if ((page_info.State & MEM_COMMIT) == 0) {
141 return 0;
142 }
143 size_t region_size = _fetch_region_size(page_info, start_addr);
144 if (region_size == 0) {
145 return 0;
146 }
147
148 const size_t size_to_read = (region_size > buffer_size) ? buffer_size : region_size;
149
150 const bool is_accessible = (page_info.Protect & PAGE_NOACCESS) == 0;
151 BOOL access_changed = FALSE;
152 DWORD oldProtect = 0;
153
154 // check the access right and eventually try to change it
155 if (force_access && !is_accessible) {
156 access_changed = VirtualProtectEx(processHandle, start_addr, region_size, PAGE_READONLY, &oldProtect);
157 if (!access_changed) {
158 DWORD err = GetLastError();
159 if (err != ERROR_ACCESS_DENIED) {
160 LOG_WARNING("0x%llx : 0x%zx inaccessible area, changing page access failed: %lu.", (unsigned long long)(ULONG_PTR)start_addr, region_size, err);
161 }
162 }
163 }
164
165 size_t size_read = 0;
166 if (is_accessible || access_changed) {
167 size_read = peconv::read_remote_memory(processHandle, start_addr, buffer, size_to_read, minimal_size);
168 if ((size_read == 0) && (page_info.Protect & PAGE_GUARD)) {
169 LOG_DEBUG("Guarded page, trying to read again.");
170 size_read = peconv::read_remote_memory(processHandle, start_addr, buffer, size_to_read, minimal_size);
171 }
172 }
173 // if the access rights were changed, change it back:
174 if (access_changed) {
175 if (!VirtualProtectEx(processHandle, start_addr, region_size, oldProtect, &oldProtect)) {
176 LOG_WARNING("Failed to restore protection of region: %p", start_addr);
177 }
178 }
179 return size_read;
180}
181
182size_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)
183{
184 if (!buffer || !start_addr || buffer_size == 0) {
185 return 0;
186 }
187 memset(buffer, 0, buffer_size);
188
189 size_t real_read = 0; // how many bytes has been realy read (not counting the skipped areas)
190 size_t last_valid = 0; // the last chunk that was really read (don't count the last skipped ones)
191
192 size_t buf_index = 0;
193 for (buf_index = 0; buf_index < buffer_size; ) {
194 LPVOID remote_chunk = LPVOID((ULONG_PTR)start_addr + buf_index);
195
196 MEMORY_BASIC_INFORMATION page_info = { 0 };
197 if (!peconv::fetch_region_info(processHandle, remote_chunk, page_info)) {
198 break;
199 }
200 const size_t region_size = _fetch_region_size(page_info, remote_chunk);
201 if (region_size == 0) {
202 break;
203 }
204
205 // read the memory:
206 const size_t read_chunk = read_remote_region(
207 processHandle,
208 remote_chunk,
209 (BYTE*)((ULONG_PTR)buffer + buf_index),
210 buffer_size - buf_index,
211 force_access,
212 minimal_size
213 );
214 if (read_chunk == 0) {
215 //skip the region that could not be read, and proceed to the next:
216 buf_index += region_size;
217 continue;
218 }
219 buf_index += read_chunk;
220 real_read += read_chunk; // total sum of the read content
221 last_valid = buf_index; // the last chunk that was really read
222 }
223 if (real_read == 0) {
224 return 0;
225 }
226 return last_valid;
227}
228
229bool peconv::read_remote_pe_header(HANDLE processHandle, LPVOID start_addr, OUT BYTE* buffer, const size_t buffer_size, bool force_access)
230{
231 if (buffer == nullptr) {
232 return false;
233 }
234 SIZE_T read_size = read_remote_area(processHandle, start_addr, buffer, buffer_size, force_access);
235 if (read_size == 0) {
236 return false;
237 }
238 BYTE *nt_ptr = get_nt_hdrs(buffer, buffer_size);
239 if (nt_ptr == nullptr) {
240 return false;
241 }
242 const size_t nt_offset = nt_ptr - buffer;
243 const size_t nt_size = peconv::is64bit(buffer) ? sizeof(IMAGE_NT_HEADERS64) : sizeof(IMAGE_NT_HEADERS32);
244 const size_t min_size = nt_offset + nt_size;
245
246 if (read_size < min_size) {
247 LOG_ERROR("[PID %lu][0x%llx] Read size: 0x%zx is smaller than the minimal size: 0x%lx.", get_process_id(processHandle), (unsigned long long)(ULONGLONG)start_addr, read_size, get_hdrs_size(buffer));
248 return false;
249 }
250 //reading succeeded and the header passed the checks:
251 return true;
252}
253
254peconv::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)
255{
256 BYTE header_buffer[MAX_HEADER_SIZE] = { 0 };
257
258 if (!read_remote_pe_header(processHandle, start_addr, header_buffer, MAX_HEADER_SIZE, force_access)) {
259 return NULL;
260 }
261 PIMAGE_SECTION_HEADER section_hdr = get_section_hdr(header_buffer, MAX_HEADER_SIZE, section_num);
262 if (section_hdr == NULL || section_hdr->Misc.VirtualSize == 0) {
263 return NULL;
264 }
265 size_t buffer_size = section_hdr->Misc.VirtualSize;
266 if (roundup) {
267 DWORD va = peconv::get_sec_alignment(header_buffer, false);
268 if (va == 0) va = PAGE_SIZE;
269 buffer_size = peconv::round_up_to_unit(section_hdr->Misc.VirtualSize, va);
270 }
271 UNALIGNED_BUF module_code = peconv::alloc_unaligned(buffer_size);
272 if (module_code == NULL) {
273 return NULL;
274 }
275 size_t read_size = peconv::read_remote_memory(processHandle, LPVOID((ULONG_PTR)start_addr + section_hdr->VirtualAddress), module_code, buffer_size);
276 if (read_size == 0) {
277 // this function is slower, so use it only if the normal read has failed:
278 read_size = read_remote_area(processHandle, LPVOID((ULONG_PTR)start_addr + section_hdr->VirtualAddress), module_code, buffer_size, force_access);
279 }
280 if (read_size == 0) {
281 peconv::free_unaligned(module_code);
282 return NULL;
283 }
284 section_size = buffer_size;
285 return module_code;
286}
287
288size_t peconv::read_remote_pe(const HANDLE processHandle, LPVOID start_addr, const size_t mod_size, OUT BYTE* buffer, const size_t bufferSize)
289{
290 if (buffer == nullptr) {
291 LOG_ERROR("Invalid output buffer: NULL pointer.");
292 return 0;
293 }
294 if (bufferSize < mod_size || bufferSize < MAX_HEADER_SIZE ) {
295 LOG_ERROR("Invalid output buffer: size too small.");
296 return 0;
297 }
298 // read PE section by section
299 PBYTE hdr_buffer = buffer;
300 //try to read headers:
301 if (!read_remote_pe_header(processHandle, start_addr, hdr_buffer, MAX_HEADER_SIZE)) {
302 LOG_ERROR("Failed to read the module header.");
303 return 0;
304 }
306 LOG_ERROR("Section headers are invalid or atypically aligned.");
307 return 0;
308 }
309 size_t sections_count = get_sections_count(hdr_buffer, MAX_HEADER_SIZE);
310 LOG_DEBUG("Sections: %zu.", sections_count);
311 size_t read_size = MAX_HEADER_SIZE;
312
313 for (size_t i = 0; i < sections_count; i++) {
314 PIMAGE_SECTION_HEADER hdr = get_section_hdr(hdr_buffer, MAX_HEADER_SIZE, i);
315 if (!hdr) {
316 LOG_ERROR("Failed to read the header of section: %zu.", i);
317 break;
318 }
319 const size_t sec_va = hdr->VirtualAddress;
320 const size_t sec_vsize = get_virtual_sec_size(hdr_buffer, hdr, true);
321 if (sec_va + sec_vsize > bufferSize) {
322 LOG_ERROR("No more space in the buffer.");
323 break;
324 }
325 if (sec_vsize > 0 && !read_remote_memory(processHandle, LPVOID((ULONG_PTR)start_addr + sec_va), buffer + sec_va, sec_vsize)) {
326 LOG_WARNING("Failed to read module section %zu at 0x%llx.", i, (unsigned long long)((ULONG_PTR)start_addr + sec_va));
327 }
328 // update the end of the read area:
329 size_t new_end = sec_va + sec_vsize;
330 if (new_end > read_size) read_size = new_end;
331 }
332 LOG_DEBUG("Total read size: %zu.", read_size);
333 return read_size;
334}
335
336DWORD peconv::get_remote_image_size(IN const HANDLE processHandle, IN LPVOID start_addr)
337{
338 BYTE hdr_buffer[MAX_HEADER_SIZE] = { 0 };
339 if (!read_remote_pe_header(processHandle, start_addr, hdr_buffer, MAX_HEADER_SIZE)) {
340 return 0;
341 }
342 return peconv::get_image_size(hdr_buffer);
343}
344
346 IN LPCTSTR out_path,
347 IN const HANDLE processHandle,
348 IN LPVOID start_addr,
349 IN OUT t_pe_dump_mode &dump_mode,
350 IN OPTIONAL peconv::ExportsMapper* exportsMap)
351{
352 DWORD mod_size = get_remote_image_size(processHandle, start_addr);
353 LOG_DEBUG("Module size: %u.", mod_size);
354 if (mod_size == 0) {
355 return false;
356 }
357 BYTE* buffer = peconv::alloc_pe_buffer(mod_size, PAGE_READWRITE);
358 if (buffer == nullptr) {
359 LOG_ERROR("Failed allocating buffer. Error: %lu.", GetLastError());
360 return false;
361 }
362 //read the module that it mapped in the remote process:
363 const size_t read_size = read_remote_pe(processHandle, start_addr, mod_size, buffer, mod_size);
364 if (read_size == 0) {
365 LOG_ERROR("Failed reading module. Error: %lu.", GetLastError());
366 peconv::free_pe_buffer(buffer, mod_size);
367 buffer = nullptr;
368 return false;
369 }
370
371 const bool is_dumped = peconv::dump_pe(out_path,
372 buffer, mod_size,
373 reinterpret_cast<ULONGLONG>(start_addr),
374 dump_mode, exportsMap);
375
376 peconv::free_pe_buffer(buffer, mod_size);
377 buffer = nullptr;
378 return is_dumped;
379}
380
Functions and classes responsible for fixing Import Table. A definition of ImportedDllCoverage class.
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
DWORD get_process_id(HANDLE hProcess)
Definition: util.cpp:82
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 dump_remote_pe(IN LPCTSTR outputFilePath, IN const HANDLE processHandle, IN LPVOID moduleBase, IN OUT t_pe_dump_mode &dump_mode, IN OPTIONAL peconv::ExportsMapper *exportsMap=nullptr)
bool is_valid_sections_hdr_offset(IN const BYTE *buffer, IN const size_t buffer_size)
UNALIGNED_BUF alloc_unaligned(size_t buf_size)
Definition: buffer_util.cpp:37
ALIGNED_BUF alloc_pe_buffer(size_t buffer_size, DWORD protect, void *desired_base=nullptr)
Definition: buffer_util.cpp:78
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
DWORD get_sec_alignment(IN const BYTE *modulePtr, IN bool is_raw)
INT_TYPE round_up_to_unit(const INT_TYPE size, const INT_TYPE unit)
Definition: util.h:43
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)
const ULONGLONG MAX_HEADER_SIZE
bool read_remote_pe_header(HANDLE processHandle, LPVOID moduleBase, OUT BYTE *buffer, const size_t bufferSize, bool force_access=false)
BYTE * get_nt_hdrs(IN const BYTE *pe_buffer, IN OPTIONAL size_t buffer_size=0, IN OPTIONAL const LONG max_pe_offset=MAX_HEADER_SIZE)
bool dump_pe(IN LPCTSTR 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:25
DWORD get_hdrs_size(IN const BYTE *pe_buffer)
PBYTE UNALIGNED_BUF
Definition: buffer_util.h:41
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:45
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.