github.com/saferwall/pe@v1.5.2/helper.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 "bytes" 9 "encoding/binary" 10 "errors" 11 "golang.org/x/text/encoding/unicode" 12 "path" 13 "path/filepath" 14 "runtime" 15 "strings" 16 ) 17 18 const ( 19 // TinyPESize On Windows XP (x32) the smallest PE executable is 97 bytes. 20 TinyPESize = 97 21 22 // FileAlignmentHardcodedValue represents the value which PointerToRawData 23 // should be at least equal or bigger to, or it will be rounded to zero. 24 // According to http://corkami.blogspot.com/2010/01/parce-que-la-planche-aura-brule.html 25 // if PointerToRawData is less that 0x200 it's rounded to zero. 26 FileAlignmentHardcodedValue = 0x200 27 ) 28 29 // Errors 30 var ( 31 32 // ErrInvalidPESize is returned when the file size is less that the smallest 33 // PE file size possible.ErrImageOS2SignatureFound 34 ErrInvalidPESize = errors.New("not a PE file, smaller than tiny PE") 35 36 // ErrDOSMagicNotFound is returned when file is potentially a ZM executable. 37 ErrDOSMagicNotFound = errors.New("DOS Header magic not found") 38 39 // ErrInvalidElfanewValue is returned when e_lfanew is larger than file size. 40 ErrInvalidElfanewValue = errors.New("invalid e_lfanew value. Probably not a PE file") 41 42 // ErrInvalidNtHeaderOffset is returned when the NT Header offset is beyond 43 // the image file. 44 ErrInvalidNtHeaderOffset = errors.New( 45 "invalid NT Header Offset. NT Header Signature not found") 46 47 // ErrImageOS2SignatureFound is returned when signature is for a NE file. 48 ErrImageOS2SignatureFound = errors.New( 49 "not a valid PE signature. Probably a NE file") 50 51 // ErrImageOS2LESignatureFound is returned when signature is for a LE file. 52 ErrImageOS2LESignatureFound = errors.New( 53 "not a valid PE signature. Probably an LE file") 54 55 // ErrImageVXDSignatureFound is returned when signature is for a LX file. 56 ErrImageVXDSignatureFound = errors.New( 57 "not a valid PE signature. Probably an LX file") 58 59 // ErrImageTESignatureFound is returned when signature is for a TE file. 60 ErrImageTESignatureFound = errors.New( 61 "not a valid PE signature. Probably a TE file") 62 63 // ErrImageNtSignatureNotFound is returned when PE magic signature is not found. 64 ErrImageNtSignatureNotFound = errors.New( 65 "not a valid PE signature. Magic not found") 66 67 // ErrImageNtOptionalHeaderMagicNotFound is returned when optional header 68 // magic is different from PE32/PE32+. 69 ErrImageNtOptionalHeaderMagicNotFound = errors.New( 70 "not a valid PE signature. Optional Header magic not found") 71 72 // ErrImageBaseNotAligned is reported when the image base is not aligned to 64K. 73 ErrImageBaseNotAligned = errors.New( 74 "corrupt PE file. Image base not aligned to 64 K") 75 76 // AnoImageBaseOverflow is reported when the image base + SizeOfImage is 77 // larger than 80000000h/FFFF080000000000h in PE32/P32+. 78 AnoImageBaseOverflow = "Image base beyond allowed address" 79 80 // ErrInvalidSectionFileAlignment is reported when section alignment is less than a 81 // PAGE_SIZE and section alignment != file alignment. 82 ErrInvalidSectionFileAlignment = errors.New("corrupt PE file. Section " + 83 "alignment is less than a PAGE_SIZE and section alignment != file alignment") 84 85 // AnoInvalidSizeOfImage is reported when SizeOfImage is not multiple of 86 // SectionAlignment. 87 AnoInvalidSizeOfImage = "Invalid SizeOfImage value, should be multiple " + 88 "of SectionAlignment" 89 90 // ErrOutsideBoundary is reported when attempting to read an address beyond 91 // file image limits. 92 ErrOutsideBoundary = errors.New("reading data outside boundary") 93 ) 94 95 // Max returns the larger of x or y. 96 func Max(x, y uint32) uint32 { 97 if x < y { 98 return y 99 } 100 return x 101 } 102 103 func min(a, b uint32) uint32 { 104 if a < b { 105 return a 106 } 107 return b 108 } 109 110 // Min returns the min number in a slice. 111 func Min(values []uint32) uint32 { 112 min := values[0] 113 for _, v := range values { 114 if v < min { 115 min = v 116 } 117 } 118 return min 119 } 120 121 // IsValidDosFilename returns true if the DLL name is likely to be valid. 122 // Valid FAT32 8.3 short filename characters according to: 123 // http://en.wikipedia.org/wiki/8.3_filename 124 // The filename length is not checked because the DLLs filename 125 // can be longer that the 8.3 126 func IsValidDosFilename(filename string) bool { 127 alphabet := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" 128 numerals := "0123456789" 129 special := "!#$%&'()-@^_`{}~+,.;=[]\\/" 130 charset := alphabet + numerals + special 131 for _, c := range filename { 132 if !strings.Contains(charset, string(c)) { 133 return false 134 } 135 } 136 return true 137 } 138 139 // IsValidFunctionName checks if an imported name uses the valid accepted 140 // characters expected in mangled function names. If the symbol's characters 141 // don't fall within this charset we will assume the name is invalid. 142 func IsValidFunctionName(functionName string) bool { 143 alphabet := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" 144 numerals := "0123456789" 145 special := "_?@$()<>" 146 charset := alphabet + numerals + special 147 for _, c := range charset { 148 if !strings.Contains(charset, string(c)) { 149 return false 150 } 151 } 152 return true 153 } 154 155 // IsPrintable checks weather a string is printable. 156 func IsPrintable(s string) bool { 157 alphabet := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" 158 numerals := "0123456789" 159 whitespace := " \t\n\r\v\f" 160 special := "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~" 161 charset := alphabet + numerals + special + whitespace 162 for _, c := range charset { 163 if !strings.Contains(charset, string(c)) { 164 return false 165 } 166 } 167 return true 168 } 169 170 // getSectionByRva returns the section containing the given address. 171 func (pe *File) getSectionByRva(rva uint32) *Section { 172 for _, section := range pe.Sections { 173 if section.Contains(rva, pe) { 174 return §ion 175 } 176 } 177 return nil 178 } 179 180 // getSectionByRva returns the section name containing the given address. 181 func (pe *File) getSectionNameByRva(rva uint32) string { 182 for _, section := range pe.Sections { 183 if section.Contains(rva, pe) { 184 return section.String() 185 } 186 } 187 return "" 188 } 189 190 func (pe *File) getSectionByOffset(offset uint32) *Section { 191 for _, section := range pe.Sections { 192 if section.Header.PointerToRawData == 0 { 193 continue 194 } 195 196 adjustedPointer := pe.adjustFileAlignment( 197 section.Header.PointerToRawData) 198 if adjustedPointer <= offset && 199 offset < (adjustedPointer+section.Header.SizeOfRawData) { 200 return §ion 201 } 202 } 203 return nil 204 } 205 206 // GetOffsetFromRva returns the file offset corresponding to this RVA. 207 func (pe *File) GetOffsetFromRva(rva uint32) uint32 { 208 209 // Given a RVA, this method will find the section where the 210 // data lies and return the offset within the file. 211 section := pe.getSectionByRva(rva) 212 if section == nil { 213 if rva < uint32(len(pe.data)) { 214 return rva 215 } 216 return ^uint32(0) 217 } 218 sectionAlignment := pe.adjustSectionAlignment(section.Header.VirtualAddress) 219 fileAlignment := pe.adjustFileAlignment(section.Header.PointerToRawData) 220 return rva - sectionAlignment + fileAlignment 221 } 222 223 // GetRVAFromOffset returns an RVA given an offset. 224 func (pe *File) GetRVAFromOffset(offset uint32) uint32 { 225 section := pe.getSectionByOffset(offset) 226 minAddr := ^uint32(0) 227 if section == nil { 228 229 if len(pe.Sections) == 0 { 230 return offset 231 } 232 233 for _, section := range pe.Sections { 234 vaddr := pe.adjustSectionAlignment(section.Header.VirtualAddress) 235 if vaddr < minAddr { 236 minAddr = vaddr 237 } 238 } 239 // Assume that offset lies within the headers 240 // The case illustrating this behavior can be found at: 241 // http://corkami.blogspot.com/2010/01/hey-hey-hey-whats-in-your-head.html 242 // where the import table is not contained by any section 243 // hence the RVA needs to be resolved to a raw offset 244 if offset < minAddr { 245 return offset 246 } 247 248 pe.logger.Warn("data at Offset can't be fetched. Corrupt header?") 249 return ^uint32(0) 250 } 251 sectionAlignment := pe.adjustSectionAlignment(section.Header.VirtualAddress) 252 fileAlignment := pe.adjustFileAlignment(section.Header.PointerToRawData) 253 return offset - fileAlignment + sectionAlignment 254 } 255 256 func (pe *File) getSectionByName(secName string) (section *ImageSectionHeader) { 257 for _, section := range pe.Sections { 258 if section.String() == secName { 259 return §ion.Header 260 } 261 262 } 263 return nil 264 } 265 266 // getStringAtRVA returns an ASCII string located at the given address. 267 func (pe *File) getStringAtRVA(rva, maxLen uint32) string { 268 if rva == 0 { 269 return "" 270 } 271 272 section := pe.getSectionByRva(rva) 273 if section == nil { 274 if rva > pe.size { 275 return "" 276 } 277 278 end := rva + maxLen 279 if end > pe.size { 280 end = pe.size 281 } 282 s := pe.GetStringFromData(0, pe.data[rva:end]) 283 return string(s) 284 } 285 s := pe.GetStringFromData(0, section.Data(rva, maxLen, pe)) 286 return string(s) 287 } 288 289 func (pe *File) readUnicodeStringAtRVA(rva uint32, maxLength uint32) string { 290 str := "" 291 offset := pe.GetOffsetFromRva(rva) 292 i := uint32(0) 293 for i = 0; i < maxLength; i += 2 { 294 if offset+i >= pe.size || pe.data[offset+i] == 0 { 295 break 296 } 297 298 str += string(pe.data[offset+i]) 299 } 300 return str 301 } 302 303 func (pe *File) readASCIIStringAtOffset(offset, maxLength uint32) (uint32, string) { 304 str := "" 305 i := uint32(0) 306 307 for i = 0; i < maxLength; i++ { 308 if offset+i >= pe.size || pe.data[offset+i] == 0 { 309 break 310 } 311 312 str += string(pe.data[offset+i]) 313 } 314 return i, str 315 } 316 317 // GetStringFromData returns ASCII string from within the data. 318 func (pe *File) GetStringFromData(offset uint32, data []byte) []byte { 319 320 dataSize := uint32(len(data)) 321 if dataSize == 0 { 322 return nil 323 } 324 325 if offset > dataSize { 326 return nil 327 } 328 329 end := offset 330 for end < dataSize { 331 if data[end] == 0 { 332 break 333 } 334 end++ 335 } 336 return data[offset:end] 337 } 338 339 // getStringAtOffset returns a string given an offset. 340 func (pe *File) getStringAtOffset(offset, size uint32) (string, error) { 341 if offset+size > pe.size { 342 return "", ErrOutsideBoundary 343 } 344 345 str := string(pe.data[offset : offset+size]) 346 return strings.Replace(str, "\x00", "", -1), nil 347 } 348 349 // GetData returns the data given an RVA regardless of the section where it 350 // lies on. 351 func (pe *File) GetData(rva, length uint32) ([]byte, error) { 352 353 // Given a RVA and the size of the chunk to retrieve, this method 354 // will find the section where the data lies and return the data. 355 section := pe.getSectionByRva(rva) 356 357 var end uint32 358 if length > 0 { 359 end = rva + length 360 } else { 361 end = 0 362 } 363 364 if section == nil { 365 if rva < uint32(len(pe.Header)) { 366 return pe.Header[rva:end], nil 367 } 368 369 // Before we give up we check whether the file might contain the data 370 // anyway. There are cases of PE files without sections that rely on 371 // windows loading the first 8291 bytes into memory and assume the data 372 // will be there. A functional file with these characteristics is: 373 // MD5: 0008892cdfbc3bda5ce047c565e52295 374 // SHA-1: c7116b9ff950f86af256defb95b5d4859d4752a9 375 376 if rva < uint32(len(pe.data)) { 377 return pe.data[rva:end], nil 378 } 379 380 return nil, errors.New("data at RVA can't be fetched. Corrupt header?") 381 } 382 return section.Data(rva, length, pe), nil 383 } 384 385 // The alignment factor (in bytes) that is used to align the raw data of sections 386 // in the image file. The value should be a power of 2 between 512 and 64 K, 387 // inclusive. The default is 512. If the SectionAlignment is less than the 388 // architecture's page size, then FileAlignment must match SectionAlignment. 389 func (pe *File) adjustFileAlignment(va uint32) uint32 { 390 391 var fileAlignment uint32 392 switch pe.Is64 { 393 case true: 394 fileAlignment = pe.NtHeader.OptionalHeader.(ImageOptionalHeader64).FileAlignment 395 case false: 396 fileAlignment = pe.NtHeader.OptionalHeader.(ImageOptionalHeader32).FileAlignment 397 } 398 399 if fileAlignment > FileAlignmentHardcodedValue && fileAlignment%2 != 0 { 400 pe.Anomalies = append(pe.Anomalies, ErrInvalidFileAlignment) 401 } 402 403 if fileAlignment < FileAlignmentHardcodedValue { 404 return va 405 } 406 407 // round it to 0x200 if not power of 2. 408 // According to https://github.com/corkami/docs/blob/master/PE/PE.md 409 // if PointerToRawData is less that 0x200 it's rounded to zero. Loading the 410 // test file in a debugger it's easy to verify that the PointerToRawData 411 // value of 1 is rounded to zero. Hence we reproduce the behavior 412 return (va / 0x200) * 0x200 413 414 } 415 416 // The alignment (in bytes) of sections when they are loaded into memory 417 // It must be greater than or equal to FileAlignment. The default is the 418 // page size for the architecture. 419 func (pe *File) adjustSectionAlignment(va uint32) uint32 { 420 var fileAlignment, sectionAlignment uint32 421 422 switch pe.Is64 { 423 case true: 424 fileAlignment = pe.NtHeader.OptionalHeader.(ImageOptionalHeader64).FileAlignment 425 sectionAlignment = pe.NtHeader.OptionalHeader.(ImageOptionalHeader64).SectionAlignment 426 case false: 427 fileAlignment = pe.NtHeader.OptionalHeader.(ImageOptionalHeader32).FileAlignment 428 sectionAlignment = pe.NtHeader.OptionalHeader.(ImageOptionalHeader32).SectionAlignment 429 } 430 431 if fileAlignment < FileAlignmentHardcodedValue && 432 fileAlignment != sectionAlignment { 433 pe.Anomalies = append(pe.Anomalies, ErrInvalidSectionAlignment) 434 } 435 436 if sectionAlignment < 0x1000 { // page size 437 sectionAlignment = fileAlignment 438 } 439 440 // 0x200 is the minimum valid FileAlignment according to the documentation 441 // although ntoskrnl.exe has an alignment of 0x80 in some Windows versions 442 if sectionAlignment != 0 && va%sectionAlignment != 0 { 443 return sectionAlignment * (va / sectionAlignment) 444 } 445 return va 446 } 447 448 // alignDword aligns the offset on a 32-bit boundary. 449 func alignDword(offset, base uint32) uint32 { 450 return ((offset + base + 3) & 0xfffffffc) - (base & 0xfffffffc) 451 } 452 453 // stringInSlice checks weather a string exists in a slice of strings. 454 func stringInSlice(a string, list []string) bool { 455 for _, b := range list { 456 if b == a { 457 return true 458 } 459 } 460 return false 461 } 462 463 // intInSlice checks weather a uint32 exists in a slice of uint32. 464 func intInSlice(a uint32, list []uint32) bool { 465 for _, b := range list { 466 if b == a { 467 return true 468 } 469 } 470 return false 471 } 472 473 // IsDriver returns true if the PE file is a Windows driver. 474 func (pe *File) IsDriver() bool { 475 476 // Checking that the ImageBase field of the OptionalHeader is above or 477 // equal to 0x80000000 (that is, whether it lies in the upper 2GB of 478 //the address space, normally belonging to the kernel) is not a 479 // reliable enough indicator. For instance, PEs that play the invalid 480 // ImageBase trick to get relocated could be incorrectly assumed to be 481 // drivers. 482 483 // Checking if any section characteristics have the IMAGE_SCN_MEM_NOT_PAGED 484 // flag set is not reliable either. 485 486 // If there's still no import directory (the PE doesn't have one or it's 487 // malformed), give up. 488 if len(pe.Imports) == 0 { 489 return false 490 } 491 492 // DIRECTORY_ENTRY_IMPORT will now exist, although it may be empty. 493 // If it imports from "ntoskrnl.exe" or other kernel components it should 494 // be a driver. 495 systemDLLs := []string{"ntoskrnl.exe", "hal.dll", "ndis.sys", 496 "bootvid.dll", "kdcom.dll"} 497 for _, dll := range pe.Imports { 498 if stringInSlice(strings.ToLower(dll.Name), systemDLLs) { 499 return true 500 } 501 } 502 503 // If still we couldn't tell, check common driver section with combination 504 // of IMAGE_SUBSYSTEM_NATIVE or IMAGE_SUBSYSTEM_NATIVE_WINDOWS. 505 subsystem := ImageOptionalHeaderSubsystemType(0) 506 oh32 := ImageOptionalHeader32{} 507 oh64 := ImageOptionalHeader64{} 508 switch pe.Is64 { 509 case true: 510 oh64 = pe.NtHeader.OptionalHeader.(ImageOptionalHeader64) 511 subsystem = oh64.Subsystem 512 case false: 513 oh32 = pe.NtHeader.OptionalHeader.(ImageOptionalHeader32) 514 subsystem = oh32.Subsystem 515 } 516 commonDriverSectionNames := []string{"page", "paged", "nonpage", "init"} 517 for _, section := range pe.Sections { 518 s := strings.ToLower(section.String()) 519 if stringInSlice(s, commonDriverSectionNames) && 520 (subsystem&ImageSubsystemNativeWindows != 0 || 521 subsystem&ImageSubsystemNative != 0) { 522 return true 523 } 524 525 } 526 527 return false 528 } 529 530 // IsDLL returns true if the PE file is a standard DLL. 531 func (pe *File) IsDLL() bool { 532 return pe.NtHeader.FileHeader.Characteristics&ImageFileDLL != 0 533 } 534 535 // IsEXE returns true if the PE file is a standard executable. 536 func (pe *File) IsEXE() bool { 537 538 // Returns true only if the file has the IMAGE_FILE_EXECUTABLE_IMAGE flag set 539 // and the IMAGE_FILE_DLL not set and the file does not appear to be a driver either. 540 if pe.IsDLL() || pe.IsDriver() { 541 return false 542 } 543 544 if pe.NtHeader.FileHeader.Characteristics&ImageFileExecutableImage == 0 { 545 return false 546 } 547 548 return true 549 } 550 551 // Checksum calculates the PE checksum as generated by CheckSumMappedFile(). 552 func (pe *File) Checksum() uint32 { 553 var checksum uint64 = 0 554 var max uint64 = 0x100000000 555 currentDword := uint32(0) 556 557 // Get the Checksum offset. 558 optionalHeaderOffset := pe.DOSHeader.AddressOfNewEXEHeader + 4 + 559 uint32(binary.Size(pe.NtHeader.FileHeader)) 560 561 // `CheckSum` field position in optional PE headers is always 64 for PE and PE+. 562 checksumOffset := optionalHeaderOffset + 64 563 564 // Verify the data is DWORD-aligned and add padding if needed 565 remainder := pe.size % 4 566 dataLen := pe.size 567 if remainder > 0 { 568 dataLen = pe.size + (4 - remainder) 569 paddedBytes := make([]byte, 4-remainder) 570 pe.data = append(pe.data, paddedBytes...) 571 } 572 573 for i := uint32(0); i < dataLen; i += 4 { 574 // Skip the checksum field. 575 if i == checksumOffset { 576 continue 577 } 578 579 // Read DWORD from file. 580 currentDword = binary.LittleEndian.Uint32(pe.data[i:]) 581 582 // Calculate checksum. 583 checksum = (checksum & 0xffffffff) + uint64(currentDword) + (checksum >> 32) 584 if checksum > max { 585 checksum = (checksum & 0xffffffff) + (checksum >> 32) 586 } 587 } 588 589 checksum = (checksum & 0xffff) + (checksum >> 16) 590 checksum = checksum + (checksum >> 16) 591 checksum = checksum & 0xffff 592 593 // The length is the one of the original data, not the padded one 594 checksum += uint64(pe.size) 595 596 return uint32(checksum) 597 } 598 599 // ReadUint64 read a uint64 from a buffer. 600 func (pe *File) ReadUint64(offset uint32) (uint64, error) { 601 if offset+8 > pe.size { 602 return 0, ErrOutsideBoundary 603 } 604 605 return binary.LittleEndian.Uint64(pe.data[offset:]), nil 606 } 607 608 // ReadUint32 read a uint32 from a buffer. 609 func (pe *File) ReadUint32(offset uint32) (uint32, error) { 610 if offset > pe.size-4 { 611 return 0, ErrOutsideBoundary 612 } 613 614 return binary.LittleEndian.Uint32(pe.data[offset:]), nil 615 } 616 617 // ReadUint16 read a uint16 from a buffer. 618 func (pe *File) ReadUint16(offset uint32) (uint16, error) { 619 if offset > pe.size-2 { 620 return 0, ErrOutsideBoundary 621 } 622 623 return binary.LittleEndian.Uint16(pe.data[offset:]), nil 624 } 625 626 // ReadUint8 read a uint8 from a buffer. 627 func (pe *File) ReadUint8(offset uint32) (uint8, error) { 628 if offset+1 > pe.size { 629 return 0, ErrOutsideBoundary 630 } 631 632 b := pe.data[offset : offset+1][0] 633 return uint8(b), nil 634 } 635 636 func (pe *File) structUnpack(iface interface{}, offset, size uint32) (err error) { 637 // Boundary check 638 totalSize := offset + size 639 640 // Integer overflow 641 if (totalSize > offset) != (size > 0) { 642 return ErrOutsideBoundary 643 } 644 645 if offset >= pe.size || totalSize > pe.size { 646 return ErrOutsideBoundary 647 } 648 649 buf := bytes.NewReader(pe.data[offset : offset+size]) 650 err = binary.Read(buf, binary.LittleEndian, iface) 651 if err != nil { 652 return err 653 } 654 return nil 655 } 656 657 // ReadBytesAtOffset returns a byte array from offset. 658 func (pe *File) ReadBytesAtOffset(offset, size uint32) ([]byte, error) { 659 // Boundary check 660 totalSize := offset + size 661 662 // Integer overflow 663 if (totalSize > offset) != (size > 0) { 664 return nil, ErrOutsideBoundary 665 } 666 667 if offset >= pe.size || totalSize > pe.size { 668 return nil, ErrOutsideBoundary 669 } 670 671 return pe.data[offset : offset+size], nil 672 } 673 674 // DecodeUTF16String decodes the UTF16 string from the byte slice. 675 func DecodeUTF16String(b []byte) (string, error) { 676 n := bytes.Index(b, []byte{0, 0}) 677 if n == 0 { 678 return "", nil 679 } 680 decoder := unicode.UTF16(unicode.LittleEndian, unicode.UseBOM).NewDecoder() 681 s, err := decoder.Bytes(b[0 : n+1]) 682 if err != nil { 683 return "", err 684 } 685 return string(s), nil 686 } 687 688 // IsBitSet returns true when a bit on a particular position is set. 689 func IsBitSet(n uint64, pos int) bool { 690 val := n & (1 << pos) 691 return (val > 0) 692 } 693 694 func getAbsoluteFilePath(testfile string) string { 695 _, p, _, _ := runtime.Caller(0) 696 return path.Join(filepath.Dir(p), testfile) 697 }