Skip to content

Commit

Permalink
Remove pointers from main PE struct (#44)
Browse files Browse the repository at this point in the history
* chore: remove pointers from fields in main PE sruct

* dump all results to JSON in pedumper

* upadte README

* fix os.WriteFile 1.15
  • Loading branch information
LordNoteworthy authored Aug 3, 2022
1 parent aeb3df6 commit 91c735f
Show file tree
Hide file tree
Showing 13 changed files with 83 additions and 75 deletions.
43 changes: 23 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,26 +1,29 @@
# Portable Executable Parser [![GoDoc](http://godoc.org/github.com/saferwall/pe?status.svg)](https://pkg.go.dev/github.com/saferwall/pe) ![Go Version](https://img.shields.io/badge/go%20version-%3E=1.15-61CFDD.svg?style=flat-square) [![Report Card](https://goreportcard.com/badge/github.com/saferwall/pe)](https://goreportcard.com/report/github.com/saferwall/pe) [![codecov](https://codecov.io/gh/saferwall/pe/branch/main/graph/badge.svg?token=W7WTOUZLRY)](https://codecov.io/gh/saferwall/pe) ![GitHub Workflow Status](https://img.shields.io/github/workflow/status/saferwall/pe/Build%20&%20Test)
# Portable Executable Parser

[![GoDoc](http://godoc.org/github.com/saferwall/pe?status.svg)](https://pkg.go.dev/github.com/saferwall/pe) ![Go Version](https://img.shields.io/badge/go%20version-%3E=1.15-61CFDD.svg) [![Report Card](https://goreportcard.com/badge/github.com/saferwall/pe)](https://goreportcard.com/report/github.com/saferwall/pe) [![codecov](https://codecov.io/gh/saferwall/pe/branch/main/graph/badge.svg?token=W7WTOUZLRY)](https://codecov.io/gh/saferwall/pe) ![GitHub Workflow Status](https://img.shields.io/github/workflow/status/saferwall/pe/Build%20&%20Test)

**pe** 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.

## Table of content

- [Features](#features)
- [Installing](#installing)
- [Using the library](#using-the-library)
- [Access the PE header](#pe-header)
- [Viewing the rich header](#rich-header)
- [Iterating over sections](#iterating-over-sections)
- [Roadmap](#roadmap)
- [Contributing](#contributing)
- [License](#license)
- [References](#references)
- [Portable Executable Parser](#portable-executable-parser)
- [Table of content](#table-of-content)
- [Features](#features)
- [Installing](#installing)
- [Using the library](#using-the-library)
- [PE Header](#pe-header)
- [Rich Header](#rich-header)
- [Iterating over sections](#iterating-over-sections)
- [Roadmap](#roadmap)
- [Fuzz Testing](#fuzz-testing)
- [References](#references)

## 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).
- Rich Header (calculate checksum and hash).
- NT Header (file header + optional header).
- COFF symbol table and string table.
- Sections headers + entropy calculation.
Expand Down Expand Up @@ -80,23 +83,23 @@ Afterwards, a call to the `Parse()` method will give you access to all the diffe
```go
type File struct {
DOSHeader ImageDOSHeader
RichHeader *RichHeader
RichHeader RichHeader
NtHeader ImageNtHeader
COFF *COFF
COFF COFF
Sections []Section
Imports []Import
Export *Export
Export Export
Debugs []DebugEntry
Relocations []Relocation
Resources *ResourceDirectory
TLS *TLSDirectory
LoadConfig *LoadConfig
Resources ResourceDirectory
TLS TLSDirectory
LoadConfig LoadConfig
Exceptions []Exception
Certificates *Certificate
Certificates Certificate
DelayImports []DelayImport
BoundImports []BoundImportDescriptorData
GlobalPtr uint32
CLR *CLRData
CLR CLRData
IAT []IATEntry
Header []byte
data mmap.MMap
Expand Down
16 changes: 13 additions & 3 deletions cmd/pedumper.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2021 Saferwall. All rights reserved.
// Copyright 2022 Saferwall. All rights reserved.
// Use of this source code is governed by Apache v2 license
// license that can be found in the LICENSE file.

Expand Down Expand Up @@ -104,8 +104,17 @@ func parsePE(filename string, cmd *cobra.Command) {
return
}

// Dump all results to disk in JSON format.
b, _ := json.Marshal(pe)
f, err := os.Create("out.json")
if err != nil {
return
}
defer f.Close()
f.WriteString(prettyPrint(b))

// Calculate the PE authentihash.
//pe.Authentihash()
pe.Authentihash()

// Calculate the PE checksum.
pe.Checksum()
Expand Down Expand Up @@ -171,7 +180,7 @@ func parsePE(filename string, cmd *cobra.Command) {
}

wantCLR, _ := cmd.Flags().GetBool("clr")
if wantCLR && pe.CLR != nil {
if wantCLR {
dotnetMetadata, _ := json.Marshal(pe.CLR)
log.Info(prettyPrint(dotnetMetadata))
if modTable, ok := pe.CLR.MetadataTables[peparser.Module]; ok {
Expand All @@ -192,6 +201,7 @@ func parsePE(filename string, cmd *cobra.Command) {
log.Info(prettyPrint(dosHeader))
log.Info(prettyPrint(ntHeader))
log.Info(prettyPrint(sectionsHeaders))
return
}

fmt.Println()
Expand Down
27 changes: 12 additions & 15 deletions dotnet.go
Original file line number Diff line number Diff line change
Expand Up @@ -624,17 +624,14 @@ func (pe *File) parseMetadataModuleTable(moduleTable *MetadataTable, off uint32)
// language runtime header in the .text section.
func (pe *File) parseCLRHeaderDirectory(rva, size uint32) error {

clr := CLRData{}
clrHeader := ImageCOR20Header{}
pe.CLR = &clr

offset := pe.GetOffsetFromRva(rva)
err := pe.structUnpack(&clrHeader, offset, size)
if err != nil {
return err
}

clr.CLRHeader = &clrHeader
pe.CLR.CLRHeader = &clrHeader
if clrHeader.MetaData.VirtualAddress == 0 || clrHeader.MetaData.Size == 0 {
return nil
}
Expand All @@ -649,8 +646,8 @@ func (pe *File) parseCLRHeaderDirectory(rva, size uint32) error {
if err != nil {
return err
}
clr.MetadataHeader = &mh
clr.MetadataStreams = make(map[string][]byte)
pe.CLR.MetadataHeader = &mh
pe.CLR.MetadataStreams = make(map[string][]byte)
offset += 16 + mh.VersionString + 4

// Immediately following the MetadataHeader is a series of Stream Headers.
Expand Down Expand Up @@ -695,8 +692,8 @@ func (pe *File) parseCLRHeaderDirectory(rva, size uint32) error {
// Save the stream into a map <string> []byte.
rva = clrHeader.MetaData.VirtualAddress + sh.Offset
start := pe.GetOffsetFromRva(rva)
clr.MetadataStreams[sh.Name] = pe.data[start : start+sh.Size]
clr.MetadataStreamHeaders = append(clr.MetadataStreamHeaders, &sh)
pe.CLR.MetadataStreams[sh.Name] = pe.data[start : start+sh.Size]
pe.CLR.MetadataStreamHeaders = append(pe.CLR.MetadataStreamHeaders, &sh)
}

// Get the Metadata Table Stream.
Expand All @@ -711,19 +708,19 @@ func (pe *File) parseCLRHeaderDirectory(rva, size uint32) error {
if err != nil {
return nil
}
clr.MetadataTablesStreamHeader = &mdTableStreamHdr
pe.CLR.MetadataTablesStreamHeader = &mdTableStreamHdr

// Get the size of indexes of #String", "#GUID" and "#Blob" streams.
clr.StringStreamIndexSize = pe.GetMetadataStreamIndexSize(StringStream)
clr.GUIDStreamIndexSize = pe.GetMetadataStreamIndexSize(GUIDStream)
clr.BlobStreamIndexSize = pe.GetMetadataStreamIndexSize(BlobStream)
pe.CLR.StringStreamIndexSize = pe.GetMetadataStreamIndexSize(StringStream)
pe.CLR.GUIDStreamIndexSize = pe.GetMetadataStreamIndexSize(GUIDStream)
pe.CLR.BlobStreamIndexSize = pe.GetMetadataStreamIndexSize(BlobStream)

// This header is followed by a sequence of 4-byte unsigned integers
// indicating the number of records in each table marked 1 in the MaskValid
// bit vector.
tablesCount := 0
offset += uint32(binary.Size(mdTableStreamHdr))
clr.MetadataTables = make(map[int]*MetadataTable)
pe.CLR.MetadataTables = make(map[int]*MetadataTable)
for i := 0; i < GenericParamConstraint; i++ {
if IsBitSet(mdTableStreamHdr.MaskValid, i) {
mdTable := MetadataTable{}
Expand All @@ -734,12 +731,12 @@ func (pe *File) parseCLRHeaderDirectory(rva, size uint32) error {
}
tablesCount++
offset += 4
clr.MetadataTables[i] = &mdTable
pe.CLR.MetadataTables[i] = &mdTable
}
}

// Parse the metadata tables.
for tableIdx, table := range clr.MetadataTables {
for tableIdx, table := range pe.CLR.MetadataTables {
switch tableIdx {
case Module:
if err = pe.parseMetadataModuleTable(table, offset); err != nil {
Expand Down
11 changes: 5 additions & 6 deletions exports.go
Original file line number Diff line number Diff line change
Expand Up @@ -312,19 +312,18 @@ func (pe *File) parseExportDirectory(rva, size uint32) error {
exp.Functions = append(exp.Functions, newExport)
}

pe.Export = &exp
pe.Export = exp
pe.HasExport = true
return nil
}

// GetExportFunctionByRVA return an export function given an RVA.
func (pe *File) GetExportFunctionByRVA(rva uint32) ExportFunction {
if pe.Export != nil {
for _, exp := range pe.Export.Functions {
if exp.FunctionRVA == rva {
return exp
}
for _, exp := range pe.Export.Functions {
if exp.FunctionRVA == rva {
return exp
}
}

return ExportFunction{}
}
18 changes: 9 additions & 9 deletions file.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2021 Saferwall. All rights reserved.
// Copyright 2022 Saferwall. All rights reserved.
// Use of this source code is governed by Apache v2 license
// license that can be found in the LICENSE file.

Expand All @@ -15,23 +15,23 @@ import (
// A File represents an open PE file.
type File struct {
DOSHeader ImageDOSHeader `json:",omitempty"`
RichHeader *RichHeader `json:",omitempty"`
RichHeader RichHeader `json:",omitempty"`
NtHeader ImageNtHeader `json:",omitempty"`
COFF *COFF `json:",omitempty"`
COFF COFF `json:",omitempty"`
Sections []Section `json:",omitempty"`
Imports []Import `json:",omitempty"`
Export *Export `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"`
Resources ResourceDirectory `json:",omitempty"`
TLS TLSDirectory `json:",omitempty"`
LoadConfig LoadConfig `json:",omitempty"`
Exceptions []Exception `json:",omitempty"`
Certificates *Certificate `json:",omitempty"`
Certificates Certificate `json:",omitempty"`
DelayImports []DelayImport `json:",omitempty"`
BoundImports []BoundImportDescriptorData `json:",omitempty"`
GlobalPtr uint32 `json:",omitempty"`
CLR *CLRData `json:",omitempty"`
CLR CLRData `json:",omitempty"`
IAT []IATEntry `json:",omitempty"`
Header []byte
data mmap.MMap
Expand Down
20 changes: 9 additions & 11 deletions loadconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -1624,37 +1624,35 @@ func (pe *File) parseLoadConfigDirectory(rva, size uint32) error {
}

// Save the load config struct.
loadConfig := LoadConfig{}
pe.LoadConfig = &loadConfig
pe.HasLoadCFG = true
loadConfig.LoadCfgStruct = loadCfg
pe.LoadConfig.LoadCfgStruct = loadCfg

// Retrieve SEH handlers if there are any..
if pe.Is32 {
handlers := pe.getSEHHandlers()
loadConfig.SEH = handlers
pe.LoadConfig.SEH = handlers
}

// Retrieve Control Flow Guard Function Targets if there are any.
loadConfig.GFIDS = pe.getControlFlowGuardFunctions()
pe.LoadConfig.GFIDS = pe.getControlFlowGuardFunctions()

// Retrieve Control Flow Guard IAT entries if there are any.
loadConfig.CFGIAT = pe.getControlFlowGuardIAT()
pe.LoadConfig.CFGIAT = pe.getControlFlowGuardIAT()

// Retrive Long jump target functions if there are any.
loadConfig.CFGLongJump = pe.getLongJumpTargetTable()
pe.LoadConfig.CFGLongJump = pe.getLongJumpTargetTable()

// Retrieve compiled hybrid PE metadata if there are any.
loadConfig.CHPE = pe.getHybridPE()
pe.LoadConfig.CHPE = pe.getHybridPE()

// Retrieve dynamic value relocation table if there are any.
loadConfig.DVRT = pe.getDynamicValueRelocTable()
pe.LoadConfig.DVRT = pe.getDynamicValueRelocTable()

// Retrieve enclave configuration if there are any.
loadConfig.Enclave = pe.getEnclaveConfiguration()
pe.LoadConfig.Enclave = pe.getEnclaveConfiguration()

// Retrieve volatile metadat table if there are any.
loadConfig.VolatileMetadata = pe.getVolatileMetadata()
pe.LoadConfig.VolatileMetadata = pe.getVolatileMetadata()

return nil
}
Expand Down
2 changes: 1 addition & 1 deletion resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ func (pe *File) parseResourceDirectory(rva, size uint32) error {
return err
}

pe.Resources = &Resources
pe.Resources = Resources
pe.HasResource = true
return err
}
7 changes: 4 additions & 3 deletions richheader.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,14 +151,14 @@ func (pe *File) ParseRichHeader() error {
rh.CompIDs = append(rh.CompIDs, cid)
}

pe.RichHeader = &rh
pe.RichHeader = rh
pe.HasRichHdr = true

checksum := pe.RichHeaderChecksum()
if checksum != rh.XorKey {
pe.Anomalies = append(pe.Anomalies, "Invalid rich header checksum")
}

pe.HasRichHdr = true
return nil
}

Expand Down Expand Up @@ -193,9 +193,10 @@ func (pe *File) RichHeaderChecksum() uint32 {

// RichHeaderHash calculate the Rich Header hash.
func (pe *File) RichHeaderHash() string {
if pe.RichHeader == nil {
if !pe.HasRichHdr {
return ""
}

richIndex := bytes.Index(pe.RichHeader.Raw, []byte(RichSignature))
if richIndex == -1 {
return ""
Expand Down
2 changes: 1 addition & 1 deletion richheader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ func TestParseRichHeader(t *testing.T) {
}

richheader := file.RichHeader
if !reflect.DeepEqual(richheader, &tt.out.richheader) {
if !reflect.DeepEqual(richheader, tt.out.richheader) {
t.Errorf("rich header test failed, got %v, want %v",
richheader, tt.out)
}
Expand Down
4 changes: 2 additions & 2 deletions security.go
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ func (pe *File) parseSecurityDirectory(rva, size uint32) error {
certContent = pe.data[fileOffset+certSize : fileOffset+certHeader.Length]
pkcs, err = pkcs7.Parse(certContent)
if err != nil {
pe.Certificates = &Certificate{Header: certHeader, Raw: certContent}
pe.Certificates = Certificate{Header: certHeader, Raw: certContent}
pe.HasSecurity = true
return err
}
Expand Down Expand Up @@ -382,7 +382,7 @@ func (pe *File) parseSecurityDirectory(rva, size uint32) error {
fileOffset = nextOffset
}

pe.Certificates = &Certificate{Header: certHeader, Content: pkcs,
pe.Certificates = Certificate{Header: certHeader, Content: pkcs,
Raw: certContent, Info: certInfo, Verified: isValid}
pe.HasSecurity = true
return nil
Expand Down
3 changes: 1 addition & 2 deletions symbol.go
Original file line number Diff line number Diff line change
Expand Up @@ -310,13 +310,12 @@ func (pe *File) ParseCOFFSymbolTable() error {
offset += size
}

pe.COFF = &COFF{}
pe.COFF.SymbolTable = symbols
pe.HasCOFF = true

// Get the COFF string table.
pe.COFFStringTable()

pe.HasCOFF = true
return nil
}

Expand Down
3 changes: 2 additions & 1 deletion symbol_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,8 @@ func TestParseCOFFSymbolTable(t *testing.T) {
)
}

if file.COFF == nil {
// exit early when err is errCOFFSymbolsTooHigh.
if err == errCOFFSymbolsTooHigh {
return
}

Expand Down
Loading

0 comments on commit 91c735f

Please sign in to comment.