Bitdefender Hypervisor Memory Introspection
winpe.c
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2020 Bitdefender
3  * SPDX-License-Identifier: Apache-2.0
4  */
5 #include "winpe.h"
6 #include "decoder.h"
7 #include "introcpu.h"
8 #include "guests.h"
9 
10 
12 #define MAX_NUMBER_OF_EXPORT_NAMES 65535ul
13 
15 #define MAX_UNWIND_INFO_TRIES 512
16 
20 #define MAX_SIZE_OF_IMAGE (2 * ONE_GIGABYTE)
21 
23 #define MAX_UNWIND_CODES 50
24 
28 typedef struct _OPTIONAL_HEADER_INFO
29 {
38 
39 
40 static INTSTATUS
42  _In_ void *OptionalHeader,
43  _In_ DWORD SizeOfOptionalHeader,
45  )
65 {
66  WORD magic = ((WORD *)OptionalHeader)[0];
67 
68  if (magic == IMAGE_OPTIONAL_HEADER_PE32)
69  {
70  IMAGE_OPTIONAL_HEADER32 *pOptional = (IMAGE_OPTIONAL_HEADER32 *)OptionalHeader;
71  DWORD actualSizeOfOptionalHeader = 0;
72 
73  // Size of OptionalHeader without the data directories
74  actualSizeOfOptionalHeader = sizeof(IMAGE_OPTIONAL_HEADER32) - sizeof(IMAGE_DATA_DIRECTORY) *
76  // Add the size of the actual data directories to the size of the optional header
77  actualSizeOfOptionalHeader += sizeof(IMAGE_DATA_DIRECTORY) * pOptional->NumberOfRvaAndSizes;
78 
79  if (actualSizeOfOptionalHeader != SizeOfOptionalHeader)
80  {
81  WARNING("[WARNING] SizeOfOptionalHeader (0x%08x) different from actual size (0x%08x).\n",
82  SizeOfOptionalHeader, actualSizeOfOptionalHeader);
84  }
85 
86  Info->SizeOfImage = pOptional->SizeOfImage;
87  Info->EntryPoint = pOptional->AddressOfEntryPoint;
88  Info->ImageBase = pOptional->ImageBase;
89  Info->Subsystem = pOptional->Subsystem;
90  Info->SectionAlign = pOptional->SectionAlignment;
91  Info->FileAlign = pOptional->FileAlignment;
92  Info->Image64 = FALSE;
93  }
94  else if (magic == IMAGE_OPTIONAL_HEADER_PE64)
95  {
96  IMAGE_OPTIONAL_HEADER64 *pOptional = (IMAGE_OPTIONAL_HEADER64 *)OptionalHeader;
97  DWORD actualSizeOfOptionalHeader = 0;
98 
99  // Size of OptionalHeader without the data directories
100  actualSizeOfOptionalHeader = sizeof(IMAGE_OPTIONAL_HEADER64) - sizeof(IMAGE_DATA_DIRECTORY) *
102  // Add the size of the actual data directories to the size of the optional header
103  actualSizeOfOptionalHeader += sizeof(IMAGE_DATA_DIRECTORY) * pOptional->NumberOfRvaAndSizes;
104 
105  if (actualSizeOfOptionalHeader != SizeOfOptionalHeader)
106  {
107  WARNING("[WARNING] SizeOfOptionalHeader (0x%08x) different from actual size (0x%08x).\n",
108  SizeOfOptionalHeader, actualSizeOfOptionalHeader);
110  }
111 
112  Info->SizeOfImage = pOptional->SizeOfImage;
113  Info->EntryPoint = pOptional->AddressOfEntryPoint;
114  Info->ImageBase = pOptional->ImageBase;
115  Info->Subsystem = pOptional->Subsystem;
116  Info->SectionAlign = pOptional->SectionAlignment;
117  Info->FileAlign = pOptional->FileAlignment;
118  Info->Image64 = TRUE;
119  }
120  else
121  {
122  ERROR("[ERROR] Optional header has an invalid magic: 0x%04x!\n", magic);
124  }
125 
126  return INT_STATUS_SUCCESS;
127 }
128 
129 
130 INTSTATUS
133  _In_opt_ BYTE *ImageBaseBuffer,
134  _In_opt_ DWORD ImageBaseBufferSize,
135  _Out_opt_ INTRO_PE_INFO *PeInfo,
136  _In_opt_ QWORD Cr3
137  )
161 {
162  INTSTATUS status = INT_STATUS_SUCCESS;
163  DWORD size;
164  BOOLEAN image64 = TRUE;
165  BYTE *pBase;
166  IMAGE_NT_HEADERS64 *pNth64;
167  IMAGE_NT_HEADERS32 *pNth32;
168  IMAGE_DOS_HEADER *pDosHeader;
169  IMAGE_SECTION_HEADER *pSec;
170  WORD subsystem = 0, machine = 0;
171  DWORD timeDateStamp = 0, sizeOfImage = 0;
172  DWORD entryPoint = 0, numberOfSections = 0;
173  DWORD actualSectionSize = 0;
174  DWORD sectionAlign = 0, fileAlign = 0;
175  QWORD imageBase = 0;
176  OPTIONAL_HEADER_INFO opthdrInfo = { 0 };
177  QWORD e_lfanew, secOff = 0;
178  BYTE *pSmallBase = NULL;
179 
180  if ((ImageBase & PAGE_OFFSET) != 0)
181  {
182  ERROR("[ERROR] Image at 0x%016llx is not page-aligned\n", ImageBase);
184  }
185 
186  if (NULL != ImageBaseBuffer)
187  {
188  // Make sure we can freely access 4K of data, even if the actual MZPE is smaller.
189  if (ImageBaseBufferSize < PAGE_SIZE)
190  {
191  TRACE("[TRACE] The MZPE to validate is smaller than 4K: 0x%08x\n", ImageBaseBufferSize);
192 
194  if (pSmallBase == NULL)
195  {
197  }
198 
199  pBase = pSmallBase;
200  size = PAGE_SIZE;
201 
202  memcpy(pBase, ImageBaseBuffer, ImageBaseBufferSize);
203  }
204  else
205  {
206  pBase = ImageBaseBuffer;
207  size = ImageBaseBufferSize;
208  }
209  }
210  else
211  {
212  if (ImageBase < ONE_KILOBYTE * 4)
213  {
214  ERROR("[ERROR] Can not map MZPE image at GVA 0x%016llx!\n", ImageBase);
216  }
217 
218  status = IntVirtMemMap(ImageBase, PAGE_SIZE, Cr3, 0, &pBase);
219  if (!INT_SUCCESS(status))
220  {
221  ERROR("[ERROR] Failed mapping VA 0x%016llx to host: 0x%08x\n", ImageBase, status);
222  return status;
223  }
224 
225  size = PAGE_SIZE;
226  }
227 
228  if (size < PAGE_SIZE)
229  {
231  goto leave;
232  }
233 
234  pDosHeader = (IMAGE_DOS_HEADER *)pBase;
235 
236  if (pDosHeader->e_magic != IMAGE_DOS_SIGNATURE)
237  {
239  goto leave;
240  }
241 
242  e_lfanew = (DWORD)pDosHeader->e_lfanew;
243  if (e_lfanew >= size)
244  {
246  goto leave;
247  }
248 
249  if (e_lfanew + sizeof(IMAGE_NT_HEADERS64) >= size)
250  {
252  goto leave;
253  }
254 
255  pNth32 = (IMAGE_NT_HEADERS32 *)(pBase + e_lfanew);
256  pNth64 = (IMAGE_NT_HEADERS64 *)(pBase + e_lfanew);
257 
258  // Validate the PE signature. Doesn't matter what we use here since only the OptionalHeader is different
260  {
261  if (pNth64->Signature != IMAGE_NT_SIGNATURE)
262  {
264  goto leave;
265  }
266 
267  // Safe cast, we know that e_lfanew is a valid RVA
268  secOff = e_lfanew + 4 + sizeof(IMAGE_FILE_HEADER) + pNth64->FileHeader.SizeOfOptionalHeader;
269  timeDateStamp = pNth64->FileHeader.TimeDateStamp;
270  numberOfSections = pNth64->FileHeader.NumberOfSections;
271  machine = pNth64->FileHeader.Machine;
272 
274  &opthdrInfo);
275  if (!INT_SUCCESS(status))
276  {
277  ERROR("[ERROR] Image at 0x%016llx has an invalid optional header!\n", ImageBase);
278  goto leave;
279  }
280 
281  if (!opthdrInfo.Image64)
282  {
283  WARNING("[WARNING] Image 0x%016llx is AMD64 but has a PE32 optional header! Will consider it 32 bits\n",
284  ImageBase);
285  }
286  }
287  else if (pNth32->FileHeader.Machine == IMAGE_FILE_MACHINE_I386)
288  {
289  if (pNth32->Signature != IMAGE_NT_SIGNATURE)
290  {
292  goto leave;
293  }
294 
295  // Safe cast, we know that e_lfanew is a valid RVA
296  secOff = e_lfanew + 4 + sizeof(IMAGE_FILE_HEADER) + pNth32->FileHeader.SizeOfOptionalHeader;
297  timeDateStamp = pNth32->FileHeader.TimeDateStamp;
298  numberOfSections = pNth32->FileHeader.NumberOfSections;
299  machine = pNth32->FileHeader.Machine;
300 
302  &opthdrInfo);
303  if (!INT_SUCCESS(status))
304  {
305  ERROR("[ERROR] Image at 0x%016llx has an invalid optional header!\n", ImageBase);
306  goto leave;
307  }
308 
309  if (opthdrInfo.Image64)
310  {
311  WARNING("[WARNING] Image 0x%016llx is I386 but has a PE64 optional header! Will consider it 64 bits\n",
312  ImageBase);
313  }
314  }
315  else
316  {
318  goto leave;
319  }
320 
321  image64 = opthdrInfo.Image64;
322  sizeOfImage = opthdrInfo.SizeOfImage;
323  entryPoint = opthdrInfo.EntryPoint;
324  imageBase = opthdrInfo.ImageBase;
325  subsystem = opthdrInfo.Subsystem;
326  sectionAlign = opthdrInfo.SectionAlign;
327  fileAlign = opthdrInfo.FileAlign;
328 
329  if (sizeOfImage > MAX_SIZE_OF_IMAGE)
330  {
331  ERROR("[ERROR] Image at 0x%016llx has SizeOfImage (0x%08x) larger than the maximum allowed (0x%016llx)\n",
332  ImageBase, sizeOfImage, MAX_SIZE_OF_IMAGE);
334  goto leave;
335  }
336 
337  if (sectionAlign == 0)
338  {
339  ERROR("[ERROR] Section alignment is 0 for image at 0x%016llx\n", ImageBase);
341  goto leave;
342  }
343 
344  if ((sectionAlign < fileAlign) || (fileAlign < MIN_FILE_ALIGNMENT || fileAlign > MAX_FILE_ALIGNMENT))
345  {
346  WARNING("[WARNING] Image alignments invalid. FileAlignment: 0x%08x, SectionAlignment: 0x%08x. "
347  "The image at 0x%016llx may have been tampered with\n",
348  fileAlign, sectionAlign, ImageBase);
349  }
350 
351  if (secOff + sizeof(IMAGE_SECTION_HEADER) * numberOfSections > size)
352  {
353  ERROR("[ERROR] Sections headers point out of the mapping. SectionOffset: 0x%08llx; NrOfSections: %d; "
354  "MaxSize: 0x%08llx; MappingSize: 0x%08x. Image base: 0x%016llx\n",
355  secOff, numberOfSections, secOff + sizeof(IMAGE_SECTION_HEADER) * numberOfSections, size, ImageBase);
357  goto leave;
358  }
359 
360  if (entryPoint >= sizeOfImage)
361  {
362  ERROR("[ERROR] EntryPoint points out of the file. EntryPoint: 0x%08x; SizeOfImage: 0x%08x; "
363  "ImageBase: 0x%016llx\n",
364  entryPoint, sizeOfImage, ImageBase);
366  goto leave;
367  }
368 
369  // Make a final validation (so we know that the NT headers weren't moved outside the image)
370  if (e_lfanew >= sizeOfImage)
371  {
372  WARNING("[WARNING] e_lfanew 0x%llx points outside of image 0x%08x. Module 0x%016llx\n",
373  e_lfanew, sizeOfImage, ImageBase);
375  goto leave;
376  }
377 
378  // Validate section headers
379  pSec = (IMAGE_SECTION_HEADER *)(pBase + secOff);
380  for (DWORD i = 0; i < numberOfSections; i++, pSec++)
381  {
382  UINT32 secVirtSize = pSec->Misc.VirtualSize;
383  UINT32 secSizeOfRawData = pSec->SizeOfRawData;
384 
385  actualSectionSize = ROUND_UP(secVirtSize ? secVirtSize : secSizeOfRawData, sectionAlign);
386 
387  if (0 == actualSectionSize)
388  {
389  CHAR name[9];
390  memcpy(name, pSec->Name, sizeof(pSec->Name));
391  name[8] = 0;
392 
393  ERROR("[ERROR] Section %d (%s) for image at 0x%016llx has actual size 0. VirtualSize = 0x%08x "
394  "SizeOfRawData = 0x%08x Align = 0x%08x\n",
395  i, name, ImageBase, secVirtSize, secSizeOfRawData, sectionAlign);
397  goto leave;
398  }
399 
400  if (0 == pSec->VirtualAddress)
401  {
402  CHAR name[9];
403  memcpy(name, pSec->Name, sizeof(pSec->Name));
404  name[8] = 0;
405 
406  ERROR("[ERROR] Section starting at 0. Section name: %s\n", name);
408  goto leave;
409  }
410 
411  if (0 == secVirtSize && (0 == secSizeOfRawData || secSizeOfRawData > PAGE_SIZE))
412  {
413  CHAR name[9];
414  memcpy(name, pSec->Name, sizeof(pSec->Name));
415  name[8] = 0;
416 
417  ERROR("[ERROR] Section %d (%s) size is invalid for image at 0x%016llx. "
418  "VirtualSize: 0x%08x. SizeOfRawData: 0x%08x\n",
419  i, name, ImageBase, pSec->Misc.VirtualSize, pSec->SizeOfRawData);
421  goto leave;
422  }
423 
424  // Make sure the section fits within sizeOfImage
425  if ((pSec->VirtualAddress >= sizeOfImage) ||
426  (actualSectionSize > sizeOfImage) ||
427  (pSec->VirtualAddress + actualSectionSize > sizeOfImage))
428  {
429  CHAR name[9];
430  memcpy(name, pSec->Name, sizeof(pSec->Name));
431  name[8] = 0;
432 
433  ERROR("[ERROR] Section %d (%s) for image at 0x%016llx seems corrupted: "
434  "sizeOfImage = 0x%x, secStart = 0x%x, secSize = 0x%x, actualSecSize = 0x%08x\n",
435  i, name, ImageBase, sizeOfImage, pSec->VirtualAddress, pSec->Misc.VirtualSize, actualSectionSize);
437  goto leave;
438  }
439  }
440 
441  if (NULL != PeInfo)
442  {
443  PeInfo->Image64Bit = image64;
444  PeInfo->SectionOffset = secOff;
445  PeInfo->SizeOfImage = sizeOfImage;
446  PeInfo->TimeDateStamp = timeDateStamp;
447  PeInfo->EntryPoint = entryPoint;
448  PeInfo->NumberOfSections = numberOfSections;
449  PeInfo->Subsystem = subsystem;
450  PeInfo->ImageBase = imageBase;
451  PeInfo->SectionAlignment = sectionAlign;
452  PeInfo->Machine = machine;
453  }
454 
455  status = INT_STATUS_SUCCESS;
456 
457 leave:
458  if (NULL == ImageBaseBuffer)
459  {
460  IntVirtMemUnmap(&pBase);
461  }
462 
463  if (NULL != pSmallBase)
464  {
466  }
467 
468  return status;
469 }
470 
471 
472 INTSTATUS
475  _In_opt_ BYTE *ImageBuffer,
476  _In_opt_ DWORD ImageBufferSize,
477  _Out_ DWORD *FirstSectionOffset,
478  _Out_ DWORD *SectionCount
479  )
493 {
494  INTSTATUS status = INT_STATUS_SUCCESS;
495  BYTE *pBase;
496  DWORD size = 0;
497  INTRO_PE_INFO peInfo = {0};
498 
499  if (NULL == FirstSectionOffset)
500  {
502  }
503 
504  if (NULL == SectionCount)
505  {
507  }
508 
509  if (NULL != ImageBuffer)
510  {
511  pBase = ImageBuffer;
512  size = ImageBufferSize;
513  }
514  else
515  {
516  status = IntVirtMemMap(ImageBase, PAGE_SIZE, gGuest.Mm.SystemCr3, 0, &pBase);
517  if (!INT_SUCCESS(status))
518  {
519  ERROR("[ERROR] IntVirtMemMap failed: 0x%08x\n", status);
520  return status;
521  }
522 
523  size = PAGE_SIZE;
524  }
525 
526  status = IntPeValidateHeader(ImageBase, pBase, size, &peInfo, 0);
527  if (!INT_SUCCESS(status))
528  {
529  ERROR("[ERROR] IntPeValidateHeader failed: 0x%08x\n", status);
530  goto cleanup_and_exit;
531  }
532 
533  *FirstSectionOffset = (DWORD)peInfo.SectionOffset;
534  *SectionCount = (DWORD)peInfo.NumberOfSections;
535 
536 cleanup_and_exit:
537  if (NULL == ImageBuffer)
538  {
539  // Don't override the status from IntPeValidateHeader
540  INTSTATUS status2 = IntVirtMemUnmap(&pBase);
541  if (!INT_SUCCESS(status2))
542  {
543  ERROR("[ERROR] IntVirtMemUnmap failed: 0x%08x\n", status2);
544  }
545  }
546 
547  return status;
548 }
549 
550 
551 INTSTATUS
554  _In_opt_ BYTE *ImageBaseBuffer,
555  _In_ DWORD DirectoryEntry,
556  _Out_ IMAGE_DATA_DIRECTORY *Directory
557  )
571 
572 {
573  INTSTATUS status;
574  BOOLEAN unmapNtHeaders;
575  BYTE *map;
576  IMAGE_DOS_HEADER *pDosHeader;
577  IMAGE_NT_HEADERS64 *pNth64;
578  IMAGE_NT_HEADERS32 *pNth32;
579  INTRO_PE_INFO peInfo = {0};
580 
581  if (DirectoryEntry > IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR)
582  {
584  }
585 
586  if (NULL == Directory)
587  {
589  }
590 
591  unmapNtHeaders = FALSE;
592 
593  if (NULL != ImageBaseBuffer)
594  {
595  map = ImageBaseBuffer;
596  }
597  else
598  {
599  status = IntVirtMemMap(ImageBase, PAGE_SIZE, 0, 0, &map);
600  if (!INT_SUCCESS(status))
601  {
602  ERROR("[ERROR] Failed mapping VA 0x%016llx to host: 0x%08x\n", ImageBase, status);
603  return status;
604  }
605  }
606 
607  // First thing, validate the buffer
608  status = IntPeValidateHeader(ImageBase, map, PAGE_SIZE, &peInfo, 0);
609  if (!INT_SUCCESS(status))
610  {
611  goto leave;
612  }
613 
614  pDosHeader = (IMAGE_DOS_HEADER *)map;
615 
616  if (peInfo.Image64Bit)
617  {
618  QWORD e_lfanew = (DWORD)pDosHeader->e_lfanew;
619 
620  if (e_lfanew + sizeof(IMAGE_NT_HEADERS64) <= PAGE_SIZE)
621  {
622  pNth64 = (IMAGE_NT_HEADERS64 *)(map + e_lfanew);
623  }
624  else
625  {
626  status = IntVirtMemMap(ImageBase + e_lfanew, sizeof(*pNth64), 0, 0, &pNth64);
627  if (!INT_SUCCESS(status))
628  {
629  ERROR("[ERROR] Failed mapping VA 0x%016llx to host: 0x%08x\n", ImageBase + e_lfanew, status);
630  goto leave;
631  }
632 
633  unmapNtHeaders = TRUE;
634  }
635 
636  Directory->Size = pNth64->OptionalHeader.DataDirectory[DirectoryEntry].Size;
637  Directory->VirtualAddress = pNth64->OptionalHeader.DataDirectory[DirectoryEntry].VirtualAddress;
638  }
639  else
640  {
641  QWORD e_lfanew = (DWORD)pDosHeader->e_lfanew;
642 
643  if (e_lfanew + sizeof(IMAGE_NT_HEADERS32) <= PAGE_SIZE)
644  {
645  pNth32 = (IMAGE_NT_HEADERS32 *)(map + e_lfanew);
646  }
647  else
648  {
649  status = IntVirtMemMap(ImageBase + e_lfanew, sizeof(*pNth32), 0, 0, &pNth32);
650  if (!INT_SUCCESS(status))
651  {
652  ERROR("[ERROR] Failed mapping VA 0x%016llx to host: 0x%08x\n", ImageBase + e_lfanew, status);
653  goto leave;
654  }
655 
656  unmapNtHeaders = TRUE;
657  }
658 
659  Directory->Size = pNth32->OptionalHeader.DataDirectory[DirectoryEntry].Size;
660  Directory->VirtualAddress = pNth32->OptionalHeader.DataDirectory[DirectoryEntry].VirtualAddress;
661  }
662 
663  if (Directory->VirtualAddress == 0 || Directory->Size == 0)
664  {
665  status = INT_STATUS_NOT_FOUND;
666  goto leave;
667  }
668 
669  // Validate the entry and return appropriate status when it's invalid
670  if ((QWORD)Directory->Size + Directory->VirtualAddress > peInfo.SizeOfImage)
671  {
673  goto leave;
674  }
675 
676  status = INT_STATUS_SUCCESS;
677 
678 leave:
679  if (unmapNtHeaders)
680  {
681  if (peInfo.Image64Bit)
682  {
683  IntVirtMemUnmap(&pNth64);
684  }
685  else
686  {
687  IntVirtMemUnmap(&pNth32);
688  }
689  }
690 
691  if (NULL == ImageBaseBuffer)
692  {
693  IntVirtMemUnmap(&map);
694  }
695 
696  if (!INT_SUCCESS(status))
697  {
698  Directory->VirtualAddress = 0;
699  Directory->Size = 0;
700  }
701 
702  return status;
703 }
704 
705 
706 INTSTATUS
709  _In_opt_ BYTE *ImageBaseBuffer,
710  _In_ DWORD GuestRva,
711  _Out_ IMAGE_SECTION_HEADER *SectionHeader
712  )
727 {
728  INTSTATUS status;
729  BOOLEAN unmapSecHeaders;
730  DWORD i;
731  IMAGE_SECTION_HEADER *pSecHeader;
732  BYTE *map;
733  INTRO_PE_INFO peInfo = {0};
734 
735  if (NULL == SectionHeader)
736  {
738  }
739 
740  if (NULL != ImageBaseBuffer)
741  {
742  map = ImageBaseBuffer;
743  }
744  else
745  {
746  status = IntVirtMemMap(ImageBase, PAGE_SIZE, 0, 0, &map);
747  if (!INT_SUCCESS(status))
748  {
749  ERROR("[ERROR] Failed mapping VA 0x%016llx to host: 0x%08x\n", ImageBase, status);
750  return status;
751  }
752  }
753 
754  unmapSecHeaders = FALSE;
755  pSecHeader = NULL;
756 
757  // First thing, validate the buffer
758  status = IntPeValidateHeader(ImageBase, map, PAGE_SIZE, &peInfo, 0);
759  if (!INT_SUCCESS(status))
760  {
761  goto leave;
762  }
763 
764  if (peInfo.SectionOffset + (peInfo.NumberOfSections * sizeof(IMAGE_SECTION_HEADER)) <= PAGE_SIZE)
765  {
766  // We are in the same page, so it's safe to use this
767  pSecHeader = (IMAGE_SECTION_HEADER *)((BYTE *)map + peInfo.SectionOffset);
768  }
769 
770  // See that the sections aren't outside of the image
771  if (peInfo.SectionOffset + peInfo.NumberOfSections * sizeof(IMAGE_SECTION_HEADER) > peInfo.SizeOfImage)
772  {
774  goto leave;
775  }
776  else if (peInfo.NumberOfSections > ((PAGE_SIZE * 2) / sizeof(IMAGE_SECTION_HEADER))) // ~227 sections
777  {
778  // Let's assume that the secCount == 65536, that will make us map 537 pages,
779  // which on XEN means allocating 537 pages + memcpy, and Napoca will
780  // probably just return INT_STATUS_INSUFFICIENT_RESOURCES. So, if secCount is bigger
781  // than two pages, then search the given RVA only in two pages.
782  WARNING("[WARNING] Image has %lld sections, which is a bit too much!\n", peInfo.NumberOfSections);
783  peInfo.NumberOfSections = (PAGE_SIZE * 2) / sizeof(IMAGE_SECTION_HEADER);
784  }
785 
786  // If the pSecHeader is NULL, then the sections doesn't end in the first page
787  if (NULL == pSecHeader)
788  {
789  status = IntVirtMemMap(ImageBase + peInfo.SectionOffset,
790  (DWORD)(peInfo.NumberOfSections * sizeof(*pSecHeader)), 0, 0, &pSecHeader);
791  if (!INT_SUCCESS(status))
792  {
793  ERROR("[ERROR] Failed mapping VA 0x%016llx to host with size %llu: 0x%08x\n",
794  ImageBase + peInfo.SectionOffset, peInfo.NumberOfSections * sizeof(IMAGE_SECTION_HEADER), status);
795  goto leave;
796  }
797 
798  unmapSecHeaders = TRUE;
799  }
800 
801  for (i = 0; i < peInfo.NumberOfSections; i++)
802  {
803  DWORD secEnd = pSecHeader[i].VirtualAddress +
804  ALIGN_UP((QWORD)pSecHeader[i].Misc.VirtualSize, peInfo.SectionAlignment);
805 
806  if (GuestRva >= pSecHeader[i].VirtualAddress &&
807  GuestRva < secEnd)
808  {
809  memcpy(SectionHeader, &pSecHeader[i], sizeof(IMAGE_SECTION_HEADER));
810  status = INT_STATUS_SUCCESS;
811  goto leave;
812  }
813  }
814 
815  status = INT_STATUS_NOT_FOUND;
816 
817 leave:
818  if (unmapSecHeaders)
819  {
820  IntVirtMemUnmap(&pSecHeader);
821  }
822 
823  if (NULL == ImageBaseBuffer)
824  {
825  IntVirtMemUnmap(&map);
826  }
827 
828  if (!INT_SUCCESS(status))
829  {
830  memzero(SectionHeader, sizeof(*SectionHeader));
831  }
832 
833  return status;
834 }
835 
836 
837 INTSTATUS
840  _In_opt_ BYTE *ImageBaseBuffer,
841  _In_ DWORD Index,
842  _Out_ IMAGE_SECTION_HEADER *SectionHeader
843  )
857 {
858  INTSTATUS status;
859  IMAGE_SECTION_HEADER *pSecHeader;
860  BYTE *map;
861  INTRO_PE_INFO peInfo = {0};
862 
863  if (NULL == SectionHeader)
864  {
866  }
867 
868  if (NULL != ImageBaseBuffer)
869  {
870  map = ImageBaseBuffer;
871  }
872  else
873  {
874  status = IntVirtMemMap(ImageBase, PAGE_SIZE, 0, 0, &map);
875  if (!INT_SUCCESS(status))
876  {
877  ERROR("[ERROR] Failed mapping VA 0x%016llx to host: 0x%08x\n", ImageBase, status);
878  return status;
879  }
880  }
881 
882  pSecHeader = NULL;
883 
884  // First thing, validate the buffer
885  status = IntPeValidateHeader(ImageBase, map, PAGE_SIZE, &peInfo, 0);
886  if (!INT_SUCCESS(status))
887  {
888  goto leave;
889  }
890 
891  if (peInfo.SectionOffset + (Index * sizeof(IMAGE_SECTION_HEADER)) <= PAGE_SIZE)
892  {
893  // We are in the same page, so it's safe to use this
894  pSecHeader = (IMAGE_SECTION_HEADER *)((BYTE *)map + peInfo.SectionOffset);
895  }
896 
897  // See that the sections aren't outside of the image
898  if (peInfo.SectionOffset + peInfo.NumberOfSections * sizeof(IMAGE_SECTION_HEADER) > peInfo.SizeOfImage)
899  {
901  goto leave;
902  }
903 
904  if (Index >= peInfo.NumberOfSections)
905  {
906  status = INT_STATUS_NOT_FOUND;
907  goto leave;
908  }
909 
910  // If the pSecHeader is NULL, then the section we are searching is not in the first page
911  if (NULL == pSecHeader)
912  {
913  status = IntKernVirtMemRead(ImageBase + peInfo.SectionOffset + Index * sizeof(IMAGE_SECTION_HEADER),
914  sizeof(IMAGE_SECTION_HEADER),
915  SectionHeader,
916  NULL);
917  if (!INT_SUCCESS(status))
918  {
919  ERROR("[ERROR] Failed reading section header from GVA 0x%016llx: 0x%08x\n",
920  ImageBase + peInfo.SectionOffset + Index * sizeof(IMAGE_SECTION_HEADER), status);
921  goto leave;
922  }
923  }
924  else
925  {
926  memcpy(SectionHeader, &pSecHeader[Index], sizeof(IMAGE_SECTION_HEADER));
927  }
928 
929  status = INT_STATUS_SUCCESS;
930 
931 leave:
932  if (NULL == ImageBaseBuffer)
933  {
934  IntVirtMemUnmap(&map);
935  }
936 
937  return status;
938 }
939 
940 
941 INTSTATUS
944  _In_opt_ BYTE *ImageBaseBuffer,
945  _In_reads_or_z_(8) PCHAR Name,
946  _In_ DWORD NumberOfSectionHeadersAllocated,
947  _In_ QWORD Cr3,
948  _Out_ IMAGE_SECTION_HEADER *SectionHeaders,
949  _Out_opt_ DWORD *NumberOfSectionHeadersFilled
950  )
968 {
969  INTSTATUS status;
970  BOOLEAN unmapSecHeaders;
971  DWORD numberSectionsFound;
972  BYTE *map;
973  IMAGE_SECTION_HEADER *pSecHeader;
974  INTRO_PE_INFO peInfo = {0};
975  SIZE_T cmpSize;
976 
977  if (0 == NumberOfSectionHeadersAllocated)
978  {
980  }
981 
982  if (NULL == SectionHeaders)
983  {
985  }
986 
987  if (NULL != ImageBaseBuffer)
988  {
989  map = ImageBaseBuffer;
990  }
991  else
992  {
993  status = IntVirtMemMap(ImageBase, PAGE_SIZE, Cr3, 0, &map);
994  if (!INT_SUCCESS(status))
995  {
996  ERROR("[ERROR] Failed mapping VA 0x%016llx to host: 0x%08x\n", ImageBase, status);
997  return status;
998  }
999  }
1000 
1001  unmapSecHeaders = FALSE;
1002  pSecHeader = NULL;
1003  numberSectionsFound = 0;
1004 
1005  // First thing, validate the buffer
1006  status = IntPeValidateHeader(ImageBase, map, PAGE_SIZE, &peInfo, 0);
1007  if (!INT_SUCCESS(status))
1008  {
1009  goto leave;
1010  }
1011 
1012  if (peInfo.SectionOffset + (peInfo.NumberOfSections * sizeof(IMAGE_SECTION_HEADER)) <= PAGE_SIZE)
1013  {
1014  // We are in the same page, so it's safe to use this
1015  pSecHeader = (IMAGE_SECTION_HEADER *)((BYTE *)map + peInfo.SectionOffset);
1016  }
1017 
1018  // See that the sections aren't outside of the image
1019  if (peInfo.SectionOffset + peInfo.NumberOfSections * sizeof(IMAGE_SECTION_HEADER) > peInfo.SizeOfImage)
1020  {
1022  goto leave;
1023  }
1024  else if (peInfo.NumberOfSections > ((PAGE_SIZE * 2) / sizeof(IMAGE_SECTION_HEADER))) // ~227 sections
1025  {
1026  // Let's assume that the secCount == 65536, that will make us map 537 pages,
1027  // which on XEN means allocating 537 pages + memcpy, and Napoca will
1028  // probably just return INT_STATUS_INSUFFICIENT_RESOURCES. So, if secCount is bigger
1029  // than two pages, then search the given RVA only in two pages.
1030  WARNING("[WARNING] Image has %lld sections, which is a bit too much!\n", peInfo.NumberOfSections);
1031  peInfo.NumberOfSections = (PAGE_SIZE * 2) / sizeof(IMAGE_SECTION_HEADER);
1032  }
1033 
1034  // If the pSecHeader is NULL, then the sections doesn't end in the first page
1035  if (NULL == pSecHeader)
1036  {
1037  status = IntVirtMemMap(ImageBase + peInfo.SectionOffset,
1038  (DWORD)(peInfo.NumberOfSections * sizeof(IMAGE_SECTION_HEADER)), 0, 0, &pSecHeader);
1039  if (!INT_SUCCESS(status))
1040  {
1041  ERROR("[ERROR] Failed mapping VA 0x%016llx to host with size 0x%016llx: 0x%08x\n",
1042  ImageBase + peInfo.SectionOffset, peInfo.NumberOfSections * sizeof(IMAGE_SECTION_HEADER), status);
1043  goto leave;
1044  }
1045 
1046  unmapSecHeaders = TRUE;
1047  }
1048 
1049  // We use memcmp, so add 1 to also compare the NULL terminator
1050  cmpSize = strlen(Name) + 1;
1051  // Section names are not guaranteed to be NULL terminated, so if we go past that, limit the compare size to 8
1052  cmpSize = MIN(cmpSize, IMAGE_SIZEOF_SHORT_NAME);
1053  for (size_t i = 0; i < peInfo.NumberOfSections; i++)
1054  {
1055  if (memcmp(Name, pSecHeader[i].Name, cmpSize) == 0)
1056  {
1057  memcpy(&SectionHeaders[numberSectionsFound], &pSecHeader[i], sizeof(IMAGE_SECTION_HEADER));
1058 
1059  numberSectionsFound++;
1060 
1061  if (numberSectionsFound == NumberOfSectionHeadersAllocated)
1062  {
1063  break;
1064  }
1065  }
1066  }
1067 
1068  if (0 == numberSectionsFound)
1069  {
1070  status = INT_STATUS_NOT_FOUND;
1071  }
1072  else
1073  {
1074  status = INT_STATUS_SUCCESS;
1075  }
1076 
1077  if (NULL != NumberOfSectionHeadersFilled)
1078  {
1079  *NumberOfSectionHeadersFilled = numberSectionsFound;
1080  }
1081 
1082 leave:
1083  if (unmapSecHeaders)
1084  {
1085  IntVirtMemUnmap(&pSecHeader);
1086  }
1087 
1088  if (NULL == ImageBaseBuffer)
1089  {
1090  IntVirtMemUnmap(&map);
1091  }
1092 
1093  if (!INT_SUCCESS(status))
1094  {
1095  memzero(SectionHeaders, sizeof(*SectionHeaders) * NumberOfSectionHeadersAllocated);
1096  }
1097 
1098  return status;
1099 }
1100 
1101 
1102 INTSTATUS
1104  _In_ QWORD ImageBase,
1105  _In_opt_ BYTE *ImageBaseBuffer,
1106  _In_ DWORD Rva
1107  )
1122 {
1123  INTSTATUS status;
1124  DWORD beginRva, i, size;
1125  QWORD address;
1127  IMAGE_EXPORT_DIRECTORY exportDir;
1128  BOOLEAN found;
1129  BYTE *functions;
1130  BYTE *map;
1131 
1132  found = FALSE;
1133 
1134  if (NULL != ImageBaseBuffer)
1135  {
1136  map = ImageBaseBuffer;
1137  }
1138  else
1139  {
1140  status = IntVirtMemMap(ImageBase, PAGE_SIZE, 0, 0, &map);
1141  if (!INT_SUCCESS(status))
1142  {
1143  ERROR("[ERROR] Failed mapping VA 0x%016llx to host: 0x%08x\n", ImageBase, status);
1144  return status;
1145  }
1146  }
1147 
1148  // Get the export directory, if present
1149  status = IntPeGetDirectory(ImageBase, map, IMAGE_DIRECTORY_ENTRY_EXPORT, &dir);
1150  if (!INT_SUCCESS(status))
1151  {
1152  goto leave;
1153  }
1154 
1155  // Find the start of the function
1156  status = IntPeFindFunctionStart(ImageBase, map, Rva, &beginRva);
1157  if (!INT_SUCCESS(status))
1158  {
1159  goto leave;
1160  }
1161 
1162  // Read the export directory
1163  status = IntKernVirtMemRead(ImageBase + dir.VirtualAddress, sizeof(IMAGE_EXPORT_DIRECTORY), &exportDir, NULL);
1164  if (!INT_SUCCESS(status))
1165  {
1166  goto leave;
1167  }
1168 
1169  // Parse the export directory and find the function corresponding to this (if any)
1170  // Cap the number of searched symbols to MAX_NUMBER_OF_EXPORT_NAMES
1171  size = MIN(exportDir.NumberOfFunctions, MAX_NUMBER_OF_EXPORT_NAMES) * 4;
1172  address = ImageBase + exportDir.AddressOfFunctions;
1173 
1174  for (i = 0; i < size; i += PAGE_SIZE)
1175  {
1176  DWORD parsed = 0;
1177  DWORD mappingSize = (i < (size & PAGE_MASK)) ? PAGE_SIZE : (address & PAGE_OFFSET);
1178 
1179  status = IntVirtMemMap(address & PAGE_MASK, PAGE_SIZE, 0, 0, &functions);
1180  if (!INT_SUCCESS(status))
1181  {
1182  ERROR("[ERROR] Failed mapping AddressOfFunctions 0x%016llx (0x%016llx + 0x%08x): 0x%08x\n",
1183  address & PAGE_MASK, ImageBase, exportDir.AddressOfFunctions, status);
1184  goto leave;
1185  }
1186 
1187  // has meaning only on the first map, on the rest it's already aligned
1188  functions += address & PAGE_OFFSET;
1189 
1190  while (parsed + mappingSize < PAGE_SIZE)
1191  {
1192  // We don't care about forwarded addresses (they still are exported)
1193  if (*(DWORD *)(functions + parsed) == beginRva)
1194  {
1195  found = TRUE;
1196  break;
1197  }
1198  parsed += 4;
1199  }
1200 
1201  functions -= address & PAGE_OFFSET;
1202  address += mappingSize; // this will align the address
1203 
1204  IntVirtMemUnmap(&functions);
1205  }
1206 
1207  if (!found)
1208  {
1209  status = INT_STATUS_NOT_FOUND;
1210  }
1211 
1212 leave:
1213  if (NULL == ImageBaseBuffer)
1214  {
1215  IntVirtMemUnmap(&map);
1216  }
1217 
1218  return status;
1219 }
1220 
1221 
1222 INTSTATUS
1224  _In_ QWORD ImageBase,
1225  _In_ BYTE *Buffer,
1226  _In_ DWORD BufferSize,
1227  _In_ DWORD Rva
1228  )
1244 {
1245  INTSTATUS status;
1246  DWORD beginRva;
1247  QWORD size;
1248  IMAGE_EXPORT_DIRECTORY exportDir;
1250 
1251  // Firstly, validate that headers are valid before going through EAT
1252  status = IntPeValidateHeader(ImageBase, Buffer, BufferSize, NULL, 0);
1253  if (!INT_SUCCESS(status))
1254  {
1255  return status;
1256  }
1257 
1258  // Find the start of the function
1259  status = IntPeFindFunctionStart(ImageBase, Buffer, Rva, &beginRva);
1260  if (!INT_SUCCESS(status))
1261  {
1262  return status;
1263  }
1264 
1265  status = IntPeGetDirectory(ImageBase, Buffer, IMAGE_DIRECTORY_ENTRY_EXPORT, &dir);
1266  if (!INT_SUCCESS(status))
1267  {
1268  return status;
1269  }
1270 
1271  if ((QWORD)dir.VirtualAddress + dir.Size > BufferSize)
1272  {
1274  }
1275 
1276  // We want to make sure that we don't overflow the buffer even for some special crafted size
1277  // (e.g. dir.VirtualAddress = BufferSize - 1, dir.Size = 1, but dir.VirtualAddress + sizeof(IMAGE_EXPORT_DIRECTORY)
1278  // is > BufferSize)
1279  if ((QWORD)dir.VirtualAddress + sizeof(IMAGE_EXPORT_DIRECTORY) > BufferSize)
1280  {
1282  }
1283 
1284  exportDir = *(IMAGE_EXPORT_DIRECTORY *)(Buffer + dir.VirtualAddress);
1285 
1286  size = (QWORD)MIN(exportDir.NumberOfFunctions, MAX_NUMBER_OF_EXPORT_NAMES) * 4;
1287 
1288  if (size > BufferSize)
1289  {
1291  }
1292 
1293  for (DWORD i = 0; i < size; i += 4)
1294  {
1295  if ((QWORD)exportDir.AddressOfFunctions + i + sizeof(DWORD) > BufferSize)
1296  {
1297  continue;
1298  }
1299 
1300  // We don't care about forwarded addresses (they still are exported)
1301  if (*(DWORD *)(Buffer + exportDir.AddressOfFunctions + i) == beginRva)
1302  {
1303  return INT_STATUS_SUCCESS;
1304  }
1305  }
1306 
1307  return INT_STATUS_NOT_FOUND;
1308 }
1309 
1310 
1311 INTSTATUS
1313  _In_ QWORD ImageBase,
1314  _In_opt_ BYTE *ImageBaseBuffer,
1315  _In_ DWORD Rva,
1316  _In_ DWORD ExportNameSize,
1317  _Out_writes_z_(ExportNameSize) CHAR *ExportName
1318  )
1334 {
1335  INTSTATUS status;
1336  DWORD i;
1337  BOOLEAN found;
1338  IMAGE_EXPORT_DIRECTORY exportDir;
1340  BYTE *map;
1341 
1342  if (0 == ExportNameSize)
1343  {
1345  }
1346 
1347  if (NULL == ExportName)
1348  {
1350  }
1351 
1352  if (NULL != ImageBaseBuffer)
1353  {
1354  map = ImageBaseBuffer;
1355  }
1356  else
1357  {
1358  status = IntVirtMemMap(ImageBase, PAGE_SIZE, 0, 0, &map);
1359  if (!INT_SUCCESS(status))
1360  {
1361  ERROR("[ERROR] Failed mapping VA 0x%016llx to host: 0x%08x\n", ImageBase, status);
1362  return status;
1363  }
1364  }
1365 
1366  // Get the export directory, if present
1367  status = IntPeGetDirectory(ImageBase, map, IMAGE_DIRECTORY_ENTRY_EXPORT, &dir);
1368  if (!INT_SUCCESS(status))
1369  {
1370  goto _cleanup_and_leave;
1371  }
1372 
1373  status = IntKernVirtMemRead(ImageBase + dir.VirtualAddress, sizeof(IMAGE_EXPORT_DIRECTORY), &exportDir, NULL);
1374  if (!INT_SUCCESS(status))
1375  {
1376  if (status != INT_STATUS_PAGE_NOT_PRESENT)
1377  {
1378  // Export directory is pageable so it CAN happen to not be present, so don't spam
1379  WARNING("[WARNING] Failed to read the export directory of 0x%016llx located at 0x%016llx: 0x%08x\n",
1380  ImageBase, ImageBase + dir.VirtualAddress, status);
1381  }
1382  goto _cleanup_and_leave;
1383  }
1384 
1385  found = FALSE;
1386 
1387  if (exportDir.NumberOfNames > MAX_NUMBER_OF_EXPORT_NAMES)
1388  {
1389  ERROR("[ERROR] Number of names %d exceeds %lu!\n", exportDir.NumberOfNames, MAX_NUMBER_OF_EXPORT_NAMES);
1391  goto _cleanup_and_leave;
1392  }
1393 
1394  for (i = 0; i < exportDir.NumberOfNames; i++)
1395  {
1396  DWORD exportOrdinal, exportRva, namePointer, retLen;
1397 
1398  // Get this name's ordinal; note that ordinals are 2 bytes in size
1399  status = IntKernVirtMemFetchDword(ImageBase + exportDir.AddressOfNameOrdinals + i * 2ull, &exportOrdinal);
1400  if (!INT_SUCCESS(status))
1401  {
1402  continue;
1403  }
1404  exportOrdinal &= 0xFFFF;
1405 
1406  // Read the export RVA
1407  status = IntKernVirtMemFetchDword(ImageBase + exportDir.AddressOfFunctions + exportOrdinal * 4ull, &exportRva);
1408  if (!INT_SUCCESS(status))
1409  {
1410  continue;
1411  }
1412 
1413  if (exportRva != Rva)
1414  {
1415  continue;
1416  }
1417 
1418  // Get the name pointer (RVA)
1419  status = IntKernVirtMemFetchDword(ImageBase + exportDir.AddressOfNames + i * 4ull, &namePointer);
1420  if (!INT_SUCCESS(status))
1421  {
1422  goto _cleanup_and_leave;
1423  }
1424 
1425  // Read the name
1426  status = IntKernVirtMemRead(ImageBase + namePointer, ExportNameSize, ExportName, &retLen);
1427  if (!INT_SUCCESS(status))
1428  {
1429  goto _cleanup_and_leave;
1430  }
1431 
1432  // retLen will always have the same value as ExportNameSize in this case
1433  ExportName[retLen - 1] = 0;
1434 
1435  found = TRUE;
1436  status = INT_STATUS_SUCCESS;
1437  break;
1438  }
1439 
1440  if (!found)
1441  {
1442  status = INT_STATUS_NOT_FOUND;
1443  goto _cleanup_and_leave;
1444  }
1445 
1446 _cleanup_and_leave:
1447  if (ImageBaseBuffer == NULL)
1448  {
1449  IntVirtMemUnmap(&map);
1450  }
1451 
1452  return status;
1453 }
1454 
1455 
1456 INTSTATUS
1458  _In_ QWORD ImageBase,
1459  _In_ BYTE *Buffer,
1460  _In_ DWORD BufferSize,
1461  _In_ DWORD Rva,
1462  _In_ DWORD ExportNameSize,
1463  _Out_writes_z_(ExportNameSize) CHAR *ExportName
1464  )
1480 {
1481  INTSTATUS status;
1482  DWORD i;
1483  IMAGE_EXPORT_DIRECTORY exportDir;
1485 
1486  if (NULL == Buffer)
1487  {
1489  }
1490 
1491  if (0 == BufferSize)
1492  {
1494  }
1495 
1496  if (0 == ExportNameSize)
1497  {
1499  }
1500 
1501  if (NULL == ExportName)
1502  {
1504  }
1505 
1506  // Firstly, validate that headers are valid before going through EAT
1507  status = IntPeValidateHeader(ImageBase, Buffer, BufferSize, NULL, 0);
1508  if (!INT_SUCCESS(status))
1509  {
1510  return status;
1511  }
1512 
1513  // Get the export directory, if present
1514  status = IntPeGetDirectory(ImageBase, Buffer, IMAGE_DIRECTORY_ENTRY_EXPORT, &dir);
1515  if (!INT_SUCCESS(status))
1516  {
1517  return status;
1518  }
1519 
1520  if ((QWORD)dir.VirtualAddress + dir.Size > BufferSize)
1521  {
1523  }
1524 
1525  // We want to make sure that we don't overflow the buffer even for some special crafted size
1526  // (e.g. dir.VirtualAddress = BufferSize - 1, dir.Size = 1, but dir.VirtualAddress +
1527  // sizeof(IMAGE_EXPORT_DIRECTORY) is > BufferSize)
1528  if ((QWORD)dir.VirtualAddress + sizeof(IMAGE_EXPORT_DIRECTORY) > BufferSize)
1529  {
1531  }
1532 
1533  // Read the export directory
1534  exportDir = *(IMAGE_EXPORT_DIRECTORY *)(Buffer + dir.VirtualAddress);
1535  if (exportDir.NumberOfNames > MAX_NUMBER_OF_EXPORT_NAMES)
1536  {
1537  ERROR("[ERROR] Number of names %d exceeds %lu!\n", exportDir.NumberOfNames, MAX_NUMBER_OF_EXPORT_NAMES);
1539  }
1540 
1541  for (i = 0; i < exportDir.NumberOfNames; i++)
1542  {
1543  DWORD exportOrdinal, exportRva, namePointer;
1544 
1545  // Get this name's ordinal; note that ordinals are 2 bytes in size
1546  if ((QWORD)exportDir.AddressOfNameOrdinals + i * 2ull + sizeof(DWORD) > BufferSize)
1547  {
1548  continue;
1549  }
1550 
1551  exportOrdinal = (*(DWORD *)(Buffer + exportDir.AddressOfNameOrdinals + i * 2ull)) & 0xFFFF;
1552  if ((QWORD)exportDir.AddressOfFunctions + exportOrdinal * 4ull + sizeof(DWORD) > BufferSize)
1553  {
1554  continue;
1555  }
1556 
1557  exportRva = *(DWORD *)(Buffer + exportDir.AddressOfFunctions + exportOrdinal * 4ull);
1558  if (exportRva != Rva)
1559  {
1560  continue;
1561  }
1562 
1563  if ((QWORD)exportDir.AddressOfNames + i * 4ull + sizeof(DWORD) > BufferSize)
1564  {
1566  }
1567 
1568  // Get the name pointer (RVA)
1569  namePointer = *(DWORD *)(Buffer + exportDir.AddressOfNames + i * 4ull);
1570  if ((QWORD)namePointer + ExportNameSize > BufferSize)
1571  {
1573  }
1574 
1575  // Read the name
1576  strlcpy(ExportName, (char *)Buffer + namePointer, ExportNameSize);
1577 
1578  return INT_STATUS_SUCCESS;
1579  }
1580 
1581  return INT_STATUS_NOT_FOUND;
1582 }
1583 
1584 
1585 INTSTATUS
1587  _In_ QWORD ImageBase,
1588  _In_bytecount_(BufferSize) BYTE *Buffer,
1589  _In_ DWORD BufferSize,
1590  _In_z_ const char *Name,
1591  _Out_ DWORD *ExportRva
1592  )
1607 {
1608  INTSTATUS status;
1610  size_t exportNameLen;
1611  int left, right;
1612  IMAGE_EXPORT_DIRECTORY *pExpDir;
1613 
1614  if (NULL == Buffer)
1615  {
1617  }
1618 
1619  if (NULL == Name)
1620  {
1622  }
1623 
1624  if (NULL == ExportRva)
1625  {
1627  }
1628 
1629  exportNameLen = strlen(Name);
1630  *ExportRva = 0;
1631 
1632  if (exportNameLen >= 512)
1633  {
1635  }
1636 
1637  // Firstly, validate that headers are valid before going through EAT
1638  status = IntPeValidateHeader(ImageBase, Buffer, BufferSize, NULL, 0);
1639  if (!INT_SUCCESS(status))
1640  {
1641  return status;
1642  }
1643 
1644  status = IntPeGetDirectory(ImageBase, Buffer, IMAGE_DIRECTORY_ENTRY_EXPORT, &dir);
1645  if (!INT_SUCCESS(status))
1646  {
1647  return status;
1648  }
1649 
1650  if ((QWORD)dir.VirtualAddress + dir.Size > BufferSize)
1651  {
1653  }
1654 
1655  // We want to make sure that we don't overflow the buffer even for some special crafted size
1656  // (e.g. dir.VirtualAddress = BufferSize - 1, dir.Size = 1, but dir.VirtualAddress + sizeof(IMAGE_EXPORT_DIRECTORY)
1657  // is > BufferSize)
1658  if ((QWORD)dir.VirtualAddress + sizeof(IMAGE_EXPORT_DIRECTORY) > BufferSize)
1659  {
1661  }
1662 
1663  // There is a validation done by IntPeGetDirectory with peinfo.SizeOfImage,
1664  // but we should also validate against BufferSize
1665  pExpDir = (IMAGE_EXPORT_DIRECTORY *)(Buffer + dir.VirtualAddress);
1666 
1667  left = 0;
1668  right = pExpDir->NumberOfNames;
1669 
1670  while (left < right)
1671  {
1672  DWORD namePointer;
1673  size_t offset;
1674  size_t cmpSize = exportNameLen + 1;
1675  int mid, res;
1676 
1677  mid = (left + right) / 2;
1678 
1679  offset = pExpDir->AddressOfNames + mid * 4ull;
1680  if (offset + sizeof(namePointer) > BufferSize)
1681  {
1682  WARNING("[WARNING] Corrupted export name pointer: 0x%lx is outside of image size of 0x%x\n",
1683  offset + sizeof(namePointer), BufferSize);
1685  }
1686 
1687  namePointer = *(DWORD *)(Buffer + offset);
1688 
1689  if (namePointer >= BufferSize)
1690  {
1691  WARNING("[WARNING] Corrupted export name. Name pointer 0x%08x is outside of image size: 0x%08x\n",
1692  namePointer, BufferSize);
1694  }
1695 
1696  if (namePointer + cmpSize > BufferSize)
1697  {
1698  // Don't leave with an error, we still need to do the comparison, maybe the export directory is at
1699  // the end of the image, and what we search for it's really big
1700  cmpSize = BufferSize - namePointer;
1701  }
1702 
1703  // Compares the null-terminator too so we can avoid cases like: ExAllocatePool equals ExAllocatePoolWithTag
1704  res = memcmp(Name, Buffer + namePointer, cmpSize);
1705 
1706  if (0 == res)
1707  {
1708  WORD ord;
1709 
1710  offset = pExpDir->AddressOfNameOrdinals + mid * 2ull;
1711  if (offset + sizeof(ord) > BufferSize)
1712  {
1713  WARNING("[WARNING] Corrupted export ordinal: 0x%lx is outside of image size of 0x%x\n",
1714  offset + sizeof(ord), BufferSize);
1716  }
1717 
1718  ord = *(WORD *)(Buffer + offset);
1719 
1720  offset = pExpDir->AddressOfFunctions + ord * 4ull;
1721  if (offset + sizeof(*ExportRva) > BufferSize)
1722  {
1723  WARNING("[WARNING] Corrupted export address: 0x%lx is outside of image size of 0x%x\n",
1724  offset + sizeof(*ExportRva), BufferSize);
1726  }
1727 
1728  *ExportRva = *(DWORD *)(Buffer + offset);
1729 
1730  // It's the callers duty to see if this is forwarded
1731  return INT_STATUS_SUCCESS;
1732  }
1733  else if (res < 0)
1734  {
1735  right = mid;
1736  }
1737  else
1738  {
1739  left = mid + 1;
1740  }
1741  }
1742 
1743  return INT_STATUS_NOT_FOUND;
1744 }
1745 
1746 
1747 INTSTATUS
1749  _In_z_ const char *Name,
1750  _Out_ QWORD *ExportGva
1751  )
1760 {
1761  INTSTATUS status;
1762  DWORD rva;
1763 
1764  *ExportGva = 0;
1765 
1769  Name,
1770  &rva);
1771  if (!INT_SUCCESS(status))
1772  {
1773  return status;
1774  }
1775 
1776  *ExportGva = gGuest.KernelVa + rva;
1777 
1778  return INT_STATUS_SUCCESS;
1779 }
1780 
1781 
1782 INTSTATUS
1784  _In_ QWORD ImageBase,
1785  _In_opt_ BYTE *ImageBaseBuffer,
1786  _In_z_ CHAR *Name,
1787  _Out_ DWORD *ExportRva
1788  )
1803 {
1804  SIZE_T exportNameLen;
1805  BOOLEAN found;
1806  INTSTATUS status;
1808  BYTE *map;
1809  BYTE *exportNameBuffer;
1810  IMAGE_EXPORT_DIRECTORY exportDir;
1811  int left, right;
1812 
1813  if (NULL == Name)
1814  {
1816  }
1817 
1818  if (NULL == ExportRva)
1819  {
1821  }
1822 
1823  exportNameBuffer = NULL;
1824  found = FALSE;
1825  exportNameLen = strlen(Name);
1826 
1827  if (exportNameLen >= 512)
1828  {
1830  }
1831 
1832  if (NULL != ImageBaseBuffer)
1833  {
1834  map = ImageBaseBuffer;
1835  }
1836  else
1837  {
1838  status = IntVirtMemMap(ImageBase, PAGE_SIZE, 0, 0, &map);
1839  if (!INT_SUCCESS(status))
1840  {
1841  ERROR("[ERROR] Failed mapping VA 0x%016llx to host: 0x%08x\n", ImageBase, status);
1842  return status;
1843  }
1844  }
1845 
1846  // Get the export directory, if present
1847  status = IntPeGetDirectory(ImageBase, map, IMAGE_DIRECTORY_ENTRY_EXPORT, &dir);
1848  if (!INT_SUCCESS(status))
1849  {
1850  goto leave;
1851  }
1852 
1853  // Read the export directory
1854  status = IntKernVirtMemRead(ImageBase + dir.VirtualAddress, sizeof(IMAGE_EXPORT_DIRECTORY), &exportDir, NULL);
1855  if (!INT_SUCCESS(status))
1856  {
1857  // Export directory is pageable so it CAN happen to not be present, so don't spam
1858  if (status != INT_STATUS_PAGE_NOT_PRESENT)
1859  {
1860  WARNING("[ERROR] Failed to read the export directory of 0x%016llx located at 0x%016llx: 0x%08x\n",
1861  ImageBase, ImageBase + dir.VirtualAddress, status);
1862  }
1863  goto leave;
1864  }
1865 
1866  // exportNameLen + 1 OK: exportNameLen is not longer than 512.
1867  exportNameBuffer = HpAllocWithTag(exportNameLen + 1ull, IC_TAG_EXPN);
1868  if (NULL == exportNameBuffer)
1869  {
1871  goto leave;
1872  }
1873 
1874  left = 0;
1875  right = MIN(exportDir.NumberOfNames, 10000ul); // Cap the number of exported names to 10K.
1876 
1877  // The export names are sorted, therefore, we can do a binary search.
1878  while (left < right)
1879  {
1880  DWORD namePointer;
1881  int mid, res;
1882 
1883  mid = (left + right) / 2;
1884 
1885  status = IntKernVirtMemFetchDword(ImageBase + exportDir.AddressOfNames + mid * 4ull, &namePointer);
1886  if (!INT_SUCCESS(status))
1887  {
1888  goto leave;
1889  }
1890 
1891  status = IntKernVirtMemRead(ImageBase + namePointer, (DWORD)exportNameLen + 1, exportNameBuffer, NULL);
1892  if (!INT_SUCCESS(status))
1893  {
1894  goto leave;
1895  }
1896 
1897  // compare the null-terminator too so we can avoid cases like: ExAllocatePool equals ExAllocatePoolWithTag
1898  res = memcmp(Name, exportNameBuffer, exportNameLen + 1ull);
1899 
1900  if (0 == res)
1901  {
1902  DWORD exportOrdinal;
1903 
1904  // Get the export ordinal
1905  status = IntKernVirtMemFetchDword(ImageBase + exportDir.AddressOfNameOrdinals + mid * 2ull,
1906  &exportOrdinal);
1907  if (!INT_SUCCESS(status))
1908  {
1909  goto leave;
1910  }
1911  exportOrdinal &= 0xFFFF;
1912 
1913  // Read the export RVA
1914  status = IntKernVirtMemFetchDword(ImageBase + exportDir.AddressOfFunctions + exportOrdinal * 4ull,
1915  ExportRva);
1916  if (!INT_SUCCESS(status))
1917  {
1918  goto leave;
1919  }
1920 
1921  // It's the callers duty to see if this is forwarded
1922  found = TRUE;
1923  break;
1924  }
1925  else if (res < 0)
1926  {
1927  right = mid;
1928  }
1929  else
1930  {
1931  left = mid + 1;
1932  }
1933  }
1934 
1935 leave:
1936  if (NULL != exportNameBuffer)
1937  {
1938  HpFreeAndNullWithTag(&exportNameBuffer, IC_TAG_EXPN);
1939  }
1940 
1941  if (ImageBaseBuffer == NULL)
1942  {
1943  IntVirtMemUnmap(&map);
1944  }
1945 
1946  // return the error if any...
1947  if (!INT_SUCCESS(status))
1948  {
1949  return status;
1950  }
1951  else if (!found)
1952  {
1953  return INT_STATUS_NOT_FOUND;
1954  }
1955 
1956  return INT_STATUS_SUCCESS;
1957 }
1958 
1959 
1960 INTSTATUS
1962  _In_ QWORD ImageBase,
1963  _In_opt_ BYTE *ImageBaseBuffer,
1964  _In_ DWORD Ordinal,
1965  _Out_ DWORD *ExportRva
1966  )
1980 {
1981  BOOLEAN found;
1982  INTSTATUS status;
1983  IMAGE_DATA_DIRECTORY dir = {0};
1984  BYTE *map;
1985  IMAGE_EXPORT_DIRECTORY exportDir;
1986 
1987  if (NULL == ExportRva)
1988  {
1990  }
1991 
1992  found = FALSE;
1993 
1994  if (NULL != ImageBaseBuffer)
1995  {
1996  map = ImageBaseBuffer;
1997  }
1998  else
1999  {
2000  status = IntVirtMemMap(ImageBase, PAGE_SIZE, 0, 0, &map);
2001  if (!INT_SUCCESS(status))
2002  {
2003  ERROR("[ERROR] Failed mapping VA 0x%016llx to host: 0x%08x\n", ImageBase, status);
2004  return status;
2005  }
2006  }
2007 
2008  // get the export directory, if present
2009  status = IntPeGetDirectory(ImageBase, map, IMAGE_DIRECTORY_ENTRY_EXPORT, &dir);
2010  if (!INT_SUCCESS(status))
2011  {
2012  goto leave;
2013  }
2014 
2015  // Read the export directory
2016  status = IntKernVirtMemRead(ImageBase + dir.VirtualAddress, sizeof(IMAGE_EXPORT_DIRECTORY), &exportDir, NULL);
2017  if (!INT_SUCCESS(status))
2018  {
2019  // Export directory is pageable so it CAN happen to not be present, so don't spam
2020  if (status != INT_STATUS_PAGE_NOT_PRESENT)
2021  {
2022  WARNING("[ERROR] Failed to read the export directory of 0x%016llx located at 0x%016llx: 0x%08x\n",
2023  ImageBase, ImageBase + dir.VirtualAddress, status);
2024  }
2025  goto leave;
2026  }
2027 
2028  if (Ordinal > exportDir.NumberOfFunctions)
2029  {
2031  goto leave;
2032  }
2033 
2034  // Read the export RVA
2035  status = IntKernVirtMemFetchDword(ImageBase + exportDir.AddressOfFunctions + Ordinal * 4ull, ExportRva);
2036  if (!INT_SUCCESS(status))
2037  {
2038  goto leave;
2039  }
2040 
2041 leave:
2042  if (ImageBaseBuffer == NULL)
2043  {
2044  IntVirtMemUnmap(&map);
2045  }
2046 
2047  // return the error if any...
2048  if (!INT_SUCCESS(status))
2049  {
2050  return status;
2051  }
2052  else if (!found)
2053  {
2054  return INT_STATUS_NOT_FOUND;
2055  }
2056 
2057  return INT_STATUS_SUCCESS;
2058 }
2059 
2060 
2061 INTSTATUS
2063  _In_ QWORD ImageBase,
2064  _In_opt_ BYTE *ImageBaseBuffer,
2065  _In_ DWORD Rva,
2066  _Out_ RUNTIME_FUNCTION *RuntimeFunction
2067  )
2081 {
2083  INTSTATUS status;
2084  DWORD i;
2085  PRUNTIME_FUNCTION pRuntimeFunction;
2086  QWORD currentAddress;
2087  BYTE *map;
2088  BOOLEAN found;
2089  INTRO_PE_INFO peInfo = {0};
2090 
2091  if (NULL == RuntimeFunction)
2092  {
2094  }
2095 
2096  found = FALSE;
2097  pRuntimeFunction = NULL;
2098 
2099  if (NULL != ImageBaseBuffer)
2100  {
2101  map = ImageBaseBuffer;
2102  }
2103  else
2104  {
2105  status = IntVirtMemMap(ImageBase, PAGE_SIZE, 0, 0, &map);
2106  if (!INT_SUCCESS(status))
2107  {
2108  ERROR("[ERROR] Failed mapping VA 0x%016llx to host: 0x%08x\n", ImageBase, status);
2109  return status;
2110  }
2111  }
2112 
2113  status = IntPeValidateHeader(ImageBase, map, PAGE_SIZE, &peInfo, 0);
2114  if (!INT_SUCCESS(status))
2115  {
2116  goto leave;
2117  }
2118 
2119  // this only works on x64 systems
2120  if (!peInfo.Image64Bit)
2121  {
2122  status = INT_STATUS_NOT_SUPPORTED;
2123  goto leave;
2124  }
2125 
2126  status = IntPeGetDirectory(ImageBase, map, IMAGE_DIRECTORY_ENTRY_EXCEPTION, &dir);
2127  if (!INT_SUCCESS(status))
2128  {
2129  ERROR("[ERROR] No exception directory for ImageBase 0x%016llx: 0x%08x\n", ImageBase, status);
2130  goto leave;
2131  }
2132 
2133  // The structures are ordered in memory by BeginAddress field so search till we find one
2134  // bigger than ours or we reach the last page
2135  currentAddress = ImageBase + dir.VirtualAddress;
2136 
2137  while (currentAddress < ImageBase + dir.VirtualAddress + dir.Size)
2138  {
2139  DWORD lastTableInPage = currentAddress & PAGE_OFFSET;
2140  DWORD beginRva;
2141 
2142  // if we get in the next page, this is the last one
2143  while (lastTableInPage + sizeof(RUNTIME_FUNCTION) < PAGE_SIZE)
2144  {
2145  lastTableInPage += sizeof(RUNTIME_FUNCTION);
2146  }
2147 
2148  // If we got out of the exception directory then check the last entry!
2149  if ((currentAddress & PAGE_MASK) + lastTableInPage > ImageBase + dir.VirtualAddress + dir.Size)
2150  {
2151  lastTableInPage = ((QWORD)dir.VirtualAddress + dir.Size - sizeof(RUNTIME_FUNCTION)) & PAGE_OFFSET;
2152  }
2153 
2154  status = IntKernVirtMemFetchDword((currentAddress & PAGE_MASK) + lastTableInPage, &beginRva);
2155  if (!INT_SUCCESS(status))
2156  {
2157  ERROR("[ERROR] IntKernVirtMemFetchDword failed for GVA 0x%016llx (dir->RVA 0x%08x, dir->Size 0x%08x, "
2158  "ImageBase 0x%016llx: 0x%08x\n", currentAddress + lastTableInPage, dir.VirtualAddress,
2159  dir.Size, ImageBase, status);
2160  goto leave;
2161  }
2162  else if (beginRva == Rva)
2163  {
2164  status = IntKernVirtMemRead((currentAddress & PAGE_MASK) + lastTableInPage,
2165  sizeof(RUNTIME_FUNCTION), RuntimeFunction, NULL);
2166  if (!INT_SUCCESS(status))
2167  {
2168  ERROR("[ERROR] Failed reading runtime function from address 0x%016llx: 0x%08x\n",
2169  currentAddress + lastTableInPage, status);
2170  }
2171 
2172  goto leave;
2173  }
2174  else if (beginRva > Rva)
2175  {
2176  found = TRUE;
2177  break;
2178  }
2179 
2180  // the first structure in the next page
2181  currentAddress = (currentAddress & PAGE_MASK) + lastTableInPage + sizeof(RUNTIME_FUNCTION);
2182  }
2183 
2184  if (!found)
2185  {
2186  status = INT_STATUS_NOT_FOUND;
2187  goto leave;
2188  }
2189 
2190  status = IntVirtMemMap(currentAddress, PAGE_REMAINING(currentAddress), 0, 0, &pRuntimeFunction);
2191  if (!INT_SUCCESS(status))
2192  {
2193  ERROR("[ERROR] Failed mapping VA 0x%016llx to host: 0x%08x\n", currentAddress, status);
2194  goto leave;
2195  }
2196 
2197  // It can't be the last one on the page, we already covered that in the while
2198  found = FALSE;
2199  for (i = 0; i < PAGE_REMAINING(currentAddress) / sizeof(RUNTIME_FUNCTION); i++)
2200  {
2201  if (pRuntimeFunction[i].BeginAddress <= Rva && pRuntimeFunction[i].EndAddress > Rva)
2202  {
2203  found = TRUE;
2204  break;
2205  }
2206  }
2207 
2208  if (found)
2209  {
2210  // This may be a pointer to the next unwind info structure
2211  if (pRuntimeFunction[i].UnwindData % sizeof(DWORD) != 0)
2212  {
2213  DWORD newUnwindInfoAddr = ALIGN_DOWN(pRuntimeFunction[i].UnwindData, sizeof(DWORD));
2214 
2215  if (newUnwindInfoAddr >= dir.VirtualAddress &&
2216  newUnwindInfoAddr < dir.VirtualAddress + dir.Size)
2217  {
2218  IntVirtMemUnmap(&pRuntimeFunction);
2219 
2220  status = IntVirtMemMap(ImageBase + newUnwindInfoAddr,
2221  sizeof(RUNTIME_FUNCTION), 0, 0, &pRuntimeFunction);
2222  if (!INT_SUCCESS(status))
2223  {
2224  ERROR("[ERROR] Failed mapping next unwind data for runtime function at "
2225  "0x%016llx in driver 0x%016llx",
2226  currentAddress + i * sizeof(RUNTIME_FUNCTION), ImageBase);
2227  goto leave;
2228  }
2229 
2230  memcpy(RuntimeFunction, pRuntimeFunction, sizeof(RUNTIME_FUNCTION));
2231  goto _done_saving;
2232  }
2233  else
2234  {
2235  TRACE("[INFO] We have a function at 0x%016llx but it's not inside the exception dir (0x%016llx, %x)\n",
2236  ImageBase + pRuntimeFunction->UnwindData, ImageBase + dir.VirtualAddress, dir.Size);
2237 
2238  status = INT_STATUS_NOT_FOUND;
2239  goto _cleanup_and_leave;
2240  }
2241  }
2242 
2243  memcpy(RuntimeFunction, &pRuntimeFunction[i], sizeof(RUNTIME_FUNCTION));
2244 
2245 _done_saving:
2246  status = INT_STATUS_SUCCESS;
2247  }
2248  else
2249  {
2250  status = INT_STATUS_NOT_FOUND;
2251  }
2252 
2253 _cleanup_and_leave:
2254  IntVirtMemUnmap(&pRuntimeFunction);
2255 
2256 leave:
2257  if (NULL == ImageBaseBuffer)
2258  {
2259  IntVirtMemUnmap(&map);
2260  }
2261 
2262  return status;
2263 }
2264 
2265 
2266 INTSTATUS
2268  _In_ QWORD ImageBase,
2269  _In_ BYTE *Buffer,
2270  _In_ DWORD BufferSize,
2271  _In_ DWORD Rva,
2272  _Out_ RUNTIME_FUNCTION *RuntimeFunction
2273  )
2289 {
2291  INTSTATUS status;
2292  RUNTIME_FUNCTION *pRuntimeFunction = NULL;
2293  BOOLEAN found = FALSE;
2294  INTRO_PE_INFO peInfo = {0};
2295  size_t left, right;
2296 
2297  if (NULL == Buffer)
2298  {
2300  }
2301 
2302  if (0 == BufferSize)
2303  {
2305  }
2306 
2307  if (NULL == RuntimeFunction)
2308  {
2310  }
2311 
2312  status = IntPeValidateHeader(ImageBase, Buffer, PAGE_SIZE, &peInfo, 0);
2313  if (!INT_SUCCESS(status))
2314  {
2315  return status;
2316  }
2317 
2318  // this only works on x64 systems
2319  if (!peInfo.Image64Bit)
2320  {
2321  return INT_STATUS_NOT_SUPPORTED;
2322  }
2323 
2324  status = IntPeGetDirectory(ImageBase, Buffer, IMAGE_DIRECTORY_ENTRY_EXCEPTION, &dir);
2325  if (!INT_SUCCESS(status))
2326  {
2327  ERROR("[ERROR] No exception directory for ImageBase 0x%016llx: 0x%08x\n", ImageBase, status);
2328  return status;
2329  }
2330 
2331  // IntPeGetDirectory already did checks for SizeOfImage, but not for BufferSize
2332  if ((QWORD)dir.VirtualAddress + dir.Size > BufferSize)
2333  {
2335  }
2336 
2337  left = 0;
2338  right = (dir.Size / sizeof(RUNTIME_FUNCTION));
2339 
2340  while (left < right)
2341  {
2342  size_t midd = (left + right) / 2;
2343 
2344  pRuntimeFunction = (RUNTIME_FUNCTION *)(Buffer + dir.VirtualAddress + midd * sizeof(*pRuntimeFunction));
2345 
2346  if (pRuntimeFunction->BeginAddress <= Rva && pRuntimeFunction->EndAddress > Rva)
2347  {
2348  found = TRUE;
2349  break;
2350  }
2351  else if (pRuntimeFunction->BeginAddress < Rva)
2352  {
2353  left = midd + 1;
2354  }
2355  else
2356  {
2357  right = midd;
2358  }
2359  }
2360 
2361  if (!found)
2362  {
2363  return INT_STATUS_NOT_FOUND;
2364  }
2365 
2366  // This may be a pointer to the next unwind info structure
2367  if (pRuntimeFunction->UnwindData % sizeof(DWORD) != 0)
2368  {
2369  DWORD newUnwindInfoAddr = ALIGN_DOWN(pRuntimeFunction->UnwindData, sizeof(DWORD));
2370 
2371  if (IN_RANGE_LEN(newUnwindInfoAddr, dir.VirtualAddress, dir.Size))
2372  {
2373  pRuntimeFunction = (RUNTIME_FUNCTION *)(Buffer + newUnwindInfoAddr);
2374 
2375  memcpy(RuntimeFunction, pRuntimeFunction, sizeof(RUNTIME_FUNCTION));
2376 
2377  return INT_STATUS_SUCCESS;
2378  }
2379  else
2380  {
2381  TRACE("[INFO] We have a function at 0x%016llx but it's not inside the exception dir (0x%016llx, %x)\n",
2382  ImageBase + pRuntimeFunction->UnwindData, ImageBase + dir.VirtualAddress, dir.Size);
2383 
2384  return INT_STATUS_NOT_FOUND;
2385  }
2386  }
2387 
2388  memcpy(RuntimeFunction, pRuntimeFunction, sizeof(RUNTIME_FUNCTION));
2389 
2390  return INT_STATUS_SUCCESS;
2391 }
2392 
2393 
2394 INTSTATUS
2396  _In_ QWORD ImageBase,
2397  _In_opt_ BYTE *ImageBaseBuffer,
2398  _In_ RUNTIME_FUNCTION *RuntimeFunction,
2399  _In_opt_ DWORD RipOffset,
2400  _Out_opt_ DWORD *ReservedStack,
2401  _Out_opt_ DWORD *BeginAddress,
2402  _Out_opt_ BOOLEAN *InterruptFunction,
2403  _Out_opt_ BOOLEAN *ExceptionFunction,
2404  _Out_opt_ BOOLEAN *HasFramePointer
2405  )
2425 {
2426  QWORD unwindInfoAddress;
2427  DWORD unwindInfoSize, i, extraSpace;
2428  INTRO_UNWIND_INFO *pUnwindInfoMap;
2429  BOOLEAN hasChainedUnwind;
2430  BOOLEAN interrupt, exception;
2431  BYTE *map;
2432  INTSTATUS status;
2433  DWORD itCount = 0;
2434 
2435  if (NULL == RuntimeFunction)
2436  {
2438  }
2439 
2440  if (RipOffset > RuntimeFunction->EndAddress - RuntimeFunction->BeginAddress)
2441  {
2442  ERROR("[ERROR] RipOffset %x, End %x, Begin %x, Total %x\n", RipOffset, RuntimeFunction->EndAddress,
2443  RuntimeFunction->BeginAddress, RuntimeFunction->EndAddress - RuntimeFunction->BeginAddress);
2445  }
2446 
2447  if (NULL != ImageBaseBuffer)
2448  {
2449  map = ImageBaseBuffer;
2450  }
2451  else
2452  {
2453  status = IntVirtMemMap(ImageBase, PAGE_SIZE, 0, 0, &map);
2454  if (!INT_SUCCESS(status))
2455  {
2456  ERROR("[ERROR] Failed mapping VA 0x%016llx to host: 0x%08x\n", ImageBase, status);
2457  return status;
2458  }
2459  }
2460 
2461  hasChainedUnwind = FALSE;
2462  interrupt = exception = FALSE;
2463  pUnwindInfoMap = NULL;
2464  extraSpace = 0;
2465 
2466  if (NULL != BeginAddress)
2467  {
2468  *BeginAddress = 0;
2469  }
2470 
2471  if (NULL != HasFramePointer)
2472  {
2473  *HasFramePointer = FALSE;
2474  }
2475 
2476  // Align to DWORD (there are some weird cases in kernel when the address is not aligned...
2477  // I assume it's because the UNWIND_INFO structure is shared by multiple structures, and the
2478  // first byte is a flag, at least that's what happened in those cases)
2479  unwindInfoAddress = ALIGN_DOWN(ImageBase + RuntimeFunction->UnwindData, sizeof(DWORD));
2480  unwindInfoSize = MAX_UNWIND_CODES * 2 + 12 + sizeof(DWORD);
2481 
2482  if (NULL != InterruptFunction)
2483  {
2484  *InterruptFunction = FALSE;
2485  }
2486 
2487  if (NULL != ExceptionFunction)
2488  {
2489  *ExceptionFunction = FALSE;
2490  }
2491 
2492  do
2493  {
2494  BOOLEAN saveExtraSpace;
2495  DWORD countOfCodes;
2496 
2497  if (pUnwindInfoMap != NULL)
2498  {
2499  IntVirtMemUnmap(&pUnwindInfoMap);
2500  }
2501 
2502  itCount++;
2503  if (itCount > MAX_UNWIND_INFO_TRIES)
2504  {
2505  ERROR("[ERROR] Reached MAX_UNWIND_INFO_TRIES for image at 0x%016llx\n", ImageBase);
2506  status = INT_STATUS_NOT_SUPPORTED;
2507  goto leave;
2508  }
2509 
2510  status = IntVirtMemMap(unwindInfoAddress, unwindInfoSize, 0, 0, &pUnwindInfoMap);
2511  if (!INT_SUCCESS(status))
2512  {
2513  ERROR("[ERROR] IntVirtMemMap failed for GVA 0x%016llx (chained: %s, RIP: 0x%016llx): 0x%08x\n",
2514  unwindInfoAddress, hasChainedUnwind ? "TRUE" : "FALSE", ImageBase + RuntimeFunction->BeginAddress,
2515  status);
2516  goto leave;
2517  }
2518 
2519  countOfCodes = pUnwindInfoMap->CountOfCodes;
2520 
2521  // Make sure we mapped enough
2522  if (countOfCodes > MAX_UNWIND_CODES)
2523  {
2524  WARNING("[WARNING] Function with %d codes at RVA %08x in driver 0x%016llx with unwind info %llx\n",
2525  countOfCodes, RuntimeFunction->BeginAddress, ImageBase, unwindInfoAddress);
2526  status = INT_STATUS_NOT_SUPPORTED;
2527  goto leave;
2528  }
2529 
2530  if (ReservedStack == NULL)
2531  {
2532  goto _get_chained_info;
2533  }
2534 
2535  // If the FrameRegister is not NULL then another register is used as a stack frame with a FrameOffset
2536  // But this doesn't change anything for what we want to do since the return address is still on RSP.
2537  i = 0; // Sometimes we need to skip the next codes (they're passed as info to this code)
2538  while (i < countOfCodes)
2539  {
2540  BYTE unwindOp = pUnwindInfoMap->UnwindCode[i].UnwindOp;
2541 
2542  // We check for version (1 on Win7, 2 on Win8) in case we somehow don't skip enough codes
2543  if (pUnwindInfoMap->Version != 1 && pUnwindInfoMap->Version != 2)
2544  {
2545  i++;
2546  continue;
2547  }
2548 
2549  // We only save if the instruction was executed. We use less-or-equal because CodeOffset field doesn't point
2550  // to the beginning of the instruction, but to the end. But if we are inside a chained entry, then this
2551  // we save automatically, because it's code that was executed before
2552  if (!hasChainedUnwind)
2553  {
2554  saveExtraSpace = RipOffset >= pUnwindInfoMap->UnwindCode[i].CodeOffset;
2555  }
2556  else
2557  {
2558  saveExtraSpace = TRUE;
2559  }
2560 
2561  // see http://msdn.microsoft.com/en-US/library/ck9asaa9%28v=vs.80%29.aspx for details
2562  switch (unwindOp)
2563  {
2564  case 0: // UWOP_PUSH_NONVOL (1)
2565  if (saveExtraSpace)
2566  {
2567  extraSpace += 8;
2568  }
2569 
2570  i++;
2571  break;
2572 
2573  case 1: // UWOP_ALLOC_LARGE (2 or 3)
2574  if (pUnwindInfoMap->UnwindCode[i].OpInfo == 0)
2575  {
2576  if (saveExtraSpace)
2577  {
2578  extraSpace += *((WORD *)&pUnwindInfoMap->UnwindCode[i + 1]) * 8;
2579  }
2580 
2581  i += 2;
2582  }
2583  else
2584  {
2585  if (saveExtraSpace)
2586  {
2587  extraSpace += *((DWORD *)&pUnwindInfoMap->UnwindCode[i + 2]) * 8;
2588  }
2589 
2590  i += 3;
2591  }
2592 
2593  break;
2594  case 2: // UWOP_ALLOC_SMALL (1)
2595  if (saveExtraSpace)
2596  {
2597  extraSpace += pUnwindInfoMap->UnwindCode[i].OpInfo * 8 + 8;
2598  }
2599 
2600  i++;
2601  break;
2602 
2603  case 3: // UWOP_SET_FPREG(1)
2604  if (HasFramePointer != NULL)
2605  {
2606  *HasFramePointer = TRUE;
2607  }
2608 
2609  i++;
2610  break;
2611 
2612  case 4: // UWOP_SAVE_NONVOL(1)
2613  i += 2;
2614  break;
2615 
2616  case 5: // UWOP_SAVE_NONVOL_FAR
2617  i += 3;
2618  break;
2619 
2620  case 6: // UWOP_EPILOG
2621  // For what I see so far it just says where the exit points are in a function
2622  // (if there are multiple exit points)
2623  i += 1;
2624  break;
2625 
2626  case 7: // UWOP_SPARE_CODE
2627  i += 2;
2628  break;
2629 
2630  case 8: // UWOP_SAVE_XMM128
2631  i += 2;
2632  break;
2633 
2634  case 9: // UWOP_SAVE_XMM128_FAR
2635  i += 3;
2636  break;
2637 
2638  case 10: // UWOP_PUSH_MACHFRAME
2639  if (pUnwindInfoMap->UnwindCode[i].OpInfo == 0)
2640  {
2641  // We are inside an exception. This is done automatically, so no need to verify rip offset
2642  // Actually here, we will do a trick. We only subtract enough to get to the RIP
2643 
2644  exception = TRUE;
2645  }
2646  else if (pUnwindInfoMap->UnwindCode[i].OpInfo == 1)
2647  {
2648  // We are inside an hardware interrupt. This is done automatically, so no need to verify rip offset
2649  // Actually here we will do a trick. We only subtract enough to get to the RIP
2650 
2651  interrupt = TRUE;
2652  }
2653  else
2654  {
2655  WARNING("[WARNING] Unknown info for UWOP_PUSH_MACHFRAME: %d\n",
2656  pUnwindInfoMap->UnwindCode[i].OpInfo);
2657  }
2658 
2659  i++;
2660  break;
2661 
2662  default:
2663  WARNING("[WARNING] UWOP value not known: %d\n", pUnwindInfoMap->UnwindCode[i].UnwindOp);
2664 
2665  i++;
2666  break;
2667  }
2668  }
2669 
2670 _get_chained_info:
2671  // Get the new unwind info address so we can map the new one if we need to. Formula:
2672  // CountOfCode * sizeof(UNWIND_CODE) + 4 (first BYTES in UNWIND_INFO) => RUNTIME_FUNCTION.
2673  // Add 8 (OFFSET_OF(RUNTIME_FUNCTION, UnwindData)) to get the new unwind info address.
2674  hasChainedUnwind = (pUnwindInfoMap->Flags == UNW_FLAG_CHAININFO);
2675 
2676  unwindInfoAddress =
2677  ALIGN_DOWN(ImageBase + * (DWORD *)((PBYTE)pUnwindInfoMap + (countOfCodes * 2ull) + 12),
2678  sizeof(DWORD));
2679 
2680  // Get the actual function start if we have a chain
2681  if (hasChainedUnwind && BeginAddress != NULL)
2682  {
2683  *BeginAddress = *(DWORD *)((PBYTE)pUnwindInfoMap + (countOfCodes * 2ull) + 4);
2684  }
2685  } while (hasChainedUnwind);
2686 
2687  if (NULL != ReservedStack)
2688  {
2689  *ReservedStack = extraSpace;
2690  }
2691 
2692  if (exception && interrupt)
2693  {
2694  WARNING("[WARNING] Why do we have both exception and interrupt context ? RIP 0x%016llx, Module 0x%016llx\n",
2695  ImageBase + RuntimeFunction->BeginAddress + RipOffset, ImageBase);
2696  }
2697 
2698  if (NULL != ExceptionFunction)
2699  {
2700  *ExceptionFunction = exception;
2701  }
2702 
2703  if (NULL != InterruptFunction)
2704  {
2705  *InterruptFunction = interrupt;
2706  }
2707 
2708  status = INT_STATUS_SUCCESS;
2709 
2710 leave:
2711  if (pUnwindInfoMap != NULL)
2712  {
2713  IntVirtMemUnmap(&pUnwindInfoMap);
2714  }
2715 
2716  if (NULL == ImageBaseBuffer)
2717  {
2718  IntVirtMemUnmap(&map);
2719  }
2720 
2721  return status;
2722 }
2723 
2724 
2725 INTSTATUS
2727  _In_ QWORD ImageBase,
2728  _In_ BYTE *Buffer,
2729  _In_ DWORD BufferSize,
2730  _In_ RUNTIME_FUNCTION *RuntimeFunction,
2731  _In_opt_ DWORD RipOffset,
2732  _Out_opt_ DWORD *ReservedStack,
2733  _Out_opt_ DWORD *BeginAddress,
2734  _Out_opt_ BOOLEAN *InterruptFunction,
2735  _Out_opt_ BOOLEAN *ExceptionFunction,
2736  _Out_opt_ BOOLEAN *HasFramePointer
2737  )
2758 {
2759  DWORD unwindInfoRva;
2760  DWORD i, extraSpace;
2761  INTRO_UNWIND_INFO *pUnwindInfo;
2762  BOOLEAN hasChainedUnwind;
2763  BOOLEAN interrupt, exception;
2764  DWORD itCount = 0;
2765  const DWORD unwindInfoSize = MAX_UNWIND_CODES * 2 + 12 + sizeof(DWORD);
2766 
2767  if (NULL == Buffer)
2768  {
2770  }
2771 
2772  if (0 == BufferSize)
2773  {
2775  }
2776 
2777  if (NULL == RuntimeFunction)
2778  {
2780  }
2781 
2782  if (RipOffset > RuntimeFunction->EndAddress - RuntimeFunction->BeginAddress)
2783  {
2784  ERROR("[ERROR] RipOffset %x, End %x, Begin %x, Total %x\n", RipOffset, RuntimeFunction->EndAddress,
2785  RuntimeFunction->BeginAddress, RuntimeFunction->EndAddress - RuntimeFunction->BeginAddress);
2787  }
2788 
2789  hasChainedUnwind = FALSE;
2790  interrupt = exception = FALSE;
2791  extraSpace = 0;
2792 
2793  if (BeginAddress != NULL)
2794  {
2795  *BeginAddress = 0;
2796  }
2797 
2798  if (HasFramePointer != NULL)
2799  {
2800  *HasFramePointer = FALSE;
2801  }
2802 
2803  // Align to DWORD (there are some weird cases in kernel when the address is not aligned...
2804  // I assume it's because the UNWIND_INFO structure is shared by multiple structures, and the
2805  // first byte is a flag, at least that's what happened in those cases)
2806  unwindInfoRva = ALIGN_DOWN(RuntimeFunction->UnwindData, sizeof(DWORD));
2807 
2808  if ((QWORD)unwindInfoRva + sizeof(*pUnwindInfo) + unwindInfoSize >= BufferSize)
2809  {
2810  ERROR("[ERROR] Invalid unwind info at 0x%04x, we have only 0x%04x\n", unwindInfoRva, BufferSize);
2812  }
2813 
2814  if (NULL != InterruptFunction)
2815  {
2816  *InterruptFunction = FALSE;
2817  }
2818 
2819  if (NULL != ExceptionFunction)
2820  {
2821  *ExceptionFunction = FALSE;
2822  }
2823 
2824  do
2825  {
2826  DWORD countOfCodes;
2827 
2828  // Although we checked before with unwindInfoSize, better safe than sorry.
2829  if ((QWORD)unwindInfoRva + sizeof(*pUnwindInfo) > BufferSize)
2830  {
2832  }
2833 
2834  itCount++;
2835  if (itCount > MAX_UNWIND_INFO_TRIES)
2836  {
2837  ERROR("[ERROR] Reached MAX_UNWIND_INFO_TRIES for image at 0x%016llx\n", ImageBase);
2838  return INT_STATUS_NOT_SUPPORTED;
2839  }
2840 
2841  pUnwindInfo = (INTRO_UNWIND_INFO *)(Buffer + unwindInfoRva);
2842 
2843  countOfCodes = pUnwindInfo->CountOfCodes;
2844 
2845  // Make sure we mapped enough
2846  if (countOfCodes > MAX_UNWIND_CODES)
2847  {
2848  WARNING("[WARNING] Function with %d codes at RVA %08x in driver 0x%016llx with unwind info 0x%04x\n",
2849  countOfCodes, RuntimeFunction->BeginAddress, ImageBase, unwindInfoRva);
2850  return INT_STATUS_NOT_SUPPORTED;
2851  }
2852 
2853  if (ReservedStack == NULL)
2854  {
2855  goto _get_chained_info;
2856  }
2857 
2858  // If the FrameRegister is not NULL then another register is used as a stack frame with a FrameOffset
2859  // But this doesn't change anything for what we want to do since the return address is still on RSP.
2860  i = 0; // Sometimes we need to skip the next codes (they're passed as info to this code)
2861  while (i < countOfCodes)
2862  {
2863  BOOLEAN saveExtraSpace;
2864  BYTE unwindOp = pUnwindInfo->UnwindCode[i].UnwindOp;
2865 
2866  // We check for version (1 on Win7, 2 on Win8) in case we somehow don't skip enough codes
2867  if (pUnwindInfo->Version != 1 && pUnwindInfo->Version != 2)
2868  {
2869  i++;
2870  continue;
2871  }
2872 
2873  // We only save if the instruction was executed. We use less-or-equal because CodeOffset field doesn't point
2874  // to the beginning of the instruction, but to the end. But if we are inside a chained entry, then this
2875  // we save automatically, because it's code that was executed before
2876  if (!hasChainedUnwind)
2877  {
2878  saveExtraSpace = RipOffset >= pUnwindInfo->UnwindCode[i].CodeOffset;
2879  }
2880  else
2881  {
2882  saveExtraSpace = TRUE;
2883  }
2884 
2885  // see http://msdn.microsoft.com/en-US/library/ck9asaa9%28v=vs.80%29.aspx for details
2886  switch (unwindOp)
2887  {
2888  case 0: // UWOP_PUSH_NONVOL (1)
2889  if (saveExtraSpace)
2890  {
2891  extraSpace += 8;
2892  }
2893 
2894  i++;
2895  break;
2896 
2897  case 1: // UWOP_ALLOC_LARGE (2 or 3)
2898  if (pUnwindInfo->UnwindCode[i].OpInfo == 0)
2899  {
2900  if (saveExtraSpace)
2901  {
2902  extraSpace += *((WORD *)&pUnwindInfo->UnwindCode[i + 1]) * 8;
2903  }
2904 
2905  i += 2;
2906  }
2907  else
2908  {
2909  if (saveExtraSpace)
2910  {
2911  extraSpace += *((DWORD *)&pUnwindInfo->UnwindCode[i + 2]) * 8;
2912  }
2913 
2914  i += 3;
2915  }
2916 
2917  break;
2918  case 2: // UWOP_ALLOC_SMALL (1)
2919  if (saveExtraSpace)
2920  {
2921  extraSpace += pUnwindInfo->UnwindCode[i].OpInfo * 8 + 8;
2922  }
2923 
2924  i++;
2925  break;
2926 
2927  case 3: // UWOP_SET_FPREG(1)
2928  if (HasFramePointer != NULL)
2929  {
2930  *HasFramePointer = TRUE;
2931  }
2932 
2933  i++;
2934  break;
2935 
2936  case 4: // UWOP_SAVE_NONVOL(1)
2937  i += 2;
2938  break;
2939 
2940  case 5: // UWOP_SAVE_NONVOL_FAR
2941  i += 3;
2942  break;
2943 
2944  case 6: // UWOP_EPILOG
2945  // For what I see so far it just says where the exit points are in a function
2946  // (if there are multiple exit points)
2947  i += 1;
2948  break;
2949 
2950  case 7: // UWOP_SPARE_CODE
2951  i += 2;
2952  break;
2953 
2954  case 8: // UWOP_SAVE_XMM128
2955  i += 2;
2956  break;
2957 
2958  case 9: // UWOP_SAVE_XMM128_FAR
2959  i += 3;
2960  break;
2961 
2962  case 10: // UWOP_PUSH_MACHFRAME
2963  if (pUnwindInfo->UnwindCode[i].OpInfo == 0)
2964  {
2965  // We are inside an exception. This is done automatically, so no need to verify rip offset
2966  // Actually here, we will do a trick. We only subtract enough to get to the RIP
2967 
2968  exception = TRUE;
2969  }
2970  else if (pUnwindInfo->UnwindCode[i].OpInfo == 1)
2971  {
2972  // We are inside an hardware interrupt. This is done automatically, so no need to verify rip offset
2973  // Actually here we will do a trick. We only subtract enough to get to the RIP
2974 
2975  interrupt = TRUE;
2976  }
2977  else
2978  {
2979  WARNING("[WARNING] Unknown info for UWOP_PUSH_MACHFRAME: %d\n",
2980  pUnwindInfo->UnwindCode[i].OpInfo);
2981  }
2982 
2983  i++;
2984  break;
2985 
2986  default:
2987  WARNING("[WARNING] UWOP value not known: %d\n", pUnwindInfo->UnwindCode[i].UnwindOp);
2988 
2989  i++;
2990  break;
2991  }
2992  }
2993 
2994 _get_chained_info:
2995  // Get the new unwind info address so we can map the new one if we need to. Formula:
2996  // CountOfCode * sizeof(UNWIND_CODE) + 4 (first BYTES in UNWIND_INFO) => RUNTIME_FUNCTION.
2997  // Add 8 (FIELD_OFFSET(RUNTIME_FUNCTION, UnwindData)) to get the new unwind info address.
2998  hasChainedUnwind = (pUnwindInfo->Flags == UNW_FLAG_CHAININFO);
2999 
3000  unwindInfoRva =
3001  ALIGN_DOWN(*(DWORD *)((BYTE *)pUnwindInfo + (countOfCodes * 2ull) + 12), sizeof(DWORD));
3002 
3003  // We want to verify only if it has chained unwind, as the next RVA will most probably be invalid and we
3004  // shouldn't bother as we don't get anything from the buffer anymore.
3005  if (hasChainedUnwind && (QWORD)unwindInfoRva + sizeof(*pUnwindInfo) + unwindInfoSize >= BufferSize)
3006  {
3007  ERROR("[ERROR] Invalid unwind info at 0x%04x, we have only 0x%04x\n", unwindInfoRva, BufferSize);
3009  }
3010 
3011  // Get the actual function start if we have a chain
3012  if (hasChainedUnwind && BeginAddress != NULL)
3013  {
3014  *BeginAddress = *(DWORD *)((BYTE *)pUnwindInfo + (countOfCodes * 2ull) + 4);
3015  }
3016  } while (hasChainedUnwind);
3017 
3018  if (NULL != ReservedStack)
3019  {
3020  *ReservedStack = extraSpace;
3021  }
3022 
3023  if (exception && interrupt)
3024  {
3025  WARNING("[WARNING] Why do we have both exception and interrupt context ? RIP 0x%016llx, Module 0x%016llx\n",
3026  ImageBase + RuntimeFunction->BeginAddress + RipOffset, ImageBase);
3027  }
3028 
3029  if (NULL != ExceptionFunction)
3030  {
3031  *ExceptionFunction = exception;
3032  }
3033 
3034  if (NULL != InterruptFunction)
3035  {
3036  *InterruptFunction = interrupt;
3037  }
3038 
3039  return INT_STATUS_SUCCESS;
3040 }
3041 
3042 
3043 INTSTATUS
3045  _In_bytecount_(BufferSize) BYTE *Buffer,
3046  _In_ DWORD BufferSize,
3048  _In_ BOOLEAN IgnoreSectionHint,
3049  _Out_ DWORD *Rva
3050  )
3066 {
3067  IMAGE_DOS_HEADER *pDos;
3068  IMAGE_FILE_HEADER *pFileHeader;
3069  IMAGE_SECTION_HEADER *pSec;
3070  DWORD secheadersRva;
3071  BYTE *p;
3072  INTSTATUS status;
3073 
3074  // Validate the header before actually going through sections
3075  status = IntPeValidateHeader(0, Buffer, BufferSize, NULL, 0);
3076  if (!INT_SUCCESS(status))
3077  {
3078  return status;
3079  }
3080 
3081  pDos = (IMAGE_DOS_HEADER *)Buffer;
3082 
3083  // Read the signature + file header
3084  pFileHeader = (IMAGE_FILE_HEADER *)(Buffer + pDos->e_lfanew + 4);
3085 
3086  secheadersRva = pDos->e_lfanew + 4 + sizeof(IMAGE_FILE_HEADER) + pFileHeader->SizeOfOptionalHeader;
3087 
3088  for (DWORD i = 0; i < pFileHeader->NumberOfSections; i++)
3089  {
3090  pSec = (IMAGE_SECTION_HEADER *)(Buffer + secheadersRva + sizeof(IMAGE_SECTION_HEADER) * i);
3091 
3092  // ERRATA has a size of 3...
3093  if (pSec->Misc.VirtualSize < Pattern->Signature.Length)
3094  {
3095  continue;
3096  }
3097 
3098  if (Pattern->SectionHint[0] && !IgnoreSectionHint && 0 != memcmp(pSec->Name, Pattern->SectionHint, 8))
3099  {
3100  continue;
3101  }
3102 
3103  if ((QWORD)pSec->VirtualAddress + pSec->Misc.VirtualSize > BufferSize)
3104  {
3105  CHAR name[9];
3106  memcpy(name, pSec->Name, sizeof(pSec->Name));
3107  name[8] = 0;
3108 
3109  ERROR("[ERROR] Section %s %08x with size %08x outside of image size %08x...\n",
3110  name, pSec->VirtualAddress, pSec->Misc.VirtualSize, BufferSize);
3112  }
3113 
3114  p = Buffer + pSec->VirtualAddress;
3115 
3116  // Try to find the pattern inside this section.
3117  for (DWORD j = 0; j < pSec->Misc.VirtualSize - Pattern->Signature.Length; j++)
3118  {
3119  BOOLEAN bFound = TRUE;
3120 
3121  for (DWORD k = 0; k < Pattern->Signature.Length; k++)
3122  {
3123  if (__likely(Pattern->Signature.Pattern[k] != 0x100 &&
3124  Pattern->Signature.Pattern[k] != p[j + k]))
3125  {
3126  bFound = FALSE;
3127  break;
3128  }
3129  }
3130 
3131  if (bFound)
3132  {
3133  *Rva = pSec->VirtualAddress + j;
3134  if (IgnoreSectionHint)
3135  {
3136  TRACE("[DEBUG] Found function inside section %d:%s, was supposed to find in %s\n",
3137  i, pSec->Name, Pattern->SectionHint);
3138  }
3139 
3140  return INT_STATUS_SUCCESS;
3141  }
3142  }
3143  }
3144 
3145  return INT_STATUS_NOT_FOUND;
3146 }
3147 
3148 
3149 INTSTATUS
3151  _In_ QWORD ImageBase,
3153  _In_ BOOLEAN IgnoreSectionHint,
3154  _Out_ DWORD *Rva
3155  )
3171 {
3172  INTSTATUS status;
3173  QWORD secheadersRva;
3174  QWORD cr3 = gGuest.Mm.SystemCr3;
3175  INTRO_PE_INFO peInfo = { 0 };
3176 
3177  status = IntPeValidateHeader(ImageBase, NULL, 0, &peInfo, cr3);
3178  if (!INT_SUCCESS(status))
3179  {
3180  ERROR("IntPeValidateHeader failed for image at 0x%016llx: 0x%08x\n", ImageBase, status);
3181  return status;
3182  }
3183 
3184  secheadersRva = peInfo.SectionOffset;
3185 
3186  for (QWORD i = 0; i < peInfo.NumberOfSections; i++)
3187  {
3188  PBYTE pPage1, pPage2;
3189  DWORD rva, k, j, origStart;
3190  BOOLEAN bFound;
3191  IMAGE_SECTION_HEADER hSec;
3192 
3193  bFound = FALSE;
3194 
3195  // Read the designated section header.
3196  status = IntKernVirtMemRead(ImageBase + secheadersRva + (sizeof(IMAGE_SECTION_HEADER) * i),
3197  sizeof(IMAGE_SECTION_HEADER), &hSec, NULL);
3198  if (!INT_SUCCESS(status))
3199  {
3200  return status;
3201  }
3202 
3203  if (!IgnoreSectionHint && 0 != memcmp(hSec.Name, Pattern->SectionHint, 8))
3204  {
3205  continue;
3206  }
3207 
3208  // Try to find the pattern inside this section.
3209  for (rva = hSec.VirtualAddress; rva < hSec.VirtualAddress + hSec.Misc.VirtualSize; rva += 0x1000)
3210  {
3211  if (rva >= peInfo.SizeOfImage)
3212  {
3213  break;
3214  }
3215 
3216  status = IntVirtMemMap(ImageBase + rva, PAGE_SIZE, cr3, 0, &pPage1);
3217  if (!INT_SUCCESS(status))
3218  {
3219  continue;
3220  }
3221 
3222  // Try to match the sig in this current page.
3223  origStart = j = k = 0;
3224  while (k < PAGE_SIZE)
3225  {
3226  origStart = k;
3227 
3228  j = 0;
3229 
3230  while ((k < PAGE_SIZE) && (j < Pattern->Signature.Length) &&
3231  ((pPage1[k] == Pattern->Signature.Pattern[j]) ||
3232  (0x100 == Pattern->Signature.Pattern[j])))
3233  {
3234  k++;
3235  j++;
3236  }
3237 
3238  // Handle page boundary access
3239  if (k == PAGE_SIZE)
3240  {
3241  DWORD l;
3242 
3243  // We must map the next page
3244  status = IntVirtMemMap(ImageBase + rva + PAGE_SIZE, PAGE_SIZE, cr3, 0, &pPage2);
3245  if (!INT_SUCCESS(status))
3246  {
3247  break;
3248  }
3249 
3250  l = 0;
3251 
3252  while ((l < PAGE_SIZE) && (j < Pattern->Signature.Length) &&
3253  ((pPage2[l] == Pattern->Signature.Pattern[j]) ||
3254  (0x100 == Pattern->Signature.Pattern[j])))
3255  {
3256  l++;
3257  j++;
3258  }
3259 
3260  IntVirtMemUnmap(&pPage2);
3261  }
3262 
3263  if (j != Pattern->Signature.Length)
3264  {
3265  k = origStart;
3266  }
3267  else
3268  {
3269  bFound = TRUE;
3270  break;
3271  }
3272 
3273  k++;
3274  }
3275 
3276  IntVirtMemUnmap(&pPage1);
3277 
3278  if (bFound)
3279  {
3280  *Rva = rva + origStart;
3281  if (IgnoreSectionHint)
3282  {
3283  TRACE("[DEBUG] Found function inside section %lld:%s, was supposed to find in %s\n",
3284  i, hSec.Name, Pattern->SectionHint);
3285  }
3286 
3287  return INT_STATUS_SUCCESS;
3288  }
3289  }
3290  }
3291 
3292  return INT_STATUS_NOT_FOUND;
3293 }
3294 
3295 
3296 INTSTATUS
3298  _In_ QWORD ImageBase,
3299  _In_opt_ BYTE *ImageBaseBuffer,
3300  _In_ DWORD Rva,
3301  _Out_ DWORD *BeginAddress
3302  )
3320 {
3321  INTRO_PE_INFO peInfo = {0};
3322  BYTE *map;
3323  BOOLEAN found;
3324  INTSTATUS status;
3325  IMAGE_SECTION_HEADER sectionHeader = {0};
3326  QWORD cr3;
3327 
3328  if (NULL == BeginAddress)
3329  {
3331  }
3332 
3333  *BeginAddress = 0;
3334  found = FALSE;
3335 
3336  if (NULL != ImageBaseBuffer)
3337  {
3338  map = ImageBaseBuffer;
3339  }
3340  else
3341  {
3342  status = IntVirtMemMap(ImageBase, PAGE_SIZE, 0, 0, &map);
3343  if (!INT_SUCCESS(status))
3344  {
3345  return status;
3346  }
3347  }
3348 
3349  // Validate that this is a good module, and get the architecture
3350  status = IntPeValidateHeader(ImageBase, map, PAGE_SIZE, &peInfo, 0);
3351  if (!INT_SUCCESS(status))
3352  {
3353  goto leave;
3354  }
3355 
3356  // we ignore the errors, because maybe we want to search outside the driver
3357  status = IntPeGetSectionHeaderByRva(ImageBase, map, Rva, &sectionHeader);
3358  if (INT_SUCCESS(status))
3359  {
3360  if (0 == ((sectionHeader.Characteristics & IMAGE_SCN_CNT_CODE)) ||
3361  0 == ((sectionHeader.Characteristics & IMAGE_SCN_MEM_EXECUTE)))
3362  {
3363  status = INT_STATUS_NOT_SUPPORTED;
3364  goto leave;
3365  }
3366  }
3367 
3368  status = IntCr3Read(IG_CURRENT_VCPU, &cr3);
3369  if (!INT_SUCCESS(status))
3370  {
3371  ERROR("[ERROR] IntCr3Read failed: 0x%08x\n", status);
3372  goto leave;
3373  }
3374 
3375  // Tested on the whole kernel memory space on Windows 7 (both 64 and 32 bit) and not a single fail
3376  if (peInfo.Image64Bit)
3377  {
3378  RUNTIME_FUNCTION runtimeFunction;
3379 
3380  status = IntPeGetRuntimeFunction(ImageBase, map, Rva, &runtimeFunction);
3381  if (!INT_SUCCESS(status))
3382  {
3383  goto leave;
3384  }
3385 
3386  *BeginAddress = runtimeFunction.BeginAddress;
3387 
3388  // Parse all the unwind info structures and get to the actual beginning of the function
3389  status = IntPeParseUnwindData(ImageBase, map, &runtimeFunction, 0, NULL, BeginAddress, NULL, NULL, NULL);
3390  if (!INT_SUCCESS(status))
3391  {
3392  WARNING("[WARNING] IntPeParseUnwindData failed for 0x%08x: 0x%08x\n", *BeginAddress, status);
3393  }
3394 
3395  status = INT_STATUS_SUCCESS;
3396  }
3397  else
3398  {
3399  PBYTE pageMap, code;
3400  QWORD currentAddress, functionStart;
3401  DWORD bytesToScan;
3402  BOOLEAN physicalUnmap;
3403 
3404  currentAddress = ImageBase + Rva;
3405 
3406  status = IntVirtMemMap(currentAddress & PAGE_MASK, PAGE_SIZE, cr3, 0, &pageMap);
3407  if (!INT_SUCCESS(status))
3408  {
3409  ERROR("[ERROR] Failed mapping VA 0x%016llx to host: 0x%08x\n", currentAddress, status);
3410  goto leave;
3411  }
3412 
3413  // go where the RIP is
3414  code = pageMap + (currentAddress & PAGE_OFFSET);
3415 
3416  // see that we aren't going in the previous section. If we do, scan only from the section start
3417  if (sectionHeader.VirtualAddress != 0 &&
3418  (Rva - MAX_FUNC_LENGTH < sectionHeader.VirtualAddress))
3419  {
3420  bytesToScan = Rva - sectionHeader.VirtualAddress;
3421  }
3422  else
3423  {
3424  bytesToScan = MAX_FUNC_LENGTH;
3425  }
3426 
3427  functionStart = 0;
3428  physicalUnmap = FALSE;
3429 
3430  while (bytesToScan > 0)
3431  {
3432  NDSTATUS ndstatus;
3433  INSTRUX instruction;
3434  BYTE sepByte;
3435 
3436  // before the function there must be a series of NOP or INT3
3437  if (*code != 0xCC && *code != 0x90)
3438  {
3439  goto _next_bytes;
3440  }
3441 
3442  // Save a pointer to the function start (at current address is a NOP or a INT3)
3443  functionStart = currentAddress + 1;
3444 
3445  // This byte must repeat itself between the function
3446  sepByte = *code;
3447 
3448  // Get where the separating bytes will end
3449  while (*code == sepByte && bytesToScan > 1)
3450  {
3451  --code;
3452  --bytesToScan;
3453  --currentAddress;
3454 
3455  // We reached the end of the page. See if we can map further.
3456  // Since code starts as pageMap it should never go lower than pageMap
3457  if (code < pageMap)
3458  {
3459  QWORD prevPage;
3460 
3461  status = IntTranslateVirtualAddress(currentAddress & PAGE_MASK, cr3, &prevPage);
3462  if (!INT_SUCCESS(status))
3463  {
3464  // We must assume this is the beginning, since the previous page is not present. Also the
3465  // current page won't be unmapped
3466  break;
3467  }
3468 
3469  IntVirtMemUnmap(&pageMap);
3470 
3471  status = IntPhysMemMap(prevPage, PAGE_SIZE, 0, &pageMap);
3472  if (!INT_SUCCESS(status))
3473  {
3474  // This shouldn't fail since we checked that the previous page is present. So exit the
3475  // function if this happens and signal the error
3476  ERROR("[ERROR] Failed mapping VA 0x%016llx with GPA 0x%016llx to host: 0x%08x\n",
3477  currentAddress & PAGE_MASK, prevPage, status);
3478  goto leave;
3479  }
3480 
3481  physicalUnmap = TRUE;
3482  code = pageMap + (currentAddress & PAGE_OFFSET);
3483  }
3484  }
3485 
3486  // There must be more than 3 bytes separating
3487  if (functionStart - currentAddress < 3)
3488  {
3489  // The current one is still different from NOP and INT3 so decrement it again
3490  goto _next_bytes;
3491  }
3492 
3493  // If the previous instruction is a RET, don't check anymore
3494  if ((*code == 0xc3 || *code == 0xCB) || ((((QWORD)code & PAGE_OFFSET) >= 0x2) && (*(code - 2) == 0xc2)) ||
3495  // RET / imm16
3496  ((((QWORD)code & PAGE_OFFSET) >= 0x4) && (*(code - 4) == 0xe9 || *(code - 4) == 0xe8))) // CALL or JMP
3497  {
3498  found = TRUE;
3499  break;
3500  }
3501 
3502  // The end of the previous function and the start of the one we search are in different pages.
3503  // Or if the function start is at an offset too big (> 0xff0) that when we will try to decode will spill
3504  // in the next page
3505  if ((functionStart & PAGE_MASK) != (currentAddress & PAGE_MASK) || (functionStart & PAGE_OFFSET) >= 0xff0)
3506  {
3507  // If we get here, the next page should be present (CODE sections are not swappable!)
3508  status = IntDecDecodeInstruction(IG_CS_TYPE_32B, functionStart, &instruction);
3509  if (!INT_SUCCESS(status))
3510  {
3511  goto _next_bytes;
3512  }
3513  }
3514  else
3515  {
3516  // We are in the same page
3517  const QWORD pageMapOffset = functionStart & PAGE_OFFSET;
3518  ndstatus = NdDecodeEx(&instruction, pageMap + pageMapOffset, PAGE_SIZE - pageMapOffset,
3519  ND_CODE_32, ND_DATA_32);
3520  if (!ND_SUCCESS(ndstatus))
3521  {
3522  goto _next_bytes;
3523  }
3524  }
3525 
3526 
3527  // a lot of functions start with a movzx
3528  if ((instruction.Instruction == ND_INS_MOV || instruction.Instruction == ND_INS_MOVZX ||
3529  instruction.Instruction == ND_INS_MOVS || instruction.Instruction == ND_INS_MOVSXD ||
3530  instruction.Instruction == ND_INS_MOVNTI) &&
3531  instruction.HasModRm)
3532  {
3533  if (instruction.Operands[0].Size != 4) // Must operate on a whole register, not just a part.
3534  {
3535  goto _next_bytes;
3536  }
3537 
3538  if ((instruction.ModRm.reg == 5 && instruction.ModRm.rm == 4) || // mov ebp, esp
3539  (instruction.ModRm.reg == 7 && instruction.ModRm.rm == 7)) // mod edi, edi
3540  {
3541  found = TRUE;
3542  break;
3543  }
3544 
3545  goto _next_bytes;
3546  }
3547 
3548  if ((ND_CAT_PUSH == instruction.Category) || (ND_INS_XOR == instruction.Instruction))
3549  {
3550  found = TRUE;
3551  break;
3552  }
3553 
3554 _next_bytes:
3555  --currentAddress;
3556  --code;
3557  --bytesToScan;
3558 
3559  // We reached the end of the page. See if we can map further.
3560  // Since code starts as pageMap it should never go lower than pageMap
3561  if (code < pageMap)
3562  {
3563  QWORD prevPage;
3564 
3565  status = IntTranslateVirtualAddress(currentAddress & PAGE_MASK, cr3, &prevPage);
3566  if (!INT_SUCCESS(status))
3567  {
3568  // The function will exit with INT_STATUS_NOT_FOUND since the start wasn't found and we cannot scan
3569  // further
3570  break;
3571  }
3572 
3573  IntVirtMemUnmap(&pageMap);
3574 
3575  status = IntPhysMemMap(prevPage, PAGE_SIZE, 0, &pageMap);
3576  if (!INT_SUCCESS(status))
3577  {
3578  // This shouldn't fail since we checked that the previous page is present. So exit the
3579  // function if this happens and signal the error
3580  ERROR("[ERROR] Failed mapping VA 0x%016llx with GPA 0x%016llx to host: 0x%08x\n",
3581  currentAddress & PAGE_MASK, prevPage, status);
3582  goto leave;
3583  }
3584 
3585  physicalUnmap = TRUE;
3586  code = pageMap + (currentAddress & PAGE_OFFSET);
3587  }
3588  }
3589 
3590  // Clean the memory
3591  if (pageMap != NULL && physicalUnmap)
3592  {
3593  IntPhysMemUnmap(&pageMap);
3594  }
3595  else if (pageMap != NULL)
3596  {
3597  IntVirtMemUnmap(&pageMap);
3598  }
3599 
3600  if (!found)
3601  {
3602  status = INT_STATUS_NOT_FOUND;
3603  goto leave;
3604  }
3605  else
3606  {
3607  status = INT_STATUS_SUCCESS;
3608  }
3609 
3610  *BeginAddress = 0xffffffff & (functionStart - ImageBase);
3611  }
3612 
3613 leave:
3614  if (NULL == ImageBaseBuffer)
3615  {
3616  IntVirtMemUnmap(&map);
3617  }
3618 
3619  return status;
3620 }
3621 
3622 
3623 INTSTATUS
3625  _In_ QWORD ImageBase,
3626  _In_ BYTE *Buffer,
3627  _In_ DWORD BufferSize,
3628  _In_ DWORD Rva,
3629  _Out_ DWORD *BeginAddress
3630  )
3649 {
3650  INTRO_PE_INFO peInfo = { 0 };
3651  BOOLEAN found;
3652  INTSTATUS status;
3653  IMAGE_SECTION_HEADER sectionHeader = { 0 };
3654 
3655  if (NULL == Buffer)
3656  {
3658  }
3659 
3660  if (0 == BufferSize)
3661  {
3663  }
3664 
3665  if (NULL == BeginAddress)
3666  {
3668  }
3669 
3670  if (Rva >= BufferSize)
3671  {
3673  }
3674 
3675  *BeginAddress = 0;
3676  found = FALSE;
3677 
3678  // Validate that this is a good module, and get the architecture
3679  status = IntPeValidateHeader(ImageBase, Buffer, PAGE_SIZE, &peInfo, 0);
3680  if (!INT_SUCCESS(status))
3681  {
3682  return status;
3683  }
3684 
3685  // we ignore the errors, because maybe we want to search outside the driver
3686  status = IntPeGetSectionHeaderByRva(ImageBase, Buffer, Rva, &sectionHeader);
3687  if (INT_SUCCESS(status))
3688  {
3689  if (0 == (sectionHeader.Characteristics & IMAGE_SCN_MEM_EXECUTE))
3690  {
3691  return INT_STATUS_NOT_SUPPORTED;
3692  }
3693  }
3694 
3695  // Tested on the whole kernel memory space on Windows 7 (both 64 and 32 bit) and not a single fail
3696  if (peInfo.Image64Bit)
3697  {
3698  RUNTIME_FUNCTION runtimeFunction;
3699 
3700  status = IntPeGetRuntimeFunctionInBuffer(ImageBase, Buffer, BufferSize, Rva, &runtimeFunction);
3701  if (!INT_SUCCESS(status))
3702  {
3703  return status;
3704  }
3705 
3706  *BeginAddress = runtimeFunction.BeginAddress;
3707 
3708  // Parse all the unwind info structures and get to the actual beginning of the function
3709  status = IntPeParseUnwindDataInBuffer(ImageBase, Buffer, BufferSize,
3710  &runtimeFunction, 0, NULL, BeginAddress, NULL, NULL, NULL);
3711  if (!INT_SUCCESS(status))
3712  {
3713  WARNING("[WARNING] IntPeParseUnwindDataInBuffer failed for 0x%08x: 0x%08x\n", *BeginAddress, status);
3714  }
3715 
3716  return INT_STATUS_SUCCESS;
3717  }
3718 
3719  {
3720  BYTE *code = Buffer + Rva;
3721  QWORD functionStartRva = 0;
3722  DWORD bytesToScan;
3723  QWORD currentRva = Rva;
3724 
3725  // see that we aren't going in the previous section. If we do, scan only from the section start
3726  if (sectionHeader.VirtualAddress != 0 &&
3727  (Rva - MAX_FUNC_LENGTH < sectionHeader.VirtualAddress))
3728  {
3729  bytesToScan = Rva - sectionHeader.VirtualAddress;
3730  }
3731  else
3732  {
3733  bytesToScan = MAX_FUNC_LENGTH;
3734  }
3735 
3736  while (bytesToScan > 0)
3737  {
3738  NDSTATUS ndstatus;
3739  INSTRUX instruction;
3740  BYTE sepByte;
3741 
3742  if (code < Buffer)
3743  {
3744  break;
3745  }
3746 
3747  // before the function there must be a series of NOP or INT3
3748  if (*code != 0xCC && *code != 0x90)
3749  {
3750  goto _next_bytes;
3751  }
3752 
3753  // Save a pointer to the function start (at current address is a NOP or a INT3)
3754  functionStartRva = currentRva + 1;
3755 
3756  if (functionStartRva >= BufferSize)
3757  {
3758  break;
3759  }
3760 
3761  // This byte must repeat itself between the function
3762  sepByte = *code;
3763 
3764  // Get where the separating bytes will end
3765  while (code >= Buffer && *code == sepByte && bytesToScan)
3766  {
3767  --code;
3768  --bytesToScan;
3769  --currentRva;
3770  }
3771 
3772  if (code < Buffer)
3773  {
3774  break;
3775  }
3776 
3777  // There must be more than 3 bytes separating
3778  if (functionStartRva - currentRva < 3)
3779  {
3780  // The current one is still different from NOP and INT3 so decrement it again
3781  goto _next_bytes;
3782  }
3783 
3784  // If the previous instruction is a RET, don't check anymore
3785  if ((*code == 0xc3 || *code == 0xCB) ||
3786  ((((QWORD)code & PAGE_OFFSET) >= 0x2) && (*(code - 2) == 0xc2)) || // RET / imm16
3787  ((((QWORD)code & PAGE_OFFSET) >= 0x4) && (*(code - 4) == 0xe9 || *(code - 4) == 0xe8))) // CALL or JMP
3788  {
3789  found = TRUE;
3790  break;
3791  }
3792 
3793  ndstatus = NdDecodeEx(&instruction, Buffer + functionStartRva, BufferSize - functionStartRva,
3794  ND_CODE_32, ND_DATA_32);
3795  if (!ND_SUCCESS(ndstatus))
3796  {
3797  goto _next_bytes;
3798  }
3799 
3800 
3801  // a lot of functions start with a movzx
3802  if ((instruction.Instruction == ND_INS_MOV || instruction.Instruction == ND_INS_MOVZX ||
3803  instruction.Instruction == ND_INS_MOVS || instruction.Instruction == ND_INS_MOVSXD ||
3804  instruction.Instruction == ND_INS_MOVNTI) &&
3805  instruction.HasModRm)
3806  {
3807  if (instruction.Operands[0].Size != 4) // Must operate on a whole register, not just a part.
3808  {
3809  goto _next_bytes;
3810  }
3811 
3812  if ((instruction.ModRm.reg == 5 && instruction.ModRm.rm == 4) || // mov ebp, esp
3813  (instruction.ModRm.reg == 7 && instruction.ModRm.rm == 7)) // mod edi, edi
3814  {
3815  found = TRUE;
3816  break;
3817  }
3818 
3819  goto _next_bytes;
3820  }
3821 
3822  if ((ND_CAT_PUSH == instruction.Category) || (ND_INS_XOR == instruction.Instruction))
3823  {
3824  found = TRUE;
3825  break;
3826  }
3827 
3828 _next_bytes:
3829  --currentRva;
3830  --code;
3831  --bytesToScan;
3832  }
3833 
3834  if (!found)
3835  {
3836  return INT_STATUS_NOT_FOUND;
3837  }
3838 
3839  *BeginAddress = functionStartRva & 0xFFFFFFFF;
3840  }
3841 
3842  return INT_STATUS_SUCCESS;
3843 }
INTSTATUS IntPeFindFunctionByPattern(QWORD ImageBase, WIN_UNEXPORTED_FUNCTION_PATTERN *Pattern, BOOLEAN IgnoreSectionHint, DWORD *Rva)
Find a function using a pattern.
Definition: winpe.c:3150
#define IMAGE_SCN_MEM_EXECUTE
Definition: winpe.h:472
UINT8 CountOfCodes
Definition: winpe.h:562
INTSTATUS IntPeFindFunctionStartInBuffer(QWORD ImageBase, BYTE *Buffer, DWORD BufferSize, DWORD Rva, DWORD *BeginAddress)
Find the start address of a function, given a Rva pointing inside of it.
Definition: winpe.c:3624
#define _In_opt_
Definition: intro_sal.h:16
UINT16 NumberOfSections
Definition: winpe.h:65
#define INT_STATUS_PAGE_NOT_PRESENT
Indicates that a virtual address is not present.
Definition: introstatus.h:438
#define MAX_FUNC_LENGTH
The maximum length (in bytes) of a function.
Definition: winpe.h:582
INTSTATUS IntPeFindExportByName(QWORD ImageBase, BYTE *ImageBaseBuffer, CHAR *Name, DWORD *ExportRva)
Find the export name a Rva lies in.
Definition: winpe.c:1783
#define _Out_
Definition: intro_sal.h:22
_Bool BOOLEAN
Definition: intro_types.h:58
INTSTATUS IntPeFindExportByRva(QWORD ImageBase, BYTE *ImageBaseBuffer, DWORD Rva)
Check if a RVA lies inside an exported function.
Definition: winpe.c:1103
#define ROUND_UP(what, to)
Definition: introdefs.h:158
struct _IMAGE_FILE_HEADER IMAGE_FILE_HEADER
INTSTATUS IntVirtMemUnmap(void **HostPtr)
Unmaps a memory range previously mapped with IntVirtMemMap.
Definition: introcore.c:2234
#define ALIGN_UP(x, a)
Definition: introdefs.h:164
uint8_t BYTE
Definition: intro_types.h:47
IMAGE_OPTIONAL_HEADER64 OptionalHeader
Definition: winpe.h:221
WINDOWS_GUEST * gWinGuest
Global variable holding the state of a Windows guest.
Definition: winguest.c:37
#define IC_TAG_EXPN
Export name buffer.
Definition: memtags.h:12
#define _In_
Definition: intro_sal.h:21
QWORD SystemCr3
The Cr3 used to map the kernel.
Definition: guests.h:211
#define INT_STATUS_SUCCESS
Definition: introstatus.h:54
#define MAX_FILE_ALIGNMENT
Definition: winpe.h:584
#define PAGE_REMAINING(addr)
Definition: pgtable.h:163
uint16_t WORD
Definition: intro_types.h:48
UINT8 CodeOffset
Definition: winpe.h:567
struct _IMAGE_DATA_DIRECTORY IMAGE_DATA_DIRECTORY
#define IMAGE_FILE_MACHINE_I386
Definition: winpe.h:480
#define IMAGE_OPTIONAL_HEADER_PE64
Definition: winpe.h:551
DWORD FileAlign
The number of bytes sections are aligned by on the disk.
Definition: winpe.c:35
#define UNW_FLAG_CHAININFO
Definition: winpe.h:413
UINT32 NumberOfRvaAndSizes
Definition: winpe.h:179
IMAGE_OPTIONAL_HEADER32 OptionalHeader
Definition: winpe.h:228
INTSTATUS IntPeGetRuntimeFunction(QWORD ImageBase, BYTE *ImageBaseBuffer, DWORD Rva, RUNTIME_FUNCTION *RuntimeFunction)
Parses the exception directory and gets the runtime function corresponding to the Rva...
Definition: winpe.c:2062
UINT32 VirtualAddress
Definition: winpe.h:98
#define INT_SUCCESS(Status)
Definition: introstatus.h:42
#define MAX_UNWIND_INFO_TRIES
The maximum number of iterations done while parsing unwind data.
Definition: winpe.c:15
QWORD SectionOffset
Offset of the first section header.
Definition: winpe.h:602
IMAGE_FILE_HEADER FileHeader
Definition: winpe.h:220
INTSTATUS IntPeFindExportByRvaInBuffer(QWORD ImageBase, BYTE *Buffer, DWORD BufferSize, DWORD Rva)
Check if the indicated Rva belongs to an exported function.
Definition: winpe.c:1223
INTSTATUS IntPeGetSectionHeadersByName(QWORD ImageBase, BYTE *ImageBaseBuffer, PCHAR Name, DWORD NumberOfSectionHeadersAllocated, QWORD Cr3, IMAGE_SECTION_HEADER *SectionHeaders, DWORD *NumberOfSectionHeadersFilled)
Return all the section headers matching the indicated Name.
Definition: winpe.c:942
uint32_t UINT32
Definition: intro_types.h:39
#define PAGE_OFFSET
Definition: pgtable.h:32
#define _In_bytecount_(expr)
Definition: intro_sal.h:42
struct _RUNTIME_FUNCTION RUNTIME_FUNCTION
INTSTATUS IntPeGetDirectory(QWORD ImageBase, BYTE *ImageBaseBuffer, DWORD DirectoryEntry, IMAGE_DATA_DIRECTORY *Directory)
Validate & return the indicated image data directory.
Definition: winpe.c:552
#define ERROR(fmt,...)
Definition: glue.h:62
#define HpAllocWithTag(Len, Tag)
Definition: glue.h:516
int INTSTATUS
The status data type.
Definition: introstatus.h:24
UINT32 AddressOfEntryPoint
Definition: winpe.h:151
#define INT_STATUS_NOT_FOUND
Definition: introstatus.h:284
UINT32 AddressOfNameOrdinals
Definition: winpe.h:339
#define ONE_KILOBYTE
Definition: introdefs.h:89
WORD Subsystem
The subsystem which the image belongs to.
Definition: winpe.c:33
INTSTATUS IntPeFindFunctionByPatternInBuffer(BYTE *Buffer, DWORD BufferSize, WIN_UNEXPORTED_FUNCTION_PATTERN *Pattern, BOOLEAN IgnoreSectionHint, DWORD *Rva)
Find a function using a pattern.
Definition: winpe.c:3044
struct _OPTIONAL_HEADER_INFO OPTIONAL_HEADER_INFO
Structure describing relevant fields extracted from the optional header.
DWORD SectionAlign
The number of bytes sections are aligned by at runtime.
Definition: winpe.c:34
DWORD SectionAlignment
Sections alignment.
Definition: winpe.h:604
#define MIN(a, b)
Definition: introdefs.h:146
struct _INTRO_UNWIND_INFO::@227 UnwindCode[]
Describes a pattern for a kernel function that is not exported.
Definition: winguest.h:85
UINT16 e_magic
Definition: winpe.h:40
32-bit selector.
Definition: glueiface.h:187
#define IMAGE_SIZEOF_SHORT_NAME
Definition: winpe.h:74
struct _IMAGE_OPTIONAL_HEADER IMAGE_OPTIONAL_HEADER32
UINT32 AddressOfEntryPoint
Definition: winpe.h:191
INTSTATUS IntPeFindExportByNameInBuffer(QWORD ImageBase, BYTE *Buffer, DWORD BufferSize, const char *Name, DWORD *ExportRva)
Find the export name a Rva lies in.
Definition: winpe.c:1586
UINT32 FileAlignment
Definition: winpe.h:161
UINT32 Signature
Definition: winpe.h:226
#define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR
Definition: winpe.h:35
UINT32 Signature
Definition: winpe.h:219
#define IMAGE_DOS_SIGNATURE
Definition: winpe.h:16
INTSTATUS IntKernVirtMemFetchDword(QWORD GuestVirtualAddress, DWORD *Data)
Reads 4 bytes from the guest kernel memory.
Definition: introcore.c:829
#define _Out_opt_
Definition: intro_sal.h:30
DWORD EntryPoint
Rva to the entry point of the image.
Definition: winpe.c:31
#define IG_CURRENT_VCPU
For APIs that take a VCPU number as a parameter, this can be used to specify that the current VCPU sh...
Definition: glueiface.h:324
#define IMAGE_OPTIONAL_HEADER_PE32
Definition: winpe.h:550
uint8_t * PBYTE
Definition: intro_types.h:47
UINT32 NumberOfFunctions
Definition: winpe.h:335
#define memzero(a, s)
Definition: introcrt.h:35
unsigned long long QWORD
Definition: intro_types.h:53
QWORD ImageBase
The base of the image.
Definition: winpe.c:32
struct _IMAGE_OPTIONAL_HEADER64 IMAGE_OPTIONAL_HEADER64
UINT32 UnwindData
Definition: winpe.h:377
INTSTATUS IntTranslateVirtualAddress(QWORD Gva, QWORD Cr3, QWORD *PhysicalAddress)
Translates a guest virtual address to a guest physical address.
Definition: introcore.c:1999
#define IN_RANGE_LEN(x, start, len)
Definition: introdefs.h:175
#define TRUE
Definition: intro_types.h:30
#define INT_STATUS_INVALID_PARAMETER_4
Definition: introstatus.h:71
UINT32 VirtualAddress
Definition: winpe.h:85
QWORD NumberOfSections
Number of sections.
Definition: winpe.h:603
INTSTATUS IntPeValidateHeader(QWORD ImageBase, BYTE *ImageBaseBuffer, DWORD ImageBaseBufferSize, INTRO_PE_INFO *PeInfo, QWORD Cr3)
Validates a PE header.
Definition: winpe.c:131
UINT32 NumberOfRvaAndSizes
Definition: winpe.h:213
#define HpFreeAndNullWithTag(Add, Tag)
Definition: glue.h:517
#define TRACE(fmt,...)
Definition: glue.h:58
#define INT_STATUS_INVALID_PARAMETER_5
Definition: introstatus.h:74
INTSTATUS IntPeParseUnwindData(QWORD ImageBase, BYTE *ImageBaseBuffer, RUNTIME_FUNCTION *RuntimeFunction, DWORD RipOffset, DWORD *ReservedStack, DWORD *BeginAddress, BOOLEAN *InterruptFunction, BOOLEAN *ExceptionFunction, BOOLEAN *HasFramePointer)
Parse the unwind data for the indicated function and return the prologue size.
Definition: winpe.c:2395
QWORD KernelVa
The guest virtual address at which the kernel image.
Definition: guests.h:283
INT32 e_lfanew
Definition: winpe.h:58
#define _In_reads_or_z_(expr)
Definition: intro_sal.h:43
UINT32 TimeDateStamp
Definition: winpe.h:66
#define IMAGE_NT_SIGNATURE
Definition: winpe.h:17
union _IMAGE_SECTION_HEADER::@214 Misc
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]
Definition: winpe.h:214
size_t strlcpy(char *dst, const char *src, size_t dest_size)
Definition: introcrt.c:1093
char * PCHAR
Definition: intro_types.h:56
#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES
Definition: winpe.h:103
#define WARNING(fmt,...)
Definition: glue.h:60
INTSTATUS IntPeGetSectionHeaderByRva(QWORD ImageBase, BYTE *ImageBaseBuffer, DWORD GuestRva, IMAGE_SECTION_HEADER *SectionHeader)
Given a relative virtual address, return the section header which describes the section the RVA lies ...
Definition: winpe.c:707
#define IMAGE_FILE_MACHINE_AMD64
Definition: winpe.h:505
INTSTATUS IntPeListSectionsHeaders(QWORD ImageBase, BYTE *ImageBuffer, DWORD ImageBufferSize, DWORD *FirstSectionOffset, DWORD *SectionCount)
Will get the offset to the first section header and the number of sections from the given module...
Definition: winpe.c:473
#define PAGE_SIZE
Definition: common.h:70
#define IMAGE_DIRECTORY_ENTRY_EXPORT
Definition: winpe.h:20
INTSTATUS IntPeGetRuntimeFunctionInBuffer(QWORD ImageBase, BYTE *Buffer, DWORD BufferSize, DWORD Rva, RUNTIME_FUNCTION *RuntimeFunction)
Parses the exception directory and gets the runtime function corresponding to the Rva...
Definition: winpe.c:2267
#define INT_STATUS_DATA_BUFFER_TOO_SMALL
Definition: introstatus.h:194
uint32_t DWORD
Definition: intro_types.h:49
UINT32 VirtualSize
Definition: winpe.h:83
DWORD KernelBufferSize
The size of the KernelBuffer.
Definition: winguest.h:852
UINT32 Characteristics
Definition: winpe.h:92
#define INT_STATUS_INVALID_PARAMETER_6
Definition: introstatus.h:77
#define MAX_UNWIND_CODES
Maximum number of unwind codes to check.
Definition: winpe.c:23
#define IMAGE_SCN_CNT_CODE
Definition: winpe.h:425
#define INT_STATUS_INVALID_OBJECT_TYPE
Definition: introstatus.h:145
__must_check INTSTATUS IntVirtMemMap(QWORD Gva, DWORD Length, QWORD Cr3, DWORD Flags, void **HostPtr)
Maps a guest virtual memory range inside Introcore virtual address space.
Definition: introcore.c:2134
MM Mm
Guest memory information, such as paging mode, system Cr3 value, etc.
Definition: guests.h:374
Structure describing relevant fields extracted from the optional header.
Definition: winpe.c:28
GUEST_STATE gGuest
The current guest state.
Definition: guests.c:50
#define _In_z_
Definition: intro_sal.h:17
UINT32 EndAddress
Definition: winpe.h:376
#define INT_STATUS_INVALID_DATA_TYPE
Definition: introstatus.h:139
INTSTATUS IntPeGetExportNameByRvaInBuffer(QWORD ImageBase, BYTE *Buffer, DWORD BufferSize, DWORD Rva, DWORD ExportNameSize, CHAR *ExportName)
Find the export name a Rva lies in.
Definition: winpe.c:1457
INTSTATUS IntKernVirtMemRead(QWORD KernelGva, DWORD Length, void *Buffer, DWORD *RetLength)
Reads data from a guest kernel virtual memory range.
Definition: introcore.c:674
INTSTATUS IntPeFindFunctionStart(QWORD ImageBase, BYTE *ImageBaseBuffer, DWORD Rva, DWORD *BeginAddress)
Find the start address of a function, given a Rva pointing inside of it.
Definition: winpe.c:3297
DWORD SizeOfImage
The size of the image.
Definition: winpe.c:30
IMAGE_FILE_HEADER FileHeader
Definition: winpe.h:227
INTSTATUS IntCr3Read(DWORD CpuNumber, QWORD *Cr3Value)
Reads the value of the guest CR3.
Definition: introcpu.c:415
static INTSTATUS IntPeValidateOptionalHeader(void *OptionalHeader, DWORD SizeOfOptionalHeader, OPTIONAL_HEADER_INFO *Info)
Validates and extracts info about the optional header.
Definition: winpe.c:41
#define _Out_writes_z_(expr)
Definition: intro_sal.h:37
__must_check INTSTATUS IntPhysMemMap(QWORD PhysAddress, DWORD Length, DWORD Flags, void **HostPtr)
Maps a guest physical address inside Introcore VA space.
Definition: glue.c:338
#define IC_TAG_SMALL_MZPE
Small MZPE.
Definition: memtags.h:141
struct _OPTIONAL_HEADER_INFO * POPTIONAL_HEADER_INFO
#define __likely(x)
Definition: common.h:63
BYTE * KernelBuffer
A buffer containing the entire kernel image.
Definition: winguest.h:851
#define MAX_NUMBER_OF_EXPORT_NAMES
We won&#39;t consider a valid image if it has more than MAX_NUMBER_OF_EXPORT_NAMES names.
Definition: winpe.c:12
BOOLEAN Image64
True if the image is considered 64 bits, False otherwise.
Definition: winpe.c:36
#define INT_STATUS_NOT_SUPPORTED
Definition: introstatus.h:287
UINT32 SizeOfRawData
Definition: winpe.h:86
UINT32 BeginAddress
Definition: winpe.h:375
UINT8 Name[IMAGE_SIZEOF_SHORT_NAME]
Definition: winpe.h:79
UINT32 AddressOfFunctions
Definition: winpe.h:337
INTSTATUS IntPeGetExportNameByRva(QWORD ImageBase, BYTE *ImageBaseBuffer, DWORD Rva, DWORD ExportNameSize, CHAR *ExportName)
Find the export name a Rva lies in.
Definition: winpe.c:1312
INTSTATUS IntPeParseUnwindDataInBuffer(QWORD ImageBase, BYTE *Buffer, DWORD BufferSize, RUNTIME_FUNCTION *RuntimeFunction, DWORD RipOffset, DWORD *ReservedStack, DWORD *BeginAddress, BOOLEAN *InterruptFunction, BOOLEAN *ExceptionFunction, BOOLEAN *HasFramePointer)
Parse the unwind data for the indicated function and return the prologue size.
Definition: winpe.c:2726
DWORD SizeOfImage
Size of the image.
Definition: winpe.h:599
BOOLEAN Image64Bit
True if the image is 64 bit.
Definition: winpe.h:596
#define IMAGE_DIRECTORY_ENTRY_EXCEPTION
Definition: winpe.h:23
INTSTATUS IntPeFindExportByOrdinal(QWORD ImageBase, BYTE *ImageBaseBuffer, DWORD Ordinal, DWORD *ExportRva)
Find an exported function using its ordinal.
Definition: winpe.c:1961
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]
Definition: winpe.h:180
UINT8 UnwindOp
Definition: winpe.h:568
char CHAR
Definition: intro_types.h:56
#define MAX_SIZE_OF_IMAGE
Definition: winpe.c:20
UINT32 SectionAlignment
Definition: winpe.h:160
INTSTATUS IntPhysMemUnmap(void **HostPtr)
Unmaps an address previously mapped with IntPhysMemMap.
Definition: glue.c:396
#define PAGE_MASK
Definition: pgtable.h:35
INTSTATUS IntPeGetSectionHeaderByIndex(QWORD ImageBase, BYTE *ImageBaseBuffer, DWORD Index, IMAGE_SECTION_HEADER *SectionHeader)
Return the section header located on position Index (0 based).
Definition: winpe.c:838
#define INT_STATUS_INVALID_PARAMETER_2
Definition: introstatus.h:65
INTSTATUS IntDecDecodeInstruction(IG_CS_TYPE CsType, QWORD Gva, void *Instrux)
Decode an instruction from the provided guest linear address.
Definition: decoder.c:180
INTSTATUS IntPeFindKernelExport(const char *Name, QWORD *ExportGva)
Find an export inside the NT kernel image.
Definition: winpe.c:1748
UINT16 SizeOfOptionalHeader
Definition: winpe.h:69
UINT16 Machine
Definition: winpe.h:64
#define INT_STATUS_INVALID_DATA_SIZE
Definition: introstatus.h:142
size_t SIZE_T
Definition: intro_types.h:60
#define FALSE
Definition: intro_types.h:34
#define ALIGN_DOWN(x, a)
Definition: introdefs.h:165
#define INT_STATUS_INSUFFICIENT_RESOURCES
Definition: introstatus.h:281
#define INT_STATUS_INVALID_PARAMETER_3
Definition: introstatus.h:68