Files
2026-07-18 21:37:15 +03:00

146 lines
4.3 KiB
Go

package winpwn
import (
"errors"
"strings"
)
// Export describes one entry of a PE's export table (EAT), the analogue of
// pwntools' libc.symbols[...] entries.
type Export struct {
// Name is empty when the function is exported by ordinal only.
Name string
Ordinal uint16
// RVA is the function's address. It is zero when ForwardTarget is set:
// the export doesn't point at code in this module at all, it forwards
// to a function in another DLL.
RVA uint32
// ForwardTarget is "DLLNAME.FuncName" when this export is a forwarder
// (e.g. api-ms-win-core-*.dll entries that forward into kernelbase.dll).
// Use ParseForwardTarget to split it.
ForwardTarget string
}
// ParseForwardTarget splits a forwarder string ("KERNELBASE.CreateFileW")
// into the target DLL and function name.
func ParseForwardTarget(forward string) (dll string, fn string) {
idx := strings.LastIndex(forward, ".")
if idx == -1 {
return "", forward
}
return forward[:idx], forward[idx+1:]
}
// exportDirectory mirrors winnt.h's IMAGE_EXPORT_DIRECTORY.
type exportDirectory struct {
Characteristics uint32
TimeDateStamp uint32
MajorVersion uint16
MinorVersion uint16
Name uint32
Base uint32
NumberOfFunctions uint32
NumberOfNames uint32
AddressOfFunctions uint32
AddressOfNames uint32
AddressOfNameOrdinals uint32
}
// ListExports walks the full Export Address Table, the analogue of
// pwntools' libc.symbols when you need every entry rather than a single
// lookup. Functions exported by ordinal only (no name) come back with
// Name == "". Works against a live-process-backed PEFile exactly as well
// as a disk-backed one (see RVAToFileOffset).
func (p *PEFile) ListExports() ([]Export, error) {
h, err := p.header()
if err != nil {
return nil, err
}
dir := h.dataDirectory[0]
if dir.VirtualAddress == 0 {
return nil, errors.New("export table not found")
}
exportOffset := p.RVAToFileOffset(dir.VirtualAddress)
if exportOffset == 0 {
return nil, errors.New("failed to map export directory RVA to file offset")
}
var expDir exportDirectory
if err := p.readStructAt(exportOffset, &expDir); err != nil {
return nil, err
}
funcsOffset := p.RVAToFileOffset(expDir.AddressOfFunctions)
namesOffset := p.RVAToFileOffset(expDir.AddressOfNames)
ordinalsOffset := p.RVAToFileOffset(expDir.AddressOfNameOrdinals)
// Build ordinal-index -> name from the name table before walking
// AddressOfFunctions, since not every function slot has a name.
nameByOrdinalIndex := make(map[uint16]string, expDir.NumberOfNames)
for i := uint32(0); i < expDir.NumberOfNames; i++ {
var nameRVA uint32
if err := p.readStructAt(namesOffset+int64(i*4), &nameRVA); err != nil {
return nil, err
}
name, err := p.readCString(p.RVAToFileOffset(nameRVA))
if err != nil {
return nil, err
}
var ordinalIndex uint16
if err := p.readStructAt(ordinalsOffset+int64(i*2), &ordinalIndex); err != nil {
return nil, err
}
nameByOrdinalIndex[ordinalIndex] = name
}
// A forwarder's "RVA" doesn't point at code: it points back inside the
// export directory itself, at an ASCII "DLL.Func" string.
forwarderLo := dir.VirtualAddress
forwarderHi := dir.VirtualAddress + dir.Size
exports := make([]Export, 0, expDir.NumberOfFunctions)
for i := uint32(0); i < expDir.NumberOfFunctions; i++ {
var rva uint32
if err := p.readStructAt(funcsOffset+int64(i*4), &rva); err != nil {
return nil, err
}
if rva == 0 {
continue // unused ordinal slot
}
e := Export{
Name: nameByOrdinalIndex[uint16(i)],
Ordinal: uint16(expDir.Base + i),
}
if rva >= forwarderLo && rva < forwarderHi {
fwd, err := p.readCString(p.RVAToFileOffset(rva))
if err != nil {
return nil, err
}
e.ForwardTarget = fwd
} else {
e.RVA = rva
}
exports = append(exports, e)
}
return exports, nil
}
// GetExport looks up a single export by name and reports whether it
// forwards to another DLL, unlike GetProcAddress which returns a bare RVA.
func (p *PEFile) GetExport(name string) (*Export, error) {
exports, err := p.ListExports()
if err != nil {
return nil, err
}
for i := range exports {
if exports[i].Name == name {
return &exports[i], nil
}
}
return nil, errors.New("function not found in export table: " + name)
}