Home | History | Annotate | Download | only in pe
      1 // Copyright 2009 The Go Authors.  All rights reserved.
      2 // Use of this source code is governed by a BSD-style
      3 // license that can be found in the LICENSE file.
      4 
      5 // Package pe implements access to PE (Microsoft Windows Portable Executable) files.
      6 package pe
      7 
      8 import (
      9 	"debug/dwarf"
     10 	"encoding/binary"
     11 	"errors"
     12 	"fmt"
     13 	"io"
     14 	"os"
     15 	"strconv"
     16 )
     17 
     18 // A File represents an open PE file.
     19 type File struct {
     20 	FileHeader
     21 	OptionalHeader interface{} // of type *OptionalHeader32 or *OptionalHeader64
     22 	Sections       []*Section
     23 	Symbols        []*Symbol
     24 
     25 	closer io.Closer
     26 }
     27 
     28 type SectionHeader struct {
     29 	Name                 string
     30 	VirtualSize          uint32
     31 	VirtualAddress       uint32
     32 	Size                 uint32
     33 	Offset               uint32
     34 	PointerToRelocations uint32
     35 	PointerToLineNumbers uint32
     36 	NumberOfRelocations  uint16
     37 	NumberOfLineNumbers  uint16
     38 	Characteristics      uint32
     39 }
     40 
     41 type Section struct {
     42 	SectionHeader
     43 
     44 	// Embed ReaderAt for ReadAt method.
     45 	// Do not embed SectionReader directly
     46 	// to avoid having Read and Seek.
     47 	// If a client wants Read and Seek it must use
     48 	// Open() to avoid fighting over the seek offset
     49 	// with other clients.
     50 	io.ReaderAt
     51 	sr *io.SectionReader
     52 }
     53 
     54 type Symbol struct {
     55 	Name          string
     56 	Value         uint32
     57 	SectionNumber int16
     58 	Type          uint16
     59 	StorageClass  uint8
     60 }
     61 
     62 type ImportDirectory struct {
     63 	OriginalFirstThunk uint32
     64 	TimeDateStamp      uint32
     65 	ForwarderChain     uint32
     66 	Name               uint32
     67 	FirstThunk         uint32
     68 
     69 	dll string
     70 }
     71 
     72 // Data reads and returns the contents of the PE section.
     73 func (s *Section) Data() ([]byte, error) {
     74 	dat := make([]byte, s.sr.Size())
     75 	n, err := s.sr.ReadAt(dat, 0)
     76 	if n == len(dat) {
     77 		err = nil
     78 	}
     79 	return dat[0:n], err
     80 }
     81 
     82 // Open returns a new ReadSeeker reading the PE section.
     83 func (s *Section) Open() io.ReadSeeker { return io.NewSectionReader(s.sr, 0, 1<<63-1) }
     84 
     85 type FormatError struct {
     86 	off int64
     87 	msg string
     88 	val interface{}
     89 }
     90 
     91 func (e *FormatError) Error() string {
     92 	msg := e.msg
     93 	if e.val != nil {
     94 		msg += fmt.Sprintf(" '%v'", e.val)
     95 	}
     96 	msg += fmt.Sprintf(" in record at byte %#x", e.off)
     97 	return msg
     98 }
     99 
    100 // Open opens the named file using os.Open and prepares it for use as a PE binary.
    101 func Open(name string) (*File, error) {
    102 	f, err := os.Open(name)
    103 	if err != nil {
    104 		return nil, err
    105 	}
    106 	ff, err := NewFile(f)
    107 	if err != nil {
    108 		f.Close()
    109 		return nil, err
    110 	}
    111 	ff.closer = f
    112 	return ff, nil
    113 }
    114 
    115 // Close closes the File.
    116 // If the File was created using NewFile directly instead of Open,
    117 // Close has no effect.
    118 func (f *File) Close() error {
    119 	var err error
    120 	if f.closer != nil {
    121 		err = f.closer.Close()
    122 		f.closer = nil
    123 	}
    124 	return err
    125 }
    126 
    127 var (
    128 	sizeofOptionalHeader32 = uint16(binary.Size(OptionalHeader32{}))
    129 	sizeofOptionalHeader64 = uint16(binary.Size(OptionalHeader64{}))
    130 )
    131 
    132 // NewFile creates a new File for accessing a PE binary in an underlying reader.
    133 func NewFile(r io.ReaderAt) (*File, error) {
    134 	f := new(File)
    135 	sr := io.NewSectionReader(r, 0, 1<<63-1)
    136 
    137 	var dosheader [96]byte
    138 	if _, err := r.ReadAt(dosheader[0:], 0); err != nil {
    139 		return nil, err
    140 	}
    141 	var base int64
    142 	if dosheader[0] == 'M' && dosheader[1] == 'Z' {
    143 		signoff := int64(binary.LittleEndian.Uint32(dosheader[0x3c:]))
    144 		var sign [4]byte
    145 		r.ReadAt(sign[:], signoff)
    146 		if !(sign[0] == 'P' && sign[1] == 'E' && sign[2] == 0 && sign[3] == 0) {
    147 			return nil, errors.New("Invalid PE File Format.")
    148 		}
    149 		base = signoff + 4
    150 	} else {
    151 		base = int64(0)
    152 	}
    153 	sr.Seek(base, os.SEEK_SET)
    154 	if err := binary.Read(sr, binary.LittleEndian, &f.FileHeader); err != nil {
    155 		return nil, err
    156 	}
    157 	if f.FileHeader.Machine != IMAGE_FILE_MACHINE_UNKNOWN && f.FileHeader.Machine != IMAGE_FILE_MACHINE_AMD64 && f.FileHeader.Machine != IMAGE_FILE_MACHINE_I386 {
    158 		return nil, errors.New("Invalid PE File Format.")
    159 	}
    160 
    161 	var ss []byte
    162 	if f.FileHeader.NumberOfSymbols > 0 {
    163 		// Get COFF string table, which is located at the end of the COFF symbol table.
    164 		sr.Seek(int64(f.FileHeader.PointerToSymbolTable+COFFSymbolSize*f.FileHeader.NumberOfSymbols), os.SEEK_SET)
    165 		var l uint32
    166 		if err := binary.Read(sr, binary.LittleEndian, &l); err != nil {
    167 			return nil, err
    168 		}
    169 		ss = make([]byte, l)
    170 		if _, err := r.ReadAt(ss, int64(f.FileHeader.PointerToSymbolTable+COFFSymbolSize*f.FileHeader.NumberOfSymbols)); err != nil {
    171 			return nil, err
    172 		}
    173 
    174 		// Process COFF symbol table.
    175 		sr.Seek(int64(f.FileHeader.PointerToSymbolTable), os.SEEK_SET)
    176 		aux := uint8(0)
    177 		for i := 0; i < int(f.FileHeader.NumberOfSymbols); i++ {
    178 			cs := new(COFFSymbol)
    179 			if err := binary.Read(sr, binary.LittleEndian, cs); err != nil {
    180 				return nil, err
    181 			}
    182 			if aux > 0 {
    183 				aux--
    184 				continue
    185 			}
    186 			var name string
    187 			if cs.Name[0] == 0 && cs.Name[1] == 0 && cs.Name[2] == 0 && cs.Name[3] == 0 {
    188 				si := int(binary.LittleEndian.Uint32(cs.Name[4:]))
    189 				name, _ = getString(ss, si)
    190 			} else {
    191 				name = cstring(cs.Name[:])
    192 			}
    193 			aux = cs.NumberOfAuxSymbols
    194 			s := &Symbol{
    195 				Name:          name,
    196 				Value:         cs.Value,
    197 				SectionNumber: cs.SectionNumber,
    198 				Type:          cs.Type,
    199 				StorageClass:  cs.StorageClass,
    200 			}
    201 			f.Symbols = append(f.Symbols, s)
    202 		}
    203 	}
    204 
    205 	// Read optional header.
    206 	sr.Seek(base, os.SEEK_SET)
    207 	if err := binary.Read(sr, binary.LittleEndian, &f.FileHeader); err != nil {
    208 		return nil, err
    209 	}
    210 	var oh32 OptionalHeader32
    211 	var oh64 OptionalHeader64
    212 	switch f.FileHeader.SizeOfOptionalHeader {
    213 	case sizeofOptionalHeader32:
    214 		if err := binary.Read(sr, binary.LittleEndian, &oh32); err != nil {
    215 			return nil, err
    216 		}
    217 		if oh32.Magic != 0x10b { // PE32
    218 			return nil, fmt.Errorf("pe32 optional header has unexpected Magic of 0x%x", oh32.Magic)
    219 		}
    220 		f.OptionalHeader = &oh32
    221 	case sizeofOptionalHeader64:
    222 		if err := binary.Read(sr, binary.LittleEndian, &oh64); err != nil {
    223 			return nil, err
    224 		}
    225 		if oh64.Magic != 0x20b { // PE32+
    226 			return nil, fmt.Errorf("pe32+ optional header has unexpected Magic of 0x%x", oh64.Magic)
    227 		}
    228 		f.OptionalHeader = &oh64
    229 	}
    230 
    231 	// Process sections.
    232 	f.Sections = make([]*Section, f.FileHeader.NumberOfSections)
    233 	for i := 0; i < int(f.FileHeader.NumberOfSections); i++ {
    234 		sh := new(SectionHeader32)
    235 		if err := binary.Read(sr, binary.LittleEndian, sh); err != nil {
    236 			return nil, err
    237 		}
    238 		var name string
    239 		if sh.Name[0] == '\x2F' {
    240 			si, _ := strconv.Atoi(cstring(sh.Name[1:]))
    241 			name, _ = getString(ss, si)
    242 		} else {
    243 			name = cstring(sh.Name[0:])
    244 		}
    245 		s := new(Section)
    246 		s.SectionHeader = SectionHeader{
    247 			Name:                 name,
    248 			VirtualSize:          sh.VirtualSize,
    249 			VirtualAddress:       sh.VirtualAddress,
    250 			Size:                 sh.SizeOfRawData,
    251 			Offset:               sh.PointerToRawData,
    252 			PointerToRelocations: sh.PointerToRelocations,
    253 			PointerToLineNumbers: sh.PointerToLineNumbers,
    254 			NumberOfRelocations:  sh.NumberOfRelocations,
    255 			NumberOfLineNumbers:  sh.NumberOfLineNumbers,
    256 			Characteristics:      sh.Characteristics,
    257 		}
    258 		s.sr = io.NewSectionReader(r, int64(s.SectionHeader.Offset), int64(s.SectionHeader.Size))
    259 		s.ReaderAt = s.sr
    260 		f.Sections[i] = s
    261 	}
    262 	return f, nil
    263 }
    264 
    265 func cstring(b []byte) string {
    266 	var i int
    267 	for i = 0; i < len(b) && b[i] != 0; i++ {
    268 	}
    269 	return string(b[0:i])
    270 }
    271 
    272 // getString extracts a string from symbol string table.
    273 func getString(section []byte, start int) (string, bool) {
    274 	if start < 0 || start >= len(section) {
    275 		return "", false
    276 	}
    277 
    278 	for end := start; end < len(section); end++ {
    279 		if section[end] == 0 {
    280 			return string(section[start:end]), true
    281 		}
    282 	}
    283 	return "", false
    284 }
    285 
    286 // Section returns the first section with the given name, or nil if no such
    287 // section exists.
    288 func (f *File) Section(name string) *Section {
    289 	for _, s := range f.Sections {
    290 		if s.Name == name {
    291 			return s
    292 		}
    293 	}
    294 	return nil
    295 }
    296 
    297 func (f *File) DWARF() (*dwarf.Data, error) {
    298 	// There are many other DWARF sections, but these
    299 	// are the ones the debug/dwarf package uses.
    300 	// Don't bother loading others.
    301 	var names = [...]string{"abbrev", "info", "line", "str"}
    302 	var dat [len(names)][]byte
    303 	for i, name := range names {
    304 		name = ".debug_" + name
    305 		s := f.Section(name)
    306 		if s == nil {
    307 			continue
    308 		}
    309 		b, err := s.Data()
    310 		if err != nil && uint32(len(b)) < s.Size {
    311 			return nil, err
    312 		}
    313 		if 0 < s.VirtualSize && s.VirtualSize < s.Size {
    314 			b = b[:s.VirtualSize]
    315 		}
    316 		dat[i] = b
    317 	}
    318 
    319 	abbrev, info, line, str := dat[0], dat[1], dat[2], dat[3]
    320 	return dwarf.New(abbrev, nil, nil, info, line, nil, nil, str)
    321 }
    322 
    323 // ImportedSymbols returns the names of all symbols
    324 // referred to by the binary f that are expected to be
    325 // satisfied by other libraries at dynamic load time.
    326 // It does not return weak symbols.
    327 func (f *File) ImportedSymbols() ([]string, error) {
    328 	pe64 := f.Machine == IMAGE_FILE_MACHINE_AMD64
    329 	ds := f.Section(".idata")
    330 	if ds == nil {
    331 		// not dynamic, so no libraries
    332 		return nil, nil
    333 	}
    334 	d, err := ds.Data()
    335 	if err != nil {
    336 		return nil, err
    337 	}
    338 	var ida []ImportDirectory
    339 	for len(d) > 0 {
    340 		var dt ImportDirectory
    341 		dt.OriginalFirstThunk = binary.LittleEndian.Uint32(d[0:4])
    342 		dt.Name = binary.LittleEndian.Uint32(d[12:16])
    343 		dt.FirstThunk = binary.LittleEndian.Uint32(d[16:20])
    344 		d = d[20:]
    345 		if dt.OriginalFirstThunk == 0 {
    346 			break
    347 		}
    348 		ida = append(ida, dt)
    349 	}
    350 	names, _ := ds.Data()
    351 	var all []string
    352 	for _, dt := range ida {
    353 		dt.dll, _ = getString(names, int(dt.Name-ds.VirtualAddress))
    354 		d, _ = ds.Data()
    355 		// seek to OriginalFirstThunk
    356 		d = d[dt.OriginalFirstThunk-ds.VirtualAddress:]
    357 		for len(d) > 0 {
    358 			if pe64 { // 64bit
    359 				va := binary.LittleEndian.Uint64(d[0:8])
    360 				d = d[8:]
    361 				if va == 0 {
    362 					break
    363 				}
    364 				if va&0x8000000000000000 > 0 { // is Ordinal
    365 					// TODO add dynimport ordinal support.
    366 				} else {
    367 					fn, _ := getString(names, int(uint32(va)-ds.VirtualAddress+2))
    368 					all = append(all, fn+":"+dt.dll)
    369 				}
    370 			} else { // 32bit
    371 				va := binary.LittleEndian.Uint32(d[0:4])
    372 				d = d[4:]
    373 				if va == 0 {
    374 					break
    375 				}
    376 				if va&0x80000000 > 0 { // is Ordinal
    377 					// TODO add dynimport ordinal support.
    378 					//ord := va&0x0000FFFF
    379 				} else {
    380 					fn, _ := getString(names, int(va-ds.VirtualAddress+2))
    381 					all = append(all, fn+":"+dt.dll)
    382 				}
    383 			}
    384 		}
    385 	}
    386 
    387 	return all, nil
    388 }
    389 
    390 // ImportedLibraries returns the names of all libraries
    391 // referred to by the binary f that are expected to be
    392 // linked with the binary at dynamic link time.
    393 func (f *File) ImportedLibraries() ([]string, error) {
    394 	// TODO
    395 	// cgo -dynimport don't use this for windows PE, so just return.
    396 	return nil, nil
    397 }
    398