Skip to content

Commit

Permalink
initial project files
Browse files Browse the repository at this point in the history
  • Loading branch information
LordNoteworthy committed Feb 11, 2021
1 parent 19a2d82 commit 393f4bb
Show file tree
Hide file tree
Showing 29 changed files with 10,789 additions and 0 deletions.
75 changes: 75 additions & 0 deletions README.md
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)
202 changes: 202 additions & 0 deletions anomaly.go
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
}
11 changes: 11 additions & 0 deletions arch.go
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
}
Loading

0 comments on commit 393f4bb

Please sign in to comment.