pe_image.cc revision 201ade2fbba22bfb27ae029f4d23fca6ded109a0
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
10namespace base {
11namespace 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.
22struct EnumAllImportsStorage {
23  PEImage::EnumImportsFunction callback;
24  PVOID cookie;
25};
26
27namespace {
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.
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.
47bool 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.
58bool 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
71void PEImage::set_module(HMODULE module) {
72  module_ = module;
73}
74
75PIMAGE_DOS_HEADER PEImage::GetDosHeader() const {
76  return reinterpret_cast<PIMAGE_DOS_HEADER>(module_);
77}
78
79PIMAGE_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
86PIMAGE_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
96WORD PEImage::GetNumSections() const {
97  return GetNTHeaders()->FileHeader.NumberOfSections;
98}
99
100DWORD PEImage::GetImageDirectoryEntrySize(UINT directory) const {
101  PIMAGE_NT_HEADERS nt_headers = GetNTHeaders();
102
103  return nt_headers->OptionalHeader.DataDirectory[directory].Size;
104}
105
106PVOID 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
113PIMAGE_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
131PIMAGE_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
151PDWORD 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
167FARPROC 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
189bool 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
239bool 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
255bool 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
309bool 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
338bool 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
360bool 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
389bool PEImage::EnumAllImports(EnumImportsFunction callback, PVOID cookie) const {
390  EnumAllImportsStorage temp = { callback, cookie };
391  return EnumImportChunks(ProcessImportChunk, &temp);
392}
393
394bool 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
449bool 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
494bool PEImage::EnumAllDelayImports(EnumImportsFunction callback,
495                                  PVOID cookie) const {
496  EnumAllImportsStorage temp = { callback, cookie };
497  return EnumDelayImportChunks(ProcessDelayImportChunk, &temp);
498}
499
500bool 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
521bool PEImage::ImageRVAToOnDiskOffset(DWORD rva, DWORD *on_disk_offset) const {
522  LPVOID address = RVAToAddr(rva);
523  return ImageAddrToOnDiskOffset(address, on_disk_offset);
524}
525
526bool 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
549PVOID PEImage::RVAToAddr(DWORD rva) const {
550  if (rva == 0)
551    return NULL;
552
553  return reinterpret_cast<char*>(module_) + rva;
554}
555
556PVOID 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