• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2010 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 // This file implements PEImage, a generic class to manipulate PE files.
6 // This file was adapted from GreenBorder's Code.
7 
8 #include "base/win/pe_image.h"
9 
10 namespace base {
11 namespace win {
12 
13 #if defined(_WIN64) && !defined(NACL_WIN64)
14 // TODO(rvargas): Bug 27218. Make sure this is ok.
15 #error This code is not tested on x64. Please make sure all the base unit tests\
16  pass before doing any real work. The current unit tests don't test the\
17  differences between 32- and 64-bits implementations. Bugs may slip through.\
18  You need to improve the coverage before continuing.
19 #endif
20 
21 // Structure to perform imports enumerations.
22 struct EnumAllImportsStorage {
23   PEImage::EnumImportsFunction callback;
24   PVOID cookie;
25 };
26 
27 namespace {
28 
29   // Compare two strings byte by byte on an unsigned basis.
30   //   if s1 == s2, return 0
31   //   if s1 < s2, return negative
32   //   if s1 > s2, return positive
33   // Exception if inputs are invalid.
StrCmpByByte(LPCSTR s1,LPCSTR s2)34   int StrCmpByByte(LPCSTR s1, LPCSTR s2) {
35     while (*s1 != '\0' && *s1 == *s2) {
36       ++s1;
37       ++s2;
38     }
39 
40     return (*reinterpret_cast<const unsigned char*>(s1) -
41             *reinterpret_cast<const unsigned char*>(s2));
42   }
43 
44 }  // namespace
45 
46 // Callback used to enumerate imports. See EnumImportChunksFunction.
47 bool ProcessImportChunk(const PEImage &image, LPCSTR module,
48                         PIMAGE_THUNK_DATA name_table,
49                         PIMAGE_THUNK_DATA iat, PVOID cookie) {
50   EnumAllImportsStorage &storage = *reinterpret_cast<EnumAllImportsStorage*>(
51                                        cookie);
52 
53   return image.EnumOneImportChunk(storage.callback, module, name_table, iat,
54                                   storage.cookie);
55 }
56 
57 // Callback used to enumerate delay imports. See EnumDelayImportChunksFunction.
58 bool ProcessDelayImportChunk(const PEImage &image,
59                              PImgDelayDescr delay_descriptor,
60                              LPCSTR module, PIMAGE_THUNK_DATA name_table,
61                              PIMAGE_THUNK_DATA iat, PIMAGE_THUNK_DATA bound_iat,
62                              PIMAGE_THUNK_DATA unload_iat, PVOID cookie) {
63   EnumAllImportsStorage &storage = *reinterpret_cast<EnumAllImportsStorage*>(
64                                        cookie);
65 
66   return image.EnumOneDelayImportChunk(storage.callback, delay_descriptor,
67                                        module, name_table, iat, bound_iat,
68                                        unload_iat, storage.cookie);
69 }
70 
71 void PEImage::set_module(HMODULE module) {
72   module_ = module;
73 }
74 
75 PIMAGE_DOS_HEADER PEImage::GetDosHeader() const {
76   return reinterpret_cast<PIMAGE_DOS_HEADER>(module_);
77 }
78 
79 PIMAGE_NT_HEADERS PEImage::GetNTHeaders() const {
80   PIMAGE_DOS_HEADER dos_header = GetDosHeader();
81 
82   return reinterpret_cast<PIMAGE_NT_HEADERS>(
83       reinterpret_cast<char*>(dos_header) + dos_header->e_lfanew);
84 }
85 
86 PIMAGE_SECTION_HEADER PEImage::GetSectionHeader(UINT section) const {
87   PIMAGE_NT_HEADERS nt_headers = GetNTHeaders();
88   PIMAGE_SECTION_HEADER first_section = IMAGE_FIRST_SECTION(nt_headers);
89 
90   if (section < nt_headers->FileHeader.NumberOfSections)
91     return first_section + section;
92   else
93     return NULL;
94 }
95 
96 WORD PEImage::GetNumSections() const {
97   return GetNTHeaders()->FileHeader.NumberOfSections;
98 }
99 
100 DWORD PEImage::GetImageDirectoryEntrySize(UINT directory) const {
101   PIMAGE_NT_HEADERS nt_headers = GetNTHeaders();
102 
103   return nt_headers->OptionalHeader.DataDirectory[directory].Size;
104 }
105 
106 PVOID PEImage::GetImageDirectoryEntryAddr(UINT directory) const {
107   PIMAGE_NT_HEADERS nt_headers = GetNTHeaders();
108 
109   return RVAToAddr(
110       nt_headers->OptionalHeader.DataDirectory[directory].VirtualAddress);
111 }
112 
113 PIMAGE_SECTION_HEADER PEImage::GetImageSectionFromAddr(PVOID address) const {
114   PBYTE target = reinterpret_cast<PBYTE>(address);
115   PIMAGE_SECTION_HEADER section;
116 
117   for (UINT i = 0; NULL != (section = GetSectionHeader(i)); i++) {
118     // Don't use the virtual RVAToAddr.
119     PBYTE start = reinterpret_cast<PBYTE>(
120                       PEImage::RVAToAddr(section->VirtualAddress));
121 
122     DWORD size = section->Misc.VirtualSize;
123 
124     if ((start <= target) && (start + size > target))
125       return section;
126   }
127 
128   return NULL;
129 }
130 
GetImageSectionHeaderByName(LPCSTR section_name) const131 PIMAGE_SECTION_HEADER PEImage::GetImageSectionHeaderByName(
132     LPCSTR section_name) const {
133   if (NULL == section_name)
134     return NULL;
135 
136   PIMAGE_SECTION_HEADER ret = NULL;
137   int num_sections = GetNumSections();
138 
139   for (int i = 0; i < num_sections; i++) {
140     PIMAGE_SECTION_HEADER section = GetSectionHeader(i);
141     if (0 == _strnicmp(reinterpret_cast<LPCSTR>(section->Name), section_name,
142                        sizeof(section->Name))) {
143       ret = section;
144       break;
145     }
146   }
147 
148   return ret;
149 }
150 
GetExportEntry(LPCSTR name) const151 PDWORD PEImage::GetExportEntry(LPCSTR name) const {
152   PIMAGE_EXPORT_DIRECTORY exports = GetExportDirectory();
153 
154   if (NULL == exports)
155     return NULL;
156 
157   WORD ordinal = 0;
158   if (!GetProcOrdinal(name, &ordinal))
159     return NULL;
160 
161   PDWORD functions = reinterpret_cast<PDWORD>(
162                          RVAToAddr(exports->AddressOfFunctions));
163 
164   return functions + ordinal - exports->Base;
165 }
166 
GetProcAddress(LPCSTR function_name) const167 FARPROC PEImage::GetProcAddress(LPCSTR function_name) const {
168   PDWORD export_entry = GetExportEntry(function_name);
169   if (NULL == export_entry)
170     return NULL;
171 
172   PBYTE function = reinterpret_cast<PBYTE>(RVAToAddr(*export_entry));
173 
174   PBYTE exports = reinterpret_cast<PBYTE>(
175       GetImageDirectoryEntryAddr(IMAGE_DIRECTORY_ENTRY_EXPORT));
176   DWORD size = GetImageDirectoryEntrySize(IMAGE_DIRECTORY_ENTRY_EXPORT);
177 
178   // Check for forwarded exports as a special case.
179   if (exports <= function && exports + size > function)
180 #pragma warning(push)
181 #pragma warning(disable: 4312)
182     // This cast generates a warning because it is 32 bit specific.
183     return reinterpret_cast<FARPROC>(0xFFFFFFFF);
184 #pragma warning(pop)
185 
186   return reinterpret_cast<FARPROC>(function);
187 }
188 
GetProcOrdinal(LPCSTR function_name,WORD * ordinal) const189 bool PEImage::GetProcOrdinal(LPCSTR function_name, WORD *ordinal) const {
190   if (NULL == ordinal)
191     return false;
192 
193   PIMAGE_EXPORT_DIRECTORY exports = GetExportDirectory();
194 
195   if (NULL == exports)
196     return false;
197 
198   if (IsOrdinal(function_name)) {
199     *ordinal = ToOrdinal(function_name);
200   } else {
201     PDWORD names = reinterpret_cast<PDWORD>(RVAToAddr(exports->AddressOfNames));
202     PDWORD lower = names;
203     PDWORD upper = names + exports->NumberOfNames;
204     int cmp = -1;
205 
206     // Binary Search for the name.
207     while (lower != upper) {
208       PDWORD middle = lower + (upper - lower) / 2;
209       LPCSTR name = reinterpret_cast<LPCSTR>(RVAToAddr(*middle));
210 
211       // This may be called by sandbox before MSVCRT dll loads, so can't use
212       // CRT function here.
213       cmp = StrCmpByByte(function_name, name);
214 
215       if (cmp == 0) {
216         lower = middle;
217         break;
218       }
219 
220       if (cmp > 0)
221         lower = middle + 1;
222       else
223         upper = middle;
224     }
225 
226     if (cmp != 0)
227       return false;
228 
229 
230     PWORD ordinals = reinterpret_cast<PWORD>(
231                          RVAToAddr(exports->AddressOfNameOrdinals));
232 
233     *ordinal = ordinals[lower - names] + static_cast<WORD>(exports->Base);
234   }
235 
236   return true;
237 }
238 
EnumSections(EnumSectionsFunction callback,PVOID cookie) const239 bool PEImage::EnumSections(EnumSectionsFunction callback, PVOID cookie) const {
240   PIMAGE_NT_HEADERS nt_headers = GetNTHeaders();
241   UINT num_sections = nt_headers->FileHeader.NumberOfSections;
242   PIMAGE_SECTION_HEADER section = GetSectionHeader(0);
243 
244   for (UINT i = 0; i < num_sections; i++, section++) {
245     PVOID section_start = RVAToAddr(section->VirtualAddress);
246     DWORD size = section->Misc.VirtualSize;
247 
248     if (!callback(*this, section, section_start, size, cookie))
249       return false;
250   }
251 
252   return true;
253 }
254 
EnumExports(EnumExportsFunction callback,PVOID cookie) const255 bool PEImage::EnumExports(EnumExportsFunction callback, PVOID cookie) const {
256   PVOID directory = GetImageDirectoryEntryAddr(IMAGE_DIRECTORY_ENTRY_EXPORT);
257   DWORD size = GetImageDirectoryEntrySize(IMAGE_DIRECTORY_ENTRY_EXPORT);
258 
259   // Check if there are any exports at all.
260   if (NULL == directory || 0 == size)
261     return true;
262 
263   PIMAGE_EXPORT_DIRECTORY exports = reinterpret_cast<PIMAGE_EXPORT_DIRECTORY>(
264                                         directory);
265   UINT ordinal_base = exports->Base;
266   UINT num_funcs = exports->NumberOfFunctions;
267   UINT num_names = exports->NumberOfNames;
268   PDWORD functions  = reinterpret_cast<PDWORD>(RVAToAddr(
269                           exports->AddressOfFunctions));
270   PDWORD names = reinterpret_cast<PDWORD>(RVAToAddr(exports->AddressOfNames));
271   PWORD ordinals = reinterpret_cast<PWORD>(RVAToAddr(
272                        exports->AddressOfNameOrdinals));
273 
274   for (UINT count = 0; count < num_funcs; count++) {
275     PVOID func = RVAToAddr(functions[count]);
276     if (NULL == func)
277       continue;
278 
279     // Check for a name.
280     LPCSTR name = NULL;
281     UINT hint;
282     for (hint = 0; hint < num_names; hint++) {
283       if (ordinals[hint] == count) {
284         name = reinterpret_cast<LPCSTR>(RVAToAddr(names[hint]));
285         break;
286       }
287     }
288 
289     if (name == NULL)
290       hint = 0;
291 
292     // Check for forwarded exports.
293     LPCSTR forward = NULL;
294     if (reinterpret_cast<char*>(func) >= reinterpret_cast<char*>(directory) &&
295         reinterpret_cast<char*>(func) <= reinterpret_cast<char*>(directory) +
296             size) {
297       forward = reinterpret_cast<LPCSTR>(func);
298       func = 0;
299     }
300 
301     if (!callback(*this, ordinal_base + count, hint, name, func, forward,
302                   cookie))
303       return false;
304   }
305 
306   return true;
307 }
308 
EnumRelocs(EnumRelocsFunction callback,PVOID cookie) const309 bool PEImage::EnumRelocs(EnumRelocsFunction callback, PVOID cookie) const {
310   PVOID directory = GetImageDirectoryEntryAddr(IMAGE_DIRECTORY_ENTRY_BASERELOC);
311   DWORD size = GetImageDirectoryEntrySize(IMAGE_DIRECTORY_ENTRY_BASERELOC);
312   PIMAGE_BASE_RELOCATION base = reinterpret_cast<PIMAGE_BASE_RELOCATION>(
313       directory);
314 
315   if (directory == NULL || size < sizeof(IMAGE_BASE_RELOCATION))
316     return true;
317 
318   while (base->SizeOfBlock) {
319     PWORD reloc = reinterpret_cast<PWORD>(base + 1);
320     UINT num_relocs = (base->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) /
321         sizeof(WORD);
322 
323     for (UINT i = 0; i < num_relocs; i++, reloc++) {
324       WORD type = *reloc >> 12;
325       PVOID address = RVAToAddr(base->VirtualAddress + (*reloc & 0x0FFF));
326 
327       if (!callback(*this, type, address, cookie))
328         return false;
329     }
330 
331     base = reinterpret_cast<PIMAGE_BASE_RELOCATION>(
332                reinterpret_cast<char*>(base) + base->SizeOfBlock);
333   }
334 
335   return true;
336 }
337 
EnumImportChunks(EnumImportChunksFunction callback,PVOID cookie) const338 bool PEImage::EnumImportChunks(EnumImportChunksFunction callback,
339                                PVOID cookie) const {
340   DWORD size = GetImageDirectoryEntrySize(IMAGE_DIRECTORY_ENTRY_IMPORT);
341   PIMAGE_IMPORT_DESCRIPTOR import = GetFirstImportChunk();
342 
343   if (import == NULL || size < sizeof(IMAGE_IMPORT_DESCRIPTOR))
344     return true;
345 
346   for (; import->FirstThunk; import++) {
347     LPCSTR module_name = reinterpret_cast<LPCSTR>(RVAToAddr(import->Name));
348     PIMAGE_THUNK_DATA name_table = reinterpret_cast<PIMAGE_THUNK_DATA>(
349                                        RVAToAddr(import->OriginalFirstThunk));
350     PIMAGE_THUNK_DATA iat = reinterpret_cast<PIMAGE_THUNK_DATA>(
351                                 RVAToAddr(import->FirstThunk));
352 
353     if (!callback(*this, module_name, name_table, iat, cookie))
354       return false;
355   }
356 
357   return true;
358 }
359 
EnumOneImportChunk(EnumImportsFunction callback,LPCSTR module_name,PIMAGE_THUNK_DATA name_table,PIMAGE_THUNK_DATA iat,PVOID cookie) const360 bool PEImage::EnumOneImportChunk(EnumImportsFunction callback,
361                                  LPCSTR module_name,
362                                  PIMAGE_THUNK_DATA name_table,
363                                  PIMAGE_THUNK_DATA iat, PVOID cookie) const {
364   if (NULL == name_table)
365     return false;
366 
367   for (; name_table && name_table->u1.Ordinal; name_table++, iat++) {
368     LPCSTR name = NULL;
369     WORD ordinal = 0;
370     WORD hint = 0;
371 
372     if (IMAGE_SNAP_BY_ORDINAL(name_table->u1.Ordinal)) {
373       ordinal = static_cast<WORD>(IMAGE_ORDINAL32(name_table->u1.Ordinal));
374     } else {
375       PIMAGE_IMPORT_BY_NAME import = reinterpret_cast<PIMAGE_IMPORT_BY_NAME>(
376           RVAToAddr(name_table->u1.ForwarderString));
377 
378       hint = import->Hint;
379       name = reinterpret_cast<LPCSTR>(&import->Name);
380     }
381 
382     if (!callback(*this, module_name, ordinal, name, hint, iat, cookie))
383       return false;
384   }
385 
386   return true;
387 }
388 
EnumAllImports(EnumImportsFunction callback,PVOID cookie) const389 bool PEImage::EnumAllImports(EnumImportsFunction callback, PVOID cookie) const {
390   EnumAllImportsStorage temp = { callback, cookie };
391   return EnumImportChunks(ProcessImportChunk, &temp);
392 }
393 
EnumDelayImportChunks(EnumDelayImportChunksFunction callback,PVOID cookie) const394 bool PEImage::EnumDelayImportChunks(EnumDelayImportChunksFunction callback,
395                                     PVOID cookie) const {
396   PVOID directory = GetImageDirectoryEntryAddr(
397                         IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT);
398   DWORD size = GetImageDirectoryEntrySize(IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT);
399   PImgDelayDescr delay_descriptor = reinterpret_cast<PImgDelayDescr>(directory);
400 
401   if (directory == NULL || size == 0)
402     return true;
403 
404   for (; delay_descriptor->rvaHmod; delay_descriptor++) {
405     PIMAGE_THUNK_DATA name_table;
406     PIMAGE_THUNK_DATA iat;
407     PIMAGE_THUNK_DATA bound_iat;    // address of the optional bound IAT
408     PIMAGE_THUNK_DATA unload_iat;   // address of optional copy of original IAT
409     LPCSTR module_name;
410 
411     // check if VC7-style imports, using RVAs instead of
412     // VC6-style addresses.
413     bool rvas = (delay_descriptor->grAttrs & dlattrRva) != 0;
414 
415     if (rvas) {
416       module_name = reinterpret_cast<LPCSTR>(
417                         RVAToAddr(delay_descriptor->rvaDLLName));
418       name_table = reinterpret_cast<PIMAGE_THUNK_DATA>(
419                        RVAToAddr(delay_descriptor->rvaINT));
420       iat = reinterpret_cast<PIMAGE_THUNK_DATA>(
421                 RVAToAddr(delay_descriptor->rvaIAT));
422       bound_iat = reinterpret_cast<PIMAGE_THUNK_DATA>(
423                       RVAToAddr(delay_descriptor->rvaBoundIAT));
424       unload_iat = reinterpret_cast<PIMAGE_THUNK_DATA>(
425                        RVAToAddr(delay_descriptor->rvaUnloadIAT));
426     } else {
427 #pragma warning(push)
428 #pragma warning(disable: 4312)
429       // These casts generate warnings because they are 32 bit specific.
430       module_name = reinterpret_cast<LPCSTR>(delay_descriptor->rvaDLLName);
431       name_table = reinterpret_cast<PIMAGE_THUNK_DATA>(
432                        delay_descriptor->rvaINT);
433       iat = reinterpret_cast<PIMAGE_THUNK_DATA>(delay_descriptor->rvaIAT);
434       bound_iat = reinterpret_cast<PIMAGE_THUNK_DATA>(
435                       delay_descriptor->rvaBoundIAT);
436       unload_iat = reinterpret_cast<PIMAGE_THUNK_DATA>(
437                        delay_descriptor->rvaUnloadIAT);
438 #pragma warning(pop)
439     }
440 
441     if (!callback(*this, delay_descriptor, module_name, name_table, iat,
442                   bound_iat, unload_iat, cookie))
443       return false;
444   }
445 
446   return true;
447 }
448 
EnumOneDelayImportChunk(EnumImportsFunction callback,PImgDelayDescr delay_descriptor,LPCSTR module_name,PIMAGE_THUNK_DATA name_table,PIMAGE_THUNK_DATA iat,PIMAGE_THUNK_DATA bound_iat,PIMAGE_THUNK_DATA unload_iat,PVOID cookie) const449 bool PEImage::EnumOneDelayImportChunk(EnumImportsFunction callback,
450                                       PImgDelayDescr delay_descriptor,
451                                       LPCSTR module_name,
452                                       PIMAGE_THUNK_DATA name_table,
453                                       PIMAGE_THUNK_DATA iat,
454                                       PIMAGE_THUNK_DATA bound_iat,
455                                       PIMAGE_THUNK_DATA unload_iat,
456                                       PVOID cookie) const {
457   UNREFERENCED_PARAMETER(bound_iat);
458   UNREFERENCED_PARAMETER(unload_iat);
459 
460   for (; name_table->u1.Ordinal; name_table++, iat++) {
461     LPCSTR name = NULL;
462     WORD ordinal = 0;
463     WORD hint = 0;
464 
465     if (IMAGE_SNAP_BY_ORDINAL(name_table->u1.Ordinal)) {
466       ordinal = static_cast<WORD>(IMAGE_ORDINAL32(name_table->u1.Ordinal));
467     } else {
468       PIMAGE_IMPORT_BY_NAME import;
469       bool rvas = (delay_descriptor->grAttrs & dlattrRva) != 0;
470 
471       if (rvas) {
472         import = reinterpret_cast<PIMAGE_IMPORT_BY_NAME>(
473                      RVAToAddr(name_table->u1.ForwarderString));
474       } else {
475 #pragma warning(push)
476 #pragma warning(disable: 4312)
477         // This cast generates a warning because it is 32 bit specific.
478         import = reinterpret_cast<PIMAGE_IMPORT_BY_NAME>(
479                      name_table->u1.ForwarderString);
480 #pragma warning(pop)
481       }
482 
483       hint = import->Hint;
484       name = reinterpret_cast<LPCSTR>(&import->Name);
485     }
486 
487     if (!callback(*this, module_name, ordinal, name, hint, iat, cookie))
488       return false;
489   }
490 
491   return true;
492 }
493 
EnumAllDelayImports(EnumImportsFunction callback,PVOID cookie) const494 bool PEImage::EnumAllDelayImports(EnumImportsFunction callback,
495                                   PVOID cookie) const {
496   EnumAllImportsStorage temp = { callback, cookie };
497   return EnumDelayImportChunks(ProcessDelayImportChunk, &temp);
498 }
499 
VerifyMagic() const500 bool PEImage::VerifyMagic() const {
501   PIMAGE_DOS_HEADER dos_header = GetDosHeader();
502 
503   if (dos_header->e_magic != IMAGE_DOS_SIGNATURE)
504     return false;
505 
506   PIMAGE_NT_HEADERS nt_headers = GetNTHeaders();
507 
508   if (nt_headers->Signature != IMAGE_NT_SIGNATURE)
509     return false;
510 
511   if (nt_headers->FileHeader.SizeOfOptionalHeader !=
512       sizeof(IMAGE_OPTIONAL_HEADER))
513     return false;
514 
515   if (nt_headers->OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR_MAGIC)
516     return false;
517 
518   return true;
519 }
520 
ImageRVAToOnDiskOffset(DWORD rva,DWORD * on_disk_offset) const521 bool PEImage::ImageRVAToOnDiskOffset(DWORD rva, DWORD *on_disk_offset) const {
522   LPVOID address = RVAToAddr(rva);
523   return ImageAddrToOnDiskOffset(address, on_disk_offset);
524 }
525 
ImageAddrToOnDiskOffset(LPVOID address,DWORD * on_disk_offset) const526 bool PEImage::ImageAddrToOnDiskOffset(LPVOID address,
527                                       DWORD *on_disk_offset) const {
528   if (NULL == address)
529     return false;
530 
531   // Get the section that this address belongs to.
532   PIMAGE_SECTION_HEADER section_header = GetImageSectionFromAddr(address);
533   if (NULL == section_header)
534     return false;
535 
536 #pragma warning(push)
537 #pragma warning(disable: 4311)
538   // These casts generate warnings because they are 32 bit specific.
539   // Don't follow the virtual RVAToAddr, use the one on the base.
540   DWORD offset_within_section = reinterpret_cast<DWORD>(address) -
541                                     reinterpret_cast<DWORD>(PEImage::RVAToAddr(
542                                         section_header->VirtualAddress));
543 #pragma warning(pop)
544 
545   *on_disk_offset = section_header->PointerToRawData + offset_within_section;
546   return true;
547 }
548 
RVAToAddr(DWORD rva) const549 PVOID PEImage::RVAToAddr(DWORD rva) const {
550   if (rva == 0)
551     return NULL;
552 
553   return reinterpret_cast<char*>(module_) + rva;
554 }
555 
RVAToAddr(DWORD rva) const556 PVOID PEImageAsData::RVAToAddr(DWORD rva) const {
557   if (rva == 0)
558     return NULL;
559 
560   PVOID in_memory = PEImage::RVAToAddr(rva);
561   DWORD disk_offset;
562 
563   if (!ImageAddrToOnDiskOffset(in_memory, &disk_offset))
564     return NULL;
565 
566   return PEImage::RVAToAddr(disk_offset);
567 }
568 
569 }  // namespace win
570 }  // namespace base
571