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 }