diff --git a/README.md b/README.md new file mode 100644 index 0000000..7d29f35 --- /dev/null +++ b/README.md @@ -0,0 +1,75 @@ +# Portable Executable Parser + +**peparser** is a go package for parsing the [portable executable](https://docs.microsoft.com/en-us/windows/win32/debug/pe-format) file format. This package was designed with malware analysis in mind, and being resistent to PE malformations. + +## Features + +- Works with PE32/PE32+ file fomat. +- Supports Intel x86/AMD64/ARM7ARM7 Thumb/ARM8-64/IA64/CHPE architectures. +- MS DOS header. +- Rich Header (calculate checksum). +- NT Header (file header + optional header). +- COFF symbol table and string table. +- Sections headers + entropy calculation. +- Data directories + - Import Table + ImpHash calculation. + - Export Table + - Resource Table + - Exceptions Table + - Security Table + Authentihash calculation. + - Relocations Table + - Debug Table (CODEVIEW, POGO, VC FEATURE, REPRO, FPO, EXDLL CHARACTERISTICS debug types). + - TLS Table + - Load Config Directory (SEH, GFID, GIAT, Guard LongJumps, CHPE, Dynamic Value Reloc Table, Enclave Configuration, Volatile Metadata tables). + - Bound Import Table + - Delay Import Table + - COM Table (CLR Metadata Header, Metadata Table Streams) +- Report several anomalies + +## Installing + +Using peparser is easy. First, use `go get` to install the latest version +of the library. This command will install the `peparser` generator executable +along with the library and its dependencies: + + go get -u github.com/saferwall/pe + +Next, include `peparser` in your application: + +```go +import "github.com/saferwall/pe" +``` + +## Using the library + +```go +package main + +import ( + peparser "github.com/saferwall/pe" +) + +func main() { + pe, err := peparser.New("C:\\Binaries\\notepad.exe", nil) + if err != nil { + log.Fatalf("Error while opening file: %s, reason: %s", filename, err) + } + + err = pe.Parse() + if err != nil { + log.Fatalf("Error while opening file: %s, reason: %s", filename, err) + } +``` + +## Todo: + +- imports MS-styled names demangling +- PE: VB5 and VB6 typical structures: project info, DLLCall-imports, referenced modules, object table + +# References + +- [Peering Inside the PE: A Tour of the Win32 Portable Executable File Format by Matt Pietrek](http://bytepointer.com/resources/pietrek_peering_inside_pe.htm) +- [An In-Depth Look into the Win32 Portable Executable File Format - Part 1 by Matt Pietrek](http://www.delphibasics.info/home/delphibasicsarticles/anin-depthlookintothewin32portableexecutablefileformat-part1) +- [An In-Depth Look into the Win32 Portable Executable File Format - Part 2 by Matt Pietrek](http://www.delphibasics.info/home/delphibasicsarticles/anin-depthlookintothewin32portableexecutablefileformat-part2) +- [Portable Executable File Format](https://blog.kowalczyk.info/articles/pefileformat.html) +- [PE Format MSDN spec](https://docs.microsoft.com/en-us/windows/win32/debug/pe-format) \ No newline at end of file diff --git a/anomaly.go b/anomaly.go new file mode 100644 index 0000000..8219952 --- /dev/null +++ b/anomaly.go @@ -0,0 +1,202 @@ +// Copyright 2021 Saferwall. All rights reserved. +// Use of this source code is governed by Apache v2 license +// license that can be found in the LICENSE file. + +package pe + +import ( + "encoding/binary" + "time" +) + +// Anomalies found in a PE +var ( + + // AnoPEHeaderOverlapDOSHeader is reported when the PE headers overlaps with + // the DOS header. + AnoPEHeaderOverlapDOSHeader = "PE Header overlaps with DOS header" + + // AnoPETimeStampNull is reported when the file header timestamp is 0. + AnoPETimeStampNull = "File Header timestamp set to 0" + + // AnoPETimeStampFuture is reported when the file header timestamp is more + // than one day ahead of the current date timestamp. + AnoPETimeStampFuture = "File Header timestamp set to 0" + + // NumberOfSections is reported when number of sections is larger or equal than 10. + AnoNumberOfSections10Plus = "Number of sections is 10+" + + // AnoNumberOfSectionsNull is reported when sections count's is 0. + AnoNumberOfSectionsNull = "Number of sections is 0" + + // AnoSizeOfOptionalHeaderNull is reported when size of optional header is 0. + AnoSizeOfOptionalHeaderNull = "Size of optional header is 0" + + // AnoUncommonSizeOfOptionalHeader32 is reported when size of optional + // header for PE32 is larger than 0xE0. + AnoUncommonSizeOfOptionalHeader32 = `Size of optional header is larger than + 0xE0 (PE32)` + + // AnoUncommonSizeOfOptionalHeader64 is reported when size of optional + // header for PE32+ is larger than 0xF0. + AnoUncommonSizeOfOptionalHeader64 = `Size of optional header is larger than + 0xF0 (PE32+)` + + // AnoAddressOfEntryPointNull is reported when address of entry point is 0. + AnoAddressOfEntryPointNull = "Address of entry point is 0." + + // AnoAddressOfEPLessSizeOfHeaders is reported when address of entry point + // is smaller than size of headers, the file cannot run under Windows. + AnoAddressOfEPLessSizeOfHeaders = `Address of entry point is smaller than + size of headers, the file cannot run under Windows 8` + + // AnoImageBaseNull is reported when the image base is null + AnoImageBaseNull = "Image base is 0" + + // AnoDanSMagicOffset is reported when the `DanS` magic offset is different + // than 0x80. + AnoDanSMagicOffset = "`DanS` magic offset is different than 0x80" + + // ErrInvalidFileAlignment is reported when file alignment is larger than + // 0x200 and not a power of 2. + ErrInvalidFileAlignment = "FileAlignment larger than 0x200 and not a power of 2" + + // ErrInvalidSectionAlignment is reported when file alignment is lesser + // than 0x200 and different from section alignment. + ErrInvalidSectionAlignment = `FileAlignment lesser than 0x200 and different + from section alignment` + + // AnoMajorSubsystemVersion is reported when MajorSubsystemVersion has a + // value different than the standard 3 --> 6. + AnoMajorSubsystemVersion = `MajorSubsystemVersion is outside 3<-->6 boundary` + + // AnonWin32VersionValue is reported when Win32VersionValue is different than 0 + AnonWin32VersionValue = `Win32VersionValue is a reserved field, should be + normally set to 0x0.` + + // AnoInvalidPEChecksum is reported when the optional header checksum field + // is different from what it should normally be. + AnoInvalidPEChecksum = `Optional header checksum is invalid.` + + // AnoNumberOfRvaAndSizes is reported when NumberOfRvaAndSizes is different + // than 16. + AnoNumberOfRvaAndSizes = `Optional header NumberOfRvaAndSizes != 16` +) + +// GetAnomalies reportes anomalies found in a PE binary. +// These nomalies does prevent the Windows loader from loading the files but +// is an interesting features for malware analysis. +func (pe *File) GetAnomalies() error { + + // ******************** Anomalies in File header ************************ + // An application for Windows NT typically has the nine predefined sections + // named: .text, .bss, .rdata, .data, .rsrc, .edata, .idata, .pdata, and + // .debug. Some applications do not need all of these sections, while + // others may define still more sections to suit their specific needs. + // NumberOfSections can be up to 96 under XP. + // NumberOfSections can be up to 65535 under Vista and later. + if pe.NtHeader.FileHeader.NumberOfSections >= 10 { + pe.Anomalies = append(pe.Anomalies, AnoNumberOfSections10Plus) + } + + // File header timestamp set to 0. + if pe.NtHeader.FileHeader.TimeDateStamp == 0 { + pe.Anomalies = append(pe.Anomalies, AnoPETimeStampNull) + } + + // File header timestamp set to the future. + now := time.Now() + future := uint32(now.Add(24 * time.Hour).Unix()) + if pe.NtHeader.FileHeader.TimeDateStamp > future { + pe.Anomalies = append(pe.Anomalies, AnoPETimeStampFuture) + } + + // NumberOfSections can be null with low alignment PEs + // and in this case, the values are just checked but not really used (under XP) + if pe.NtHeader.FileHeader.NumberOfSections == 0 { + pe.Anomalies = append(pe.Anomalies, AnoNumberOfSectionsNull) + } + + // SizeOfOptionalHeader is not the size of the optional header, but the delta + // between the top of the Optional header and the start of the section table. + // Thus, it can be null (the section table will overlap the Optional Header, + // or can be null when no sections are present) + if pe.NtHeader.FileHeader.SizeOfOptionalHeader == 0 { + pe.Anomalies = append(pe.Anomalies, AnoSizeOfOptionalHeaderNull) + } + + // SizeOfOptionalHeader can be bigger than the file + // (the section table will be in virtual space, full of zeroes), but can't be negative. + // Do some check here. + oh32 := ImageOptionalHeader32{} + oh64 := ImageOptionalHeader64{} + + // SizeOfOptionalHeader standard value is 0xE0 for PE32. + if pe.Is32 && + pe.NtHeader.FileHeader.SizeOfOptionalHeader > uint16(binary.Size(oh32)) { + pe.Anomalies = append(pe.Anomalies, AnoUncommonSizeOfOptionalHeader32) + } + + // SizeOfOptionalHeader standard value is 0xF0 for PE32+. + if pe.Is64 && + pe.NtHeader.FileHeader.SizeOfOptionalHeader > uint16(binary.Size(oh64)) { + pe.Anomalies = append(pe.Anomalies, AnoUncommonSizeOfOptionalHeader64) + } + + // ***************** Anomalies in Optional header ********************* + // Under Windows 8, AddressOfEntryPoint is not allowed to be smaller than + // SizeOfHeaders, except if it's null. + switch pe.Is64 { + case true: + oh64 = pe.NtHeader.OptionalHeader.(ImageOptionalHeader64) + case false: + oh32 = pe.NtHeader.OptionalHeader.(ImageOptionalHeader32) + } + + // Use oh for fields which are common for both structures. + oh := oh32 + if oh.AddressOfEntryPoint != 0 && oh.AddressOfEntryPoint < oh.SizeOfHeaders { + pe.Anomalies = append(pe.Anomalies, AnoAddressOfEPLessSizeOfHeaders) + } + + // AddressOfEntryPoint can be null in DLLs: in this case, + // DllMain is just not called. can be null + if oh.AddressOfEntryPoint == 0 { + pe.Anomalies = append(pe.Anomalies, AnoAddressOfEntryPointNull) + } + + // ImageBase can be null, under XP. + // In this case, the binary will be relocated to 10000h + if (pe.Is64 && oh64.ImageBase == 0) || + (pe.Is32 && oh32.ImageBase == 0) { + pe.Anomalies = append(pe.Anomalies, AnoImageBaseNull) + } + + // For DLLs, MajorSubsystemVersion is ignored until Windows 8. It can have + // any value. Under Windows 8, it needs a standard value (3.10 < 6.30). + if oh.MajorSubsystemVersion < 3 || oh.MajorSubsystemVersion > 6 { + pe.Anomalies = append(pe.Anomalies, AnoMajorSubsystemVersion) + } + + // Win32VersionValue officially defined as `reserved` and should be null + // if non null, it overrides MajorVersion/MinorVersion/BuildNumber/PlatformId + // OperatingSystem Versions values located in the PEB, after loading. + if oh.Win32VersionValue != 0 { + pe.Anomalies = append(pe.Anomalies, AnonWin32VersionValue) + } + + // Checksums are required for kernel-mode drivers and some system DLLs. + // Otherwise, this field can be 0. + if pe.Checksum() != oh.CheckSum && oh.CheckSum != 0 { + pe.Anomalies = append(pe.Anomalies, AnoInvalidPEChecksum) + } + + // This field contains the number of IMAGE_DATA_DIRECTORY entries. + // This field has been 16 since the earliest releases of Windows NT. + if (pe.Is64 && oh64.NumberOfRvaAndSizes == 0xA) || + (pe.Is32 && oh32.NumberOfRvaAndSizes == 0xA) { + pe.Anomalies = append(pe.Anomalies, AnoNumberOfRvaAndSizes) + } + + return nil +} diff --git a/arch.go b/arch.go new file mode 100644 index 0000000..e00247d --- /dev/null +++ b/arch.go @@ -0,0 +1,11 @@ +// Copyright 2021 Saferwall. All rights reserved. +// Use of this source code is governed by Apache v2 license +// license that can be found in the LICENSE file. + +package pe + +// Architecture-specific data. This data directory is not used +// (set to all zeros) for I386, IA64, or AMD64 architecture. +func (pe *File) parseArchitectureDirectory(rva, size uint32) error { + return nil +} diff --git a/boundimports.go b/boundimports.go new file mode 100644 index 0000000..414a5a8 --- /dev/null +++ b/boundimports.go @@ -0,0 +1,151 @@ +// Copyright 2021 Saferwall. All rights reserved. +// Use of this source code is governed by Apache v2 license +// license that can be found in the LICENSE file. + +package pe + +import ( + "bytes" + "encoding/binary" + "log" +) + +const ( + // MaxStringLength represents the maximum length of a string to be retrieved + // from the file. It's there to prevent loading massive amounts of data from + // memory mapped files. Strings longer than 0x100B should be rather rare. + MaxStringLength = uint32(0x100) +) + +// ImageBoundImportDescriptor represents the IMAGE_BOUND_IMPORT_DESCRIPTOR. +type ImageBoundImportDescriptor struct { + TimeDateStamp uint32 // is just the value from the Exports information of the DLL which is being imported from. + OffsetModuleName uint16 // offset of the DLL name counted from the beginning of the BOUND_IMPORT table + NumberOfModuleForwarderRefs uint16 // number of forwards + // Array of zero or more IMAGE_BOUND_FORWARDER_REF follows +} + +// ImageBoundForwardedRef represents the IMAGE_BOUND_FORWARDER_REF. +type ImageBoundForwardedRef struct { + TimeDateStamp uint32 + OffsetModuleName uint16 + Reserved uint16 +} + +// BoundImportDescriptorData represents the descripts in addition to forwarded refs. +type BoundImportDescriptorData struct { + Struct ImageBoundImportDescriptor + Name string + ForwardedRefs []BoundForwardedRefData +} + +// BoundForwardedRefData reprents the struct in addition to the dll name. +type BoundForwardedRefData struct { + Struct ImageBoundForwardedRef + Name string +} + +// This table is an array of bound import descriptors, each of which describes +// a DLL this image was bound up with at the time of the image creation. +// The descriptors also carry the time stamps of the bindings, and if the +// bindings are up-to-date, the OS loader uses these bindings as a “shortcut” +// for API import. Otherwise, the loader ignores the bindings and resolves the +// imported APIs through the Import tables. +func (pe *File) parseBoundImportDirectory(rva, size uint32) (err error) { + var sectionsAfterOffset []uint32 + var safetyBoundary uint32 + var start = rva + + for { + bndDesc := ImageBoundImportDescriptor{} + bndDescSize := uint32(binary.Size(bndDesc)) + buf := bytes.NewReader(pe.data[rva : rva+bndDescSize]) + err := binary.Read(buf, binary.LittleEndian, &bndDesc) + // If the RVA is invalid all would blow up. Some EXEs seem to be + // specially nasty and have an invalid RVA. + if err != nil { + return err + } + + // If the structure is all zeros, we reached the end of the list. + if bndDesc == (ImageBoundImportDescriptor{}) { + break + } + + rva += bndDescSize + sectionsAfterOffset = nil + + fileOffset := pe.getOffsetFromRva(rva) + section := pe.getSectionByRva(rva) + if section == nil { + safetyBoundary = pe.size - fileOffset + for _, section := range pe.Sections { + if section.Header.PointerToRawData > fileOffset { + sectionsAfterOffset = append( + sectionsAfterOffset, section.Header.PointerToRawData) + } + } + if len(sectionsAfterOffset) > 0 { + // Find the first section starting at a later offset than that + // specified by 'rva' + firstSectionAfterOffset := Min(sectionsAfterOffset) + section = pe.getSectionByOffset(firstSectionAfterOffset) + if section != nil { + safetyBoundary = section.Header.PointerToRawData - fileOffset + } + } + } else { + sectionLen := uint32(len(section.Data(0, 0, pe))) + safetyBoundary = (section.Header.PointerToRawData + sectionLen) - fileOffset + } + + if section == nil { + log.Printf("RVA of IMAGE_BOUND_IMPORT_DESCRIPTOR points to an invalid address: 0x%x", rva) + return nil + } + + bndFrwdRef := ImageBoundForwardedRef{} + bndFrwdRefSize := uint32(binary.Size(bndFrwdRef)) + count := min(uint32(bndDesc.NumberOfModuleForwarderRefs), safetyBoundary/bndFrwdRefSize) + + var forwarderRefs []BoundForwardedRefData + for i := uint32(0); i < count; i++ { + buf := bytes.NewReader(pe.data[rva : rva+bndFrwdRefSize]) + err := binary.Read(buf, binary.LittleEndian, &bndFrwdRef) + if err != nil { + return err + } + + rva += bndFrwdRefSize + + offset := start + uint32(bndFrwdRef.OffsetModuleName) + DllNameBuff := string(pe.getStringFromData(0, pe.data[offset:offset+MaxStringLength])) + DllName := string(DllNameBuff) + + // OffsetModuleName points to a DLL name. These shouldn't be too long. + // Anything longer than a safety length of 128 will be taken to indicate + // a corrupt entry and abort the processing of these entries. + // Names shorter than 4 characters will be taken as invalid as well. + if DllName != "" && (len(DllName) > 256 || !IsPrintable(DllName)) { + break + } + + forwarderRefs = append(forwarderRefs, BoundForwardedRefData{ + Struct: bndFrwdRef, Name: DllName}) + } + + offset := start + uint32(bndDesc.OffsetModuleName) + DllNameBuff := pe.getStringFromData(0, pe.data[offset:offset+MaxStringLength]) + DllName := string(DllNameBuff) + if DllName != "" && (len(DllName) > 256 || !IsPrintable(DllName)) { + break + } + + pe.BoundImports = append(pe.BoundImports, BoundImportDescriptorData{ + Struct: bndDesc, + Name: DllName, + ForwardedRefs: forwarderRefs}) + } + + return nil +} diff --git a/debug.go b/debug.go new file mode 100644 index 0000000..0485689 --- /dev/null +++ b/debug.go @@ -0,0 +1,615 @@ +// Copyright 2021 Saferwall. All rights reserved. +// Use of this source code is governed by Apache v2 license +// license that can be found in the LICENSE file. + +package pe + +import ( + "encoding/binary" + "errors" + "fmt" +) + +// The following values are defined for the Type field of the debug directory entry: +const ( + // An unknown value that is ignored by all tools. + ImageDebugTypeUnknown = 0 + + // The COFF debug information (line numbers, symbol table, and string table). + // This type of debug information is also pointed to by fields in the file headers. + ImageDebugTypeCOFF = 1 + + // The Visual C++ debug information. + ImageDebugTypeCodeview = 2 + + // The frame pointer omission (FPO) information. This information tells the + // debugger how to interpret nonstandard stack frames, which use the EBP + // register for a purpose other than as a frame pointer. + ImageDebugTypeFPO = 3 + + // The location of DBG file. + ImageDebugTypeMisc = 4 + + // A copy of .pdata section. + ImageDebugTypeException = 5 + + // Reserved. + ImageDebugTypeFixup = 6 + + // The mapping from an RVA in image to an RVA in source image. + ImageDebugTypeOmapToSrc = 7 + + // The mapping from an RVA in source image to an RVA in image. + ImageDebugTypeOmapFromSrc = 8 + + // Reserved for Borland. + ImageDebugTypeBorland = 9 + + // Reserved. + ImageDebugTypeReserved10 = 10 + + // Reserved. + ImageDebugTypeClsid = 11 + + // Visual C++ features (/GS counts /sdl counts and guardN counts) + ImageDebugTypeVCFeature = 12 + + // Profile Guided Optimization + ImageDebugTypePOGO = 13 + + // Incremental Link Time Code Generation (iLTCG) + ImageDebugTypeILTCG = 14 + + // Intel MPX + ImageDebugTypeMPX = 15 + + // PE determinism or reproducibility. + ImageDebugTypeRepro = 16 + + // Extended DLL characteristics bits. + ImageDebugTypeExDllCharacteristics = 20 +) + +const ( + // CVSignatureRSDS represents the CodeView signature 'SDSR'. + CVSignatureRSDS = 0x53445352 + + // CVSignatureNB10 represents the CodeView signature 'NB10'. + CVSignatureNB10 = 0x3031424e +) + +const ( + // FrameFPO indicates a frame of type FPO. + FrameFPO = 0x0 + + // FrameTrap indicates a frame of type Trap. + FrameTrap = 0x1 + + // FrameTSS indicates a frame of type TSS. + FrameTSS = 0x2 + + // FrameNonFPO indicates a frame of type Non-FPO. + FrameNonFPO = 0x3 +) + +const ( + // mage is CET compatible. + ImageDllCharacteristicsExCETCompat = 0x0001 +) + +const ( + POGOTypePGU = 0x50475500 + POGzOTypePGI = 0x50474900 + POGOTypePGO = 0x50474F00 + POGOTypeLTCG = 0x4c544347 +) + +// ImageDebugDirectory represents the IMAGE_DEBUG_DIRECTORY structure. +// This directory indicates what form of debug information is present +// and where it is. This directory consists of an array of debug directory +// entries whose location and size are indicated in the image optional header. +type ImageDebugDirectory struct { + // Reserved, must be 0. + Characteristics uint32 + + // The time and date that the debug data was created. + TimeDateStamp uint32 + + // The major version number of the debug data format. + MajorVersion uint16 + + // The minor version number of the debug data format. + MinorVersion uint16 + + // The format of debugging information. This field enables support of + // multiple debuggers. + Type uint32 + + // The size of the debug data (not including the debug directory itself). + SizeOfData uint32 + + //The address of the debug data when loaded, relative to the image base. + AddressOfRawData uint32 + + // The file pointer to the debug data. + PointerToRawData uint32 +} + +// DebugEntry wraps ImageDebugDirectory to include debug directory type. +type DebugEntry struct { + // Points to the image debug entry structure. + Struct ImageDebugDirectory + + // Holds specific information about the debug type entry. + Info interface{} +} + +// GUID is a 128-bit value consisting of one group of 8 hexadecimal digits, +// followed by three groups of 4 hexadecimal digits each, followed by one +//group of 12 hexadecimal digits. +type GUID struct { + Data1 uint32 + Data2 uint16 + Data3 uint16 + Data4 [8]byte +} + +// CvInfoPDB70 represents the the CodeView data block of a PDB 7.0 file. +type CvInfoPDB70 struct { + // CodeView signature, equal to `RSDS` + CvSignature uint32 + + // A unique identifier, which changes with every rebuild of the executable and PDB file. + Signature GUID + + // Ever-incrementing value, which is initially set to 1 and incremented every + // time when a part of the PDB file is updated without rewriting the whole file. + Age uint32 + + // Null-terminated name of the PDB file. It can also contain full or partial + // path to the file. + PDBFileName string +} + +// CVHeader represents the the CodeView header struct to the PDB 2.0 file. +type CVHeader struct { + // CodeView signature, equal to `NB10` + Signature uint32 + + // CodeView offset. Set to 0, because debug information is stored in a separate file. + Offset uint32 +} + +// CvInfoPDB20 represents the the CodeView data block of a PDB 2.0 file. +type CvInfoPDB20 struct { + // Points to the CodeView header structure. + CvHeader CVHeader + + // The time when debug information was created (in seconds since 01.01.1970) + Signature uint32 + + // Ever-incrementing value, which is initially set to 1 and incremented every + // time when a part of the PDB file is updated without rewriting the whole file. + Age uint32 + + // Null-terminated name of the PDB file. It can also contain full or partial + // path to the file. + PDBFileName string +} + +// FPOData Represents the stack frame layout for a function on an x86 computer when frame pointer omission (FPO) optimization is used. The structure is used to locate the base of the call frame. +type FPOData struct { + // The offset of the first byte of the function code. + OffStart uint32 + + // The number of bytes in the function. + ProcSize uint32 + + // The number of local variables. + NumLocals uint32 + + // The size of the parameters, in DWORDs. + ParamsSize uint16 + + // The number of bytes in the function prolog code. + PrologLength uint8 + + // The number of registers saved. + SavedRegsCount uint8 + + // A variable that indicates whether the function uses structured exception handling. + HasSEH uint8 + + // A variable that indicates whether the EBP register has been allocated. + UseBP uint8 + + // Reserved for future use. + Reserved uint8 + + // A variable that indicates the frame type. + FrameType uint8 +} + +type ImagePGOItem struct { + Rva uint32 + Size uint32 + Name string +} + +type POGO struct { + Signature uint32 // _IMAGE_POGO_INFO + Entries []ImagePGOItem +} + +type VCFeature struct { + PreVC11 uint32 `json:"Pre VC 11"` + CCpp uint32 `json:"C/C++"` + Gs uint32 `json:"/GS"` + Sdl uint32 `json:"/sdl"` + GuardN uint32 +} + +type REPRO struct { + Size uint32 + Hash []byte +} + +// ImageDebugMisc represents the IMAGE_DEBUG_MISC structure. +type ImageDebugMisc struct { + DataType uint32 // The type of data carried in the `Data` field. + + // The length of this structure in bytes, including the entire Data field + // and its NUL terminator (rounded to four byte multiple.) + Length uint32 + + // The encoding of the Data field. True if data is unicode string + Unicode bool + + // Reserved + Reserved [3]byte + + // Actual data + Data string +} + +// Image files contain an optional debug directory that indicates what form of +// debug information is present and where it is. This directory consists of an +// array of debug directory entries whose location and size are indicated in the +// image optional header. +// The debug directory can be in a discardable .debug section (if one exists), +// or it can be included in any other section in the image file, or not be in a +// section at all. +func (pe *File) parseDebugDirectory(rva, size uint32) error { + + // Define some vars. + debugDir := ImageDebugDirectory{} + debugEntry := DebugEntry{} + errorMsg := fmt.Sprintf("Invalid debug information. Can't read data at RVA: 0x%x", rva) + debugDirSize := uint32(binary.Size(debugDir)) + debugDirsCount := size / debugDirSize + + for i := uint32(0); i < debugDirsCount; i++ { + offset := pe.getOffsetFromRva(rva + debugDirSize*i) + err := pe.structUnpack(&debugDir, offset, debugDirSize) + if err != nil { + return errors.New(errorMsg) + } + + switch debugDir.Type { + case ImageDebugTypeCodeview: + debugSignature, err := pe.ReadUint32(debugDir.PointerToRawData) + if err != nil { + continue + } + + if debugSignature == CVSignatureRSDS { + // PDB 7.0 + pdb := CvInfoPDB70{CvSignature: CVSignatureRSDS} + + // Guid + offset := debugDir.PointerToRawData + 4 + guidSize := uint32(binary.Size(pdb.Signature)) + err = pe.structUnpack(&pdb.Signature, offset, guidSize) + if err != nil { + continue + } + // Age + offset += guidSize + pdb.Age, err = pe.ReadUint32(offset) + if err != nil { + continue + } + offset += 4 + + // PDB file name + pdbFilenameSize := debugDir.SizeOfData - 24 - 1 + + // pdbFileName_size can be negative here, as seen in the malware + // sample with MD5 hash: 7c297600870d026c014d42596bb9b5fd + // Checking for positive size here to ensure proper parsing. + if pdbFilenameSize > 0 { + pdbFilename := make([]byte, pdbFilenameSize) + err = pe.structUnpack(&pdbFilename, offset, pdbFilenameSize) + if err != nil { + continue + } + pdb.PDBFileName = string(pdbFilename) + } + + // Include these extra informations + debugEntry.Info = pdb + + } else if debugSignature == CVSignatureNB10 { + // PDB 2.0 + cvHeader := CVHeader{} + offset := debugDir.PointerToRawData + err = pe.structUnpack(&cvHeader, offset, size) + if err != nil { + continue + } + + pdb := CvInfoPDB20{CvHeader: cvHeader} + + // Signature + pdb.Signature, err = pe.ReadUint32(offset + 8) + if err != nil { + continue + } + + // Age + pdb.Age, err = pe.ReadUint32(offset + 12) + if err != nil { + continue + } + offset += 16 + + pdbFilenameSize := debugDir.SizeOfData - 16 - 1 + if pdbFilenameSize > 0 { + pdbFilename := make([]byte, pdbFilenameSize) + err = pe.structUnpack(&pdbFilename, offset, pdbFilenameSize) + if err != nil { + continue + } + pdb.PDBFileName = string(pdbFilename) + } + + // Include these extra informations + debugEntry.Info = pdb + } + case ImageDebugTypePOGO: + pogoSignature, err := pe.ReadUint32(debugDir.PointerToRawData) + if err != nil { + continue + } + + pogo := POGO{} + + switch pogoSignature { + case POGOTypePGU: + case POGzOTypePGI: + case POGOTypePGO: + case POGOTypeLTCG: + pogo.Signature = pogoSignature + offset = debugDir.PointerToRawData + 4 + c := uint32(0) + for c < debugDir.SizeOfData-4 { + + pogoEntry := ImagePGOItem{} + pogoEntry.Rva, err = pe.ReadUint32(offset) + if err != nil { + break + } + pogoEntry.Size, err = pe.ReadUint32(offset + 4) + if err != nil { + break + } + + pogoEntry.Name = string(pe.getStringFromData(0, pe.data[offset+8:offset+8+32])) + + pogo.Entries = append(pogo.Entries, pogoEntry) + c += 8 + uint32(len(pogoEntry.Name)) + 4 + offset += 8 + uint32(len(pogoEntry.Name)) + 4 + } + + debugEntry.Info = pogo + } + + case ImageDebugTypeVCFeature: + vcf := VCFeature{} + size := uint32(binary.Size(vcf)) + err := pe.structUnpack(&vcf, debugDir.PointerToRawData, size) + if err != nil { + continue + } + debugEntry.Info = vcf + + case ImageDebugTypeRepro: + repro := REPRO{} + offset := debugDir.PointerToRawData + + repro.Size, err = pe.ReadUint32(offset) + if err != nil { + continue + } + repro.Hash, err = pe.ReadBytesAtOffset(offset+4, repro.Size) + if err != nil { + continue + } + debugEntry.Info = repro + + case ImageDebugTypeFPO: + offset := debugDir.PointerToRawData + size := uint32(16) + fpoEntries := []FPOData{} + c := uint32(0) + for c < debugDir.SizeOfData { + fpo := FPOData{} + fpo.OffStart, err = pe.ReadUint32(offset) + if err != nil { + break + } + + fpo.ProcSize, err = pe.ReadUint32(offset + 4) + if err != nil { + break + } + + fpo.NumLocals, err = pe.ReadUint32(offset + 8) + if err != nil { + break + } + + fpo.ParamsSize, err = pe.ReadUint16(offset + 12) + if err != nil { + break + } + + fpo.PrologLength, err = pe.ReadUint8(offset + 14) + if err != nil { + break + } + + attributes, err := pe.ReadUint16(offset + 15) + if err != nil { + break + } + + // + // UChar cbRegs :3; /* # regs saved */ + // UChar fHasSEH:1; /* Structured Exception Handling */ + // UChar fUseBP :1; /* EBP has been used */ + // UChar reserved:1; + // UChar cbFrame:2; /* frame type */ + // + + // The lowest 3 bits + fpo.SavedRegsCount = uint8(attributes & 0x7) + + // The next bit. + fpo.HasSEH = uint8(attributes & 0x8 >> 3) + + // The next bit. + fpo.UseBP = uint8(attributes & 0x10 >> 4) + + // The next bit. + fpo.Reserved = uint8(attributes & 0x20 >> 5) + + // The next 2 bits. + fpo.FrameType = uint8(attributes & 0xC0 >> 6) + + fpoEntries = append(fpoEntries, fpo) + c += size + offset += 16 + } + debugEntry.Info = fpoEntries + case ImageDebugTypeExDllCharacteristics: + exllChar, err := pe.ReadUint32(debugDir.PointerToRawData) + if err != nil { + continue + } + + debugEntry.Info = exllChar + } + + debugEntry.Struct = debugDir + pe.Debugs = append(pe.Debugs, debugEntry) + } + + return nil +} + +// SectionAttributeDescription maps a section attribute to a friendly name. +func SectionAttributeDescription(section string) string { + sectionNameMap := map[string]string{ + ".CRT$XCA": "First C++ Initializer", + ".CRT$XCAA": "Startup C++ Initializer", + ".CRT$XCZ": "Last C++ Initializer", + ".CRT$XDA": "First Dynamic TLS Initializer", + ".CRT$XDZ": "Last Dynamic TLS Initializer", + ".CRT$XIA": "First C Initializer", + ".CRT$XIAA": "Startup C Initializer", + ".CRT$XIAB": "PGO C Initializer", + ".CRT$XIAC": "Post-PGO C Initializer", + ".CRT$XIC": "CRT C Initializers", + ".CRT$XIYA": "VCCorLib Threading Model Initializer", + ".CRT$XIYAA": "XAML Designer Threading Model Override Initializer", + ".CRT$XIYB": "VCCorLib Main Initializer", + ".CRT$XIZ": "Last C Initializer", + ".CRT$XLA": "First Loader TLS Callback", + ".CRT$XLC": "CRT TLS Constructor", + ".CRT$XLD": "CRT TLS Terminator", + ".CRT$XLZ": "Last Loader TLS Callback", + ".CRT$XPA": "First Pre-Terminator", + ".CRT$XPB": "CRT ConcRT Pre-Terminator", + ".CRT$XPX": "CRT Pre-Terminators", + ".CRT$XPXA": "CRT stdio Pre-Terminator", + ".CRT$XPZ": "Last Pre-Terminator", + ".CRT$XTA": "First Terminator", + ".CRT$XTZ": "Last Terminator", + ".CRTMA$XCA": "First Managed C++ Initializer", + ".CRTMA$XCZ": "Last Managed C++ Initializer", + ".CRTVT$XCA": "First Managed VTable Initializer", + ".CRTVT$XCZ": "Last Managed VTable Initializer", + ".rtc$IAA": "First RTC Initializer", + ".rtc$IZZ": "Last RTC Initializer", + ".rtc$TAA": "First RTC Terminator", + ".rtc$TZZ": "Last RTC Terminator", + ".text$x": "EH Filters", + ".text$di": "MSVC Dynamic Initializers", + ".text$yd": "MSVC Destructors", + ".text$mn": "Contains EP", + ".00cfg": "CFG Check Functions Pointers", + ".rdata$T": "TLS Header", + ".rdata$r": "RTTI Data", + ".data$r": "RTTI Type Descriptors", + ".rdata$sxdata": "Safe SEH", + ".rdata$zzzdbg": "Debug Data", + ".idata$2": "Import Descriptors", + ".idata$3": "Final Null Entry", + ".idata$4": "INT Array", + ".idata$5": "IAT Array", + ".idata$6": "Symbol and DLL names", + ".rsrc$01": "Resources Header", + ".rsrc$02": "Resources Data", + } + + if val, ok := sectionNameMap[section]; ok { + return val + } + + return "?" +} + +// FPOFrameTypePretty returns a string interpretation of the FPO frame type. +func FPOFrameTypePretty(ft uint8) string { + frameTypeMap := map[uint8]string{ + FrameFPO: "FPO", + FrameTrap: "Trap", + FrameTSS: "TSS", + FrameNonFPO: "NonFPO", + } + + v, ok := frameTypeMap[ft] + if ok { + return v + } + + return "?" +} + +// PrettyExtendedDLLCharacteristics maps dll char to string. +func PrettyExtendedDLLCharacteristics(characteristics uint32) []string { + + var values []string + + exDllCharacteristicsMap := map[uint32]string{ + ImageDllCharacteristicsExCETCompat: "CET Compatible", + } + for k, s := range exDllCharacteristicsMap { + if k&characteristics != 0 { + values = append(values, s) + } + } + + return values +} diff --git a/delayimports.go b/delayimports.go new file mode 100644 index 0000000..d684bff --- /dev/null +++ b/delayimports.go @@ -0,0 +1,152 @@ +// Copyright 2021 Saferwall. All rights reserved. +// Use of this source code is governed by Apache v2 license +// license that can be found in the LICENSE file. + +package pe + +import ( + "encoding/binary" +) + +// ImageDelayImportDescriptor represents the _IMAGE_DELAYLOAD_DESCRIPTOR structure. +type ImageDelayImportDescriptor struct { + // As yet, no attribute flags are defined. The linker sets this field to zero + // in the image. This field can be used to extend the record by indicating + // the presence of new fields, or it can be used to indicate behaviors to + // the delay or unload helper functions. + Attributes uint32 + + // The name of the DLL to be delay-loaded resides in the read-only data + // section of the image. It is referenced through the szName field. + Name uint32 + + // The handle of the DLL to be delay-loaded is in the data section of the + // image. The phmod field points to the handle. The supplied delay-load + // helper uses this location to store the handle to the loaded DLL. + ModuleHandleRVA uint32 + + // The delay import address table (IAT) is referenced by the delay import + // descriptor through the pIAT field. The delay-load helper updates these + // pointers with the real entry points so that the thunks are no longer in + // the calling loop + ImportAddressTableRVA uint32 + + // The delay import name table (INT) contains the names of the imports that + // might require loading. They are ordered in the same fashion as the + // function pointers in the IAT. + ImportNameTableRVA uint32 + + // The delay bound import address table (BIAT) is an optional table of + // IMAGE_THUNK_DATA items that is used along with the timestamp field + // of the delay-load directory table by a post-process binding phase. + BoundImportAddressTableRVA uint32 + + // The delay unload import address table (UIAT) is an optional table of + // IMAGE_THUNK_DATA items that the unload code uses to handle an explicit + // unload request. It consists of initialized data in the read-only section + // that is an exact copy of the original IAT that referred the code to the + // delay-load thunks. On the unload request, the library can be freed, + // the *phmod cleared, and the UIAT written over the IAT to restore + // everything to its preload state. + UnloadInformationTableRVA uint32 + + // 0 if not bound, otherwise, date/time of the target DLL. + TimeDateStamp uint32 +} + +// DelayImport represents an entry in the delay import table. +type DelayImport struct { + Offset uint32 + Name string + Functions []*ImportFunction + Descriptor ImageDelayImportDescriptor +} + +// Delay-Load Import Tables tables were added to the image to support a uniform +// mechanism for applications to delay the loading of a DLL until the first call +// into that DLL. The delay-load directory table is the counterpart to the +// import directory table. +func (pe *File) parseDelayImportDirectory(rva, size uint32) error { + for { + importDelayDesc := ImageDelayImportDescriptor{} + fileOffset := pe.getOffsetFromRva(rva) + importDescSize := uint32(binary.Size(importDelayDesc)) + err := pe.structUnpack(&importDelayDesc, fileOffset, importDescSize) + + // If the RVA is invalid all would blow up. Some EXEs seem to be + // specially nasty and have an invalid RVA. + if err != nil { + return err + } + + // If the structure is all zeros, we reached the end of the list. + if importDelayDesc == (ImageDelayImportDescriptor{}) { + break + } + + rva += importDescSize + + // If the array of thunks is somewhere earlier than the import + // descriptor we can set a maximum length for the array. Otherwise + // just set a maximum length of the size of the file + maxLen := uint32(len(pe.data)) - fileOffset + if rva > importDelayDesc.ImportNameTableRVA || + rva > importDelayDesc.ImportAddressTableRVA { + if rva < importDelayDesc.ImportNameTableRVA { + maxLen = rva - importDelayDesc.ImportAddressTableRVA + } else if rva < importDelayDesc.ImportAddressTableRVA { + maxLen = rva - importDelayDesc.ImportNameTableRVA + } else { + maxLen = Max(rva-importDelayDesc.ImportNameTableRVA, + rva-importDelayDesc.ImportAddressTableRVA) + } + } + + var importedFunctions []*ImportFunction + if pe.Is64 { + importedFunctions, err = pe.parseImports64(&importDelayDesc, maxLen) + } else { + importedFunctions, err = pe.parseImports32(&importDelayDesc, maxLen) + } + if err != nil { + return err + } + + nameRVA := uint32(0) + if importDelayDesc.Attributes == 0 { + nameRVA = importDelayDesc.Name - + pe.NtHeader.OptionalHeader.(ImageOptionalHeader32).ImageBase + } else { + nameRVA = importDelayDesc.Name + } + dllName := pe.getStringAtRVA(nameRVA, maxLen) + if !IsValidDosFilename(dllName) { + dllName = "*invalid*" + continue + } + + pe.DelayImports = append(pe.DelayImports, DelayImport{ + Offset: fileOffset, + Name: string(dllName), + Functions: importedFunctions, + Descriptor: importDelayDesc, + }) + + } + + return nil +} + +// GetDelayImportEntryInfoByRVA return an import function + index of the entry given +// an RVA. +func (pe *File) GetDelayImportEntryInfoByRVA(rva uint32) (DelayImport, int) { + for _, imp := range pe.DelayImports { + for i, entry := range imp.Functions { + if entry.ThunkRVA == rva { + return imp, i + } + } + } + + return DelayImport{}, 0 +} diff --git a/dosheader.go b/dosheader.go new file mode 100644 index 0000000..f221839 --- /dev/null +++ b/dosheader.go @@ -0,0 +1,107 @@ +// Copyright 2021 Saferwall. All rights reserved. +// Use of this source code is governed by Apache v2 license +// license that can be found in the LICENSE file. + +package pe + +import ( + "encoding/binary" +) + +// ImageDosHeader represents the DOS stub of a PE. +type ImageDosHeader struct { + // Magic number. + Magic uint16 + + // Bytes on last page of file. + BytesOnLastPageOfFile uint16 + + // Pages in file. + PagesInFile uint16 + + // Relocations. + Relocations uint16 + + // Size of header in paragraphs. + SizeOfHeader uint16 + + // Minimum extra paragraphs needed. + MinExtraParagraphsNeeded uint16 + + // Maximum extra paragraphs needed. + MaxExtraParagraphsNeeded uint16 + + // Initial (relative) SS value. + InitialSS uint16 + + // Initial SP value. + InitialSP uint16 + + // Checksum. + Checksum uint16 + + // Initial IP value. + InitialIP uint16 + + // Initial (relative) CS value. + InitialCS uint16 + + // File address of relocation table. + AddressOfRelocationTable uint16 + + // Overlay number. + OverlayNumber uint16 + + // Reserved words. + ReservedWords1 [4]uint16 + + // OEM identifier. + OEMIdentifier uint16 + + // OEM information. + OEMInformation uint16 + + // Reserved words. + ReservedWords2 [10]uint16 + + // File address of new exe header (Elfanew). + AddressOfNewEXEHeader uint32 +} + +// ParseDOSHeader parses the DOS header stub. Every PE file begins with a small +// MS-DOS stub. The need for this arose in the early days of Windows, before a +// significant number of consumers were running it. When executed on a machine +// without Windows, the program could at least print out amessage saying that +// Windows was required to run the executable. +func (pe *File) ParseDOSHeader() (err error) { + offset := uint32(0) + size := uint32(binary.Size(pe.DosHeader)) + err = pe.structUnpack(&pe.DosHeader, offset, size) + if err != nil { + return err + } + + // It can be ZM on an (non-PE) EXE. + // These executables still work under XP via ntvdm. + if pe.DosHeader.Magic != ImageDOSSignature && + pe.DosHeader.Magic != ImageDOSZMSignature { + return ErrDOSMagicNotFound + } + + // `e_lfanew` is the only required element (besides the signature) of the + // DOS header to turn the EXE into a PE. It is is a relative offset to the + // NT Headers. It can't be null (signatures would overlap). + // Can be 4 at minimum. + if pe.DosHeader.AddressOfNewEXEHeader < 4 || + pe.DosHeader.AddressOfNewEXEHeader > pe.size { + return ErrInvalidElfanewValue + } + + // tiny pe has a e_lfanew of 4, which means the NT Headers is overlapping + // the DOS Header. + if pe.DosHeader.AddressOfNewEXEHeader <= 0x3c { + pe.Anomalies = append(pe.Anomalies, AnoPEHeaderOverlapDOSHeader) + } + + return nil +} diff --git a/dotnet.go b/dotnet.go new file mode 100644 index 0000000..8d05843 --- /dev/null +++ b/dotnet.go @@ -0,0 +1,315 @@ +// Copyright 2021 Saferwall. All rights reserved. +// Use of this source code is governed by Apache v2 license +// license that can be found in the LICENSE file. + +package pe + +// COM+ Header entry point flags. +const ( + // The image file contains IL code only, with no embedded native unmanaged + // code except the start-up stub (which simply executes an indirect jump to + // the CLR entry point). + COMImageFlagsILOnly = 0x00000001 + + // The image file can be loaded only into a 32-bit process. + COMImageFlags32BitRequired = 0x00000002 + + // This flag is obsolete and should not be set. Setting it—as the IL + // assembler allows, using the .corflags directive—will render your module + // unloadable. + COMImageFlagILLibrary = 0x00000004 + + // The image file is protected with a strong name signature. + COMImageFlagsStrongNameSigned = 0x00000008 + + // The executable’s entry point is an unmanaged method. The EntryPointToken/ + // EntryPointRVA field of the CLR header contains the RVA of this native + // method. This flag was introduced in version 2.0 of the CLR. + COMImageFlagsNativeEntrypoint = 0x00000010 + + // The CLR loader and the JIT compiler are required to track debug + // information about the methods. This flag is not used. + COMImageFlagsTrackDebugData = 0x00010000 + + // The image file can be loaded into any process, but preferably into a + // 32-bit process. This flag can be only set together with flag + // COMIMAGE_FLAGS_32BITREQUIRED. When set, these two flags mean the image + // is platformneutral, but prefers to be loaded as 32-bit when possible. + // This flag was introduced in CLR v4.0 + COMImageFlags32BitPreferred = 0x00020000 +) + +// V-table constants. +const ( + // V-table slots are 32-bits in size. + CORVTable32Bit = 0x01 + + // V-table slots are 64-bits in size. + CORVTable64Bit = 0x02 + + // The thunk created by the common language runtime must provide data + // marshaling between managed and unmanaged code. + CORVTableFromUnmanaged = 0x04 + + // The thunk created by the common language runtime must provide data + // marshaling between managed and unmanaged code. Current appdomain should + // be selected to dispatch the call. + CORVTableFromUnmanagedRetainAppDomain = 0x08 + + // Call most derived method described by + CORVTableCallMostDerived = 0x10 +) + +// ImageDataDirectory represents the directory format. +type ImageDataDirectory struct { + + // The relative virtual address of the table. + VirtualAddress uint32 + + // The size of the table, in bytes. + Size uint32 +} + +// ImageCOR20Header represents the CLR 2.0 header structure. +type ImageCOR20Header struct { + + // Size of the header in bytes. + Cb uint32 + + // Major number of the minimum version of the runtime required to run the + // program. + MajorRuntimeVersion uint16 + + // Minor number of the version of the runtime required to run the program. + MinorRuntimeVersio uint16 + + // RVA and size of the metadata. + MetaData ImageDataDirectory + + // Bitwise flags indicating attributes of this executable. + Flags uint32 + + // Metadata identifier (token) of the entry point for the image file; can + // be 0 for DLL images. This field identifies a method belonging to this + // module or a module containing the entry point method. + // In images of version 2.0 and newer, this field may contain RVA of the + // embedded native entry point method. + // union { + // + // If COMIMAGE_FLAGS_NATIVE_ENTRYPOINT is not set, + // EntryPointToken represents a managed entrypoint. + // DWORD EntryPointToken; + // + // If COMIMAGE_FLAGS_NATIVE_ENTRYPOINT is set, + // EntryPointRVA represents an RVA to a native entrypoint + // DWORD EntryPointRVA; + //}; + EntryPointRVAorToken uint32 + + // This is the blob of managed resources. Fetched using + // code:AssemblyNative.GetResource and code:PEFile.GetResource and accessible + // from managed code from System.Assembly.GetManifestResourceStream. The + // metadata has a table that maps names to offsets into this blob, so + // logically the blob is a set of resources. + Resources ImageDataDirectory + + // RVA and size of the hash data for this PE file, used by the loader for + // binding and versioning. IL assemblies can be signed with a public-private + // key to validate who created it. The signature goes here if this feature + // is used. + StrongNameSignature ImageDataDirectory + + // RVA and size of the Code Manager table. In the existing releases of the + // runtime, this field is reserved and must be set to 0. + CodeManagerTable ImageDataDirectory + + // RVA and size in bytes of an array of virtual table (v-table) fixups. + // Among current managed compilers, only the VC++ linker and the IL + // assembler can produce this array. + VTableFixups ImageDataDirectory + + // RVA and size of an array of addresses of jump thunks. Among managed + // compilers, only the VC++ of versions pre-8.0 could produce this table, + // which allows the export of unmanaged native methods embedded in the + // managed PE file. In v2.0+ of CLR this entry is obsolete and must be set + // to 0. + ExportAddressTableJumps ImageDataDirectory + + // Reserved for precompiled images; set to 0 + // NGEN images it points at a code:CORCOMPILE_HEADER structure + ManagedNativeHeader ImageDataDirectory +} + +// ImageCORVTableFixup defines the v-table fixups that contains the +// initializing information necessary for the runtime to create the thunks. +// Non VOS v-table entries. Define an array of these pointed to by +// IMAGE_COR20_HEADER.VTableFixups. Each entry describes a contiguous array of +// v-table slots. The slots start out initialized to the meta data token value +// for the method they need to call. At image load time, the CLR Loader will +// turn each entry into a pointer to machine code for the CPU and can be +// called directly. +type ImageCORVTableFixup struct { + RVA uint32 // Offset of v-table array in image. + Count uint16 // How many entries at location. + Type uint16 // COR_VTABLE_xxx type of entries. +} + +// MetadataHeader consists of a storage signature and a storage header. +type MetadataHeader struct { + + // The storage signature, which must be 4-byte aligned: + // ==================================================== + + // ”Magic” signature for physical metadata, currently 0x424A5342, or, read + // as characters, BSJB—the initials of four “founding fathers” Brian Harry, + // Susa Radke-Sproull, Jason Zander, and Bill Evans, who started the + // runtime development in 1998. + Signature uint32 + + // Major version. + MajorVersion uint16 + + // Minor version. + MinorVersion uint16 + + // Reserved; set to 0. + ExtraData uint32 + + // Length of the version string. + VersionString uint32 + + // Version string. + Version string + + // The storage header follows the storage signature, aligned on a 4-byte + // boundary. + // ==================================================================== + + // Reserved; set to 0. + Flags uint8 + + // Another byte used for [padding] + + // Number of streams. + Streams uint16 +} + +// MetadataStreamHeader represents a Metadata Stream Header Structure. +type MetadataStreamHeader struct { + // Offset Offset in the file for this stream. + Offset uint32 + + // Size of the stream in bytes. + Size uint32 + + // Name of the stream; a zero-terminated ASCII string no longer than 31 + // characters (plus zero terminator). The name might be shorter, in which + // case the size of the stream header is correspondingly reduced, padded to + // the 4-byte boundary. + Name string +} + +// CLRData embeds the Common Language Runtime Header structure as well as the +// Metadata header structure. +type CLRData struct { + CLRHeader *ImageCOR20Header + MetadataHeader *MetadataHeader + MetadataStreamHeaders []*MetadataStreamHeader +} + +func (pe *File) parseMetadataHeader(rva, size uint32, clr *CLRData) error { + var err error + mh := MetadataHeader{} + + offset := pe.getOffsetFromRva(rva) + if mh.Signature, err = pe.ReadUint32(offset); err != nil { + return err + } + if mh.MajorVersion, err = pe.ReadUint16(offset + 4); err != nil { + return err + } + if mh.MinorVersion, err = pe.ReadUint16(offset + 6); err != nil { + return err + } + if mh.ExtraData, err = pe.ReadUint32(offset + 8); err != nil { + return err + } + if mh.VersionString, err = pe.ReadUint32(offset + 12); err != nil { + return err + } + mh.Version, err = pe.getStringAtOffset(offset+16, mh.VersionString) + if err != nil { + return err + } + + offset += 16 + mh.VersionString + if mh.Flags, err = pe.ReadUint8(offset); err != nil { + return err + } + + if mh.Streams, err = pe.ReadUint16(offset + 2); err != nil { + return err + } + + clr.MetadataHeader = &mh + + // Immediately following the MetadataHeader is a series of Stream Headers. + // A “stream” is to the metadata what a “section” is to the assembly. The + // NumberOfStreams property indicates how many StreamHeaders to read. + offset += 4 + for i := uint16(0); i < mh.Streams; i++ { + sh := MetadataStreamHeader{} + if sh.Offset, err = pe.ReadUint32(offset); err != nil { + return err + } + if sh.Size, err = pe.ReadUint32(offset + 4); err != nil { + return err + } + + // Name requires a special treatement. + offset += 8 + for j := uint32(0); j <= 32; j++ { + var c uint8 + if c, err = pe.ReadUint8(offset); err != nil { + return err + } + + offset++ + if c == 0 && (j+1)%4 == 0 { + break + } + if c != 0 { + sh.Name += string(c) + } + } + + clr.MetadataStreamHeaders = append(clr.MetadataStreamHeaders, &sh) + + } + return nil +} + +// The 15th directory entry of the PE header contains the RVA and size of the +// runtime header in the image file. The runtime header, which contains all of +// the runtime-specific data entries and other information, should reside in a +// read-only section of the image file. The IL assembler puts the common +// language runtime header in the .text section. +func (pe *File) parseCLRHeaderDirectory(rva, size uint32) error { + + clr := CLRData{} + + clrHeader := ImageCOR20Header{} + offset := pe.getOffsetFromRva(rva) + err := pe.structUnpack(&clrHeader, offset, size) + if err != nil { + return err + } + clr.CLRHeader = &clrHeader + if clrHeader.MetaData.VirtualAddress != 0 && clrHeader.MetaData.Size != 0 { + pe.parseMetadataHeader(clrHeader.MetaData.VirtualAddress, + clrHeader.MetaData.Size, &clr) + } + + pe.CLR = &clr + return nil +} diff --git a/exception.go b/exception.go new file mode 100644 index 0000000..9d7d3a5 --- /dev/null +++ b/exception.go @@ -0,0 +1,540 @@ +// Copyright 2021 Saferwall. All rights reserved. +// Use of this source code is governed by Apache v2 license +// license that can be found in the LICENSE file. + +package pe + +import ( + "encoding/binary" + "log" + "strconv" +) + +// ImageARMRuntimeFunctionEntry represents the function table entry for the ARM +// platform. +type ImageARMRuntimeFunctionEntry struct { + // Function Start RVA is the 32-bit RVA of the start of the function. If + // the function contains thumb code, the low bit of this address must be set. + BeginAddress uint32 `bitfield:",functionstart"` + + // Flag is a 2-bit field that indicates how to interpret the remaining + // 30 bits of the second .pdata word. If Flag is 0, then the remaining bits + // form an Exception Information RVA (with the low two bits implicitly 0). + // If Flag is non-zero, then the remaining bits form a Packed Unwind Data + // structure. + Flag uint8 + + /* Exception Information RVA or Packed Unwind Data. + + Exception Information RVA is the address of the variable-length exception + information structure, stored in the .xdata section. + This data must be 4-byte aligned. + + Packed Unwind Data is a compressed description of the operations required + to unwind from a function, assuming a canonical form. In this case, no + .xdata record is required. */ + ExceptionFlag uint32 +} + +const ( + // Unwind information flags. + + // UnwFlagNHandler - The function has no handler. + UnwFlagNHandler = uint8(0x0) + + // UnwFlagEHandler - The function has an exception handler that should + // be called when looking for functions that need to examine exceptions. + UnwFlagEHandler = uint8(0x1) + + // UnwFlagUHandler - The function has a termination handler that should + // be called when unwinding an exception. + UnwFlagUHandler = uint8(0x2) + + // UnwFlagChainInfo - This unwind info structure is not the primary one + // for the procedure. Instead, the chained unwind info entry is the contents + // of a previous RUNTIME_FUNCTION entry. For information, see Chained unwind + // info structures. If this flag is set, then the UNW_FLAG_EHANDLER and + // UNW_FLAG_UHANDLER flags must be cleared. Also, the frame register and + // fixed-stack allocation field must have the same values as in the primary + // unwind info. + UnwFlagChainInfo = uint8(0x4) +) + +// The meaning of the operation info bits depends upon the operation code. +// To encode a general-purpose (integer) register, this mapping is used: +const ( + rax = iota + rcx + rdx + rbx + rsp + rbp + rsi + rdi + r8 + r9 + r10 + r11 + r12 + r13 + r14 + r15 +) + +// OpInfoRegisters maps registers to string. +var OpInfoRegisters = map[uint8]string{ + rax: "RAX", + rcx: "RCX", + rdx: "RDX", + rbx: "RBX", + rsp: "RSP", + rbp: "RBP", + rsi: "RSI", + rdi: "RDI", + r8: "R8", + r9: "R9", + r10: "R10", + r11: "R11", + r12: "R12", + r13: "R13", + r14: "R14", + r15: "R15", +} + +// _UNWIND_OP_CODES +const ( + // Push a nonvolatile integer register, decrementing RSP by 8. The + // operation info is the number of the register. Because of the constraints + // on epilogs, UWOP_PUSH_NONVOL unwind codes must appear first in the + // prolog and correspondingly, last in the unwind code array. This relative + // ordering applies to all other unwind codes except UWOP_PUSH_MACHFRAME. + UwOpPushNonVol = uint8(0) + + // Allocate a large-sized area on the stack. There are two forms. If the + // operation info equals 0, then the size of the allocation divided by 8 is + // recorded in the next slot, allowing an allocation up to 512K - 8. If the + // operation info equals 1, then the unscaled size of the allocation is + // recorded in the next two slots in little-endian format, allowing + // allocations up to 4GB - 8. + UwOpAllocLarge = uint8(1) + + // Allocate a small-sized area on the stack. The size of the allocation is + // the operation info field * 8 + 8, allowing allocations from 8 to 128 + // bytes. + UwOpAllocSmall = uint8(2) + + // Establish the frame pointer register by setting the register to some + // offset of the current RSP. The offset is equal to the Frame Register + // offset (scaled) field in the UNWIND_INFO * 16, allowing offsets from 0 + // to 240. The use of an offset permits establishing a frame pointer that + // points to the middle of the fixed stack allocation, helping code density + // by allowing more accesses to use short instruction forms. The operation + // info field is reserved and shouldn't be used. + UwOpSetFpReg = uint8(3) + + // Save a nonvolatile integer register on the stack using a MOV instead of + // a PUSH. This code is primarily used for shrink-wrapping, where a + // nonvolatile register is saved to the stack in a position that was + // previously allocated. The operation info is the number of the register. + // The scaled-by-8 stack offset is recorded in the next unwind operation + // code slot, as described in the note above. + UwOpSaveNonVol = uint8(4) + + // Save a nonvolatile integer register on the stack with a long offset, + // using a MOV instead of a PUSH. This code is primarily used for + // shrink-wrapping, where a nonvolatile register is saved to the stack in a + // position that was previously allocated. The operation info is the number + // of the register. The unscaled stack offset is recorded in the next two + // unwind operation code slots, as described in the note above. + UwOpSaveNonVolFar = uint8(5) + + // For version 1 of the UNWIND_INFO structure, this code was called + // UWOP_SAVE_XMM and occupied 2 records, it retained the lower 64 bits of + // the XMM register, but was later removed and is now skipped. In practice, + // this code has never been used. + // For version 2 of the UNWIND_INFO structure, this code is called + // UWOP_EPILOG, takes 2 entries, and describes the function epilogue. + UwOpEpilog = uint8(6) + + // For version 1 of the UNWIND_INFO structure, this code was called + // UWOP_SAVE_XMM_FAR and occupied 3 records, it saved the lower 64 bits of + // the XMM register, but was later removed and is now skipped. In practice, + // this code has never been used. + // For version 2 of the UNWIND_INFO structure, this code is called + // UWOP_SPARE_CODE, takes 3 entries, and makes no sense. + UwOpSpareCode = uint8(7) + + // Save all 128 bits of a nonvolatile XMM register on the stack. The + // operation info is the number of the register. The scaled-by-16 stack + // offset is recorded in the next slot. + UwOpSaveXmm128 = uint8(8) + + // Save all 128 bits of a nonvolatile XMM register on the stack with a long + // offset. The operation info is the number of the register. The unscaled + // stack offset is recorded in the next two slots. + UwOpSaveXmm128Far = uint8(9) + + // Push a machine frame. This unwind code is used to record the effect of a + // hardware interrupt or exception. + UwOpPushMachFrame = uint8(10) + + // UWOP_SET_FPREG_LARGE is a CLR Unix-only extension to the Windows AMD64 + // unwind codes. It is not part of the standard Windows AMD64 unwind codes + // specification. UWOP_SET_FPREG allows for a maximum of a 240 byte offset + // between RSP and the frame pointer, when the frame pointer is + // established. UWOP_SET_FPREG_LARGE has a 32-bit range scaled by 16. When + // UWOP_SET_FPREG_LARGE is used, UNWIND_INFO.FrameRegister must be set to + // the frame pointer register, and UNWIND_INFO.FrameOffset must be set to + // 15 (its maximum value). UWOP_SET_FPREG_LARGE is followed by two + // UNWIND_CODEs that are combined to form a 32-bit offset (the same as + // UWOP_SAVE_NONVOL_FAR). This offset is then scaled by 16. The result must + // be less than 2^32 (that is, the top 4 bits of the unscaled 32-bit number + // must be zero). This result is used as the frame pointer register offset + // from RSP at the time the frame pointer is established. Either + // UWOP_SET_FPREG or UWOP_SET_FPREG_LARGE can be used, but not both. + UwOpSetFpRegLarge = uint8(11) +) + +// UnOpToString maps unwind opcodes to strings. +var UnOpToString = map[uint8]string{ + UwOpPushNonVol: "UWOP_PUSH_NONVOL", + UwOpAllocLarge: "UWOP_ALLOC_LARE", + UwOpAllocSmall: "UWOP_ALLOC_SMALL", + UwOpSetFpReg: "UWOP_SET_FPREG", + UwOpSaveNonVol: "UWOP_SAVE_NONVOL", + UwOpSaveNonVolFar: "UWOP_SAVE_NONVOL_FAR", + UwOpEpilog: "UWOP_EPILOG", + UwOpSpareCode: "UWOP_SPARE_CODE", + UwOpSaveXmm128: "UWOP_SAVE_XMM128", + UwOpSaveXmm128Far: "UWOP_SAVE_XMM128_FAR", + UwOpPushMachFrame: "UWOP_PUSH_MACHFRAME", + UwOpSetFpRegLarge: "UWOP_SET_FPREG_LARGE", +} + +// ImageRuntimeFunctionEntry represents an entry in the function table on 64-bit +// Windows. Table-based exception handling reques a table entry for all +// functions that allocate stack space or call another function (for example, +// nonleaf functions). +type ImageRuntimeFunctionEntry struct { + // The address of the start of the function. + BeginAddress uint32 + + // The address of the end of the function. + EndAddress uint32 + + // The unwind data info ructure is used to record the effects a function has + // on the stack pointer, and where the nonvolatile registers are saved on + // the stack. + UnwindInfoAddress uint32 +} + +// UnwindCode is used to record the sequence of operations in the prolog that +// affect the nonvolatile registers and RSP. Each code item has this format: +/* typedef union _UNWIND_CODE { + struct { + UCHAR CodeOffset; + UCHAR UnwindOp : 4; + UCHAR OpInfo : 4; + } DUMMYUNIONNAME; + + struct { + UCHAR OffsetLow; + UCHAR UnwindOp : 4; + UCHAR OffsetHigh : 4; + } EpilogueCode; + + USHORT FrameOffset; +} UNWIND_CODE, *PUNWIND_CODE;*/ +type UnwindCode struct { + // Offset (from the beginng of the prolog) of the end of the instruction + // that performs is operation, plus 1 (that is, the offset of the start of + // the next instruction). + CodeOffset uint8 + + // The unwind operation code + UnwindOp uint8 + + // Operation info + OpInfo uint8 + + // Allocation size + Operand string + FrameOffset uint16 +} + +// UnwindInfo represents the _UNWIND_INFO structure. It is used to record the +// effects a function has on the stack pointer, and where the nonvolatile +// registers are saved on the stack. +type UnwindInfo struct { + // (3 bits) Version number of the unwind data, currently 1 and 2. + Version uint8 + + // (5 bits) Three flags are currently defined above. + Flags uint8 + + // Length of the function prolog in bytes. + SizeOfProlog uint8 + + // The number of slots in the unwind codes array. Some unwind codes, + // for example, UWOP_SAVE_NONVOL, require more than one slot in the array. + CountOfCodes uint8 + + // If nonzero, then the function uses a frame pointer (FP), and this field + // is the number of the nonvolatile register used as the frame pointer, + // using the same encoding for the operation info field of UNWIND_CODE nodes. + FrameRegister uint8 + + // If the frame register field nonzero, this field is the scaled offset + // from RSP that is applied to the FP register when it's established. The + // actual FP register is set to RSP + 16 * this number, allowing offsets + // from 0 to 240. This offset permits pointing the FP register into the + // middle of the local stack allocation for dynamic stack frames, allowing + // better code density through shorter instructions. (That is, more + // instructions can use the 8-bit signed offset form.) + FrameOffset uint8 + + // An array of items that explains the effect of the prolog on the + // nonvolatile registers and RSP. See the section on UNWIND_CODE for the + // meanings of individual items. For alignment purposes, this array always + // has an even number of entries, and the final entry is potentially + // unused. In that case, the array is one longer than indicated by the + // count of unwind codes field. + UnwindCodes []UnwindCode + + // Address of exception handler. + ExceptionHandler uint32 + + // If flag UNW_FLAG_CHAININFO is set, then the UNWIND_INFO structure ends + // with three UWORDs. These UWORDs represent the RUNTIME_FUNCTION + // information for the function of the chained unwind. + FunctionEntry ImageRuntimeFunctionEntry +} + +// +// The unwind codes are followed by an optional DWORD aligned field that +// contains the exception handler address or the address of chained unwind +// information. If an exception handler address is specified, then it is +// followed by the language specified exception handler data. +// +// union { +// ULONG ExceptionHandler; +// ULONG FunctionEntry; +// }; +// +// ULONG ExceptionData[]; +// + +// Exception represent an entry in the function table. +type Exception struct { + RuntimeFunction ImageRuntimeFunctionEntry + UnwinInfo UnwindInfo +} + +func (pe *File) parseUnwindCode(offset uint32, version uint8) (UnwindCode, int) { + + unwindCode := UnwindCode{} + + // Read the unwince code at offset (2 bytes) + uc, err := pe.ReadUint16(offset) + if err != nil { + return unwindCode, 0 + } + + advanceBy := 0 + + unwindCode.CodeOffset = uint8(uc & 0xff) + unwindCode.UnwindOp = uint8(uc & 0xf00 >> 8) + unwindCode.OpInfo = uint8(uc & 0xf000 >> 12) + + switch unwindCode.UnwindOp { + case UwOpAllocSmall: + size := int(unwindCode.OpInfo*8 + 8) + unwindCode.Operand = "Size=" + strconv.Itoa(size) + advanceBy++ + case UwOpAllocLarge: + if unwindCode.OpInfo == 0 { + size := int(binary.LittleEndian.Uint16(pe.data[offset+2:]) * 8) + unwindCode.Operand = "Size=" + strconv.Itoa(size) + advanceBy += 2 + } else { + size := int(binary.LittleEndian.Uint32(pe.data[offset+2:]) << 16) + unwindCode.Operand = "Size=" + strconv.Itoa(size) + advanceBy += 3 + } + case UwOpSetFpReg: + unwindCode.Operand = "Register=" + OpInfoRegisters[unwindCode.OpInfo] + advanceBy++ + case UwOpPushNonVol: + unwindCode.Operand = "Register=" + OpInfoRegisters[unwindCode.OpInfo] + advanceBy++ + case UwOpSaveNonVol: + fo := binary.LittleEndian.Uint16(pe.data[offset+2:]) + unwindCode.FrameOffset = fo * 8 + unwindCode.Operand = "Register=" + OpInfoRegisters[unwindCode.OpInfo] + + ", Offset=" + strconv.Itoa(int(unwindCode.FrameOffset)) + advanceBy += 2 + case UwOpSaveNonVolFar: + fo := binary.LittleEndian.Uint32(pe.data[offset+2:]) + unwindCode.FrameOffset = uint16(fo * 8) + unwindCode.Operand = "Register=" + OpInfoRegisters[unwindCode.OpInfo] + + ", Offset=" + strconv.Itoa(int(unwindCode.FrameOffset)) + advanceBy += 3 + case UwOpSaveXmm128: + fo := binary.LittleEndian.Uint16(pe.data[offset+2:]) + unwindCode.FrameOffset = fo * 16 + unwindCode.Operand = "Rgister=XMM" + strconv.Itoa(int(unwindCode.OpInfo)) + + ", Offset=" + strconv.Itoa(int(unwindCode.FrameOffset)) + advanceBy += 2 + case UwOpSaveXmm128Far: + fo := binary.LittleEndian.Uint32(pe.data[offset+2:]) + unwindCode.FrameOffset = uint16(fo) + unwindCode.Operand = "Register=XMM" + strconv.Itoa(int(unwindCode.OpInfo)) + + ", Offset=" + strconv.Itoa(int(unwindCode.FrameOffset)) + advanceBy += 3 + case UwOpSetFpRegLarge: + unwindCode.Operand = "Register=" + OpInfoRegisters[unwindCode.OpInfo] + advanceBy += 2 + + case UwOpPushMachFrame: + advanceBy++ + + case UwOpEpilog: + if version == 2 { + unwindCode.Operand = "Flags=" + strconv.Itoa(int(unwindCode.OpInfo)) + ", Size=" + strconv.Itoa(int(unwindCode.CodeOffset)) + } + advanceBy += 2 + case UwOpSpareCode: + advanceBy += 3 + + default: + advanceBy++ // so we can get out of the loop + log.Printf("Wrong unwind opcode %d", unwindCode.UnwindOp) + } + + return unwindCode, advanceBy +} + +func (pe *File) parseUnwinInfo(unwindInfo uint32) UnwindInfo { + + ui := UnwindInfo{} + + offset := pe.getOffsetFromRva(unwindInfo) + v, err := pe.ReadUint32(offset) + if err != nil { + return ui + } + + // The lowest 3 bits + ui.Version = uint8(v & 0x7) + + // The next 5 bits. + ui.Flags = uint8(v & 0xf8 >> 3) + + // The next byte + ui.SizeOfProlog = uint8(v & 0xff00 >> 8) + + // The next byte + ui.CountOfCodes = uint8(v & 0xff0000 >> 16) + + // The next 4 bits + ui.FrameRegister = uint8(v & 0xf00000 >> 24) + + // The next 4 bits. + ui.FrameOffset = uint8(v&0xf0000000>>28) * 6 + + // Each unwind code struct is 2 bytes wide. + offset += 4 + i := 0 + for i < int(ui.CountOfCodes) { + ucOffset := offset + 2*uint32(i) + unwindCode, advanceBy := pe.parseUnwindCode(ucOffset, ui.Version) + ui.UnwindCodes = append(ui.UnwindCodes, unwindCode) + i += advanceBy + } + + if ui.CountOfCodes&1 == 1 { + offset += 2 + } + + // An image-relative pointer to either the function's language-specific + // exception or termination handler, if flag UNW_FLAG_CHAININFO is clear + // and one of the flags UNW_FLAG_EHADLER or UNW_FLAG_UHANDLER is set. + if ui.Flags&UnwFlagEHandler != 0 || ui.Flags&UnwFlagUHandler != 0 { + if ui.Flags&UnwFlagChainInfo == 0 { + handlerOffset := offset + 2*uint32(i) + ui.ExceptionHandler = binary.LittleEndian.Uint32(pe.data[handlerOffset:]) + } + } + + // If the UNW_FLAG_CHAININFO flag is set, then an unwind info structure + // is a secondary one, and the shared exception-handler/chained-info + // address field contains the primary unwind information. + // This sample code retrieves the primary unwind information, + // assuming that unwindInfo is the structure that has the + // UNW_FLAG_CHAININFO flag set. + if ui.Flags&UnwFlagChainInfo != 0 { + chainOffset := offset + 2*uint32(i) + rf := ImageRuntimeFunctionEntry{} + size := uint32(binary.Size(ImageRuntimeFunctionEntry{})) + err := pe.structUnpack(&rf, chainOffset, size) + if err != nil { + return ui + } + ui.FunctionEntry = rf + } + + return ui +} + +// Exception directory contains an array of function table entries that are used +// for exception handling. +func (pe *File) parseExceptionDirectory(rva, size uint32) error { + + // The target platform determines which format of the function table entry + // to use. + var exceptions []Exception + fileOffset := pe.getOffsetFromRva(rva) + + entrySize := uint32(binary.Size(ImageRuntimeFunctionEntry{})) + entriesCount := size / entrySize + + for i := uint32(0); i < entriesCount; i++ { + functionEntry := ImageRuntimeFunctionEntry{} + offset := fileOffset + (entrySize * i) + err := pe.structUnpack(&functionEntry, offset, entrySize) + if err != nil { + return err + } + + exception := Exception{RuntimeFunction: functionEntry} + + if pe.Is64 { + exception.UnwinInfo = pe.parseUnwinInfo(functionEntry.UnwindInfoAddress) + } + + exceptions = append(exceptions, exception) + } + + pe.Exceptions = exceptions + return nil +} + +// PrettyUnwindInfoHandlerFlags returns the string representation of the +// `flags` field of the unwind info structure. +func PrettyUnwindInfoHandlerFlags(flags uint8) []string { + var values []string + + unwFlagHandlerMap := map[uint8]string{ + UnwFlagNHandler: "No Handler", + UnwFlagEHandler: "Exception", + UnwFlagUHandler: "Termination", + UnwFlagChainInfo: "Chain", + } + + for k, s := range unwFlagHandlerMap { + if k&flags != 0 { + values = append(values, s) + } + } + return values +} diff --git a/exports.go b/exports.go new file mode 100644 index 0000000..712059c --- /dev/null +++ b/exports.go @@ -0,0 +1,327 @@ +// Copyright 2021 Saferwall. All rights reserved. +// Use of this source code is governed by Apache v2 license +// license that can be found in the LICENSE file. + +package pe + +import ( + "encoding/binary" + "errors" + "fmt" +) + +const ( + maxExportedSymbols = 0x2000 +) + +var ( + ErrExportMaxOrdEntries = "Export directory contains more than max ordinal entries" + ErrExportManyRepeatedEntries = "Export directory contains many repeated entries" + AnoNullNumberOfFunctions = "Export directory contains zero number of functions" + AnoNullAddressOfFunctions = "Export directory contains zero address of functions" +) + +// ImageExportDirectory represents the IMAGE_EXPORT_DIRECTORY structure. +// The export directory table contains address information that is used +// to resolve imports to the entry points within this image. +type ImageExportDirectory struct { + // Reserved, must be 0. + Characteristics uint32 + + // The time and date that the export data was created. + TimeDateStamp uint32 + + // The major version number. + //The major and minor version numbers can be set by the user. + MajorVersion uint16 + + // The minor version number. + MinorVersion uint16 + + // The address of the ASCII string that contains the name of the DLL. + // This address is relative to the image base. + Name uint32 + + // The starting ordinal number for exports in this image. This field + // specifies the starting ordinal number for the export address table. + // It is usually set to 1. + Base uint32 + + // The number of entries in the export address table. + NumberOfFunctions uint32 + + // The number of entries in the name pointer table. This is also the number + // of entries in the ordinal table. + NumberOfNames uint32 + + // The address of the export address table, relative to the image base. + AddressOfFunctions uint32 + + // The address of the export name pointer table, relative to the image base. + // The table size is given by the Number of Name Pointers field. + AddressOfNames uint32 + + // The address of the ordinal table, relative to the image base. + AddressOfNameOrdinals uint32 +} + +// ExportFunction represents an imported function in the export table. +type ExportFunction struct { + Ordinal uint32 + FunctionRVA uint32 + NameOrdinal uint32 + NameRVA uint32 + Name string + Forwarder string + ForwarderRVA uint32 +} + +// Export represent the export table. +type Export struct { + Functions []ExportFunction + Struct ImageExportDirectory + Name string +} + +// A few notes learned from `Corkami` about parsing export directory: +// - like many data directories, Exports' size are not necessary, except for +// forwarding. +// - Characteristics, TimeDateStamp, MajorVersion and MinorVersion are not necessary. +// the export name is not necessary, and can be anything. +// - AddressOfNames is lexicographically-ordered. +// - export names can have any value (even null or more than 65536 characters +// long, with unprintable characters), just null terminated. +// - an EXE can have exports (no need of relocation nor DLL flag), and can use +// them normally +// - exports can be not used for execution, but for documenting the internal code +// - numbers of functions will be different from number of names when the file +// is exporting some functions by ordinal. +func (pe *File) parseExportDirectory(rva, size uint32) error { + + // Define some vars. + exp := Export{} + exportDir := ImageExportDirectory{} + errorMsg := fmt.Sprintf("Error parsing export directory at RVA: 0x%x", rva) + + fileOffset := pe.getOffsetFromRva(rva) + exportDirSize := uint32(binary.Size(exportDir)) + err := pe.structUnpack(&exportDir, fileOffset, exportDirSize) + if err != nil { + return errors.New(errorMsg) + } + exp.Struct = exportDir + + // We keep track of the bytes left in the file and use it to set a upper + // bound in the number of items that can be read from the different arrays. + lengthUntilEOF := func(rva uint32) uint32 { + return pe.size - pe.getOffsetFromRva(rva) + } + var length uint32 + var addressOfNames []byte + + // Some DLLs have null number of functions. + if exportDir.NumberOfFunctions == 0 { + pe.Anomalies = append(pe.Anomalies, AnoNullNumberOfFunctions) + return nil + } + + // Some DLLs have null address of functions. + if exportDir.AddressOfFunctions == 0 { + pe.Anomalies = append(pe.Anomalies, AnoNullAddressOfFunctions) + return nil + } + + length = min(lengthUntilEOF(exportDir.AddressOfNames), + exportDir.NumberOfNames*4) + addressOfNames, err = pe.getData(exportDir.AddressOfNames, length) + if err != nil { + return errors.New(errorMsg) + } + + length = min(lengthUntilEOF(exportDir.AddressOfNameOrdinals), + exportDir.NumberOfNames*4) + addressOfNameOrdinals, err := pe.getData(exportDir.AddressOfNameOrdinals, length) + if err != nil { + return errors.New(errorMsg) + } + + length = min(lengthUntilEOF(exportDir.AddressOfFunctions), + exportDir.NumberOfFunctions*4) + addressOfFunctions, err := pe.getData(exportDir.AddressOfFunctions, length) + if err != nil { + return errors.New(errorMsg) + } + + exp.Name = pe.getStringAtRVA(exportDir.Name, 0x100000) + + maxFailedEntries := 10 + var forwarderStr string + var forwarderOffset uint32 + safetyBoundary := pe.size // overly generous upper bound + symbolCounts := make(map[uint32]int) + parsingFailed := false + + // read the image export directory + section := pe.getSectionByRva(exportDir.AddressOfNames) + if section != nil { + safetyBoundary = (section.Header.VirtualAddress + + uint32(len(section.Data(0, 0, pe)))) - exportDir.AddressOfNames + } + + numNames := min(exportDir.NumberOfNames, safetyBoundary/4) + var symbolAddress uint32 + for i := uint32(0); i < numNames; i++ { + + defer func() { + // recover from panic if one occured. Set err to nil otherwise. + if recover() != nil { + err = errors.New("array index out of bounds") + } + }() + + symbolOrdinal := binary.LittleEndian.Uint16(addressOfNameOrdinals[i*2:]) + symbolAddress = binary.LittleEndian.Uint32(addressOfFunctions[symbolOrdinal*4:]) + if symbolAddress == 0 { + continue + } + + // If the function's RVA points within the export directory + // it will point to a string with the forwarded symbol's string + // instead of pointing the the function start address. + if symbolAddress >= rva && symbolAddress < rva+size { + forwarderStr = pe.getStringAtRVA(symbolAddress, 0x100000) + forwarderOffset = pe.getOffsetFromRva(symbolAddress) + } else { + forwarderStr = "" + fileOffset = 0 + } + + symbolNameAddress := binary.LittleEndian.Uint32(addressOfNames[i*4:]) + if symbolNameAddress == 0 { + maxFailedEntries-- + if maxFailedEntries <= 0 { + parsingFailed = true + break + } + } + symbolName := pe.getStringAtRVA(symbolNameAddress, 0x100000) + if !IsValidFunctionName(symbolName) { + parsingFailed = true + break + } + + symbolNameOffset := pe.getOffsetFromRva(symbolNameAddress) + if symbolNameOffset == 0 { + maxFailedEntries-- + if maxFailedEntries <= 0 { + parsingFailed = true + break + } + } + + // File 0b1d3d3664915577ab9a32188d29bbf3542b86c7b9ce333e245496c3018819f1 + // was being parsed as potentially containing millions of exports. + // Checking for duplicates addresses the issue. + symbolCounts[symbolAddress]++ + if symbolCounts[symbolAddress] > 10 { + if !stringInSlice(ErrExportManyRepeatedEntries, pe.Anomalies) { + pe.Anomalies = append(pe.Anomalies, ErrExportManyRepeatedEntries) + } + } + if len(symbolCounts) > maxExportedSymbols { + if !stringInSlice(ErrExportMaxOrdEntries, pe.Anomalies) { + pe.Anomalies = append(pe.Anomalies, ErrExportMaxOrdEntries) + } + } + newExport := ExportFunction{ + Name: symbolName, + NameRVA: symbolNameAddress, + NameOrdinal: uint32(symbolOrdinal), + Ordinal: exportDir.Base + uint32(symbolOrdinal), + FunctionRVA: symbolAddress, + Forwarder: forwarderStr, + ForwarderRVA: forwarderOffset, + } + + exp.Functions = append(exp.Functions, newExport) + } + + if parsingFailed { + fmt.Printf("RVA AddressOfNames in the export directory points to an "+ + "invalid address: 0x%x\n", exportDir.AddressOfNames) + } + + maxFailedEntries = 10 + section = pe.getSectionByRva(exportDir.AddressOfFunctions) + + // Overly generous upper bound + safetyBoundary = pe.size + if section != nil { + safetyBoundary = section.Header.VirtualAddress + + uint32(len(section.Data(0, 0, pe))) - exportDir.AddressOfNames + } + parsingFailed = false + ordinals := make(map[uint32]bool) + for _, export := range exp.Functions { + ordinals[export.Ordinal] = true + } + numNames = min(exportDir.NumberOfFunctions, safetyBoundary/4) + for i := uint32(0); i < numNames; i++ { + value := i + exportDir.Base + if ordinals[value] { + continue + } + + symbolAddress = binary.LittleEndian.Uint32(addressOfFunctions[i*4:]) + if symbolAddress == 0 { + continue + } + + // Checking for forwarder again. + if symbolAddress >= rva && symbolAddress < rva+size { + forwarderStr = pe.getStringAtRVA(symbolAddress, 0x100000) + forwarderOffset = pe.getOffsetFromRva(symbolAddress) + } else { + forwarderStr = "" + fileOffset = 0 + } + + // File 0b1d3d3664915577ab9a32188d29bbf3542b86c7b9ce333e245496c3018819f1 + // was being parsed as potentially containing millions of exports. + // Checking for duplicates addresses the issue. + symbolCounts[symbolAddress]++ + if symbolCounts[symbolAddress] > 10 { + if !stringInSlice(ErrExportManyRepeatedEntries, pe.Anomalies) { + pe.Anomalies = append(pe.Anomalies, ErrExportManyRepeatedEntries) + } + } + if len(symbolCounts) > maxExportedSymbols { + if !stringInSlice(ErrExportMaxOrdEntries, pe.Anomalies) { + + pe.Anomalies = append(pe.Anomalies, ErrExportMaxOrdEntries) + } + } + newExport := ExportFunction{ + Ordinal: exportDir.Base + i, + FunctionRVA: symbolAddress, + Forwarder: forwarderStr, + ForwarderRVA: forwarderOffset, + } + + exp.Functions = append(exp.Functions, newExport) + } + + pe.Export = &exp + return nil +} + +// GetExportFunctionByRVA return an export function given an RVA. +func (pe *File) GetExportFunctionByRVA(rva uint32) ExportFunction { + for _, exp := range pe.Export.Functions { + if exp.FunctionRVA == rva { + return exp + } + } + + return ExportFunction{} +} diff --git a/file.go b/file.go new file mode 100644 index 0000000..f025157 --- /dev/null +++ b/file.go @@ -0,0 +1,241 @@ +// Copyright 2021 Saferwall. All rights reserved. +// Use of this source code is governed by Apache v2 license +// license that can be found in the LICENSE file. + +package pe + +import ( + "errors" + "fmt" + "io" + "os" + + mmap "github.com/edsrzf/mmap-go" +) + +// A File represents an open PE file. +type File struct { + DosHeader ImageDosHeader `json:",omitempty"` + RichHeader *RichHeader `json:",omitempty"` + NtHeader ImageNtHeader `json:",omitempty"` + COFF COFF `json:",omitempty"` + Sections []Section `json:",omitempty"` + Imports []Import `json:",omitempty"` + Export *Export `json:",omitempty"` + Debugs []DebugEntry `json:",omitempty"` + Relocations []Relocation `json:",omitempty"` + Resources *ResourceDirectory `json:",omitempty"` + TLS *TLSDirectory `json:",omitempty"` + LoadConfig *LoadConfig `json:",omitempty"` + Exceptions []Exception `json:",omitempty"` + Certificates *Certificate `json:",omitempty"` + DelayImports []DelayImport `json:",omitempty"` + BoundImports []BoundImportDescriptorData `json:",omitempty"` + GlobalPtr uint32 `json:",omitempty"` + CLR *CLRData `json:",omitempty"` + IAT []IATEntry `json:",omitempty"` + Header []byte + data mmap.MMap + closer io.Closer + Is64 bool + Is32 bool + Anomalies []string `json:",omitempty"` + size uint32 + f *os.File + opts *Options +} + +// Options for Parsing +type Options struct { + + // Parse only the header, do not parse data directories, by default (false). + Fast bool + + // Includes section entropy. + SectionEntropy bool +} + +// New instaniates a file instance with options. +func New(name string, opts *Options) (*File, error) { + + f, err := os.Open(name) + if err != nil { + return nil, err + } + + // Memory map the file insead of using read/write. + data, err := mmap.Map(f, mmap.RDONLY, 0) + if err != nil { + f.Close() + return nil, err + } + + file := File{} + if opts != nil { + file.opts = opts + } else { + file.opts = &Options{} + } + file.data = data + file.size = uint32(len(file.data)) + file.f = f + return &file, nil +} + +// Close closes the File. +func (pe *File) Close() error { + var err error + if pe.f != nil { + err = pe.f.Close() + } + return err +} + +// Parse performs the file parsing for a PE binary. +func (pe *File) Parse() error { + + // check for the smallest PE size. + if len(pe.data) < TinyPESize { + return ErrInvalidPESize + } + + // Parse the DOS header. + err := pe.ParseDOSHeader() + if err != nil { + return err + } + + // Parse the Rich header. + err = pe.ParseRichHeader() + if err != nil { + return err + } + + // Parse the NT header. + err = pe.ParseNTHeader() + if err != nil { + return err + } + + // Parse COFF symbol table. + err = pe.ParseCOFFSymbolTable() + if err != nil { + return err + } + + // Parse the Section Header. + err = pe.ParseSectionHeader() + if err != nil { + return err + } + + // Parse the Data Directory entries. + err = pe.ParseDataDirectories() + return err +} + +// PrettyDataDirectory returns the string representations +// of the data directory entry. +func (pe *File) PrettyDataDirectory(entry int) string { + dataDirMap := map[int]string{ + ImageDirectoryEntryExport: "Export", + ImageDirectoryEntryImport: "Import", + ImageDirectoryEntryResource: "Resource", + ImageDirectoryEntryException: "Exception", + ImageDirectoryEntryCertificate: "Security", + ImageDirectoryEntryBaseReloc: "Relocation", + ImageDirectoryEntryDebug: "Debug", + ImageDirectoryEntryArchitecture: "Architecture", + ImageDirectoryEntryGlobalPtr: "GlobalPtr", + ImageDirectoryEntryTLS: "TLS", + ImageDirectoryEntryLoadConfig: "LoadConfig", + ImageDirectoryEntryBoundImport: "BoundImport", + ImageDirectoryEntryIAT: "IAT", + ImageDirectoryEntryDelayImport: "DelayImport", + ImageDirectoryEntryCLR: "CLR", + } + + return dataDirMap[entry] +} + +// ParseDataDirectories parses the data directores. The DataDirectory is an +// array of 16 structures. Each array entry has a predefined meaning for what +// it refers to. +func (pe *File) ParseDataDirectories() error { + + // In fast mode, do not parse data directories. + if pe.opts.Fast { + return nil + } + + foundErr := false + oh32 := ImageOptionalHeader32{} + oh64 := ImageOptionalHeader64{} + + switch pe.Is64 { + case true: + oh64 = pe.NtHeader.OptionalHeader.(ImageOptionalHeader64) + case false: + oh32 = pe.NtHeader.OptionalHeader.(ImageOptionalHeader32) + } + + // Maps data directory index to function which parses that directory. + funcMaps := map[int](func(uint32, uint32) error){ + ImageDirectoryEntryExport: pe.parseExportDirectory, + ImageDirectoryEntryImport: pe.parseImportDirectory, + ImageDirectoryEntryResource: pe.parseResourceDirectory, + ImageDirectoryEntryException: pe.parseExceptionDirectory, + ImageDirectoryEntryCertificate: pe.parseSecurityDirectory, + ImageDirectoryEntryBaseReloc: pe.parseRelocDirectory, + ImageDirectoryEntryDebug: pe.parseDebugDirectory, + ImageDirectoryEntryArchitecture: pe.parseArchitectureDirectory, + ImageDirectoryEntryGlobalPtr: pe.parseGlobalPtrDirectory, + ImageDirectoryEntryTLS: pe.parseTLSDirectory, + ImageDirectoryEntryLoadConfig: pe.parseLoadConfigDirectory, + ImageDirectoryEntryBoundImport: pe.parseBoundImportDirectory, + ImageDirectoryEntryIAT: pe.parseIATDirectory, + ImageDirectoryEntryDelayImport: pe.parseDelayImportDirectory, + ImageDirectoryEntryCLR: pe.parseCLRHeaderDirectory, + } + + // Iterate over data directories and call the appropriate function. + for entryIndex := 0; entryIndex < ImageNumberOfDirectoryEntries; entryIndex++ { + + var va, size uint32 + switch pe.Is64 { + case true: + dirEntry := oh64.DataDirectory[entryIndex] + va = dirEntry.VirtualAddress + size = dirEntry.Size + case false: + dirEntry := oh32.DataDirectory[entryIndex] + va = dirEntry.VirtualAddress + size = dirEntry.Size + } + + if va != 0 { + func() { + // keep parsing data directories even though some entries fails. + defer func() { + if e := recover(); e != nil { + fmt.Printf("Unhandled Exception when trying to parse data directory %s, reason: %v\n", + pe.PrettyDataDirectory(entryIndex), e) + foundErr = true + } + }() + + err := funcMaps[entryIndex](va, size) + if err != nil { + fmt.Printf("Failed to parse data directory %s, reason: %v\n", + pe.PrettyDataDirectory(entryIndex), err) + foundErr = true + } + }() + } + } + + if foundErr { + return errors.New("Data directory parsing failed") + } + return nil +} diff --git a/file_test.go b/file_test.go new file mode 100644 index 0000000..42d1b22 --- /dev/null +++ b/file_test.go @@ -0,0 +1,42 @@ +// Copyright 2021 Saferwall. All rights reserved. +// Use of this source code is governed by Apache v2 license +// license that can be found in the LICENSE file. + +package pe + +import ( + "path" + "path/filepath" + "runtime" + "testing" +) + +func getAbsoluteFilePath(testfile string) string { + _, p, _, _ := runtime.Caller(0) + return path.Join(filepath.Dir(p), "..", "..", testfile) +} + +var tridtests = []struct { + in string + out error +}{ + {getAbsoluteFilePath("test/multiav/clean/putty.exe"), nil}, +} + +func TestParse(t *testing.T) { + for _, tt := range tridtests { + t.Run(tt.in, func(t *testing.T) { + filePath := tt.in + mype, err := New(filePath, nil) + if err != nil { + t.Errorf("TestParse(%s) failed, reason: %v", tt.in, err) + return + } + + got := mype.Parse() + if err != nil { + t.Errorf("TestParse(%s) got %v, want %v", tt.in, got, tt.out) + } + }) + } +} diff --git a/globalptr.go b/globalptr.go new file mode 100644 index 0000000..86e5833 --- /dev/null +++ b/globalptr.go @@ -0,0 +1,31 @@ +// Copyright 2021 Saferwall. All rights reserved. +// Use of this source code is governed by Apache v2 license +// license that can be found in the LICENSE file. + +package pe + +import ( + "encoding/binary" + "log" +) + +// RVA of the value to be stored in the global pointer register. The size must +// be set to 0. This data directory is set to all zeros if the target +// architecture (for example, I386 or AMD64) does not use the concept of a \ +// global pointer. +func (pe *File) parseGlobalPtrDirectory(rva, size uint32) error { + + log.Println("PE Contains a Global Pointer directory") + + // RVA of the value to be stored in the global pointer register. + offset := pe.getOffsetFromRva(rva) + if offset == ^uint32(0) { + pe.Anomalies = append(pe.Anomalies, + "Global pointer register offset outside of image") + return nil + } + + pe.GlobalPtr = binary.LittleEndian.Uint32(pe.data[offset:]) + + return nil +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..32aebb2 --- /dev/null +++ b/go.mod @@ -0,0 +1,9 @@ +module github.com/saferwall/pe + +go 1.15 + +require ( + github.com/edsrzf/mmap-go v1.0.0 + go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1 + golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..99ec715 --- /dev/null +++ b/go.sum @@ -0,0 +1,6 @@ +github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw= +github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1 h1:A/5uWzF44DlIgdm/PQFwfMkW0JX+cIcQi/SwLAmZP5M= +go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= +golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f h1:gWF768j/LaZugp8dyS4UwsslYCYz9XgFxvlgsn0n9H8= +golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/helper.go b/helper.go new file mode 100644 index 0000000..e118e6e --- /dev/null +++ b/helper.go @@ -0,0 +1,669 @@ +// Copyright 2021 Saferwall. All rights reserved. +// Use of this source code is governed by Apache v2 license +// license that can be found in the LICENSE file. + +package pe + +import ( + "bytes" + "encoding/binary" + "errors" + "log" + "strings" +) + +const ( + // TinyPESize On Windows XP (x32) the smallest PE executable is 97 bytes. + TinyPESize = 97 + + // FileAlignmentHardcodedValue represents the value which PointerToRawData + // should be at least equal or bigger to, or it will be rounded to zero. + // According to http://corkami.blogspot.com/2010/01/parce-que-la-planche-aura-brule.html + // if PointerToRawData is less that 0x200 it's rounded to zero. + FileAlignmentHardcodedValue = 0x200 +) + +// Errors +var ( + + // ErrInvalidPESize is returned when the file size is less that the smallest + // PE file size possible.ErrImageOS2SignatureFound + ErrInvalidPESize = errors.New("Not a PE file, smaller than tiny PE") + + // ErrDOSMagicNotFound is returned when file is potentially a ZM executable. + ErrDOSMagicNotFound = errors.New("DOS Header magic not found") + + // ErrInvalidElfanewValue is returned when e_lfanew is larger than file size. + ErrInvalidElfanewValue = errors.New( + "Invalid e_lfanew value. Probably not a PE file") + + // ErrImageOS2SignatureFound is returned when signature is for a NE file. + ErrImageOS2SignatureFound = errors.New( + "Not a valid PE signature. Probably a NE file") + + // ErrImageOS2LESignatureFound is returned when signature is for a LE file. + ErrImageOS2LESignatureFound = errors.New( + "Not a valid PE signature. Probably an LE file") + + // ErrImageVXDSignatureFound is returned when signature is for a LX file. + ErrImageVXDSignatureFound = errors.New( + "Not a valid PE signature. Probably an LX file") + + // ErrImageTESignatureFound is returned when signature is for a TE file. + ErrImageTESignatureFound = errors.New( + "Not a valid PE signature. Probably a TE file") + + // ErrImageNtSignatureNotFound is returned when PE magic signature is not found. + ErrImageNtSignatureNotFound = errors.New( + "Not a valid PE signature. Magic not found") + + // ErrImageNtOptionalHeaderMagicNotFound is returned when optional header + // magic is different from PE32/PE32+. + ErrImageNtOptionalHeaderMagicNotFound = errors.New( + "Not a valid PE signature. Optional Header magic not found") + + // ErrImageBaseNotAligned is reported when the image base is not aligned to 64K. + ErrImageBaseNotAligned = errors.New( + "Corrupt PE file. Image base not aligned to 64 K") + + // AnoImageBaseOverflow is reported when the image base + SizeOfImage is + // larger than 80000000h/FFFF080000000000h in PE32/P32+. + AnoImageBaseOverflow = "Image base beyong allowed address" + + // ErrInvalidSectionFileAlignment is reported when section alignment is less than a + // PAGE_SIZE and section alignement != file alignment. + ErrInvalidSectionFileAlignment = errors.New("Corrupt PE file. Section " + + "alignment is less than a PAGE_SIZE and section alignement != file alignment") + + // AnoInvalidSizeOfImage is reported when SizeOfImage is not multiple of + // SectionAlignment. + AnoInvalidSizeOfImage = "Invalid SizeOfImage value, should be multiple " + + "of SectionAlignment" + + // ErrOutsideBoundary is reported when attempting to read an address beyond + // file image limits. + ErrOutsideBoundary = errors.New("Reading data outside boundary") +) + +// Max returns the larger of x or y. +func Max(x, y uint32) uint32 { + if x < y { + return y + } + return x +} + +func min(a, b uint32) uint32 { + if a < b { + return a + } + return b +} + +// Min returns the min number in a slice. +func Min(values []uint32) uint32 { + min := values[0] + for _, v := range values { + if v < min { + min = v + } + } + return min +} + +// IsValidDosFilename returns true if the DLL name is likely to be valid. +// Valid FAT32 8.3 short filename characters according to: +// http://en.wikipedia.org/wiki/8.3_filename +// The filename length is not checked because the DLLs filename +// can be longer that the 8.3 +func IsValidDosFilename(filename string) bool { + alphabet := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + numerals := "0123456789" + special := "!#$%&'()-@^_`{}~+,.;=[]\\/" + charset := alphabet + numerals + special + for _, c := range filename { + if !strings.Contains(charset, string(c)) { + return false + } + } + return true +} + +// IsValidFunctionName checks if an imported name uses the valid accepted +// characters expected in mangled function names. If the symbol's characters +// don't fall within this charset we will assume the name is invalid. +func IsValidFunctionName(functionName string) bool { + alphabet := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + numerals := "0123456789" + special := "_?@$()<>" + charset := alphabet + numerals + special + for _, c := range charset { + if !strings.Contains(charset, string(c)) { + return false + } + } + return true +} + +// IsPrintable checks weather a string is printable. +func IsPrintable(s string) bool { + alphabet := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + numerals := "0123456789" + whitespace := " \t\n\r\v\f" + special := "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~" + charset := alphabet + numerals + special + whitespace + for _, c := range charset { + if !strings.Contains(charset, string(c)) { + return false + } + } + return true +} + +// getSectionByRva returns the section containing the given address. +func (pe *File) getSectionByRva(rva uint32) *Section { + for _, section := range pe.Sections { + if section.Contains(rva, pe) { + return §ion + } + } + return nil +} + +// getSectionByRva returns the section name containing the given address. +func (pe *File) getSectionNameByRva(rva uint32) string { + for _, section := range pe.Sections { + if section.Contains(rva, pe) { + return section.NameString() + } + } + return "" +} + +func (pe *File) getSectionByOffset(offset uint32) *Section { + for _, section := range pe.Sections { + if section.Header.PointerToRawData == 0 { + continue + } + + adjustedPointer := pe.adjustFileAlignment( + section.Header.PointerToRawData) + if adjustedPointer <= offset && + offset < (adjustedPointer+section.Header.SizeOfRawData) { + return §ion + } + } + return nil +} + +// getOffsetFromRva returns the file offset corresponding to this RVA. +func (pe *File) getOffsetFromRva(rva uint32) uint32 { + + // Given a RVA, this method will find the section where the + // data lies and return the offset within the file. + section := pe.getSectionByRva(rva) + if section == nil { + if rva < uint32(len(pe.data)) { + return rva + } + return ^uint32(0) + } + sectionAlignment := pe.adjustSectionAlignment(section.Header.VirtualAddress) + fileAlignment := pe.adjustFileAlignment(section.Header.PointerToRawData) + return rva - sectionAlignment + fileAlignment +} + +func (pe *File) getRvaFromOffset(offset uint32) uint32 { + section := pe.getSectionByOffset(offset) + minAddr := ^uint32(0) + if section == nil { + + if len(pe.Sections) == 0 { + return offset + } + + for _, section := range pe.Sections { + vaddr := pe.adjustSectionAlignment(section.Header.VirtualAddress) + if vaddr < minAddr { + minAddr = vaddr + } + } + // Assume that offset lies within the headers + // The case illustrating this behavior can be found at: + // http://corkami.blogspot.com/2010/01/hey-hey-hey-whats-in-your-head.html + // where the import table is not contained by any section + // hence the RVA needs to be resolved to a raw offset + if offset < minAddr { + return offset + } + + log.Println("data at Offset can't be fetched. Corrupt header?") + return ^uint32(0) + } + sectionAlignment := pe.adjustSectionAlignment(section.Header.VirtualAddress) + fileAlignment := pe.adjustFileAlignment(section.Header.PointerToRawData) + return offset - fileAlignment + sectionAlignment +} + +func (pe *File) getSectionByName(secName string) (section *ImageSectionHeader) { + for _, section := range pe.Sections { + if section.NameString() == secName { + return §ion.Header + } + + } + return nil +} + +// getStringAtRVA returns an ASCII string located at the given address. +func (pe *File) getStringAtRVA(rva, maxLen uint32) string { + if rva == 0 { + return "" + } + + section := pe.getSectionByRva(rva) + if section == nil { + if rva > pe.size { + return "" + } + + end := rva + maxLen + if end > pe.size { + end = pe.size + } + s := pe.getStringFromData(0, pe.data[rva:end]) + return string(s) + } + s := pe.getStringFromData(0, section.Data(rva, maxLen, pe)) + return string(s) +} + +func (pe *File) readUnicodeStringAtRVA(rva uint32, maxLength uint32) string { + unicodeString := "" + offset := pe.getOffsetFromRva(rva) + buff := pe.data[offset : offset+(maxLength*2)] + for i := uint32(0); i < maxLength*2; i += 2 { + unicodeString += string(buff[i]) + } + return unicodeString +} + +func (pe *File) readASCIIStringAtOffset(offset, maxLength uint32) (uint32, string) { + var i uint32 + str := "" + buff := pe.data[offset : offset+maxLength] + for i = 0; i < maxLength; i++ { + if buff[i] == 0 { + break + } + str += string(buff[i]) + } + return i, str +} + +// getStringFromData returns ASCII string from within the data. +func (pe *File) getStringFromData(offset uint32, data []byte) []byte { + + dataSize := uint32(len(data)) + if dataSize == 0 { + return nil + } + + if offset > dataSize { + return nil + } + + end := offset + for end < dataSize { + if data[end] == 0 { + break + } + end++ + } + return data[offset:end] +} + +// getStringAtOffset returns a string given an offset. +func (pe *File) getStringAtOffset(offset, size uint32) (string, error) { + if offset+size > pe.size { + return "", ErrOutsideBoundary + } + + str := string(pe.data[offset : offset+size]) + return strings.Replace(str, "\x00", "", -1), nil +} + +// getData returns the data given an RVA regardless of the section where it lies on. +func (pe *File) getData(rva, length uint32) ([]byte, error) { + + // Given a RVA and the size of the chunk to retrieve, this method + // will find the section where the data lies and return the data. + section := pe.getSectionByRva(rva) + + var end uint32 + if length > 0 { + end = rva + length + } else { + end = 0 + } + + if section == nil { + if rva < uint32(len(pe.Header)) { + return pe.Header[rva:end], nil + } + + // Before we give up we check whether the file might contain the data + // anyway. There are cases of PE files without sections that rely on + // windows loading the first 8291 bytes into memory and assume the data + // will be there. A functional file with these characteristics is: + // MD5: 0008892cdfbc3bda5ce047c565e52295 + // SHA-1: c7116b9ff950f86af256defb95b5d4859d4752a9 + + if rva < uint32(len(pe.data)) { + return pe.data[rva:end], nil + } + + return nil, errors.New("data at RVA can't be fetched. Corrupt header?") + } + return section.Data(rva, length, pe), nil +} + +// The alignment factor (in bytes) that is used to align the raw data of sections +// in the image file. The value should be a power of 2 between 512 and 64 K, +// inclusive. The default is 512. If the SectionAlignment is less than the +// architecture's page size, then FileAlignment must match SectionAlignment. +func (pe *File) adjustFileAlignment(va uint32) uint32 { + + var fileAlignment uint32 + switch pe.Is64 { + case true: + fileAlignment = pe.NtHeader.OptionalHeader.(ImageOptionalHeader64).FileAlignment + case false: + fileAlignment = pe.NtHeader.OptionalHeader.(ImageOptionalHeader32).FileAlignment + } + + if fileAlignment > FileAlignmentHardcodedValue && fileAlignment%2 != 0 { + pe.Anomalies = append(pe.Anomalies, ErrInvalidFileAlignment) + } + + if fileAlignment < FileAlignmentHardcodedValue { + return va + } + + // round it to 0x200 if not power of 2. + // According to https://github.com/corkami/docs/blob/master/PE/PE.md + // if PointerToRawData is less that 0x200 it's rounded to zero. Loading the + // test filein a debugger it's easy to verify that the PointerToRawData + // value of 1 is rounded to zero. Hence we reproduce the behavior + return (va / 0x200) * 0x200 + +} + +// The alignment (in bytes) of sections when they are loaded into memory +// It must be greater than or equal to FileAlignment. The default is the +// page size for the architecture. +func (pe *File) adjustSectionAlignment(va uint32) uint32 { + var fileAlignment, sectionAlignment uint32 + + switch pe.Is64 { + case true: + fileAlignment = pe.NtHeader.OptionalHeader.(ImageOptionalHeader64).FileAlignment + sectionAlignment = pe.NtHeader.OptionalHeader.(ImageOptionalHeader64).FileAlignment + case false: + fileAlignment = pe.NtHeader.OptionalHeader.(ImageOptionalHeader32).SectionAlignment + sectionAlignment = pe.NtHeader.OptionalHeader.(ImageOptionalHeader32).SectionAlignment + } + + if fileAlignment < FileAlignmentHardcodedValue && + fileAlignment != sectionAlignment { + pe.Anomalies = append(pe.Anomalies, ErrInvalidSectionAlignment) + } + + // 0x200 is the minimum valid FileAlignment according to the documentation + // although ntoskrnl.exe has an alignment of 0x80 in some Windows versions + if sectionAlignment != 0 && va%sectionAlignment != 0 { + return sectionAlignment * (va / sectionAlignment) + } + return va +} + +// stringInSlice checks weather a string exists in a slice of strings. +func stringInSlice(a string, list []string) bool { + for _, b := range list { + if b == a { + return true + } + } + return false +} + +// intInSlice checks weather a uint32 exists in a slice of uint32. +func intInSlice(a uint32, list []uint32) bool { + for _, b := range list { + if b == a { + return true + } + } + return false +} + +// IsDriver returns true if the PE file is a Windows driver. +func (pe *File) IsDriver() bool { + + // Checking that the ImageBase field of the OptionalHeader is above or + // equal to 0x80000000 (that is, whether it lies in the upper 2GB of + //the address space, normally belonging to the kernel) is not a + // reliable enough indicator. For instance, PEs that play the invalid + // ImageBase trick to get relocated could be incorrectly assumed to be + // drivers. + + // Checking if any section characteristics have the IMAGE_SCN_MEM_NOT_PAGED + // flag set is not reliable either. + + // If there's still no import directory (the PE doesn't have one or it's + // malformed), give up. + if len(pe.Imports) == 0 { + return false + } + + // DIRECTORY_ENTRY_IMPORT will now exist, although it may be empty. + // If it imports from "ntoskrnl.exe" or other kernel components it should + // be a driver. + systemDLLs := []string{"ntoskrnl.exe", "hal.dll", "ndis.sys", + "bootvid.dll", "kdcom.dll"} + for _, dll := range pe.Imports { + if stringInSlice(strings.ToLower(dll.Name), systemDLLs) { + return true + } + } + + // If still we couldn't tell, check common driver section with combinaison + // of IMAGE_SUBSYSTEM_NATIVE or IMAGE_SUBSYSTEM_NATIVE_WINDOWS. + subsystem := uint16(0) + oh32 := ImageOptionalHeader32{} + oh64 := ImageOptionalHeader64{} + switch pe.Is64 { + case true: + oh64 = pe.NtHeader.OptionalHeader.(ImageOptionalHeader64) + subsystem = oh64.Subsystem + case false: + oh32 = pe.NtHeader.OptionalHeader.(ImageOptionalHeader32) + subsystem = oh32.Subsystem + } + commonDriverSectionNames := []string{"page", "paged", "nonpage", "init"} + for _, section := range pe.Sections { + s := strings.ToLower(section.NameString()) + if stringInSlice(s, commonDriverSectionNames) && + (subsystem&ImageSubsystemNativeWindows != 0 || + subsystem&ImageSubsystemNative != 0) { + return true + } + + } + + return false +} + +// IsDLL returns true if the PE file is a standard DLL. +func (pe *File) IsDLL() bool { + if pe.NtHeader.FileHeader.Characteristics&ImageFileDLL != 0 { + return true + } + return false +} + +// IsEXE returns true if the PE file is a standard executable. +func (pe *File) IsEXE() bool { + + // Returns true only if the file has the IMAGE_FILE_EXECUTABLE_IMAGE flag set + // and the IMAGE_FILE_DLL not set and the file does not appear to be a driver either. + if pe.IsDLL() || pe.IsDriver() { + return false + } + + if pe.NtHeader.FileHeader.Characteristics&ImageFileExecutableImage == 0 { + return false + } + + return true +} + +// padOrTrim returns (size) bytes from input (bb) +// Short bb gets zeros prefixed, Long bb gets left/MSB bits trimmed +func padOrTrim(bb []byte, size int) []byte { + l := len(bb) + if l == size { + return bb + } + if l > size { + return bb[l-size:] + } + tmp := make([]byte, size) + copy(tmp[size-l:], bb) + return tmp +} + +// Checksum calculates the PE checksum as generated by CheckSumMappedFile(). +func (pe *File) Checksum() uint32 { + checksum := 0 + max := 0x100000000 + currentDword := uint32(0) + + // Get the Checksum offset. + optionalHeaderOffset := pe.DosHeader.AddressOfNewEXEHeader + uint32(binary.Size(pe.NtHeader)) + + // `CheckSum` field position in optional PE headers is always 64 for PE and PE+. + checksumOffset := optionalHeaderOffset + 64 + + // Verify the data is DWORD-aligned and add padding if needed + remainder := pe.size % 4 + dataLen := pe.size + if remainder > 0 { + dataLen = pe.size + (4 - remainder) + } + + for i := uint32(0); i < dataLen/4; i++ { + // Skip the checksum field + if i*4 == checksumOffset { + continue + } + + // Did we reach the last dword ? + if i+1 == dataLen/4 && remainder > 0 { + bb := pe.data[i*4 : i*4+(4-remainder)] + lastDword := padOrTrim(bb, 4) + currentDword = binary.LittleEndian.Uint32(lastDword) + } else { + currentDword = binary.LittleEndian.Uint32(pe.data[i*4:]) + } + + checksum += int(currentDword) + if checksum > max { + checksum = (checksum & 0xffffffff) + (checksum >> 32) + } + } + + checksum = (checksum & 0xffff) + (checksum >> 16) + checksum = checksum + (checksum >> 16) + checksum = checksum & 0xffff + + // The length is the one of the original data, not the padded one + checksum += int(pe.size) + + return uint32(checksum) +} + +// ReadUint64 read a uint64 from a buffer. +func (pe *File) ReadUint64(offset uint32) (uint64, error) { + if offset+8 > pe.size { + return 0, ErrOutsideBoundary + } + + return binary.LittleEndian.Uint64(pe.data[offset:]), nil +} + +// ReadUint32 read a uint32 from a buffer. +func (pe *File) ReadUint32(offset uint32) (uint32, error) { + if offset+4 > pe.size { + return 0, ErrOutsideBoundary + } + + return binary.LittleEndian.Uint32(pe.data[offset:]), nil +} + +// ReadUint16 read a uint16 from a buffer. +func (pe *File) ReadUint16(offset uint32) (uint16, error) { + if offset+2 > pe.size { + return 0, ErrOutsideBoundary + } + + return binary.LittleEndian.Uint16(pe.data[offset:]), nil +} + +// ReadUint8 read a uint8 from a buffer. +func (pe *File) ReadUint8(offset uint32) (uint8, error) { + if offset+1 > pe.size { + return 0, ErrOutsideBoundary + } + + b := pe.data[offset : offset+1][0] + return uint8(b), nil +} + +func (pe *File) structUnpack(iface interface{}, offset, size uint32) (err error) { + // Boundary check + totalSize := offset + size + + // Integer overflow + if (totalSize > offset) != (size > 0) { + return ErrOutsideBoundary + } + + if offset >= pe.size || totalSize > pe.size { + return ErrOutsideBoundary + } + + buf := bytes.NewReader(pe.data[offset : offset+size]) + err = binary.Read(buf, binary.LittleEndian, iface) + if err != nil { + return err + } + return nil +} + +// ReadBytesAtOffset returns a byte array from offset. +func (pe *File) ReadBytesAtOffset(offset, size uint32) ([]byte, error) { + // Boundary check + totalSize := offset + size + + // Integer overflow + if (totalSize > offset) != (size > 0) { + return nil, ErrOutsideBoundary + } + + if offset >= pe.size || totalSize > pe.size { + return nil, ErrOutsideBoundary + } + + return pe.data[offset : offset+size], nil +} diff --git a/iat.go b/iat.go new file mode 100644 index 0000000..e437da6 --- /dev/null +++ b/iat.go @@ -0,0 +1,66 @@ +// Copyright 2021 Saferwall. All rights reserved. +// Use of this source code is governed by Apache v2 license +// license that can be found in the LICENSE file. + +package pe + +// IATEntry represents an entry inside the IAT. +type IATEntry struct { + Index uint32 + Rva uint32 + Value interface{} + Meaning string +} + +// The structure and content of the import address table are identical to those +// of the import lookup table, until the file is bound. During binding, the +// entries in the import address table are overwritten with the 32-bit (for +// PE32) or 64-bit (for PE32+) addresses of the symbols that are being imported. +// These addresses are the actual memory addresses of the symbols, although +// technically they are still called “virtual addresses.” The loader typically +// processes the binding. +// +// The Import Address Table is there to to only trigger Copy On Write for as +// few pages as possible (those being the actual Import Address Table pages +// themselves). +// This is, partially the reason there's that extra level of indirection in the +// PE to begin with. +func (pe *File) parseIATDirectory(rva, size uint32) error { + + var entries []IATEntry + var index uint32 + var err error + + startRva := rva + + for startRva+size > rva { + ie := IATEntry{} + offset := pe.getOffsetFromRva(rva) + if pe.Is64 { + ie.Value, err = pe.ReadUint64(offset) + if err != nil { + break + } + ie.Rva = rva + rva += 8 + } else { + ie.Value, err = pe.ReadUint32(offset) + if err != nil { + break + } + ie.Rva = rva + + rva += 4 + } + ie.Index = index + imp, i := pe.GetImportEntryInfoByRVA(rva) + if len(imp.Functions) != 0 { + ie.Meaning = imp.Name + "!" + imp.Functions[i].Name + } + entries = append(entries, ie) + index++ + } + + pe.IAT = entries + return nil +} diff --git a/imports.go b/imports.go new file mode 100644 index 0000000..4b00f66 --- /dev/null +++ b/imports.go @@ -0,0 +1,793 @@ +// Copyright 2021 Saferwall. All rights reserved. +// Use of this source code is governed by Apache v2 license +// license that can be found in the LICENSE file. + +package pe + +import ( + "crypto/md5" + "encoding/binary" + "encoding/hex" + "errors" + "fmt" + "log" + "strconv" + "strings" +) + +const ( + imageOrdinalFlag32 = uint32(0x80000000) + imageOrdinalFlag64 = uint64(0x8000000000000000) + maxRepeatedAddresses = uint32(0xF) + maxAddressSpread = uint32(0x8000000) + addressMask32 = uint32(0x7fffffff) + addressMask64 = uint64(0x7fffffffffffffff) + maxDllLength = 0x200 + maxImportNameLength = 0x200 +) + +var ( + // AnoInvalidThunkAddressOfData is reported when thunk address is too spread out. + AnoInvalidThunkAddressOfData = "Thunk Address Of Data too spread out" + + // AnoManyRepeatedEntries is reported when import directory contains many + // entries have the same RVA. + AnoManyRepeatedEntries = "Import directory contains many repeated entries" + + // AnoAddressOfDataBeyondLimits is reported when Thunk AddressOfData goes + // beyond limites. + AnoAddressOfDataBeyondLimits = "Thunk AddressOfData beyond limits" + + // AnoImportNoNameNoOrdinal is reported when an import entry does not have + // a name neither an oridinal, most probably malformed data. + AnoImportNoNameNoOrdinal = "Must have either an ordinal or a name in an import" + + // ErrDamagedImportTable is reported when the IAT and ILT table length is 0. + ErrDamagedImportTable = errors.New( + "Damaged Import Table information. ILT and/or IAT appear to be broken") +) + +// ImageImportDescriptor describes the remainder of the import information. +// The import directory table contains address information that is used to +// resolve fixup references to the entry points within a DLL image. +// It consists of an array of import directory entries, one entry for each DLL +// to which the image refers. The last directory entry is empty (filled with +// null values), which indicates the end of the directory table. +type ImageImportDescriptor struct { + // The RVA of the import lookup/name table (INT). This table contains a name + // or ordinal for each import. The INT is an array of IMAGE_THUNK_DATA structs. + OriginalFirstThunk uint32 + + // The stamp that is set to zero until the image is bound. After the image + // is bound, this field is set to the time/data stamp of the DLL. + TimeDateStamp uint32 + + // The index of the first forwarder reference (-1 if no forwarders). + ForwarderChain uint32 + + // The address of an ASCII string that contains the name of the DLL. + // This address is relative to the image base. + Name uint32 + + // The RVA of the import address table (IAT). The contents of this table are + // identical to the contents of the import lookup table until the image is bound. + FirstThunk uint32 +} + +// ImageThunkData32 corresponds to one imported function from the executable. +// The entries are an array of 32-bit numbers for PE32 or an array of 64-bit +// numbers for PE32+. The ends of both arrays are indicated by an +// IMAGE_THUNK_DATA element with a value of zero. +// The IMAGE_THUNK_DATA union is a DWORD with these interpretations: +// DWORD Function; // Memory address of the imported function +// DWORD Ordinal; // Ordinal value of imported API +// DWORD AddressOfData; // RVA to an IMAGE_IMPORT_BY_NAME with the imported API name +// DWORD ForwarderString;// RVA to a forwarder string +type ImageThunkData32 struct { + AddressOfData uint32 +} + +// ImageThunkData64 is the PE32+ version of IMAGE_THUNK_DATA. +type ImageThunkData64 struct { + AddressOfData uint64 +} + +type ThunkData32 struct { + ImageThunkData ImageThunkData32 + Offset uint32 +} + +type ThunkData64 struct { + ImageThunkData ImageThunkData64 + Offset uint32 +} + +// ImportFunction represents an imported function in the import table. +type ImportFunction struct { + // An ASCII string that contains the name to import. This is the string that + // must be matched to the public name in the DLL. This string is case + // sensitive and terminated by a null byte. + Name string + + // An index into the export name pointer table. A match is attempted first + // with this value. If it fails, a binary search is performed on the DLL's + // export name pointer table. + Hint uint16 + + // If this is true, import by ordinal. Otherwise, import by name. + ByOrdinal bool + + // A 16-bit ordinal number. This field is used only if the Ordinal/Name Flag + // bit field is 1 (import by ordinal). Bits 30-15 or 62-15 must be 0. + Ordinal uint32 + + // Name Thunk Value (OFT) + OriginalThunkValue uint64 + + // Address Thunk Value (FT) + ThunkValue uint64 + + // Address Thunk RVA. + ThunkRVA uint32 + + // Name Thunk RVA. + OriginalThunkRVA uint32 +} + +// Import represents an empty entry in the emport table. +type Import struct { + Offset uint32 + Name string + Functions []*ImportFunction + Descriptor ImageImportDescriptor +} + +func (pe *File) parseImportDirectory(rva, size uint32) (err error) { + + for { + importDesc := ImageImportDescriptor{} + fileOffset := pe.getOffsetFromRva(rva) + importDescSize := uint32(binary.Size(importDesc)) + err := pe.structUnpack(&importDesc, fileOffset, importDescSize) + + // If the RVA is invalid all would blow up. Some EXEs seem to be + // specially nasty and have an invalid RVA. + if err != nil { + return err + } + + // If the structure is all zeros, we reached the end of the list. + if importDesc == (ImageImportDescriptor{}) { + break + } + + rva += importDescSize + + // If the array of thunks is somewhere earlier than the import + // descriptor we can set a maximum length for the array. Otherwise + // just set a maximum length of the size of the file + maxLen := uint32(len(pe.data)) - fileOffset + if rva > importDesc.OriginalFirstThunk || rva > importDesc.FirstThunk { + if rva < importDesc.OriginalFirstThunk { + maxLen = rva - importDesc.FirstThunk + } else if rva < importDesc.FirstThunk { + maxLen = rva - importDesc.OriginalFirstThunk + } else { + maxLen = Max(rva-importDesc.OriginalFirstThunk, + rva-importDesc.FirstThunk) + } + } + + var importedFunctions []*ImportFunction + if pe.Is64 { + importedFunctions, err = pe.parseImports64(&importDesc, maxLen) + } else { + importedFunctions, err = pe.parseImports32(&importDesc, maxLen) + } + if err != nil { + return err + } + + dllName := pe.getStringAtRVA(importDesc.Name, maxDllLength) + if !IsValidDosFilename(dllName) { + dllName = "*invalid*" + continue + } + + pe.Imports = append(pe.Imports, Import{ + Offset: fileOffset, + Name: string(dllName), + Functions: importedFunctions, + Descriptor: importDesc, + }) + } + + return nil +} + +func (pe *File) getImportTable32(rva uint32, maxLen uint32, + isOldDelayImport bool) ([]*ThunkData32, error) { + + // Setup variables + thunkTable := make(map[uint32]*ImageThunkData32) + retVal := make([]*ThunkData32, 0) + minAddressOfData := ^uint32(0) + maxAddressOfData := uint32(0) + repeatedAddress := uint32(0) + var size uint32 = 4 + addressesOfData := make(map[uint32]bool) + + startRVA := rva + + if rva == 0 { + return []*ThunkData32{}, nil + } + + for { + if rva >= startRVA+maxLen { + log.Println("Error parsing the import table. Entries go beyond bounds.") + break + } + + // if we see too many times the same entry we assume it could be + // a table containing bogus data (with malicious intent or otherwise) + if repeatedAddress >= maxRepeatedAddresses { + if !stringInSlice(AnoManyRepeatedEntries, pe.Anomalies) { + pe.Anomalies = append(pe.Anomalies, AnoManyRepeatedEntries) + } + } + + // if the addresses point somewhere but the difference between the + // highest and lowest address is larger than maxAddressSpread we assume + // a bogus table as the addresses should be contained within a module + if maxAddressOfData-minAddressOfData > maxAddressSpread { + if !stringInSlice(AnoInvalidThunkAddressOfData, pe.Anomalies) { + pe.Anomalies = append(pe.Anomalies, AnoInvalidThunkAddressOfData) + } + } + + // In its original incarnation in Visual C++ 6.0, all ImgDelayDescr + // fields containing addresses used virtual addresses, rather than RVAs. + // That is, they contained actual addresses where the delayload data + // could be found. These fields are DWORDs, the size of a pointer on the x86. + // Now fast-forward to IA-64 support. All of a sudden, 4 bytes isn't + // enough to hold a complete address. At this point, Microsoft did the + // correct thing and changed the fields containing addresses to RVAs. + offset := uint32(0) + if isOldDelayImport { + oh32 := pe.NtHeader.OptionalHeader.(ImageOptionalHeader32) + newRVA := rva - oh32.ImageBase + offset = pe.getOffsetFromRva(newRVA) + if offset == ^uint32(0) { + return []*ThunkData32{}, nil + } + } else { + offset = pe.getOffsetFromRva(rva) + if offset == ^uint32(0) { + return []*ThunkData32{}, nil + } + } + + // Read the image thunk data. + thunk := ImageThunkData32{} + err := pe.structUnpack(&thunk, offset, size) + if err != nil { + // log.Printf("Error parsing the import table. " + + // "Invalid data at RVA: 0x%x", rva) + return []*ThunkData32{}, nil + } + + if thunk == (ImageThunkData32{}) { + break + } + + // Check if the AddressOfData lies within the range of RVAs that it's + // being scanned, abort if that is the case, as it is very unlikely + // to be legitimate data. + // Seen in PE with SHA256: + // 5945bb6f0ac879ddf61b1c284f3b8d20c06b228e75ae4f571fa87f5b9512902c + if thunk.AddressOfData >= startRVA && thunk.AddressOfData <= rva { + log.Printf("Error parsing the import table. "+ + "AddressOfData overlaps with THUNK_DATA for THUNK at: "+ + "RVA 0x%x", rva) + break + } + + if thunk.AddressOfData&imageOrdinalFlag32 > 0 { + // If the entry looks like could be an ordinal. + if thunk.AddressOfData&0x7fffffff > 0xffff { + // but its value is beyond 2^16, we will assume it's a + // corrupted and ignore it altogether + if !stringInSlice(AnoAddressOfDataBeyondLimits, pe.Anomalies) { + pe.Anomalies = append(pe.Anomalies, AnoAddressOfDataBeyondLimits) + } + } + } else { + // and if it looks like it should be an RVA keep track of the RVAs seen + // and store them to study their properties. When certain non-standard + // features are detected the parsing will be aborted + _, ok := addressesOfData[thunk.AddressOfData] + if ok { + repeatedAddress++ + } else { + addressesOfData[thunk.AddressOfData] = true + } + + if thunk.AddressOfData > maxAddressOfData { + maxAddressOfData = thunk.AddressOfData + } + + if thunk.AddressOfData < minAddressOfData { + minAddressOfData = thunk.AddressOfData + } + } + + thunkTable[rva] = &thunk + thunkData := ThunkData32{ImageThunkData: thunk, Offset: rva} + retVal = append(retVal, &thunkData) + rva += size + } + return retVal, nil +} + +func (pe *File) getImportTable64(rva uint32, maxLen uint32, + isOldDelayImport bool) ([]*ThunkData64, error) { + + // Setup variables + thunkTable := make(map[uint32]*ImageThunkData64) + retVal := make([]*ThunkData64, 0) + minAddressOfData := ^uint64(0) + maxAddressOfData := uint64(0) + repeatedAddress := uint64(0) + var size uint32 = 8 + addressesOfData := make(map[uint64]bool) + + startRVA := rva + + if rva == 0 { + return []*ThunkData64{}, nil + } + + for { + if rva >= startRVA+maxLen { + log.Println("Error parsing the import table. Entries go beyond bounds.") + break + } + + // if we see too many times the same entry we assume it could be + // a table containing bogus data (with malicious intent or otherwise) + if repeatedAddress >= uint64(maxRepeatedAddresses) { + if !stringInSlice(AnoManyRepeatedEntries, pe.Anomalies) { + pe.Anomalies = append(pe.Anomalies, AnoManyRepeatedEntries) + } + } + + // if the addresses point somewhere but the difference between the highest + // and lowest address is larger than maxAddressSpread we assume a bogus + // table as the addresses should be contained within a module + if maxAddressOfData-minAddressOfData > uint64(maxAddressSpread) { + if !stringInSlice(AnoInvalidThunkAddressOfData, pe.Anomalies) { + pe.Anomalies = append(pe.Anomalies, AnoInvalidThunkAddressOfData) + } + } + + // In its original incarnation in Visual C++ 6.0, all ImgDelayDescr + // fields containing addresses used virtual addresses, rather than RVAs. + // That is, they contained actual addresses where the delayload data + // could be found. These fields are DWORDs, the size of a pointer on the x86. + // Now fast-forward to IA-64 support. All of a sudden, 4 bytes isn't + // enough to hold a complete address. At this point, Microsoft did the + // correct thing and changed the fields containing addresses to RVAs. + offset := uint32(0) + if isOldDelayImport { + oh64 := pe.NtHeader.OptionalHeader.(ImageOptionalHeader64) + newRVA := rva - uint32(oh64.ImageBase) + offset = pe.getOffsetFromRva(newRVA) + if offset == ^uint32(0) { + return []*ThunkData64{}, nil + } + } else { + offset = pe.getOffsetFromRva(rva) + if offset == ^uint32(0) { + return []*ThunkData64{}, nil + } + } + + // Read the image thunk data. + thunk := ImageThunkData64{} + err := pe.structUnpack(&thunk, offset, size) + if err != nil { + // log.Printf("Error parsing the import table. " + + // "Invalid data at RVA: 0x%x", rva) + return []*ThunkData64{}, nil + } + + if thunk == (ImageThunkData64{}) { + break + } + + // Check if the AddressOfData lies within the range of RVAs that it's + // being scanned, abort if that is the case, as it is very unlikely + // to be legitimate data. + // Seen in PE with SHA256: + // 5945bb6f0ac879ddf61b1c284f3b8d20c06b228e75ae4f571fa87f5b9512902c + if thunk.AddressOfData >= uint64(startRVA) && + thunk.AddressOfData <= uint64(rva) { + log.Printf("Error parsing the import table. "+ + "AddressOfData overlaps with THUNK_DATA for THUNK at: "+ + "RVA 0x%x", rva) + break + } + + // If the entry looks like could be an ordinal + if thunk.AddressOfData&imageOrdinalFlag64 > 0 { + // but its value is beyond 2^16, we will assume it's a + // corrupted and ignore it altogether + if thunk.AddressOfData&0x7fffffff > 0xffff { + if !stringInSlice(AnoAddressOfDataBeyondLimits, pe.Anomalies) { + pe.Anomalies = append(pe.Anomalies, AnoAddressOfDataBeyondLimits) + } + } + // and if it looks like it should be an RVA + } else { + // keep track of the RVAs seen and store them to study their + // properties. When certain non-standard features are detected + // the parsing will be aborted + _, ok := addressesOfData[thunk.AddressOfData] + if ok { + repeatedAddress++ + } else { + addressesOfData[thunk.AddressOfData] = true + } + + if thunk.AddressOfData > maxAddressOfData { + maxAddressOfData = thunk.AddressOfData + } + + if thunk.AddressOfData < minAddressOfData { + minAddressOfData = thunk.AddressOfData + } + } + + thunkTable[rva] = &thunk + thunkData := ThunkData64{ImageThunkData: thunk, Offset: rva} + retVal = append(retVal, &thunkData) + rva += size + } + return retVal, nil +} + +func (pe *File) parseImports32(importDesc interface{}, maxLen uint32) ( + []*ImportFunction, error) { + + var OriginalFirstThunk uint32 + var FirstThunk uint32 + var isOldDelayImport bool + + switch desc := importDesc.(type) { + case *ImageImportDescriptor: + OriginalFirstThunk = desc.OriginalFirstThunk + FirstThunk = desc.FirstThunk + case *ImageDelayImportDescriptor: + OriginalFirstThunk = desc.ImportNameTableRVA + FirstThunk = desc.ImportAddressTableRVA + if desc.Attributes == 0 { + isOldDelayImport = true + } + } + + // Import Lookup Table (OFT). Contains ordinals or pointers to strings. + ilt, err := pe.getImportTable32(OriginalFirstThunk, maxLen, isOldDelayImport) + if err != nil { + return []*ImportFunction{}, err + } + + // Import Address Table (FT). May have identical content to ILT if PE file is + // not bound. It will contain the address of the imported symbols once + // the binary is loaded or if it is already bound. + iat, err := pe.getImportTable32(FirstThunk, maxLen, isOldDelayImport) + if err != nil { + return []*ImportFunction{}, err + } + + // Some DLLs has IAT or ILT with nil type. + if len(iat) == 0 && len(ilt) == 0 { + return []*ImportFunction{}, ErrDamagedImportTable + } + + var table []*ThunkData32 + if len(ilt) > 0 { + table = ilt + } else if len(iat) > 0 { + table = iat + } else { + return []*ImportFunction{}, err + } + + importedFunctions := make([]*ImportFunction, 0) + numInvalid := uint32(0) + for idx := uint32(0); idx < uint32(len(table)); idx++ { + imp := ImportFunction{} + if table[idx].ImageThunkData.AddressOfData > 0 { + // If imported by ordinal, we will append the ordinal number + if table[idx].ImageThunkData.AddressOfData&imageOrdinalFlag32 > 0 { + imp.ByOrdinal = true + imp.Ordinal = table[idx].ImageThunkData.AddressOfData & uint32(0xffff) + + // Original Thunk + if uint32(len(ilt)) > idx { + imp.OriginalThunkValue = uint64(ilt[idx].ImageThunkData.AddressOfData) + imp.OriginalThunkRVA = ilt[idx].Offset + } + + // Thunk + if uint32(len(iat)) > idx { + imp.ThunkValue = uint64(iat[idx].ImageThunkData.AddressOfData) + imp.ThunkRVA = iat[idx].Offset + } + + imp.Name = "#" + strconv.Itoa(int(imp.Ordinal)) + } else { + imp.ByOrdinal = false + if isOldDelayImport { + table[idx].ImageThunkData.AddressOfData -= + pe.NtHeader.OptionalHeader.(ImageOptionalHeader32).ImageBase + } + + // Original Thunk + if uint32(len(ilt)) > idx { + imp.OriginalThunkValue = uint64(ilt[idx].ImageThunkData.AddressOfData & addressMask32) + imp.OriginalThunkRVA = ilt[idx].Offset + } + + // Thunk + if uint32(len(iat)) > idx { + imp.ThunkValue = uint64(iat[idx].ImageThunkData.AddressOfData & addressMask32) + imp.ThunkRVA = iat[idx].Offset + } + + // Thunk + hintNameTableRva := table[idx].ImageThunkData.AddressOfData & addressMask32 + off := pe.getOffsetFromRva(hintNameTableRva) + imp.Hint, err = pe.ReadUint16(off) + if err != nil { + imp.Hint = ^uint16(0) + } + imp.Name = pe.getStringAtRVA(table[idx].ImageThunkData.AddressOfData+2, + maxImportNameLength) + if !IsValidFunctionName(imp.Name) { + imp.Name = "*invalid*" + } + } + } + + // This file bfe97192e8107d52dd7b4010d12b2924 has an invalid table built + // in a way that it's parseable but contains invalid entries that lead + // pefile to take extremely long amounts of time to parse. It also leads + // to extreme memory consumption. To prevent similar cases, if invalid + // entries are found in the middle of a table the parsing will be aborted. + hasName := len(imp.Name) > 0 + if imp.Ordinal == 0 && !hasName { + if !stringInSlice(AnoImportNoNameNoOrdinal, pe.Anomalies) { + pe.Anomalies = append(pe.Anomalies, AnoImportNoNameNoOrdinal) + } + } + + // Some PEs appear to interleave valid and invalid imports. Instead of + // aborting the parsing altogether we will simply skip the invalid entries. + // Although if we see 1000 invalid entries and no legit ones, we abort. + if imp.Name == "*invalid*" { + if numInvalid > 1000 && numInvalid == idx { + return []*ImportFunction{}, errors.New( + `Too many invalid names, aborting parsing`) + } + numInvalid++ + continue + } + + importedFunctions = append(importedFunctions, &imp) + } + + return importedFunctions, nil +} + +func (pe *File) parseImports64(importDesc interface{}, maxLen uint32) ([]*ImportFunction, error) { + + var OriginalFirstThunk uint32 + var FirstThunk uint32 + var isOldDelayImport bool + + switch desc := importDesc.(type) { + case *ImageImportDescriptor: + OriginalFirstThunk = desc.OriginalFirstThunk + FirstThunk = desc.FirstThunk + case *ImageDelayImportDescriptor: + OriginalFirstThunk = desc.ImportNameTableRVA + FirstThunk = desc.ImportAddressTableRVA + if desc.Attributes == 0 { + isOldDelayImport = true + } + } + + // Import Lookup Table. Contains ordinals or pointers to strings. + ilt, err := pe.getImportTable64(OriginalFirstThunk, maxLen, isOldDelayImport) + if err != nil { + return []*ImportFunction{}, err + } + + // Import Address Table. May have identical content to ILT if PE file is + // not bound. It will contain the address of the imported symbols once + // the binary is loaded or if it is already bound. + iat, err := pe.getImportTable64(FirstThunk, maxLen, isOldDelayImport) + if err != nil { + return []*ImportFunction{}, err + } + + // Would crash if IAT or ILT had nil type + if len(iat) == 0 && len(ilt) == 0 { + return []*ImportFunction{}, ErrDamagedImportTable + } + + var table []*ThunkData64 + if len(ilt) > 0 { + table = ilt + } else if len(iat) > 0 { + table = iat + } else { + return []*ImportFunction{}, err + } + + importedFunctions := make([]*ImportFunction, 0) + numInvalid := uint32(0) + for idx := uint32(0); idx < uint32(len(table)); idx++ { + imp := ImportFunction{} + if table[idx].ImageThunkData.AddressOfData > 0 { + + // If imported by ordinal, we will append the ordinal number + if table[idx].ImageThunkData.AddressOfData&imageOrdinalFlag64 > 0 { + imp.ByOrdinal = true + imp.Ordinal = uint32(table[idx].ImageThunkData.AddressOfData) & uint32(0xffff) + + // Original Thunk + if uint32(len(ilt)) > idx { + imp.OriginalThunkValue = + ilt[idx].ImageThunkData.AddressOfData + imp.OriginalThunkRVA = ilt[idx].Offset + } + + // Thunk + if uint32(len(iat)) > idx { + imp.ThunkValue = iat[idx].ImageThunkData.AddressOfData + imp.ThunkRVA = iat[idx].Offset + } + + imp.Name = "#" + strconv.Itoa(int(imp.Ordinal)) + + } else { + + imp.ByOrdinal = false + + if isOldDelayImport { + table[idx].ImageThunkData.AddressOfData -= + pe.NtHeader.OptionalHeader.(ImageOptionalHeader64).ImageBase + } + + // Original Thunk + if uint32(len(ilt)) > idx { + imp.OriginalThunkValue = + ilt[idx].ImageThunkData.AddressOfData & addressMask64 + imp.OriginalThunkRVA = ilt[idx].Offset + } + + // Thunk + if uint32(len(iat)) > idx { + imp.ThunkValue = iat[idx].ImageThunkData.AddressOfData & addressMask64 + imp.ThunkRVA = iat[idx].Offset + } + + hintNameTableRva := table[idx].ImageThunkData.AddressOfData & addressMask64 + off := pe.getOffsetFromRva(uint32(hintNameTableRva)) + imp.Hint = binary.LittleEndian.Uint16(pe.data[off:]) + imp.Name = pe.getStringAtRVA(uint32(table[idx].ImageThunkData.AddressOfData+2), + maxImportNameLength) + if !IsValidFunctionName(imp.Name) { + imp.Name = "*invalid*" + } + } + } + + // This file bfe97192e8107d52dd7b4010d12b2924 has an invalid table built + // in a way that it's parseable but contains invalid entries that lead + // pefile to take extremely long amounts of time to parse. It also leads + // to extreme memory consumption. To prevent similar cases, if invalid + // entries are found in the middle of a table the parsing will be aborted. + hasName := len(imp.Name) > 0 + if imp.Ordinal == 0 && !hasName { + if !stringInSlice(AnoImportNoNameNoOrdinal, pe.Anomalies) { + pe.Anomalies = append(pe.Anomalies, AnoImportNoNameNoOrdinal) + } + } + // Some PEs appear to interleave valid and invalid imports. Instead of + // aborting the parsing altogether we will simply skip the invalid entries. + // Although if we see 1000 invalid entries and no legit ones, we abort. + if imp.Name == "*invalid*" { + if numInvalid > 1000 && numInvalid == idx { + return []*ImportFunction{}, errors.New( + `Too many invalid names, aborting parsing`) + } + numInvalid++ + continue + } + + importedFunctions = append(importedFunctions, &imp) + } + + return importedFunctions, nil +} + +// GetImportEntryInfoByRVA return an import function + index of the entry given +// an RVA. +func (pe *File) GetImportEntryInfoByRVA(rva uint32) (Import, int) { + for _, imp := range pe.Imports { + for i, entry := range imp.Functions { + if entry.ThunkRVA == rva { + return imp, i + } + } + } + + return Import{}, 0 +} + +// md5hash hashes using md5 algorithm. +func md5hash(text string) string { + h := md5.New() + h.Write([]byte(text)) + return hex.EncodeToString(h.Sum(nil)) +} + +// ImpHash calculates the import hash. +// Algorithm: +// ========== +// Resolving ordinals to function names when they appear +// Converting both DLL names and function names to all lowercase +// Removing the file extensions from imported module names +// Building and storing the lowercased string . in an ordered list +// Generating the MD5 hash of the ordered list +func (pe *File) ImpHash() (string, error) { + if len(pe.Imports) == 0 { + return "", errors.New("No imports found") + } + + extensions := []string{"ocx", "sys", "dll"} + var impStrs []string + + for _, imp := range pe.Imports { + var libname string + parts := strings.Split(imp.Name, ".") + if len(parts) > 0 && stringInSlice(parts[1], extensions) { + libname = parts[0] + } + + libname = strings.ToLower(libname) + + for _, function := range imp.Functions { + var funcname string + if function.ByOrdinal { + funcname = OrdLookup(libname, uint64(function.Ordinal), true) + } else { + funcname = function.Name + } + + if funcname == "" { + continue + } + + impStr := fmt.Sprintf("%s.%s", libname, strings.ToLower(funcname)) + impStrs = append(impStrs, impStr) + } + } + + hash := md5hash(strings.Join(impStrs, ",")) + return hash, nil +} diff --git a/loadconfig.go b/loadconfig.go new file mode 100644 index 0000000..2c17f59 --- /dev/null +++ b/loadconfig.go @@ -0,0 +1,2295 @@ +// Copyright 2021 Saferwall. All rights reserved. +// Use of this source code is governed by Apache v2 license +// license that can be found in the LICENSE file. + +package pe + +import ( + "encoding/binary" + "fmt" + "reflect" +) + +const ( + + // The GuardFlags field contains a combination of one or more of the + // following flags and subfields: + + // ImageGuardCfInstrumented indicates that the module performs control flow + // integrity checks using system-supplied support. + ImageGuardCfInstrumented = 0x00000100 + + // ImageGuardCfWInstrumented indicates that the module performs control + // flow and write integrity checks. + ImageGuardCfWInstrumented = 0x00000200 + + // ImageGuardCfFunctionTablePresent indicates that the module contains + // valid control flow target metadata. + ImageGuardCfFunctionTablePresent = 0x00000400 + + // ImageGuardSecurityCookieUnused indicates that the module does not make + // use of the /GS security cookie. + ImageGuardSecurityCookieUnused = 0x00000800 + + // ImageGuardProtectDelayloadIAT indicates that the module supports read + // only delay load IAT. + ImageGuardProtectDelayloadIAT = 0x00001000 + + // ImageGuardDelayloadIATInItsOwnSection indicates that the Delayload + // import table in its own .didat section (with nothing else in it) that + // can be freely reprotected. + ImageGuardDelayloadIATInItsOwnSection = 0x00002000 + + // ImageGuardCfExportSuppressionInfoPresent indicates that the module + // contains suppressed export information. This also infers that the + // address taken IAT table is also present in the load config. + ImageGuardCfExportSuppressionInfoPresent = 0x00004000 + + // ImageGuardCfEnableExportSuppression indicates that the module enables + // suppression of exports. + ImageGuardCfEnableExportSuppression = 0x00008000 + + // ImageGuardCfLongjumpTablePresent indicates that the module contains + // longjmp target information. + ImageGuardCfLongjumpTablePresent = 0x00010000 + + // ImageGuardCfFnctionTableSizeMask indicates that the mask for the + // subfield that contains the stride of Control Flow Guard function table + // entries (that is, the additional count of bytes per table entry). + ImageGuardCfFnctionTableSizeMask = 0xF0000000 + + // ImageGuardCfFnctionTableSizeShift indicates the shift to right-justify + // Guard CF function table stride. + ImageGuardCfFnctionTableSizeShift = 28 + + // ImageGuardFlagFIDSupressed indicates that the call target is explicitly + // suppressed (do not treat it as valid for purposes of CFG) + ImageGuardFlagFIDSupressed = 0x1 + + // ImageGuardFlagExportSupressed indicates that the call target is export + // suppressed. See Export suppression for more details + ImageGuardFlagExportSupressed = 0x2 + + ImageDynamicRelocationGuardRfPrologue = 0x00000001 + ImageDynamicRelocationGuardREpilogue = 0x00000002 + ImageEnclaveLongIdLength = 32 + ImageEnclaveShortIdLength = 16 + + // ImageEnclaveImportMatchNone indicates that none of the identifiers of the image need to match the value in the import record. + ImageEnclaveImportMatchNone = 0x00000000 + + // ImageEnclaveImportMatchUniqueId indicates that the value of the enclave unique identifier of the image must match the value in the import record. Otherwise, loading of the image fails. + ImageEnclaveImportMatchUniqueId = 0x00000001 + + // ImageEnclaveImportMatchAuthorId indicates that the value of the enclave author identifier of the image must match the value in the import record. Otherwise, loading of the image fails. If this flag is set and the import record indicates an author identifier of all zeros, the imported image must be part of the Windows installation. + ImageEnclaveImportMatchAuthorId = 0x00000002 + + // ImageEnclaveImportMatchFamilyId indicates that the value of the enclave family identifier of the image must match the value in the import record. Otherwise, loading of the image fails. + ImageEnclaveImportMatchFamilyId = 0x00000003 + + // ImageEnclaveImportMatchImageId indicates that the value of the enclave image identifier must match the value in the import record. Otherwise, loading of the image fails. + ImageEnclaveImportMatchImageId = 0x00000004 +) + +// https://www.virtualbox.org/svn/vbox/trunk/include/iprt/formats/pecoff.h + +// ImageLoadConfigDirectory32v1 size is 0x40. +type ImageLoadConfigDirectory32v1 struct { + Size uint32 + TimeDateStamp uint32 + MajorVersion uint16 + MinorVersion uint16 + GlobalFlagsClear uint32 + GlobalFlagsSet uint32 + CriticalSectionDefaultTimeout uint32 + DeCommitFreeBlockThreshold uint32 + DeCommitTotalFreeThreshold uint32 + LockPrefixTable uint32 + MaximumAllocationSize uint32 + VirtualMemoryThreshold uint32 + ProcessHeapFlags uint32 + ProcessAffinityMask uint32 + CSDVersion uint16 + DependentLoadFlags uint16 + EditList uint32 + SecurityCookie uint32 +} + +// ImageLoadConfigDirectory32v2 size is 0x48. +type ImageLoadConfigDirectory32v2 struct { + Size uint32 + TimeDateStamp uint32 + MajorVersion uint16 + MinorVersion uint16 + GlobalFlagsClear uint32 + GlobalFlagsSet uint32 + CriticalSectionDefaultTimeout uint32 + DeCommitFreeBlockThreshold uint32 + DeCommitTotalFreeThreshold uint32 + LockPrefixTable uint32 + MaximumAllocationSize uint32 + VirtualMemoryThreshold uint32 + ProcessHeapFlags uint32 + ProcessAffinityMask uint32 + CSDVersion uint16 + DependentLoadFlags uint16 + EditList uint32 + SecurityCookie uint32 + SEHandlerTable uint32 + SEHandlerCount uint32 +} + +// ImageLoadConfigDirectory32v3 size is 0x5C. +type ImageLoadConfigDirectory32v3 struct { + Size uint32 + TimeDateStamp uint32 + MajorVersion uint16 + MinorVersion uint16 + GlobalFlagsClear uint32 + GlobalFlagsSet uint32 + CriticalSectionDefaultTimeout uint32 + DeCommitFreeBlockThreshold uint32 + DeCommitTotalFreeThreshold uint32 + LockPrefixTable uint32 + MaximumAllocationSize uint32 + VirtualMemoryThreshold uint32 + ProcessHeapFlags uint32 + ProcessAffinityMask uint32 + CSDVersion uint16 + DependentLoadFlags uint16 + EditList uint32 + SecurityCookie uint32 + SEHandlerTable uint32 + SEHandlerCount uint32 + GuardCFCheckFunctionPointer uint32 + GuardCFDispatchFunctionPointer uint32 + GuardCFFunctionTable uint32 + GuardCFFunctionCount uint32 + GuardFlags uint32 +} + +// ImageLoadConfigDirectory32v4 size is 0x6c +// since Windows 10 (preview 9879) +type ImageLoadConfigDirectory32v4 struct { + Size uint32 + TimeDateStamp uint32 + MajorVersion uint16 + MinorVersion uint16 + GlobalFlagsClear uint32 + GlobalFlagsSet uint32 + CriticalSectionDefaultTimeout uint32 + DeCommitFreeBlockThreshold uint32 + DeCommitTotalFreeThreshold uint32 + LockPrefixTable uint32 + MaximumAllocationSize uint32 + VirtualMemoryThreshold uint32 + ProcessHeapFlags uint32 + ProcessAffinityMask uint32 + CSDVersion uint16 + DependentLoadFlags uint16 + EditList uint32 + SecurityCookie uint32 + SEHandlerTable uint32 + SEHandlerCount uint32 + GuardCFCheckFunctionPointer uint32 + GuardCFDispatchFunctionPointer uint32 + GuardCFFunctionTable uint32 + GuardCFFunctionCount uint32 + GuardFlags uint32 + CodeIntegrity ImageLoadConfigCodeIntegrity +} + +// ImageLoadConfigDirectory32v5 size is 0x78 +// since Windows 10 build 14286 (or maybe earlier). +type ImageLoadConfigDirectory32v5 struct { + Size uint32 + TimeDateStamp uint32 + MajorVersion uint16 + MinorVersion uint16 + GlobalFlagsClear uint32 + GlobalFlagsSet uint32 + CriticalSectionDefaultTimeout uint32 + DeCommitFreeBlockThreshold uint32 + DeCommitTotalFreeThreshold uint32 + LockPrefixTable uint32 + MaximumAllocationSize uint32 + VirtualMemoryThreshold uint32 + ProcessHeapFlags uint32 + ProcessAffinityMask uint32 + CSDVersion uint16 + DependentLoadFlags uint16 + EditList uint32 + SecurityCookie uint32 + SEHandlerTable uint32 + SEHandlerCount uint32 + GuardCFCheckFunctionPointer uint32 + GuardCFDispatchFunctionPointer uint32 + GuardCFFunctionTable uint32 + GuardCFFunctionCount uint32 + GuardFlags uint32 + CodeIntegrity ImageLoadConfigCodeIntegrity + GuardAddressTakenIatEntryTable uint32 + GuardAddressTakenIatEntryCount uint32 + GuardLongJumpTargetTable uint32 + GuardLongJumpTargetCount uint32 +} + +// ImageLoadConfigDirectory32v6 size is 0x80 +// since Windows 10 build 14383 (or maybe earlier). +type ImageLoadConfigDirectory32v6 struct { + Size uint32 + TimeDateStamp uint32 + MajorVersion uint16 + MinorVersion uint16 + GlobalFlagsClear uint32 + GlobalFlagsSet uint32 + CriticalSectionDefaultTimeout uint32 + DeCommitFreeBlockThreshold uint32 + DeCommitTotalFreeThreshold uint32 + LockPrefixTable uint32 + MaximumAllocationSize uint32 + VirtualMemoryThreshold uint32 + ProcessHeapFlags uint32 + ProcessAffinityMask uint32 + CSDVersion uint16 + DependentLoadFlags uint16 + EditList uint32 + SecurityCookie uint32 + SEHandlerTable uint32 + SEHandlerCount uint32 + GuardCFCheckFunctionPointer uint32 + GuardCFDispatchFunctionPointer uint32 + GuardCFFunctionTable uint32 + GuardCFFunctionCount uint32 + GuardFlags uint32 + CodeIntegrity ImageLoadConfigCodeIntegrity + GuardAddressTakenIatEntryTable uint32 + GuardAddressTakenIatEntryCount uint32 + GuardLongJumpTargetTable uint32 + GuardLongJumpTargetCount uint32 + DynamicValueRelocTable uint32 + HybridMetadataPointer uint32 +} + +// ImageLoadConfigDirectory32v7 size is 0x90 +// since Windows 10 build 14901 (or maybe earlier) +type ImageLoadConfigDirectory32v7 struct { + Size uint32 + TimeDateStamp uint32 + MajorVersion uint16 + MinorVersion uint16 + GlobalFlagsClear uint32 + GlobalFlagsSet uint32 + CriticalSectionDefaultTimeout uint32 + DeCommitFreeBlockThreshold uint32 + DeCommitTotalFreeThreshold uint32 + LockPrefixTable uint32 + MaximumAllocationSize uint32 + VirtualMemoryThreshold uint32 + ProcessHeapFlags uint32 + ProcessAffinityMask uint32 + CSDVersion uint16 + DependentLoadFlags uint16 + EditList uint32 + SecurityCookie uint32 + SEHandlerTable uint32 + SEHandlerCount uint32 + GuardCFCheckFunctionPointer uint32 + GuardCFDispatchFunctionPointer uint32 + GuardCFFunctionTable uint32 + GuardCFFunctionCount uint32 + GuardFlags uint32 + CodeIntegrity ImageLoadConfigCodeIntegrity + GuardAddressTakenIatEntryTable uint32 + GuardAddressTakenIatEntryCount uint32 + GuardLongJumpTargetTable uint32 + GuardLongJumpTargetCount uint32 + DynamicValueRelocTable uint32 + CHPEMetadataPointer uint32 + GuardRFFailureRoutine uint32 + GuardRFFailureRoutineFunctionPointer uint32 + DynamicValueRelocTableOffset uint32 + DynamicValueRelocTableSection uint16 + Reserved2 uint16 +} + +// ImageLoadConfigDirectory32v8 size is 0x98 +// since Windows 10 build 15002 (or maybe earlier). +type ImageLoadConfigDirectory32v8 struct { + Size uint32 + TimeDateStamp uint32 + MajorVersion uint16 + MinorVersion uint16 + GlobalFlagsClear uint32 + GlobalFlagsSet uint32 + CriticalSectionDefaultTimeout uint32 + DeCommitFreeBlockThreshold uint32 + DeCommitTotalFreeThreshold uint32 + LockPrefixTable uint32 + MaximumAllocationSize uint32 + VirtualMemoryThreshold uint32 + ProcessHeapFlags uint32 + ProcessAffinityMask uint32 + CSDVersion uint16 + DependentLoadFlags uint16 + EditList uint32 + SecurityCookie uint32 + SEHandlerTable uint32 + SEHandlerCount uint32 + GuardCFCheckFunctionPointer uint32 + GuardCFDispatchFunctionPointer uint32 + GuardCFFunctionTable uint32 + GuardCFFunctionCount uint32 + GuardFlags uint32 + CodeIntegrity ImageLoadConfigCodeIntegrity + GuardAddressTakenIatEntryTable uint32 + GuardAddressTakenIatEntryCount uint32 + GuardLongJumpTargetTable uint32 + GuardLongJumpTargetCount uint32 + DynamicValueRelocTable uint32 + CHPEMetadataPointer uint32 + GuardRFFailureRoutine uint32 + GuardRFFailureRoutineFunctionPointer uint32 + DynamicValueRelocTableOffset uint32 + DynamicValueRelocTableSection uint16 + Reserved2 uint16 + GuardRFVerifyStackPointerFunctionPointer uint32 + HotPatchTableOffset uint32 +} + +// ImageLoadConfigDirectory32v9 size is 0xA0 +// since Windows 10 build 16237 (or maybe earlier). +type ImageLoadConfigDirectory32v9 struct { + Size uint32 + TimeDateStamp uint32 + MajorVersion uint16 + MinorVersion uint16 + GlobalFlagsClear uint32 + GlobalFlagsSet uint32 + CriticalSectionDefaultTimeout uint32 + DeCommitFreeBlockThreshold uint32 + DeCommitTotalFreeThreshold uint32 + LockPrefixTable uint32 + MaximumAllocationSize uint32 + VirtualMemoryThreshold uint32 + ProcessHeapFlags uint32 + ProcessAffinityMask uint32 + CSDVersion uint16 + DependentLoadFlags uint16 + EditList uint32 + SecurityCookie uint32 + SEHandlerTable uint32 + SEHandlerCount uint32 + GuardCFCheckFunctionPointer uint32 + GuardCFDispatchFunctionPointer uint32 + GuardCFFunctionTable uint32 + GuardCFFunctionCount uint32 + GuardFlags uint32 + CodeIntegrity ImageLoadConfigCodeIntegrity + GuardAddressTakenIatEntryTable uint32 + GuardAddressTakenIatEntryCount uint32 + GuardLongJumpTargetTable uint32 + GuardLongJumpTargetCount uint32 + DynamicValueRelocTable uint32 + CHPEMetadataPointer uint32 + GuardRFFailureRoutine uint32 + GuardRFFailureRoutineFunctionPointer uint32 + DynamicValueRelocTableOffset uint32 + DynamicValueRelocTableSection uint16 + Reserved2 uint16 + GuardRFVerifyStackPointerFunctionPointer uint32 + HotPatchTableOffset uint32 + Reserved3 uint32 + EnclaveConfigurationPointer uint32 +} + +// ImageLoadConfigDirectory32v10 size is 0xA4 +// since Windows 10 build 18362 (or maybe earlier). +type ImageLoadConfigDirectory32v10 struct { + Size uint32 + TimeDateStamp uint32 + MajorVersion uint16 + MinorVersion uint16 + GlobalFlagsClear uint32 + GlobalFlagsSet uint32 + CriticalSectionDefaultTimeout uint32 + DeCommitFreeBlockThreshold uint32 + DeCommitTotalFreeThreshold uint32 + LockPrefixTable uint32 + MaximumAllocationSize uint32 + VirtualMemoryThreshold uint32 + ProcessHeapFlags uint32 + ProcessAffinityMask uint32 + CSDVersion uint16 + DependentLoadFlags uint16 + EditList uint32 + SecurityCookie uint32 + SEHandlerTable uint32 + SEHandlerCount uint32 + GuardCFCheckFunctionPointer uint32 + GuardCFDispatchFunctionPointer uint32 + GuardCFFunctionTable uint32 + GuardCFFunctionCount uint32 + GuardFlags uint32 + CodeIntegrity ImageLoadConfigCodeIntegrity + GuardAddressTakenIatEntryTable uint32 + GuardAddressTakenIatEntryCount uint32 + GuardLongJumpTargetTable uint32 + GuardLongJumpTargetCount uint32 + DynamicValueRelocTable uint32 + CHPEMetadataPointer uint32 + GuardRFFailureRoutine uint32 + GuardRFFailureRoutineFunctionPointer uint32 + DynamicValueRelocTableOffset uint32 + DynamicValueRelocTableSection uint16 + Reserved2 uint16 + GuardRFVerifyStackPointerFunctionPointer uint32 + HotPatchTableOffset uint32 + Reserved3 uint32 + EnclaveConfigurationPointer uint32 + VolatileMetadataPointer uint32 +} + +// ImageLoadConfigDirectory32v11 size is 0xAC +// since Windows 10 build 19564 (or maybe earlier). +type ImageLoadConfigDirectory32v11 struct { + Size uint32 + TimeDateStamp uint32 + MajorVersion uint16 + MinorVersion uint16 + GlobalFlagsClear uint32 + GlobalFlagsSet uint32 + CriticalSectionDefaultTimeout uint32 + DeCommitFreeBlockThreshold uint32 + DeCommitTotalFreeThreshold uint32 + LockPrefixTable uint32 + MaximumAllocationSize uint32 + VirtualMemoryThreshold uint32 + ProcessHeapFlags uint32 + ProcessAffinityMask uint32 + CSDVersion uint16 + DependentLoadFlags uint16 + EditList uint32 + SecurityCookie uint32 + SEHandlerTable uint32 + SEHandlerCount uint32 + GuardCFCheckFunctionPointer uint32 + GuardCFDispatchFunctionPointer uint32 + GuardCFFunctionTable uint32 + GuardCFFunctionCount uint32 + GuardFlags uint32 + CodeIntegrity ImageLoadConfigCodeIntegrity + GuardAddressTakenIatEntryTable uint32 + GuardAddressTakenIatEntryCount uint32 + GuardLongJumpTargetTable uint32 + GuardLongJumpTargetCount uint32 + DynamicValueRelocTable uint32 + CHPEMetadataPointer uint32 + GuardRFFailureRoutine uint32 + GuardRFFailureRoutineFunctionPointer uint32 + DynamicValueRelocTableOffset uint32 + DynamicValueRelocTableSection uint16 + Reserved2 uint16 + GuardRFVerifyStackPointerFunctionPointer uint32 + HotPatchTableOffset uint32 + Reserved3 uint32 + EnclaveConfigurationPointer uint32 + VolatileMetadataPointer uint32 + GuardEHContinuationTable uint32 + GuardEHContinuationCount uint32 +} + +// ImageLoadConfigDirectory32v12 size is 0xB8 +// since Visual C++ 2019 / RS5_IMAGE_LOAD_CONFIG_DIRECTORY32. +type ImageLoadConfigDirectory32v12 struct { + Size uint32 + TimeDateStamp uint32 + MajorVersion uint16 + MinorVersion uint16 + GlobalFlagsClear uint32 + GlobalFlagsSet uint32 + CriticalSectionDefaultTimeout uint32 + DeCommitFreeBlockThreshold uint32 + DeCommitTotalFreeThreshold uint32 + LockPrefixTable uint32 + MaximumAllocationSize uint32 + VirtualMemoryThreshold uint32 + ProcessHeapFlags uint32 + ProcessAffinityMask uint32 + CSDVersion uint16 + DependentLoadFlags uint16 + EditList uint32 + SecurityCookie uint32 + SEHandlerTable uint32 + SEHandlerCount uint32 + GuardCFCheckFunctionPointer uint32 + GuardCFDispatchFunctionPointer uint32 + GuardCFFunctionTable uint32 + GuardCFFunctionCount uint32 + GuardFlags uint32 + CodeIntegrity ImageLoadConfigCodeIntegrity + GuardAddressTakenIatEntryTable uint32 + GuardAddressTakenIatEntryCount uint32 + GuardLongJumpTargetTable uint32 + GuardLongJumpTargetCount uint32 + DynamicValueRelocTable uint32 + CHPEMetadataPointer uint32 + GuardRFFailureRoutine uint32 + GuardRFFailureRoutineFunctionPointer uint32 + DynamicValueRelocTableOffset uint32 + DynamicValueRelocTableSection uint16 + Reserved2 uint16 + GuardRFVerifyStackPointerFunctionPointer uint32 + HotPatchTableOffset uint32 + Reserved3 uint32 + EnclaveConfigurationPointer uint32 + VolatileMetadataPointer uint32 + GuardEHContinuationTable uint32 + GuardEHContinuationCount uint32 + GuardXFGCheckFunctionPointer uint32 + GuardXFGDispatchFunctionPointer uint32 + GuardXFGTableDispatchFunctionPointer uint32 +} + +// ImageLoadConfigDirectory32 Contains the load configuration data of an image +// for x86 binaries. +type ImageLoadConfigDirectory32 struct { + // The actual size of the structure inclusive. May differ from the size + // given in the data directory for Windows XP and earlier compatibility. + Size uint32 + + // Date and time stamp value. + TimeDateStamp uint32 + + // Major version number. + MajorVersion uint16 + + // Minor version number. + MinorVersion uint16 + + // The global loader flags to clear for this process as the loader starts + // the process. + GlobalFlagsClear uint32 + + // The global loader flags to set for this process as the loader starts the + // process. + GlobalFlagsSet uint32 + + // The default timeout value to use for this process's critical sections + // that are abandoned. + CriticalSectionDefaultTimeout uint32 + + // Memory that must be freed before it is returned to the system, in bytes. + DeCommitFreeBlockThreshold uint32 + + // Total amount of free memory, in bytes. + DeCommitTotalFreeThreshold uint32 + + // [x86 only] The VA of a list of addresses where the LOCK prefix is used so + // that they can be replaced with NOP on single processor machines. + LockPrefixTable uint32 + + // Maximum allocation size, in bytes. + MaximumAllocationSize uint32 + + // Maximum virtual memory size, in bytes. + VirtualMemoryThreshold uint32 + + // Process heap flags that correspond to the first argument of the HeapCreate + // function. These flags apply to the process heap that is created during + // process startup. + ProcessHeapFlags uint32 + + // Setting this field to a non-zero value is equivalent to calling + // SetProcessAffinityMask with this value during process startup (.exe only) + ProcessAffinityMask uint32 + + // The service pack version identifier. + CSDVersion uint16 + + // Must be zero. + DependentLoadFlags uint16 + + // Reserved for use by the system. + EditList uint32 + + // A pointer to a cookie that is used by Visual C++ or GS implementation. + SecurityCookie uint32 + + // [x86 only] The VA of the sorted table of RVAs of each valid, unique SE + // handler in the image. + SEHandlerTable uint32 + + // [x86 only] The count of unique handlers in the table. + SEHandlerCount uint32 + + // The VA where Control Flow Guard check-function pointer is stored. + GuardCFCheckFunctionPointer uint32 + + // The VA where Control Flow Guard dispatch-function pointer is stored. + GuardCFDispatchFunctionPointer uint32 + + // The VA of the sorted table of RVAs of each Control Flow Guard function in + // the image. + GuardCFFunctionTable uint32 + + // The count of unique RVAs in the above table. + GuardCFFunctionCount uint32 + + // Control Flow Guard related flags. + GuardFlags uint32 + + // Code integrity information. + CodeIntegrity ImageLoadConfigCodeIntegrity + + // The VA where Control Flow Guard address taken IAT table is stored. + GuardAddressTakenIatEntryTable uint32 + + // The count of unique RVAs in the above table. + GuardAddressTakenIatEntryCount uint32 + + // The VA where Control Flow Guard long jump target table is stored. + GuardLongJumpTargetTable uint32 + + // The count of unique RVAs in the above table. + GuardLongJumpTargetCount uint32 + + DynamicValueRelocTable uint32 + + // Not sure when this was renamed from HybridMetadataPointer. + CHPEMetadataPointer uint32 + + GuardRFFailureRoutine uint32 + GuardRFFailureRoutineFunctionPointer uint32 + DynamicValueRelocTableOffset uint32 + DynamicValueRelocTableSection uint16 + Reserved2 uint16 + GuardRFVerifyStackPointerFunctionPointer uint32 + HotPatchTableOffset uint32 + Reserved3 uint32 + EnclaveConfigurationPointer uint32 + VolatileMetadataPointer uint32 + GuardEHContinuationTable uint32 + GuardEHContinuationCount uint32 + GuardXFGCheckFunctionPointer uint32 + GuardXFGDispatchFunctionPointer uint32 + GuardXFGTableDispatchFunctionPointer uint32 +} + +// ImageLoadConfigDirectory64v2 is the first structure for x64. +// No _IMAGE_LOAD_CONFIG_DIRECTORY64_V1 exists +type ImageLoadConfigDirectory64v2 struct { + Size uint32 + TimeDateStamp uint32 + MajorVersion uint16 + MinorVersion uint16 + GlobalFlagsClear uint32 + GlobalFlagsSet uint32 + CriticalSectionDefaultTimeout uint32 + DeCommitFreeBlockThreshold uint64 + DeCommitTotalFreeThreshold uint64 + LockPrefixTable uint64 + MaximumAllocationSize uint64 + VirtualMemoryThreshold uint64 + ProcessAffinityMask uint64 + ProcessHeapFlags uint32 + CSDVersion uint16 + DependentLoadFlags uint16 + EditList uint64 + SecurityCookie uint64 + SEHandlerTable uint64 + SEHandlerCount uint64 +} + +// ImageLoadConfigDirectory64v3 added #pragma pack(4). +type ImageLoadConfigDirectory64v3 struct { + Size uint32 + TimeDateStamp uint32 + MajorVersion uint16 + MinorVersion uint16 + GlobalFlagsClear uint32 + GlobalFlagsSet uint32 + CriticalSectionDefaultTimeout uint32 + DeCommitFreeBlockThreshold uint64 + DeCommitTotalFreeThreshold uint64 + LockPrefixTable uint64 + MaximumAllocationSize uint64 + VirtualMemoryThreshold uint64 + ProcessAffinityMask uint64 + ProcessHeapFlags uint32 + CSDVersion uint16 + DependentLoadFlags uint16 + EditList uint64 + SecurityCookie uint64 + SEHandlerTable uint64 + SEHandlerCount uint64 + GuardCFCheckFunctionPointer uint64 + GuardCFDispatchFunctionPointer uint64 + GuardCFFunctionTable uint64 + GuardCFFunctionCount uint64 + GuardFlags uint32 +} + +// ImageLoadConfigDirectory64v4 for binaries compiled +// since Windows 10 build 9879 (or maybe earlier). +type ImageLoadConfigDirectory64v4 struct { + Size uint32 + TimeDateStamp uint32 + MajorVersion uint16 + MinorVersion uint16 + GlobalFlagsClear uint32 + GlobalFlagsSet uint32 + CriticalSectionDefaultTimeout uint32 + DeCommitFreeBlockThreshold uint64 + DeCommitTotalFreeThreshold uint64 + LockPrefixTable uint64 + MaximumAllocationSize uint64 + VirtualMemoryThreshold uint64 + ProcessAffinityMask uint64 + ProcessHeapFlags uint32 + CSDVersion uint16 + DependentLoadFlags uint16 + EditList uint64 + SecurityCookie uint64 + SEHandlerTable uint64 + SEHandlerCount uint64 + GuardCFCheckFunctionPointer uint64 + GuardCFDispatchFunctionPointer uint64 + GuardCFFunctionTable uint64 + GuardCFFunctionCount uint64 + GuardFlags uint32 + CodeIntegrity ImageLoadConfigCodeIntegrity +} + +// ImageLoadConfigDirectory64v5 for binaries compiled +// since Windows 10 build 14286 (or maybe earlier). +type ImageLoadConfigDirectory64v5 struct { + Size uint32 + TimeDateStamp uint32 + MajorVersion uint16 + MinorVersion uint16 + GlobalFlagsClear uint32 + GlobalFlagsSet uint32 + CriticalSectionDefaultTimeout uint32 + DeCommitFreeBlockThreshold uint64 + DeCommitTotalFreeThreshold uint64 + LockPrefixTable uint64 + MaximumAllocationSize uint64 + VirtualMemoryThreshold uint64 + ProcessAffinityMask uint64 + ProcessHeapFlags uint32 + CSDVersion uint16 + DependentLoadFlags uint16 + EditList uint64 + SecurityCookie uint64 + SEHandlerTable uint64 + SEHandlerCount uint64 + GuardCFCheckFunctionPointer uint64 + GuardCFDispatchFunctionPointer uint64 + GuardCFFunctionTable uint64 + GuardCFFunctionCount uint64 + GuardFlags uint32 + CodeIntegrity ImageLoadConfigCodeIntegrity + GuardAddressTakenIatEntryTable uint64 + GuardAddressTakenIatEntryCount uint64 + GuardLongJumpTargetTable uint64 + GuardLongJumpTargetCount uint64 +} + +// ImageLoadConfigDirectory64v6 for binaries compiled +// since Windows 10 build 14393 (or maybe earlier). +type ImageLoadConfigDirectory64v6 struct { + Size uint32 + TimeDateStamp uint32 + MajorVersion uint16 + MinorVersion uint16 + GlobalFlagsClear uint32 + GlobalFlagsSet uint32 + CriticalSectionDefaultTimeout uint32 + DeCommitFreeBlockThreshold uint64 + DeCommitTotalFreeThreshold uint64 + LockPrefixTable uint64 + MaximumAllocationSize uint64 + VirtualMemoryThreshold uint64 + ProcessAffinityMask uint64 + ProcessHeapFlags uint32 + CSDVersion uint16 + DependentLoadFlags uint16 + EditList uint64 + SecurityCookie uint64 + SEHandlerTable uint64 + SEHandlerCount uint64 + GuardCFCheckFunctionPointer uint64 + GuardCFDispatchFunctionPointer uint64 + GuardCFFunctionTable uint64 + GuardCFFunctionCount uint64 + GuardFlags uint32 + CodeIntegrity ImageLoadConfigCodeIntegrity + GuardAddressTakenIatEntryTable uint64 + GuardAddressTakenIatEntryCount uint64 + GuardLongJumpTargetTable uint64 + GuardLongJumpTargetCount uint64 + DynamicValueRelocTable uint64 + HybridMetadataPointer uint64 +} + +// ImageLoadConfigDirectory64v7 for binaries compiled +// since Windows 10 build 14901 (or maybe earlier). +type ImageLoadConfigDirectory64v7 struct { + Size uint32 + TimeDateStamp uint32 + MajorVersion uint16 + MinorVersion uint16 + GlobalFlagsClear uint32 + GlobalFlagsSet uint32 + CriticalSectionDefaultTimeout uint32 + DeCommitFreeBlockThreshold uint64 + DeCommitTotalFreeThreshold uint64 + LockPrefixTable uint64 + MaximumAllocationSize uint64 + VirtualMemoryThreshold uint64 + ProcessAffinityMask uint64 + ProcessHeapFlags uint32 + CSDVersion uint16 + DependentLoadFlags uint16 + EditList uint64 + SecurityCookie uint64 + SEHandlerTable uint64 + SEHandlerCount uint64 + GuardCFCheckFunctionPointer uint64 + GuardCFDispatchFunctionPointer uint64 + GuardCFFunctionTable uint64 + GuardCFFunctionCount uint64 + GuardFlags uint32 + CodeIntegrity ImageLoadConfigCodeIntegrity + GuardAddressTakenIatEntryTable uint64 + GuardAddressTakenIatEntryCount uint64 + GuardLongJumpTargetTable uint64 + GuardLongJumpTargetCount uint64 + DynamicValueRelocTable uint64 + CHPEMetadataPointer uint64 + GuardRFFailureRoutine uint64 + GuardRFFailureRoutineFunctionPointer uint64 + DynamicValueRelocTableOffset uint32 + DynamicValueRelocTableSection uint16 + Reserved2 uint16 +} + +// ImageLoadConfigDirectory64v8 for binaries compiled +// since Windows 10 build 15002 (or maybe earlier). +// #pragma pack(4) available here. +type ImageLoadConfigDirectory64v8 struct { + Size uint32 + TimeDateStamp uint32 + MajorVersion uint16 + MinorVersion uint16 + GlobalFlagsClear uint32 + GlobalFlagsSet uint32 + CriticalSectionDefaultTimeout uint32 + DeCommitFreeBlockThreshold uint64 + DeCommitTotalFreeThreshold uint64 + LockPrefixTable uint64 + MaximumAllocationSize uint64 + VirtualMemoryThreshold uint64 + ProcessAffinityMask uint64 + ProcessHeapFlags uint32 + CSDVersion uint16 + DependentLoadFlags uint16 + EditList uint64 + SecurityCookie uint64 + SEHandlerTable uint64 + SEHandlerCount uint64 + GuardCFCheckFunctionPointer uint64 + GuardCFDispatchFunctionPointer uint64 + GuardCFFunctionTable uint64 + GuardCFFunctionCount uint64 + GuardFlags uint32 + CodeIntegrity ImageLoadConfigCodeIntegrity + GuardAddressTakenIatEntryTable uint64 + GuardAddressTakenIatEntryCount uint64 + GuardLongJumpTargetTable uint64 + GuardLongJumpTargetCount uint64 + DynamicValueRelocTable uint64 + CHPEMetadataPointer uint64 + GuardRFFailureRoutine uint64 + GuardRFFailureRoutineFunctionPointer uint64 + DynamicValueRelocTableOffset uint32 + DynamicValueRelocTableSection uint16 + Reserved2 uint16 + GuardRFVerifyStackPointerFunctionPointer uint64 + HotPatchTableOffset uint32 +} + +// ImageLoadConfigDirectory64v9 for binaries compiled +// since Windows 10 build 15002 (or maybe earlier). +// #pragma pack(4) was taken. +type ImageLoadConfigDirectory64v9 struct { + Size uint32 + TimeDateStamp uint32 + MajorVersion uint16 + MinorVersion uint16 + GlobalFlagsClear uint32 + GlobalFlagsSet uint32 + CriticalSectionDefaultTimeout uint32 + DeCommitFreeBlockThreshold uint64 + DeCommitTotalFreeThreshold uint64 + LockPrefixTable uint64 + MaximumAllocationSize uint64 + VirtualMemoryThreshold uint64 + ProcessAffinityMask uint64 + ProcessHeapFlags uint32 + CSDVersion uint16 + DependentLoadFlags uint16 + EditList uint64 + SecurityCookie uint64 + SEHandlerTable uint64 + SEHandlerCount uint64 + GuardCFCheckFunctionPointer uint64 + GuardCFDispatchFunctionPointer uint64 + GuardCFFunctionTable uint64 + GuardCFFunctionCount uint64 + GuardFlags uint32 + CodeIntegrity ImageLoadConfigCodeIntegrity + GuardAddressTakenIatEntryTable uint64 + GuardAddressTakenIatEntryCount uint64 + GuardLongJumpTargetTable uint64 + GuardLongJumpTargetCount uint64 + DynamicValueRelocTable uint64 + CHPEMetadataPointer uint64 + GuardRFFailureRoutine uint64 + GuardRFFailureRoutineFunctionPointer uint64 + DynamicValueRelocTableOffset uint32 + DynamicValueRelocTableSection uint16 + Reserved2 uint16 + GuardRFVerifyStackPointerFunctionPointer uint64 + HotPatchTableOffset uint32 + Reserved3 uint32 + EnclaveConfigurationPointer uint64 +} + +// ImageLoadConfigDirectory64v10 for binaries compiled +// since Windows 10 build 18362 (or maybe earlier). +type ImageLoadConfigDirectory64v10 struct { + Size uint32 + TimeDateStamp uint32 + MajorVersion uint16 + MinorVersion uint16 + GlobalFlagsClear uint32 + GlobalFlagsSet uint32 + CriticalSectionDefaultTimeout uint32 + DeCommitFreeBlockThreshold uint64 + DeCommitTotalFreeThreshold uint64 + LockPrefixTable uint64 + MaximumAllocationSize uint64 + VirtualMemoryThreshold uint64 + ProcessAffinityMask uint64 + ProcessHeapFlags uint32 + CSDVersion uint16 + DependentLoadFlags uint16 + EditList uint64 + SecurityCookie uint64 + SEHandlerTable uint64 + SEHandlerCount uint64 + GuardCFCheckFunctionPointer uint64 + GuardCFDispatchFunctionPointer uint64 + GuardCFFunctionTable uint64 + GuardCFFunctionCount uint64 + GuardFlags uint32 + CodeIntegrity ImageLoadConfigCodeIntegrity + GuardAddressTakenIatEntryTable uint64 + GuardAddressTakenIatEntryCount uint64 + GuardLongJumpTargetTable uint64 + GuardLongJumpTargetCount uint64 + DynamicValueRelocTable uint64 + CHPEMetadataPointer uint64 + GuardRFFailureRoutine uint64 + GuardRFFailureRoutineFunctionPointer uint64 + DynamicValueRelocTableOffset uint32 + DynamicValueRelocTableSection uint16 + Reserved2 uint16 + GuardRFVerifyStackPointerFunctionPointer uint64 + HotPatchTableOffset uint32 + Reserved3 uint32 + EnclaveConfigurationPointer uint64 + VolatileMetadataPointer uint64 +} + +// ImageLoadConfigDirectory64v11 for binaries compiled +// since Windows 10 build 19534 (or maybe earlier). +type ImageLoadConfigDirectory64v11 struct { + Size uint32 + TimeDateStamp uint32 + MajorVersion uint16 + MinorVersion uint16 + GlobalFlagsClear uint32 + GlobalFlagsSet uint32 + CriticalSectionDefaultTimeout uint32 + DeCommitFreeBlockThreshold uint64 + DeCommitTotalFreeThreshold uint64 + LockPrefixTable uint64 + MaximumAllocationSize uint64 + VirtualMemoryThreshold uint64 + ProcessAffinityMask uint64 + ProcessHeapFlags uint32 + CSDVersion uint16 + DependentLoadFlags uint16 + EditList uint64 + SecurityCookie uint64 + SEHandlerTable uint64 + SEHandlerCount uint64 + GuardCFCheckFunctionPointer uint64 + GuardCFDispatchFunctionPointer uint64 + GuardCFFunctionTable uint64 + GuardCFFunctionCount uint64 + GuardFlags uint32 + CodeIntegrity ImageLoadConfigCodeIntegrity + GuardAddressTakenIatEntryTable uint64 + GuardAddressTakenIatEntryCount uint64 + GuardLongJumpTargetTable uint64 + GuardLongJumpTargetCount uint64 + DynamicValueRelocTable uint64 + CHPEMetadataPointer uint64 + GuardRFFailureRoutine uint64 + GuardRFFailureRoutineFunctionPointer uint64 + DynamicValueRelocTableOffset uint32 + DynamicValueRelocTableSection uint16 + Reserved2 uint16 + GuardRFVerifyStackPointerFunctionPointer uint64 + HotPatchTableOffset uint32 + Reserved3 uint32 + EnclaveConfigurationPointer uint64 + VolatileMetadataPointer uint64 + GuardEHContinuationTable uint64 + GuardEHContinuationCount uint64 +} + +// ImageLoadConfigDirectory64v12 for binaries compiled +// since Visual C++ 2019 / RS5_IMAGE_LOAD_CONFIG_DIRECTORY64. +type ImageLoadConfigDirectory64v12 struct { + Size uint32 + TimeDateStamp uint32 + MajorVersion uint16 + MinorVersion uint16 + GlobalFlagsClear uint32 + GlobalFlagsSet uint32 + CriticalSectionDefaultTimeout uint32 + DeCommitFreeBlockThreshold uint64 + DeCommitTotalFreeThreshold uint64 + LockPrefixTable uint64 + MaximumAllocationSize uint64 + VirtualMemoryThreshold uint64 + ProcessAffinityMask uint64 + ProcessHeapFlags uint32 + CSDVersion uint16 + DependentLoadFlags uint16 + EditList uint64 + SecurityCookie uint64 + SEHandlerTable uint64 + SEHandlerCount uint64 + GuardCFCheckFunctionPointer uint64 + GuardCFDispatchFunctionPointer uint64 + GuardCFFunctionTable uint64 + GuardCFFunctionCount uint64 + GuardFlags uint32 + CodeIntegrity ImageLoadConfigCodeIntegrity + GuardAddressTakenIatEntryTable uint64 + GuardAddressTakenIatEntryCount uint64 + GuardLongJumpTargetTable uint64 + GuardLongJumpTargetCount uint64 + DynamicValueRelocTable uint64 + CHPEMetadataPointer uint64 + GuardRFFailureRoutine uint64 + GuardRFFailureRoutineFunctionPointer uint64 + DynamicValueRelocTableOffset uint32 + DynamicValueRelocTableSection uint16 + Reserved2 uint16 + GuardRFVerifyStackPointerFunctionPointer uint64 + HotPatchTableOffset uint32 + Reserved3 uint32 + EnclaveConfigurationPointer uint64 + VolatileMetadataPointer uint64 + GuardEHContinuationTable uint64 + GuardEHContinuationCount uint64 + GuardXFGCheckFunctionPointer uint64 + GuardXFGDispatchFunctionPointer uint64 + GuardXFGTableDispatchFunctionPointer uint64 +} + +// ImageLoadConfigDirectory64 Contains the load configuration data of an image +// for x64 binaries. +type ImageLoadConfigDirectory64 struct { + Size uint32 + TimeDateStamp uint32 + MajorVersion uint16 + MinorVersion uint16 + GlobalFlagsClear uint32 + GlobalFlagsSet uint32 + CriticalSectionDefaultTimeout uint32 + DeCommitFreeBlockThreshold uint64 + DeCommitTotalFreeThreshold uint64 + LockPrefixTable uint64 + MaximumAllocationSize uint64 + VirtualMemoryThreshold uint64 + ProcessAffinityMask uint64 + ProcessHeapFlags uint32 + CSDVersion uint16 + DependentLoadFlags uint16 + EditList uint64 + SecurityCookie uint64 + SEHandlerTable uint64 + SEHandlerCount uint64 + GuardCFCheckFunctionPointer uint64 + GuardCFDispatchFunctionPointer uint64 + GuardCFFunctionTable uint64 + GuardCFFunctionCount uint64 + GuardFlags uint32 + CodeIntegrity ImageLoadConfigCodeIntegrity + GuardAddressTakenIatEntryTable uint64 + GuardAddressTakenIatEntryCount uint64 + GuardLongJumpTargetTable uint64 + GuardLongJumpTargetCount uint64 + DynamicValueRelocTable uint64 + CHPEMetadataPointer uint64 + GuardRFFailureRoutine uint64 + GuardRFFailureRoutineFunctionPointer uint64 + DynamicValueRelocTableOffset uint32 + DynamicValueRelocTableSection uint16 + Reserved2 uint16 + GuardRFVerifyStackPointerFunctionPointer uint64 + HotPatchTableOffset uint32 + Reserved3 uint32 + EnclaveConfigurationPointer uint64 + VolatileMetadataPointer uint64 + GuardEHContinuationTable uint64 + GuardEHContinuationCount uint64 + GuardXFGCheckFunctionPointer uint64 + GuardXFGDispatchFunctionPointer uint64 + GuardXFGTableDispatchFunctionPointer uint64 +} + +type ImageCHPEMetadataX86v1 struct { + Version uint32 + CHPECodeAddressRangeOffset uint32 + CHPECodeAddressRangeCount uint32 + WowA64ExceptionHandlerFunctionPtr uint32 + WowA64DispatchCallFunctionPtr uint32 + WowA64DispatchIndirectCallFunctionPtr uint32 + WowA64DispatchIndirectCallCfgFunctionPtr uint32 + WowA64DispatchRetFunctionPtr uint32 + WowA64DispatchRetLeafFunctionPtr uint32 + WowA64DispatchJumpFunctionPtr uint32 +} + +type ImageCHPEMetadataX86v2 struct { + Version uint32 + CHPECodeAddressRangeOffset uint32 + CHPECodeAddressRangeCount uint32 + WowA64ExceptionHandlerFunctionPtr uint32 + WowA64DispatchCallFunctionPtr uint32 + WowA64DispatchIndirectCallFunctionPtr uint32 + WowA64DispatchIndirectCallCfgFunctionPtr uint32 + WowA64DispatchRetFunctionPtr uint32 + WowA64DispatchRetLeafFunctionPtr uint32 + WowA64DispatchJumpFunctionPtr uint32 + CompilerIATPointer uint32 // Present if Version >= 2 +} + +type ImageCHPEMetadataX86v3 struct { + Version uint32 + CHPECodeAddressRangeOffset uint32 + CHPECodeAddressRangeCount uint32 + WowA64ExceptionHandlerFunctionPtr uint32 + WowA64DispatchCallFunctionPtr uint32 + WowA64DispatchIndirectCallFunctionPtr uint32 + WowA64DispatchIndirectCallCfgFunctionPtr uint32 + WowA64DispatchRetFunctionPtr uint32 + WowA64DispatchRetLeafFunctionPtr uint32 + WowA64DispatchJumpFunctionPtr uint32 + CompilerIATPointer uint32 + WowA64RdtscFunctionPtr uint32 // Present if Version >= 3 +} + +type CodeRange struct { + Begin uint32 + Length uint32 + Machine uint8 +} + +type CompilerIAT struct { + RVA uint32 + Value uint32 + Description string +} +type HybridPE struct { + CHPEMetadata interface{} + CodeRanges []CodeRange + CompilerIAT []CompilerIAT +} + +type ImageDynamicRelocationTable struct { + Version uint32 + Size uint32 + // IMAGE_DYNAMIC_RELOCATION DynamicRelocations[0]; +} + +type ImageDynamicRelocation32 struct { + Symbol uint32 + BaseRelocSize uint32 + // IMAGE_BASE_RELOCATION BaseRelocations[0]; +} + +type ImageDynamicRelocation64 struct { + Symbol uint64 + BaseRelocSize uint32 + // IMAGE_BASE_RELOCATION BaseRelocations[0]; +} + +type ImageDynamicRelocation32v2 struct { + HeaderSize uint32 + FixupInfoSize uint32 + Symbol uint32 + SymbolGroup uint32 + Flags uint32 + // ... variable length header fields + // UCHAR FixupInfo[FixupInfoSize] +} + +type ImageDynamicRelocation64v2 struct { + HeaderSize uint32 + FixupInfoSize uint32 + Symbol uint64 + SymbolGroup uint32 + Flags uint32 + // ... variable length header fields + // UCHAR FixupInfo[FixupInfoSize] +} + +type ImagePrologueDynamicRelocationHeader struct { + PrologueByteCount uint8 + // UCHAR PrologueBytes[PrologueByteCount]; +} + +type ImageEpilogueDynamicRelocationHeader struct { + EpilogueCount uint32 + EpilogueByteCount uint8 + BranchDescriptorElementSize uint8 + BranchDescriptorCount uint8 + // UCHAR BranchDescriptors[...]; + // UCHAR BranchDescriptorBitMap[...]; +} + +type CFGFunction struct { + Target uint32 + Flags *uint8 + Description string +} + +type CFGIATEntry struct { + RVA uint32 + IATValue uint32 + INTValue uint32 + Description string +} + +type TypeOffset struct { + Value uint16 + Type uint8 + DynamicSymbolOffset uint16 +} + +type RelocBlock struct { + ImgBaseReloc ImageBaseRelocation + TypeOffsets []TypeOffset +} +type RelocEntry struct { + // Could be ImageDynamicRelocation32{} or ImageDynamicRelocation64{} + ImgDynReloc interface{} + RelocBlocks []RelocBlock +} + +// DVRT Dynamic Value Relocation Table +type DVRT struct { + ImgDynRelocTable ImageDynamicRelocationTable + Entries []RelocEntry +} + +type Enclave struct { + + // Points to either ImageEnclaveConfig32{} or ImageEnclaveConfig64{} + Config interface{} + + Imports []ImageEnclaveImport +} + +type RangeTableEntry struct { + Rva uint32 + Size uint32 +} + +type VolatileMetadata struct { + Struct ImageVolatileMetadata + AccessRVATable []uint32 + InfoRangeTable []RangeTableEntry +} +type LoadConfig struct { + LoadCfgStruct interface{} `json:",omitempty"` + SEH []uint32 `json:",omitempty"` + GFIDS []CFGFunction `json:",omitempty"` + CFGIAT []CFGIATEntry `json:",omitempty"` + CFGLongJump []uint32 `json:",omitempty"` + CHPE *HybridPE `json:",omitempty"` + DVRT *DVRT `json:",omitempty"` + Enclave *Enclave `json:",omitempty"` + VolatileMetadata *VolatileMetadata `json:",omitempty"` +} + +// ImageLoadConfigCodeIntegrity Code Integrity in loadconfig (CI). +type ImageLoadConfigCodeIntegrity struct { + Flags uint16 // Flags to indicate if CI information is available, etc. + Catalog uint16 // 0xFFFF means not available + CatalogOffset uint32 + Reserved uint32 // Additional bitmask to be defined later +} + +type ImageEnclaveConfig32 struct { + + // The size of the IMAGE_ENCLAVE_CONFIG32 structure, in bytes. + Size uint32 + + // The minimum size of the IMAGE_ENCLAVE_CONFIG32 structure that the image loader must be able to process in order for the enclave to be usable. This member allows an enclave to inform an earlier version of the image loader that the image loader can safely load the enclave and ignore optional members added to IMAGE_ENCLAVE_CONFIG32 for later versions of the enclave. + + // If the size of IMAGE_ENCLAVE_CONFIG32 that the image loader can process is less than MinimumRequiredConfigSize, the enclave cannot be run securely. If MinimumRequiredConfigSize is zero, the minimum size of the IMAGE_ENCLAVE_CONFIG32 structure that the image loader must be able to process in order for the enclave to be usable is assumed to be the size of the structure through and including the MinimumRequiredConfigSize member. + MinimumRequiredConfigSize uint32 + + // A flag that indicates whether the enclave permits debugging. + PolicyFlags uint32 + + // The number of images in the array of images that the ImportList member + // points to. + NumberOfImports uint32 + + // The relative virtual address of the array of images that the enclave + // image may import, with identity information for each image. + ImportList uint32 + + // The size of each image in the array of images that the ImportList member + // points to. + ImportEntrySize uint32 + + // The family identifier that the author of the enclave assigned to the enclave. + FamilyID [ImageEnclaveShortIdLength]uint8 + + // The image identifier that the author of the enclave assigned to the enclave. + ImageID [ImageEnclaveShortIdLength]uint8 + + // The version number that the author of the enclave assigned to the enclave. + ImageVersion uint32 + + // The security version number that the author of the enclave assigned to + // the enclave. + SecurityVersion uint32 + + // The expected virtual size of the private address range for the enclave, + // in bytes. + EnclaveSize uint32 + + // The maximum number of threads that can be created within the enclave. + NumberOfThreads uint32 + + // A flag that indicates whether the image is suitable for use as the + // primary image in the enclave. + EnclaveFlags uint32 +} + +type ImageEnclaveConfig64 struct { + + // The size of the IMAGE_ENCLAVE_CONFIG32 structure, in bytes. + Size uint32 + + // The minimum size of the IMAGE_ENCLAVE_CONFIG32 structure that the image loader must be able to process in order for the enclave to be usable. This member allows an enclave to inform an earlier version of the image loader that the image loader can safely load the enclave and ignore optional members added to IMAGE_ENCLAVE_CONFIG32 for later versions of the enclave. + + // If the size of IMAGE_ENCLAVE_CONFIG32 that the image loader can process is less than MinimumRequiredConfigSize, the enclave cannot be run securely. If MinimumRequiredConfigSize is zero, the minimum size of the IMAGE_ENCLAVE_CONFIG32 structure that the image loader must be able to process in order for the enclave to be usable is assumed to be the size of the structure through and including the MinimumRequiredConfigSize member. + MinimumRequiredConfigSize uint32 + + // A flag that indicates whether the enclave permits debugging. + PolicyFlags uint32 + + // The number of images in the array of images that the ImportList member + // points to. + NumberOfImports uint32 + + // The relative virtual address of the array of images that the enclave + // image may import, with identity information for each image. + ImportList uint32 + + // The size of each image in the array of images that the ImportList member + // points to. + ImportEntrySize uint32 + + // The family identifier that the author of the enclave assigned to the enclave. + FamilyID [ImageEnclaveShortIdLength]uint8 + + // The image identifier that the author of the enclave assigned to the enclave. + ImageID [ImageEnclaveShortIdLength]uint8 + + // The version number that the author of the enclave assigned to the enclave. + ImageVersion uint32 + + // The security version number that the author of the enclave assigned to the enclave. + SecurityVersion uint32 + + // The expected virtual size of the private address range for the enclave,in bytes. + EnclaveSize uint64 + + // The maximum number of threads that can be created within the enclave. + NumberOfThreads uint32 + + // A flag that indicates whether the image is suitable for use as the primary image in the enclave. + EnclaveFlags uint32 +} + +// ImageEnclaveImport defines a entry in the array of images that an enclave +// can import. +type ImageEnclaveImport struct { + + // The type of identifier of the image that must match the value in the import record. + MatchType uint32 + + // The minimum enclave security version that each image must have for the image to be imported successfully. The image is rejected unless its enclave security version is equal to or greater than the minimum value in the import record. Set the value in the import record to zero to turn off the security version check. + MinimumSecurityVersion uint32 + + // The unique identifier of the primary module for the enclave, if the MatchType member is IMAGE_ENCLAVE_IMPORT_MATCH_UNIQUE_ID. Otherwise, the author identifier of the primary module for the enclave.. + UniqueOrAuthorID [ImageEnclaveLongIdLength]uint8 + + // The family identifier of the primary module for the enclave. + FamilyID [ImageEnclaveShortIdLength]uint8 + + // The image identifier of the primary module for the enclave. + ImageID [ImageEnclaveShortIdLength]uint8 + + // The relative virtual address of a NULL-terminated string that contains the same value found in the import directory for the image. + ImportName uint32 + + // Reserved. + Reserved uint32 +} + +type ImageVolatileMetadata struct { + Size uint32 + Version uint32 + VolatileAccessTable uint32 + VolatileAccessTableSize uint32 + VolatileInfoRangeTable uint32 + VolatileInfoRangeTableSize uint32 +} + +// The load configuration structure (IMAGE_LOAD_CONFIG_DIRECTORY) was formerly +// used in very limited cases in the Windows NT operating system itself to +// describe various features too difficult or too large to describe in the file + +// header or optional header of the image. Current versions of the Microsoft +// linker and Windows XP and later versions of Windows use a new version of this +// structure for 32-bit x86-based systems that include reserved SEH technology. +// The data directory entry for a pre-reserved SEH load configuration structure +// must specify a particular size of the load configuration structure because +// the operating system loader always expects it to be a certain value. In that +// regard, the size is really only a version check. For compatibility with +// Windows XP and earlier versions of Windows, the size must be 64 for x86 images. +func (pe *File) parseLoadConfigDirectory(rva, size uint32) error { + + // As the load config structure changes over time, + // we first read it size to figure out which one we have to cast against. + fileOffset := pe.getOffsetFromRva(rva) + structSize, err := pe.ReadUint32(fileOffset) + if err != nil { + return err + } + + // Use this helper function to print struct size. + // PrintLoadConfigStruct() + var loadCfg interface{} + + if pe.Is32 { + switch int(structSize) { + case 0x40: + loadCfgv1 := ImageLoadConfigDirectory32v1{} + err = pe.structUnpack(&loadCfgv1, fileOffset, structSize) + loadCfg = loadCfgv1 + case 0x48: + loadCfgv2 := ImageLoadConfigDirectory32v2{} + err = pe.structUnpack(&loadCfgv2, fileOffset, structSize) + loadCfg = loadCfgv2 + case 0x5c: + loadCfgv3 := ImageLoadConfigDirectory32v3{} + err = pe.structUnpack(&loadCfgv3, fileOffset, structSize) + loadCfg = loadCfgv3 + case 0x68: + loadCfgv4 := ImageLoadConfigDirectory32v4{} + err = pe.structUnpack(&loadCfgv4, fileOffset, structSize) + loadCfg = loadCfgv4 + case 0x78: + loadCfgv5 := ImageLoadConfigDirectory32v5{} + err = pe.structUnpack(&loadCfgv5, fileOffset, structSize) + loadCfg = loadCfgv5 + case 0x80: + loadCfgv6 := ImageLoadConfigDirectory32v6{} + err = pe.structUnpack(&loadCfgv6, fileOffset, structSize) + loadCfg = loadCfgv6 + case 0x90: + loadCfgv7 := ImageLoadConfigDirectory32v7{} + err = pe.structUnpack(&loadCfgv7, fileOffset, structSize) + loadCfg = loadCfgv7 + case 0x98: + loadCfgv8 := ImageLoadConfigDirectory32v8{} + err = pe.structUnpack(&loadCfgv8, fileOffset, structSize) + loadCfg = loadCfgv8 + case 0xa0: + loadCfgv9 := ImageLoadConfigDirectory32v9{} + err = pe.structUnpack(&loadCfgv9, fileOffset, structSize) + loadCfg = loadCfgv9 + case 0xa4: + loadCfgv10 := ImageLoadConfigDirectory32v10{} + err = pe.structUnpack(&loadCfgv10, fileOffset, structSize) + loadCfg = loadCfgv10 + case 0xac: + loadCfgv11 := ImageLoadConfigDirectory32v11{} + err = pe.structUnpack(&loadCfgv11, fileOffset, structSize) + loadCfg = loadCfgv11 + case 0xb8: + loadCfgv12 := ImageLoadConfigDirectory32v12{} + err = pe.structUnpack(&loadCfgv12, fileOffset, structSize) + loadCfg = loadCfgv12 + default: + // We use the oldest load config. + loadCfg32 := ImageLoadConfigDirectory32v1{} + err = pe.structUnpack(&loadCfg32, fileOffset, structSize) + loadCfg = loadCfg32 + } + } else { + switch int(structSize) { + case 0x70: + loadCfgv2 := ImageLoadConfigDirectory64v2{} + err = pe.structUnpack(&loadCfgv2, fileOffset, structSize) + loadCfg = loadCfgv2 + case 0x94: + loadCfgv3 := ImageLoadConfigDirectory64v3{} + err = pe.structUnpack(&loadCfgv3, fileOffset, structSize) + loadCfg = loadCfgv3 + case 0xa0: + loadCfgv4 := ImageLoadConfigDirectory64v4{} + err = pe.structUnpack(&loadCfgv4, fileOffset, structSize) + loadCfg = loadCfgv4 + case 0xc0: + loadCfgv5 := ImageLoadConfigDirectory64v5{} + err = pe.structUnpack(&loadCfgv5, fileOffset, structSize) + loadCfg = loadCfgv5 + case 0xd0: + loadCfgv6 := ImageLoadConfigDirectory64v6{} + err = pe.structUnpack(&loadCfgv6, fileOffset, structSize) + loadCfg = loadCfgv6 + case 0xe8: + loadCfgv7 := ImageLoadConfigDirectory64v7{} + err = pe.structUnpack(&loadCfgv7, fileOffset, structSize) + loadCfg = loadCfgv7 + case 0xf4: + loadCfgv8 := ImageLoadConfigDirectory64v8{} + err = pe.structUnpack(&loadCfgv8, fileOffset, structSize) + loadCfg = loadCfgv8 + case 0x100: + loadCfgv9 := ImageLoadConfigDirectory64v9{} + err = pe.structUnpack(&loadCfgv9, fileOffset, structSize) + loadCfg = loadCfgv9 + case 0x108: + loadCfgv10 := ImageLoadConfigDirectory64v10{} + err = pe.structUnpack(&loadCfgv10, fileOffset, structSize) + loadCfg = loadCfgv10 + case 0x118: + loadCfgv11 := ImageLoadConfigDirectory64v11{} + err = pe.structUnpack(&loadCfgv11, fileOffset, structSize) + loadCfg = loadCfgv11 + case 0x130: + loadCfgv12 := ImageLoadConfigDirectory64v12{} + err = pe.structUnpack(&loadCfgv12, fileOffset, structSize) + loadCfg = loadCfgv12 + default: + // We use the oldest load config. + loadCfg64 := ImageLoadConfigDirectory64v2{} + err = pe.structUnpack(&loadCfg64, fileOffset, structSize) + loadCfg = loadCfg64 + } + } + + if err != nil { + return err + } + + // Save the load config struct. + loadConfig := LoadConfig{} + pe.LoadConfig = &loadConfig + loadConfig.LoadCfgStruct = loadCfg + + // Retrieve SEH handlers if there are any.. + if pe.Is32 { + handlers := pe.getSEHHandlers() + loadConfig.SEH = handlers + } + + // Retrieve Control Flow Guard Function Targets if there are any. + loadConfig.GFIDS = pe.getControlFlowGuardFunctions() + + // Retrieve Control Flow Guard IAT entries if there are any. + loadConfig.CFGIAT = pe.getControlFlowGuardIAT() + + // Retrive Long jump target functions if there are any. + loadConfig.CFGLongJump = pe.getLongJumpTargetTable() + + // Retrieve compiled hybrid PE metadata if there are any. + loadConfig.CHPE = pe.getHybridPE() + + // Retrieve dynamic value relocation table if there are any. + loadConfig.DVRT = pe.getDynamicValueRelocTable() + + // Retrieve enclave configuration if there are any. + loadConfig.Enclave = pe.getEnclaveConfiguration() + + // Retrieve volatile metadat table if there are any. + loadConfig.VolatileMetadata = pe.getVolatileMetadata() + + return nil +} + +// PrintLoadConfigStruct will print size of each load config struct. +func PrintLoadConfigStruct() { + fmt.Printf("ImageLoadConfigDirectory32v1 size : 0x%x\n", binary.Size(ImageLoadConfigDirectory32v1{})) + fmt.Printf("ImageLoadConfigDirectory32v2 size : 0x%x\n", binary.Size(ImageLoadConfigDirectory32v2{})) + fmt.Printf("ImageLoadConfigDirectory32v3 size : 0x%x\n", binary.Size(ImageLoadConfigDirectory32v3{})) + fmt.Printf("ImageLoadConfigDirectory32v4 size : 0x%x\n", binary.Size(ImageLoadConfigDirectory32v4{})) + fmt.Printf("ImageLoadConfigDirectory32v5 size : 0x%x\n", binary.Size(ImageLoadConfigDirectory32v5{})) + fmt.Printf("ImageLoadConfigDirectory32v6 size : 0x%x\n", binary.Size(ImageLoadConfigDirectory32v6{})) + fmt.Printf("ImageLoadConfigDirectory32v7 size : 0x%x\n", binary.Size(ImageLoadConfigDirectory32v7{})) + fmt.Printf("ImageLoadConfigDirectory32v8 size : 0x%x\n", binary.Size(ImageLoadConfigDirectory32v8{})) + fmt.Printf("ImageLoadConfigDirectory32v9 size : 0x%x\n", binary.Size(ImageLoadConfigDirectory32v9{})) + fmt.Printf("ImageLoadConfigDirectory32v10 size : 0x%x\n", binary.Size(ImageLoadConfigDirectory32v10{})) + fmt.Printf("ImageLoadConfigDirectory32v11 size : 0x%x\n", binary.Size(ImageLoadConfigDirectory32v11{})) + fmt.Printf("ImageLoadConfigDirectory32v12 size : 0x%x\n", binary.Size(ImageLoadConfigDirectory32v12{})) + + fmt.Printf("ImageLoadConfigDirectory64v2 size : 0x%x\n", binary.Size(ImageLoadConfigDirectory64v2{})) + fmt.Printf("ImageLoadConfigDirectory64v3 size : 0x%x\n", binary.Size(ImageLoadConfigDirectory64v3{})) + fmt.Printf("ImageLoadConfigDirectory64v4 size : 0x%x\n", binary.Size(ImageLoadConfigDirectory64v4{})) + fmt.Printf("ImageLoadConfigDirectory64v5 size : 0x%x\n", binary.Size(ImageLoadConfigDirectory64v5{})) + fmt.Printf("ImageLoadConfigDirectory64v6 size : 0x%x\n", binary.Size(ImageLoadConfigDirectory64v6{})) + fmt.Printf("ImageLoadConfigDirectory64v7 size : 0x%x\n", binary.Size(ImageLoadConfigDirectory64v7{})) + fmt.Printf("ImageLoadConfigDirectory64v8 size : 0x%x\n", binary.Size(ImageLoadConfigDirectory64v8{})) + fmt.Printf("ImageLoadConfigDirectory64v9 size : 0x%x\n", binary.Size(ImageLoadConfigDirectory64v9{})) + fmt.Printf("ImageLoadConfigDirectory64v10 size : 0x%x\n", binary.Size(ImageLoadConfigDirectory64v10{})) + fmt.Printf("ImageLoadConfigDirectory64v11 size : 0x%x\n", binary.Size(ImageLoadConfigDirectory64v11{})) + fmt.Printf("ImageLoadConfigDirectory64v12 size : 0x%x\n", binary.Size(ImageLoadConfigDirectory64v12{})) +} + +// StringifyGuardFlags returns list of strings which describes the GuardFlags. +func StringifyGuardFlags(flags uint32) []string { + var values []string + guardFlagMap := map[uint32]string{ + ImageGuardCfInstrumented: "Instrumented", + ImageGuardCfWInstrumented: "WriteInstrumented", + ImageGuardCfFunctionTablePresent: "TargetMetadata", + ImageGuardSecurityCookieUnused: "SecurityCookieUnused", + ImageGuardProtectDelayloadIAT: "DelayloadIAT", + ImageGuardDelayloadIATInItsOwnSection: "DelayloadIATInItsOwnSection", + ImageGuardCfExportSuppressionInfoPresent: "ExportSuppressionInfoPresent", + ImageGuardCfEnableExportSuppression: "EnableExportSuppression", + ImageGuardCfLongjumpTablePresent: "LongjumpTablePresent", + } + + for k, s := range guardFlagMap { + if k&flags != 0 { + values = append(values, s) + } + } + return values +} + +func (pe *File) getSEHHandlers() []uint32 { + + var handlers []uint32 + v := reflect.ValueOf(pe.LoadConfig.LoadCfgStruct) + + // SEHandlerCount is found in index 19 of the struct. + if v.NumField() > 19 { + SEHandlerCount := uint32(v.Field(19).Uint()) + if SEHandlerCount > 0 { + SEHandlerTable := uint32(v.Field(18).Uint()) + imageBase := pe.NtHeader.OptionalHeader.(ImageOptionalHeader32).ImageBase + rva := SEHandlerTable - imageBase + for i := uint32(0); i < SEHandlerCount; i++ { + offset := pe.getOffsetFromRva(rva + i*4) + handler, err := pe.ReadUint32(offset) + if err != nil { + return handlers + } + + handlers = append(handlers, handler) + } + } + } + + return handlers +} + +func (pe *File) getControlFlowGuardFunctions() []CFGFunction { + + v := reflect.ValueOf(pe.LoadConfig.LoadCfgStruct) + var GFIDS []CFGFunction + var err error + + // GuardCFFunctionCount is found in index 23 of the struct. + if v.NumField() > 23 { + // The GFIDS table is an array of 4 + n bytes, where n is given by : + // ((GuardFlags & IMAGE_GUARD_CF_FUNCTION_TABLE_SIZE_MASK) >> + // IMAGE_GUARD_CF_FUNCTION_TABLE_SIZE_SHIFT). + + // This allows for extra metadata to be attached to CFG call targets in + // the future. The only currently defined metadata is an optional 1-byte + // extra flags field (“GFIDS flags”) that is attached to each GFIDS + // entry if any call targets have metadata. + GuardFlags := v.Field(24).Uint() + n := (GuardFlags & ImageGuardCfFnctionTableSizeMask) >> + ImageGuardCfFnctionTableSizeShift + GuardCFFunctionCount := v.Field(23).Uint() + if GuardCFFunctionCount > 0 { + if pe.Is32 { + GuardCFFunctionTable := uint32(v.Field(22).Uint()) + imageBase := pe.NtHeader.OptionalHeader.(ImageOptionalHeader32).ImageBase + rva := GuardCFFunctionTable - imageBase + offset := pe.getOffsetFromRva(rva) + for i := uint32(1); i <= uint32(GuardCFFunctionCount); i++ { + cfgFunction := CFGFunction{} + var cfgFlags uint8 + cfgFunction.Target, err = pe.ReadUint32(offset) + if err != nil { + return GFIDS + } + if n > 0 { + pe.structUnpack(&cfgFlags, offset+4, uint32(n)) + cfgFunction.Flags = &cfgFlags + if cfgFlags == ImageGuardFlagFIDSupressed || + cfgFlags == ImageGuardFlagExportSupressed { + exportName := pe.GetExportFunctionByRVA(cfgFunction.Target) + cfgFunction.Description = exportName.Name + } + } + + GFIDS = append(GFIDS, cfgFunction) + offset += 4 + uint32(n) + } + } else { + GuardCFFunctionTable := v.Field(22).Uint() + imageBase := pe.NtHeader.OptionalHeader.(ImageOptionalHeader64).ImageBase + rva := uint32(GuardCFFunctionTable - imageBase) + offset := pe.getOffsetFromRva(rva) + for i := uint64(1); i <= GuardCFFunctionCount; i++ { + var cfgFlags uint8 + cfgFunction := CFGFunction{} + cfgFunction.Target, err = pe.ReadUint32(offset) + if err != nil { + return GFIDS + } + if n > 0 { + pe.structUnpack(&cfgFlags, offset+4, uint32(n)) + cfgFunction.Flags = &cfgFlags + if cfgFlags == ImageGuardFlagFIDSupressed || + cfgFlags == ImageGuardFlagExportSupressed { + exportName := pe.GetExportFunctionByRVA(cfgFunction.Target) + cfgFunction.Description = exportName.Name + } + } + + GFIDS = append(GFIDS, cfgFunction) + offset += 4 + uint32(n) + } + } + + } + } + return GFIDS +} + +func (pe *File) getControlFlowGuardIAT() []CFGIATEntry { + + v := reflect.ValueOf(pe.LoadConfig.LoadCfgStruct) + var GFGIAT []CFGIATEntry + var err error + + // GuardAddressTakenIatEntryCount is found in index 27 of the struct. + if v.NumField() > 27 { + // An image that supports CFG ES includes a GuardAddressTakenIatEntryTable + // whose count is provided by the GuardAddressTakenIatEntryCount as part + // of its load configuration directory. This table is structurally + // formatted the same as the GFIDS table. It uses the same GuardFlags + // IMAGE_GUARD_CF_FUNCTION_TABLE_SIZE_MASK mechanism to encode extra + // optional metadata bytes in the address taken IAT table, though all + // metadata bytes must be zero for the address taken IAT table and are + // reserved. + GuardFlags := v.Field(24).Uint() + n := (GuardFlags & ImageGuardCfFnctionTableSizeMask) >> + ImageGuardCfFnctionTableSizeShift + GuardAddressTakenIatEntryCount := v.Field(27).Uint() + if GuardAddressTakenIatEntryCount > 0 { + if pe.Is32 { + GuardAddressTakenIatEntryTable := uint32(v.Field(26).Uint()) + imageBase := pe.NtHeader.OptionalHeader.(ImageOptionalHeader32).ImageBase + rva := GuardAddressTakenIatEntryTable - imageBase + offset := pe.getOffsetFromRva(rva) + for i := uint32(1); i <= uint32(GuardAddressTakenIatEntryCount); i++ { + cfgIATEntry := CFGIATEntry{} + cfgIATEntry.RVA, err = pe.ReadUint32(offset) + if err != nil { + return GFGIAT + } + imp, index := pe.GetImportEntryInfoByRVA(cfgIATEntry.RVA) + if len(imp.Functions) != 0 { + cfgIATEntry.INTValue = uint32(imp.Functions[index].OriginalThunkValue) + cfgIATEntry.IATValue = uint32(imp.Functions[index].ThunkValue) + cfgIATEntry.Description = imp.Name + "!" + imp.Functions[index].Name + } + GFGIAT = append(GFGIAT, cfgIATEntry) + offset += 4 + uint32(n) + } + } else { + GuardAddressTakenIatEntryTable := v.Field(26).Uint() + imageBase := pe.NtHeader.OptionalHeader.(ImageOptionalHeader64).ImageBase + rva := uint32(GuardAddressTakenIatEntryTable - imageBase) + offset := pe.getOffsetFromRva(rva) + for i := uint64(1); i <= GuardAddressTakenIatEntryCount; i++ { + cfgIATEntry := CFGIATEntry{} + cfgIATEntry.RVA, err = pe.ReadUint32(offset) + if err != nil { + return GFGIAT + } + imp, index := pe.GetImportEntryInfoByRVA(cfgIATEntry.RVA) + if len(imp.Functions) != 0 { + cfgIATEntry.INTValue = uint32(imp.Functions[index].OriginalThunkValue) + cfgIATEntry.IATValue = uint32(imp.Functions[index].ThunkValue) + cfgIATEntry.Description = imp.Name + "!" + imp.Functions[index].Name + } + + GFGIAT = append(GFGIAT, cfgIATEntry) + GFGIAT = append(GFGIAT, cfgIATEntry) + offset += 4 + uint32(n) + } + } + + } + } + return GFGIAT +} + +func (pe *File) getLongJumpTargetTable() []uint32 { + + v := reflect.ValueOf(pe.LoadConfig.LoadCfgStruct) + var longJumpTargets []uint32 + + // GuardLongJumpTargetCount is found in index 29 of the struct. + if v.NumField() > 29 { + // The long jump table represents a sorted array of RVAs that are valid + // long jump targets. If a long jump target module sets + // IMAGE_GUARD_CF_LONGJUMP_TABLE_PRESENT in its GuardFlags field, then + // all long jump targets must be enumerated in the LongJumpTargetTable. + GuardFlags := v.Field(24).Uint() + n := (GuardFlags & ImageGuardCfFnctionTableSizeMask) >> + ImageGuardCfFnctionTableSizeShift + GuardLongJumpTargetCount := v.Field(29).Uint() + if GuardLongJumpTargetCount > 0 { + if pe.Is32 { + GuardLongJumpTargetTable := uint32(v.Field(28).Uint()) + imageBase := pe.NtHeader.OptionalHeader.(ImageOptionalHeader32).ImageBase + rva := GuardLongJumpTargetTable - imageBase + offset := pe.getOffsetFromRva(rva) + for i := uint32(1); i <= uint32(GuardLongJumpTargetCount); i++ { + target, err := pe.ReadUint32(offset) + if err != nil { + return longJumpTargets + } + longJumpTargets = append(longJumpTargets, target) + offset += 4 + uint32(n) + } + } else { + GuardLongJumpTargetTable := v.Field(26).Uint() + imageBase := pe.NtHeader.OptionalHeader.(ImageOptionalHeader64).ImageBase + rva := uint32(GuardLongJumpTargetTable - imageBase) + offset := pe.getOffsetFromRva(rva) + for i := uint64(1); i <= GuardLongJumpTargetCount; i++ { + target, err := pe.ReadUint32(offset) + if err != nil { + return longJumpTargets + } + longJumpTargets = append(longJumpTargets, target) + offset += 4 + uint32(n) + } + } + + } + } + return longJumpTargets +} + +func (pe *File) getHybridPE() *HybridPE { + v := reflect.ValueOf(pe.LoadConfig.LoadCfgStruct) + hybridPE := HybridPE{} + + // CHPEMetadataPointer is found in index 31 of the struct. + if v.NumField() <= 30 { + return nil + } + + CHPEMetadataPointer := v.Field(31).Uint() + if CHPEMetadataPointer == 0 { + return nil + } + var rva uint32 + if pe.Is32 { + imageBase := pe.NtHeader.OptionalHeader.(ImageOptionalHeader32).ImageBase + rva = uint32(CHPEMetadataPointer) - imageBase + } else { + imageBase := pe.NtHeader.OptionalHeader.(ImageOptionalHeader64).ImageBase + rva = uint32(CHPEMetadataPointer - imageBase) + } + + // As the image chpe metadata structure changes over time, + // we first read its version to figure out which one we have to + // cast against. + fileOffset := pe.getOffsetFromRva(rva) + version, err := pe.ReadUint32(fileOffset) + if err != nil { + return nil + } + + var ImageCHPEMetaX86 interface{} + + switch version { + case 0x1: + ImageCHPEMetaX86v1 := ImageCHPEMetadataX86v1{} + structSize := uint32(binary.Size(ImageCHPEMetaX86v1)) + err = pe.structUnpack(&ImageCHPEMetaX86v1, fileOffset, structSize) + ImageCHPEMetaX86 = ImageCHPEMetaX86v1 + case 0x2: + ImageCHPEMetaX86v2 := ImageCHPEMetadataX86v2{} + structSize := uint32(binary.Size(ImageCHPEMetaX86v2)) + err = pe.structUnpack(&ImageCHPEMetaX86v2, fileOffset, structSize) + ImageCHPEMetaX86 = ImageCHPEMetaX86v2 + case 0x3: + default: + ImageCHPEMetaX86v3 := ImageCHPEMetadataX86v3{} + structSize := uint32(binary.Size(ImageCHPEMetaX86v3)) + err = pe.structUnpack(&ImageCHPEMetaX86v3, fileOffset, structSize) + ImageCHPEMetaX86 = ImageCHPEMetaX86v3 + } + + hybridPE.CHPEMetadata = ImageCHPEMetaX86 + + v = reflect.ValueOf(ImageCHPEMetaX86) + CHPECodeAddressRangeOffset := uint32(v.Field(1).Uint()) + CHPECodeAddressRangeCount := int(v.Field(2).Uint()) + + // Code Ranges + + /* + typedef struct _IMAGE_CHPE_RANGE_ENTRY { + union { + ULONG StartOffset; + struct { + ULONG NativeCode : 1; + ULONG AddressBits : 31; + } DUMMYSTRUCTNAME; + } DUMMYUNIONNAME; + + ULONG Length; + } IMAGE_CHPE_RANGE_ENTRY, *PIMAGE_CHPE_RANGE_ENTRY; + */ + + rva = CHPECodeAddressRangeOffset + for i := 0; i < CHPECodeAddressRangeCount; i++ { + + codeRange := CodeRange{} + fileOffset := pe.getOffsetFromRva(rva) + begin, err := pe.ReadUint32(fileOffset) + if err != nil { + break + } + + if begin&1 == 1 { + codeRange.Machine = 1 + begin = uint32(int(begin) & ^1) + } + codeRange.Begin = begin + + fileOffset += 4 + size, err := pe.ReadUint32(fileOffset) + if err != nil { + break + } + codeRange.Length = size + + hybridPE.CodeRanges = append(hybridPE.CodeRanges, codeRange) + rva += 8 + } + + // Compiler IAT + CompilerIATPointer := uint32(v.Field(10).Uint()) + if CompilerIATPointer != 0 { + rva := CompilerIATPointer + for i := 0; i < 1024; i++ { + compilerIAT := CompilerIAT{} + compilerIAT.RVA = rva + fileOffset = pe.getOffsetFromRva(rva) + compilerIAT.Value, err = pe.ReadUint32(fileOffset) + if err != nil { + break + } + + pe.LoadConfig.CHPE.CompilerIAT = append( + pe.LoadConfig.CHPE.CompilerIAT, compilerIAT) + rva += 4 + } + } + return &hybridPE +} + +func (pe *File) getDynamicValueRelocTable() *DVRT { + + var structSize uint32 + var imgDynRelocSize uint32 + dvrt := DVRT{} + imgDynRelocTable := ImageDynamicRelocationTable{} + + v := reflect.ValueOf(pe.LoadConfig.LoadCfgStruct) + if v.NumField() <= 35 { + return nil + } + + DynamicValueRelocTableOffset := v.Field(34).Uint() + DynamicValueRelocTableSection := v.Field(35).Uint() + if DynamicValueRelocTableOffset == 0 || DynamicValueRelocTableSection == 0 { + return nil + } + + section := pe.getSectionByName(".reloc") + if section == nil { + return nil + } + + // Get the dynamic value relocation table. + rva := section.VirtualAddress + uint32(DynamicValueRelocTableOffset) + offset := pe.getOffsetFromRva(rva) + structSize = uint32(binary.Size(imgDynRelocTable)) + err := pe.structUnpack(&imgDynRelocTable, offset, structSize) + if err != nil { + return nil + } + + dvrt.ImgDynRelocTable = imgDynRelocTable + offset += structSize + + // Get dynamic relocation entries accrording to version. + switch imgDynRelocTable.Version { + case 1: + relocTableIt := uint32(0) + baseBlockSize := uint32(0) + + // Itreate over our dynamic reloc table entries + for relocTableIt < imgDynRelocTable.Size { + + relocEntry := RelocEntry{} + if pe.Is32 { + imgDynReloc := ImageDynamicRelocation32{} + imgDynRelocSize = uint32(binary.Size(imgDynReloc)) + err = pe.structUnpack(&imgDynReloc, offset, imgDynRelocSize) + if err != nil { + return nil + } + relocEntry.ImgDynReloc = imgDynReloc + baseBlockSize = imgDynReloc.BaseRelocSize + } else { + imgDynReloc := ImageDynamicRelocation64{} + imgDynRelocSize = uint32(binary.Size(imgDynReloc)) + err = pe.structUnpack(&imgDynReloc, offset, imgDynRelocSize) + if err != nil { + return nil + } + relocEntry.ImgDynReloc = imgDynReloc + baseBlockSize = imgDynReloc.BaseRelocSize + } + offset += imgDynRelocSize + relocTableIt += imgDynRelocSize + + // Iterate over reach block + blockIt := uint32(0) + for blockIt < baseBlockSize-imgDynRelocSize { + relocBlock := RelocBlock{} + + baseReloc := ImageBaseRelocation{} + structSize = uint32(binary.Size(baseReloc)) + err = pe.structUnpack(&baseReloc, offset, structSize) + if err != nil { + return nil + } + + relocBlock.ImgBaseReloc = baseReloc + offset += structSize + numTypeOffsets := (baseReloc.SizeOfBlock - structSize) / 2 + for i := uint32(0); i < numTypeOffsets; i++ { + typeOffset := TypeOffset{} + typeOffset.Value, err = pe.ReadUint16(offset) + if err != nil { + return nil + } + + typeOffset.DynamicSymbolOffset = typeOffset.Value & 0xfff + typeOffset.Type = uint8(typeOffset.Value & 0xf000 >> 12) + offset += 2 + + // Padding at the end of the block ? + if (TypeOffset{}) == typeOffset && i+1 == numTypeOffsets { + break + } + + relocBlock.TypeOffsets = append(relocBlock.TypeOffsets, typeOffset) + } + + blockIt += baseReloc.SizeOfBlock + relocEntry.RelocBlocks = append(relocEntry.RelocBlocks, relocBlock) + } + + dvrt.Entries = append(dvrt.Entries, relocEntry) + relocTableIt += baseBlockSize + } + case 2: + fmt.Print("Got version 2 !") + } + + return &dvrt +} + +func (pe *File) getEnclaveConfiguration() *Enclave { + + enclave := Enclave{} + + v := reflect.ValueOf(pe.LoadConfig.LoadCfgStruct) + if v.NumField() <= 40 { + return nil + } + + EnclaveConfigurationPointer := v.Field(40).Uint() + if EnclaveConfigurationPointer == 0 { + return nil + } + + if pe.Is32 { + imgEnclaveCfg := ImageEnclaveConfig32{} + imgEnclaveCfgSize := uint32(binary.Size(imgEnclaveCfg)) + imageBase := pe.NtHeader.OptionalHeader.(ImageOptionalHeader32).ImageBase + rva := uint32(EnclaveConfigurationPointer) - imageBase + offset := pe.getOffsetFromRva(rva) + err := pe.structUnpack(&imgEnclaveCfg, offset, imgEnclaveCfgSize) + if err != nil { + return nil + } + enclave.Config = imgEnclaveCfg + } else { + imgEnclaveCfg := ImageEnclaveConfig64{} + imgEnclaveCfgSize := uint32(binary.Size(imgEnclaveCfg)) + imageBase := pe.NtHeader.OptionalHeader.(ImageOptionalHeader64).ImageBase + rva := uint32(EnclaveConfigurationPointer - imageBase) + offset := pe.getOffsetFromRva(rva) + err := pe.structUnpack(&imgEnclaveCfg, offset, imgEnclaveCfgSize) + if err != nil { + return nil + } + enclave.Config = imgEnclaveCfg + } + + // Get the array of images that an enclave can import. + + val := reflect.ValueOf(enclave.Config) + ImportListRVA := val.FieldByName("ImportList").Interface().(uint32) + NumberOfImports := val.FieldByName("NumberOfImports").Interface().(uint32) + + offset := pe.getOffsetFromRva(ImportListRVA) + for i := uint32(0); i < NumberOfImports; i++ { + imgEncImp := ImageEnclaveImport{} + imgEncImpSize := uint32(binary.Size(imgEncImp)) + err := pe.structUnpack(&imgEncImp, offset, imgEncImpSize) + if err != nil { + return nil + } + + offset += imgEncImpSize + enclave.Imports = append(enclave.Imports, imgEncImp) + } + + return nil +} + +func (pe *File) getVolatileMetadata() *VolatileMetadata { + + volatileMeta := VolatileMetadata{} + imgVolatileMeta := ImageVolatileMetadata{} + rva := uint32(0) + + v := reflect.ValueOf(pe.LoadConfig.LoadCfgStruct) + if v.NumField() <= 41 { + return nil + } + + VolatileMetadataPointer := v.Field(41).Uint() + if VolatileMetadataPointer == 0 { + return nil + } + + if pe.Is32 { + imageBase := pe.NtHeader.OptionalHeader.(ImageOptionalHeader32).ImageBase + rva = uint32(VolatileMetadataPointer) - imageBase + } else { + imageBase := pe.NtHeader.OptionalHeader.(ImageOptionalHeader64).ImageBase + rva = uint32(VolatileMetadataPointer - imageBase) + } + + offset := pe.getOffsetFromRva(rva) + imgVolatileMetaSize := uint32(binary.Size(imgVolatileMeta)) + err := pe.structUnpack(&imgVolatileMeta, offset, imgVolatileMetaSize) + if err != nil { + return nil + } + volatileMeta.Struct = imgVolatileMeta + + if imgVolatileMeta.VolatileAccessTable != 0 && + imgVolatileMeta.VolatileAccessTableSize != 0 { + offset := pe.getOffsetFromRva(imgVolatileMeta.VolatileAccessTable) + for i := uint32(0); i < imgVolatileMeta.VolatileAccessTableSize/4; i++ { + accessRVA, err := pe.ReadUint32(offset) + if err != nil { + break + } + + volatileMeta.AccessRVATable = append(volatileMeta.AccessRVATable, accessRVA) + offset += 4 + } + } + + if imgVolatileMeta.VolatileInfoRangeTable != 0 && imgVolatileMeta.VolatileAccessTableSize != 0 { + offset := pe.getOffsetFromRva(imgVolatileMeta.VolatileInfoRangeTable) + rangeEntrySize := uint32(binary.Size(RangeTableEntry{})) + for i := uint32(0); i < imgVolatileMeta.VolatileAccessTableSize/rangeEntrySize; i++ { + entry := RangeTableEntry{} + err := pe.structUnpack(&entry, offset, rangeEntrySize) + if err != nil { + break + } + + volatileMeta.InfoRangeTable = append(volatileMeta.InfoRangeTable, entry) + offset += rangeEntrySize + } + } + + return &volatileMeta +} diff --git a/ntheader.go b/ntheader.go new file mode 100644 index 0000000..1fbc31c --- /dev/null +++ b/ntheader.go @@ -0,0 +1,584 @@ +// Copyright 2021 Saferwall. All rights reserved. +// Use of this source code is governed by Apache v2 license +// license that can be found in the LICENSE file. + +package pe + +import ( + "encoding/binary" +) + +// ImageNtHeader represents the PE header and is the general term for a structure +// named IMAGE_NT_HEADERS. +type ImageNtHeader struct { + // Signature is a DWORD containing the value 50h, 45h, 00h, 00h. + Signature uint32 + + // IMAGE_NT_HEADERS privdes a standard COFF header. It is located + // immediately after the PE signature. The COFF header provides the most + // general characteristics of a PE/COFF file, applicable to both object and + // executable files. It is represented with IMAGE_FILE_HEADER structure. + FileHeader ImageFileHeader + + // OptionalHeader is of type *OptionalHeader32 or *OptionalHeader64. + OptionalHeader interface{} +} + +// ImageFileHeader contains infos about the physical layout and properties of the +// file. +type ImageFileHeader struct { + // The number that identifies the type of target machine. + Machine uint16 + + // The number of sections. This indicates the size of the section table, + // which immediately follows the headers. + NumberOfSections uint16 + + // // The low 32 bits of the number of seconds since 00:00 January 1, 1970 + // (a C run-time time_t value), that indicates when the file was created. + TimeDateStamp uint32 + + // // The file offset of the COFF symbol table, or zero if no COFF symbol + // table is present. This value should be zero for an image because COFF + // debugging information is deprecated. + PointerToSymbolTable uint32 + + // The number of entries in the symbol table. This data can be used to + // locate the string table, which immediately follows the symbol table. + // This value should be zero for an image because COFF debugging information + // is deprecated. + NumberOfSymbols uint32 + + // The size of the optional header, which is required for executable files + // but not for object files. This value should be zero for an object file. + SizeOfOptionalHeader uint16 + + // The flags that indicate the attributes of the file. + Characteristics uint16 +} + +// ImageOptionalHeader32 represents the PE32 format structure of the optional header. +// PE32 contains this additional field, which is absent in PE32+. +type ImageOptionalHeader32 struct { + + // The unsigned integer that identifies the state of the image file. + // The most common number is 0x10B, which identifies it as a normal + // executable file. 0x107 identifies it as a ROM image, and 0x20B identifies + // it as a PE32+ executable. + Magic uint16 + + // Linker major version number. The VC++ linker sets this field to current + // version of Visual Studio. + MajorLinkerVersion uint8 + + // The linker minor version number. + MinorLinkerVersion uint8 + + // The size of the code (text) section, or the sum of all code sections + // if there are multiple sections. + SizeOfCode uint32 + + // The size of the initialized data section (held in the field SizeOfRawData + // of the respective section header), or the sum of all such sections if + // there are multiple data sections. + SizeOfInitializedData uint32 + + // The size of the uninitialized data section (BSS), or the sum of all + // such sections if there are multiple BSS sections. This data is not part + // of the disk file and does not have specific values, but the OS loader + // commits memory space for this data when the file is loaded. + SizeOfUninitializedData uint32 + + // The address of the entry point relative to the image base when the + // executable file is loaded into memory. For program images, this is the + // starting address. For device drivers, this is the address of the + // initialization function. An entry point is optional for DLLs. When no + // entry point is present, this field must be zero. For managed PE files, + // this value always points to the common language runtime invocation stub. + AddressOfEntryPoint uint32 + + // The address that is relative to the image base of the beginning-of-code + // section when it is loaded into memory. + BaseOfCode uint32 + + // The address that is relative to the image base of the beginning-of-data + // section when it is loaded into memory.This entry doesn’t exist in the + // 64-bit Optional header. + BaseOfData uint32 + + // The preferred address of the first byte of image when loaded into memory; + // must be a multiple of 64 K. The default for DLLs is 0x10000000. The + // default for Windows CE EXEs is 0x00010000. The default for Windows NT, + // Windows 2000, Windows XP, Windows 95, Windows 98, and Windows Me is + // 0x00400000. + ImageBase uint32 + + // The alignment (in bytes) of sections when they are loaded into memory. + // It must be greater than or equal to FileAlignment. The default is the + // page size for the architecture. + SectionAlignment uint32 + + // The alignment factor (in bytes) that is used to align the raw data of + // sections in the image file. The value should be a power of 2 between 512 + // and 64 K, inclusive. The default is 512. If the SectionAlignment is less + // than the architecture's page size, then FileAlignment must match + // SectionAlignment. + FileAlignment uint32 + + // The major version number of the required operating system. + MajorOperatingSystemVersion uint16 + + // The minor version number of the required operating system. + MinorOperatingSystemVersion uint16 + + // The major version number of the image. + MajorImageVersion uint16 + + // The minor version number of the image. + MinorImageVersion uint16 + + // The major version number of the subsystem. + MajorSubsystemVersion uint16 + + // The minor version number of the subsystem. + MinorSubsystemVersion uint16 + + // Reserved, must be zero. + Win32VersionValue uint32 + + // The size (in bytes) of the image, including all headers, as the image + // is loaded in memory. It must be a multiple of SectionAlignment. + SizeOfImage uint32 + + // The combined size of an MS-DOS stub, PE header, and section headers + // rounded up to a multiple of FileAlignment. + SizeOfHeaders uint32 + + // The image file checksum. The algorithm for computing the checksum is + // incorporated into IMAGHELP.DLL. The following are checked for validation + // at load time: all drivers, any DLL loaded at boot time, and any DLL + // that is loaded into a critical Windows process. + CheckSum uint32 + + // The subsystem that is required to run this image. + Subsystem uint16 + + // For more information, see DLL Characteristics later in this specification. + DllCharacteristics uint16 + + // Size of virtual memory to reserve for the initial thread’s stack. Only + // the SizeOfStackCommit field is committed; the rest is available in + // one-page increments. The default is 1MB for 32-bit images and 4MB for + // 64-bit images. + SizeOfStackReserve uint32 + + // Size of virtual memory initially committed for the initial thread’s + // stack. The default is one page (4KB) for 32-bit images and 16KB for + // 64-bit images. + SizeOfStackCommit uint32 + + // size of the local heap space to reserve. Only SizeOfHeapCommit is + // committed; the rest is made available one page at a time until the + // reserve size is reached. The default is 1MB for both 32-bit and 64-bit + // images. + SizeOfHeapReserve uint32 + + // Size of virtual memory initially committed for the process heap. The + // default is 4KB (one operating system memory page) for 32-bit images and + // 16KB for 64-bit images. + SizeOfHeapCommit uint32 + + // Reserved, must be zero. + LoaderFlags uint32 + + // Number of entries in the DataDirectory array; at least 16. Although it + // is theoretically possible to emit more than 16 data directories, all + // existing managed compilers emit exactly 16 data directories, with the + // 16th (last) data directory never used (reserved). + NumberOfRvaAndSizes uint32 + + // An array of 16 IMAGE_DATA_DIRECTORY structures. + DataDirectory [16]DataDirectory +} + +// ImageOptionalHeader64 represents the PE32+ format structure of the optional header. +type ImageOptionalHeader64 struct { + // The unsigned integer that identifies the state of the image file. + // The most common number is 0x10B, which identifies it as a normal + // executable file. 0x107 identifies it as a ROM image, and 0x20B identifies + // it as a PE32+ executable. + Magic uint16 + + // Linker major version number. The VC++ linker sets this field to current + // version of Visual Studio. + MajorLinkerVersion uint8 + + // The linker minor version number. + MinorLinkerVersion uint8 + + // The size of the code (text) section, or the sum of all code sections + // if there are multiple sections. + SizeOfCode uint32 + + // The size of the initialized data section (held in the field SizeOfRawData + // of the respective section header), or the sum of all such sections if + // there are multiple data sections. + SizeOfInitializedData uint32 + + // The size of the uninitialized data section (BSS), or the sum of all + // such sections if there are multiple BSS sections. This data is not part + // of the disk file and does not have specific values, but the OS loader + // commits memory space for this data when the file is loaded. + SizeOfUninitializedData uint32 + + // The address of the entry point relative to the image base when the + // executable file is loaded into memory. For program images, this is the + // starting address. For device drivers, this is the address of the + // initialization function. An entry point is optional for DLLs. When no + // entry point is present, this field must be zero. For managed PE files, + // this value always points to the common language runtime invocation stub. + AddressOfEntryPoint uint32 + + // The address that is relative to the image base of the beginning-of-code + // section when it is loaded into memory. + BaseOfCode uint32 + + // In PE+, ImageBase is 8 bytes size. + ImageBase uint64 + + // The alignment (in bytes) of sections when they are loaded into memory. + // It must be greater than or equal to FileAlignment. The default is the + // page size for the architecture. + SectionAlignment uint32 + + // The alignment factor (in bytes) that is used to align the raw data of + // sections in the image file. The value should be a power of 2 between 512 + // and 64 K, inclusive. The default is 512. If the SectionAlignment is less + // than the architecture's page size, then FileAlignment must match SectionAlignment. + FileAlignment uint32 + + // The major version number of the required operating system. + MajorOperatingSystemVersion uint16 + + // The minor version number of the required operating system. + MinorOperatingSystemVersion uint16 + + // The major version number of the image. + MajorImageVersion uint16 + + // The minor version number of the image. + MinorImageVersion uint16 + + // The major version number of the subsystem. + MajorSubsystemVersion uint16 + + // The minor version number of the subsystem. + MinorSubsystemVersion uint16 + + // Reserved, must be zero. + Win32VersionValue uint32 + + // The size (in bytes) of the image, including all headers, as the image + // is loaded in memory. It must be a multiple of SectionAlignment. + SizeOfImage uint32 + + // The combined size of an MS-DOS stub, PE header, and section headers + // rounded up to a multiple of FileAlignment. + SizeOfHeaders uint32 + + // The image file checksum. The algorithm for computing the checksum is + // incorporated into IMAGHELP.DLL. The following are checked for validation + // at load time: all drivers, any DLL loaded at boot time, and any DLL + // that is loaded into a critical Windows process. + CheckSum uint32 + + // The subsystem that is required to run this image. + Subsystem uint16 + + // For more information, see DLL Characteristics later in this specification. + DllCharacteristics uint16 + + // Size of virtual memory to reserve for the initial thread’s stack. Only + // the SizeOfStackCommit field is committed; the rest is available in + // one-page increments. The default is 1MB for 32-bit images and 4MB for + // 64-bit images. + SizeOfStackReserve uint64 + + // Size of virtual memory initially committed for the initial thread’s + // stack. The default is one page (4KB) for 32-bit images and 16KB for + // 64-bit images. + SizeOfStackCommit uint64 + + // size of the local heap space to reserve. Only SizeOfHeapCommit is + // committed; the rest is made available one page at a time until the + // reserve size is reached. The default is 1MB for both 32-bit and 64-bit + // images. + SizeOfHeapReserve uint64 + + // Size of virtual memory initially committed for the process heap. The + // default is 4KB (one operating system memory page) for 32-bit images and + // 16KB for 64-bit images. + SizeOfHeapCommit uint64 + + // Reserved, must be zero. + LoaderFlags uint32 + + // Number of entries in the DataDirectory array; at least 16. Although it + // is theoretically possible to emit more than 16 data directories, all + // existing managed compilers emit exactly 16 data directories, with the + // 16th (last) data directory never used (reserved). + NumberOfRvaAndSizes uint32 + + // An array of 16 IMAGE_DATA_DIRECTORY structures. + DataDirectory [16]DataDirectory +} + +// DataDirectory represents an array of 16 IMAGE_DATA_DIRECTORY structures, +// 8 bytes apiece, each relating to an important data structure in the PE file. +// The data directory table starts at offset 96 in a 32-bit PE header and at +// offset 112 in a 64-bit PE header. Each entry in the data directory table +// contains the RVA and size of a table or a string that this particular +// directory entry describes;this information is used by the operating system. +type DataDirectory struct { + VirtualAddress uint32 // The RVA of the data structure. + Size uint32 // The size in bytes of the data structure refered to. +} + +// ParseNTHeader parse the PE NT header structure refered as IMAGE_NT_HEADERS. +// Its offset is given by the e_lfanew field in the IMAGE_DOS_HEADER at the +// beginning of the file. +func (pe *File) ParseNTHeader() (err error) { + ntHeaderOffset := pe.DosHeader.AddressOfNewEXEHeader + signature := binary.LittleEndian.Uint32(pe.data[ntHeaderOffset:]) + + // Probe for PE signature. + if signature&0xFFFF == ImageOS2Signature { + return ErrImageOS2SignatureFound + } + if signature&0xFFFF == ImageOS2LESignature { + return ErrImageOS2LESignatureFound + } + if signature&0xFFFF == ImageVXDSignature { + return ErrImageVXDSignatureFound + } + if signature&0xFFFF == ImageTESignature { + return ErrImageTESignatureFound + } + + // This is the smallest requirement for a valid PE. + if signature != ImageNTSignature { + return ErrImageNtSignatureNotFound + } + pe.NtHeader.Signature = signature + + // The file header structure contains some basic information about the file; + // most importantly, a field describing the size of the optional data that + // follows it. + fileHeaderSize := uint32(binary.Size(pe.NtHeader.FileHeader)) + fileHeaderOffset := ntHeaderOffset + 4 + err = pe.structUnpack(&pe.NtHeader.FileHeader, fileHeaderOffset, fileHeaderSize) + if err != nil { + return err + } + + // The PE header which immediately follows the COFF header, provides + // information for the OS loader. Although this header is referred to as + // the optional header, it is optional only in the sense that object files + // usually don’t contain it. For PE files, this header is mandatory. + // The size of the PE header is not fixed. It depends on the number of data + // directories defined in the header and is specified in the + // SizeOfOptionalHeader field of the COFF header. + // The optional header could be either for a PE or PE+ file. + oh32 := ImageOptionalHeader32{} + oh64 := ImageOptionalHeader64{} + + optHeaderOffset := ntHeaderOffset + (fileHeaderSize + 4) + magic, err := pe.ReadUint16(optHeaderOffset) + if err != nil { + return err + } + + // Probes for PE32/PE32+ optional header magic. + if magic != ImageNtOptionalHeader32Magic && + magic != ImageNtOptionalHeader64Magic { + return ErrImageNtOptionalHeaderMagicNotFound + } + + // Are we dealing with a PE64 optional header. + switch magic { + case ImageNtOptionalHeader64Magic: + size := uint32(binary.Size(oh64)) + err = pe.structUnpack(&oh64, optHeaderOffset, size) + if err != nil { + return err + } + pe.Is64 = true + pe.NtHeader.OptionalHeader = oh64 + case ImageNtOptionalHeader32Magic: + size := uint32(binary.Size(oh32)) + err = pe.structUnpack(&oh32, optHeaderOffset, size) + if err != nil { + return err + } + pe.Is32 = true + pe.NtHeader.OptionalHeader = oh32 + } + + // ImageBase should be multiple of 10000h. + if pe.Is64 && oh64.ImageBase%0x10000 != 0 { + return ErrImageBaseNotAligned + } + if pe.Is32 && oh32.ImageBase%0x10000 != 0 { + return ErrImageBaseNotAligned + } + + // ImageBase can be any value as long as: + // ImageBase + SizeOfImage < 80000000h for PE32. + if (pe.Is32 && oh32.ImageBase+oh32.SizeOfImage >= 0x80000000) || (pe.Is64 && + oh64.ImageBase+uint64(oh64.SizeOfImage) >= 0xffff080000000000) { + pe.Anomalies = append(pe.Anomalies, AnoImageBaseOverflow) + } + + // The msdn states that SizeOfImage must be a multiple of the section + // alignment. This is not true though. Adding it as anomaly. + if (pe.Is32 && oh32.SizeOfImage%oh32.SectionAlignment != 0) || + (pe.Is64 && oh64.SizeOfImage%oh64.SectionAlignment != 0) { + pe.Anomalies = append(pe.Anomalies, AnoInvalidSizeOfImage) + } + + return nil +} + +// PrettyMachineType returns the string representations +// of the `Machine` field of the IMAGE_FILE_HEADER. +func (pe *File) PrettyMachineType() string { + machineType := map[uint16]string{ + ImageFileMachineUnknown: "Unknown", + ImageFileMachineAM33: "Matsushita AM33", + ImageFileMachineAMD64: "x64", + ImageFileMachineARM: "ARM little endian", + ImageFileMachineARM64: "ARM64 little endian", + ImageFileMachineARMNT: "ARM Thumb-2 little endian", + ImageFileMachineEBC: "EFI byte code", + ImageFileMachineI386: "Intel 386 or later / compatible processors", + ImageFileMachineIA64: "Intel Itanium processor family", + ImageFileMachineM32R: "Mitsubishi M32R little endian", + ImageFileMachineMIPS16: "MIPS16", + ImageFileMachineMIPSFPU: "MIPS with FPU", + ImageFileMachineMIPSFPU16: "MIPS16 with FPU", + ImageFileMachinePowerPC: "Power PC little endian", + ImageFileMachinePowerPCFP: "Power PC with floating point support", + ImageFileMachineR4000: "MIPS little endian", + ImageFileMachineRISCV32: "RISC-V 32-bit address space", + ImageFileMachineRISCV64: "RISC-V 64-bit address space", + ImageFileMachineRISCV128: "RISC-V 128-bit address space", + ImageFileMachineSH3: "Hitachi SH3", + ImageFileMachineSH3DSP: "Hitachi SH3 DSP", + ImageFileMachineSH4: "Hitachi SH4", + ImageFileMachineSH5: "Hitachi SH5", + ImageFileMachineTHUMB: "Thumb", + ImageFileMachineWCEMIPSv2: "MIPS little-endian WCE v2", + } + + return machineType[pe.NtHeader.FileHeader.Machine] +} + +// PrettyImageFileCharacteristics returns the string representations +// of the `Characteristics` field of the IMAGE_FILE_HEADER. +func (pe *File) PrettyImageFileCharacteristics() []string { + var values []string + fileHeaderCharacteristics := map[uint16]string{ + ImageFileRelocsStripped: "RelocsStripped", + ImageFileExecutableImage: "ExecutableImage", + ImageFileLineNumsStripped: "LineNumsStripped", + ImageFileLocalSymsStripped: "LocalSymsStripped", + ImageFileAgressibeWsTrim: "AgressibeWsTrim", + ImageFileLargeAddressAware: "LargeAddressAware", + ImageFileBytesReservedLow: "BytesReservedLow", + ImageFile32BitMachine: "32BitMachine", + ImageFileDebugStripped: "DebugStripped", + ImageFileRemovableRunFromSwap: "RemovableRunFromSwap", + ImageFileSystem: "FileSystem", + ImageFileDLL: "DLL", + ImageFileUpSystemOnly: "UpSystemOnly", + ImageFileBytesReservedHigh: "BytesReservedHigh", + } + + for k, s := range fileHeaderCharacteristics { + if k&pe.NtHeader.FileHeader.Characteristics != 0 { + values = append(values, s) + } + } + return values +} + +// PrettyDllCharacteristics returns the string representations +// of the `DllCharacteristics` field of ImageOptionalHeader. +func (pe *File) PrettyDllCharacteristics() []string { + var values []string + var characteristics uint16 + + if pe.Is64 { + characteristics = + pe.NtHeader.OptionalHeader.(ImageOptionalHeader64).DllCharacteristics + } else { + characteristics = + pe.NtHeader.OptionalHeader.(ImageOptionalHeader32).DllCharacteristics + } + + imgDllCharacteristics := map[uint16]string{ + ImageDllCharacteristicsHighEntropyVA: "HighEntropyVA", + ImageDllCharacteristicsDynamicBase: "DynamicBase", + ImageDllCharacteristicsForceIntegrity: "ForceIntegrity", + ImageDllCharacteristicsNXCompact: "NXCompact", + ImageDllCharacteristicsNoIsolation: "NoIsolation", + ImageDllCharacteristicsNoSEH: "NoSEH", + ImageDllCharacteristicsNoBind: "NoBind", + ImageDllCharacteristicsAppContainer: "AppContainer", + ImageDllCharacteristicsWdmDriver: "WdmDriver", + ImageDllCharacteristicsGuardCF: "GuardCF", + ImageDllCharacteristicsTerminalServiceAware: "TerminalServiceAware", + } + + for k, s := range imgDllCharacteristics { + if k&characteristics != 0 { + values = append(values, s) + } + } + + return values +} + +// PrettySubsystem returns the string representations +// of the `Subsystem` field of ImageOptionalHeader. +func (pe *File) PrettySubsystem() string { + + var subsystem uint16 + + if pe.Is64 { + subsystem = + pe.NtHeader.OptionalHeader.(ImageOptionalHeader64).Subsystem + } else { + subsystem = + pe.NtHeader.OptionalHeader.(ImageOptionalHeader32).Subsystem + } + + subsystemMap := map[uint16]string{ + ImageSubsystemUnknown: "Unknown", + ImageSubsystemNative: "Native", + ImageSubsystemWindowsGUI: "Windows GUI", + ImageSubsystemWindowsCUI: "Windows CUI", + ImageSubsystemOS2CUI: "OS/2 character", + ImageSubsystemPosixCUI: "POSIX character", + ImageSubsystemNativeWindows: "Native Win9x driver", + ImageSubsystemWindowsCEGUI: "Windows CE GUI", + ImageSubsystemEFIApplication: "EFI Application", + ImageSubsystemEFIBootServiceDriver: "EFI Boot Service Driver", + ImageSubsystemEFIRuntimeDriver: "EFI ROM image", + ImageSubsystemEFIRom: "EFI ROM image", + ImageSubsystemXBOX: "XBOX", + ImageSubsystemWindowsBootApplication: "Windows boot application", + } + + return subsystemMap[subsystem] +} diff --git a/ordlookup.go b/ordlookup.go new file mode 100644 index 0000000..d94a612 --- /dev/null +++ b/ordlookup.go @@ -0,0 +1,554 @@ +// Copyright 2021 Saferwall. All rights reserved. +// Use of this source code is governed by Apache v2 license +// license that can be found in the LICENSE file. + +package pe + +import ( + "fmt" + "strings" +) + +// WS232OrdNames maps ordinals to name. +var WS232OrdNames = map[uint64]string{ + 1: "accept", + 2: "bind", + 3: "closesocket", + 4: "connect", + 5: "getpeername", + 6: "getsockname", + 7: "getsockopt", + 8: "htonl", + 9: "htons", + 10: "ioctlsocket", + 11: "inet_addr", + 12: "inet_ntoa", + 13: "listen", + 14: "ntohl", + 15: "ntohs", + 16: "recv", + 17: "recvfrom", + 18: "select", + 19: "send", + 20: "sendto", + 21: "setsockopt", + 22: "shutdown", + 23: "socket", + 24: "GetAddrInfoW", + 25: "GetNameInfoW", + 26: "WSApSetPostRoutine", + 27: "FreeAddrInfoW", + 28: "WPUCompleteOverlappedRequest", + 29: "WSAAccept", + 30: "WSAAddressToStringA", + 31: "WSAAddressToStringW", + 32: "WSACloseEvent", + 33: "WSAConnect", + 34: "WSACreateEvent", + 35: "WSADuplicateSocketA", + 36: "WSADuplicateSocketW", + 37: "WSAEnumNameSpaceProvidersA", + 38: "WSAEnumNameSpaceProvidersW", + 39: "WSAEnumNetworkEvents", + 40: "WSAEnumProtocolsA", + 41: "WSAEnumProtocolsW", + 42: "WSAEventSelect", + 43: "WSAGetOverlappedResult", + 44: "WSAGetQOSByName", + 45: "WSAGetServiceClassInfoA", + 46: "WSAGetServiceClassInfoW", + 47: "WSAGetServiceClassNameByClassIdA", + 48: "WSAGetServiceClassNameByClassIdW", + 49: "WSAHtonl", + 50: "WSAHtons", + 51: "gethostbyaddr", + 52: "gethostbyname", + 53: "getprotobyname", + 54: "getprotobynumber", + 55: "getservbyname", + 56: "getservbyport", + 57: "gethostname", + 58: "WSAInstallServiceClassA", + 59: "WSAInstallServiceClassW", + 60: "WSAIoctl", + 61: "WSAJoinLeaf", + 62: "WSALookupServiceBeginA", + 63: "WSALookupServiceBeginW", + 64: "WSALookupServiceEnd", + 65: "WSALookupServiceNextA", + 66: "WSALookupServiceNextW", + 67: "WSANSPIoctl", + 68: "WSANtohl", + 69: "WSANtohs", + 70: "WSAProviderConfigChange", + 71: "WSARecv", + 72: "WSARecvDisconnect", + 73: "WSARecvFrom", + 74: "WSARemoveServiceClass", + 75: "WSAResetEvent", + 76: "WSASend", + 77: "WSASendDisconnect", + 78: "WSASendTo", + 79: "WSASetEvent", + 80: "WSASetServiceA", + 81: "WSASetServiceW", + 82: "WSASocketA", + 83: "WSASocketW", + 84: "WSAStringToAddressA", + 85: "WSAStringToAddressW", + 86: "WSAWaitForMultipleEvents", + 87: "WSCDeinstallProvider", + 88: "WSCEnableNSProvider", + 89: "WSCEnumProtocols", + 90: "WSCGetProviderPath", + 91: "WSCInstallNameSpace", + 92: "WSCInstallProvider", + 93: "WSCUnInstallNameSpace", + 94: "WSCUpdateProvider", + 95: "WSCWriteNameSpaceOrder", + 96: "WSCWriteProviderOrder", + 97: "freeaddrinfo", + 98: "getaddrinfo", + 99: "getnameinfo", + 101: "WSAAsyncSelect", + 102: "WSAAsyncGetHostByAddr", + 103: "WSAAsyncGetHostByName", + 104: "WSAAsyncGetProtoByNumber", + 105: "WSAAsyncGetProtoByName", + 106: "WSAAsyncGetServByPort", + 107: "WSAAsyncGetServByName", + 108: "WSACancelAsyncRequest", + 109: "WSASetBlockingHook", + 110: "WSAUnhookBlockingHook", + 111: "WSAGetLastError", + 112: "WSASetLastError", + 113: "WSACancelBlockingCall", + 114: "WSAIsBlocking", + 115: "WSAStartup", + 116: "WSACleanup", + 151: "__WSAFDIsSet", + 500: "WEP", +} + +// OleAut32OrdNames maps ordinals to names. +var OleAut32OrdNames = map[uint64]string{ + 2: "SysAllocString", + 3: "SysReAllocString", + 4: "SysAllocStringLen", + 5: "SysReAllocStringLen", + 6: "SysFreeString", + 7: "SysStringLen", + 8: "VariantInit", + 9: "VariantClear", + 10: "VariantCopy", + 11: "VariantCopyInd", + 12: "VariantChangeType", + 13: "VariantTimeToDosDateTime", + 14: "DosDateTimeToVariantTime", + 15: "SafeArrayCreate", + 16: "SafeArrayDestroy", + 17: "SafeArrayGetDim", + 18: "SafeArrayGetElemsize", + 19: "SafeArrayGetUBound", + 20: "SafeArrayGetLBound", + 21: "SafeArrayLock", + 22: "SafeArrayUnlock", + 23: "SafeArrayAccessData", + 24: "SafeArrayUnaccessData", + 25: "SafeArrayGetElement", + 26: "SafeArrayPutElement", + 27: "SafeArrayCopy", + 28: "DispGetParam", + 29: "DispGetIDsOfNames", + 30: "DispInvoke", + 31: "CreateDispTypeInfo", + 32: "CreateStdDispatch", + 33: "RegisterActiveObject", + 34: "RevokeActiveObject", + 35: "GetActiveObject", + 36: "SafeArrayAllocDescriptor", + 37: "SafeArrayAllocData", + 38: "SafeArrayDestroyDescriptor", + 39: "SafeArrayDestroyData", + 40: "SafeArrayRedim", + 41: "SafeArrayAllocDescriptorEx", + 42: "SafeArrayCreateEx", + 43: "SafeArrayCreateVectorEx", + 44: "SafeArraySetRecordInfo", + 45: "SafeArrayGetRecordInfo", + 46: "VarParseNumFromStr", + 47: "VarNumFromParseNum", + 48: "VarI2FromUI1", + 49: "VarI2FromI4", + 50: "VarI2FromR4", + 51: "VarI2FromR8", + 52: "VarI2FromCy", + 53: "VarI2FromDate", + 54: "VarI2FromStr", + 55: "VarI2FromDisp", + 56: "VarI2FromBool", + 57: "SafeArraySetIID", + 58: "VarI4FromUI1", + 59: "VarI4FromI2", + 60: "VarI4FromR4", + 61: "VarI4FromR8", + 62: "VarI4FromCy", + 63: "VarI4FromDate", + 64: "VarI4FromStr", + 65: "VarI4FromDisp", + 66: "VarI4FromBool", + 67: "SafeArrayGetIID", + 68: "VarR4FromUI1", + 69: "VarR4FromI2", + 70: "VarR4FromI4", + 71: "VarR4FromR8", + 72: "VarR4FromCy", + 73: "VarR4FromDate", + 74: "VarR4FromStr", + 75: "VarR4FromDisp", + 76: "VarR4FromBool", + 77: "SafeArrayGetVartype", + 78: "VarR8FromUI1", + 79: "VarR8FromI2", + 80: "VarR8FromI4", + 81: "VarR8FromR4", + 82: "VarR8FromCy", + 83: "VarR8FromDate", + 84: "VarR8FromStr", + 85: "VarR8FromDisp", + 86: "VarR8FromBool", + 87: "VarFormat", + 88: "VarDateFromUI1", + 89: "VarDateFromI2", + 90: "VarDateFromI4", + 91: "VarDateFromR4", + 92: "VarDateFromR8", + 93: "VarDateFromCy", + 94: "VarDateFromStr", + 95: "VarDateFromDisp", + 96: "VarDateFromBool", + 97: "VarFormatDateTime", + 98: "VarCyFromUI1", + 99: "VarCyFromI2", + 100: "VarCyFromI4", + 101: "VarCyFromR4", + 102: "VarCyFromR8", + 103: "VarCyFromDate", + 104: "VarCyFromStr", + 105: "VarCyFromDisp", + 106: "VarCyFromBool", + 107: "VarFormatNumber", + 108: "VarBstrFromUI1", + 109: "VarBstrFromI2", + 110: "VarBstrFromI4", + 111: "VarBstrFromR4", + 112: "VarBstrFromR8", + 113: "VarBstrFromCy", + 114: "VarBstrFromDate", + 115: "VarBstrFromDisp", + 116: "VarBstrFromBool", + 117: "VarFormatPercent", + 118: "VarBoolFromUI1", + 119: "VarBoolFromI2", + 120: "VarBoolFromI4", + 121: "VarBoolFromR4", + 122: "VarBoolFromR8", + 123: "VarBoolFromDate", + 124: "VarBoolFromCy", + 125: "VarBoolFromStr", + 126: "VarBoolFromDisp", + 127: "VarFormatCurrency", + 128: "VarWeekdayName", + 129: "VarMonthName", + 130: "VarUI1FromI2", + 131: "VarUI1FromI4", + 132: "VarUI1FromR4", + 133: "VarUI1FromR8", + 134: "VarUI1FromCy", + 135: "VarUI1FromDate", + 136: "VarUI1FromStr", + 137: "VarUI1FromDisp", + 138: "VarUI1FromBool", + 139: "VarFormatFromTokens", + 140: "VarTokenizeFormatString", + 141: "VarAdd", + 142: "VarAnd", + 143: "VarDiv", + 144: "DllCanUnloadNow", + 145: "DllGetClassObject", + 146: "DispCallFunc", + 147: "VariantChangeTypeEx", + 148: "SafeArrayPtrOfIndex", + 149: "SysStringByteLen", + 150: "SysAllocStringByteLen", + 151: "DllRegisterServer", + 152: "VarEqv", + 153: "VarIdiv", + 154: "VarImp", + 155: "VarMod", + 156: "VarMul", + 157: "VarOr", + 158: "VarPow", + 159: "VarSub", + 160: "CreateTypeLib", + 161: "LoadTypeLib", + 162: "LoadRegTypeLib", + 163: "RegisterTypeLib", + 164: "QueryPathOfRegTypeLib", + 165: "LHashValOfNameSys", + 166: "LHashValOfNameSysA", + 167: "VarXor", + 168: "VarAbs", + 169: "VarFix", + 170: "OaBuildVersion", + 171: "ClearCustData", + 172: "VarInt", + 173: "VarNeg", + 174: "VarNot", + 175: "VarRound", + 176: "VarCmp", + 177: "VarDecAdd", + 178: "VarDecDiv", + 179: "VarDecMul", + 180: "CreateTypeLib2", + 181: "VarDecSub", + 182: "VarDecAbs", + 183: "LoadTypeLibEx", + 184: "SystemTimeToVariantTime", + 185: "VariantTimeToSystemTime", + 186: "UnRegisterTypeLib", + 187: "VarDecFix", + 188: "VarDecInt", + 189: "VarDecNeg", + 190: "VarDecFromUI1", + 191: "VarDecFromI2", + 192: "VarDecFromI4", + 193: "VarDecFromR4", + 194: "VarDecFromR8", + 195: "VarDecFromDate", + 196: "VarDecFromCy", + 197: "VarDecFromStr", + 198: "VarDecFromDisp", + 199: "VarDecFromBool", + 200: "GetErrorInfo", + 201: "SetErrorInfo", + 202: "CreateErrorInfo", + 203: "VarDecRound", + 204: "VarDecCmp", + 205: "VarI2FromI1", + 206: "VarI2FromUI2", + 207: "VarI2FromUI4", + 208: "VarI2FromDec", + 209: "VarI4FromI1", + 210: "VarI4FromUI2", + 211: "VarI4FromUI4", + 212: "VarI4FromDec", + 213: "VarR4FromI1", + 214: "VarR4FromUI2", + 215: "VarR4FromUI4", + 216: "VarR4FromDec", + 217: "VarR8FromI1", + 218: "VarR8FromUI2", + 219: "VarR8FromUI4", + 220: "VarR8FromDec", + 221: "VarDateFromI1", + 222: "VarDateFromUI2", + 223: "VarDateFromUI4", + 224: "VarDateFromDec", + 225: "VarCyFromI1", + 226: "VarCyFromUI2", + 227: "VarCyFromUI4", + 228: "VarCyFromDec", + 229: "VarBstrFromI1", + 230: "VarBstrFromUI2", + 231: "VarBstrFromUI4", + 232: "VarBstrFromDec", + 233: "VarBoolFromI1", + 234: "VarBoolFromUI2", + 235: "VarBoolFromUI4", + 236: "VarBoolFromDec", + 237: "VarUI1FromI1", + 238: "VarUI1FromUI2", + 239: "VarUI1FromUI4", + 240: "VarUI1FromDec", + 241: "VarDecFromI1", + 242: "VarDecFromUI2", + 243: "VarDecFromUI4", + 244: "VarI1FromUI1", + 245: "VarI1FromI2", + 246: "VarI1FromI4", + 247: "VarI1FromR4", + 248: "VarI1FromR8", + 249: "VarI1FromDate", + 250: "VarI1FromCy", + 251: "VarI1FromStr", + 252: "VarI1FromDisp", + 253: "VarI1FromBool", + 254: "VarI1FromUI2", + 255: "VarI1FromUI4", + 256: "VarI1FromDec", + 257: "VarUI2FromUI1", + 258: "VarUI2FromI2", + 259: "VarUI2FromI4", + 260: "VarUI2FromR4", + 261: "VarUI2FromR8", + 262: "VarUI2FromDate", + 263: "VarUI2FromCy", + 264: "VarUI2FromStr", + 265: "VarUI2FromDisp", + 266: "VarUI2FromBool", + 267: "VarUI2FromI1", + 268: "VarUI2FromUI4", + 269: "VarUI2FromDec", + 270: "VarUI4FromUI1", + 271: "VarUI4FromI2", + 272: "VarUI4FromI4", + 273: "VarUI4FromR4", + 274: "VarUI4FromR8", + 275: "VarUI4FromDate", + 276: "VarUI4FromCy", + 277: "VarUI4FromStr", + 278: "VarUI4FromDisp", + 279: "VarUI4FromBool", + 280: "VarUI4FromI1", + 281: "VarUI4FromUI2", + 282: "VarUI4FromDec", + 283: "BSTR_UserSize", + 284: "BSTR_UserMarshal", + 285: "BSTR_UserUnmarshal", + 286: "BSTR_UserFree", + 287: "VARIANT_UserSize", + 288: "VARIANT_UserMarshal", + 289: "VARIANT_UserUnmarshal", + 290: "VARIANT_UserFree", + 291: "LPSAFEARRAY_UserSize", + 292: "LPSAFEARRAY_UserMarshal", + 293: "LPSAFEARRAY_UserUnmarshal", + 294: "LPSAFEARRAY_UserFree", + 295: "LPSAFEARRAY_Size", + 296: "LPSAFEARRAY_Marshal", + 297: "LPSAFEARRAY_Unmarshal", + 298: "VarDecCmpR8", + 299: "VarCyAdd", + 300: "DllUnregisterServer", + 301: "OACreateTypeLib2", + 303: "VarCyMul", + 304: "VarCyMulI4", + 305: "VarCySub", + 306: "VarCyAbs", + 307: "VarCyFix", + 308: "VarCyInt", + 309: "VarCyNeg", + 310: "VarCyRound", + 311: "VarCyCmp", + 312: "VarCyCmpR8", + 313: "VarBstrCat", + 314: "VarBstrCmp", + 315: "VarR8Pow", + 316: "VarR4CmpR8", + 317: "VarR8Round", + 318: "VarCat", + 319: "VarDateFromUdateEx", + 322: "GetRecordInfoFromGuids", + 323: "GetRecordInfoFromTypeInfo", + 325: "SetVarConversionLocaleSetting", + 326: "GetVarConversionLocaleSetting", + 327: "SetOaNoCache", + 329: "VarCyMulI8", + 330: "VarDateFromUdate", + 331: "VarUdateFromDate", + 332: "GetAltMonthNames", + 333: "VarI8FromUI1", + 334: "VarI8FromI2", + 335: "VarI8FromR4", + 336: "VarI8FromR8", + 337: "VarI8FromCy", + 338: "VarI8FromDate", + 339: "VarI8FromStr", + 340: "VarI8FromDisp", + 341: "VarI8FromBool", + 342: "VarI8FromI1", + 343: "VarI8FromUI2", + 344: "VarI8FromUI4", + 345: "VarI8FromDec", + 346: "VarI2FromI8", + 347: "VarI2FromUI8", + 348: "VarI4FromI8", + 349: "VarI4FromUI8", + 360: "VarR4FromI8", + 361: "VarR4FromUI8", + 362: "VarR8FromI8", + 363: "VarR8FromUI8", + 364: "VarDateFromI8", + 365: "VarDateFromUI8", + 366: "VarCyFromI8", + 367: "VarCyFromUI8", + 368: "VarBstrFromI8", + 369: "VarBstrFromUI8", + 370: "VarBoolFromI8", + 371: "VarBoolFromUI8", + 372: "VarUI1FromI8", + 373: "VarUI1FromUI8", + 374: "VarDecFromI8", + 375: "VarDecFromUI8", + 376: "VarI1FromI8", + 377: "VarI1FromUI8", + 378: "VarUI2FromI8", + 379: "VarUI2FromUI8", + 401: "OleLoadPictureEx", + 402: "OleLoadPictureFileEx", + 411: "SafeArrayCreateVector", + 412: "SafeArrayCopyData", + 413: "VectorFromBstr", + 414: "BstrFromVector", + 415: "OleIconToCursor", + 416: "OleCreatePropertyFrameIndirect", + 417: "OleCreatePropertyFrame", + 418: "OleLoadPicture", + 419: "OleCreatePictureIndirect", + 420: "OleCreateFontIndirect", + 421: "OleTranslateColor", + 422: "OleLoadPictureFile", + 423: "OleSavePictureFile", + 424: "OleLoadPicturePath", + 425: "VarUI4FromI8", + 426: "VarUI4FromUI8", + 427: "VarI8FromUI8", + 428: "VarUI8FromI8", + 429: "VarUI8FromUI1", + 430: "VarUI8FromI2", + 431: "VarUI8FromR4", + 432: "VarUI8FromR8", + 433: "VarUI8FromCy", + 434: "VarUI8FromDate", + 435: "VarUI8FromStr", + 436: "VarUI8FromDisp", + 437: "VarUI8FromBool", + 438: "VarUI8FromI1", + 439: "VarUI8FromUI2", + 440: "VarUI8FromUI4", + 441: "VarUI8FromDec", + 442: "RegisterTypeLibForUser", + 443: "UnRegisterTypeLibForUser", +} + +// OrdNames maps the dll names to ordinal names. +var OrdNames = map[string]map[uint64]string{ + "ws2_32.dll": WS232OrdNames, + "wsock32.dll": WS232OrdNames, + "oleaut32.dll": OleAut32OrdNames, +} + +// OrdLookup returns API name given an ordinal. +func OrdLookup(libname string, ord uint64, makeName bool) string { + names, ok := OrdNames[strings.ToLower(libname)] + if ok { + if name, ok := names[ord]; ok { + return name + } + } + if makeName { + return fmt.Sprintf("ord%d", ord) + } + return "" +} diff --git a/pe.go b/pe.go new file mode 100644 index 0000000..fff2dee --- /dev/null +++ b/pe.go @@ -0,0 +1,198 @@ +// Copyright 2021 Saferwall. All rights reserved. +// Use of this source code is governed by Apache v2 license +// license that can be found in the LICENSE file. + +package pe + +// Image executable types +const ( + + // The DOS MZ executable format is the executable file format used + // for .EXE files in DOS. + ImageDOSSignature = 0x5A4D // MZ + ImageDOSZMSignature = 0x4D5A // ZM + + // The New Executable (abbreviated NE or NewEXE) is a 16-bit .exe file + // format, a successor to the DOS MZ executable format. It was used in + // Windows 1.0–3.x, multitasking MS-DOS 4.0, OS/2 1.x, and the OS/2 subset + // of Windows NT up to version 5.0 (Windows 2000). A NE is also called a + // segmented executable. + ImageOS2Signature = 0x454E + + // Linear Executable is an executable file format in the EXE family. + // It was used by 32-bit OS/2, by some DOS extenders, and by Microsoft + // Windows VxD files. It is an extension of MS-DOS EXE, and a successor + // to NE (New Executable). + ImageOS2LESignature = 0x454C + + // There are two main varieties of LE executables: + // LX (32-bit), and LE (mixed 16/32-bit). + ImageVXDSignature = 0x584C + + // Terse Executables have a 'VZ' signature. + ImageTESignature = 0x5A56 + + // The Portable Executable (PE) format is a file format for executables, + // object code, DLLs and others used in 32-bit and 64-bit versions of + // Windows operating systems. + ImageNTSignature = 0x00004550 // PE00 +) + +// Optional Header magic +const ( + ImageNtOptionalHeader32Magic = 0x10b + ImageNtOptionalHeader64Magic = 0x20b + ImageROMOptionalHeaderMagic = 0x10 +) + +// Image file machine types +const ( + ImageFileMachineUnknown = uint16(0x0) // The contents of this field are assumed to be applicable to any machine type + ImageFileMachineAM33 = uint16(0x1d3) // Matsushita AM33 + ImageFileMachineAMD64 = uint16(0x8664) // x64 + ImageFileMachineARM = uint16(0x1c0) // ARM little endian + ImageFileMachineARM64 = uint16(0xaa64) // ARM64 little endian + ImageFileMachineARMNT = uint16(0x1c4) // ARM Thumb-2 little endian + ImageFileMachineEBC = uint16(0xebc) // EFI byte code + ImageFileMachineI386 = uint16(0x14c) // Intel 386 or later processors and compatible processors + ImageFileMachineIA64 = uint16(0x200) // Intel Itanium processor family + ImageFileMachineM32R = uint16(0x9041) // Mitsubishi M32R little endian + ImageFileMachineMIPS16 = uint16(0x266) // MIPS16 + ImageFileMachineMIPSFPU = uint16(0x366) // MIPS with FPU + ImageFileMachineMIPSFPU16 = uint16(0x466) // MIPS16 with FPU + ImageFileMachinePowerPC = uint16(0x1f0) // Power PC little endian + ImageFileMachinePowerPCFP = uint16(0x1f1) // Power PC with floating point support + ImageFileMachineR4000 = uint16(0x166) // MIPS little endian + ImageFileMachineRISCV32 = uint16(0x5032) // RISC-V 32-bit address space + ImageFileMachineRISCV64 = uint16(0x5064) // RISC-V 64-bit address space + ImageFileMachineRISCV128 = uint16(0x5128) // RISC-V 128-bit address space + ImageFileMachineSH3 = uint16(0x1a2) // Hitachi SH3 + ImageFileMachineSH3DSP = uint16(0x1a3) // Hitachi SH3 DSP + ImageFileMachineSH4 = uint16(0x1a6) // Hitachi SH4 + ImageFileMachineSH5 = uint16(0x1a8) // Hitachi SH5 + ImageFileMachineTHUMB = uint16(0x1c2) // Thumb + ImageFileMachineWCEMIPSv2 = uint16(0x169) // MIPS little-endian WCE v2 +) + +// The Characteristics field contains flags that indicate attributes of the object or image file. +const ( + // Image file only. This flag indicates that the file contains no base + // relocations and must be loaded at its preferred base address. In the + // case of base address conflict, the OS loader reports an error. This flag + // should not be set for managed PE files. + ImageFileRelocsStripped = 0x0001 + + // Flag indicates that the file is an image file (EXE or DLL). This flag + // should be set for managed PE files. If it is not set, this generally + // indicates a linker error (i.e. no unresolved external references). + ImageFileExecutableImage = 0x0002 + + // COFF line numbers have been removed. This flag should be set for managed + // PE files because they do not use the debug information embedded in the + // PE file itself. Instead, the debug information is saved in accompanying + // program database (PDB) files. + ImageFileLineNumsStripped = 0x0004 + + // COFF symbol table entries for local symbols have been removed. This flag + // should be set for managed PE files, for the reason given in the preceding + // entry. + ImageFileLocalSymsStripped = 0x0008 + + // Aggressively trim the working set. + ImageFileAgressibeWsTrim = 0x0010 + + // Application can handle addresses beyond the 2GB range. This flag should + // not be set for pure-IL managed PE files of versions 1.0 and 1.1 but can + // be set for v2.0+ files. + ImageFileLargeAddressAware = 0x0020 + + // Little endian. + ImageFileBytesReservedLow = 0x0080 + + // Machine is based on 32-bit architecture. This flag is usually set by + // the current versions of code generators producing managed PE files. + // Version 2.0 and newer, however, can produce 64-bit specific images, + // which don’t have this flag set. + ImageFile32BitMachine = 0x0100 + + // Debug information has been removed from the image file. + ImageFileDebugStripped = 0x0200 + + // If the image file is on removable media, copy and run it from the swap + // file. + ImageFileRemovableRunFromSwap = 0x0400 + + // If the image file is on a network, copy and run it from the swap file. + ImageFileNetRunFromSwap = 0x0800 + + // The image file is a system file (for example, a device driver). This flag + ImageFileSystem = 0x1000 + + // The image file is a DLL rather than an EXE. It cannot be directly run. + ImageFileDLL = 0x2000 + + // The image file should be run on a uniprocessor machine only. + ImageFileUpSystemOnly = 0x4000 + + // Big endian. + ImageFileBytesReservedHigh = 0x8000 +) + +// Subsystem values of an OptionalHeader. +const ( + ImageSubsystemUnknown = 0 // An unknown subsystem. + ImageSubsystemNative = 1 // Device drivers and native Windows processes + ImageSubsystemWindowsGUI = 2 // The Windows graphical user interface (GUI) subsystem. + ImageSubsystemWindowsCUI = 3 // The Windows character subsystem + ImageSubsystemOS2CUI = 5 // The OS/2 character subsystem. + ImageSubsystemPosixCUI = 7 // The Posix character subsystem. + ImageSubsystemNativeWindows = 8 // Native Win9x driver + ImageSubsystemWindowsCEGUI = 9 // Windows CE + ImageSubsystemEFIApplication = 10 // An Extensible Firmware Interface (EFI) application + ImageSubsystemEFIBootServiceDriver = 11 // An EFI driver with boot services + ImageSubsystemEFIRuntimeDriver = 12 // An EFI driver with run-time services + ImageSubsystemEFIRom = 13 // An EFI ROM image . + ImageSubsystemXBOX = 14 // XBOX. + ImageSubsystemWindowsBootApplication = 16 // Windows boot application. +) + +// DllCharacteristics values of an OptionalHeader +const ( + ImageDllCharacteristicsReserved1 = 0x0001 // Reserved, must be zero. + ImageDllCharacteristicsReserved2 = 0x0002 // Reserved, must be zero. + ImageDllCharacteristicsReserved4 = 0x0004 // Reserved, must be zero. + ImageDllCharacteristicsReserved8 = 0x0008 // Reserved, must be zero. + ImageDllCharacteristicsHighEntropyVA = 0x0020 // Image can handle a high entropy 64-bit virtual address space + ImageDllCharacteristicsDynamicBase = 0x0040 // DLL can be relocated at load time. + ImageDllCharacteristicsForceIntegrity = 0x0080 // Code Integrity checks are enforced. + ImageDllCharacteristicsNXCompact = 0x0100 // Image is NX compatible. + ImageDllCharacteristicsNoIsolation = 0x0200 // Isolation aware, but do not isolate the image. + ImageDllCharacteristicsNoSEH = 0x0400 // Does not use structured exception (SE) handling. No SE handler may be called in this image. + ImageDllCharacteristicsNoBind = 0x0800 // Do not bind the image. + ImageDllCharacteristicsAppContainer = 0x1000 // Image must execute in an AppContainer + ImageDllCharacteristicsWdmDriver = 0x2000 // A WDM driver. + ImageDllCharacteristicsGuardCF = 0x4000 // Image supports Control Flow Guard. + ImageDllCharacteristicsTerminalServiceAware = 0x8000 // Terminal Server aware. + +) + +// DataDirectory entries of an OptionalHeader +const ( + ImageDirectoryEntryExport = 0 // Export Table + ImageDirectoryEntryImport = 1 // Import Table + ImageDirectoryEntryResource = 2 // Resource Table + ImageDirectoryEntryException = 3 // Exception Table + ImageDirectoryEntryCertificate = 4 // Certificate Directory + ImageDirectoryEntryBaseReloc = 5 // Base Relocation Table + ImageDirectoryEntryDebug = 6 // Debug + ImageDirectoryEntryArchitecture = 7 // Architecture Specific Data + ImageDirectoryEntryGlobalPtr = 8 // The RVA of the value to be stored in the global pointer register. + ImageDirectoryEntryTLS = 9 // The thread local storage (TLS) table + ImageDirectoryEntryLoadConfig = 10 // The load configuration table + ImageDirectoryEntryBoundImport = 11 // The bound import table + ImageDirectoryEntryIAT = 12 // Import Address Table + ImageDirectoryEntryDelayImport = 13 // Delay Import Descriptor + ImageDirectoryEntryCLR = 14 // CLR Runtime Header + ImageDirectoryEntryReserved = 15 // Must be zero + ImageNumberOfDirectoryEntries = 16 // Tables count. +) diff --git a/reloc.go b/reloc.go new file mode 100644 index 0000000..93d177c --- /dev/null +++ b/reloc.go @@ -0,0 +1,241 @@ +// Copyright 2021 Saferwall. All rights reserved. +// Use of this source code is governed by Apache v2 license +// license that can be found in the LICENSE file. + +package pe + +import ( + "encoding/binary" + "errors" +) + +var ( + // ErrInvalidBaseRelocVA is reposed when base reloc lies outside of the image. + ErrInvalidBaseRelocVA = errors.New("Invalid relocation information." + + " Base Relocation VirtualAddress is outside of PE Image") + + // ErrInvalidBasicRelocSizeOfBloc is reposed when base reloc is too large. + ErrInvalidBasicRelocSizeOfBloc = errors.New("Invalid relocation " + + "information. Base Relocation SizeOfBlock too large") +) + +// The Type field of the relocation record indicates what kind of relocation +// should be performed. Different relocation types are defined for each type +// of machine. +const ( + // The base relocation is skipped. This type can be used to pad a block. + ImageRelBasedAbsolute = 0 + + // The base relocation adds the high 16 bits of the difference to the 16-bit + // field at offset. The 16-bit field represents the high value of a 32-bit word. + ImageRelBasedHigh = 1 + + // The base relocation adds the low 16 bits of the difference to the 16-bit + // field at offset. The 16-bit field represents the low half of a 32-bit word. + ImageRelBasedLow = 2 + + // The base relocation applies all 32 bits of the difference to the 32-bit + // field at offset. + ImageRelBasedHighLow = 3 + + // The base relocation adds the high 16 bits of the difference to the 16-bit + // field at offset. The 16-bit field represents the high value of a 32-bit + // word. The low 16 bits of the 32-bit value are stored in the 16-bit word + // that follows this base relocation. This means that this base relocation + // occupies two slots. + ImageRelBasedHighAdj = 4 + + // The relocation interpretation is dependent on the machine type. + // When the machine type is MIPS, the base relocation applies to a MIPS jump + // instruction. + ImageRelBasedMIPSJmpAddr = 5 + + // This relocation is meaningful only when the machine type is ARM or Thumb. + // The base relocation applies the 32-bit address of a symbol across a + // consecutive MOVW/MOVT instruction pair. + ImageRelBasedARMMov32 = 5 + + // This relocation is only meaningful when the machine type is RISC-V. The + // base relocation applies to the high 20 bits of a 32-bit absolute address. + ImageRelBasedRISCVHigh20 = 5 + + // Reserved, must be zero. + ImageRelReserved = 6 + + // This relocation is meaningful only when the machine type is Thumb. + // The base relocation applies the 32-bit address of a symbol to a + // consecutive MOVW/MOVT instruction pair. + ImageRelBasedThumbMov32 = 7 + + // This relocation is only meaningful when the machine type is RISC-V. + // The base relocation applies to the low 12 bits of a 32-bit absolute + // address formed in RISC-V I-type instruction format. + ImageRelBasedRISCVLow12i = 7 + + // This relocation is only meaningful when the machine type is RISC-V. + // The base relocation applies to the low 12 bits of a 32-bit absolute + // address formed in RISC-V S-type instruction format. + ImageRelBasedRISCVLow12s = 8 + + // The relocation is only meaningful when the machine type is MIPS. + // The base relocation applies to a MIPS16 jump instruction. + ImageRelBasedMIPSJmpAddr16 = 9 + + // The base relocation applies the difference to the 64-bit field at offset. + ImageRelBasedDir64 = 10 +) + +// ImageBaseRelocation represents the IMAGE_BASE_RELOCATION structure. +// Each chunk of base relocation data begins with an IMAGE_BASE_RELOCATION structure. +type ImageBaseRelocation struct { + // The image base plus the page RVA is added to each offset to create the + // VA where the base relocation must be applied. + VirtualAddress uint32 + + // The total number of bytes in the base relocation block, including the + // Page RVA and Block Size fields and the Type/Offset fields that follow. + SizeOfBlock uint32 +} + +// ImageBaseRelocationEntry represents an image base relocation entry. +type ImageBaseRelocationEntry struct { + // Locate data that must be reallocated in buffer (data being an address + // we use pointer of pointer). + Data uint16 + + // The offset of the relocation. This value plus the VirtualAddress + // in IMAGE_BASE_RELOCATION is the complete RVA. + Offset uint16 + + // A value that indicates the kind of relocation that should be performed. + // Valid relocation types depend on machine type. + Type uint8 +} + +// Relocation represents the relocation table which holds the data that needs to +// be relocated. +type Relocation struct { + // Points to the ImageBaseRelocation structure. + Data ImageBaseRelocation + + // holds the list of entries for each chunk. + Entries []ImageBaseRelocationEntry +} + +func (pe *File) parseRelocations(dataRVA, rva, size uint32) ([]ImageBaseRelocationEntry, error) { + var relocEntries []ImageBaseRelocationEntry + relocEntriesCount := size / 2 + offset := pe.getOffsetFromRva(dataRVA) + for i := uint32(0); i < relocEntriesCount; i++ { + entry := ImageBaseRelocationEntry{} + entry.Data = binary.LittleEndian.Uint16(pe.data[offset+(i*2):]) + entry.Type = uint8(entry.Data >> 12) + entry.Offset = entry.Data & 0x0fff + relocEntries = append(relocEntries, entry) + } + + return relocEntries, nil +} + +func (pe *File) parseRelocDirectory(rva, size uint32) error { + var sizeOfImage uint32 + switch pe.Is64 { + case true: + sizeOfImage = pe.NtHeader.OptionalHeader.(ImageOptionalHeader64).SizeOfImage + case false: + sizeOfImage = pe.NtHeader.OptionalHeader.(ImageOptionalHeader32).SizeOfImage + } + + relocSize := uint32(binary.Size(ImageBaseRelocation{})) + end := rva + size + for rva < end { + baseReloc := ImageBaseRelocation{} + offset := pe.getOffsetFromRva(rva) + err := pe.structUnpack(&baseReloc, offset, relocSize) + if err != nil { + return err + } + + // VirtualAddress must lie within the Image. + if baseReloc.VirtualAddress > sizeOfImage { + return ErrInvalidBaseRelocVA + } + + // SizeOfBlock must be less or equal than the size of the image. + // It's a rather loose sanity test. + if baseReloc.SizeOfBlock > sizeOfImage { + return ErrInvalidBasicRelocSizeOfBloc + } + + relocEntries, err := pe.parseRelocations(rva+relocSize, + baseReloc.VirtualAddress, baseReloc.SizeOfBlock-relocSize) + if err != nil { + return err + } + + pe.Relocations = append(pe.Relocations, Relocation{ + Data: baseReloc, + Entries: relocEntries, + }) + + if baseReloc.SizeOfBlock == 0 { + break + } + rva += baseReloc.SizeOfBlock + } + + return nil +} + +// PrettyRelocTypeEntry returns the string representation +// of the `Type` field of a base reloc entry. +func (pe *File) PrettyRelocTypeEntry(k uint8) string { + relocTypesMap := map[uint8]string{ + ImageRelBasedAbsolute: "Absolute", + ImageRelBasedHigh: "High", + ImageRelBasedLow: "Low", + ImageRelBasedHighLow: "HighLow", + ImageRelBasedHighAdj: "HighAdj", + ImageRelReserved: "Reserved", + ImageRelBasedRISCVLow12s: "RISC-V Low12s", + ImageRelBasedMIPSJmpAddr16: "MIPS Jmp Addr16", + ImageRelBasedDir64: "DIR64", + } + + if value, ok := relocTypesMap[k]; ok { + return value + } + + switch pe.NtHeader.FileHeader.Machine { + case ImageFileMachineMIPS16: + case ImageFileMachineMIPSFPU: + case ImageFileMachineMIPSFPU16: + case ImageFileMachineWCEMIPSv2: + if k == ImageRelBasedMIPSJmpAddr { + return "MIPS JMP Addr" + } + + case ImageFileMachineARM: + case ImageFileMachineARM64: + case ImageFileMachineARMNT: + if k == ImageRelBasedARMMov32 { + return "ARM MOV 32" + } + + if k == ImageRelBasedThumbMov32 { + return "Thumb MOV 32" + } + case ImageFileMachineRISCV32: + case ImageFileMachineRISCV64: + case ImageFileMachineRISCV128: + if k == ImageRelBasedRISCVHigh20 { + return "RISC-V High 20" + } + + if k == ImageRelBasedRISCVLow12i { + return "RISC-V Low 12" + } + } + + return "?" +} diff --git a/resource.go b/resource.go new file mode 100644 index 0000000..6686c0d --- /dev/null +++ b/resource.go @@ -0,0 +1,318 @@ +// Copyright 2021 Saferwall. All rights reserved. +// Use of this source code is governed by Apache v2 license +// license that can be found in the LICENSE file. + +package pe + +import ( + "encoding/binary" + "log" + "unsafe" +) + +const ( + maxAllowedEntries = 0x1000 +) + +var ( + depth = 0 +) + +// Predefined Resource Types +var ( + RTCursor = makeIntResource(1) + RTBitmap = makeIntResource(2) + RTIcon = makeIntResource(3) + RTMenu = makeIntResource(4) + RTDialog = makeIntResource(5) + RTString = makeIntResource(6) + RTFontdir = makeIntResource(7) + RTFont = makeIntResource(8) + RTAccelerator = makeIntResource(9) + RTRCdata = makeIntResource(10) + RTMessagetable = makeIntResource(11) + RTGroupCursor = makeIntResource(12) + RTGroupIcon = makeIntResource(14) + RTVersion = makeIntResource(16) + RTDlgInclude = makeIntResource(17) + RTPlugPlay = makeIntResource(19) + RTVxd = makeIntResource(20) + RTAniCursor = makeIntResource(21) + RTAniIcon = makeIntResource(22) + RTHtml = makeIntResource(23) + RTManifest = makeIntResource(24) +) + +// ImageResourceDirectory represents the IMAGE_RESOURCE_DIRECTORY. +// This data structure should be considered the heading of a table because the +// table actually consists of directory entries. +type ImageResourceDirectory struct { + // Resource flags. This field is reserved for future use. It is currently + // set to zero. + Characteristics uint32 + + // The time that the resource data was created by the resource compiler. + TimeDateStamp uint32 + + // The major version number, set by the user. + MajorVersion uint16 + + // The minor version number, set by the user. + MinorVersion uint16 + + // The number of directory entries immediately following the table that use + // strings to identify Type, Name, or Language entries (depending on the + // level of the table). + NumberOfNamedEntries uint16 + + // The number of directory entries immediately following the Name entries + // that use numeric IDs for Type, Name, or Language entries. + NumberOfIDEntries uint16 +} + +// ImageResourceDirectoryEntry represents an entry in the resource directory +// entries. +type ImageResourceDirectoryEntry struct { + // is used to identify either a type of resource, a resource name, or a + // resource's language ID. + Name uint32 + + //is always used to point to a sibling in the tree, either a directory node + // or a leaf node. + OffsetToData uint32 +} + +// ImageResourceDataEntry Each Resource Data entry describes an actual unit of +// raw data in the Resource Data area. +type ImageResourceDataEntry struct { + // The address of a unit of resource data in the Resource Data area. + OffsetToData uint32 + + // The size, in bytes, of the resource data that is pointed to by the Data + // RVA field. + Size uint32 + + // The code page that is used to decode code point values within the + // resource data. Typically, the code page would be the Unicode code page. + CodePage uint32 + + // Reserved, must be 0. + Reserved uint32 +} + +// ResourceDirectory represents resource directory information. +type ResourceDirectory struct { + // IMAGE_RESOURCE_DIRECTORY structure + Struct ImageResourceDirectory + + // list of entries + Entries []ResourceDirectoryEntry +} + +// ResourceDirectoryEntry represents a resource directory entry. +type ResourceDirectoryEntry struct { + // IMAGE_RESOURCE_DIRECTORY_ENTRY structure. + Struct ImageResourceDirectoryEntry + + // If the resource is identified by name this attribute will contain the + // name string. Empty string otherwise. If identified by id, the id is + // available at .Id field. + Name string + + // The resource identifier. + ID uint32 + + // If this entry has a lower level directory this attribute will point to + // the ResourceDirData instance representing it. + Directory ResourceDirectory + + // If this entry has no further lower directories and points to the actual + // resource data, this attribute will reference the corresponding + // ResourceDataEntry instance. + Data ResourceDataEntry +} + +// ResourceDataEntry represents a resource data entry. +type ResourceDataEntry struct { + + // IMAGE_RESOURCE_DATA_ENTRY structure. + Struct ImageResourceDataEntry + + // Primary language ID + Lang uint32 + Sublang uint32 // Sublanguage ID +} + +// makeIntResource mimics the MAKEINTRESOURCE macro. +func makeIntResource(id uintptr) *uint16 { + return (*uint16)(unsafe.Pointer(id)) +} + +func (pe *File) parseResourceDataEntry(rva uint32) *ImageResourceDataEntry { + dataEntry := ImageResourceDataEntry{} + dataEntrySize := uint32(binary.Size(dataEntry)) + offset := pe.getOffsetFromRva(rva) + err := pe.structUnpack(&dataEntry, offset, dataEntrySize) + if err != nil { + log.Println("Error parsing a resource directory data entry, the RVA is invalid") + return nil + } + return &dataEntry +} + +func (pe *File) parseResourceDirectoryEntry(rva uint32) *ImageResourceDirectoryEntry { + resource := ImageResourceDirectoryEntry{} + resourceSize := uint32(binary.Size(resource)) + offset := pe.getOffsetFromRva(rva) + err := pe.structUnpack(&resource, offset, resourceSize) + if err != nil { + return nil + } + + if resource == (ImageResourceDirectoryEntry{}) { + return nil + } + + // resource.NameOffset = resource.Name & 0x7FFFFFFF + + // resource.__pad = resource.Name & 0xFFFF0000 + // resource.Id = resource.Name & 0x0000FFFF + + // resource.DataIsDirectory = (resource.OffsetToData & 0x80000000) >> 31 + // resource.OffsetToDirectory = resource.OffsetToData & 0x7FFFFFFF + + return &resource +} + +// Navigating the resource directory hierarchy is like navigating a hard disk. +// There's a master directory (the root directory), which has subdirectories. +// The subdirectories have subdirectories of their own that may point to the +// raw resource data for things like dialog templates. +func (pe *File) doParseResourceDirectory(rva, size, baseRVA, level uint32, + dirs []uint32) (ResourceDirectory, error) { + + resourceDir := ImageResourceDirectory{} + resourceDirSize := uint32(binary.Size(resourceDir)) + offset := pe.getOffsetFromRva(rva) + err := pe.structUnpack(&resourceDir, offset, resourceDirSize) + if err != nil { + return ResourceDirectory{}, err + } + + if baseRVA == 0 { + baseRVA = rva + } + + if len(dirs) == 0 { + dirs = append(dirs, rva) + } + + // Advance the RVA to the position immediately following the directory + // table header and pointing to the first entry in the table. + rva += resourceDirSize + + numberOfEntries := int(resourceDir.NumberOfNamedEntries + + resourceDir.NumberOfIDEntries) + var dirEntries []ResourceDirectoryEntry + + // Set a hard limit on the maximum reasonable number of entries. + if numberOfEntries > maxAllowedEntries { + log.Printf(`Error parsing the resources directory. + The directory contains %d entries`, numberOfEntries) + return ResourceDirectory{}, nil + } + + for i := 0; i < numberOfEntries; i++ { + res := pe.parseResourceDirectoryEntry(rva) + if res == nil { + log.Println("Error parsing a resource directory entry, the RVA is invalid") + break + } + + nameIsString := (res.Name & 0x80000000) >> 31 + entryName := "" + entryID := uint32(0) + if nameIsString == 0 { + entryID = res.Name + } else { + nameOffset := res.Name & 0x7FFFFFFF + uStringOffset := pe.getOffsetFromRva(baseRVA + nameOffset) + maxLen, err := pe.ReadUint16(uStringOffset) + if err != nil { + break + } + entryName = pe.readUnicodeStringAtRVA(baseRVA+nameOffset+2, + uint32(maxLen)) + } + + // A directory entry points to either another resource directory or to + // the data for an individual resource. When the directory entry points + // to another resource directory, the high bit of the second DWORD in + // the structure is set and the remaining 31 bits are an offset to the + // resource directory. + dataIsDirectory := (res.OffsetToData & 0x80000000) >> 31 + + // The offset is relative to the beginning of the resource section, + // not an RVA. + OffsetToDirectory := res.OffsetToData & 0x7FFFFFFF + if dataIsDirectory > 0 { + // One trick malware can do is to recursively reference + // the next directory. This causes hilarity to ensue when + // trying to parse everything correctly. + // If the original RVA given to this function is equal to + // the next one to parse, we assume that it's a trick. + // Instead of raising a PEFormatError this would skip some + // reasonable data so we just break. + // 9ee4d0a0caf095314fd7041a3e4404dc is the offending sample. + if intInSlice(baseRVA+OffsetToDirectory, dirs) { + break + } + + level++ + dirs = append(dirs, baseRVA+OffsetToDirectory) + directoryEntry, _ := pe.doParseResourceDirectory( + baseRVA+OffsetToDirectory, + size-(rva-baseRVA), + baseRVA, + level, + dirs) + + dirEntries = append(dirEntries, ResourceDirectoryEntry{ + Struct: *res, + Name: entryName, + ID: entryID, + Directory: directoryEntry}) + } else { + // data is entry + dataEntryStruct := pe.parseResourceDataEntry(baseRVA + + OffsetToDirectory) + entryData := ResourceDataEntry{ + Struct: *dataEntryStruct, + Lang: res.Name & 0x3ff, + Sublang: res.Name >> 10, + } + + dirEntries = append(dirEntries, ResourceDirectoryEntry{ + Struct: *res, + Name: entryName, + ID: entryID, + Data: entryData}) + } + + rva += uint32(binary.Size(res)) + } + + return ResourceDirectory{ + Struct: resourceDir, + Entries: dirEntries, + }, nil +} + +// The resource directory contains resources like dialog templates, icons, +// and bitmaps. The resources are found in a section called .rsrc section. +func (pe *File) parseResourceDirectory(rva, size uint32) error { + var dirs []uint32 + Resources, err := pe.doParseResourceDirectory(rva, size, 0, 0, dirs) + pe.Resources = &Resources + return err +} diff --git a/richheader.go b/richheader.go new file mode 100644 index 0000000..855936f --- /dev/null +++ b/richheader.go @@ -0,0 +1,777 @@ +// Copyright 2021 Saferwall. All rights reserved. +// Use of this source code is governed by Apache v2 license +// license that can be found in the LICENSE file. + +package pe + +import ( + "bytes" + "encoding/binary" +) + +const ( + // DansSignature ('DanS' as dword) is where the rich header struct starts. + DansSignature = 0x536E6144 + + // RichSignature ('0x68636952' as dword) is where the rich header struct ends. + RichSignature = "Rich" + + // AnoDansSigNotFound is reported when rich header signature was found, but + AnoDansSigNotFound = "Rich Header found, but could not locate DanS " + + "signature" + + // AnoPaddingDwordNotZero is repoted when rich header signature leading + // padding DWORDs are not equal to 0. + AnoPaddingDwordNotZero = "Rich header found: 3 leading padding DWORDs " + + "not found after DanS signature" +) + +// CompID represents the `@comp.id` structure. +type CompID struct { + // The minor version information for the compiler used when building the product. + MinorCV uint16 + + // Provides information about the identity or type of the objects used to + // build the PE32. + ProdID uint16 + + // Indicates how often the object identified by the former two fields is + // referenced by this PE32 file. + Count uint32 + + // The raw @comp.id structure (unmasked). + Unmasked uint32 +} + +// RichHeader is a structure that is written right after the MZ DOS header. +// It consists of pairs of 4-byte integers. And it is also +// encrypted using a simple XOR operation using the checksum as the key. +// The data between the magic values encodes the ‘bill of materials’ that were +// collected by the linker to produce the binary. +type RichHeader struct { + XorKey uint32 + CompIDs []CompID + DansOffset int + Raw []byte +} + +// ParseRichHeader parses the rich header struct. +func (pe *File) ParseRichHeader() error { + + rh := RichHeader{} + ntHeaderOffset := pe.DosHeader.AddressOfNewEXEHeader + richSigOffset := bytes.Index(pe.data[:ntHeaderOffset], []byte(RichSignature)) + + // For example, .NET executable files do not use the MSVC linker and these + // executables do not contain a detectable Rich Header. + if richSigOffset < 0 { + return nil + } + + // The DWORD following the "Rich" sequence is the XOR key stored by and + // calculated by the linker. It is actually a checksum of the DOS header with + // the e_lfanew zeroed out, and additionally includes the values of the + // unencrypted "Rich" array. Using a checksum with encryption will not only + // obfuscate the values, but it also serves as a rudimentary digital + // signature. If the checksum is calculated from scratch once the values + // have been decrypted, but doesn't match the stored key, it can be assumed + // the structure had been tampered with. For those that go the extra step to + // recalculate the checksum/key, this simple protection mechanism can be bypassed. + rh.XorKey = binary.LittleEndian.Uint32(pe.data[richSigOffset+4:]) + + // To decrypt the array, start with the DWORD just prior to the `Rich` sequence + // and XOR it with the key. Continue the loop backwards, 4 bytes at a time, + // until the sequence `DanS` is decrypted. + var decRichHeader []uint32 + dansSigOffset := -1 + estimatedBeginDans := richSigOffset - 4 - binary.Size(ImageDosHeader{}) + for it := 0; it < estimatedBeginDans; it += 4 { + buff := binary.LittleEndian.Uint32(pe.data[richSigOffset-4-it:]) + res := buff ^ rh.XorKey + if res == DansSignature { + dansSigOffset = richSigOffset - it - 4 + break + } + + decRichHeader = append(decRichHeader, res) + } + + // Probe we successfuly found the `DanS` magic. + if dansSigOffset == -1 { + pe.Anomalies = append(pe.Anomalies, AnoDansSigNotFound) + return nil + } + + // Anomaly check: dansSigOffset is usually found in offset 0x80. + if dansSigOffset != 0x80 { + pe.Anomalies = append(pe.Anomalies, AnoDanSMagicOffset) + } + + rh.DansOffset = dansSigOffset + rh.Raw = pe.data[dansSigOffset : richSigOffset+8] + + // Reverse the decrypted rich header + for i, j := 0, len(decRichHeader)-1; i < j; i, j = i+1, j-1 { + decRichHeader[i], decRichHeader[j] = decRichHeader[j], decRichHeader[i] + } + + // After the `DanS` signature, there are some zero-padded In practice, + // Microsoft seems to have wanted the entries to begin on a 16-byte + // (paragraph) boundary, so the 3 leading padding DWORDs can be safely + // skipped as not belonging to the data. + if decRichHeader[0] != 0 || decRichHeader[1] != 0 || decRichHeader[2] != 0 { + pe.Anomalies = append(pe.Anomalies, AnoPaddingDwordNotZero) + } + + // The array stores entries that are 8-bytes each, broken into 3 members. + // Each entry represents either a tool that was employed as part of building + // the executable or a statistic. + // The @compid struct should be multiple of 8 (bytes), some malformed pe + // files have incorrect number of entries. + var lenCompIDs int + if (len(decRichHeader)-3)%2 != 0 { + lenCompIDs = len(decRichHeader) - 1 + } else { + lenCompIDs = len(decRichHeader) + } + + for i := 3; i < lenCompIDs; i += 2 { + cid := CompID{} + compid := make([]byte, binary.Size(cid)) + binary.LittleEndian.PutUint32(compid, decRichHeader[i]) + binary.LittleEndian.PutUint32(compid[4:], decRichHeader[i+1]) + buf := bytes.NewReader(compid) + err := binary.Read(buf, binary.LittleEndian, &cid) + if err != nil { + return err + } + cid.Unmasked = binary.LittleEndian.Uint32(compid) + rh.CompIDs = append(rh.CompIDs, cid) + } + + pe.RichHeader = &rh + + checksum := pe.RichHeaderChecksum() + if checksum != rh.XorKey { + pe.Anomalies = append(pe.Anomalies, "Invalid rich header checksum") + } + return nil +} + +// RichHeaderChecksum calculate the Rich Header checksum. +func (pe *File) RichHeaderChecksum() uint32 { + + checksum := uint32(pe.RichHeader.DansOffset) + + // First, calculate the sum of the DOS header bytes each rotated left the + // number of times their position relative to the start of the DOS header e.g. + // second byte is rotated left 2x using rol operation. + for i := 0; i < pe.RichHeader.DansOffset; i++ { + // skip over dos e_lfanew field at offset 0x3C + if i >= 0x3C && i < 0x40 { + continue + } + b := uint32(pe.data[i]) + checksum += ((b << (i % 32)) | (b>>(32-(i%32)))&0xff) + checksum &= 0xFFFFFFFF + } + + // Next, take summation of each Rich header entry by combining its ProductId + // and BuildNumber into a single 32 bit number and rotating by its count. + for _, compid := range pe.RichHeader.CompIDs { + checksum += (compid.Unmasked<<(compid.Count%32) | + compid.Unmasked>>(32-(compid.Count%32))) + checksum &= 0xFFFFFFFF + } + + return checksum +} + +// ProdIDtoStr mapps product ids to MS internal names. +// list from: https://github.com/kirschju/richheader +func ProdIDtoStr(prodID uint16) string { + switch prodID { + case 0x0000: + return "Unknown" + case 0x0001: + return "Import0" + case 0x0002: + return "Linker510" + case 0x0003: + return "Cvtomf510" + case 0x0004: + return "Linker600" + case 0x0005: + return "Cvtomf600" + case 0x0006: + return "Cvtres500" + case 0x0007: + return "Utc11_Basic" + case 0x0008: + return "Utc11_C" + case 0x0009: + return "Utc12_Basic" + case 0x000a: + return "Utc12_C" + case 0x000b: + return "Utc12_CPP" + case 0x000c: + return "AliasObj60" + case 0x000d: + return "VisualBasic60" + case 0x000e: + return "Masm613" + case 0x000f: + return "Masm710" + case 0x0010: + return "Linker511" + case 0x0011: + return "Cvtomf511" + case 0x0012: + return "Masm614" + case 0x0013: + return "Linker512" + case 0x0014: + return "Cvtomf512" + case 0x0015: + return "Utc12_C_Std" + case 0x0016: + return "Utc12_CPP_Std" + case 0x0017: + return "Utc12_C_Book" + case 0x0018: + return "Utc12_CPP_Book" + case 0x0019: + return "Implib700" + case 0x001a: + return "Cvtomf700" + case 0x001b: + return "Utc13_Basic" + case 0x001c: + return "Utc13_C" + case 0x001d: + return "Utc13_CPP" + case 0x001e: + return "Linker610" + case 0x001f: + return "Cvtomf610" + case 0x0020: + return "Linker601" + case 0x0021: + return "Cvtomf601" + case 0x0022: + return "Utc12_1_Basic" + case 0x0023: + return "Utc12_1_C" + case 0x0024: + return "Utc12_1_CPP" + case 0x0025: + return "Linker620" + case 0x0026: + return "Cvtomf620" + case 0x0027: + return "AliasObj70" + case 0x0028: + return "Linker621" + case 0x0029: + return "Cvtomf621" + case 0x002a: + return "Masm615" + case 0x002b: + return "Utc13_LTCG_C" + case 0x002c: + return "Utc13_LTCG_CPP" + case 0x002d: + return "Masm620" + case 0x002e: + return "ILAsm100" + case 0x002f: + return "Utc12_2_Basic" + case 0x0030: + return "Utc12_2_C" + case 0x0031: + return "Utc12_2_CPP" + case 0x0032: + return "Utc12_2_C_Std" + case 0x0033: + return "Utc12_2_CPP_Std" + case 0x0034: + return "Utc12_2_C_Book" + case 0x0035: + return "Utc12_2_CPP_Book" + case 0x0036: + return "Implib622" + case 0x0037: + return "Cvtomf622" + case 0x0038: + return "Cvtres501" + case 0x0039: + return "Utc13_C_Std" + case 0x003a: + return "Utc13_CPP_Std" + case 0x003b: + return "Cvtpgd1300" + case 0x003c: + return "Linker622" + case 0x003d: + return "Linker700" + case 0x003e: + return "Export622" + case 0x003f: + return "Export700" + case 0x0040: + return "Masm700" + case 0x0041: + return "Utc13_POGO_I_C" + case 0x0042: + return "Utc13_POGO_I_CPP" + case 0x0043: + return "Utc13_POGO_O_C" + case 0x0044: + return "Utc13_POGO_O_CPP" + case 0x0045: + return "Cvtres700" + case 0x0046: + return "Cvtres710p" + case 0x0047: + return "Linker710p" + case 0x0048: + return "Cvtomf710p" + case 0x0049: + return "Export710p" + case 0x004a: + return "Implib710p" + case 0x004b: + return "Masm710p" + case 0x004c: + return "Utc1310p_C" + case 0x004d: + return "Utc1310p_CPP" + case 0x004e: + return "Utc1310p_C_Std" + case 0x004f: + return "Utc1310p_CPP_Std" + case 0x0050: + return "Utc1310p_LTCG_C" + case 0x0051: + return "Utc1310p_LTCG_CPP" + case 0x0052: + return "Utc1310p_POGO_I_C" + case 0x0053: + return "Utc1310p_POGO_I_CPP" + case 0x0054: + return "Utc1310p_POGO_O_C" + case 0x0055: + return "Utc1310p_POGO_O_CPP" + case 0x0056: + return "Linker624" + case 0x0057: + return "Cvtomf624" + case 0x0058: + return "Export624" + case 0x0059: + return "Implib624" + case 0x005a: + return "Linker710" + case 0x005b: + return "Cvtomf710" + case 0x005c: + return "Export710" + case 0x005d: + return "Implib710" + case 0x005e: + return "Cvtres710" + case 0x005f: + return "Utc1310_C" + case 0x0060: + return "Utc1310_CPP" + case 0x0061: + return "Utc1310_C_Std" + case 0x0062: + return "Utc1310_CPP_Std" + case 0x0063: + return "Utc1310_LTCG_C" + case 0x0064: + return "Utc1310_LTCG_CPP" + case 0x0065: + return "Utc1310_POGO_I_C" + case 0x0066: + return "Utc1310_POGO_I_CPP" + case 0x0067: + return "Utc1310_POGO_O_C" + case 0x0068: + return "Utc1310_POGO_O_CPP" + case 0x0069: + return "AliasObj710" + case 0x006a: + return "AliasObj710p" + case 0x006b: + return "Cvtpgd1310" + case 0x006c: + return "Cvtpgd1310p" + case 0x006d: + return "Utc1400_C" + case 0x006e: + return "Utc1400_CPP" + case 0x006f: + return "Utc1400_C_Std" + case 0x0070: + return "Utc1400_CPP_Std" + case 0x0071: + return "Utc1400_LTCG_C" + case 0x0072: + return "Utc1400_LTCG_CPP" + case 0x0073: + return "Utc1400_POGO_I_C" + case 0x0074: + return "Utc1400_POGO_I_CPP" + case 0x0075: + return "Utc1400_POGO_O_C" + case 0x0076: + return "Utc1400_POGO_O_CPP" + case 0x0077: + return "Cvtpgd1400" + case 0x0078: + return "Linker800" + case 0x0079: + return "Cvtomf800" + case 0x007a: + return "Export800" + case 0x007b: + return "Implib800" + case 0x007c: + return "Cvtres800" + case 0x007d: + return "Masm800" + case 0x007e: + return "AliasObj800" + case 0x007f: + return "PhoenixPrerelease" + case 0x0080: + return "Utc1400_CVTCIL_C" + case 0x0081: + return "Utc1400_CVTCIL_CPP" + case 0x0082: + return "Utc1400_LTCG_MSIL" + case 0x0083: + return "Utc1500_C" + case 0x0084: + return "Utc1500_CPP" + case 0x0085: + return "Utc1500_C_Std" + case 0x0086: + return "Utc1500_CPP_Std" + case 0x0087: + return "Utc1500_CVTCIL_C" + case 0x0088: + return "Utc1500_CVTCIL_CPP" + case 0x0089: + return "Utc1500_LTCG_C" + case 0x008a: + return "Utc1500_LTCG_CPP" + case 0x008b: + return "Utc1500_LTCG_MSIL" + case 0x008c: + return "Utc1500_POGO_I_C" + case 0x008d: + return "Utc1500_POGO_I_CPP" + case 0x008e: + return "Utc1500_POGO_O_C" + case 0x008f: + return "Utc1500_POGO_O_CPP" + case 0x0090: + return "Cvtpgd1500" + case 0x0091: + return "Linker900" + case 0x0092: + return "Export900" + case 0x0093: + return "Implib900" + case 0x0094: + return "Cvtres900" + case 0x0095: + return "Masm900" + case 0x0096: + return "AliasObj900" + case 0x0097: + return "Resource" + case 0x0098: + return "AliasObj1000" + case 0x0099: + return "Cvtpgd1600" + case 0x009a: + return "Cvtres1000" + case 0x009b: + return "Export1000" + case 0x009c: + return "Implib1000" + case 0x009d: + return "Linker1000" + case 0x009e: + return "Masm1000" + case 0x009f: + return "Phx1600_C" + case 0x00a0: + return "Phx1600_CPP" + case 0x00a1: + return "Phx1600_CVTCIL_C" + case 0x00a2: + return "Phx1600_CVTCIL_CPP" + case 0x00a3: + return "Phx1600_LTCG_C" + case 0x00a4: + return "Phx1600_LTCG_CPP" + case 0x00a5: + return "Phx1600_LTCG_MSIL" + case 0x00a6: + return "Phx1600_POGO_I_C" + case 0x00a7: + return "Phx1600_POGO_I_CPP" + case 0x00a8: + return "Phx1600_POGO_O_C" + case 0x00a9: + return "Phx1600_POGO_O_CPP" + case 0x00aa: + return "Utc1600_C" + case 0x00ab: + return "Utc1600_CPP" + case 0x00ac: + return "Utc1600_CVTCIL_C" + case 0x00ad: + return "Utc1600_CVTCIL_CPP" + case 0x00ae: + return "Utc1600_LTCG_C" + case 0x00af: + return "Utc1600_LTCG_CPP" + case 0x00b0: + return "Utc1600_LTCG_MSIL" + case 0x00b1: + return "Utc1600_POGO_I_C" + case 0x00b2: + return "Utc1600_POGO_I_CPP" + case 0x00b3: + return "Utc1600_POGO_O_C" + case 0x00b4: + return "Utc1600_POGO_O_CPP" + case 0x00b5: + return "AliasObj1010" + case 0x00b6: + return "Cvtpgd1610" + case 0x00b7: + return "Cvtres1010" + case 0x00b8: + return "Export1010" + case 0x00b9: + return "Implib1010" + case 0x00ba: + return "Linker1010" + case 0x00bb: + return "Masm1010" + case 0x00bc: + return "Utc1610_C" + case 0x00bd: + return "Utc1610_CPP" + case 0x00be: + return "Utc1610_CVTCIL_C" + case 0x00bf: + return "Utc1610_CVTCIL_CPP" + case 0x00c0: + return "Utc1610_LTCG_C" + case 0x00c1: + return "Utc1610_LTCG_CPP" + case 0x00c2: + return "Utc1610_LTCG_MSIL" + case 0x00c3: + return "Utc1610_POGO_I_C" + case 0x00c4: + return "Utc1610_POGO_I_CPP" + case 0x00c5: + return "Utc1610_POGO_O_C" + case 0x00c6: + return "Utc1610_POGO_O_CPP" + case 0x00c7: + return "AliasObj1100" + case 0x00c8: + return "Cvtpgd1700" + case 0x00c9: + return "Cvtres1100" + case 0x00ca: + return "Export1100" + case 0x00cb: + return "Implib1100" + case 0x00cc: + return "Linker1100" + case 0x00cd: + return "Masm1100" + case 0x00ce: + return "Utc1700_C" + case 0x00cf: + return "Utc1700_CPP" + case 0x00d0: + return "Utc1700_CVTCIL_C" + case 0x00d1: + return "Utc1700_CVTCIL_CPP" + case 0x00d2: + return "Utc1700_LTCG_C" + case 0x00d3: + return "Utc1700_LTCG_CPP" + case 0x00d4: + return "Utc1700_LTCG_MSIL" + case 0x00d5: + return "Utc1700_POGO_I_C" + case 0x00d6: + return "Utc1700_POGO_I_CPP" + case 0x00d7: + return "Utc1700_POGO_O_C" + case 0x00d8: + return "Utc1700_POGO_O_CPP" + case 0x00d9: + return "AliasObj1200" + case 0x00da: + return "Cvtpgd1800" + case 0x00db: + return "Cvtres1200" + case 0x00dc: + return "Export1200" + case 0x00dd: + return "Implib1200" + case 0x00de: + return "Linker1200" + case 0x00df: + return "Masm1200" + case 0x00e0: + return "Utc1800_C" + case 0x00e1: + return "Utc1800_CPP" + case 0x00e2: + return "Utc1800_CVTCIL_C" + case 0x00e3: + return "Utc1800_CVTCIL_CPP" + case 0x00e4: + return "Utc1800_LTCG_C" + case 0x00e5: + return "Utc1800_LTCG_CPP" + case 0x00e6: + return "Utc1800_LTCG_MSIL" + case 0x00e7: + return "Utc1800_POGO_I_C" + case 0x00e8: + return "Utc1800_POGO_I_CPP" + case 0x00e9: + return "Utc1800_POGO_O_C" + case 0x00ea: + return "Utc1800_POGO_O_CPP" + case 0x00eb: + return "AliasObj1210" + case 0x00ec: + return "Cvtpgd1810" + case 0x00ed: + return "Cvtres1210" + case 0x00ee: + return "Export1210" + case 0x00ef: + return "Implib1210" + case 0x00f0: + return "Linker1210" + case 0x00f1: + return "Masm1210" + case 0x00f2: + return "Utc1810_C" + case 0x00f3: + return "Utc1810_CPP" + case 0x00f4: + return "Utc1810_CVTCIL_C" + case 0x00f5: + return "Utc1810_CVTCIL_CPP" + case 0x00f6: + return "Utc1810_LTCG_C" + case 0x00f7: + return "Utc1810_LTCG_CPP" + case 0x00f8: + return "Utc1810_LTCG_MSIL" + case 0x00f9: + return "Utc1810_POGO_I_C" + case 0x00fa: + return "Utc1810_POGO_I_CPP" + case 0x00fb: + return "Utc1810_POGO_O_C" + case 0x00fc: + return "Utc1810_POGO_O_CPP" + case 0x00fd: + return "AliasObj1400" + case 0x00fe: + return "Cvtpgd1900" + case 0x00ff: + return "Cvtres1400" + case 0x0100: + return "Export1400" + case 0x0101: + return "Implib1400" + case 0x0102: + return "Linker1400" + case 0x0103: + return "Masm1400" + case 0x0104: + return "Utc1900_C" + case 0x0105: + return "Utc1900_CPP" + case 0x0106: + return "Utc1900_CVTCIL_C" + case 0x0107: + return "Utc1900_CVTCIL_CPP" + case 0x0108: + return "Utc1900_LTCG_C" + case 0x0109: + return "Utc1900_LTCG_CPP" + case 0x010a: + return "Utc1900_LTCG_MSIL" + case 0x010b: + return ": 'Utc1900_POGO_I_C" + case 0x010c: + return "Utc1900_POGO_I_CPP" + case 0x010d: + return "Utc1900_POGO_O_C" + case 0x010e: + return "Utc1900_POGO_O_CPP" + } + return "?" +} + +// ProdIDtoVSversion retrieves the Visual Studio version from product id. +// list from: https://github.com/kirschju/richheader +func ProdIDtoVSversion(prodID uint16) string { + if prodID > 0x010e || prodID < 0 { + return "" + } + if prodID >= 0x00fd && prodID < (0x010e+1) { + return "Visual Studio 2015 14.00" + } + if prodID >= 0x00eb && prodID < 0x00fd { + return "Visual Studio 2013 12.10" + } + if prodID >= 0x00d9 && prodID < 0x00eb { + return "Visual Studio 2013 12.00" + } + if prodID >= 0x00c7 && prodID < 0x00d9 { + return "Visual Studio 2012 11.00" + } + if prodID >= 0x00b5 && prodID < 0x00c7 { + return "Visual Studio 2010 10.10" + } + if prodID >= 0x0098 && prodID < 0x00b5 { + return "Visual Studio 2010 10.00" + } + if prodID >= 0x0083 && prodID < 0x0098 { + return "Visual Studio 2008 09.00" + } + if prodID >= 0x006d && prodID < 0x0083 { + return "Visual Studio 2005 08.00" + } + if prodID >= 0x005a && prodID < 0x006d { + return "Visual Studio 2003 07.10" + } + if prodID == 1 { + return "Visual Studio" + } + return "" +} diff --git a/section.go b/section.go new file mode 100644 index 0000000..5cc8961 --- /dev/null +++ b/section.go @@ -0,0 +1,555 @@ +// Copyright 2021 Saferwall. All rights reserved. +// Use of this source code is governed by Apache v2 license +// license that can be found in the LICENSE file. + +package pe + +import ( + "encoding/binary" + "math" + "reflect" + "sort" + "strings" +) + +const ( + // ImageScnReserved1 for future use. + ImageScnReserved1 = 0x00000000 + + // ImageScnReserved2 for future use. + ImageScnReserved2 = 0x00000001 + + // ImageScnReserved3 for future use. + ImageScnReserved3 = 0x00000002 + + // ImageScnReserved4 for future use. + ImageScnReserved4 = 0x00000004 + + // ImageScnTypeNoPad indicates the section should not be padded to the next + // boundary. This flag is obsolete and is replaced by ImageScnAlign1Bytes. + // This is valid only for object files. + ImageScnTypeNoPad = 0x00000008 + + // ImageScnReserved5 for future use. + ImageScnReserved5 = 0x00000010 + + // ImageScnCntCode indicates the section contains executable code. + ImageScnCntCode = 0x00000020 + + // ImageScnCntInitializedData indicates the section contains initialized data. + ImageScnCntInitializedData = 0x00000040 + + // ImageScnCntUninitializedData indicates the section contains uninitialized + // data. + ImageScnCntUninitializedData = 0x00000080 + + // ImageScnLnkOther is reserved for future use. + ImageScnLnkOther = 0x00000100 + + // ImageScnLnkInfo indicates the section contains comments or other + // information. The .drectve section has this type. This is valid for + // object files only. + ImageScnLnkInfo = 0x00000200 + + // ImageScnReserved6 for future use. + ImageScnReserved6 = 0x00000400 + + // ImageScnLnkRemove indicates the section will not become part of the image + // This is valid only for object files. + ImageScnLnkRemove = 0x00000800 + + // ImageScnLnkComdat indicates the section contains COMDAT data. For more + // information, see COMDAT Sections (Object Only). This is valid only for + // object files. + ImageScnLnkComdat = 0x00001000 + + // ImageScnGpRel indicates the section contains data referenced through the + // global pointer (GP). + ImageScnGpRel = 0x00008000 + + // ImageScnMemPurgeable is reserved for future use. + ImageScnMemPurgeable = 0x00020000 + + // ImageScnMem16Bit is reserved for future use. + ImageScnMem16Bit = 0x00020000 + + // ImageScnMemLocked is reserved for future use. + ImageScnMemLocked = 0x00040000 + + // ImageScnMemPreload is reserved for future use. + ImageScnMemPreload = 0x00080000 + + // ImageScnAlign1Bytes indicates to align data on a 1-byte boundary. + // Valid only for object files. + ImageScnAlign1Bytes = 0x00100000 + + // ImageScnAlign2Bytes indicates to align data on a 2-byte boundary. + // Valid only for object files. + ImageScnAlign2Bytes = 0x00200000 + + // ImageScnAlign4Bytes indicates to align data on a 4-byte boundary. + // Valid only for object files. + ImageScnAlign4Bytes = 0x00300000 + + // ImageScnAlign8Bytes indicates to align data on a 8-byte boundary. + // Valid only for object files. + ImageScnAlign8Bytes = 0x00400000 + + // ImageScnAlign16Bytes indicates to align data on a 16-byte boundary. + // Valid only for object files. + ImageScnAlign16Bytes = 0x00500000 + + // ImageScnAlign32Bytes indicates to align data on a 32-byte boundary. + // Valid only for object files. + ImageScnAlign32Bytes = 0x00600000 + + // ImageScnAlign64Bytes indicates to align data on a 64-byte boundary. + // Valid only for object files. + ImageScnAlign64Bytes = 0x00700000 + + // ImageScnAlign128Bytes indicates to align data on a 128-byte boundary. + // Valid only for object files. + ImageScnAlign128Bytes = 0x00800000 + + // ImageScnAlign256Bytes indicates to align data on a 256-byte boundary. + // Valid only for object files. + ImageScnAlign256Bytes = 0x00900000 + + // ImageScnAlign512Bytes indicates to align data on a 512-byte boundary. + // Valid only for object files. + ImageScnAlign512Bytes = 0x00A00000 + + // ImageScnAlign1024Bytes indicates to align data on a 1024-byte boundary. + // Valid only for object files. + ImageScnAlign1024Bytes = 0x00B00000 + + // ImageScnAlign2048Bytes indicates to align data on a 2048-byte boundary. + // Valid only for object files. + ImageScnAlign2048Bytes = 0x00C00000 + + // ImageScnAlign4096Bytes indicates to align data on a 4096-byte boundary. + // Valid only for object files. + ImageScnAlign4096Bytes = 0x00D00000 + + // ImageScnAlign8192Bytes indicates to align data on a 8192-byte boundary. + // Valid only for object files. + ImageScnAlign8192Bytes = 0x00E00000 + + // ImageScnLnkMRelocOvfl indicates the section contains extended relocations. + ImageScnLnkMRelocOvfl = 0x01000000 + + // ImageScnMemDiscardable indicates the section can be discarded as needed. + ImageScnMemDiscardable = 0x02000000 + + //ImageScnMemNotCached indicates the section cannot be cached. + ImageScnMemNotCached = 0x04000000 + + // ImageScnMemNotPaged indicates the section is not pageable. + ImageScnMemNotPaged = 0x08000000 + + // ImageScnMemShared indicates the section can be shared in memory. + ImageScnMemShared = 0x10000000 + + // ImageScnMemExecute indicates the section can be executed as code. + ImageScnMemExecute = 0x20000000 + + // ImageScnMemRead indicates the section can be read. + ImageScnMemRead = 0x40000000 + + // ImageScnMemWrite indicates the section can be written to. + ImageScnMemWrite = 0x80000000 +) + +// ImageSectionHeader is part of the section table , in fact section table is an +// array of Image Section Header each contains information about one section of +// the whole file such as attribute,virtual offset. the array size is the number +// of sections in the file. +// Binary Spec : each struct is 40 byte and there is no padding . +type ImageSectionHeader struct { + + // An 8-byte, null-padded UTF-8 encoded string. If the string is exactly 8 + // characters long, there is no terminating null. For longer names, this + // field contains a slash (/) that is followed by an ASCII representation of + // a decimal number that is an offset into the string table. Executable + // images do not use a string table and do not support section names longer + // than 8 characters. Long names in object files are truncated if they are + // emitted to an executable file. + Name [8]uint8 + + // The total size of the section when loaded into memory. If this value is + // greater than SizeOfRawData, the section is zero-padded. This field is + // valid only for executable images and should be set to zero for object files. + VirtualSize uint32 + + // For executable images, the address of the first byte of the section + // relative to the image base when the section is loaded into memory. + // For object files, this field is the address of the first byte before + // relocation is applied; for simplicity, compilers should set this to zero. + // Otherwise, it is an arbitrary value that is subtracted from offsets during + // relocation. + VirtualAddress uint32 + + // The size of the section (for object files) or the size of the initialized + // data on disk (for image files). For executable images, this must be a + // multiple of FileAlignment from the optional header. If this is less than + // VirtualSize, the remainder of the section is zero-filled. Because the + // SizeOfRawData field is rounded but the VirtualSize field is not, it is + // possible for SizeOfRawData to be greater than VirtualSize as well. When + // a section contains only uninitialized data, this field should be zero. + SizeOfRawData uint32 + + // The file pointer to the first page of the section within the COFF file. + // For executable images, this must be a multiple of FileAlignment from the + // optional header. For object files, the value should be aligned on a + // 4-byte boundary for best performance. When a section contains only + // uninitialized data, this field should be zero. + PointerToRawData uint32 + + // The file pointer to the beginning of relocation entries for the section. + // This is set to zero for executable images or if there are no relocations. + PointerToRelocations uint32 + + // The file pointer to the beginning of line-number entries for the section. + // This is set to zero if there are no COFF line numbers. This value should + // be zero for an image because COFF debugging information is deprecated. + PointerToLineNumbers uint32 + + // The number of relocation entries for the section. + // This is set to zero for executable images. + NumberOfRelocations uint16 + + // The number of line-number entries for the section. This value should be + // zero for an image because COFF debugging information is deprecated. + NumberOfLineNumbers uint16 + + // The flags that describe the characteristics of the section. + Characteristics uint32 +} + +// Section represents a PE section header, plus additionnal data like entropy. +type Section struct { + Header ImageSectionHeader + Entropy float64 `json:",omitempty"` +} + +// ParseSectionHeader parses the PE section headers. Each row of the section +// table is, in effect, a section header. It must immediately follow the PE +// header. +func (pe *File) ParseSectionHeader() (err error) { + + // Get the first section offset. + optionalHeaderOffset := pe.DosHeader.AddressOfNewEXEHeader + 4 + + uint32(binary.Size(pe.NtHeader.FileHeader)) + offset := optionalHeaderOffset + + uint32(pe.NtHeader.FileHeader.SizeOfOptionalHeader) + + // Track invalid/suspicous values while parsing sections. + maxErr := 3 + + secHeader := ImageSectionHeader{} + numberOfSections := pe.NtHeader.FileHeader.NumberOfSections + secHeaderSize := uint32(binary.Size(secHeader)) + + // The section header indexing in the table is one-based, with the order of + // the sections defined by the linker. The sections follow one another + // contiguously in the order defined by the section header table, with + // starting RVAs aligned by the value of the SectionAlignment field of the + // PE header. + for i := uint16(0); i < numberOfSections; i++ { + err := pe.structUnpack(&secHeader, offset, secHeaderSize) + if err != nil { + return err + } + + countErr := 0 + sec := Section{Header: secHeader} + secName := sec.NameString() + + if (ImageSectionHeader{}) == secHeader { + pe.Anomalies = append(pe.Anomalies, "Section `"+secName+"` Contents are null-bytes") + countErr++ + } + + if secHeader.SizeOfRawData+secHeader.PointerToRawData > pe.size { + pe.Anomalies = append(pe.Anomalies, "Section `"+secName+ + "` SizeOfRawData is larger than file") + countErr++ + } + + if pe.adjustFileAlignment(secHeader.PointerToRawData) > pe.size { + pe.Anomalies = append(pe.Anomalies, "Section `"+secName+ + "` PointerToRawData points beyond the end of the file") + countErr++ + } + + if secHeader.VirtualSize > 0x10000000 { + pe.Anomalies = append(pe.Anomalies, "Section `"+secName+ + "` VirtualSize is extremely large > 256MiB") + countErr++ + } + + if pe.adjustSectionAlignment(secHeader.VirtualAddress) > 0x10000000 { + pe.Anomalies = append(pe.Anomalies, "Section `"+secName+ + "` VirtualAddress is beyond 0x10000000") + countErr++ + } + + var fileAlignment uint32 + switch pe.Is64 { + case true: + fileAlignment = pe.NtHeader.OptionalHeader.(ImageOptionalHeader64).FileAlignment + case false: + fileAlignment = pe.NtHeader.OptionalHeader.(ImageOptionalHeader32).FileAlignment + } + if fileAlignment != 0 && secHeader.PointerToRawData%fileAlignment != 0 { + pe.Anomalies = append(pe.Anomalies, "Section `"+secName+ + "` PointerToRawData is not multiple of FileAlignment") + countErr++ + } + + if countErr >= maxErr { + break + } + + // Append to the list of sections. + if pe.opts.SectionEntropy { + sec.Entropy = sec.CalculateEntropy(pe) + } + pe.Sections = append(pe.Sections, sec) + + offset += secHeaderSize + } + + // Sort the sections by their VirtualAddress. This will allow to check + // for potentially overlapping sections in badly constructed PEs. + sort.Sort(byVirtualAddress(pe.Sections)) + + if pe.NtHeader.FileHeader.NumberOfSections > 0 && len(pe.Sections) > 0 { + offset += secHeaderSize * uint32(pe.NtHeader.FileHeader.NumberOfSections) + } + + // There could be a problem if there are no raw data sections + // greater than 0. Example: fc91013eb72529da005110a3403541b6 + // Should this throw an exception in the minimum header offset + // can't be found? + var rawDataPointers []uint32 + for _, sec := range pe.Sections { + if sec.Header.PointerToRawData > 0 { + rawDataPointers = append( + rawDataPointers, pe.adjustFileAlignment( + sec.Header.PointerToRawData)) + } + } + + var lowestSectionOffset uint32 + if len(rawDataPointers) > 0 { + lowestSectionOffset = Min(rawDataPointers) + } else { + lowestSectionOffset = 0 + } + + if lowestSectionOffset == 0 || lowestSectionOffset < offset { + if offset <= pe.size { + pe.Header = pe.data[:offset] + } + } else { + if lowestSectionOffset <= pe.size { + pe.Header = pe.data[:lowestSectionOffset] + } + } + + return nil +} + +// NameString returns string representation of a ImageSectionHeader.Name field. +func (section *Section) NameString() string { + return strings.Replace(string(section.Header.Name[:]), "\x00", "", -1) +} + +// NextHeaderAddr returns the VirtualAddress of the next section. +func (section *Section) NextHeaderAddr(pe *File) uint32 { + for i, currentSection := range pe.Sections { + if i == len(pe.Sections)-1 { + return 0 + } + + if reflect.DeepEqual(section.Header, ¤tSection.Header) { + return pe.Sections[i+1].Header.VirtualAddress + } + } + + return 0 +} + +// Contains checks whether the section contains a given RVA. +func (section *Section) Contains(rva uint32, pe *File) bool { + + // Check if the SizeOfRawData is realistic. If it's bigger than the size of + // the whole PE file minus the start address of the section it could be + // either truncated or the SizeOfRawData contains a misleading value. + // In either of those cases we take the VirtualSize. + + var size uint32 + adjustedPointer := pe.adjustFileAlignment(section.Header.PointerToRawData) + if uint32(len(pe.data))-adjustedPointer < section.Header.SizeOfRawData { + size = section.Header.VirtualSize + } else { + size = Max(section.Header.SizeOfRawData, section.Header.VirtualSize) + } + vaAdj := pe.adjustSectionAlignment(section.Header.VirtualAddress) + + // Check whether there's any section after the current one that starts before + // the calculated end for the current one. If so, cut the current section's + // size to fit in the range up to where the next section starts. + if section.NextHeaderAddr(pe) != 0 && + section.NextHeaderAddr(pe) > section.Header.VirtualAddress && + vaAdj+size > section.NextHeaderAddr(pe) { + size = section.NextHeaderAddr(pe) - vaAdj + } + + return vaAdj <= rva && rva < vaAdj+size +} + +// Data returns a data chunk from a section. +func (section *Section) Data(start, length uint32, pe *File) []byte { + + pointerToRawDataAdj := pe.adjustFileAlignment( + section.Header.PointerToRawData) + virtualAddressAdj := pe.adjustSectionAlignment( + section.Header.VirtualAddress) + + var offset uint32 + if start == 0 { + offset = pointerToRawDataAdj + } else { + offset = (start - virtualAddressAdj) + pointerToRawDataAdj + } + + if offset > pe.size { + return nil + } + + var end uint32 + if length != 0 { + end = offset + length + } else { + end = offset + section.Header.SizeOfRawData + } + + // PointerToRawData is not adjusted here as we might want to read any + // possible extra bytes that might get cut off by aligning the start (and + // hence cutting something off the end) + if end > section.Header.PointerToRawData+section.Header.SizeOfRawData && + section.Header.PointerToRawData+section.Header.SizeOfRawData > offset { + end = section.Header.PointerToRawData + section.Header.SizeOfRawData + } + + if end > pe.size { + end = pe.size + } + + return pe.data[offset:end] +} + +// CalculateEntropy calculates section entropy. +func (section *Section) CalculateEntropy(pe *File) float64 { + sectionData := section.Data(0, 0, pe) + if sectionData == nil { + return 0.0 + } + + sectionSize := float64(len(sectionData)) + if sectionSize == 0.0 { + return 0.0 + } + + var frequencies [256]uint64 + for _, v := range section.Data(0, 0, pe) { + frequencies[v]++ + } + + var entropy float64 + for _, p := range frequencies { + if p > 0 { + freq := float64(p) / sectionSize + entropy += freq * math.Log2(freq) + } + } + + return -entropy +} + +// byVirtualAddress sorts all sections by Virtual Address. +type byVirtualAddress []Section + +func (s byVirtualAddress) Len() int { return len(s) } +func (s byVirtualAddress) Swap(i, j int) { s[i], s[j] = s[j], s[i] } +func (s byVirtualAddress) Less(i, j int) bool { + return s[i].Header.VirtualAddress < s[j].Header.VirtualAddress +} + +// byPointerToRawData sorts all sections by PointerToRawData. +type byPointerToRawData []Section + +func (s byPointerToRawData) Len() int { return len(s) } +func (s byPointerToRawData) Swap(i, j int) { s[i], s[j] = s[j], s[i] } +func (s byPointerToRawData) Less(i, j int) bool { + return s[i].Header.PointerToRawData < s[j].Header.PointerToRawData +} + +// PrettySectionFlags returns the string representations of the +// `Flags` field of section header. +func (pe *File) PrettySectionFlags(curSectionFlag uint32) []string { + var values []string + + sectionFlags := map[uint32]string{ + ImageScnReserved1: "Reserved1", + ImageScnReserved2: "Reserved2", + ImageScnReserved3: "Reserved3", + ImageScnReserved4: "Reserved4", + ImageScnTypeNoPad: "No Padd", + ImageScnReserved5: "Reserved5", + ImageScnCntCode: "Contains Code", + ImageScnCntInitializedData: "Initialized Data", + ImageScnCntUninitializedData: "Uninitialized Data", + ImageScnLnkOther: "Lnk Other", + ImageScnLnkInfo: "Lnk Info", + ImageScnReserved6: "Reserved6", + ImageScnLnkRemove: "LnkRemove", + ImageScnLnkComdat: "LnkComdat", + ImageScnGpRel: "GpReferenced", + ImageScnMemPurgeable: "Purgeable", + ImageScnMemLocked: "Locked", + ImageScnMemPreload: "Preload", + ImageScnAlign1Bytes: "Align1Bytes", + ImageScnAlign2Bytes: "Align2Bytes", + ImageScnAlign4Bytes: "Align4Bytes", + ImageScnAlign8Bytes: "Align8Bytes", + ImageScnAlign16Bytes: "Align16Bytes", + ImageScnAlign32Bytes: "Align32Bytes", + ImageScnAlign64Bytes: "Align64Bytes", + ImageScnAlign128Bytes: "Align128Bytes", + ImageScnAlign256Bytes: "Align265Bytes", + ImageScnAlign512Bytes: "Align512Bytes", + ImageScnAlign1024Bytes: "Align1024Bytes", + ImageScnAlign2048Bytes: "Align2048Bytes", + ImageScnAlign4096Bytes: "Align4096Bytes", + ImageScnAlign8192Bytes: "Align8192Bytes", + ImageScnLnkMRelocOvfl: "ExtendedReloc", + ImageScnMemDiscardable: "Discardable", + ImageScnMemNotCached: "NotCached", + ImageScnMemNotPaged: "NotPaged", + ImageScnMemShared: "Shared", + ImageScnMemExecute: "Executable", + ImageScnMemRead: "Readable", + ImageScnMemWrite: "Writable", + } + + for k, s := range sectionFlags { + if k&curSectionFlag != 0 { + values = append(values, s) + } + } + + return values +} diff --git a/security.go b/security.go new file mode 100644 index 0000000..13d5ffe --- /dev/null +++ b/security.go @@ -0,0 +1,320 @@ +// Copyright 2021 Saferwall. All rights reserved. +// Use of this source code is governed by Apache v2 license +// license that can be found in the LICENSE file. + +package pe + +import ( + "crypto/sha256" + "crypto/x509" + "encoding/binary" + "errors" + "reflect" + "sort" + "time" + "unsafe" + + "go.mozilla.org/pkcs7" +) + +// The options for the WIN_CERTIFICATE Revision member include +// (but are not limited to) the following. +const ( + // WinCertRevision1_0 represents the WIN_CERT_REVISION_1_0 Version 1, + // legacy version of the Win_Certificate structure. + // It is supported only for purposes of verifying legacy Authenticode + // signatures + WinCertRevision1_0 = 0x0100 + + // WinCertRevision2_0 represents the WIN_CERT_REVISION_2_0. Version 2 + // is the current version of the Win_Certificate structure. + WinCertRevision2_0 = 0x0200 +) + +// The options for the WIN_CERTIFICATE CertificateType member include +// (but are not limited to) the items in the following table. Note that some +// values are not currently supported. +const ( + // Certificate contains an X.509 Certificate (Not Supported) + WinCertTypeX509 = 0x0001 + + // Certificate contains a PKCS#7 SignedData structure. + WinCertTypePKCSSignedData = 0x0002 + + // Reserved. + WinCertTypeReserved1 = 0x0003 + + // Terminal Server Protocol Stack Certificate signing (Not Supported). + WinCertTypeTsStackSigned = 0x0004 +) + +var ( + errSecurityDataDirOutOfBands = errors.New( + `Boundary checks failed in security data directory`) +) + +// Certificate directory. +type Certificate struct { + Header WinCertificate + Content *pkcs7.PKCS7 `json:"-"` + Info CertInfo + Verified bool +} + +// WinCertificate encapsulates a signature used in verifying executable files. +type WinCertificate struct { + // Specifies the length, in bytes, of the signature. + Length uint32 + + // Specifies the certificate revision. + Revision uint16 + + // Specifies the type of certificate. + CertificateType uint16 +} + +// CertInfo wraps the important fields of the pkcs7 structure. +// This is what we what keep in JSON marshalling. +type CertInfo struct { + // The certificate authority (CA) that charges customers to issue + // certificates for them. + Issuer string + + // The subject of the certificate is the entity its public key is associated + // with (i.e. the "owner" of the certificate). + Subject string + + // The certificate won't be valid after this timestamp. + NotBefore time.Time + + // The certificate won't be valid after this timestamp. + NotAfter time.Time +} + +// Authentihash generates the pe image file hash. +// The relevant sections to exclude during hashing are: +// - The location of the checksum +// - The location of the entry of the Certificate Table in the Data Directory +// - The location of the Certificate Table. +func (pe *File) Authentihash() []byte { + + // Declare some vars. + var certDirSize uint32 + var sizeOfHeaders uint32 + var dataDirOffset uint32 + + // Initialize a hash algorithm context. + h := sha256.New() + + // Hash the image header from its base to immediately before the start of + // the checksum address, as specified in Optional Header Windows-Specific + // Fields. + start := uint32(0) + optionalHeaderOffset := pe.DosHeader.AddressOfNewEXEHeader + uint32(binary.Size(pe.NtHeader)) + checksumOffset := optionalHeaderOffset + 64 + h.Write(pe.data[start:checksumOffset]) + + // Skip over the checksum, which is a 4-byte field. + start += checksumOffset + uint32(4) + + // Hash everything from the end of the checksum field to immediately before + // the start of the Certificate Table entry, as specified in Optional Header + // Data Directories. + oh32 := ImageOptionalHeader32{} + oh64 := ImageOptionalHeader64{} + switch pe.Is64 { + case true: + oh64 = pe.NtHeader.OptionalHeader.(ImageOptionalHeader64) + certDirSize = oh64.DataDirectory[ImageDirectoryEntryCertificate].Size + sizeOfHeaders = oh64.SizeOfHeaders + dataDirOffset = uint32(unsafe.Offsetof(oh64.DataDirectory)) + case false: + oh32 = pe.NtHeader.OptionalHeader.(ImageOptionalHeader32) + certDirSize = oh32.DataDirectory[ImageDirectoryEntryCertificate].Size + sizeOfHeaders = oh32.SizeOfHeaders + dataDirOffset = uint32(unsafe.Offsetof(oh32.DataDirectory)) + } + securityDirOffset := optionalHeaderOffset + dataDirOffset + securityDirOffset += uint32( + binary.Size(DataDirectory{}) * ImageDirectoryEntryCertificate) + h.Write(pe.data[start:securityDirOffset]) + + // Skip over the Certificate Table entry, which is 8 bytes long. + start = securityDirOffset + uint32(8) + + // Hash everything from the end of the Certificate Table entry to the end of + // image header, including Section Table (headers). + endPeHeader := uint32(len(pe.Header)) + h.Write(pe.data[start:endPeHeader]) + + // Create a counter called SUM_OF_BYTES_HASHED, which is not part of the + // signature. Set this counter to the SizeOfHeaders field, as specified in + // Optional Header Windows-Specific Field. + SumOfBytesHashes := sizeOfHeaders + + // Build a temporary table of pointers to all of the section headers in the + // image. The NumberOfSections field of COFF File Header indicates how big + // the table should be. Do not include any section headers in the table + // whose SizeOfRawData field is zero. + sections := []Section{} + for _, section := range pe.Sections { + if section.Header.SizeOfRawData != 0 { + sections = append(sections, section) + } + } + + // Using the PointerToRawData field (offset 20) in the referenced + // SectionHeader structure as a key, arrange the table's elements in + // ascending order. In other words, sort the section headers in ascending + // order according to the disk-file offset of the sections. + sort.Sort(byPointerToRawData(sections)) + + // Walk through the sorted table, load the corresponding section into + // memory, and hash the entire section. Use the SizeOfRawData field in the + // SectionHeader structure to determine the amount of data to hash. + // Add the section’s SizeOfRawData value to SUM_OF_BYTES_HASHED. + for _, s := range sections { + sectionData := pe.data[s.Header.PointerToRawData : s.Header.PointerToRawData+s.Header.SizeOfRawData] + SumOfBytesHashes += s.Header.SizeOfRawData + h.Write(sectionData) + } + + // Create a value called FILE_SIZE, which is not part of the signature. + // Set this value to the image’s file size, acquired from the underlying + // file system. If FILE_SIZE is greater than SUM_OF_BYTES_HASHED, the file + // contains extra data that must be added to the hash. This data begins at + // the SUM_OF_BYTES_HASHED file offset, and its length is: + // (File Size) – ((Size of AttributeCertificateTable) + SUM_OF_BYTES_HASHED) + if pe.size > SumOfBytesHashes { + length := pe.size - (certDirSize + SumOfBytesHashes) + extraData := pe.data[SumOfBytesHashes : SumOfBytesHashes+length] + h.Write(extraData) + } + + return h.Sum(nil) +} + +// The security directory contains the authenticode signature, which is a digital +// signature format that is used, among other purposes, to determine the origin +// and integrity of software binaries. Authenticode is based on the Public-Key +// Cryptography Standards (PKCS) #7 standard and uses X.509 v3 certificates to +// bind an Authenticode-signed file to the identity of a software publisher. +// This data are not loaded into memory as part of the image file. +func (pe *File) parseSecurityDirectory(rva, size uint32) error { + + var pkcs *pkcs7.PKCS7 + var isValid bool + certInfo := CertInfo{} + certHeader := WinCertificate{} + certSize := uint32(binary.Size(certHeader)) + + // The virtual address value from the Certificate Table entry in the + // Optional Header Data Directory is a file offset to the first attribute + // certificate entry. + fileOffset := rva + + for { + err := pe.structUnpack(&certHeader, fileOffset, certSize) + if err != nil { + return errSecurityDataDirOutOfBands + } + + if fileOffset+certHeader.Length > pe.size { + return errSecurityDataDirOutOfBands + } + + certContent := pe.data[fileOffset+certSize : fileOffset+certHeader.Length] + pkcs, err = pkcs7.Parse(certContent) + if err != nil { + pe.Certificates = &Certificate{Header: certHeader} + return err + } + + // The pkcs7.PKCS7 structure contains many fields that we are not + // interested to, so create another structure, similar to _CERT_INFO + // structure which contains only the imporant information. + serialNumber := pkcs.Signers[0].IssuerAndSerialNumber.SerialNumber + for _, cert := range pkcs.Certificates { + if !reflect.DeepEqual(cert.SerialNumber, serialNumber) { + continue + } + + certInfo.NotAfter = cert.NotAfter + certInfo.NotBefore = cert.NotBefore + + // Issuer infos + if len(cert.Issuer.Country) > 0 { + certInfo.Issuer = cert.Issuer.Country[0] + } + + if len(cert.Issuer.Province) > 0 { + certInfo.Issuer += ", " + cert.Issuer.Province[0] + } + + if len(cert.Issuer.Locality) > 0 { + certInfo.Issuer += ", " + cert.Issuer.Locality[0] + } + + certInfo.Issuer += ", " + cert.Issuer.CommonName + + // Subject infos + if len(cert.Subject.Country) > 0 { + certInfo.Subject = cert.Subject.Country[0] + } + + if len(cert.Subject.Province) > 0 { + certInfo.Subject += ", " + cert.Subject.Province[0] + } + + if len(cert.Subject.Locality) > 0 { + certInfo.Subject += ", " + cert.Subject.Locality[0] + } + + if len(cert.Subject.Organization) > 0 { + certInfo.Subject += ", " + cert.Subject.Organization[0] + } + + certInfo.Subject += ", " + cert.Subject.CommonName + + break + } + + // Verify the signature. This will also verify the chain of trust of the + // the end-entity signer cert to one of the root in the truststore. + // Let's load the system root certs. + var certPool *x509.CertPool + skipCertVerification := false + certPool, err = x509.SystemCertPool() + if err != nil { + skipCertVerification = true + } + + // SystemCertPool() return an error in Windows, so we skip verification + // for now. + if skipCertVerification { + err = pkcs.VerifyWithChain(certPool) + if err == nil { + isValid = true + } else { + isValid = false + } + } + + // Subsequent entries are accessed by advancing that entry's dwLength + // bytes, rounded up to an 8-byte multiple, from the start of the + // current attribute certificate entry. + nextOffset := certHeader.Length + fileOffset + nextOffset = ((nextOffset + 8 - 1) / 8) * 8 + + // Check if we walked the entire table. + if nextOffset == fileOffset+size { + break + } + + fileOffset = nextOffset + } + + pe.Certificates = &Certificate{Header: certHeader, Content: pkcs, + Info: certInfo, Verified: isValid} + return nil +} diff --git a/symbol.go b/symbol.go new file mode 100644 index 0000000..ad64be8 --- /dev/null +++ b/symbol.go @@ -0,0 +1,409 @@ +package pe + +import ( + "bytes" + "encoding/binary" + "errors" + "strings" +) + +const ( + // + // Type Representation + // + + // ImageSymTypeNull indicates no type information or unknown base type. + // Microsoft tools use this setting. + ImageSymTypeNull = 0 + + // ImageSymTypeVoid indicates no type no valid type; used with void pointers and functions. + ImageSymTypeVoid = 1 + + // ImageSymTypeChar indicates a character (signed byte). + ImageSymTypeChar = 2 + + // ImageSymTypeShort indicates a 2-byte signed integer. + ImageSymTypeShort = 3 + + // ImageSymTypeInt indicates a natural integer type (normally 4 bytes in + // Windows). + ImageSymTypeInt = 4 + + // ImageSymTypeLong indicates a 4-byte signed integer. + ImageSymTypeLong = 5 + + // ImageSymTypeFloat indicates a 4-byte floating-point number. + ImageSymTypeFloat = 6 + + // ImageSymTypeDouble indicates an 8-byte floating-point number. + ImageSymTypeDouble = 7 + + // ImageSymTypeStruct indicates a structure. + ImageSymTypeStruct = 8 + + // ImageSymTypeUnion indicates a union. + ImageSymTypeUnion = 9 + + // ImageSymTypeEnum indicates an enumerated type. + ImageSymTypeEnum = 10 + + // ImageSymTypeMoe A member of enumeration (a specific value). + ImageSymTypeMoe = 11 + + // ImageSymTypeByte indicates a byte; unsigned 1-byte integer. + ImageSymTypeByte = 12 + + // ImageSymTypeWord indicates a word; unsigned 2-byte integer. + ImageSymTypeWord = 13 + + // ImageSymTypeUint indicates an unsigned integer of natural size + // (normally, 4 bytes). + ImageSymTypeUint = 14 + + // ImageSymTypeDword indicates an unsigned 4-byte integer. + ImageSymTypeDword = 15 + + // + // Storage Class + // + + // ImageSymClassEndOfFunction indicates a special symbol that represents + // the end of function, for debugging purposes. + ImageSymClassEndOfFunction = 0xff + + // ImageSymClassNull indicates no assigned storage class. + ImageSymClassNull = 0 + + // ImageSymClassAutomatic indicates automatic (stack) variable. The Value + // field specifies the stack frame offset. + ImageSymClassAutomatic = 1 + + // ImageSymClassExternal indicates a value that Microsoft tools use for + // external symbols. The Value field indicates the size if the section + // number is IMAGE_SYM_UNDEFINED (0). If the section number is not zero, + // then the Value field specifies the offset within the section. + ImageSymClassExternal = 2 + + // ImageSymClassStatic indicates the offset of the symbol within the + // section. If the Value field is zero, then the symbol represents a + // section name. + ImageSymClassStatic = 3 + + // ImageSymClassRegister indicates a register variable. The Value field + // specifies the register number. + ImageSymClassRegister = 4 + + // ImageSymClassExternalDef indicates a symbol that is defined externally. + ImageSymClassExternalDef = 5 + + // ImageSymClassLabel indicates a code label that is defined within the + // module. The Value field specifies the offset of the symbol within the + // section. + ImageSymClassLabel = 6 + + // ImageSymClassUndefinedLabel indicates a reference to a code label that + // is not defined. + ImageSymClassUndefinedLabel = 7 + + // ImageSymClassMemberOfStruct indicates the structure member. The Value + // field specifies the n th member. + ImageSymClassMemberOfStruct = 8 + + // ImageSymClassArgument indicates a formal argument (parameter) of a + // function. The Value field specifies the n th argument. + ImageSymClassArgument = 9 + + // ImageSymClassStructTag indicates the structure tag-name entry. + ImageSymClassStructTag = 10 + + // ImageSymClassMemberOfUnion indicates a union member. The Value field + // specifies the n th member. + ImageSymClassMemberOfUnion = 11 + + // ImageSymClassUnionTag indicates the structure tag-name entry. + ImageSymClassUnionTag = 12 + + // ImageSymClassTypeDefinition indicates a typedef entry. + ImageSymClassTypeDefinition = 13 + + // ImageSymClassUndefinedStatic indicates a static data declaration. + ImageSymClassUndefinedStatic = 14 + + // ImageSymClassEnumTag indicates an enumerated type tagname entry. + ImageSymClassEnumTag = 15 + + // ImageSymClassMemberOfEnum indicates a member of an enumeration. The + // Value field specifies the n th member. + ImageSymClassMemberOfEnum = 16 + + // ImageSymClassRegisterParam indicates a register parameter. + ImageSymClassRegisterParam = 17 + + // ImageSymClassBitField indicates a bit-field reference. The Value field + // specifies the n th bit in the bit field. + ImageSymClassBitField = 18 + + // ImageSymClassBlock indicates a .bb (beginning of block) or .eb (end of + // block) record. The Value field is the relocatable address of the code + // location. + ImageSymClassBlock = 100 + + // ImageSymClassFunction indicates a value that Microsoft tools use for + // symbol records that define the extent of a function: begin function (.bf + // ), end function ( .ef ), and lines in function ( .lf ). For .lf + // records, the Value field gives the number of source lines in the + // function. For .ef records, the Value field gives the size of the + // function code. + ImageSymClassFunction = 101 + + // ImageSymClassEndOfStruct indicates an end-of-structure entry. + ImageSymClassEndOfStruct = 102 + + // ImageSymClassFile indicates a value that Microsoft tools, as well as + // traditional COFF format, use for the source-file symbol record. The + // symbol is followed by auxiliary records that name the file. + ImageSymClassFile = 103 + + // ImageSymClassSsection indicates a definition of a section (Microsoft + // tools use STATIC storage class instead). + ImageSymClassSsection = 104 + + // ImageSymClassWeakExternal indicates a weak external. For more + // information, see Auxiliary Format 3: Weak Externals. + ImageSymClassWeakExternal = 24 + + // ImageSymClassClrToken indicates a CLR token symbol. The name is an ASCII + // string that consists of the hexadecimal value of the token. For more + // information, see CLR Token Definition (Object Only). + ImageSymClassClrToken = 25 + + // + // Section Number Values. + // + + // ImageSymUndefined indicates that the symbol record is not yet assigned a + // section. A value of zero indicates that a reference to an external + // symbol is defined elsewhere. A value of non-zero is a common symbol with + // a size that is specified by the value. + ImageSymUndefined = 0 + + // ImageSymAbsolute indicates that the symbol has an absolute + // (non-relocatable) value and is not an address. + ImageSymAbsolute = -1 + + // ImageSymDebug indicates that the symbol provides general type or + // debugging information but does not correspond to a section. Microsoft + // tools use this setting along with .file records (storage class FILE). + ImageSymDebug = -2 +) + +var ( + errCOFFTableNotPresent = errors.New("PE image does not countains a COFF symbol table") + errNoCOFFStringInTable = errors.New("PE image got a PointerToSymbolTable but no string in the COFF string table") + errCOFFSymbolOutOfBounds = errors.New("COFF symbol offset out of bounds") +) + +// COFFSymbol represents an entry in the COFF symbol table, which it is an +// array of records, each 18 bytes long. Each record is either a standard or +// auxiliary symbol-table record. A standard record defines a symbol or name +// and has the following format. +type COFFSymbol struct { + // The name of the symbol, represented by a union of three structures. An + // array of 8 bytes is used if the name is not more than 8 bytes long. + // union { + // BYTE ShortName[8]; + // struct { + // DWORD Short; // if 0, use LongName + // DWORD Long; // offset into string table + // } Name; + // DWORD LongName[2]; // PBYTE [2] + // } N; + Name [8]byte + + // The value that is associated with the symbol. The interpretation of this + // field depends on SectionNumber and StorageClass. A typical meaning is + // the relocatable address. + Value uint32 + + // The signed integer that identifies the section, using a one-based index + // into the section table. Some values have special meaning, as defined in section 5.4.2, "Section Number Values." + SectionNumber int16 + + // A number that represents type. Microsoft tools set this field to 0x20 (function) or 0x0 (not a function). For more information, see Type Representation. + Type uint16 + + // An enumerated value that represents storage class. For more information, see Storage Class. + StorageClass uint8 + + // The number of auxiliary symbol table entries that follow this record. + NumberOfAuxSymbols uint8 +} + +// COFF holds properties related to the COFF format. +type COFF struct { + SymbolTable []COFFSymbol + StringTable []string + StringTableOffset uint32 + StringTableM map[uint32]string // Map the symbol offset => symbol name. +} + +// ParseCOFFSymbolTable parses the COFF symbol table. The symbol table is +// inherited from the traditional COFF format. It is distinct from Microsoft +// Visual C++ debug information. A file can contain both a COFF symbol table +// and Visual C++ debug information, and the two are kept separate. Some +// Microsoft tools use the symbol table for limited but important purposes, +// such as communicating COMDAT information to the linker. Section names and +// file names, as well as code and data symbols, are listed in the symbol table. +func (pe *File) ParseCOFFSymbolTable() error { + pointerToSymbolTable := pe.NtHeader.FileHeader.PointerToSymbolTable + if pointerToSymbolTable == 0 { + return errCOFFTableNotPresent + } + + size := uint32(binary.Size(COFFSymbol{})) + symCount := pe.NtHeader.FileHeader.NumberOfSymbols + offset := pe.NtHeader.FileHeader.PointerToSymbolTable + symbols := make([]COFFSymbol, symCount) + + for i := uint32(0); i < symCount; i++ { + err := pe.structUnpack(&symbols[i], offset, size) + if err != nil { + return err + } + offset += size + } + pe.COFF.SymbolTable = symbols + + // Get the COFF string table. + pe.COFFStringTable() + + return nil +} + +// COFFStringTable retrieves the list of strings in the COFF string table if +// any. +func (pe *File) COFFStringTable() error { + m := make(map[uint32]string) + pointerToSymbolTable := pe.NtHeader.FileHeader.PointerToSymbolTable + if pointerToSymbolTable == 0 { + return errCOFFTableNotPresent + } + + // COFF String Table immediately following the COFF symbol table. The + // position of this table is found by taking the symbol table address in + // the COFF header and adding the number of symbols multiplied by the size + // of a symbol. + size := uint32(binary.Size(COFFSymbol{})) + symCount := pe.NtHeader.FileHeader.NumberOfSymbols + offset := pointerToSymbolTable + (size * symCount) + pe.COFF.StringTableOffset = offset + + // At the beginning of the COFF string table are 4 bytes that contain the + // total size (in bytes) of the rest of the string table. This size + // includes the size field itself, so that the value in this location would + // be 4 if no strings were present. + strTableSize, err := pe.ReadUint32(offset) + if err != nil { + return err + } + if strTableSize <= 4 { + return errNoCOFFStringInTable + } + offset += 4 + + // Following the size are null-terminated strings that are pointed to by + // symbols in the COFF symbol table. We create a map to map offset to + // string. + end := offset + strTableSize + for offset <= end { + len, str := pe.readASCIIStringAtOffset(offset, 0x30) + if len == 0 { + break + } + m[offset] = str + offset += len + 1 + pe.COFF.StringTable = append(pe.COFF.StringTable, str) + } + + pe.COFF.StringTableM = m + return nil +} + +// String returns represenation of the symbol name. +func (symbol *COFFSymbol) String(pe *File) (string, error) { + // contain the name itself, if it is not more than 8 bytes long, or the + // ShortName field gives an offset into the string table. To determine + // whether the name itself or an offset is given, test the first 4 + // bytes for equality to zero. + var short, long uint32 + highDw := bytes.NewBuffer(symbol.Name[4:]) + lowDw := bytes.NewBuffer(symbol.Name[:4]) + errl := binary.Read(lowDw, binary.LittleEndian, &short) + errh := binary.Read(highDw, binary.LittleEndian, &long) + if errl != nil || errh != nil { + return "", errCOFFSymbolOutOfBounds + } + + // if 0, use LongName. + if short != 0 { + name := strings.Replace(string(symbol.Name[:]), "\x00", "", -1) + return name, nil + } + + // Long name offset to the string table. + strOff := pe.COFF.StringTableOffset + long + name := pe.COFF.StringTableM[strOff] + return name, nil +} + +// SectionNumberName returns the name of the section corresponding to a section +// symbol number if any. +func (symbol *COFFSymbol) SectionNumberName(pe *File) string { + + // Normally, the Section Value field in a symbol table entry is a one-based + // index into the section table. However, this field is a signed integer + // and can take negative values. The following values, less than one, have + // special meanings. + if symbol.SectionNumber > 0 && symbol.SectionNumber < int16(len(pe.Sections)) { + return pe.Sections[symbol.SectionNumber-1].NameString() + } + + switch symbol.SectionNumber { + case ImageSymUndefined: + return "Undefined" + case ImageSymAbsolute: + return "Absolute" + case ImageSymDebug: + return "Debug" + } + + return "Unknown" +} + +// PrettyCOFFTypeRepresentation returns the string representation of the `Type` +// field of a COFF table entry. +func (pe *File) PrettyCOFFTypeRepresentation(k uint8) string { + coffSymTypeMap := map[uint8]string{ + ImageSymTypeNull: "Null", + ImageSymTypeVoid: "Void", + ImageSymTypeChar: "Char", + ImageSymTypeShort: "Short", + ImageSymTypeInt: "Int", + ImageSymTypeLong: "Long", + ImageSymTypeFloat: "Float", + ImageSymTypeDouble: "Double", + ImageSymTypeStruct: "Struct", + ImageSymTypeUnion: "Union", + ImageSymTypeEnum: "Enum", + ImageSymTypeMoe: "Moe", + ImageSymTypeByte: "Byte", + ImageSymTypeWord: "Word", + ImageSymTypeUint: "Uint", + ImageSymTypeDword: "Dword", + } + + if value, ok := coffSymTypeMap[k]; ok { + return value + } + return "" +} diff --git a/tls.go b/tls.go new file mode 100644 index 0000000..4861d78 --- /dev/null +++ b/tls.go @@ -0,0 +1,186 @@ +// Copyright 2021 Saferwall. All rights reserved. +// Use of this source code is governed by Apache v2 license +// license that can be found in the LICENSE file. + +package pe + +import ( + "encoding/binary" +) + +// TLSDirectory represents tls directory information with callback entries. +type TLSDirectory struct { + + // of type *IMAGE_TLS_DIRECTORY32 or *IMAGE_TLS_DIRECTORY64 structure. + Struct interface{} + + // of type uint32 or uint64. + Callbacks interface{} +} + +// ImageTLSDirectory32 represents the IMAGE_TLS_DIRECTORY32 structure. +// It Points to the Thread Local Storage initialization section. +type ImageTLSDirectory32 struct { + + // The starting address of the TLS template. The template is a block of data + // that is used to initialize TLS data. + StartAddressOfRawData uint32 + + // The address of the last byte of the TLS, except for the zero fill. + // As with the Raw Data Start VA field, this is a VA, not an RVA. + EndAddressOfRawData uint32 + + // The location to receive the TLS index, which the loader assigns. This + // location is in an ordinary data section, so it can be given a symbolic + // name that is accessible to the program. + AddressOfIndex uint32 + + // The pointer to an array of TLS callback functions. The array is + // null-terminated, so if no callback function is supported, this field + // points to 4 bytes set to zero. + AddressOfCallBacks uint32 + + // The size in bytes of the template, beyond the initialized data delimited + // by the Raw Data Start VA and Raw Data End VA fields. The total template + // size should be the same as the total size of TLS data in the image file. + // The zero fill is the amount of data that comes after the initialized + // nonzero data. + SizeOfZeroFill uint32 + + // The four bits [23:20] describe alignment info. Possible values are those + // defined as IMAGE_SCN_ALIGN_*, which are also used to describe alignment + // of section in object files. The other 28 bits are reserved for future use. + Characteristics uint32 +} + +// ImageTLSDirectory64 represents the IMAGE_TLS_DIRECTORY64 structure. +// It Points to the Thread Local Storage initialization section. +type ImageTLSDirectory64 struct { + // The starting address of the TLS template. The template is a block of data + // that is used to initialize TLS data. + StartAddressOfRawData uint64 + + // The address of the last byte of the TLS, except for the zero fill. As + // with the Raw Data Start VA field, this is a VA, not an RVA. + EndAddressOfRawData uint64 + + // The location to receive the TLS index, which the loader assigns. This + // location is in an ordinary data section, so it can be given a symbolic + // name that is accessible to the program. + AddressOfIndex uint64 + + // The pointer to an array of TLS callback functions. The array is + // null-terminated, so if no callback function is supported, this field + // points to 4 bytes set to zero. + AddressOfCallBacks uint64 + + // The size in bytes of the template, beyond the initialized data delimited + // by the Raw Data Start VA and Raw Data End VA fields. The total template + // size should be the same as the total size of TLS data in the image file. + // The zero fill is the amount of data that comes after the initialized + // nonzero data. + SizeOfZeroFill uint32 + + // The four bits [23:20] describe alignment info. Possible values are those + // defined as IMAGE_SCN_ALIGN_*, which are also used to describe alignment + // of section in object files. The other 28 bits are reserved for future use. + Characteristics uint32 +} + +// TLS provides direct PE and COFF support for static thread local storage (TLS). +// TLS is a special storage class that Windows supports in which a data object +// is not an automatic (stack) variable, yet is local to each individual thread +// that runs the code. Thus, each thread can maintain a different value for a +// variable declared by using TLS. +func (pe *File) parseTLSDirectory(rva, size uint32) error { + + tls := TLSDirectory{} + + if pe.Is64 { + tlsDir := ImageTLSDirectory64{} + tlsSize := uint32(binary.Size(tlsDir)) + fileOffset := pe.getOffsetFromRva(rva) + err := pe.structUnpack(&tlsDir, fileOffset, tlsSize) + if err != nil { + return err + } + tls.Struct = tlsDir + + if tlsDir.AddressOfCallBacks != 0 { + var callbacks []uint64 + rvaAddressOfCallBacks := uint32(tlsDir.AddressOfCallBacks - + pe.NtHeader.OptionalHeader.(ImageOptionalHeader64).ImageBase) + offset := pe.getOffsetFromRva(rvaAddressOfCallBacks) + for { + c, err := pe.ReadUint64(offset) + if err != nil || c == 0 { + break + } + callbacks = append(callbacks, c) + offset += 8 + } + tls.Callbacks = callbacks + } + } else { + tlsDir := ImageTLSDirectory32{} + tlsSize := uint32(binary.Size(tlsDir)) + fileOffset := pe.getOffsetFromRva(rva) + err := pe.structUnpack(&tlsDir, fileOffset, tlsSize) + if err != nil { + return err + } + tls.Struct = tlsDir + + // 94a9dc17d47b03f6fb01cb639e25503b37761b452e7c07ec6b6c2280635f1df9 + // Callbacks may be empty + if tlsDir.AddressOfCallBacks != 0 { + var callbacks []uint32 + rvaAddressOfCallBacks := tlsDir.AddressOfCallBacks - + pe.NtHeader.OptionalHeader.(ImageOptionalHeader32).ImageBase + offset := pe.getOffsetFromRva(rvaAddressOfCallBacks) + for { + c, err := pe.ReadUint32(offset) + if err != nil || c == 0 { + break + } + callbacks = append(callbacks, c) + offset += 4 + } + tls.Callbacks = callbacks + } + } + + pe.TLS = &tls + return nil +} + +// PrettyTLSCharacteristics returns the string representations of the +// `Characteristics` field of TLS directory. +func (pe *File) PrettyTLSCharacteristics(Characteristics uint32) []string { + var values []string + + TLSCharacteristicsMap := map[uint32]string{ + ImageScnAlign1Bytes: "Align1Bytes", + ImageScnAlign2Bytes: "Align2Bytes", + ImageScnAlign4Bytes: "Align4Bytes", + ImageScnAlign8Bytes: "Align8Bytes", + ImageScnAlign16Bytes: "Align16Bytes", + ImageScnAlign32Bytes: "Align32Bytes", + ImageScnAlign64Bytes: "Align64Bytes", + ImageScnAlign128Bytes: "Align128Bytes", + ImageScnAlign256Bytes: "Align265Bytes", + ImageScnAlign512Bytes: "Align512Bytes", + ImageScnAlign1024Bytes: "Align1024Bytes", + ImageScnAlign2048Bytes: "Align2048Bytes", + ImageScnAlign4096Bytes: "Align4096Bytes", + ImageScnAlign8192Bytes: "Align8192Bytes", + } + + for k, s := range TLSCharacteristicsMap { + if k&Characteristics != 0 { + values = append(values, s) + } + } + + return values +}