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) }