github.com/saferwall/pe@v1.5.2/anomaly.go (about)

     1  // Copyright 2021 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  	"encoding/binary"
     9  	"time"
    10  )
    11  
    12  // Anomalies found in a PE
    13  var (
    14  
    15  	// AnoPEHeaderOverlapDOSHeader is reported when the PE headers overlaps with the DOS header.
    16  	AnoPEHeaderOverlapDOSHeader = "PE header overlaps with DOS header"
    17  
    18  	// AnoPETimeStampNull is reported when the file header timestamp is 0.
    19  	AnoPETimeStampNull = "file header timestamp set to 0"
    20  
    21  	// AnoPETimeStampFuture is reported when the file header timestamp is more
    22  	// than one day ahead of the current date timestamp.
    23  	AnoPETimeStampFuture = "file header timestamp set to 0"
    24  
    25  	// NumberOfSections is reported when number of sections is larger or equal than 10.
    26  	AnoNumberOfSections10Plus = "number of sections is 10+"
    27  
    28  	// AnoNumberOfSectionsNull is reported when sections count's is 0.
    29  	AnoNumberOfSectionsNull = "number of sections is 0"
    30  
    31  	// AnoSizeOfOptionalHeaderNull is reported when size of optional header is 0.
    32  	AnoSizeOfOptionalHeaderNull = "size of optional header is 0"
    33  
    34  	// AnoUncommonSizeOfOptionalHeader32 is reported when size of optional
    35  	// header for PE32 is larger than 0xE0.
    36  	AnoUncommonSizeOfOptionalHeader32 = "size of optional header is larger than 0xE0 (PE32)"
    37  
    38  	// AnoUncommonSizeOfOptionalHeader64 is reported when size of optional
    39  	// header for PE32+ is larger than 0xF0.
    40  	AnoUncommonSizeOfOptionalHeader64 = "size of optional header is larger than 0xF0 (PE32+)"
    41  
    42  	// AnoAddressOfEntryPointNull is reported when address of entry point is 0.
    43  	AnoAddressOfEntryPointNull = "address of entry point is 0"
    44  
    45  	// AnoAddressOfEPLessSizeOfHeaders is reported when address of entry point
    46  	// is smaller than size of headers, the file cannot run under Windows.
    47  	AnoAddressOfEPLessSizeOfHeaders = "address of entry point is smaller than size of headers, " +
    48  		"the file cannot run under Windows 8"
    49  
    50  	// AnoImageBaseNull is reported when the image base is null.
    51  	AnoImageBaseNull = "image base is 0"
    52  
    53  	// AnoDanSMagicOffset is reported when the `DanS` magic offset is different than 0x80.
    54  	AnoDanSMagicOffset = "`DanS` magic offset is different than 0x80"
    55  
    56  	// ErrInvalidFileAlignment is reported when file alignment is larger than
    57  	//  0x200 and not a power of 2.
    58  	ErrInvalidFileAlignment = "FileAlignment larger than 0x200 and not a power of 2"
    59  
    60  	// ErrInvalidSectionAlignment is reported when file alignment is lesser
    61  	// than 0x200 and different from section alignment.
    62  	ErrInvalidSectionAlignment = "FileAlignment lesser than 0x200 and different from section alignment"
    63  
    64  	// AnoMajorSubsystemVersion is reported when MajorSubsystemVersion has a
    65  	// value different than the standard 3 --> 6.
    66  	AnoMajorSubsystemVersion = "MajorSubsystemVersion is outside 3<-->6 boundary"
    67  
    68  	// AnonWin32VersionValue is reported when Win32VersionValue is different than 0
    69  	AnonWin32VersionValue = "Win32VersionValue is a reserved field, must be set to zero"
    70  
    71  	// AnoInvalidPEChecksum is reported when the optional header checksum field
    72  	// is different from what it should normally be.
    73  	AnoInvalidPEChecksum = "optional header checksum is invalid"
    74  
    75  	// AnoNumberOfRvaAndSizes is reported when NumberOfRvaAndSizes is different than 16.
    76  	AnoNumberOfRvaAndSizes = "optional header NumberOfRvaAndSizes != 16"
    77  
    78  	// AnoReservedDataDirectoryEntry is reported when the last data directory entry is not zero.
    79  	AnoReservedDataDirectoryEntry = "last data directory entry is a reserved field, must be set to zero"
    80  
    81  	// AnoCOFFSymbolsCount is reported when the number of COFF symbols is absurdly high.
    82  	AnoCOFFSymbolsCount = "COFF symbols count is absurdly high"
    83  
    84  	// AnoRelocationEntriesCount is reported when the number of relocation entries is absurdly high.
    85  	AnoRelocationEntriesCount = "relocation entries count is absurdly high"
    86  )
    87  
    88  // GetAnomalies reportes anomalies found in a PE binary.
    89  // These nomalies does prevent the Windows loader from loading the files but
    90  // is an interesting features for malware analysis.
    91  func (pe *File) GetAnomalies() error {
    92  
    93  	// ******************** Anomalies in File header ************************
    94  	// An application for Windows NT typically has the nine predefined sections
    95  	// named: .text, .bss, .rdata, .data, .rsrc, .edata, .idata, .pdata, and
    96  	// .debug. Some applications do not need all of these sections, while
    97  	// others may define still more sections to suit their specific needs.
    98  	// NumberOfSections can be up to 96 under XP.
    99  	// NumberOfSections can be up to 65535 under Vista and later.
   100  	if pe.NtHeader.FileHeader.NumberOfSections >= 10 {
   101  		pe.Anomalies = append(pe.Anomalies, AnoNumberOfSections10Plus)
   102  	}
   103  
   104  	// File header timestamp set to 0.
   105  	if pe.NtHeader.FileHeader.TimeDateStamp == 0 {
   106  		pe.Anomalies = append(pe.Anomalies, AnoPETimeStampNull)
   107  	}
   108  
   109  	// File header timestamp set to the future.
   110  	now := time.Now()
   111  	future := uint32(now.Add(24 * time.Hour).Unix())
   112  	if pe.NtHeader.FileHeader.TimeDateStamp > future {
   113  		pe.Anomalies = append(pe.Anomalies, AnoPETimeStampFuture)
   114  	}
   115  
   116  	// NumberOfSections can be null with low alignment PEs
   117  	// and in this case, the values are just checked but not really used (under XP)
   118  	if pe.NtHeader.FileHeader.NumberOfSections == 0 {
   119  		pe.Anomalies = append(pe.Anomalies, AnoNumberOfSectionsNull)
   120  	}
   121  
   122  	// SizeOfOptionalHeader is not the size of the optional header, but the delta
   123  	// between the top of the Optional header and the start of the section table.
   124  	// Thus, it can be null (the section table will overlap the Optional Header,
   125  	// or can be null when no sections are present)
   126  	if pe.NtHeader.FileHeader.SizeOfOptionalHeader == 0 {
   127  		pe.Anomalies = append(pe.Anomalies, AnoSizeOfOptionalHeaderNull)
   128  	}
   129  
   130  	// SizeOfOptionalHeader can be bigger than the file
   131  	// (the section table will be in virtual space, full of zeroes), but can't be negative.
   132  	// Do some check here.
   133  	oh32 := ImageOptionalHeader32{}
   134  	oh64 := ImageOptionalHeader64{}
   135  
   136  	// SizeOfOptionalHeader standard value is 0xE0 for PE32.
   137  	if pe.Is32 &&
   138  		pe.NtHeader.FileHeader.SizeOfOptionalHeader > uint16(binary.Size(oh32)) {
   139  		pe.Anomalies = append(pe.Anomalies, AnoUncommonSizeOfOptionalHeader32)
   140  	}
   141  
   142  	// SizeOfOptionalHeader standard value is 0xF0 for PE32+.
   143  	if pe.Is64 &&
   144  		pe.NtHeader.FileHeader.SizeOfOptionalHeader > uint16(binary.Size(oh64)) {
   145  		pe.Anomalies = append(pe.Anomalies, AnoUncommonSizeOfOptionalHeader64)
   146  	}
   147  
   148  	// ***************** Anomalies in Optional header *********************
   149  	// Under Windows 8, AddressOfEntryPoint is not allowed to be smaller than
   150  	// SizeOfHeaders, except if it's null.
   151  	switch pe.Is64 {
   152  	case true:
   153  		oh64 = pe.NtHeader.OptionalHeader.(ImageOptionalHeader64)
   154  	case false:
   155  		oh32 = pe.NtHeader.OptionalHeader.(ImageOptionalHeader32)
   156  	}
   157  
   158  	// Use oh for fields which are common for both structures.
   159  	oh := oh32
   160  	if oh.AddressOfEntryPoint != 0 && oh.AddressOfEntryPoint < oh.SizeOfHeaders {
   161  		pe.Anomalies = append(pe.Anomalies, AnoAddressOfEPLessSizeOfHeaders)
   162  	}
   163  
   164  	// AddressOfEntryPoint can be null in DLLs: in this case,
   165  	// DllMain is just not called. can be null
   166  	if oh.AddressOfEntryPoint == 0 {
   167  		pe.Anomalies = append(pe.Anomalies, AnoAddressOfEntryPointNull)
   168  	}
   169  
   170  	// ImageBase can be null, under XP.
   171  	// In this case, the binary will be relocated to 10000h
   172  	if (pe.Is64 && oh64.ImageBase == 0) ||
   173  		(pe.Is32 && oh32.ImageBase == 0) {
   174  		pe.Anomalies = append(pe.Anomalies, AnoImageBaseNull)
   175  	}
   176  
   177  	// The msdn states that SizeOfImage must be a multiple of the section
   178  	// alignment. This is not a requirement though. Adding it as anomaly.
   179  	// Todo: raise an anomaly when SectionAlignment is NULL ?
   180  	if oh.SectionAlignment != 0 && oh.SizeOfImage%oh.SectionAlignment != 0 {
   181  		pe.Anomalies = append(pe.Anomalies, AnoInvalidSizeOfImage)
   182  	}
   183  
   184  	// For DLLs, MajorSubsystemVersion is ignored until Windows 8. It can have
   185  	// any value. Under Windows 8, it needs a standard value (3.10 < 6.30).
   186  	if oh.MajorSubsystemVersion < 3 || oh.MajorSubsystemVersion > 6 {
   187  		pe.Anomalies = append(pe.Anomalies, AnoMajorSubsystemVersion)
   188  	}
   189  
   190  	// Win32VersionValue officially defined as `reserved` and should be null
   191  	// if non null, it overrides MajorVersion/MinorVersion/BuildNumber/PlatformId
   192  	// OperatingSystem Versions values located in the PEB, after loading.
   193  	if oh.Win32VersionValue != 0 {
   194  		pe.Anomalies = append(pe.Anomalies, AnonWin32VersionValue)
   195  	}
   196  
   197  	// Checksums are required for kernel-mode drivers and some system DLLs.
   198  	// Otherwise, this field can be 0.
   199  	if pe.Checksum() != oh.CheckSum && oh.CheckSum != 0 {
   200  		pe.Anomalies = append(pe.Anomalies, AnoInvalidPEChecksum)
   201  	}
   202  
   203  	// This field contains the number of IMAGE_DATA_DIRECTORY entries.
   204  	//  This field has been 16 since the earliest releases of Windows NT.
   205  	if (pe.Is64 && oh64.NumberOfRvaAndSizes == 0xA) ||
   206  		(pe.Is32 && oh32.NumberOfRvaAndSizes == 0xA) {
   207  		pe.Anomalies = append(pe.Anomalies, AnoNumberOfRvaAndSizes)
   208  	}
   209  
   210  	return nil
   211  }
   212  
   213  // addAnomaly appends the given anomaly to the list of anomalies.
   214  func (pe *File) addAnomaly(anomaly string) {
   215  	if !stringInSlice(anomaly, pe.Anomalies) {
   216  		pe.Anomalies = append(pe.Anomalies, anomaly)
   217  	}
   218  }