Files
2024-11-01 17:43:06 +00:00

747 lines
22 KiB
Go

// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
// Package pe provides facilities for extracting information from PE binaries.
// It only supports the same CPU architectures as the rest of wingoes.
package pe
import (
"bufio"
"bytes"
dpe "debug/pe"
"encoding/binary"
"errors"
"fmt"
"io"
"math/bits"
"os"
"reflect"
"strings"
"unsafe"
"github.com/dblohm7/wingoes"
"golang.org/x/exp/constraints"
)
// The following constants are from the PE spec
const (
maxNumSections = 96
mzSignature = uint16(0x5A4D) // little-endian
offsetIMAGE_DOS_HEADERe_lfanew = 0x3C
peSignature = uint32(0x00004550) // little-endian
)
var (
// ErrBadLength is returned when the actual length of a data field in the
// binary is shorter than the expected length of that field.
ErrBadLength = errors.New("effective length did not match expected length")
// ErrBadCodeView is returned by (*PEHeaders).ExtractCodeViewInfo if the data
// at the requested address does not appear to contain valid CodeView information.
ErrBadCodeView = errors.New("invalid CodeView debug info")
// ErrIndexOutOfRange is returned by (*PEHeaders).DataDirectoryEntry if the
// specified index is greater than the maximum allowable index.
ErrIndexOutOfRange = errors.New("index out of range")
// ErrInvalidBinary is returned whenever the headers do not parse as expected,
// or reference locations outside the bounds of the PE file or module.
// The headers might be corrupt, malicious, or have been tampered with.
ErrInvalidBinary = errors.New("invalid PE binary")
// ErrBadCodeView is returned by (*PEHeaders).ExtractCodeViewInfo if the data
// at the requested address contains a non-CodeView debug info format.
ErrNotCodeView = errors.New("debug info is not CodeView")
// ErrIndexOutOfRange is returned by (*PEHeaders).DataDirectoryEntry if the
// corresponding entry is not populated in the PE image.
ErrNotPresent = errors.New("not present in this PE image")
// ErrResolvingFileRVA is returned when the result of arithmetic on a relative
// virtual address did not resolve to a valid RVA.
ErrResolvingFileRVA = errors.New("could not resolve file RVA")
// ErrUnavailableInModule is returned when requesting data from the binary
// that is not mapped into memory when loaded. The information must be
// loaded from a file-based PEHeaders.
ErrUnavailableInModule = errors.New("this information is unavailable from loaded modules; the PE file itself must be examined")
// ErrUnsupportedMachine is returned if the binary's CPU architecture is
// unsupported. This package currently implements support for x86, amd64,
// and arm64.
ErrUnsupportedMachine = errors.New("unsupported machine")
)
// FileHeader is the PE/COFF IMAGE_FILE_HEADER structure.
type FileHeader dpe.FileHeader
// SectionHeader is the PE/COFF IMAGE_SECTION_HEADER structure.
type SectionHeader dpe.SectionHeader32
// NameString returns the name of s as a Go string.
func (s *SectionHeader) NameString() string {
// s.Name is UTF-8. When the string's length is < len(s.Name), the remaining
// bytes are padded with zeros.
for i, c := range s.Name {
if c == 0 {
return string(s.Name[:i])
}
}
return string(s.Name[:])
}
type peReader interface {
io.Closer
io.ReaderAt
io.ReadSeeker
Base() uintptr
Limit() uintptr
}
// PEHeaders represents the partially-parsed headers from a PE binary.
type PEHeaders struct {
r peReader
fileHeader *FileHeader
optionalHeader OptionalHeader
sections []SectionHeader
}
// FileHeader returns the FileHeader that was parsed from peh.
func (peh *PEHeaders) FileHeader() *FileHeader {
return peh.fileHeader
}
// FileHeader returns the OptionalHeader that was parsed from peh.
func (peh *PEHeaders) OptionalHeader() OptionalHeader {
return peh.optionalHeader
}
// Sections returns a slice containing all section headers parsed from peh.
func (peh *PEHeaders) Sections() []SectionHeader {
return peh.sections
}
// DataDirectoryEntry is a PE/COFF IMAGE_DATA_DIRECTORY structure.
type DataDirectoryEntry = dpe.DataDirectory
func resolveOptionalHeader(machine uint16, r peReader, offset uint32) (OptionalHeader, error) {
switch machine {
case dpe.IMAGE_FILE_MACHINE_I386:
return readStruct[optionalHeader32](r, offset)
case dpe.IMAGE_FILE_MACHINE_AMD64, dpe.IMAGE_FILE_MACHINE_ARM64:
return readStruct[optionalHeader64](r, offset)
default:
return nil, ErrUnsupportedMachine
}
}
func checkMagic(oh OptionalHeader, machine uint16) bool {
var expectedMagic uint16
switch machine {
case dpe.IMAGE_FILE_MACHINE_I386:
expectedMagic = 0x010B
case dpe.IMAGE_FILE_MACHINE_AMD64, dpe.IMAGE_FILE_MACHINE_ARM64:
expectedMagic = 0x020B
default:
panic("unsupported machine")
}
return oh.GetMagic() == expectedMagic
}
type peBounds struct {
base uintptr
limit uintptr
}
type peFile struct {
*os.File
peBounds
}
func (pef *peFile) Base() uintptr {
return pef.base
}
func (pef *peFile) Limit() uintptr {
if pef.limit == 0 {
if fi, err := pef.Stat(); err == nil {
pef.limit = uintptr(fi.Size())
}
}
return pef.limit
}
type peModule struct {
*bytes.Reader
peBounds
modLock uintptr
}
func (pei *peModule) Base() uintptr {
return pei.base
}
func (pei *peModule) Limit() uintptr {
return pei.limit
}
// NewPEFromFileName opens a PE binary located at filename and parses its PE
// headers. Upon success it returns a non-nil *PEHeaders, otherwise it returns a
// nil *PEHeaders and a non-nil error.
// Call Close() on the returned *PEHeaders when it is no longer needed.
func NewPEFromFileName(filename string) (*PEHeaders, error) {
f, err := os.Open(filename)
if err != nil {
return nil, err
}
return newPEFromFile(f)
}
func newPEFromFile(f *os.File) (*PEHeaders, error) {
// peBounds base is 0, limit is loaded lazily
pef := &peFile{File: f}
peh, err := loadHeaders(pef)
if err != nil {
pef.Close()
return nil, err
}
return peh, nil
}
// Close frees any resources that were opened when peh was created.
func (peh *PEHeaders) Close() error {
return peh.r.Close()
}
type rvaType interface {
~int8 | ~int16 | ~int32 | ~uint8 | ~uint16 | ~uint32
}
// addOffset ensures that, if off is negative, it does not underflow base.
func addOffset[O rvaType](base uintptr, off O) uintptr {
if off >= 0 {
return base + uintptr(off)
}
negation := uintptr(-off)
if negation >= base {
return 0
}
return base - negation
}
func binaryRead(r io.Reader, data any) (err error) {
// Supported Windows archs are now always LittleEndian
err = binary.Read(r, binary.LittleEndian, data)
if err == io.ErrUnexpectedEOF {
err = ErrBadLength
}
return err
}
// readStruct reads a T from offset rva. If r is a *peModule, the returned *T
// points to the data in-place.
// Note that currently this function will fail if rva references memory beyond
// the bounds of the binary; in the case of modules, this may need to be relaxed
// in some cases due to tampering by third-party crapware.
func readStruct[T any, R rvaType](r peReader, rva R) (*T, error) {
switch v := r.(type) {
case *peFile:
if _, err := r.Seek(int64(rva), io.SeekStart); err != nil {
return nil, err
}
result := new(T)
if err := binaryRead(r, result); err != nil {
return nil, err
}
return result, nil
case *peModule:
addr := addOffset(r.Base(), rva)
szT := unsafe.Sizeof(*((*T)(nil)))
if addr+szT >= v.Limit() {
return nil, ErrInvalidBinary
}
return (*T)(unsafe.Pointer(addr)), nil
default:
return nil, os.ErrInvalid
}
}
// readStructArray reads a []T with length count from offset rva. If r is a
// *peModule, the returned []T references the data in-place.
// Note that currently this function will fail if rva references memory beyond
// the bounds of the binary; in the case of modules, this may need to be relaxed
// in some cases due to tampering by third-party crapware.
func readStructArray[T any, R rvaType](r peReader, rva R, count int) ([]T, error) {
switch v := r.(type) {
case *peFile:
if _, err := r.Seek(int64(rva), io.SeekStart); err != nil {
return nil, err
}
result := make([]T, count)
if err := binaryRead(r, result); err != nil {
return nil, err
}
return result, nil
case *peModule:
addr := addOffset(r.Base(), rva)
szT := reflect.ArrayOf(count, reflect.TypeOf((*T)(nil)).Elem()).Size()
if addr+szT >= v.Limit() {
return nil, ErrInvalidBinary
}
return unsafe.Slice((*T)(unsafe.Pointer(addr)), count), nil
default:
return nil, os.ErrInvalid
}
}
func loadHeaders(r peReader) (*PEHeaders, error) {
// Check the signature of the DOS stub header
if _, err := r.Seek(0, io.SeekStart); err != nil {
return nil, err
}
var mz uint16
if err := binaryRead(r, &mz); err != nil {
if err == ErrBadLength {
err = ErrInvalidBinary
}
return nil, err
}
if mz != mzSignature {
return nil, ErrInvalidBinary
}
// Seek to the offset of the value that points to the beginning of the PE headers
if _, err := r.Seek(offsetIMAGE_DOS_HEADERe_lfanew, io.SeekStart); err != nil {
return nil, ErrInvalidBinary
}
// Load the offset to the beginning of the PE headers
var e_lfanew int32
if err := binaryRead(r, &e_lfanew); err != nil {
if err == ErrBadLength {
err = ErrInvalidBinary
}
return nil, err
}
if e_lfanew <= 0 {
return nil, ErrInvalidBinary
}
if addOffset(r.Base(), e_lfanew) >= r.Limit() {
return nil, ErrInvalidBinary
}
// Check the PE signature
if _, err := r.Seek(int64(e_lfanew), io.SeekStart); err != nil {
return nil, ErrInvalidBinary
}
var pe uint32
if err := binaryRead(r, &pe); err != nil {
if err == ErrBadLength {
err = ErrInvalidBinary
}
return nil, err
}
if pe != peSignature {
return nil, ErrInvalidBinary
}
// Read the file header
fileHeaderOffset := uint32(e_lfanew) + uint32(unsafe.Sizeof(pe))
if r.Base()+uintptr(fileHeaderOffset) >= r.Limit() {
return nil, ErrInvalidBinary
}
fileHeader, err := readStruct[FileHeader](r, fileHeaderOffset)
if err != nil {
return nil, err
}
machine := fileHeader.Machine
if !checkMachine(r, machine) {
return nil, ErrUnsupportedMachine
}
// Read the optional header
optionalHeaderOffset := uint32(fileHeaderOffset) + uint32(unsafe.Sizeof(*fileHeader))
if r.Base()+uintptr(optionalHeaderOffset) >= r.Limit() {
return nil, ErrInvalidBinary
}
optionalHeader, err := resolveOptionalHeader(machine, r, optionalHeaderOffset)
if err != nil {
return nil, err
}
if !checkMagic(optionalHeader, machine) {
return nil, ErrInvalidBinary
}
if fileHeader.SizeOfOptionalHeader < optionalHeader.SizeOf() {
return nil, ErrInvalidBinary
}
// Coarse-grained check that header sizes make sense
totalEssentialHeaderLen := uint32(offsetIMAGE_DOS_HEADERe_lfanew) +
uint32(unsafe.Sizeof(e_lfanew)) +
uint32(unsafe.Sizeof(*fileHeader)) +
uint32(fileHeader.SizeOfOptionalHeader)
if optionalHeader.GetSizeOfImage() < totalEssentialHeaderLen {
return nil, ErrInvalidBinary
}
numSections := fileHeader.NumberOfSections
if numSections > maxNumSections {
// More than 96 sections?! Really?!
return nil, ErrInvalidBinary
}
// Read in the section table
sectionTableOffset := optionalHeaderOffset + uint32(fileHeader.SizeOfOptionalHeader)
if r.Base()+uintptr(sectionTableOffset) >= r.Limit() {
return nil, ErrInvalidBinary
}
sections, err := readStructArray[SectionHeader](r, sectionTableOffset, int(numSections))
if err != nil {
return nil, err
}
return &PEHeaders{r: r, fileHeader: fileHeader, optionalHeader: optionalHeader, sections: sections}, nil
}
type rva32 interface {
~int32 | ~uint32
}
// resolveRVA resolves rva, or returns 0 if unavailable.
func resolveRVA[R rva32](nfo *PEHeaders, rva R) R {
if _, ok := nfo.r.(*peFile); !ok {
// Just the identity function in this case.
return rva
}
if rva <= 0 {
return 0
}
// We walk the section table, locating the section that would contain rva if
// we were mapped into memory. We then calculate the offset of rva from the
// starting virtual address of the section, and then add that offset to the
// section's starting file pointer.
urva := uint32(rva)
for _, s := range nfo.sections {
if urva < s.VirtualAddress {
continue
}
if urva >= (s.VirtualAddress + s.VirtualSize) {
continue
}
voff := urva - s.VirtualAddress
foff := s.PointerToRawData + voff
if foff >= s.PointerToRawData+s.SizeOfRawData {
return 0
}
return R(foff)
}
return 0
}
// DataDirectoryIndex is an enumeration specifying a particular entry in the
// data directory.
type DataDirectoryIndex int
const (
IMAGE_DIRECTORY_ENTRY_EXPORT = DataDirectoryIndex(dpe.IMAGE_DIRECTORY_ENTRY_EXPORT)
IMAGE_DIRECTORY_ENTRY_IMPORT = DataDirectoryIndex(dpe.IMAGE_DIRECTORY_ENTRY_IMPORT)
IMAGE_DIRECTORY_ENTRY_RESOURCE = DataDirectoryIndex(dpe.IMAGE_DIRECTORY_ENTRY_RESOURCE)
IMAGE_DIRECTORY_ENTRY_EXCEPTION = DataDirectoryIndex(dpe.IMAGE_DIRECTORY_ENTRY_EXCEPTION)
IMAGE_DIRECTORY_ENTRY_SECURITY = DataDirectoryIndex(dpe.IMAGE_DIRECTORY_ENTRY_SECURITY)
IMAGE_DIRECTORY_ENTRY_BASERELOC = DataDirectoryIndex(dpe.IMAGE_DIRECTORY_ENTRY_BASERELOC)
IMAGE_DIRECTORY_ENTRY_DEBUG = DataDirectoryIndex(dpe.IMAGE_DIRECTORY_ENTRY_DEBUG)
IMAGE_DIRECTORY_ENTRY_ARCHITECTURE = DataDirectoryIndex(dpe.IMAGE_DIRECTORY_ENTRY_ARCHITECTURE)
IMAGE_DIRECTORY_ENTRY_GLOBALPTR = DataDirectoryIndex(dpe.IMAGE_DIRECTORY_ENTRY_GLOBALPTR)
IMAGE_DIRECTORY_ENTRY_TLS = DataDirectoryIndex(dpe.IMAGE_DIRECTORY_ENTRY_TLS)
IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG = DataDirectoryIndex(dpe.IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG)
IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT = DataDirectoryIndex(dpe.IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT)
IMAGE_DIRECTORY_ENTRY_IAT = DataDirectoryIndex(dpe.IMAGE_DIRECTORY_ENTRY_IAT)
IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT = DataDirectoryIndex(dpe.IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT)
IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR = DataDirectoryIndex(dpe.IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR)
)
// DataDirectoryEntry returns information from nfo's data directory at index idx.
// The type of the return value depends on the value of idx. Most values for idx
// currently return the DataDirectoryEntry itself, however it will return more
// sophisticated information for the following values of idx:
//
// * IMAGE_DIRECTORY_ENTRY_SECURITY returns []AuthenticodeCert
// * IMAGE_DIRECTORY_ENTRY_DEBUG returns []IMAGE_DEBUG_DIRECTORY
//
// Note that other idx values _will_ be modified in the future to support more
// sophisticated return values, so be careful to structure your type assertions
// accordingly.
func (nfo *PEHeaders) DataDirectoryEntry(idx DataDirectoryIndex) (any, error) {
dd := nfo.optionalHeader.GetDataDirectory()
if int(idx) >= len(dd) {
return nil, ErrIndexOutOfRange
}
dde := dd[idx]
if dde.VirtualAddress == 0 || dde.Size == 0 {
return nil, ErrNotPresent
}
switch idx {
/* TODO(aaron): (don't forget to sync tests!)
case IMAGE_DIRECTORY_ENTRY_EXPORT:
case IMAGE_DIRECTORY_ENTRY_IMPORT:
case IMAGE_DIRECTORY_ENTRY_RESOURCE:
*/
case IMAGE_DIRECTORY_ENTRY_SECURITY:
return nfo.extractAuthenticode(dde)
case IMAGE_DIRECTORY_ENTRY_DEBUG:
return nfo.extractDebugInfo(dde)
// case IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR:
default:
return dde, nil
}
}
// WIN_CERT_REVISION is an enumeration from the Windows SDK.
type WIN_CERT_REVISION uint16
const (
WIN_CERT_REVISION_1_0 WIN_CERT_REVISION = 0x0100
WIN_CERT_REVISION_2_0 WIN_CERT_REVISION = 0x0200
)
// WIN_CERT_TYPE is an enumeration from the Windows SDK.
type WIN_CERT_TYPE uint16
const (
WIN_CERT_TYPE_X509 WIN_CERT_TYPE = 0x0001
WIN_CERT_TYPE_PKCS_SIGNED_DATA WIN_CERT_TYPE = 0x0002
WIN_CERT_TYPE_TS_STACK_SIGNED WIN_CERT_TYPE = 0x0004
)
type _WIN_CERTIFICATE_HEADER struct {
Length uint32
Revision WIN_CERT_REVISION
CertificateType WIN_CERT_TYPE
}
// AuthenticodeCert represents an authenticode signature that has been extracted
// from a signed PE binary but not fully parsed.
type AuthenticodeCert struct {
header _WIN_CERTIFICATE_HEADER
data []byte
}
// Revision returns the revision of ac.
func (ac *AuthenticodeCert) Revision() WIN_CERT_REVISION {
return ac.header.Revision
}
// Type returns the type of ac.
func (ac *AuthenticodeCert) Type() WIN_CERT_TYPE {
return ac.header.CertificateType
}
// Data returns the raw bytes of ac's cert.
func (ac *AuthenticodeCert) Data() []byte {
return ac.data
}
func alignUp[V constraints.Integer](v V, powerOfTwo uint8) V {
if bits.OnesCount8(powerOfTwo) != 1 {
panic("invalid powerOfTwo argument to alignUp")
}
return v + ((-v) & (V(powerOfTwo) - 1))
}
// IMAGE_DEBUG_TYPE is an enumeration for indicating the type of debug
// information referenced by a particular [IMAGE_DEBUG_DIRECTORY].
type IMAGE_DEBUG_TYPE uint32
const (
IMAGE_DEBUG_TYPE_UNKNOWN IMAGE_DEBUG_TYPE = 0
IMAGE_DEBUG_TYPE_COFF IMAGE_DEBUG_TYPE = 1
IMAGE_DEBUG_TYPE_CODEVIEW IMAGE_DEBUG_TYPE = 2
IMAGE_DEBUG_TYPE_FPO IMAGE_DEBUG_TYPE = 3
IMAGE_DEBUG_TYPE_MISC IMAGE_DEBUG_TYPE = 4
IMAGE_DEBUG_TYPE_EXCEPTION IMAGE_DEBUG_TYPE = 5
IMAGE_DEBUG_TYPE_FIXUP IMAGE_DEBUG_TYPE = 6
IMAGE_DEBUG_TYPE_OMAP_TO_SRC IMAGE_DEBUG_TYPE = 7
IMAGE_DEBUG_TYPE_OMAP_FROM_SRC IMAGE_DEBUG_TYPE = 8
IMAGE_DEBUG_TYPE_BORLAND IMAGE_DEBUG_TYPE = 9
IMAGE_DEBUG_TYPE_RESERVED10 IMAGE_DEBUG_TYPE = 10
IMAGE_DEBUG_TYPE_BBT IMAGE_DEBUG_TYPE = IMAGE_DEBUG_TYPE_RESERVED10
IMAGE_DEBUG_TYPE_CLSID IMAGE_DEBUG_TYPE = 11
IMAGE_DEBUG_TYPE_VC_FEATURE IMAGE_DEBUG_TYPE = 12
IMAGE_DEBUG_TYPE_POGO IMAGE_DEBUG_TYPE = 13
IMAGE_DEBUG_TYPE_ILTCG IMAGE_DEBUG_TYPE = 14
IMAGE_DEBUG_TYPE_MPX IMAGE_DEBUG_TYPE = 15
IMAGE_DEBUG_TYPE_REPRO IMAGE_DEBUG_TYPE = 16
IMAGE_DEBUG_TYPE_SPGO IMAGE_DEBUG_TYPE = 18
IMAGE_DEBUG_TYPE_EX_DLLCHARACTERISTICS IMAGE_DEBUG_TYPE = 20
)
// IMAGE_DEBUG_DIRECTORY describes debug information embedded in the binary.
type IMAGE_DEBUG_DIRECTORY struct {
Characteristics uint32
TimeDateStamp uint32
MajorVersion uint16
MinorVersion uint16
Type IMAGE_DEBUG_TYPE
SizeOfData uint32
AddressOfRawData uint32
PointerToRawData uint32
}
func (nfo *PEHeaders) extractDebugInfo(dde DataDirectoryEntry) (any, error) {
rva := resolveRVA(nfo, dde.VirtualAddress)
if rva == 0 {
return nil, ErrResolvingFileRVA
}
count := dde.Size / uint32(unsafe.Sizeof(IMAGE_DEBUG_DIRECTORY{}))
return readStructArray[IMAGE_DEBUG_DIRECTORY](nfo.r, rva, int(count))
}
// IMAGE_DEBUG_INFO_CODEVIEW_UNPACKED contains CodeView debug information
// embedded in the PE file. Note that this structure's ABI does not match its C
// counterpart because the former uses a Go string and the latter is packed and
// also includes a signature field.
type IMAGE_DEBUG_INFO_CODEVIEW_UNPACKED struct {
GUID wingoes.GUID
Age uint32
PDBPath string
}
// String returns the data from u formatted in the same way that Microsoft
// debugging tools and symbol servers use to identify PDB files corresponding
// to a specific binary.
func (u *IMAGE_DEBUG_INFO_CODEVIEW_UNPACKED) String() string {
var b strings.Builder
fmt.Fprintf(&b, "%08X%04X%04X", u.GUID.Data1, u.GUID.Data2, u.GUID.Data3)
for _, v := range u.GUID.Data4 {
fmt.Fprintf(&b, "%02X", v)
}
fmt.Fprintf(&b, "%X", u.Age)
return b.String()
}
const codeViewSignature = 0x53445352
func (u *IMAGE_DEBUG_INFO_CODEVIEW_UNPACKED) unpack(r *bufio.Reader) error {
var signature uint32
if err := binaryRead(r, &signature); err != nil {
return err
}
if signature != codeViewSignature {
return ErrBadCodeView
}
if err := binaryRead(r, &u.GUID); err != nil {
return err
}
if err := binaryRead(r, &u.Age); err != nil {
return err
}
var storage [16]byte
pdbBytes := storage[:0]
for b, err := r.ReadByte(); err == nil && b != 0; b, err = r.ReadByte() {
pdbBytes = append(pdbBytes, b)
}
u.PDBPath = string(pdbBytes)
return nil
}
// ExtractCodeViewInfo obtains CodeView debug information from de, assuming that
// de represents CodeView debug info.
func (nfo *PEHeaders) ExtractCodeViewInfo(de IMAGE_DEBUG_DIRECTORY) (*IMAGE_DEBUG_INFO_CODEVIEW_UNPACKED, error) {
if de.Type != IMAGE_DEBUG_TYPE_CODEVIEW {
return nil, ErrNotCodeView
}
var sr *io.SectionReader
switch v := nfo.r.(type) {
case *peFile:
sr = io.NewSectionReader(v, int64(de.PointerToRawData), int64(de.SizeOfData))
case *peModule:
sr = io.NewSectionReader(v, int64(de.AddressOfRawData), int64(de.SizeOfData))
default:
return nil, ErrInvalidBinary
}
cv := new(IMAGE_DEBUG_INFO_CODEVIEW_UNPACKED)
if err := cv.unpack(bufio.NewReader(sr)); err != nil {
return nil, err
}
return cv, nil
}
func readFull(r io.Reader, buf []byte) (n int, err error) {
n, err = io.ReadFull(r, buf)
if err == io.ErrUnexpectedEOF {
err = ErrBadLength
}
return n, err
}
func (nfo *PEHeaders) extractAuthenticode(dde DataDirectoryEntry) (any, error) {
if _, ok := nfo.r.(*peFile); !ok {
// Authenticode; only available in file, not loaded at runtime.
return nil, ErrUnavailableInModule
}
var result []AuthenticodeCert
// The VirtualAddress is a file offset.
sr := io.NewSectionReader(nfo.r, int64(dde.VirtualAddress), int64(dde.Size))
var curOffset int64
szEntry := unsafe.Sizeof(_WIN_CERTIFICATE_HEADER{})
for {
var entry AuthenticodeCert
if err := binaryRead(sr, &entry.header); err != nil {
if err == io.EOF {
break
}
return nil, err
}
curOffset += int64(szEntry)
if uintptr(entry.header.Length) < szEntry {
return nil, ErrInvalidBinary
}
entry.data = make([]byte, uintptr(entry.header.Length)-szEntry)
n, err := readFull(sr, entry.data)
if err != nil {
return nil, err
}
curOffset += int64(n)
result = append(result, entry)
curOffset = alignUp(curOffset, 8)
if _, err := sr.Seek(curOffset, io.SeekStart); err != nil {
if err == io.EOF {
break
}
return nil, err
}
}
return result, nil
}