github.com/saferwall/pe@v1.5.2/file.go (about) 1 // Copyright 2018 Saferwall. All rights reserved. 2 // Use of this source code is governed by Apache v2 license 3 // license that can be found in the LICENSE file. 4 5 package pe 6 7 import ( 8 "errors" 9 "os" 10 11 "github.com/edsrzf/mmap-go" 12 "github.com/saferwall/pe/log" 13 ) 14 15 // A File represents an open PE file. 16 type File struct { 17 DOSHeader ImageDOSHeader `json:"dos_header,omitempty"` 18 RichHeader RichHeader `json:"rich_header,omitempty"` 19 NtHeader ImageNtHeader `json:"nt_header,omitempty"` 20 COFF COFF `json:"coff,omitempty"` 21 Sections []Section `json:"sections,omitempty"` 22 Imports []Import `json:"imports,omitempty"` 23 Export Export `json:"export,omitempty"` 24 Debugs []DebugEntry `json:"debugs,omitempty"` 25 Relocations []Relocation `json:"relocations,omitempty"` 26 Resources ResourceDirectory `json:"resources,omitempty"` 27 TLS TLSDirectory `json:"tls,omitempty"` 28 LoadConfig LoadConfig `json:"load_config,omitempty"` 29 Exceptions []Exception `json:"exceptions,omitempty"` 30 Certificates Certificate `json:"certificates,omitempty"` 31 DelayImports []DelayImport `json:"delay_imports,omitempty"` 32 BoundImports []BoundImportDescriptorData `json:"bound_imports,omitempty"` 33 GlobalPtr uint32 `json:"global_ptr,omitempty"` 34 CLR CLRData `json:"clr,omitempty"` 35 IAT []IATEntry `json:"iat,omitempty"` 36 Anomalies []string `json:"anomalies,omitempty"` 37 Header []byte 38 data mmap.MMap 39 FileInfo 40 size uint32 41 OverlayOffset int64 42 f *os.File 43 opts *Options 44 logger *log.Helper 45 } 46 47 // Options that influence the PE parsing behaviour. 48 type Options struct { 49 50 // Parse only the PE header and do not parse data directories, by default (false). 51 Fast bool 52 53 // Includes section entropy, by default (false). 54 SectionEntropy bool 55 56 // Maximum COFF symbols to parse, by default (MaxDefaultCOFFSymbolsCount). 57 MaxCOFFSymbolsCount uint32 58 59 // Maximum relocations to parse, by default (MaxDefaultRelocEntriesCount). 60 MaxRelocEntriesCount uint32 61 62 // Disable certificate validation, by default (false). 63 DisableCertValidation bool 64 65 // Disable signature validation, by default (false). 66 DisableSignatureValidation bool 67 68 // A custom logger. 69 Logger log.Logger 70 71 // OmitExportDirectory determines if export directory parsing is skipped, by default (false). 72 OmitExportDirectory bool 73 74 // OmitImportDirectory determines if import directory parsing is skipped, by default (false). 75 OmitImportDirectory bool 76 77 // OmitExceptionDirectory determines if exception directory parsing is skipped, by default (false). 78 OmitExceptionDirectory bool 79 80 // OmitResourceDirectory determines if resource directory parsing is skipped, by default (false). 81 OmitResourceDirectory bool 82 83 // OmitSecurityDirectory determines if security directory parsing is skipped, by default (false). 84 OmitSecurityDirectory bool 85 86 // OmitRelocDirectory determines if relocation directory parsing is skipped, by default (false). 87 OmitRelocDirectory bool 88 89 // OmitDebugDirectory determines if debug directory parsing is skipped, by default (false). 90 OmitDebugDirectory bool 91 92 // OmitArchitectureDirectory determines if architecture directory parsing is skipped, by default (false). 93 OmitArchitectureDirectory bool 94 95 // OmitGlobalPtrDirectory determines if global pointer directory parsing is skipped, by default (false). 96 OmitGlobalPtrDirectory bool 97 98 // OmitTLSDirectory determines if TLS directory parsing is skipped, by default (false). 99 OmitTLSDirectory bool 100 101 // OmitLoadConfigDirectory determines if load config directory parsing is skipped, by default (false). 102 OmitLoadConfigDirectory bool 103 104 // OmitBoundImportDirectory determines if bound import directory parsing is skipped, by default (false). 105 OmitBoundImportDirectory bool 106 107 // OmitIATDirectory determines if IAT directory parsing is skipped, by default (false). 108 OmitIATDirectory bool 109 110 // OmitDelayImportDirectory determines if delay import directory parsing is skipped, by default (false). 111 OmitDelayImportDirectory bool 112 113 // OmitCLRHeaderDirectory determines if CLR header directory parsing is skipped, by default (false). 114 OmitCLRHeaderDirectory bool 115 } 116 117 // New instantiates a file instance with options given a file name. 118 func New(name string, opts *Options) (*File, error) { 119 120 f, err := os.Open(name) 121 if err != nil { 122 return nil, err 123 } 124 125 // Memory map the file instead of using read/write. 126 data, err := mmap.Map(f, mmap.RDONLY, 0) 127 if err != nil { 128 f.Close() 129 return nil, err 130 } 131 132 file := File{} 133 if opts != nil { 134 file.opts = opts 135 } else { 136 file.opts = &Options{} 137 } 138 139 if file.opts.MaxCOFFSymbolsCount == 0 { 140 file.opts.MaxCOFFSymbolsCount = MaxDefaultCOFFSymbolsCount 141 } 142 if file.opts.MaxRelocEntriesCount == 0 { 143 file.opts.MaxRelocEntriesCount = MaxDefaultRelocEntriesCount 144 } 145 146 var logger log.Logger 147 if opts.Logger == nil { 148 logger = log.NewStdLogger(os.Stdout) 149 file.logger = log.NewHelper(log.NewFilter(logger, 150 log.FilterLevel(log.LevelError))) 151 } else { 152 file.logger = log.NewHelper(opts.Logger) 153 } 154 155 file.data = data 156 file.size = uint32(len(file.data)) 157 file.f = f 158 return &file, nil 159 } 160 161 // NewBytes instantiates a file instance with options given a memory buffer. 162 func NewBytes(data []byte, opts *Options) (*File, error) { 163 164 file := File{} 165 if opts != nil { 166 file.opts = opts 167 } else { 168 file.opts = &Options{} 169 } 170 171 if file.opts.MaxCOFFSymbolsCount == 0 { 172 file.opts.MaxCOFFSymbolsCount = MaxDefaultCOFFSymbolsCount 173 } 174 if file.opts.MaxRelocEntriesCount == 0 { 175 file.opts.MaxRelocEntriesCount = MaxDefaultRelocEntriesCount 176 } 177 178 var logger log.Logger 179 if opts.Logger == nil { 180 logger = log.NewStdLogger(os.Stdout) 181 file.logger = log.NewHelper(log.NewFilter(logger, 182 log.FilterLevel(log.LevelError))) 183 } else { 184 file.logger = log.NewHelper(opts.Logger) 185 } 186 187 file.data = data 188 file.size = uint32(len(file.data)) 189 return &file, nil 190 } 191 192 // Close closes the File. 193 func (pe *File) Close() error { 194 if pe.data != nil { 195 _ = pe.data.Unmap() 196 } 197 198 if pe.f != nil { 199 return pe.f.Close() 200 } 201 return nil 202 } 203 204 // Parse performs the file parsing for a PE binary. 205 func (pe *File) Parse() error { 206 207 // check for the smallest PE size. 208 if len(pe.data) < TinyPESize { 209 return ErrInvalidPESize 210 } 211 212 // Parse the DOS header. 213 err := pe.ParseDOSHeader() 214 if err != nil { 215 return err 216 } 217 218 // Parse the Rich header. 219 err = pe.ParseRichHeader() 220 if err != nil { 221 pe.logger.Errorf("rich header parsing failed: %v", err) 222 } 223 224 // Parse the NT header. 225 err = pe.ParseNTHeader() 226 if err != nil { 227 return err 228 } 229 230 // Parse COFF symbol table. 231 err = pe.ParseCOFFSymbolTable() 232 if err != nil { 233 pe.logger.Debugf("coff symbols parsing failed: %v", err) 234 } 235 236 // Parse the Section Header. 237 err = pe.ParseSectionHeader() 238 if err != nil { 239 return err 240 } 241 242 // In fast mode, do not parse data directories. 243 if pe.opts.Fast { 244 return nil 245 } 246 247 // Parse the Data Directory entries. 248 return pe.ParseDataDirectories() 249 } 250 251 // String stringify the data directory entry. 252 func (entry ImageDirectoryEntry) String() string { 253 dataDirMap := map[ImageDirectoryEntry]string{ 254 ImageDirectoryEntryExport: "Export", 255 ImageDirectoryEntryImport: "Import", 256 ImageDirectoryEntryResource: "Resource", 257 ImageDirectoryEntryException: "Exception", 258 ImageDirectoryEntryCertificate: "Security", 259 ImageDirectoryEntryBaseReloc: "Relocation", 260 ImageDirectoryEntryDebug: "Debug", 261 ImageDirectoryEntryArchitecture: "Architecture", 262 ImageDirectoryEntryGlobalPtr: "GlobalPtr", 263 ImageDirectoryEntryTLS: "TLS", 264 ImageDirectoryEntryLoadConfig: "LoadConfig", 265 ImageDirectoryEntryBoundImport: "BoundImport", 266 ImageDirectoryEntryIAT: "IAT", 267 ImageDirectoryEntryDelayImport: "DelayImport", 268 ImageDirectoryEntryCLR: "CLR", 269 ImageDirectoryEntryReserved: "Reserved", 270 } 271 272 return dataDirMap[entry] 273 } 274 275 // ParseDataDirectories parses the data directories. The DataDirectory is an 276 // array of 16 structures. Each array entry has a predefined meaning for what 277 // it refers to. 278 func (pe *File) ParseDataDirectories() error { 279 280 foundErr := false 281 oh32 := ImageOptionalHeader32{} 282 oh64 := ImageOptionalHeader64{} 283 284 switch pe.Is64 { 285 case true: 286 oh64 = pe.NtHeader.OptionalHeader.(ImageOptionalHeader64) 287 case false: 288 oh32 = pe.NtHeader.OptionalHeader.(ImageOptionalHeader32) 289 } 290 291 // Maps data directory index to function which parses that directory. 292 funcMaps := make(map[ImageDirectoryEntry]func(uint32, uint32) error) 293 if !pe.opts.OmitExportDirectory { 294 funcMaps[ImageDirectoryEntryExport] = pe.parseExportDirectory 295 } 296 if !pe.opts.OmitImportDirectory { 297 funcMaps[ImageDirectoryEntryImport] = pe.parseImportDirectory 298 } 299 if !pe.opts.OmitExceptionDirectory { 300 funcMaps[ImageDirectoryEntryException] = pe.parseExceptionDirectory 301 } 302 if !pe.opts.OmitResourceDirectory { 303 funcMaps[ImageDirectoryEntryResource] = pe.parseResourceDirectory 304 } 305 if !pe.opts.OmitSecurityDirectory { 306 funcMaps[ImageDirectoryEntryCertificate] = pe.parseSecurityDirectory 307 } 308 if !pe.opts.OmitRelocDirectory { 309 funcMaps[ImageDirectoryEntryBaseReloc] = pe.parseRelocDirectory 310 } 311 if !pe.opts.OmitDebugDirectory { 312 funcMaps[ImageDirectoryEntryDebug] = pe.parseDebugDirectory 313 } 314 if !pe.opts.OmitArchitectureDirectory { 315 funcMaps[ImageDirectoryEntryArchitecture] = pe.parseArchitectureDirectory 316 } 317 if !pe.opts.OmitGlobalPtrDirectory { 318 funcMaps[ImageDirectoryEntryGlobalPtr] = pe.parseGlobalPtrDirectory 319 } 320 if !pe.opts.OmitTLSDirectory { 321 funcMaps[ImageDirectoryEntryTLS] = pe.parseTLSDirectory 322 } 323 if !pe.opts.OmitLoadConfigDirectory { 324 funcMaps[ImageDirectoryEntryLoadConfig] = pe.parseLoadConfigDirectory 325 } 326 if !pe.opts.OmitBoundImportDirectory { 327 funcMaps[ImageDirectoryEntryBoundImport] = pe.parseBoundImportDirectory 328 } 329 if !pe.opts.OmitIATDirectory { 330 funcMaps[ImageDirectoryEntryIAT] = pe.parseIATDirectory 331 } 332 if !pe.opts.OmitDelayImportDirectory { 333 funcMaps[ImageDirectoryEntryDelayImport] = pe.parseDelayImportDirectory 334 } 335 if !pe.opts.OmitCLRHeaderDirectory { 336 funcMaps[ImageDirectoryEntryCLR] = pe.parseCLRHeaderDirectory 337 } 338 339 // Iterate over data directories and call the appropriate function. 340 for entryIndex := ImageDirectoryEntry(0); entryIndex < ImageNumberOfDirectoryEntries; entryIndex++ { 341 342 var va, size uint32 343 switch pe.Is64 { 344 case true: 345 dirEntry := oh64.DataDirectory[entryIndex] 346 va = dirEntry.VirtualAddress 347 size = dirEntry.Size 348 case false: 349 dirEntry := oh32.DataDirectory[entryIndex] 350 va = dirEntry.VirtualAddress 351 size = dirEntry.Size 352 } 353 354 if va != 0 { 355 func() { 356 // keep parsing data directories even though some entries fails. 357 defer func() { 358 if e := recover(); e != nil { 359 pe.logger.Errorf("unhandled exception when parsing data directory %s, reason: %v", 360 entryIndex.String(), e) 361 foundErr = true 362 } 363 }() 364 365 // the last entry in the data directories is reserved and must be zero. 366 if entryIndex == ImageDirectoryEntryReserved { 367 pe.Anomalies = append(pe.Anomalies, AnoReservedDataDirectoryEntry) 368 return 369 } 370 371 parseDirectory, ok := funcMaps[entryIndex] 372 if !ok { 373 return 374 } 375 err := parseDirectory(va, size) 376 if err != nil { 377 pe.logger.Warnf("failed to parse data directory %s, reason: %v", 378 entryIndex.String(), err) 379 } 380 }() 381 } 382 } 383 384 if foundErr { 385 return errors.New("Data directory parsing failed") 386 } 387 return nil 388 }