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  }