-
-
Notifications
You must be signed in to change notification settings - Fork 46
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
19a2d82
commit 393f4bb
Showing
29 changed files
with
10,789 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} |
Oops, something went wrong.