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 = 0;
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, secOff = 0;
174  DWORD sectionAlign = 0, fileAlign = 0;
175  QWORD imageBase = 0;
176  OPTIONAL_HEADER_INFO opthdrInfo = { 0 };
177  QWORD e_lfanew;
178 
179  if ((ImageBase & PAGE_OFFSET) != 0)
180  {
181  ERROR("[ERROR] Image at 0x%016llx is not page-aligned\n", ImageBase);
183  }
184 
185  if (NULL != ImageBaseBuffer)
186  {
187  pBase = ImageBaseBuffer;
188  size = ImageBaseBufferSize;
189  }
190  else
191  {
192  if (ImageBase < ONE_KILOBYTE * 4)
193  {
194  ERROR("[ERROR] Can not map MZPE image at GVA 0x%016llx!\n", ImageBase);
196  }
197 
198  status = IntVirtMemMap(ImageBase, PAGE_SIZE, Cr3, 0, &pBase);
199  if (!INT_SUCCESS(status))
200  {
201  ERROR("[ERROR] Failed mapping VA 0x%016llx to host: 0x%08x\n", ImageBase, status);
202  return status;
203  }
204 
205  size = PAGE_SIZE;
206  }
207 
208  if (size < PAGE_SIZE)
209  {
211  goto leave;
212  }
213 
214  pDosHeader = (IMAGE_DOS_HEADER *)pBase;
215 
216  if (pDosHeader->e_magic != IMAGE_DOS_SIGNATURE)
217  {
219  goto leave;
220  }
221 
222  e_lfanew = (QWORD)pDosHeader->e_lfanew;
223  if (e_lfanew >= size)
224  {
226  goto leave;
227  }
228 
229  if (e_lfanew + sizeof(IMAGE_NT_HEADERS64) >= size)
230  {
232  goto leave;
233  }
234 
235  pNth32 = (IMAGE_NT_HEADERS32 *)(pBase + e_lfanew);
236  pNth64 = (IMAGE_NT_HEADERS64 *)(pBase + e_lfanew);
237 
238  // Validate the PE signature. Doesn't matter what we use here since only the OptionalHeader is different
240  {
241  if (pNth64->Signature != IMAGE_NT_SIGNATURE)
242  {
244  goto leave;
245  }
246 
247  // Safe cast, we know that e_lfanew is a valid RVA
248  secOff = (DWORD)e_lfanew + 4 + sizeof(IMAGE_FILE_HEADER) + pNth64->FileHeader.SizeOfOptionalHeader;
249  timeDateStamp = pNth64->FileHeader.TimeDateStamp;
250  numberOfSections = pNth64->FileHeader.NumberOfSections;
251  machine = pNth64->FileHeader.Machine;
252 
254  &opthdrInfo);
255  if (!INT_SUCCESS(status))
256  {
257  ERROR("[ERROR] Image at 0x%016llx has an invalid optional header!\n", ImageBase);
258  goto leave;
259  }
260 
261  if (!opthdrInfo.Image64)
262  {
263  WARNING("[WARNING] Image 0x%016llx is AMD64 but has a PE32 optional header! Will consider it 32 bits\n",
264  ImageBase);
265  }
266  }
267  else if (pNth32->FileHeader.Machine == IMAGE_FILE_MACHINE_I386)
268  {
269  if (pNth32->Signature != IMAGE_NT_SIGNATURE)
270  {
272  goto leave;
273  }
274 
275  // Safe cast, we know that e_lfanew is a valid RVA
276  secOff = (DWORD)e_lfanew + 4 + sizeof(IMAGE_FILE_HEADER) + pNth32->FileHeader.SizeOfOptionalHeader;
277  timeDateStamp = pNth32->FileHeader.TimeDateStamp;
278  numberOfSections = pNth32->FileHeader.NumberOfSections;
279  machine = pNth32->FileHeader.Machine;
280 
282  &opthdrInfo);
283  if (!INT_SUCCESS(status))
284  {
285  ERROR("[ERROR] Image at 0x%016llx has an invalid optional header!\n", ImageBase);
286  goto leave;
287  }
288 
289  if (opthdrInfo.Image64)
290  {
291  WARNING("[WARNING] Image 0x%016llx is I386 but has a PE64 optional header! Will consider it 64 bits\n",
292  ImageBase);
293  }
294  }
295  else
296  {
298  goto leave;
299  }
300 
301  image64 = opthdrInfo.Image64;
302  sizeOfImage = opthdrInfo.SizeOfImage;
303  entryPoint = opthdrInfo.EntryPoint;
304  imageBase = opthdrInfo.ImageBase;
305  subsystem = opthdrInfo.Subsystem;
306  sectionAlign = opthdrInfo.SectionAlign;
307  fileAlign = opthdrInfo.FileAlign;
308 
309  if (sizeOfImage > MAX_SIZE_OF_IMAGE)
310  {
311  ERROR("[ERROR] Image at 0x%016llx has SizeOfImage (0x%08x) larger than the maximum allowed (0x%016llx)\n",
312  ImageBase, sizeOfImage, MAX_SIZE_OF_IMAGE);
314  goto leave;
315  }
316 
317  if (sectionAlign == 0)
318  {
319  ERROR("[ERROR] Section alignment is 0 for image at 0x%016llx\n", ImageBase);
321  goto leave;
322  }
323 
324  if ((sectionAlign < fileAlign) || (fileAlign < MIN_FILE_ALIGNMENT || fileAlign > MAX_FILE_ALIGNMENT))
325  {
326  WARNING("[WARNING] Image alignments invalid. FileAlignment: 0x%08x, SectionAlignment: 0x%08x. "
327  "The image at 0x%016llx may have been tampered with\n",
328  fileAlign, sectionAlign, ImageBase);
329  }
330 
331  if (secOff + sizeof(IMAGE_SECTION_HEADER) * numberOfSections > size)
332  {
333  ERROR("[ERROR] Sections headers point out of the mapping. SectionOffset: 0x%08x; NrOfSections: %d; "
334  "MaxSize: 0x%08lx; MappingSize: 0x%08x. Image base: 0x%016llx\n",
335  secOff, numberOfSections, secOff + sizeof(IMAGE_SECTION_HEADER) * numberOfSections, size, ImageBase);
337  goto leave;
338  }
339 
340  if (entryPoint >= sizeOfImage)
341  {
342  ERROR("[ERROR] EntryPoint points out of the file. EntryPoint: 0x%08x; SizeOfImage: 0x%08x; "
343  "ImageBase: 0x%016llx\n",
344  entryPoint, sizeOfImage, ImageBase);
346  goto leave;
347  }
348 
349  // Make a final validation (so we know that the NT headers weren't moved outside the image)
350  if (e_lfanew >= sizeOfImage)
351  {
352  WARNING("[WARNING] e_lfanew 0x%llx points outside of image 0x%08x. Module 0x%016llx\n",
353  e_lfanew, sizeOfImage, ImageBase);
355  goto leave;
356  }
357 
358  // Validate section headers
359  pSec = (IMAGE_SECTION_HEADER *)(pBase + secOff);
360  for (DWORD i = 0; i < numberOfSections; i++, pSec++)
361  {
362  UINT32 secVirtSize = pSec->Misc.VirtualSize;
363  UINT32 secSizeOfRawData = pSec->SizeOfRawData;
364 
365  actualSectionSize = ROUND_UP(secVirtSize ? secVirtSize : secSizeOfRawData, sectionAlign);
366 
367  if (0 == actualSectionSize)
368  {
369  CHAR name[9];
370  memcpy(name, pSec->Name, sizeof(pSec->Name));
371  name[8] = 0;
372 
373  ERROR("[ERROR] Section %d (%s) for image at 0x%016llx has actual size 0. VirtualSize = 0x%08x "
374  "SizeOfRawData = 0x%08x Align = 0x%08x\n",
375  i, name, ImageBase, secVirtSize, secSizeOfRawData, sectionAlign);
377  goto leave;
378  }
379 
380  if (0 == pSec->VirtualAddress)
381  {
382  CHAR name[9];
383  memcpy(name, pSec->Name, sizeof(pSec->Name));
384  name[8] = 0;
385 
386  ERROR("[ERROR] Section starting at 0. Section name: %s\n", name);
388  goto leave;
389  }
390 
391  if (0 == secVirtSize && (0 == secSizeOfRawData || secSizeOfRawData > PAGE_SIZE))
392  {
393  CHAR name[9];
394  memcpy(name, pSec->Name, sizeof(pSec->Name));
395  name[8] = 0;
396 
397  ERROR("[ERROR] Section %d (%s) size is invalid for image at 0x%016llx. "
398  "VirtualSize: 0x%08x. SizeOfRawData: 0x%08x\n",
399  i, name, ImageBase, pSec->Misc.VirtualSize, pSec->SizeOfRawData);
401  goto leave;
402  }
403 
404  // Make sure the section fits within sizeOfImage
405  if ((pSec->VirtualAddress >= sizeOfImage) ||
406  (actualSectionSize > sizeOfImage) ||
407  (pSec->VirtualAddress + actualSectionSize > sizeOfImage))
408  {
409  CHAR name[9];
410  memcpy(name, pSec->Name, sizeof(pSec->Name));
411  name[8] = 0;
412 
413  ERROR("[ERROR] Section %d (%s) for image at 0x%016llx seems corrupted: "
414  "sizeOfImage = 0x%x, secStart = 0x%x, secSize = 0x%x, actualSecSize = 0x%08x\n",
415  i, name, ImageBase, sizeOfImage, pSec->VirtualAddress, pSec->Misc.VirtualSize, actualSectionSize);
417  goto leave;
418  }
419  }
420 
421  if (NULL != PeInfo)
422  {
423  PeInfo->Image64Bit = image64;
424  PeInfo->SectionOffset = secOff;
425  PeInfo->SizeOfImage = sizeOfImage;
426  PeInfo->TimeDateStamp = timeDateStamp;
427  PeInfo->EntryPoint = entryPoint;
428  PeInfo->NumberOfSections = numberOfSections;
429  PeInfo->Subsystem = subsystem;
430  PeInfo->ImageBase = imageBase;
431  PeInfo->SectionAlignment = sectionAlign;
432  PeInfo->Machine = machine;
433  }
434 
435  status = INT_STATUS_SUCCESS;
436 
437 leave:
438  if (NULL == ImageBaseBuffer)
439  {
440  IntVirtMemUnmap(&pBase);
441  }
442 
443  return status;
444 }
445 
446 
447 INTSTATUS
450  _In_opt_ BYTE *ImageBuffer,
451  _In_opt_ DWORD ImageBufferSize,
452  _Out_ DWORD *FirstSectionOffset,
453  _Out_ DWORD *SectionCount
454  )
468 {
469  INTSTATUS status = INT_STATUS_SUCCESS;
470  BYTE *pBase;
471  DWORD size = 0;
472  INTRO_PE_INFO peInfo = {0};
473 
474  if (NULL == FirstSectionOffset)
475  {
477  }
478 
479  if (NULL == SectionCount)
480  {
482  }
483 
484  if (NULL != ImageBuffer)
485  {
486  pBase = ImageBuffer;
487  size = ImageBufferSize;
488  }
489  else
490  {
491  status = IntVirtMemMap(ImageBase, PAGE_SIZE, gGuest.Mm.SystemCr3, 0, &pBase);
492  if (!INT_SUCCESS(status))
493  {
494  ERROR("[ERROR] IntVirtMemMap failed: 0x%08x\n", status);
495  return status;
496  }
497 
498  size = PAGE_SIZE;
499  }
500 
501  status = IntPeValidateHeader(ImageBase, pBase, size, &peInfo, 0);
502  if (!INT_SUCCESS(status))
503  {
504  ERROR("[ERROR] IntPeValidateHeader failed: 0x%08x\n", status);
505  goto cleanup_and_exit;
506  }
507 
508  *FirstSectionOffset = (DWORD)peInfo.SectionOffset;
509  *SectionCount = (DWORD)peInfo.NumberOfSections;
510 
511 cleanup_and_exit:
512  if (NULL == ImageBuffer)
513  {
514  // Don't override the status from IntPeValidateHeader
515  INTSTATUS status2 = IntVirtMemUnmap(&pBase);
516  if (!INT_SUCCESS(status2))
517  {
518  ERROR("[ERROR] IntVirtMemUnmap failed: 0x%08x\n", status2);
519  }
520  }
521 
522  return status;
523 }
524 
525 
526 INTSTATUS
529  _In_opt_ BYTE *ImageBaseBuffer,
530  _In_ DWORD DirectoryEntry,
531  _Out_ IMAGE_DATA_DIRECTORY *Directory
532  )
546 
547 {
548  INTSTATUS status;
549  BOOLEAN unmapNtHeaders;
550  BYTE *map;
551  IMAGE_DOS_HEADER *pDosHeader;
552  IMAGE_NT_HEADERS64 *pNth64;
553  IMAGE_NT_HEADERS32 *pNth32;
554  INTRO_PE_INFO peInfo = {0};
555 
556  if (DirectoryEntry > IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR)
557  {
559  }
560 
561  if (NULL == Directory)
562  {
564  }
565 
566  unmapNtHeaders = FALSE;
567 
568  if (NULL != ImageBaseBuffer)
569  {
570  map = ImageBaseBuffer;
571  }
572  else
573  {
574  status = IntVirtMemMap(ImageBase, PAGE_SIZE, 0, 0, &map);
575  if (!INT_SUCCESS(status))
576  {
577  ERROR("[ERROR] Failed mapping VA 0x%016llx to host: 0x%08x\n", ImageBase, status);
578  return status;
579  }
580  }
581 
582  // First thing, validate the buffer
583  status = IntPeValidateHeader(ImageBase, map, PAGE_SIZE, &peInfo, 0);
584  if (!INT_SUCCESS(status))
585  {
586  goto leave;
587  }
588 
589  pDosHeader = (IMAGE_DOS_HEADER *)map;
590 
591  if (peInfo.Image64Bit)
592  {
593  QWORD e_lfanew = pDosHeader->e_lfanew;
594 
595  if (e_lfanew + sizeof(IMAGE_NT_HEADERS64) <= PAGE_SIZE)
596  {
597  pNth64 = (IMAGE_NT_HEADERS64 *)(map + e_lfanew);
598  }
599  else
600  {
601  status = IntVirtMemMap(ImageBase + e_lfanew, sizeof(*pNth64), 0, 0, &pNth64);
602  if (!INT_SUCCESS(status))
603  {
604  ERROR("[ERROR] Failed mapping VA 0x%016llx to host: 0x%08x\n", ImageBase + e_lfanew, status);
605  goto leave;
606  }
607 
608  unmapNtHeaders = TRUE;
609  }
610 
611  Directory->Size = pNth64->OptionalHeader.DataDirectory[DirectoryEntry].Size;
612  Directory->VirtualAddress = pNth64->OptionalHeader.DataDirectory[DirectoryEntry].VirtualAddress;
613  }
614  else
615  {
616  QWORD e_lfanew = pDosHeader->e_lfanew;
617 
618  if (e_lfanew + sizeof(IMAGE_NT_HEADERS32) <= PAGE_SIZE)
619  {
620  pNth32 = (IMAGE_NT_HEADERS32 *)(map + e_lfanew);
621  }
622  else
623  {
624  status = IntVirtMemMap(ImageBase + e_lfanew, sizeof(*pNth32), 0, 0, &pNth32);
625  if (!INT_SUCCESS(status))
626  {
627  ERROR("[ERROR] Failed mapping VA 0x%016llx to host: 0x%08x\n", ImageBase + e_lfanew, status);
628  goto leave;
629  }
630 
631  unmapNtHeaders = TRUE;
632  }
633 
634  Directory->Size = pNth32->OptionalHeader.DataDirectory[DirectoryEntry].Size;
635  Directory->VirtualAddress = pNth32->OptionalHeader.DataDirectory[DirectoryEntry].VirtualAddress;
636  }
637 
638  if (Directory->VirtualAddress == 0 || Directory->Size == 0)
639  {
640  status = INT_STATUS_NOT_FOUND;
641  goto leave;
642  }
643 
644  // Validate the entry and return appropriate status when it's invalid
645  if ((QWORD)Directory->Size + Directory->VirtualAddress > peInfo.SizeOfImage)
646  {
648  goto leave;
649  }
650 
651  status = INT_STATUS_SUCCESS;
652 
653 leave:
654  if (unmapNtHeaders)
655  {
656  if (peInfo.Image64Bit)
657  {
658  IntVirtMemUnmap(&pNth64);
659  }
660  else
661  {
662  IntVirtMemUnmap(&pNth32);
663  }
664  }
665 
666  if (NULL == ImageBaseBuffer)
667  {
668  IntVirtMemUnmap(&map);
669  }
670 
671  if (!INT_SUCCESS(status))
672  {
673  Directory->VirtualAddress = 0;
674  Directory->Size = 0;
675  }
676 
677  return status;
678 }
679 
680 
681 INTSTATUS
684  _In_opt_ BYTE *ImageBaseBuffer,
685  _In_ DWORD GuestRva,
686  _Out_ IMAGE_SECTION_HEADER *SectionHeader
687  )
702 {
703  INTSTATUS status;
704  BOOLEAN unmapSecHeaders;
705  DWORD i;
706  IMAGE_SECTION_HEADER *pSecHeader;
707  BYTE *map;
708  INTRO_PE_INFO peInfo = {0};
709 
710  if (NULL == SectionHeader)
711  {
713  }
714 
715  if (NULL != ImageBaseBuffer)
716  {
717  map = ImageBaseBuffer;
718  }
719  else
720  {
721  status = IntVirtMemMap(ImageBase, PAGE_SIZE, 0, 0, &map);
722  if (!INT_SUCCESS(status))
723  {
724  ERROR("[ERROR] Failed mapping VA 0x%016llx to host: 0x%08x\n", ImageBase, status);
725  return status;
726  }
727  }
728 
729  unmapSecHeaders = FALSE;
730  pSecHeader = NULL;
731 
732  // First thing, validate the buffer
733  status = IntPeValidateHeader(ImageBase, map, PAGE_SIZE, &peInfo, 0);
734  if (!INT_SUCCESS(status))
735  {
736  goto leave;
737  }
738 
739  if (peInfo.SectionOffset + (peInfo.NumberOfSections * sizeof(IMAGE_SECTION_HEADER)) <= PAGE_SIZE)
740  {
741  // We are in the same page, so it's safe to use this
742  pSecHeader = (IMAGE_SECTION_HEADER *)((BYTE *)map + peInfo.SectionOffset);
743  }
744 
745  // See that the sections aren't outside of the image
746  if (peInfo.SectionOffset + peInfo.NumberOfSections * sizeof(IMAGE_SECTION_HEADER) > peInfo.SizeOfImage)
747  {
749  goto leave;
750  }
751  else if (peInfo.NumberOfSections > ((PAGE_SIZE * 2) / sizeof(IMAGE_SECTION_HEADER))) // ~227 sections
752  {
753  // Let's assume that the secCount == 65536, that will make us map 537 pages,
754  // which on XEN means allocating 537 pages + memcpy, and Napoca will
755  // probably just return INT_STATUS_INSUFFICIENT_RESOURCES. So, if secCount is bigger
756  // than two pages, then search the given RVA only in two pages.
757  WARNING("[WARNING] Image has %lld sections, which is a bit too much!\n", peInfo.NumberOfSections);
758  peInfo.NumberOfSections = (PAGE_SIZE * 2) / sizeof(IMAGE_SECTION_HEADER);
759  }
760 
761  // If the pSecHeader is NULL, then the sections doesn't end in the first page
762  if (NULL == pSecHeader)
763  {
764  status = IntVirtMemMap(ImageBase + peInfo.SectionOffset,
765  (DWORD)(peInfo.NumberOfSections * sizeof(*pSecHeader)), 0, 0, &pSecHeader);
766  if (!INT_SUCCESS(status))
767  {
768  ERROR("[ERROR] Failed mapping VA 0x%016llx to host with size %llu: 0x%08x\n",
769  ImageBase + peInfo.SectionOffset, peInfo.NumberOfSections * sizeof(IMAGE_SECTION_HEADER), status);
770  goto leave;
771  }
772 
773  unmapSecHeaders = TRUE;
774  }
775 
776  for (i = 0; i < peInfo.NumberOfSections; i++)
777  {
778  DWORD secEnd = pSecHeader[i].VirtualAddress +
779  ALIGN_UP((QWORD)pSecHeader[i].Misc.VirtualSize, peInfo.SectionAlignment);
780 
781  if (GuestRva >= pSecHeader[i].VirtualAddress &&
782  GuestRva < secEnd)
783  {
784  memcpy(SectionHeader, &pSecHeader[i], sizeof(IMAGE_SECTION_HEADER));
785  status = INT_STATUS_SUCCESS;
786  goto leave;
787  }
788  }
789 
790  status = INT_STATUS_NOT_FOUND;
791 
792 leave:
793  if (unmapSecHeaders)
794  {
795  IntVirtMemUnmap(&pSecHeader);
796  }
797 
798  if (NULL == ImageBaseBuffer)
799  {
800  IntVirtMemUnmap(&map);
801  }
802 
803  if (!INT_SUCCESS(status))
804  {
805  memzero(SectionHeader, sizeof(*SectionHeader));
806  }
807 
808  return status;
809 }
810 
811 
812 INTSTATUS
815  _In_opt_ BYTE *ImageBaseBuffer,
816  _In_ DWORD Index,
817  _Out_ IMAGE_SECTION_HEADER *SectionHeader
818  )
832 {
833  INTSTATUS status;
834  IMAGE_SECTION_HEADER *pSecHeader;
835  BYTE *map;
836  INTRO_PE_INFO peInfo = {0};
837 
838  if (NULL == SectionHeader)
839  {
841  }
842 
843  if (NULL != ImageBaseBuffer)
844  {
845  map = ImageBaseBuffer;
846  }
847  else
848  {
849  status = IntVirtMemMap(ImageBase, PAGE_SIZE, 0, 0, &map);
850  if (!INT_SUCCESS(status))
851  {
852  ERROR("[ERROR] Failed mapping VA 0x%016llx to host: 0x%08x\n", ImageBase, status);
853  return status;
854  }
855  }
856 
857  pSecHeader = NULL;
858 
859  // First thing, validate the buffer
860  status = IntPeValidateHeader(ImageBase, map, PAGE_SIZE, &peInfo, 0);
861  if (!INT_SUCCESS(status))
862  {
863  goto leave;
864  }
865 
866  if (peInfo.SectionOffset + (Index * sizeof(IMAGE_SECTION_HEADER)) <= PAGE_SIZE)
867  {
868  // We are in the same page, so it's safe to use this
869  pSecHeader = (IMAGE_SECTION_HEADER *)((BYTE *)map + peInfo.SectionOffset);
870  }
871 
872  // See that the sections aren't outside of the image
873  if (peInfo.SectionOffset + peInfo.NumberOfSections * sizeof(IMAGE_SECTION_HEADER) > peInfo.SizeOfImage)
874  {
876  goto leave;
877  }
878 
879  if (Index >= peInfo.NumberOfSections)
880  {
881  status = INT_STATUS_NOT_FOUND;
882  goto leave;
883  }
884 
885  // If the pSecHeader is NULL, then the section we are searching is not in the first page
886  if (NULL == pSecHeader)
887  {
888  status = IntKernVirtMemRead(ImageBase + peInfo.SectionOffset + Index * sizeof(IMAGE_SECTION_HEADER),
889  sizeof(IMAGE_SECTION_HEADER),
890  SectionHeader,
891  NULL);
892  if (!INT_SUCCESS(status))
893  {
894  ERROR("[ERROR] Failed reading section header from GVA 0x%016llx: 0x%08x\n",
895  ImageBase + peInfo.SectionOffset + Index * sizeof(IMAGE_SECTION_HEADER), status);
896  goto leave;
897  }
898  }
899  else
900  {
901  memcpy(SectionHeader, &pSecHeader[Index], sizeof(IMAGE_SECTION_HEADER));
902  }
903 
904  status = INT_STATUS_SUCCESS;
905 
906 leave:
907  if (NULL == ImageBaseBuffer)
908  {
909  IntVirtMemUnmap(&map);
910  }
911 
912  return status;
913 }
914 
915 
916 INTSTATUS
919  _In_opt_ BYTE *ImageBaseBuffer,
920  _In_reads_or_z_(8) PCHAR Name,
921  _In_ DWORD NumberOfSectionHeadersAllocated,
922  _In_ QWORD Cr3,
923  _Out_ IMAGE_SECTION_HEADER *SectionHeaders,
924  _Out_opt_ DWORD *NumberOfSectionHeadersFilled
925  )
943 {
944  INTSTATUS status;
945  BOOLEAN unmapSecHeaders;
946  DWORD numberSectionsFound;
947  BYTE *map;
948  IMAGE_SECTION_HEADER *pSecHeader;
949  INTRO_PE_INFO peInfo = {0};
950  SIZE_T cmpSize;
951 
952  if (0 == NumberOfSectionHeadersAllocated)
953  {
955  }
956 
957  if (NULL == SectionHeaders)
958  {
960  }
961 
962  if (NULL != ImageBaseBuffer)
963  {
964  map = ImageBaseBuffer;
965  }
966  else
967  {
968  status = IntVirtMemMap(ImageBase, PAGE_SIZE, Cr3, 0, &map);
969  if (!INT_SUCCESS(status))
970  {
971  ERROR("[ERROR] Failed mapping VA 0x%016llx to host: 0x%08x\n", ImageBase, status);
972  return status;
973  }
974  }
975 
976  unmapSecHeaders = FALSE;
977  pSecHeader = NULL;
978  numberSectionsFound = 0;
979 
980  // First thing, validate the buffer
981  status = IntPeValidateHeader(ImageBase, map, PAGE_SIZE, &peInfo, 0);
982  if (!INT_SUCCESS(status))
983  {
984  goto leave;
985  }
986 
987  if (peInfo.SectionOffset + (peInfo.NumberOfSections * sizeof(IMAGE_SECTION_HEADER)) <= PAGE_SIZE)
988  {
989  // We are in the same page, so it's safe to use this
990  pSecHeader = (IMAGE_SECTION_HEADER *)((BYTE *)map + peInfo.SectionOffset);
991  }
992 
993  // See that the sections aren't outside of the image
994  if (peInfo.SectionOffset + peInfo.NumberOfSections * sizeof(IMAGE_SECTION_HEADER) > peInfo.SizeOfImage)
995  {
997  goto leave;
998  }
999  else if (peInfo.NumberOfSections > ((PAGE_SIZE * 2) / sizeof(IMAGE_SECTION_HEADER))) // ~227 sections
1000  {
1001  // Let's assume that the secCount == 65536, that will make us map 537 pages,
1002  // which on XEN means allocating 537 pages + memcpy, and Napoca will
1003  // probably just return INT_STATUS_INSUFFICIENT_RESOURCES. So, if secCount is bigger
1004  // than two pages, then search the given RVA only in two pages.
1005  WARNING("[WARNING] Image has %lld sections, which is a bit too much!\n", peInfo.NumberOfSections);
1006  peInfo.NumberOfSections = (PAGE_SIZE * 2) / sizeof(IMAGE_SECTION_HEADER);
1007  }
1008 
1009  // If the pSecHeader is NULL, then the sections doesn't end in the first page
1010  if (NULL == pSecHeader)
1011  {
1012  status = IntVirtMemMap(ImageBase + peInfo.SectionOffset,
1013  (DWORD)(peInfo.NumberOfSections * sizeof(IMAGE_SECTION_HEADER)), 0, 0, &pSecHeader);
1014  if (!INT_SUCCESS(status))
1015  {
1016  ERROR("[ERROR] Failed mapping VA 0x%016llx to host with size 0x%016llx: 0x%08x\n",
1017  ImageBase + peInfo.SectionOffset, peInfo.NumberOfSections * sizeof(IMAGE_SECTION_HEADER), status);
1018  goto leave;
1019  }
1020 
1021  unmapSecHeaders = TRUE;
1022  }
1023 
1024  // We use memcmp, so add 1 to also compare the NULL terminator
1025  cmpSize = strlen(Name) + 1;
1026  // Section names are not guaranteed to be NULL terminated, so if we go past that, limit the compare size to 8
1027  cmpSize = MIN(cmpSize, IMAGE_SIZEOF_SHORT_NAME);
1028  for (size_t i = 0; i < peInfo.NumberOfSections; i++)
1029  {
1030  if (memcmp(Name, pSecHeader[i].Name, cmpSize) == 0)
1031  {
1032  memcpy(&SectionHeaders[numberSectionsFound], &pSecHeader[i], sizeof(IMAGE_SECTION_HEADER));
1033 
1034  numberSectionsFound++;
1035 
1036  if (numberSectionsFound == NumberOfSectionHeadersAllocated)
1037  {
1038  break;
1039  }
1040  }
1041  }
1042 
1043  if (0 == numberSectionsFound)
1044  {
1045  status = INT_STATUS_NOT_FOUND;
1046  }
1047  else
1048  {
1049  status = INT_STATUS_SUCCESS;
1050  }
1051 
1052  if (NULL != NumberOfSectionHeadersFilled)
1053  {
1054  *NumberOfSectionHeadersFilled = numberSectionsFound;
1055  }
1056 
1057 leave:
1058  if (unmapSecHeaders)
1059  {
1060  IntVirtMemUnmap(&pSecHeader);
1061  }
1062 
1063  if (NULL == ImageBaseBuffer)
1064  {
1065  IntVirtMemUnmap(&map);
1066  }
1067 
1068  if (!INT_SUCCESS(status))
1069  {
1070  memzero(SectionHeaders, sizeof(*SectionHeaders) * NumberOfSectionHeadersAllocated);
1071  }
1072 
1073  return status;
1074 }
1075 
1076 
1077 INTSTATUS
1079  _In_ QWORD ImageBase,
1080  _In_opt_ BYTE *ImageBaseBuffer,
1081  _In_ DWORD Rva
1082  )
1097 {
1098  INTSTATUS status;
1099  DWORD beginRva, i, size;
1100  QWORD address;
1102  IMAGE_EXPORT_DIRECTORY exportDir;
1103  BOOLEAN found;
1104  BYTE *functions;
1105  BYTE *map;
1106 
1107  found = FALSE;
1108 
1109  if (NULL != ImageBaseBuffer)
1110  {
1111  map = ImageBaseBuffer;
1112  }
1113  else
1114  {
1115  status = IntVirtMemMap(ImageBase, PAGE_SIZE, 0, 0, &map);
1116  if (!INT_SUCCESS(status))
1117  {
1118  ERROR("[ERROR] Failed mapping VA 0x%016llx to host: 0x%08x\n", ImageBase, status);
1119  return status;
1120  }
1121  }
1122 
1123  // Get the export directory, if present
1124  status = IntPeGetDirectory(ImageBase, map, IMAGE_DIRECTORY_ENTRY_EXPORT, &dir);
1125  if (!INT_SUCCESS(status))
1126  {
1127  goto leave;
1128  }
1129 
1130  // Find the start of the function
1131  status = IntPeFindFunctionStart(ImageBase, map, Rva, &beginRva);
1132  if (!INT_SUCCESS(status))
1133  {
1134  goto leave;
1135  }
1136 
1137  // Read the export directory
1138  status = IntKernVirtMemRead(ImageBase + dir.VirtualAddress, sizeof(IMAGE_EXPORT_DIRECTORY), &exportDir, NULL);
1139  if (!INT_SUCCESS(status))
1140  {
1141  goto leave;
1142  }
1143 
1144  // Parse the export directory and find the function corresponding to this (if any)
1145  // Cap the number of searched symbols to MAX_NUMBER_OF_EXPORT_NAMES
1146  size = MIN(exportDir.NumberOfFunctions, MAX_NUMBER_OF_EXPORT_NAMES) * 4;
1147  address = ImageBase + exportDir.AddressOfFunctions;
1148 
1149  for (i = 0; i < size; i += PAGE_SIZE)
1150  {
1151  DWORD parsed = 0;
1152  DWORD mappingSize = (i < (size & PAGE_MASK)) ? PAGE_SIZE : (address & PAGE_OFFSET);
1153 
1154  status = IntVirtMemMap(address & PAGE_MASK, PAGE_SIZE, 0, 0, &functions);
1155  if (!INT_SUCCESS(status))
1156  {
1157  ERROR("[ERROR] Failed mapping AddressOfFunctions 0x%016llx (0x%016llx + 0x%08x): 0x%08x\n",
1158  address & PAGE_MASK, ImageBase, exportDir.AddressOfFunctions, status);
1159  goto leave;
1160  }
1161 
1162  // has meaning only on the first map, on the rest it's already aligned
1163  functions += address & PAGE_OFFSET;
1164 
1165  while (parsed + mappingSize < PAGE_SIZE)
1166  {
1167  // We don't care about forwarded addresses (they still are exported)
1168  if (*(DWORD *)(functions + parsed) == beginRva)
1169  {
1170  found = TRUE;
1171  break;
1172  }
1173  parsed += 4;
1174  }
1175 
1176  functions -= address & PAGE_OFFSET;
1177  address += mappingSize; // this will align the address
1178 
1179  IntVirtMemUnmap(&functions);
1180  }
1181 
1182  if (!found)
1183  {
1184  status = INT_STATUS_NOT_FOUND;
1185  }
1186 
1187 leave:
1188  if (NULL == ImageBaseBuffer)
1189  {
1190  IntVirtMemUnmap(&map);
1191  }
1192 
1193  return status;
1194 }
1195 
1196 
1197 INTSTATUS
1199  _In_ QWORD ImageBase,
1200  _In_ BYTE *Buffer,
1201  _In_ DWORD BufferSize,
1202  _In_ DWORD Rva
1203  )
1219 {
1220  INTSTATUS status;
1221  DWORD beginRva;
1222  QWORD size;
1223  IMAGE_EXPORT_DIRECTORY exportDir;
1225 
1226  // Firstly, validate that headers are valid before going through EAT
1227  status = IntPeValidateHeader(ImageBase, Buffer, BufferSize, NULL, 0);
1228  if (!INT_SUCCESS(status))
1229  {
1230  return status;
1231  }
1232 
1233  // Find the start of the function
1234  status = IntPeFindFunctionStart(ImageBase, Buffer, Rva, &beginRva);
1235  if (!INT_SUCCESS(status))
1236  {
1237  return status;
1238  }
1239 
1240  status = IntPeGetDirectory(ImageBase, Buffer, IMAGE_DIRECTORY_ENTRY_EXPORT, &dir);
1241  if (!INT_SUCCESS(status))
1242  {
1243  return status;
1244  }
1245 
1246  if ((QWORD)dir.VirtualAddress + dir.Size > BufferSize)
1247  {
1249  }
1250 
1251  // We want to make sure that we don't overflow the buffer even for some special crafted size
1252  // (e.g. dir.VirtualAddress = BufferSize - 1, dir.Size = 1, but dir.VirtualAddress + sizeof(IMAGE_EXPORT_DIRECTORY)
1253  // is > BufferSize)
1254  if ((QWORD)dir.VirtualAddress + sizeof(IMAGE_EXPORT_DIRECTORY) > BufferSize)
1255  {
1257  }
1258 
1259  exportDir = *(IMAGE_EXPORT_DIRECTORY *)(Buffer + dir.VirtualAddress);
1260 
1261  size = (QWORD)MIN(exportDir.NumberOfFunctions, MAX_NUMBER_OF_EXPORT_NAMES) * 4;
1262 
1263  if (size > BufferSize)
1264  {
1266  }
1267 
1268  for (DWORD i = 0; i < size; i += 4)
1269  {
1270  if ((QWORD)exportDir.AddressOfFunctions + i + sizeof(DWORD) > BufferSize)
1271  {
1272  continue;
1273  }
1274 
1275  // We don't care about forwarded addresses (they still are exported)
1276  if (*(DWORD *)(Buffer + exportDir.AddressOfFunctions + i) == beginRva)
1277  {
1278  return INT_STATUS_SUCCESS;
1279  }
1280  }
1281 
1282  return INT_STATUS_NOT_FOUND;
1283 }
1284 
1285 
1286 INTSTATUS
1288  _In_ QWORD ImageBase,
1289  _In_opt_ BYTE *ImageBaseBuffer,
1290  _In_ DWORD Rva,
1291  _In_ DWORD ExportNameSize,
1292  _Out_writes_z_(ExportNameSize) CHAR *ExportName
1293  )
1309 {
1310  INTSTATUS status;
1311  DWORD i;
1312  BOOLEAN found;
1313  IMAGE_EXPORT_DIRECTORY exportDir;
1315  BYTE *map;
1316 
1317  if (0 == ExportNameSize)
1318  {
1320  }
1321 
1322  if (NULL == ExportName)
1323  {
1325  }
1326 
1327  if (NULL != ImageBaseBuffer)
1328  {
1329  map = ImageBaseBuffer;
1330  }
1331  else
1332  {
1333  status = IntVirtMemMap(ImageBase, PAGE_SIZE, 0, 0, &map);
1334  if (!INT_SUCCESS(status))
1335  {
1336  ERROR("[ERROR] Failed mapping VA 0x%016llx to host: 0x%08x\n", ImageBase, status);
1337  return status;
1338  }
1339  }
1340 
1341  // Get the export directory, if present
1342  status = IntPeGetDirectory(ImageBase, map, IMAGE_DIRECTORY_ENTRY_EXPORT, &dir);
1343  if (!INT_SUCCESS(status))
1344  {
1345  goto _cleanup_and_leave;
1346  }
1347 
1348  status = IntKernVirtMemRead(ImageBase + dir.VirtualAddress, sizeof(IMAGE_EXPORT_DIRECTORY), &exportDir, NULL);
1349  if (!INT_SUCCESS(status))
1350  {
1351  if (status != INT_STATUS_PAGE_NOT_PRESENT)
1352  {
1353  // Export directory is pageable so it CAN happen to not be present, so don't spam
1354  WARNING("[WARNING] Failed to read the export directory of 0x%016llx located at 0x%016llx: 0x%08x\n",
1355  ImageBase, ImageBase + dir.VirtualAddress, status);
1356  }
1357  goto _cleanup_and_leave;
1358  }
1359 
1360  found = FALSE;
1361 
1362  if (exportDir.NumberOfNames > MAX_NUMBER_OF_EXPORT_NAMES)
1363  {
1364  ERROR("[ERROR] Number of names %d exceeds %lu!\n", exportDir.NumberOfNames, MAX_NUMBER_OF_EXPORT_NAMES);
1366  goto _cleanup_and_leave;
1367  }
1368 
1369  for (i = 0; i < exportDir.NumberOfNames; i++)
1370  {
1371  DWORD exportOrdinal, exportRva, namePointer, retLen;
1372 
1373  // Get this name's ordinal; note that ordinals are 2 bytes in size
1374  status = IntKernVirtMemFetchDword(ImageBase + exportDir.AddressOfNameOrdinals + i * 2ull, &exportOrdinal);
1375  if (!INT_SUCCESS(status))
1376  {
1377  continue;
1378  }
1379  exportOrdinal &= 0xFFFF;
1380 
1381  // Read the export RVA
1382  status = IntKernVirtMemFetchDword(ImageBase + exportDir.AddressOfFunctions + exportOrdinal * 4ull, &exportRva);
1383  if (!INT_SUCCESS(status))
1384  {
1385  continue;
1386  }
1387 
1388  if (exportRva != Rva)
1389  {
1390  continue;
1391  }
1392 
1393  // Get the name pointer (RVA)
1394  status = IntKernVirtMemFetchDword(ImageBase + exportDir.AddressOfNames + i * 4ull, &namePointer);
1395  if (!INT_SUCCESS(status))
1396  {
1397  goto _cleanup_and_leave;
1398  }
1399 
1400  // Read the name
1401  status = IntKernVirtMemRead(ImageBase + namePointer, ExportNameSize, ExportName, &retLen);
1402  if (!INT_SUCCESS(status))
1403  {
1404  goto _cleanup_and_leave;
1405  }
1406 
1407  // retLen will always have the same value as ExportNameSize in this case
1408  ExportName[retLen - 1] = 0;
1409 
1410  found = TRUE;
1411  status = INT_STATUS_SUCCESS;
1412  break;
1413  }
1414 
1415  if (!found)
1416  {
1417  status = INT_STATUS_NOT_FOUND;
1418  goto _cleanup_and_leave;
1419  }
1420 
1421 _cleanup_and_leave:
1422  if (ImageBaseBuffer == NULL)
1423  {
1424  IntVirtMemUnmap(&map);
1425  }
1426 
1427  return status;
1428 }
1429 
1430 
1431 INTSTATUS
1433  _In_ QWORD ImageBase,
1434  _In_ BYTE *Buffer,
1435  _In_ DWORD BufferSize,
1436  _In_ DWORD Rva,
1437  _In_ DWORD ExportNameSize,
1438  _Out_writes_z_(ExportNameSize) CHAR *ExportName
1439  )
1455 {
1456  INTSTATUS status;
1457  DWORD i;
1458  IMAGE_EXPORT_DIRECTORY exportDir;
1460 
1461  if (NULL == Buffer)
1462  {
1464  }
1465 
1466  if (0 == BufferSize)
1467  {
1469  }
1470 
1471  if (0 == ExportNameSize)
1472  {
1474  }
1475 
1476  if (NULL == ExportName)
1477  {
1479  }
1480 
1481  // Firstly, validate that headers are valid before going through EAT
1482  status = IntPeValidateHeader(ImageBase, Buffer, BufferSize, NULL, 0);
1483  if (!INT_SUCCESS(status))
1484  {
1485  return status;
1486  }
1487 
1488  // Get the export directory, if present
1489  status = IntPeGetDirectory(ImageBase, Buffer, IMAGE_DIRECTORY_ENTRY_EXPORT, &dir);
1490  if (!INT_SUCCESS(status))
1491  {
1492  return status;
1493  }
1494 
1495  if ((QWORD)dir.VirtualAddress + dir.Size > BufferSize)
1496  {
1498  }
1499 
1500  // We want to make sure that we don't overflow the buffer even for some special crafted size
1501  // (e.g. dir.VirtualAddress = BufferSize - 1, dir.Size = 1, but dir.VirtualAddress +
1502  // sizeof(IMAGE_EXPORT_DIRECTORY) is > BufferSize)
1503  if ((QWORD)dir.VirtualAddress + sizeof(IMAGE_EXPORT_DIRECTORY) > BufferSize)
1504  {
1506  }
1507 
1508  // Read the export directory
1509  exportDir = *(IMAGE_EXPORT_DIRECTORY *)(Buffer + dir.VirtualAddress);
1510  if (exportDir.NumberOfNames > MAX_NUMBER_OF_EXPORT_NAMES)
1511  {
1512  ERROR("[ERROR] Number of names %d exceeds %lu!\n", exportDir.NumberOfNames, MAX_NUMBER_OF_EXPORT_NAMES);
1514  }
1515 
1516  for (i = 0; i < exportDir.NumberOfNames; i++)
1517  {
1518  DWORD exportOrdinal, exportRva, namePointer;
1519 
1520  // Get this name's ordinal; note that ordinals are 2 bytes in size
1521  if ((QWORD)exportDir.AddressOfNameOrdinals + i * 2ull + sizeof(DWORD) > BufferSize)
1522  {
1523  continue;
1524  }
1525 
1526  exportOrdinal = (*(DWORD *)(Buffer + exportDir.AddressOfNameOrdinals + i * 2ull)) & 0xFFFF;
1527  if ((QWORD)exportDir.AddressOfFunctions + exportOrdinal * 4ull + sizeof(DWORD) > BufferSize)
1528  {
1529  continue;
1530  }
1531 
1532  exportRva = *(DWORD *)(Buffer + exportDir.AddressOfFunctions + exportOrdinal * 4ull);
1533  if (exportRva != Rva)
1534  {
1535  continue;
1536  }
1537 
1538  if ((QWORD)exportDir.AddressOfNames + i * 4ull + sizeof(DWORD) > BufferSize)
1539  {
1541  }
1542 
1543  // Get the name pointer (RVA)
1544  namePointer = *(DWORD *)(Buffer + exportDir.AddressOfNames + i * 4ull);
1545  if ((QWORD)namePointer + ExportNameSize > BufferSize)
1546  {
1548  }
1549 
1550  // Read the name
1551  strlcpy(ExportName, (char *)Buffer + namePointer, ExportNameSize);
1552 
1553  return INT_STATUS_SUCCESS;
1554  }
1555 
1556  return INT_STATUS_NOT_FOUND;
1557 }
1558 
1559 
1560 INTSTATUS
1562  _In_ QWORD ImageBase,
1563  _In_bytecount_(BufferSize) BYTE *Buffer,
1564  _In_ DWORD BufferSize,
1565  _In_z_ const char *Name,
1566  _Out_ DWORD *ExportRva
1567  )
1582 {
1583  INTSTATUS status;
1585  size_t exportNameLen;
1586  int left, right;
1587  IMAGE_EXPORT_DIRECTORY *pExpDir;
1588 
1589  if (NULL == Buffer)
1590  {
1592  }
1593 
1594  if (NULL == Name)
1595  {
1597  }
1598 
1599  if (NULL == ExportRva)
1600  {
1602  }
1603 
1604  exportNameLen = strlen(Name);
1605  *ExportRva = 0;
1606 
1607  if (exportNameLen >= 512)
1608  {
1610  }
1611 
1612  // Firstly, validate that headers are valid before going through EAT
1613  status = IntPeValidateHeader(ImageBase, Buffer, BufferSize, NULL, 0);
1614  if (!INT_SUCCESS(status))
1615  {
1616  return status;
1617  }
1618 
1619  status = IntPeGetDirectory(ImageBase, Buffer, IMAGE_DIRECTORY_ENTRY_EXPORT, &dir);
1620  if (!INT_SUCCESS(status))
1621  {
1622  return status;
1623  }
1624 
1625  if ((QWORD)dir.VirtualAddress + dir.Size > BufferSize)
1626  {
1628  }
1629 
1630  // We want to make sure that we don't overflow the buffer even for some special crafted size
1631  // (e.g. dir.VirtualAddress = BufferSize - 1, dir.Size = 1, but dir.VirtualAddress + sizeof(IMAGE_EXPORT_DIRECTORY)
1632  // is > BufferSize)
1633  if ((QWORD)dir.VirtualAddress + sizeof(IMAGE_EXPORT_DIRECTORY) > BufferSize)
1634  {
1636  }
1637 
1638  // There is a validation done by IntPeGetDirectory with peinfo.SizeOfImage,
1639  // but we should also validate against BufferSize
1640  pExpDir = (IMAGE_EXPORT_DIRECTORY *)(Buffer + dir.VirtualAddress);
1641 
1642  left = 0;
1643  right = pExpDir->NumberOfNames;
1644 
1645  while (left < right)
1646  {
1647  DWORD namePointer;
1648  size_t offset;
1649  size_t cmpSize = exportNameLen + 1;
1650  int mid, res;
1651 
1652  mid = (left + right) / 2;
1653 
1654  offset = pExpDir->AddressOfNames + mid * 4ull;
1655  if (offset + sizeof(namePointer) > BufferSize)
1656  {
1657  WARNING("[WARNING] Corrupted export name pointer: 0x%lx is outside of image size of 0x%x\n",
1658  offset + sizeof(namePointer), BufferSize);
1660  }
1661 
1662  namePointer = *(DWORD *)(Buffer + offset);
1663 
1664  if (namePointer >= BufferSize)
1665  {
1666  WARNING("[WARNING] Corrupted export name. Name pointer 0x%08x is outside of image size: 0x%08x\n",
1667  namePointer, BufferSize);
1669  }
1670 
1671  if (namePointer + cmpSize > BufferSize)
1672  {
1673  // Don't leave with an error, we still need to do the comparison, maybe the export directory is at
1674  // the end of the image, and what we search for it's really big
1675  cmpSize = BufferSize - namePointer;
1676  }
1677 
1678  // Compares the null-terminator too so we can avoid cases like: ExAllocatePool equals ExAllocatePoolWithTag
1679  res = memcmp(Name, Buffer + namePointer, cmpSize);
1680 
1681  if (0 == res)
1682  {
1683  WORD ord;
1684 
1685  offset = pExpDir->AddressOfNameOrdinals + mid * 2ull;
1686  if (offset + sizeof(ord) > BufferSize)
1687  {
1688  WARNING("[WARNING] Corrupted export ordinal: 0x%lx is outside of image size of 0x%x\n",
1689  offset + sizeof(ord), BufferSize);
1691  }
1692 
1693  ord = *(WORD *)(Buffer + offset);
1694 
1695  offset = pExpDir->AddressOfFunctions + ord * 4ull;
1696  if (offset + sizeof(*ExportRva) > BufferSize)
1697  {
1698  WARNING("[WARNING] Corrupted export address: 0x%lx is outside of image size of 0x%x\n",
1699  offset + sizeof(*ExportRva), BufferSize);
1701  }
1702 
1703  *ExportRva = *(DWORD *)(Buffer + offset);
1704 
1705  // It's the callers duty to see if this is forwarded
1706  return INT_STATUS_SUCCESS;
1707  }
1708  else if (res < 0)
1709  {
1710  right = mid;
1711  }
1712  else
1713  {
1714  left = mid + 1;
1715  }
1716  }
1717 
1718  return INT_STATUS_NOT_FOUND;
1719 }
1720 
1721 
1722 INTSTATUS
1724  _In_z_ const char *Name,
1725  _Out_ QWORD *ExportGva
1726  )
1735 {
1736  INTSTATUS status;
1737  DWORD rva;
1738 
1739  *ExportGva = 0;
1740 
1744  Name,
1745  &rva);
1746  if (!INT_SUCCESS(status))
1747  {
1748  return status;
1749  }
1750 
1751  *ExportGva = gGuest.KernelVa + rva;
1752 
1753  return INT_STATUS_SUCCESS;
1754 }
1755 
1756 
1757 INTSTATUS
1759  _In_ QWORD ImageBase,
1760  _In_opt_ BYTE *ImageBaseBuffer,
1761  _In_z_ CHAR *Name,
1762  _Out_ DWORD *ExportRva
1763  )
1778 {
1779  SIZE_T exportNameLen;
1780  BOOLEAN found;
1781  INTSTATUS status;
1783  BYTE *map;
1784  BYTE *exportNameBuffer;
1785  IMAGE_EXPORT_DIRECTORY exportDir;
1786  int left, right;
1787 
1788  if (NULL == Name)
1789  {
1791  }
1792 
1793  if (NULL == ExportRva)
1794  {
1796  }
1797 
1798  exportNameBuffer = NULL;
1799  found = FALSE;
1800  exportNameLen = strlen(Name);
1801 
1802  if (exportNameLen >= 512)
1803  {
1805  }
1806 
1807  if (NULL != ImageBaseBuffer)
1808  {
1809  map = ImageBaseBuffer;
1810  }
1811  else
1812  {
1813  status = IntVirtMemMap(ImageBase, PAGE_SIZE, 0, 0, &map);
1814  if (!INT_SUCCESS(status))
1815  {
1816  ERROR("[ERROR] Failed mapping VA 0x%016llx to host: 0x%08x\n", ImageBase, status);
1817  return status;
1818  }
1819  }
1820 
1821  // Get the export directory, if present
1822  status = IntPeGetDirectory(ImageBase, map, IMAGE_DIRECTORY_ENTRY_EXPORT, &dir);
1823  if (!INT_SUCCESS(status))
1824  {
1825  goto leave;
1826  }
1827 
1828  // Read the export directory
1829  status = IntKernVirtMemRead(ImageBase + dir.VirtualAddress, sizeof(IMAGE_EXPORT_DIRECTORY), &exportDir, NULL);
1830  if (!INT_SUCCESS(status))
1831  {
1832  // Export directory is pageable so it CAN happen to not be present, so don't spam
1833  if (status != INT_STATUS_PAGE_NOT_PRESENT)
1834  {
1835  WARNING("[ERROR] Failed to read the export directory of 0x%016llx located at 0x%016llx: 0x%08x\n",
1836  ImageBase, ImageBase + dir.VirtualAddress, status);
1837  }
1838  goto leave;
1839  }
1840 
1841  // exportNameLen + 1 OK: exportNameLen is not longer than 512.
1842  exportNameBuffer = HpAllocWithTag(exportNameLen + 1ull, IC_TAG_EXPN);
1843  if (NULL == exportNameBuffer)
1844  {
1846  goto leave;
1847  }
1848 
1849  left = 0;
1850  right = MIN(exportDir.NumberOfNames, 10000ul); // Cap the number of exported names to 10K.
1851 
1852  // The export names are sorted, therefore, we can do a binary search.
1853  while (left < right)
1854  {
1855  DWORD namePointer;
1856  int mid, res;
1857 
1858  mid = (left + right) / 2;
1859 
1860  status = IntKernVirtMemFetchDword(ImageBase + exportDir.AddressOfNames + mid * 4ull, &namePointer);
1861  if (!INT_SUCCESS(status))
1862  {
1863  goto leave;
1864  }
1865 
1866  status = IntKernVirtMemRead(ImageBase + namePointer, (DWORD)exportNameLen + 1, exportNameBuffer, NULL);
1867  if (!INT_SUCCESS(status))
1868  {
1869  goto leave;
1870  }
1871 
1872  // compare the null-terminator too so we can avoid cases like: ExAllocatePool equals ExAllocatePoolWithTag
1873  res = memcmp(Name, exportNameBuffer, exportNameLen + 1ull);
1874 
1875  if (0 == res)
1876  {
1877  DWORD exportOrdinal;
1878 
1879  // Get the export ordinal
1880  status = IntKernVirtMemFetchDword(ImageBase + exportDir.AddressOfNameOrdinals + mid * 2ull,
1881  &exportOrdinal);
1882  if (!INT_SUCCESS(status))
1883  {
1884  goto leave;
1885  }
1886  exportOrdinal &= 0xFFFF;
1887 
1888  // Read the export RVA
1889  status = IntKernVirtMemFetchDword(ImageBase + exportDir.AddressOfFunctions + exportOrdinal * 4ull,
1890  ExportRva);
1891  if (!INT_SUCCESS(status))
1892  {
1893  goto leave;
1894  }
1895 
1896  // It's the callers duty to see if this is forwarded
1897  found = TRUE;
1898  break;
1899  }
1900  else if (res < 0)
1901  {
1902  right = mid;
1903  }
1904  else
1905  {
1906  left = mid + 1;
1907  }
1908  }
1909 
1910 leave:
1911  if (NULL != exportNameBuffer)
1912  {
1913  HpFreeAndNullWithTag(&exportNameBuffer, IC_TAG_EXPN);
1914  }
1915 
1916  if (ImageBaseBuffer == NULL)
1917  {
1918  IntVirtMemUnmap(&map);
1919  }
1920 
1921  // return the error if any...
1922  if (!INT_SUCCESS(status))
1923  {
1924  return status;
1925  }
1926  else if (!found)
1927  {
1928  return INT_STATUS_NOT_FOUND;
1929  }
1930 
1931  return INT_STATUS_SUCCESS;
1932 }
1933 
1934 
1935 INTSTATUS
1937  _In_ QWORD ImageBase,
1938  _In_opt_ BYTE *ImageBaseBuffer,
1939  _In_ DWORD Ordinal,
1940  _Out_ DWORD *ExportRva
1941  )
1955 {
1956  BOOLEAN found;
1957  INTSTATUS status;
1958  IMAGE_DATA_DIRECTORY dir = {0};
1959  BYTE *map;
1960  IMAGE_EXPORT_DIRECTORY exportDir;
1961 
1962  if (NULL == ExportRva)
1963  {
1965  }
1966 
1967  found = FALSE;
1968 
1969  if (NULL != ImageBaseBuffer)
1970  {
1971  map = ImageBaseBuffer;
1972  }
1973  else
1974  {
1975  status = IntVirtMemMap(ImageBase, PAGE_SIZE, 0, 0, &map);
1976  if (!INT_SUCCESS(status))
1977  {
1978  ERROR("[ERROR] Failed mapping VA 0x%016llx to host: 0x%08x\n", ImageBase, status);
1979  return status;
1980  }
1981  }
1982 
1983  // get the export directory, if present
1984  status = IntPeGetDirectory(ImageBase, map, IMAGE_DIRECTORY_ENTRY_EXPORT, &dir);
1985  if (!INT_SUCCESS(status))
1986  {
1987  goto leave;
1988  }
1989 
1990  // Read the export directory
1991  status = IntKernVirtMemRead(ImageBase + dir.VirtualAddress, sizeof(IMAGE_EXPORT_DIRECTORY), &exportDir, NULL);
1992  if (!INT_SUCCESS(status))
1993  {
1994  // Export directory is pageable so it CAN happen to not be present, so don't spam
1995  if (status != INT_STATUS_PAGE_NOT_PRESENT)
1996  {
1997  WARNING("[ERROR] Failed to read the export directory of 0x%016llx located at 0x%016llx: 0x%08x\n",
1998  ImageBase, ImageBase + dir.VirtualAddress, status);
1999  }
2000  goto leave;
2001  }
2002 
2003  if (Ordinal > exportDir.NumberOfFunctions)
2004  {
2006  goto leave;
2007  }
2008 
2009  // Read the export RVA
2010  status = IntKernVirtMemFetchDword(ImageBase + exportDir.AddressOfFunctions + Ordinal * 4ull, ExportRva);
2011  if (!INT_SUCCESS(status))
2012  {
2013  goto leave;
2014  }
2015 
2016 leave:
2017  if (ImageBaseBuffer == NULL)
2018  {
2019  IntVirtMemUnmap(&map);
2020  }
2021 
2022  // return the error if any...
2023  if (!INT_SUCCESS(status))
2024  {
2025  return status;
2026  }
2027  else if (!found)
2028  {
2029  return INT_STATUS_NOT_FOUND;
2030  }
2031 
2032  return INT_STATUS_SUCCESS;
2033 }
2034 
2035 
2036 INTSTATUS
2038  _In_ QWORD ImageBase,
2039  _In_opt_ BYTE *ImageBaseBuffer,
2040  _In_ DWORD Rva,
2041  _Out_ RUNTIME_FUNCTION *RuntimeFunction
2042  )
2056 {
2058  INTSTATUS status;
2059  DWORD i;
2060  PRUNTIME_FUNCTION pRuntimeFunction;
2061  QWORD currentAddress;
2062  BYTE *map;
2063  BOOLEAN found;
2064  INTRO_PE_INFO peInfo = {0};
2065 
2066  if (NULL == RuntimeFunction)
2067  {
2069  }
2070 
2071  found = FALSE;
2072  pRuntimeFunction = NULL;
2073 
2074  if (NULL != ImageBaseBuffer)
2075  {
2076  map = ImageBaseBuffer;
2077  }
2078  else
2079  {
2080  status = IntVirtMemMap(ImageBase, PAGE_SIZE, 0, 0, &map);
2081  if (!INT_SUCCESS(status))
2082  {
2083  ERROR("[ERROR] Failed mapping VA 0x%016llx to host: 0x%08x\n", ImageBase, status);
2084  return status;
2085  }
2086  }
2087 
2088  status = IntPeValidateHeader(ImageBase, map, PAGE_SIZE, &peInfo, 0);
2089  if (!INT_SUCCESS(status))
2090  {
2091  goto leave;
2092  }
2093 
2094  // this only works on x64 systems
2095  if (!peInfo.Image64Bit)
2096  {
2097  status = INT_STATUS_NOT_SUPPORTED;
2098  goto leave;
2099  }
2100 
2101  status = IntPeGetDirectory(ImageBase, map, IMAGE_DIRECTORY_ENTRY_EXCEPTION, &dir);
2102  if (!INT_SUCCESS(status))
2103  {
2104  ERROR("[ERROR] No exception directory for ImageBase 0x%016llx: 0x%08x\n", ImageBase, status);
2105  goto leave;
2106  }
2107 
2108  // The structures are ordered in memory by BeginAddress field so search till we find one
2109  // bigger than ours or we reach the last page
2110  currentAddress = ImageBase + dir.VirtualAddress;
2111 
2112  while (currentAddress < ImageBase + dir.VirtualAddress + dir.Size)
2113  {
2114  DWORD lastTableInPage = currentAddress & PAGE_OFFSET;
2115  DWORD beginRva;
2116 
2117  // if we get in the next page, this is the last one
2118  while (lastTableInPage + sizeof(RUNTIME_FUNCTION) < PAGE_SIZE)
2119  {
2120  lastTableInPage += sizeof(RUNTIME_FUNCTION);
2121  }
2122 
2123  // If we got out of the exception directory then check the last entry!
2124  if ((currentAddress & PAGE_MASK) + lastTableInPage > ImageBase + dir.VirtualAddress + dir.Size)
2125  {
2126  lastTableInPage = ((QWORD)dir.VirtualAddress + dir.Size - sizeof(RUNTIME_FUNCTION)) & PAGE_OFFSET;
2127  }
2128 
2129  status = IntKernVirtMemFetchDword((currentAddress & PAGE_MASK) + lastTableInPage, &beginRva);
2130  if (!INT_SUCCESS(status))
2131  {
2132  ERROR("[ERROR] IntKernVirtMemFetchDword failed for GVA 0x%016llx (dir->RVA 0x%08x, dir->Size 0x%08x, "
2133  "ImageBase 0x%016llx: 0x%08x\n", currentAddress + lastTableInPage, dir.VirtualAddress,
2134  dir.Size, ImageBase, status);
2135  goto leave;
2136  }
2137  else if (beginRva == Rva)
2138  {
2139  status = IntKernVirtMemRead((currentAddress & PAGE_MASK) + lastTableInPage,
2140  sizeof(RUNTIME_FUNCTION), RuntimeFunction, NULL);
2141  if (!INT_SUCCESS(status))
2142  {
2143  ERROR("[ERROR] Failed reading runtime function from address 0x%016llx: 0x%08x\n",
2144  currentAddress + lastTableInPage, status);
2145  }
2146 
2147  goto leave;
2148  }
2149  else if (beginRva > Rva)
2150  {
2151  found = TRUE;
2152  break;
2153  }
2154 
2155  // the first structure in the next page
2156  currentAddress = (currentAddress & PAGE_MASK) + lastTableInPage + sizeof(RUNTIME_FUNCTION);
2157  }
2158 
2159  if (!found)
2160  {
2161  status = INT_STATUS_NOT_FOUND;
2162  goto leave;
2163  }
2164 
2165  status = IntVirtMemMap(currentAddress, PAGE_REMAINING(currentAddress), 0, 0, &pRuntimeFunction);
2166  if (!INT_SUCCESS(status))
2167  {
2168  ERROR("[ERROR] Failed mapping VA 0x%016llx to host: 0x%08x\n", currentAddress, status);
2169  goto leave;
2170  }
2171 
2172  // It can't be the last one on the page, we already covered that in the while
2173  found = FALSE;
2174  for (i = 0; i < PAGE_REMAINING(currentAddress) / sizeof(RUNTIME_FUNCTION); i++)
2175  {
2176  if (pRuntimeFunction[i].BeginAddress <= Rva && pRuntimeFunction[i].EndAddress > Rva)
2177  {
2178  found = TRUE;
2179  break;
2180  }
2181  }
2182 
2183  if (found)
2184  {
2185  // This may be a pointer to the next unwind info structure
2186  if (pRuntimeFunction[i].UnwindData % sizeof(DWORD) != 0)
2187  {
2188  DWORD newUnwindInfoAddr = ALIGN_DOWN(pRuntimeFunction[i].UnwindData, sizeof(DWORD));
2189 
2190  if (newUnwindInfoAddr >= dir.VirtualAddress &&
2191  newUnwindInfoAddr < dir.VirtualAddress + dir.Size)
2192  {
2193  IntVirtMemUnmap(&pRuntimeFunction);
2194 
2195  status = IntVirtMemMap(ImageBase + newUnwindInfoAddr,
2196  sizeof(RUNTIME_FUNCTION), 0, 0, &pRuntimeFunction);
2197  if (!INT_SUCCESS(status))
2198  {
2199  ERROR("[ERROR] Failed mapping next unwind data for runtime function at "
2200  "0x%016llx in driver 0x%016llx",
2201  currentAddress + i * sizeof(RUNTIME_FUNCTION), ImageBase);
2202  goto leave;
2203  }
2204 
2205  memcpy(RuntimeFunction, pRuntimeFunction, sizeof(RUNTIME_FUNCTION));
2206  goto _done_saving;
2207  }
2208  else
2209  {
2210  TRACE("[INFO] We have a function at 0x%016llx but it's not inside the exception dir (0x%016llx, %x)\n",
2211  ImageBase + pRuntimeFunction->UnwindData, ImageBase + dir.VirtualAddress, dir.Size);
2212 
2213  status = INT_STATUS_NOT_FOUND;
2214  goto _cleanup_and_leave;
2215  }
2216  }
2217 
2218  memcpy(RuntimeFunction, &pRuntimeFunction[i], sizeof(RUNTIME_FUNCTION));
2219 
2220 _done_saving:
2221  status = INT_STATUS_SUCCESS;
2222  }
2223  else
2224  {
2225  status = INT_STATUS_NOT_FOUND;
2226  }
2227 
2228 _cleanup_and_leave:
2229  IntVirtMemUnmap(&pRuntimeFunction);
2230 
2231 leave:
2232  if (NULL == ImageBaseBuffer)
2233  {
2234  IntVirtMemUnmap(&map);
2235  }
2236 
2237  return status;
2238 }
2239 
2240 
2241 INTSTATUS
2243  _In_ QWORD ImageBase,
2244  _In_ BYTE *Buffer,
2245  _In_ DWORD BufferSize,
2246  _In_ DWORD Rva,
2247  _Out_ RUNTIME_FUNCTION *RuntimeFunction
2248  )
2264 {
2266  INTSTATUS status;
2267  RUNTIME_FUNCTION *pRuntimeFunction = NULL;
2268  BOOLEAN found = FALSE;
2269  INTRO_PE_INFO peInfo = {0};
2270  size_t left, right;
2271 
2272  if (NULL == Buffer)
2273  {
2275  }
2276 
2277  if (0 == BufferSize)
2278  {
2280  }
2281 
2282  if (NULL == RuntimeFunction)
2283  {
2285  }
2286 
2287  status = IntPeValidateHeader(ImageBase, Buffer, PAGE_SIZE, &peInfo, 0);
2288  if (!INT_SUCCESS(status))
2289  {
2290  return status;
2291  }
2292 
2293  // this only works on x64 systems
2294  if (!peInfo.Image64Bit)
2295  {
2296  return INT_STATUS_NOT_SUPPORTED;
2297  }
2298 
2299  status = IntPeGetDirectory(ImageBase, Buffer, IMAGE_DIRECTORY_ENTRY_EXCEPTION, &dir);
2300  if (!INT_SUCCESS(status))
2301  {
2302  ERROR("[ERROR] No exception directory for ImageBase 0x%016llx: 0x%08x\n", ImageBase, status);
2303  return status;
2304  }
2305 
2306  // IntPeGetDirectory already did checks for SizeOfImage, but not for BufferSize
2307  if ((QWORD)dir.VirtualAddress + dir.Size > BufferSize)
2308  {
2310  }
2311 
2312  left = 0;
2313  right = (dir.Size / sizeof(RUNTIME_FUNCTION));
2314 
2315  while (left < right)
2316  {
2317  size_t midd = (left + right) / 2;
2318 
2319  pRuntimeFunction = (RUNTIME_FUNCTION *)(Buffer + dir.VirtualAddress + midd * sizeof(*pRuntimeFunction));
2320 
2321  if (pRuntimeFunction->BeginAddress <= Rva && pRuntimeFunction->EndAddress > Rva)
2322  {
2323  found = TRUE;
2324  break;
2325  }
2326  else if (pRuntimeFunction->BeginAddress < Rva)
2327  {
2328  left = midd + 1;
2329  }
2330  else
2331  {
2332  right = midd;
2333  }
2334  }
2335 
2336  if (!found)
2337  {
2338  return INT_STATUS_NOT_FOUND;
2339  }
2340 
2341  // This may be a pointer to the next unwind info structure
2342  if (pRuntimeFunction->UnwindData % sizeof(DWORD) != 0)
2343  {
2344  DWORD newUnwindInfoAddr = ALIGN_DOWN(pRuntimeFunction->UnwindData, sizeof(DWORD));
2345 
2346  if (IN_RANGE_LEN(newUnwindInfoAddr, dir.VirtualAddress, dir.Size))
2347  {
2348  pRuntimeFunction = (RUNTIME_FUNCTION *)(Buffer + newUnwindInfoAddr);
2349 
2350  memcpy(RuntimeFunction, pRuntimeFunction, sizeof(RUNTIME_FUNCTION));
2351 
2352  return INT_STATUS_SUCCESS;
2353  }
2354  else
2355  {
2356  TRACE("[INFO] We have a function at 0x%016llx but it's not inside the exception dir (0x%016llx, %x)\n",
2357  ImageBase + pRuntimeFunction->UnwindData, ImageBase + dir.VirtualAddress, dir.Size);
2358 
2359  return INT_STATUS_NOT_FOUND;
2360  }
2361  }
2362 
2363  memcpy(RuntimeFunction, pRuntimeFunction, sizeof(RUNTIME_FUNCTION));
2364 
2365  return INT_STATUS_SUCCESS;
2366 }
2367 
2368 
2369 INTSTATUS
2371  _In_ QWORD ImageBase,
2372  _In_opt_ BYTE *ImageBaseBuffer,
2373  _In_ RUNTIME_FUNCTION *RuntimeFunction,
2374  _In_opt_ DWORD RipOffset,
2375  _Out_opt_ DWORD *ReservedStack,
2376  _Out_opt_ DWORD *BeginAddress,
2377  _Out_opt_ BOOLEAN *InterruptFunction,
2378  _Out_opt_ BOOLEAN *ExceptionFunction,
2379  _Out_opt_ BOOLEAN *HasFramePointer
2380  )
2400 {
2401  QWORD unwindInfoAddress;
2402  DWORD unwindInfoSize, i, extraSpace;
2403  INTRO_UNWIND_INFO *pUnwindInfoMap;
2404  BOOLEAN hasChainedUnwind;
2405  BOOLEAN interrupt, exception;
2406  BYTE *map;
2407  INTSTATUS status;
2408  DWORD itCount = 0;
2409 
2410  if (NULL == RuntimeFunction)
2411  {
2413  }
2414 
2415  if (RipOffset > RuntimeFunction->EndAddress - RuntimeFunction->BeginAddress)
2416  {
2417  ERROR("[ERROR] RipOffset %x, End %x, Begin %x, Total %x\n", RipOffset, RuntimeFunction->EndAddress,
2418  RuntimeFunction->BeginAddress, RuntimeFunction->EndAddress - RuntimeFunction->BeginAddress);
2420  }
2421 
2422  if (NULL != ImageBaseBuffer)
2423  {
2424  map = ImageBaseBuffer;
2425  }
2426  else
2427  {
2428  status = IntVirtMemMap(ImageBase, PAGE_SIZE, 0, 0, &map);
2429  if (!INT_SUCCESS(status))
2430  {
2431  ERROR("[ERROR] Failed mapping VA 0x%016llx to host: 0x%08x\n", ImageBase, status);
2432  return status;
2433  }
2434  }
2435 
2436  hasChainedUnwind = FALSE;
2437  interrupt = exception = FALSE;
2438  pUnwindInfoMap = NULL;
2439  extraSpace = 0;
2440 
2441  if (NULL != BeginAddress)
2442  {
2443  *BeginAddress = 0;
2444  }
2445 
2446  if (NULL != HasFramePointer)
2447  {
2448  *HasFramePointer = FALSE;
2449  }
2450 
2451  // Align to DWORD (there are some weird cases in kernel when the address is not aligned...
2452  // I assume it's because the UNWIND_INFO structure is shared by multiple structures, and the
2453  // first byte is a flag, at least that's what happened in those cases)
2454  unwindInfoAddress = ALIGN_DOWN(ImageBase + RuntimeFunction->UnwindData, sizeof(DWORD));
2455  unwindInfoSize = MAX_UNWIND_CODES * 2 + 12 + sizeof(DWORD);
2456 
2457  if (NULL != InterruptFunction)
2458  {
2459  *InterruptFunction = FALSE;
2460  }
2461 
2462  if (NULL != ExceptionFunction)
2463  {
2464  *ExceptionFunction = FALSE;
2465  }
2466 
2467  do
2468  {
2469  BOOLEAN saveExtraSpace;
2470  DWORD countOfCodes;
2471 
2472  if (pUnwindInfoMap != NULL)
2473  {
2474  IntVirtMemUnmap(&pUnwindInfoMap);
2475  }
2476 
2477  itCount++;
2478  if (itCount > MAX_UNWIND_INFO_TRIES)
2479  {
2480  ERROR("[ERROR] Reached MAX_UNWIND_INFO_TRIES for image at 0x%016llx\n", ImageBase);
2481  status = INT_STATUS_NOT_SUPPORTED;
2482  goto leave;
2483  }
2484 
2485  status = IntVirtMemMap(unwindInfoAddress, unwindInfoSize, 0, 0, &pUnwindInfoMap);
2486  if (!INT_SUCCESS(status))
2487  {
2488  ERROR("[ERROR] IntVirtMemMap failed for GVA 0x%016llx (chained: %s, RIP: 0x%016llx): 0x%08x\n",
2489  unwindInfoAddress, hasChainedUnwind ? "TRUE" : "FALSE", ImageBase + RuntimeFunction->BeginAddress,
2490  status);
2491  goto leave;
2492  }
2493 
2494  countOfCodes = pUnwindInfoMap->CountOfCodes;
2495 
2496  // Make sure we mapped enough
2497  if (countOfCodes > MAX_UNWIND_CODES)
2498  {
2499  WARNING("[WARNING] Function with %d codes at RVA %08x in driver 0x%016llx with unwind info %llx\n",
2500  countOfCodes, RuntimeFunction->BeginAddress, ImageBase, unwindInfoAddress);
2501  status = INT_STATUS_NOT_SUPPORTED;
2502  goto leave;
2503  }
2504 
2505  if (ReservedStack == NULL)
2506  {
2507  goto _get_chained_info;
2508  }
2509 
2510  // If the FrameRegister is not NULL then another register is used as a stack frame with a FrameOffset
2511  // But this doesn't change anything for what we want to do since the return address is still on RSP.
2512  i = 0; // Sometimes we need to skip the next codes (they're passed as info to this code)
2513  while (i < countOfCodes)
2514  {
2515  // We check for version (1 on Win7, 2 on Win8) in case we somehow don't skip enough codes
2516  if (pUnwindInfoMap->Version != 1 && pUnwindInfoMap->Version != 2)
2517  {
2518  i++;
2519  continue;
2520  }
2521 
2522  // We only save if the instruction was executed. We use less-or-equal because CodeOffset field doesn't point
2523  // to the beginning of the instruction, but to the end. But if we are inside a chained entry, then this
2524  // we save automatically, because it's code that was executed before
2525  if (!hasChainedUnwind)
2526  {
2527  saveExtraSpace = RipOffset >= pUnwindInfoMap->UnwindCode[i].CodeOffset;
2528  }
2529  else
2530  {
2531  saveExtraSpace = TRUE;
2532  }
2533 
2534  // see http://msdn.microsoft.com/en-US/library/ck9asaa9%28v=vs.80%29.aspx for details
2535  switch (pUnwindInfoMap->UnwindCode[i].UnwindOp)
2536  {
2537  case 0: // UWOP_PUSH_NONVOL (1)
2538  if (saveExtraSpace)
2539  {
2540  extraSpace += 8;
2541  }
2542 
2543  i++;
2544  break;
2545 
2546  case 1: // UWOP_ALLOC_LARGE (2 or 3)
2547  if (pUnwindInfoMap->UnwindCode[i].OpInfo == 0)
2548  {
2549  if (saveExtraSpace)
2550  {
2551  extraSpace += *((WORD *)&pUnwindInfoMap->UnwindCode[i + 1]) * 8;
2552  }
2553 
2554  i += 2;
2555  }
2556  else
2557  {
2558  if (saveExtraSpace)
2559  {
2560  extraSpace += *((DWORD *)&pUnwindInfoMap->UnwindCode[i + 2]) * 8;
2561  }
2562 
2563  i += 3;
2564  }
2565 
2566  break;
2567  case 2: // UWOP_ALLOC_SMALL (1)
2568  if (saveExtraSpace)
2569  {
2570  extraSpace += pUnwindInfoMap->UnwindCode[i].OpInfo * 8 + 8;
2571  }
2572 
2573  i++;
2574  break;
2575 
2576  case 3: // UWOP_SET_FPREG(1)
2577  if (HasFramePointer != NULL)
2578  {
2579  *HasFramePointer = TRUE;
2580  }
2581 
2582  i++;
2583  break;
2584 
2585  case 4: // UWOP_SAVE_NONVOL(1)
2586  i += 2;
2587  break;
2588 
2589  case 5: // UWOP_SAVE_NONVOL_FAR
2590  i += 3;
2591  break;
2592 
2593  case 6: // UWOP_EPILOG
2594  // For what I see so far it just says where the exit points are in a function
2595  // (if there are multiple exit points)
2596  i += 1;
2597  break;
2598 
2599  case 7: // UWOP_SPARE_CODE
2600  i += 2;
2601  break;
2602 
2603  case 8: // UWOP_SAVE_XMM128
2604  i += 2;
2605  break;
2606 
2607  case 9: // UWOP_SAVE_XMM128_FAR
2608  i += 3;
2609  break;
2610 
2611  case 10: // UWOP_PUSH_MACHFRAME
2612  if (pUnwindInfoMap->UnwindCode[i].OpInfo == 0)
2613  {
2614  // We are inside an exception. This is done automatically, so no need to verify rip offset
2615  // Actually here, we will do a trick. We only subtract enough to get to the RIP
2616 
2617  exception = TRUE;
2618  }
2619  else if (pUnwindInfoMap->UnwindCode[i].OpInfo == 1)
2620  {
2621  // We are inside an hardware interrupt. This is done automatically, so no need to verify rip offset
2622  // Actually here we will do a trick. We only subtract enough to get to the RIP
2623 
2624  interrupt = TRUE;
2625  }
2626  else
2627  {
2628  WARNING("[WARNING] Unknown info for UWOP_PUSH_MACHFRAME: %d\n",
2629  pUnwindInfoMap->UnwindCode[i].OpInfo);
2630  }
2631 
2632  i++;
2633  break;
2634 
2635  default:
2636  WARNING("[WARNING] UWOP value not known: %d\n", pUnwindInfoMap->UnwindCode[i].UnwindOp);
2637 
2638  i++;
2639  break;
2640  }
2641  }
2642 
2643 _get_chained_info:
2644  // Get the new unwind info address so we can map the new one if we need to. Formula:
2645  // CountOfCode * sizeof(UNWIND_CODE) + 4 (first BYTES in UNWIND_INFO) => RUNTIME_FUNCTION.
2646  // Add 8 (OFFSET_OF(RUNTIME_FUNCTION, UnwindData)) to get the new unwind info address.
2647  hasChainedUnwind = (pUnwindInfoMap->Flags == UNW_FLAG_CHAININFO);
2648 
2649  unwindInfoAddress =
2650  ALIGN_DOWN(ImageBase + * (DWORD *)((PBYTE)pUnwindInfoMap + (pUnwindInfoMap->CountOfCodes * 2ull) + 12),
2651  sizeof(DWORD));
2652 
2653  // Get the actual function start if we have a chain
2654  if (hasChainedUnwind && BeginAddress != NULL)
2655  {
2656  *BeginAddress = *(DWORD *)((PBYTE)pUnwindInfoMap + (pUnwindInfoMap->CountOfCodes * 2ull) + 4);
2657  }
2658  } while (hasChainedUnwind);
2659 
2660  if (NULL != ReservedStack)
2661  {
2662  *ReservedStack = extraSpace;
2663  }
2664 
2665  if (exception && interrupt)
2666  {
2667  WARNING("[WARNING] Why do we have both exception and interrupt context ? RIP 0x%016llx, Module 0x%016llx\n",
2668  ImageBase + RuntimeFunction->BeginAddress + RipOffset, ImageBase);
2669  }
2670 
2671  if (NULL != ExceptionFunction)
2672  {
2673  *ExceptionFunction = exception;
2674  }
2675 
2676  if (NULL != InterruptFunction)
2677  {
2678  *InterruptFunction = interrupt;
2679  }
2680 
2681  status = INT_STATUS_SUCCESS;
2682 
2683 leave:
2684  if (pUnwindInfoMap != NULL)
2685  {
2686  IntVirtMemUnmap(&pUnwindInfoMap);
2687  }
2688 
2689  if (NULL == ImageBaseBuffer)
2690  {
2691  IntVirtMemUnmap(&map);
2692  }
2693 
2694  return status;
2695 }
2696 
2697 
2698 INTSTATUS
2700  _In_ QWORD ImageBase,
2701  _In_ BYTE *Buffer,
2702  _In_ DWORD BufferSize,
2703  _In_ RUNTIME_FUNCTION *RuntimeFunction,
2704  _In_opt_ DWORD RipOffset,
2705  _Out_opt_ DWORD *ReservedStack,
2706  _Out_opt_ DWORD *BeginAddress,
2707  _Out_opt_ BOOLEAN *InterruptFunction,
2708  _Out_opt_ BOOLEAN *ExceptionFunction,
2709  _Out_opt_ BOOLEAN *HasFramePointer
2710  )
2731 {
2732  DWORD unwindInfoRva;
2733  DWORD i, extraSpace;
2734  INTRO_UNWIND_INFO *pUnwindInfo;
2735  BOOLEAN hasChainedUnwind;
2736  BOOLEAN interrupt, exception;
2737  DWORD itCount = 0;
2738  const DWORD unwindInfoSize = MAX_UNWIND_CODES * 2 + 12 + sizeof(DWORD);
2739 
2740  if (NULL == Buffer)
2741  {
2743  }
2744 
2745  if (0 == BufferSize)
2746  {
2748  }
2749 
2750  if (NULL == RuntimeFunction)
2751  {
2753  }
2754 
2755  if (RipOffset > RuntimeFunction->EndAddress - RuntimeFunction->BeginAddress)
2756  {
2757  ERROR("[ERROR] RipOffset %x, End %x, Begin %x, Total %x\n", RipOffset, RuntimeFunction->EndAddress,
2758  RuntimeFunction->BeginAddress, RuntimeFunction->EndAddress - RuntimeFunction->BeginAddress);
2760  }
2761 
2762  hasChainedUnwind = FALSE;
2763  interrupt = exception = FALSE;
2764  extraSpace = 0;
2765 
2766  if (BeginAddress != NULL)
2767  {
2768  *BeginAddress = 0;
2769  }
2770 
2771  if (HasFramePointer != NULL)
2772  {
2773  *HasFramePointer = FALSE;
2774  }
2775 
2776  // Align to DWORD (there are some weird cases in kernel when the address is not aligned...
2777  // I assume it's because the UNWIND_INFO structure is shared by multiple structures, and the
2778  // first byte is a flag, at least that's what happened in those cases)
2779  unwindInfoRva = ALIGN_DOWN(RuntimeFunction->UnwindData, sizeof(DWORD));
2780 
2781  if ((QWORD)unwindInfoRva + sizeof(*pUnwindInfo) + unwindInfoSize >= BufferSize)
2782  {
2783  ERROR("[ERROR] Invalid unwind info at 0x%04x, we have only 0x%04x\n", unwindInfoRva, BufferSize);
2785  }
2786 
2787  if (NULL != InterruptFunction)
2788  {
2789  *InterruptFunction = FALSE;
2790  }
2791 
2792  if (NULL != ExceptionFunction)
2793  {
2794  *ExceptionFunction = FALSE;
2795  }
2796 
2797  do
2798  {
2799  // Although we checked before with unwindInfoSize, better safe than sorry.
2800  if ((QWORD)unwindInfoRva + sizeof(*pUnwindInfo) > BufferSize)
2801  {
2803  }
2804 
2805  itCount++;
2806  if (itCount > MAX_UNWIND_INFO_TRIES)
2807  {
2808  ERROR("[ERROR] Reached MAX_UNWIND_INFO_TRIES for image at 0x%016llx\n", ImageBase);
2809  return INT_STATUS_NOT_SUPPORTED;
2810  }
2811 
2812  pUnwindInfo = (INTRO_UNWIND_INFO *)(Buffer + unwindInfoRva);
2813 
2814  // Make sure we mapped enough
2815  if (pUnwindInfo->CountOfCodes > MAX_UNWIND_CODES)
2816  {
2817  WARNING("[WARNING] Function with %d codes at RVA %08x in driver 0x%016llx with unwind info 0x%04x\n",
2818  pUnwindInfo->CountOfCodes, RuntimeFunction->BeginAddress, ImageBase, unwindInfoRva);
2819  return INT_STATUS_NOT_SUPPORTED;
2820  }
2821 
2822  if (ReservedStack == NULL)
2823  {
2824  goto _get_chained_info;
2825  }
2826 
2827  // If the FrameRegister is not NULL then another register is used as a stack frame with a FrameOffset
2828  // But this doesn't change anything for what we want to do since the return address is still on RSP.
2829  i = 0; // Sometimes we need to skip the next codes (they're passed as info to this code)
2830  while (i < pUnwindInfo->CountOfCodes)
2831  {
2832  BOOLEAN saveExtraSpace;
2833 
2834  // We check for version (1 on Win7, 2 on Win8) in case we somehow don't skip enough codes
2835  if (pUnwindInfo->Version != 1 && pUnwindInfo->Version != 2)
2836  {
2837  i++;
2838  continue;
2839  }
2840 
2841  // We only save if the instruction was executed. We use less-or-equal because CodeOffset field doesn't point
2842  // to the beginning of the instruction, but to the end. But if we are inside a chained entry, then this
2843  // we save automatically, because it's code that was executed before
2844  if (!hasChainedUnwind)
2845  {
2846  saveExtraSpace = RipOffset >= pUnwindInfo->UnwindCode[i].CodeOffset;
2847  }
2848  else
2849  {
2850  saveExtraSpace = TRUE;
2851  }
2852 
2853  // see http://msdn.microsoft.com/en-US/library/ck9asaa9%28v=vs.80%29.aspx for details
2854  switch (pUnwindInfo->UnwindCode[i].UnwindOp)
2855  {
2856  case 0: // UWOP_PUSH_NONVOL (1)
2857  if (saveExtraSpace)
2858  {
2859  extraSpace += 8;
2860  }
2861 
2862  i++;
2863  break;
2864 
2865  case 1: // UWOP_ALLOC_LARGE (2 or 3)
2866  if (pUnwindInfo->UnwindCode[i].OpInfo == 0)
2867  {
2868  if (saveExtraSpace)
2869  {
2870  extraSpace += *((WORD *)&pUnwindInfo->UnwindCode[i + 1]) * 8;
2871  }
2872 
2873  i += 2;
2874  }
2875  else
2876  {
2877  if (saveExtraSpace)
2878  {
2879  extraSpace += *((DWORD *)&pUnwindInfo->UnwindCode[i + 2]) * 8;
2880  }
2881 
2882  i += 3;
2883  }
2884 
2885  break;
2886  case 2: // UWOP_ALLOC_SMALL (1)
2887  if (saveExtraSpace)
2888  {
2889  extraSpace += pUnwindInfo->UnwindCode[i].OpInfo * 8 + 8;
2890  }
2891 
2892  i++;
2893  break;
2894 
2895  case 3: // UWOP_SET_FPREG(1)
2896  if (HasFramePointer != NULL)
2897  {
2898  *HasFramePointer = TRUE;
2899  }
2900 
2901  i++;
2902  break;
2903 
2904  case 4: // UWOP_SAVE_NONVOL(1)
2905  i += 2;
2906  break;
2907 
2908  case 5: // UWOP_SAVE_NONVOL_FAR
2909  i += 3;
2910  break;
2911 
2912  case 6: // UWOP_EPILOG
2913  // For what I see so far it just says where the exit points are in a function
2914  // (if there are multiple exit points)
2915  i += 1;
2916  break;
2917 
2918  case 7: // UWOP_SPARE_CODE
2919  i += 2;
2920  break;
2921 
2922  case 8: // UWOP_SAVE_XMM128
2923  i += 2;
2924  break;
2925 
2926  case 9: // UWOP_SAVE_XMM128_FAR
2927  i += 3;
2928  break;
2929 
2930  case 10: // UWOP_PUSH_MACHFRAME
2931  if (pUnwindInfo->UnwindCode[i].OpInfo == 0)
2932  {
2933  // We are inside an exception. This is done automatically, so no need to verify rip offset
2934  // Actually here, we will do a trick. We only subtract enough to get to the RIP
2935 
2936  exception = TRUE;
2937  }
2938  else if (pUnwindInfo->UnwindCode[i].OpInfo == 1)
2939  {
2940  // We are inside an hardware interrupt. This is done automatically, so no need to verify rip offset
2941  // Actually here we will do a trick. We only subtract enough to get to the RIP
2942 
2943  interrupt = TRUE;
2944  }
2945  else
2946  {
2947  WARNING("[WARNING] Unknown info for UWOP_PUSH_MACHFRAME: %d\n",
2948  pUnwindInfo->UnwindCode[i].OpInfo);
2949  }
2950 
2951  i++;
2952  break;
2953 
2954  default:
2955  WARNING("[WARNING] UWOP value not known: %d\n", pUnwindInfo->UnwindCode[i].UnwindOp);
2956 
2957  i++;
2958  break;
2959  }
2960  }
2961 
2962 _get_chained_info:
2963  // Get the new unwind info address so we can map the new one if we need to. Formula:
2964  // CountOfCode * sizeof(UNWIND_CODE) + 4 (first BYTES in UNWIND_INFO) => RUNTIME_FUNCTION.
2965  // Add 8 (FIELD_OFFSET(RUNTIME_FUNCTION, UnwindData)) to get the new unwind info address.
2966  hasChainedUnwind = (pUnwindInfo->Flags == UNW_FLAG_CHAININFO);
2967 
2968  unwindInfoRva =
2969  ALIGN_DOWN(*(DWORD *)((BYTE *)pUnwindInfo + (pUnwindInfo->CountOfCodes * 2ull) + 12), sizeof(DWORD));
2970 
2971  // We want to verify only if it has chained unwind, as the next RVA will most probably be invalid and we
2972  // shouldn't bother as we don't get anything from the buffer anymore.
2973  if (hasChainedUnwind && (QWORD)unwindInfoRva + sizeof(*pUnwindInfo) + unwindInfoSize >= BufferSize)
2974  {
2975  ERROR("[ERROR] Invalid unwind info at 0x%04x, we have only 0x%04x\n", unwindInfoRva, BufferSize);
2977  }
2978 
2979  // Get the actual function start if we have a chain
2980  if (hasChainedUnwind && BeginAddress != NULL)
2981  {
2982  *BeginAddress = *(DWORD *)((BYTE *)pUnwindInfo + (pUnwindInfo->CountOfCodes * 2ull) + 4);
2983  }
2984  } while (hasChainedUnwind);
2985 
2986  if (NULL != ReservedStack)
2987  {
2988  *ReservedStack = extraSpace;
2989  }
2990 
2991  if (exception && interrupt)
2992  {
2993  WARNING("[WARNING] Why do we have both exception and interrupt context ? RIP 0x%016llx, Module 0x%016llx\n",
2994  ImageBase + RuntimeFunction->BeginAddress + RipOffset, ImageBase);
2995  }
2996 
2997  if (NULL != ExceptionFunction)
2998  {
2999  *ExceptionFunction = exception;
3000  }
3001 
3002  if (NULL != InterruptFunction)
3003  {
3004  *InterruptFunction = interrupt;
3005  }
3006 
3007  return INT_STATUS_SUCCESS;
3008 }
3009 
3010 
3011 INTSTATUS
3013  _In_bytecount_(BufferSize) BYTE *Buffer,
3014  _In_ DWORD BufferSize,
3016  _In_ BOOLEAN IgnoreSectionHint,
3017  _Out_ DWORD *Rva
3018  )
3034 {
3035  IMAGE_DOS_HEADER *pDos;
3036  IMAGE_FILE_HEADER *pFileHeader;
3037  IMAGE_SECTION_HEADER *pSec;
3038  DWORD secheadersRva;
3039  BYTE *p;
3040  INTSTATUS status;
3041 
3042  // Validate the header before actually going through sections
3043  status = IntPeValidateHeader(0, Buffer, BufferSize, NULL, 0);
3044  if (!INT_SUCCESS(status))
3045  {
3046  return status;
3047  }
3048 
3049  pDos = (IMAGE_DOS_HEADER *)Buffer;
3050 
3051  // Read the signature + file header
3052  pFileHeader = (IMAGE_FILE_HEADER *)(Buffer + pDos->e_lfanew + 4);
3053 
3054  secheadersRva = pDos->e_lfanew + 4 + sizeof(IMAGE_FILE_HEADER) + pFileHeader->SizeOfOptionalHeader;
3055 
3056  for (DWORD i = 0; i < pFileHeader->NumberOfSections; i++)
3057  {
3058  pSec = (IMAGE_SECTION_HEADER *)(Buffer + secheadersRva + sizeof(IMAGE_SECTION_HEADER) * i);
3059 
3060  // ERRATA has a size of 3...
3061  if (pSec->Misc.VirtualSize < Pattern->Signature.Length)
3062  {
3063  continue;
3064  }
3065 
3066  if (Pattern->SectionHint[0] && !IgnoreSectionHint && 0 != memcmp(pSec->Name, Pattern->SectionHint, 8))
3067  {
3068  continue;
3069  }
3070 
3071  if ((QWORD)pSec->VirtualAddress + pSec->Misc.VirtualSize > BufferSize)
3072  {
3073  CHAR name[9];
3074  memcpy(name, pSec->Name, sizeof(pSec->Name));
3075  name[8] = 0;
3076 
3077  ERROR("[ERROR] Section %s %08x with size %08x outside of image size %08x...\n",
3078  name, pSec->VirtualAddress, pSec->Misc.VirtualSize, BufferSize);
3080  }
3081 
3082  p = Buffer + pSec->VirtualAddress;
3083 
3084  // Try to find the pattern inside this section.
3085  for (DWORD j = 0; j < pSec->Misc.VirtualSize - Pattern->Signature.Length; j++)
3086  {
3087  BOOLEAN bFound = TRUE;
3088 
3089  for (DWORD k = 0; k < Pattern->Signature.Length; k++)
3090  {
3091  if (__likely(Pattern->Signature.Pattern[k] != 0x100 &&
3092  Pattern->Signature.Pattern[k] != p[j + k]))
3093  {
3094  bFound = FALSE;
3095  break;
3096  }
3097  }
3098 
3099  if (bFound)
3100  {
3101  *Rva = pSec->VirtualAddress + j;
3102  if (IgnoreSectionHint)
3103  {
3104  TRACE("[DEBUG] Found function inside section %d:%s, was supposed to find in %s\n",
3105  i, pSec->Name, Pattern->SectionHint);
3106  }
3107 
3108  return INT_STATUS_SUCCESS;
3109  }
3110  }
3111  }
3112 
3113  return INT_STATUS_NOT_FOUND;
3114 }
3115 
3116 
3117 INTSTATUS
3119  _In_ QWORD ImageBase,
3121  _In_ BOOLEAN IgnoreSectionHint,
3122  _Out_ DWORD *Rva
3123  )
3139 {
3140  INTSTATUS status;
3141  QWORD secheadersRva;
3142  QWORD cr3 = gGuest.Mm.SystemCr3;
3143  INTRO_PE_INFO peInfo = { 0 };
3144 
3145  status = IntPeValidateHeader(ImageBase, NULL, 0, &peInfo, cr3);
3146  if (!INT_SUCCESS(status))
3147  {
3148  ERROR("IntPeValidateHeader failed for image at 0x%016llx: 0x%08x\n", ImageBase, status);
3149  return status;
3150  }
3151 
3152  secheadersRva = peInfo.SectionOffset;
3153 
3154  for (QWORD i = 0; i < peInfo.NumberOfSections; i++)
3155  {
3156  PBYTE pPage1, pPage2;
3157  DWORD rva, k, j, origStart;
3158  BOOLEAN bFound;
3159  IMAGE_SECTION_HEADER hSec;
3160 
3161  bFound = FALSE;
3162 
3163  // Read the designated section header.
3164  status = IntKernVirtMemRead(ImageBase + secheadersRva + (sizeof(IMAGE_SECTION_HEADER) * i),
3165  sizeof(IMAGE_SECTION_HEADER), &hSec, NULL);
3166  if (!INT_SUCCESS(status))
3167  {
3168  return status;
3169  }
3170 
3171  if (!IgnoreSectionHint && 0 != memcmp(hSec.Name, Pattern->SectionHint, 8))
3172  {
3173  continue;
3174  }
3175 
3176  // Try to find the pattern inside this section.
3177  for (rva = hSec.VirtualAddress; rva < hSec.VirtualAddress + hSec.Misc.VirtualSize; rva += 0x1000)
3178  {
3179  if (rva >= peInfo.SizeOfImage)
3180  {
3181  break;
3182  }
3183 
3184  status = IntVirtMemMap(ImageBase + rva, PAGE_SIZE, cr3, 0, &pPage1);
3185  if (!INT_SUCCESS(status))
3186  {
3187  continue;
3188  }
3189 
3190  // Try to match the sig in this current page.
3191  origStart = j = k = 0;
3192  while (k < PAGE_SIZE)
3193  {
3194  origStart = k;
3195 
3196  j = 0;
3197 
3198  while ((k < PAGE_SIZE) && (j < Pattern->Signature.Length) &&
3199  ((pPage1[k] == Pattern->Signature.Pattern[j]) ||
3200  (0x100 == Pattern->Signature.Pattern[j])))
3201  {
3202  k++;
3203  j++;
3204  }
3205 
3206  // Handle page boundary access
3207  if (k == PAGE_SIZE)
3208  {
3209  DWORD l;
3210 
3211  // We must map the next page
3212  status = IntVirtMemMap(ImageBase + rva + PAGE_SIZE, PAGE_SIZE, cr3, 0, &pPage2);
3213  if (!INT_SUCCESS(status))
3214  {
3215  break;
3216  }
3217 
3218  l = 0;
3219 
3220  while ((l < PAGE_SIZE) && (j < Pattern->Signature.Length) &&
3221  ((pPage2[l] == Pattern->Signature.Pattern[j]) ||
3222  (0x100 == Pattern->Signature.Pattern[j])))
3223  {
3224  l++;
3225  j++;
3226  }
3227 
3228  IntVirtMemUnmap(&pPage2);
3229  }
3230 
3231  if (j != Pattern->Signature.Length)
3232  {
3233  k = origStart;
3234  }
3235  else
3236  {
3237  bFound = TRUE;
3238  break;
3239  }
3240 
3241  k++;
3242  }
3243 
3244  IntVirtMemUnmap(&pPage1);
3245 
3246  if (bFound)
3247  {
3248  *Rva = rva + origStart;
3249  if (IgnoreSectionHint)
3250  {
3251  TRACE("[DEBUG] Found function inside section %lld:%s, was supposed to find in %s\n",
3252  i, hSec.Name, Pattern->SectionHint);
3253  }
3254 
3255  return INT_STATUS_SUCCESS;
3256  }
3257  }
3258  }
3259 
3260  return INT_STATUS_NOT_FOUND;
3261 }
3262 
3263 
3264 INTSTATUS
3266  _In_ QWORD ImageBase,
3267  _In_opt_ BYTE *ImageBaseBuffer,
3268  _In_ DWORD Rva,
3269  _Out_ DWORD *BeginAddress
3270  )
3288 {
3289  INTRO_PE_INFO peInfo = {0};
3290  BYTE *map;
3291  BOOLEAN found;
3292  INTSTATUS status;
3293  IMAGE_SECTION_HEADER sectionHeader = {0};
3294  QWORD cr3;
3295 
3296  if (NULL == BeginAddress)
3297  {
3299  }
3300 
3301  *BeginAddress = 0;
3302  found = FALSE;
3303 
3304  if (NULL != ImageBaseBuffer)
3305  {
3306  map = ImageBaseBuffer;
3307  }
3308  else
3309  {
3310  status = IntVirtMemMap(ImageBase, PAGE_SIZE, 0, 0, &map);
3311  if (!INT_SUCCESS(status))
3312  {
3313  return status;
3314  }
3315  }
3316 
3317  // Validate that this is a good module, and get the architecture
3318  status = IntPeValidateHeader(ImageBase, map, PAGE_SIZE, &peInfo, 0);
3319  if (!INT_SUCCESS(status))
3320  {
3321  goto leave;
3322  }
3323 
3324  // we ignore the errors, because maybe we want to search outside the driver
3325  status = IntPeGetSectionHeaderByRva(ImageBase, map, Rva, &sectionHeader);
3326  if (INT_SUCCESS(status))
3327  {
3328  if (0 == ((sectionHeader.Characteristics & IMAGE_SCN_CNT_CODE)) ||
3329  0 == ((sectionHeader.Characteristics & IMAGE_SCN_MEM_EXECUTE)))
3330  {
3331  status = INT_STATUS_NOT_SUPPORTED;
3332  goto leave;
3333  }
3334  }
3335 
3336  status = IntCr3Read(IG_CURRENT_VCPU, &cr3);
3337  if (!INT_SUCCESS(status))
3338  {
3339  ERROR("[ERROR] IntCr3Read failed: 0x%08x\n", status);
3340  goto leave;
3341  }
3342 
3343  // Tested on the whole kernel memory space on Windows 7 (both 64 and 32 bit) and not a single fail
3344  if (peInfo.Image64Bit)
3345  {
3346  RUNTIME_FUNCTION runtimeFunction;
3347 
3348  status = IntPeGetRuntimeFunction(ImageBase, map, Rva, &runtimeFunction);
3349  if (!INT_SUCCESS(status))
3350  {
3351  goto leave;
3352  }
3353 
3354  *BeginAddress = runtimeFunction.BeginAddress;
3355 
3356  // Parse all the unwind info structures and get to the actual beginning of the function
3357  status = IntPeParseUnwindData(ImageBase, map, &runtimeFunction, 0, NULL, BeginAddress, NULL, NULL, NULL);
3358  if (!INT_SUCCESS(status))
3359  {
3360  WARNING("[WARNING] IntPeParseUnwindData failed for 0x%08x: 0x%08x\n", *BeginAddress, status);
3361  }
3362 
3363  status = INT_STATUS_SUCCESS;
3364  }
3365  else
3366  {
3367  PBYTE pageMap, code;
3368  QWORD currentAddress, functionStart;
3369  DWORD bytesToScan;
3370  BOOLEAN physicalUnmap;
3371 
3372  currentAddress = ImageBase + Rva;
3373 
3374  status = IntVirtMemMap(currentAddress & PAGE_MASK, PAGE_SIZE, cr3, 0, &pageMap);
3375  if (!INT_SUCCESS(status))
3376  {
3377  ERROR("[ERROR] Failed mapping VA 0x%016llx to host: 0x%08x\n", currentAddress, status);
3378  goto leave;
3379  }
3380 
3381  // go where the RIP is
3382  code = pageMap + (currentAddress & PAGE_OFFSET);
3383 
3384  // see that we aren't going in the previous section. If we do, scan only from the section start
3385  if (sectionHeader.VirtualAddress != 0 &&
3386  (Rva - MAX_FUNC_LENGTH < sectionHeader.VirtualAddress))
3387  {
3388  bytesToScan = Rva - sectionHeader.VirtualAddress;
3389  }
3390  else
3391  {
3392  bytesToScan = MAX_FUNC_LENGTH;
3393  }
3394 
3395  functionStart = 0;
3396  physicalUnmap = FALSE;
3397 
3398  while (bytesToScan > 0)
3399  {
3400  NDSTATUS ndstatus;
3401  INSTRUX instruction;
3402  BYTE sepByte;
3403 
3404  // before the function there must be a series of NOP or INT3
3405  if (*code != 0xCC && *code != 0x90)
3406  {
3407  goto _next_bytes;
3408  }
3409 
3410  // Save a pointer to the function start (at current address is a NOP or a INT3)
3411  functionStart = currentAddress + 1;
3412 
3413  // This byte must repeat itself between the function
3414  sepByte = *code;
3415 
3416  // Get where the separating bytes will end
3417  while (*code == sepByte && bytesToScan > 1)
3418  {
3419  --code;
3420  --bytesToScan;
3421  --currentAddress;
3422 
3423  // We reached the end of the page. See if we can map further.
3424  // Since code starts as pageMap it should never go lower than pageMap
3425  if (code < pageMap)
3426  {
3427  QWORD prevPage;
3428 
3429  status = IntTranslateVirtualAddress(currentAddress & PAGE_MASK, cr3, &prevPage);
3430  if (!INT_SUCCESS(status))
3431  {
3432  // We must assume this is the beginning, since the previous page is not present. Also the
3433  // current page won't be unmapped
3434  break;
3435  }
3436 
3437  IntVirtMemUnmap(&pageMap);
3438 
3439  status = IntPhysMemMap(prevPage, PAGE_SIZE, 0, &pageMap);
3440  if (!INT_SUCCESS(status))
3441  {
3442  // This shouldn't fail since we checked that the previous page is present. So exit the
3443  // function if this happens and signal the error
3444  ERROR("[ERROR] Failed mapping VA 0x%016llx with GPA 0x%016llx to host: 0x%08x\n",
3445  currentAddress & PAGE_MASK, prevPage, status);
3446  goto leave;
3447  }
3448 
3449  physicalUnmap = TRUE;
3450  code = pageMap + (currentAddress & PAGE_OFFSET);
3451  }
3452  }
3453 
3454  // There must be more than 3 bytes separating
3455  if (functionStart - currentAddress < 3)
3456  {
3457  // The current one is still different from NOP and INT3 so decrement it again
3458  goto _next_bytes;
3459  }
3460 
3461  // If the previous instruction is a RET, don't check anymore
3462  if ((*code == 0xc3 || *code == 0xCB) || ((((QWORD)code & PAGE_OFFSET) >= 0x2) && (*(code - 2) == 0xc2)) ||
3463  // RET / imm16
3464  ((((QWORD)code & PAGE_OFFSET) >= 0x4) && (*(code - 4) == 0xe9 || *(code - 4) == 0xe8))) // CALL or JMP
3465  {
3466  found = TRUE;
3467  break;
3468  }
3469 
3470  // The end of the previous function and the start of the one we search are in different pages.
3471  // Or if the function start is at an offset too big (> 0xff0) that when we will try to decode will spill
3472  // in the next page
3473  if ((functionStart & PAGE_MASK) != (currentAddress & PAGE_MASK) || (functionStart & PAGE_OFFSET) >= 0xff0)
3474  {
3475  // If we get here, the next page should be present (CODE sections are not swappable!)
3476  status = IntDecDecodeInstruction(IG_CS_TYPE_32B, functionStart, &instruction);
3477  if (!INT_SUCCESS(status))
3478  {
3479  goto _next_bytes;
3480  }
3481  }
3482  else
3483  {
3484  // We are in the same page
3485  const QWORD pageMapOffset = functionStart & PAGE_OFFSET;
3486  ndstatus = NdDecodeEx(&instruction, pageMap + pageMapOffset, PAGE_SIZE - pageMapOffset,
3487  ND_CODE_32, ND_DATA_32);
3488  if (!ND_SUCCESS(ndstatus))
3489  {
3490  goto _next_bytes;
3491  }
3492  }
3493 
3494 
3495  // a lot of functions start with a movzx
3496  if ((instruction.Instruction == ND_INS_MOV || instruction.Instruction == ND_INS_MOVZX ||
3497  instruction.Instruction == ND_INS_MOVS || instruction.Instruction == ND_INS_MOVSXD ||
3498  instruction.Instruction == ND_INS_MOVNTI) &&
3499  instruction.HasModRm)
3500  {
3501  if (instruction.Operands[0].Size != 4) // Must operate on a whole register, not just a part.
3502  {
3503  goto _next_bytes;
3504  }
3505 
3506  if ((instruction.ModRm.reg == 5 && instruction.ModRm.rm == 4) || // mov ebp, esp
3507  (instruction.ModRm.reg == 7 && instruction.ModRm.rm == 7)) // mod edi, edi
3508  {
3509  found = TRUE;
3510  break;
3511  }
3512 
3513  goto _next_bytes;
3514  }
3515 
3516  if ((ND_CAT_PUSH == instruction.Category) || (ND_INS_XOR == instruction.Instruction))
3517  {
3518  found = TRUE;
3519  break;
3520  }
3521 
3522 _next_bytes:
3523  --currentAddress;
3524  --code;
3525  --bytesToScan;
3526 
3527  // We reached the end of the page. See if we can map further.
3528  // Since code starts as pageMap it should never go lower than pageMap
3529  if (code < pageMap)
3530  {
3531  QWORD prevPage;
3532 
3533  status = IntTranslateVirtualAddress(currentAddress & PAGE_MASK, cr3, &prevPage);
3534  if (!INT_SUCCESS(status))
3535  {
3536  // The function will exit with INT_STATUS_NOT_FOUND since the start wasn't found and we cannot scan
3537  // further
3538  break;
3539  }
3540 
3541  IntVirtMemUnmap(&pageMap);
3542 
3543  status = IntPhysMemMap(prevPage, PAGE_SIZE, 0, &pageMap);
3544  if (!INT_SUCCESS(status))
3545  {
3546  // This shouldn't fail since we checked that the previous page is present. So exit the
3547  // function if this happens and signal the error
3548  ERROR("[ERROR] Failed mapping VA 0x%016llx with GPA 0x%016llx to host: 0x%08x\n",
3549  currentAddress & PAGE_MASK, prevPage, status);
3550  goto leave;
3551  }
3552 
3553  physicalUnmap = TRUE;
3554  code = pageMap + (currentAddress & PAGE_OFFSET);
3555  }
3556  }
3557 
3558  // Clean the memory
3559  if (pageMap != NULL && physicalUnmap)
3560  {
3561  IntPhysMemUnmap(&pageMap);
3562  }
3563  else if (pageMap != NULL)
3564  {
3565  IntVirtMemUnmap(&pageMap);
3566  }
3567 
3568  if (!found)
3569  {
3570  status = INT_STATUS_NOT_FOUND;
3571  goto leave;
3572  }
3573  else
3574  {
3575  status = INT_STATUS_SUCCESS;
3576  }
3577 
3578  *BeginAddress = 0xffffffff & (functionStart - ImageBase);
3579  }
3580 
3581 leave:
3582  if (NULL == ImageBaseBuffer)
3583  {
3584  IntVirtMemUnmap(&map);
3585  }
3586 
3587  return status;
3588 }
3589 
3590 
3591 INTSTATUS
3593  _In_ QWORD ImageBase,
3594  _In_ BYTE *Buffer,
3595  _In_ DWORD BufferSize,
3596  _In_ DWORD Rva,
3597  _Out_ DWORD *BeginAddress
3598  )
3617 {
3618  INTRO_PE_INFO peInfo = { 0 };
3619  BOOLEAN found;
3620  INTSTATUS status;
3621  IMAGE_SECTION_HEADER sectionHeader = { 0 };
3622 
3623  if (NULL == Buffer)
3624  {
3626  }
3627 
3628  if (0 == BufferSize)
3629  {
3631  }
3632 
3633  if (NULL == BeginAddress)
3634  {
3636  }
3637 
3638  if (Rva >= BufferSize)
3639  {
3641  }
3642 
3643  *BeginAddress = 0;
3644  found = FALSE;
3645 
3646  // Validate that this is a good module, and get the architecture
3647  status = IntPeValidateHeader(ImageBase, Buffer, PAGE_SIZE, &peInfo, 0);
3648  if (!INT_SUCCESS(status))
3649  {
3650  return status;
3651  }
3652 
3653  // we ignore the errors, because maybe we want to search outside the driver
3654  status = IntPeGetSectionHeaderByRva(ImageBase, Buffer, Rva, &sectionHeader);
3655  if (INT_SUCCESS(status))
3656  {
3657  if (0 == (sectionHeader.Characteristics & IMAGE_SCN_MEM_EXECUTE))
3658  {
3659  return INT_STATUS_NOT_SUPPORTED;
3660  }
3661  }
3662 
3663  // Tested on the whole kernel memory space on Windows 7 (both 64 and 32 bit) and not a single fail
3664  if (peInfo.Image64Bit)
3665  {
3666  RUNTIME_FUNCTION runtimeFunction;
3667 
3668  status = IntPeGetRuntimeFunctionInBuffer(ImageBase, Buffer, BufferSize, Rva, &runtimeFunction);
3669  if (!INT_SUCCESS(status))
3670  {
3671  return status;
3672  }
3673 
3674  *BeginAddress = runtimeFunction.BeginAddress;
3675 
3676  // Parse all the unwind info structures and get to the actual beginning of the function
3677  status = IntPeParseUnwindDataInBuffer(ImageBase, Buffer, BufferSize,
3678  &runtimeFunction, 0, NULL, BeginAddress, NULL, NULL, NULL);
3679  if (!INT_SUCCESS(status))
3680  {
3681  WARNING("[WARNING] IntPeParseUnwindDataInBuffer failed for 0x%08x: 0x%08x\n", *BeginAddress, status);
3682  }
3683 
3684  return INT_STATUS_SUCCESS;
3685  }
3686 
3687  {
3688  BYTE *code = Buffer + Rva;
3689  QWORD functionStartRva = 0;
3690  DWORD bytesToScan;
3691  QWORD currentRva = Rva;
3692 
3693  // see that we aren't going in the previous section. If we do, scan only from the section start
3694  if (sectionHeader.VirtualAddress != 0 &&
3695  (Rva - MAX_FUNC_LENGTH < sectionHeader.VirtualAddress))
3696  {
3697  bytesToScan = Rva - sectionHeader.VirtualAddress;
3698  }
3699  else
3700  {
3701  bytesToScan = MAX_FUNC_LENGTH;
3702  }
3703 
3704  while (bytesToScan > 0)
3705  {
3706  NDSTATUS ndstatus;
3707  INSTRUX instruction;
3708  BYTE sepByte;
3709 
3710  if (code < Buffer)
3711  {
3712  break;
3713  }
3714 
3715  // before the function there must be a series of NOP or INT3
3716  if (*code != 0xCC && *code != 0x90)
3717  {
3718  goto _next_bytes;
3719  }
3720 
3721  // Save a pointer to the function start (at current address is a NOP or a INT3)
3722  functionStartRva = currentRva + 1;
3723 
3724  if (functionStartRva >= BufferSize)
3725  {
3726  break;
3727  }
3728 
3729  // This byte must repeat itself between the function
3730  sepByte = *code;
3731 
3732  // Get where the separating bytes will end
3733  while (code >= Buffer && *code == sepByte && bytesToScan)
3734  {
3735  --code;
3736  --bytesToScan;
3737  --currentRva;
3738  }
3739 
3740  if (code < Buffer)
3741  {
3742  break;
3743  }
3744 
3745  // There must be more than 3 bytes separating
3746  if (functionStartRva - currentRva < 3)
3747  {
3748  // The current one is still different from NOP and INT3 so decrement it again
3749  goto _next_bytes;
3750  }
3751 
3752  // If the previous instruction is a RET, don't check anymore
3753  if ((*code == 0xc3 || *code == 0xCB) ||
3754  ((((QWORD)code & PAGE_OFFSET) >= 0x2) && (*(code - 2) == 0xc2)) || // RET / imm16
3755  ((((QWORD)code & PAGE_OFFSET) >= 0x4) && (*(code - 4) == 0xe9 || *(code - 4) == 0xe8))) // CALL or JMP
3756  {
3757  found = TRUE;
3758  break;
3759  }
3760 
3761  ndstatus = NdDecodeEx(&instruction, Buffer + functionStartRva, BufferSize - functionStartRva,
3762  ND_CODE_32, ND_DATA_32);
3763  if (!ND_SUCCESS(ndstatus))
3764  {
3765  goto _next_bytes;
3766  }
3767 
3768 
3769  // a lot of functions start with a movzx
3770  if ((instruction.Instruction == ND_INS_MOV || instruction.Instruction == ND_INS_MOVZX ||
3771  instruction.Instruction == ND_INS_MOVS || instruction.Instruction == ND_INS_MOVSXD ||
3772  instruction.Instruction == ND_INS_MOVNTI) &&
3773  instruction.HasModRm)
3774  {
3775  if (instruction.Operands[0].Size != 4) // Must operate on a whole register, not just a part.
3776  {
3777  goto _next_bytes;
3778  }
3779 
3780  if ((instruction.ModRm.reg == 5 && instruction.ModRm.rm == 4) || // mov ebp, esp
3781  (instruction.ModRm.reg == 7 && instruction.ModRm.rm == 7)) // mod edi, edi
3782  {
3783  found = TRUE;
3784  break;
3785  }
3786 
3787  goto _next_bytes;
3788  }
3789 
3790  if ((ND_CAT_PUSH == instruction.Category) || (ND_INS_XOR == instruction.Instruction))
3791  {
3792  found = TRUE;
3793  break;
3794  }
3795 
3796 _next_bytes:
3797  --currentRva;
3798  --code;
3799  --bytesToScan;
3800  }
3801 
3802  if (!found)
3803  {
3804  return INT_STATUS_NOT_FOUND;
3805  }
3806 
3807  *BeginAddress = functionStartRva & 0xFFFFFFFF;
3808  }
3809 
3810  return INT_STATUS_SUCCESS;
3811 }
INTSTATUS IntPeFindFunctionByPattern(QWORD ImageBase, WIN_UNEXPORTED_FUNCTION_PATTERN *Pattern, BOOLEAN IgnoreSectionHint, DWORD *Rva)
Find a function using a pattern.
Definition: winpe.c:3118
#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:3592
#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:1758
#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:1078
#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:35
#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:207
#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:2037
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:1198
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:917
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:527
#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:3012
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
Describes a pattern for a kernel function that is not exported.
Definition: winguest.h:84
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:1561
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
union _IMAGE_SECTION_HEADER::@209 Misc
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
struct _INTRO_UNWIND_INFO::@222 UnwindCode[]
#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 TRACE(fmt,...)
Definition: glue.h:58
#define HpFreeAndNullWithTag(Add, Tag)
Definition: glue.h:517
#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:2370
QWORD KernelVa
The guest virtual address at which the kernel image.
Definition: guests.h:279
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
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:682
#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:448
#define PAGE_SIZE
Definition: common.h:53
#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:2242
#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:838
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:370
Structure describing relevant fields extracted from the optional header.
Definition: winpe.c:28
GUEST_STATE gGuest
The current guest state.
Definition: guests.c:48
#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:1432
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:3265
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
struct _OPTIONAL_HEADER_INFO * POPTIONAL_HEADER_INFO
#define __likely(x)
Definition: common.h:46
BYTE * KernelBuffer
A buffer containing the entire kernel image.
Definition: winguest.h:837
#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:1287
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:2699
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:1936
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:813
#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:1723
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