github.com/decomp/exp@v0.0.0-20210624183419-6d058f5e1da6/cmd/dump_imports/main.go (about) 1 // The dump_imports tool dumps the imports of a PE binary in NASM syntax. 2 package main 3 4 import ( 5 "bytes" 6 "encoding/binary" 7 "encoding/hex" 8 "flag" 9 "fmt" 10 "io" 11 "io/ioutil" 12 "log" 13 "os" 14 "sort" 15 "strings" 16 17 "github.com/kr/pretty" 18 "github.com/mewkiz/pkg/pathutil" 19 "github.com/mewrev/pe" 20 "github.com/pkg/errors" 21 ) 22 23 func main() { 24 flag.Parse() 25 for _, path := range flag.Args() { 26 if err := dumpImports(path); err != nil { 27 log.Fatalf("%+v", err) 28 } 29 } 30 } 31 32 // dumpImports dumps the imports of the given executable in NASM syntax. 33 func dumpImports(path string) error { 34 // Parse PE file. 35 file, err := pe.Open(path) 36 if err != nil { 37 return errors.WithStack(err) 38 } 39 defer file.Close() 40 optHdr, err := file.OptHeader() 41 if err != nil { 42 return errors.WithStack(err) 43 } 44 sectHdrs, err := file.SectHeaders() 45 if err != nil { 46 return errors.WithStack(err) 47 } 48 // data returns the data at the given address. 49 data := func(relAddr uint32) []byte { 50 for _, sectHdr := range sectHdrs { 51 start := sectHdr.RelAddr 52 end := start + sectHdr.Size 53 if start <= relAddr && relAddr < end { 54 data, err := file.Section(sectHdr) 55 if err != nil { 56 panic(fmt.Errorf("unable to read section contents at RVA 0x%08X; %v", relAddr, err)) 57 } 58 offset := relAddr - start 59 return data[offset:] 60 } 61 } 62 panic(fmt.Errorf("unable to locate section containing RVA 0x%08X", relAddr)) 63 } 64 const ( 65 importTableIndex = 1 66 importAddressTableIndex = 12 67 ) 68 itDir := optHdr.DataDirs[importTableIndex] 69 iatDir := optHdr.DataDirs[importAddressTableIndex] 70 71 it := data(itDir.RelAddr) 72 it = it[:itDir.Size] 73 fmt.Println("import table:") 74 fmt.Println(hex.Dump(it)) 75 76 iat := data(iatDir.RelAddr) 77 iat = iat[:iatDir.Size] 78 fmt.Println("import address table:") 79 fmt.Println(hex.Dump(iat)) 80 81 // Parse imports. 82 if err := parseImports(it, data); err != nil { 83 return errors.WithStack(err) 84 } 85 return nil 86 } 87 88 // An importDesc is an import descriptor. 89 type importDesc struct { 90 // Import name table RVA. 91 ImportNameTableRVA uint32 92 // Time stamp. 93 Date uint32 94 // Forward chain; index into importAddressTableRVA for forwarding a function 95 // to another DLL. 96 ForwardChain uint32 97 // DLL name RVA. 98 DLLNameRVA uint32 99 // Import address table RVA. 100 ImportAddressTableRVA uint32 101 } 102 103 // An importName specifies the name of an import. 104 type importName struct { 105 // Approximate ordinal number (used by loader to initiate binary search). 106 Ordinal uint16 107 // Name of the import. 108 Name string 109 } 110 111 // parseImports parses the given import table and import address table. 112 func parseImports(it []byte, data func(addr uint32) []byte) error { 113 buf := &bytes.Buffer{} 114 const itHeader = ` 115 ; === [ import table ] ========================================================= 116 ; 117 ; Array of IMAGE_IMPORT_DESCRIPTOR structures, which is terminated by an 118 ; empty struct. 119 ; 120 ; ------------------------------------------------------------------------------ 121 122 import_table: 123 ` 124 buf.WriteString(itHeader[1:]) 125 r := bytes.NewReader(it) 126 var impDescs []importDesc 127 for { 128 var impDesc importDesc 129 if err := binary.Read(r, binary.LittleEndian, &impDesc); err != nil { 130 if errors.Cause(err) == io.EOF { 131 break 132 } 133 return errors.WithStack(err) 134 } 135 if impDesc == (importDesc{}) { 136 buf.WriteString(" times 5 dd 0x00000000\n\n") 137 break 138 } 139 impDescs = append(impDescs, impDesc) 140 pretty.Println("import descriptor:", impDesc) 141 dllName := parseString(data(impDesc.DLLNameRVA)) 142 fmt.Println(" => DLL name:", dllName) 143 144 const importDescFormat = ` 145 dd int_%s - IMAGE_BASE ; pINT_first_trunk 146 dd 0x%08X ; TimeDateStamp 147 dd 0x%08X ; pForwardChain 148 dd sz%s_dll - IMAGE_BASE 149 dd iat_%s - IMAGE_BASE ; pIAT_first_trunk 150 151 ` 152 name := strings.ToLower(pathutil.TrimExt(dllName)) 153 fmt.Fprintf(buf, importDescFormat[1:], name, impDesc.Date, impDesc.ForwardChain, strings.Title(name), name) 154 } 155 const importTableFooter = ` 156 import_table_size equ $ - import_table 157 158 ; === [/ import table ] ======================================================== 159 160 ` 161 buf.WriteString(importTableFooter[1:]) 162 163 // Dump INTs. 164 const intHeader = ` 165 ; === [ Import Name Tables (INTs) ] ============================================ 166 ; 167 ; Each Import Name Table (INT) consists of an array of IMAGE_THUNK_DATA 168 ; structures, which are terminated by an empty struct. 169 ; 170 ; This entire table is UNUSED. It is sometimes refered to as the 171 ; "hint table", which is never overwritten or altered. 172 ; 173 ; ------------------------------------------------------------------------------ 174 175 int: 176 177 ` 178 buf.WriteString(intHeader[1:]) 179 sort.Slice(impDescs, func(i, j int) bool { 180 return impDescs[i].ImportNameTableRVA < impDescs[j].ImportNameTableRVA 181 }) 182 for _, impDesc := range impDescs { 183 dllINT := data(impDesc.ImportNameTableRVA) 184 r := bytes.NewReader(dllINT) 185 dllName := parseString(data(impDesc.DLLNameRVA)) 186 name := strings.ToLower(pathutil.TrimExt(dllName)) 187 if err := dumpINT(name, r, buf, data); err != nil { 188 return errors.WithStack(err) 189 } 190 } 191 const intHeaderFooter = ` 192 int_size equ $ - int 193 194 ; === [/ Import Name Tables (INTs) ] =========================================== 195 196 ` 197 buf.WriteString(intHeaderFooter[1:]) 198 199 // Dump IATs. 200 const iatHeader = ` 201 ; === [ Import Address Tables (IATs) ] ========================================= 202 ; 203 ; Each Import Address Table (IAT) consists of an array of IMAGE_THUNK_DATA 204 ; structures, which are terminated by an empty struct. 205 ; 206 ; The linker will overwrite these DWORDs with the actuall address of the 207 ; imported functions 208 ; 209 ; ------------------------------------------------------------------------------ 210 211 iat: 212 213 ` 214 buf.WriteString(iatHeader[1:]) 215 sort.Slice(impDescs, func(i, j int) bool { 216 return impDescs[i].ImportAddressTableRVA < impDescs[j].ImportAddressTableRVA 217 }) 218 for _, impDesc := range impDescs { 219 dllIAT := data(impDesc.ImportAddressTableRVA) 220 r := bytes.NewReader(dllIAT) 221 dllName := parseString(data(impDesc.DLLNameRVA)) 222 name := strings.ToLower(pathutil.TrimExt(dllName)) 223 if err := dumpIAT(name, r, buf, data); err != nil { 224 return errors.WithStack(err) 225 } 226 } 227 const iatHeaderFooter = ` 228 iat_size equ $ - iat 229 230 ; === [/ Import Address Tables (IATs) ] ======================================== 231 232 ` 233 buf.WriteString(iatHeaderFooter[1:]) 234 235 // Dump DLL strings. 236 237 var dlls []*DLL 238 for _, impDesc := range impDescs { 239 dllIAT := data(impDesc.ImportAddressTableRVA) 240 r = bytes.NewReader(dllIAT) 241 dllName := parseString(data(impDesc.DLLNameRVA)) 242 dll, err := parseDLL(dllName, r, data) 243 if err != nil { 244 return errors.WithStack(err) 245 } 246 dll.Addr = impDesc.DLLNameRVA 247 dlls = append(dlls, dll) 248 } 249 sort.Slice(dlls, func(i, j int) bool { 250 return dlls[i].Addr < dlls[j].Addr 251 }) 252 pretty.Println(dlls) 253 dumpDLLs(dlls, buf) 254 255 if err := os.MkdirAll("_dump_", 0755); err != nil { 256 return errors.WithStack(err) 257 } 258 if err := ioutil.WriteFile("_dump_/_idata.asm", buf.Bytes(), 0644); err != nil { 259 return errors.WithStack(err) 260 } 261 return nil 262 } 263 264 func dumpDLLs(dlls []*DLL, w io.Writer) { 265 266 const dllsHeader = ` 267 ; === [ dll and function names ] =============================================== 268 ; 269 ; each dll stores an array of IMAGE_IMPORT_BY_NAME structures and a string 270 ; corresponding to it's dll name. 271 ; 272 ; ------------------------------------------------------------------------------ 273 274 ` 275 fmt.Fprint(w, dllsHeader[1:]) 276 for _, dll := range dlls { 277 const dllHeaderFormat = ` 278 ; --- [ %s ] --------------------------------------------------------- 279 280 ` 281 fmt.Fprintf(w, dllHeaderFormat[1:], dll.Name) 282 for _, f := range dll.Funcs { 283 const funcFormat = ` 284 imp_%s: 285 dw 0x%04X 286 db '%s', 0x00 ; 0x%08X 287 align 2, db 0x00 288 289 ` 290 fmt.Fprintf(w, funcFormat[1:], f.Name, f.Ordinal, f.Name, f.Addr) 291 } 292 const dllFooterFormat = ` 293 sz%s: 294 db '%s', 0x00 ; 0x%08X 295 align 2, db 0x00 296 297 ` 298 fmt.Fprintf(w, dllFooterFormat[1:], strings.Title(strings.ToLower(strings.Replace(dll.Name, ".", "_", -1))), dll.Name, dll.Addr) 299 } 300 const dllsFooter = ` 301 ; === [/ dll and function names ] ============================================== 302 ` 303 fmt.Fprint(w, dllsFooter[1:]) 304 } 305 306 func dumpINT(dllName string, r io.Reader, w io.Writer, data func(addr uint32) []byte) error { 307 const intHeaderFormat = ` 308 ; --- [ %s.dll ] --------------------------------------------------------- 309 310 int_%s: 311 ` 312 fmt.Fprintf(w, intHeaderFormat[1:], dllName, dllName) 313 for { 314 var addr uint32 315 if err := binary.Read(r, binary.LittleEndian, &addr); err != nil { 316 if errors.Cause(err) == io.EOF { 317 break 318 } 319 return errors.WithStack(err) 320 } 321 if addr == 0 { 322 const intNullEntry = ` 323 dd 0x00000000 324 ` 325 fmt.Fprintln(w, intNullEntry[1:]) 326 break 327 } 328 fmt.Printf("addr: 0x%08X\n", addr) 329 if addr&0x80000000 != 0 { 330 // addr is an encoded ordinal, not an address. 331 ordinal := addr &^ 0x80000000 332 fmt.Println("ordinal:", ordinal) 333 const intOrdinalFormat = ` 334 dd 0x80000000 | %d 335 ` 336 fmt.Fprintf(w, intOrdinalFormat[1:], ordinal) 337 } else { 338 var ordinal uint16 339 if err := binary.Read(bytes.NewReader(data(addr)), binary.LittleEndian, &ordinal); err != nil { 340 return errors.WithStack(err) 341 } 342 funcName := parseString(data(addr + 2)) 343 fmt.Printf("function: %s (%d)\n", funcName, ordinal) 344 const intEntryFormat = ` 345 dd imp_%s - IMAGE_BASE 346 ` 347 fmt.Fprintf(w, intEntryFormat[1:], funcName) 348 } 349 } 350 return nil 351 } 352 353 type DLL struct { 354 Name string 355 Addr uint32 356 Funcs []*Func 357 } 358 359 type Func struct { 360 Addr uint32 361 Ordinal uint16 362 Name string 363 } 364 365 func parseDLL(dllName string, r io.Reader, data func(addr uint32) []byte) (*DLL, error) { 366 dll := &DLL{ 367 Name: dllName, 368 } 369 for { 370 var addr uint32 371 if err := binary.Read(r, binary.LittleEndian, &addr); err != nil { 372 if errors.Cause(err) == io.EOF { 373 break 374 } 375 return nil, errors.WithStack(err) 376 } 377 if addr == 0 { 378 break 379 } 380 if addr&0x80000000 != 0 { 381 // ordinal, nothing to do. 382 continue 383 } 384 f := &Func{ 385 Addr: addr, 386 } 387 dll.Funcs = append(dll.Funcs, f) 388 if err := binary.Read(bytes.NewReader(data(addr)), binary.LittleEndian, &f.Ordinal); err != nil { 389 return nil, errors.WithStack(err) 390 } 391 f.Name = parseString(data(addr + 2)) 392 } 393 less := func(i, j int) bool { 394 return dll.Funcs[i].Addr < dll.Funcs[j].Addr 395 } 396 sort.Slice(dll.Funcs, less) 397 return dll, nil 398 } 399 400 func dumpIAT(dllName string, r io.Reader, w io.Writer, data func(addr uint32) []byte) error { 401 const iatHeaderFormat = ` 402 ; --- [ %s.dll ] --------------------------------------------------------- 403 404 iat_%s: 405 ` 406 fmt.Fprintf(w, iatHeaderFormat[1:], dllName, dllName) 407 for { 408 var addr uint32 409 if err := binary.Read(r, binary.LittleEndian, &addr); err != nil { 410 if errors.Cause(err) == io.EOF { 411 break 412 } 413 return errors.WithStack(err) 414 } 415 if addr == 0 { 416 const iatNullEntry = ` 417 dd 0x00000000 418 ` 419 fmt.Fprintln(w, iatNullEntry[1:]) 420 break 421 } 422 fmt.Printf("addr: 0x%08X\n", addr) 423 if addr&0x80000000 != 0 { 424 // addr is an encoded ordinal, not an address. 425 ordinal := addr &^ 0x80000000 426 fmt.Println("ordinal:", ordinal) 427 const iatOrdinalFormat = ` 428 ia_%s_%d: 429 dd 0x80000000 | %d 430 ` 431 fmt.Fprintf(w, iatOrdinalFormat[1:], dllName, ordinal, ordinal) 432 } else { 433 var ordinal uint16 434 if err := binary.Read(bytes.NewReader(data(addr)), binary.LittleEndian, &ordinal); err != nil { 435 return errors.WithStack(err) 436 } 437 funcName := parseString(data(addr + 2)) 438 fmt.Printf("function: %s (%d)\n", funcName, ordinal) 439 const iatEntryFormat = ` 440 ia_%s: 441 dd imp_%s - IMAGE_BASE 442 ` 443 fmt.Fprintf(w, iatEntryFormat[1:], funcName, funcName) 444 } 445 } 446 return nil 447 } 448 449 // ### [ Helper functions ] #################################################### 450 451 // parseString converts the given NULL-terminated string to a Go string. 452 func parseString(buf []byte) string { 453 pos := bytes.IndexByte(buf, '\x00') 454 if pos == -1 { 455 panic(fmt.Errorf("unable to locate NULL-terminated string in % 02X", buf)) 456 } 457 return string(buf[:pos]) 458 }