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

165 lines
4.1 KiB
Go

package winpwn
import (
"errors"
)
// Import describes one entry of a PE's Import Address Table (IAT), the
// analogue of pwntools reading a binary's .got/.dynsym to find which libc
// functions it pulls in.
type Import struct {
DLL string
// Name is empty when the function is imported by ordinal only.
Name string
Ordinal uint16
// IATRVA is the RVA of this import's slot in the IAT -- the address the
// loader overwrites with the real function pointer at load time, and
// the address you'd target if you wanted to overwrite the import to
// redirect a call.
IATRVA uint32
}
// ImportedLib describes one DLL a PE imports, plus that DLL's current live
// image base in this process (see (*PEFile).ImportedLibs).
type ImportedLib struct {
Name string
// Base is 0 if Err is set (the library failed to load in this process).
Base uint64
Err error
}
const importOrdinalFlag64 = uint64(1) << 63
const importOrdinalFlag32 = uint32(1) << 31
// importDescriptor mirrors winnt.h's IMAGE_IMPORT_DESCRIPTOR.
type importDescriptor struct {
OriginalFirstThunk uint32
TimeDateStamp uint32
ForwarderChain uint32
Name uint32
FirstThunk uint32
}
// ListImports walks the full Import Directory Table (IDT) and each DLL's
// thunk array, resolving every imported name/ordinal and its IAT slot
// address. The analogue of pwntools poking at a binary's dynamic symbol
// table to see what it links against.
func (p *PEFile) ListImports() ([]Import, error) {
h, err := p.header()
if err != nil {
return nil, err
}
dir := h.dataDirectory[1]
if dir.VirtualAddress == 0 {
return nil, errors.New("import table not found")
}
descOffset := p.RVAToFileOffset(dir.VirtualAddress)
if descOffset == 0 {
return nil, errors.New("failed to map import directory RVA to file offset")
}
var imports []Import
for i := 0; ; i++ {
var desc importDescriptor
if err := p.readStructAt(descOffset+int64(i*20), &desc); err != nil {
return nil, err
}
if desc.OriginalFirstThunk == 0 && desc.Name == 0 && desc.FirstThunk == 0 {
break // null terminator descriptor
}
dllName, err := p.readCString(p.RVAToFileOffset(desc.Name))
if err != nil {
return nil, err
}
thunkRVA := desc.OriginalFirstThunk
if thunkRVA == 0 {
thunkRVA = desc.FirstThunk // some linkers omit the ILT entirely
}
entries, err := p.walkThunks(h.is64, thunkRVA, desc.FirstThunk)
if err != nil {
return nil, err
}
for i := range entries {
entries[i].DLL = dllName
}
imports = append(imports, entries...)
}
return imports, nil
}
func (p *PEFile) walkThunks(is64 bool, thunkRVA uint32, iatRVA uint32) ([]Import, error) {
var out []Import
thunkSize := uint32(4)
if is64 {
thunkSize = 8
}
for j := uint32(0); ; j++ {
thunkOffset := p.RVAToFileOffset(thunkRVA + j*thunkSize)
var imp Import
imp.IATRVA = iatRVA + j*thunkSize
if is64 {
var thunk uint64
if err := p.readStructAt(thunkOffset, &thunk); err != nil {
return nil, err
}
if thunk == 0 {
break
}
if thunk&importOrdinalFlag64 != 0 {
imp.Ordinal = uint16(thunk & 0xFFFF)
} else {
name, err := p.readCString(p.RVAToFileOffset(uint32(thunk)) + 2) // skip Hint WORD
if err != nil {
return nil, err
}
imp.Name = name
}
} else {
var thunk uint32
if err := p.readStructAt(thunkOffset, &thunk); err != nil {
return nil, err
}
if thunk == 0 {
break
}
if thunk&importOrdinalFlag32 != 0 {
imp.Ordinal = uint16(thunk & 0xFFFF)
} else {
name, err := p.readCString(p.RVAToFileOffset(thunk) + 2)
if err != nil {
return nil, err
}
imp.Name = name
}
}
out = append(out, imp)
}
return out, nil
}
// FindImport looks for a specific imported function by name across every
// imported DLL -- the quick "does this binary already pull in
// VirtualProtect/LoadLibraryA/GetProcAddress" check.
func (p *PEFile) FindImport(funcName string) (*Import, error) {
imports, err := p.ListImports()
if err != nil {
return nil, err
}
for i := range imports {
if imports[i].Name == funcName {
return &imports[i], nil
}
}
return nil, errors.New("import not found: " + funcName)
}