Skip to content

Commit

Permalink
feat: unit test bound imports directory
Browse files Browse the repository at this point in the history
  • Loading branch information
LordNoteworthy committed Mar 8, 2022
1 parent 51c86f8 commit dc9fb19
Show file tree
Hide file tree
Showing 6 changed files with 179 additions and 56 deletions.
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,29 @@
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

## Added

- Unit test for bound imports directory.

## Changed

- Make GetData() and GetRVAFromOffset() helper routines public.

## Fixed

- Imphash calculation [#17](https://github.com/saferwall/pe/pull/17) thanks to [@secDre4mer](https://github.com/secDre4mer).


## [1.1.0] - 2021-12-20

### Added

- Add .editorconfig and .vscode config.
- Add github action CI workflow to test the package.
- Add few badges for the README.md to track build status, coverage and code quality.
Expand All @@ -19,13 +35,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Add an option `New()` to customize max of relocations entries and COFF symbols to parse.

### Changed

- Remove uneeded break statements & lowercase error messages and anomalies.
- Make COFF entry in File struct a pointer.
- Remove unsafe pointer usage from resource directory.
- Do not return an error when COFF symbol table is not found.
- License from Apache 2 to MIT.

### Fixed

- Probe for invalid Nt Header offset.
- Fix authenticode hash calculation.
- Compile correctly on 32 bit thnkas to @Max Altgelt.
Expand All @@ -36,6 +54,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Safe ready of global pointer register

## [1.0.0] - 2021-03-04 (Initial Release)

- Works with PE32/PE32+ file fomat.
- Supports Intel x86/AMD64/ARM7ARM7 Thumb/ARM8-64/IA64/CHPE architectures.
- MS DOS header.
Expand Down
106 changes: 53 additions & 53 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,39 +4,39 @@

## Table of content

- [Features](#features)
- [Installing](#installing)
- [Using the library](#using-the-library)
- [Access the PE header](#pe-header)
- [Iterating over sections](#iterating-over-sections)
- [Roadmap](#roadmap)
- [Contributing](#contributing)
- [License](#license)
- [References](#references)
- [Features](#features)
- [Installing](#installing)
- [Using the library](#using-the-library)
- [Access the PE header](#pe-header)
- [Iterating over sections](#iterating-over-sections)
- [Roadmap](#roadmap)
- [Contributing](#contributing)
- [License](#license)
- [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).
- 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
- 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

Expand Down Expand Up @@ -65,7 +65,7 @@ func main() {
if err != nil {
log.Fatalf("Error while opening file: %s, reason: %v", filename, err)
}

err = pe.Parse()
if err != nil {
log.Fatalf("Error while parsing file: %s, reason: %v", filename, err)
Expand All @@ -78,31 +78,31 @@ Afterwards, a call to the `Parse()` method will give you access to all the diffe
```go
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"`
DosHeader ImageDosHeader
RichHeader *RichHeader
NtHeader ImageNtHeader
COFF *COFF
Sections []Section
Imports []Import
Export *Export
Debugs []DebugEntry
Relocations []Relocation
Resources *ResourceDirectory
TLS *TLSDirectory
LoadConfig *LoadConfig
Exceptions []Exception
Certificates *Certificate
DelayImports []DelayImport
BoundImports []BoundImportDescriptorData
GlobalPtr uint32
CLR *CLRData
IAT []IATEntry
Header []byte
data mmap.MMap
closer io.Closer
Is64 bool
Is32 bool
Anomalies []string `json:",omitempty"`
Anomalies []string
size uint32
f *os.File
opts *Options
Expand Down Expand Up @@ -165,11 +165,11 @@ Section Flags : 40600040, Meaning: [Align2Bytes Align8Bytes Readable Initialized
To validate the parser we use the [go-fuzz](https://github.com/dvyukov/go-fuzz) and a corpus of known malformed and tricky PE files from [corkami](https://github.com/corkami/pocs/tree/master/PE).
# References
## 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)
- https://www.ntcore.com/files/dotnetformat.htm
- [DotNET format](https://www.ntcore.com/files/dotnetformat.htm)
5 changes: 2 additions & 3 deletions boundimports.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ type ImageBoundForwardedRef struct {
Reserved uint16
}

// BoundImportDescriptorData represents the descripts in addition to forwarded refs.
// BoundImportDescriptorData represents the descriptor in addition to forwarded refs.
type BoundImportDescriptorData struct {
Struct ImageBoundImportDescriptor
Name string
Expand All @@ -59,8 +59,7 @@ func (pe *File) parseBoundImportDirectory(rva, size uint32) (err error) {
for {
bndDesc := ImageBoundImportDescriptor{}
bndDescSize := uint32(binary.Size(bndDesc))
buf := bytes.NewReader(pe.data[rva : rva+bndDescSize])
err := binary.Read(buf, binary.LittleEndian, &bndDesc)
err = pe.structUnpack(&bndDesc, rva, bndDescSize)
// If the RVA is invalid all would blow up. Some EXEs seem to be
// specially nasty and have an invalid RVA.
if err != nil {
Expand Down
105 changes: 105 additions & 0 deletions boundimports_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
// 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 (
"reflect"
"testing"
)

type TestBoundImportEntry struct {
entryCount int
entryIndex int
entry BoundImportDescriptorData
errOutOfBounds error
}

func TestBoundImportDirectory(t *testing.T) {

tests := []struct {
in string
out TestBoundImportEntry
}{
{
getAbsoluteFilePath("test/mfc40u.dll"),
TestBoundImportEntry{
entryCount: 4,
entryIndex: 0,
entry: BoundImportDescriptorData{
Struct: ImageBoundImportDescriptor{
TimeDateStamp: 0x31CB50F3,
OffsetModuleName: 0x38,
NumberOfModuleForwarderRefs: 0x1,
},
Name: "MSVCRT40.dll",
ForwardedRefs: []BoundForwardedRefData{
{
Struct: ImageBoundForwardedRef{
TimeDateStamp: 0x3B7DFE0E,
OffsetModuleName: 0x45,
Reserved: 0x0,
},
Name: "msvcrt.DLL",
},
},
},
errOutOfBounds: nil,
},
},
{
// fake bound imports directory
getAbsoluteFilePath("test/0044e1870806c048a7558082d4482d1650dcd3ea73152ed2218a554983130721"),
TestBoundImportEntry{
errOutOfBounds: ErrOutsideBoundary,
},
},
}

for _, tt := range tests {
t.Run(tt.in, func(t *testing.T) {
ops := Options{Fast: true}
file, err := New(tt.in, &ops)
if err != nil {
t.Fatalf("New(%s) failed, reason: %v", tt.in, err)
}

err = file.Parse()
if err != nil {
t.Fatalf("Parse(%s) failed, reason: %v", tt.in, err)
}

var va, size uint32

if file.Is64 {
oh64 := file.NtHeader.OptionalHeader.(ImageOptionalHeader64)
dirEntry := oh64.DataDirectory[ImageDirectoryEntryBoundImport]
va = dirEntry.VirtualAddress
size = dirEntry.Size
} else {
oh32 := file.NtHeader.OptionalHeader.(ImageOptionalHeader32)
dirEntry := oh32.DataDirectory[ImageDirectoryEntryBoundImport]
va = dirEntry.VirtualAddress
size = dirEntry.Size
}

err = file.parseBoundImportDirectory(va, size)
if err != tt.out.errOutOfBounds {
t.Fatalf("parseBoundImportDirectory(%s) failed, reason: %v", tt.in, err)
}
got := file.BoundImports
if len(got) != tt.out.entryCount {
t.Errorf("bound imports entry count assertion failed, got %v, want %v", len(got), tt.out.entryCount)
}

if len(file.BoundImports) > 0 {
boundImportEntry := file.BoundImports[tt.out.entryIndex]
if !reflect.DeepEqual(boundImportEntry, tt.out.entry) {
t.Errorf("bound import entry assertion failed, got %v, want %v", boundImportEntry, tt.out.entry)
}
}

})
}
}
Binary file not shown.
Binary file added test/mfc40u.dll
Binary file not shown.

0 comments on commit dc9fb19

Please sign in to comment.