github.com/saferwall/pe@v1.5.2/richheader.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 "crypto/md5" 10 "encoding/binary" 11 "fmt" 12 ) 13 14 const ( 15 // DansSignature ('DanS' as dword) is where the rich header struct starts. 16 DansSignature = 0x536E6144 17 18 // RichSignature ('0x68636952' as dword) is where the rich header struct ends. 19 RichSignature = "Rich" 20 21 // AnoDansSigNotFound is reported when rich header signature was found, but 22 AnoDansSigNotFound = "Rich Header found, but could not locate DanS " + 23 "signature" 24 25 // AnoPaddingDwordNotZero is reported when rich header signature leading 26 // padding DWORDs are not equal to 0. 27 AnoPaddingDwordNotZero = "Rich header found: 3 leading padding DWORDs " + 28 "not found after DanS signature" 29 ) 30 31 // CompID represents the `@comp.id` structure. 32 type CompID struct { 33 // The minor version information for the compiler used when building the product. 34 MinorCV uint16 `json:"minor_compiler_version"` 35 36 // Provides information about the identity or type of the objects used to 37 // build the PE32. 38 ProdID uint16 `json:"product_id"` 39 40 // Indicates how often the object identified by the former two fields is 41 // referenced by this PE32 file. 42 Count uint32 `json:"count"` 43 44 // The raw @comp.id structure (unmasked). 45 Unmasked uint32 `json:"unmasked"` 46 } 47 48 // RichHeader is a structure that is written right after the MZ DOS header. 49 // It consists of pairs of 4-byte integers. And it is also 50 // encrypted using a simple XOR operation using the checksum as the key. 51 // The data between the magic values encodes the ‘bill of materials’ that were 52 // collected by the linker to produce the binary. 53 type RichHeader struct { 54 XORKey uint32 `json:"xor_key"` 55 CompIDs []CompID `json:"comp_ids"` 56 DansOffset int `json:"dans_offset"` 57 Raw []byte `json:"raw"` 58 } 59 60 // ParseRichHeader parses the rich header struct. 61 func (pe *File) ParseRichHeader() error { 62 63 rh := RichHeader{} 64 ntHeaderOffset := pe.DOSHeader.AddressOfNewEXEHeader 65 richSigOffset := bytes.Index(pe.data[:ntHeaderOffset], []byte(RichSignature)) 66 67 // For example, .NET executable files do not use the MSVC linker and these 68 // executables do not contain a detectable Rich Header. 69 if richSigOffset < 0 { 70 return nil 71 } 72 73 // The DWORD following the "Rich" sequence is the XOR key stored by and 74 // calculated by the linker. It is actually a checksum of the DOS header with 75 // the e_lfanew zeroed out, and additionally includes the values of the 76 // unencrypted "Rich" array. Using a checksum with encryption will not only 77 // obfuscate the values, but it also serves as a rudimentary digital 78 // signature. If the checksum is calculated from scratch once the values 79 // have been decrypted, but doesn't match the stored key, it can be assumed 80 // the structure had been tampered with. For those that go the extra step to 81 // recalculate the checksum/key, this simple protection mechanism can be bypassed. 82 rh.XORKey = binary.LittleEndian.Uint32(pe.data[richSigOffset+4:]) 83 84 // To decrypt the array, start with the DWORD just prior to the `Rich` sequence 85 // and XOR it with the key. Continue the loop backwards, 4 bytes at a time, 86 // until the sequence `DanS` is decrypted. 87 var decRichHeader []uint32 88 dansSigOffset := -1 89 estimatedBeginDans := richSigOffset - 4 - binary.Size(ImageDOSHeader{}) 90 for it := 0; it < estimatedBeginDans; it += 4 { 91 buff := binary.LittleEndian.Uint32(pe.data[richSigOffset-4-it:]) 92 res := buff ^ rh.XORKey 93 if res == DansSignature { 94 dansSigOffset = richSigOffset - it - 4 95 break 96 } 97 98 decRichHeader = append(decRichHeader, res) 99 } 100 101 // Probe we successfuly found the `DanS` magic. 102 if dansSigOffset == -1 { 103 pe.Anomalies = append(pe.Anomalies, AnoDansSigNotFound) 104 return nil 105 } 106 107 // Anomaly check: dansSigOffset is usually found in offset 0x80. 108 if dansSigOffset != 0x80 { 109 pe.Anomalies = append(pe.Anomalies, AnoDanSMagicOffset) 110 } 111 112 rh.DansOffset = dansSigOffset 113 rh.Raw = pe.data[dansSigOffset : richSigOffset+8] 114 115 // Reverse the decrypted rich header 116 for i, j := 0, len(decRichHeader)-1; i < j; i, j = i+1, j-1 { 117 decRichHeader[i], decRichHeader[j] = decRichHeader[j], decRichHeader[i] 118 } 119 120 // After the `DanS` signature, there are some zero-padded In practice, 121 // Microsoft seems to have wanted the entries to begin on a 16-byte 122 // (paragraph) boundary, so the 3 leading padding DWORDs can be safely 123 // skipped as not belonging to the data. 124 if decRichHeader[0] != 0 || decRichHeader[1] != 0 || decRichHeader[2] != 0 { 125 pe.Anomalies = append(pe.Anomalies, AnoPaddingDwordNotZero) 126 } 127 128 // The array stores entries that are 8-bytes each, broken into 3 members. 129 // Each entry represents either a tool that was employed as part of building 130 // the executable or a statistic. 131 // The @compid struct should be multiple of 8 (bytes), some malformed pe 132 // files have incorrect number of entries. 133 var lenCompIDs int 134 if (len(decRichHeader)-3)%2 != 0 { 135 lenCompIDs = len(decRichHeader) - 1 136 } else { 137 lenCompIDs = len(decRichHeader) 138 } 139 140 for i := 3; i < lenCompIDs; i += 2 { 141 cid := CompID{} 142 compid := make([]byte, binary.Size(cid)) 143 binary.LittleEndian.PutUint32(compid, decRichHeader[i]) 144 binary.LittleEndian.PutUint32(compid[4:], decRichHeader[i+1]) 145 buf := bytes.NewReader(compid) 146 err := binary.Read(buf, binary.LittleEndian, &cid) 147 if err != nil { 148 return err 149 } 150 cid.Unmasked = binary.LittleEndian.Uint32(compid) 151 rh.CompIDs = append(rh.CompIDs, cid) 152 } 153 154 pe.RichHeader = rh 155 pe.HasRichHdr = true 156 157 checksum := pe.RichHeaderChecksum() 158 if checksum != rh.XORKey { 159 pe.Anomalies = append(pe.Anomalies, "Invalid rich header checksum") 160 } 161 162 return nil 163 } 164 165 // RichHeaderChecksum calculate the Rich Header checksum. 166 func (pe *File) RichHeaderChecksum() uint32 { 167 168 checksum := uint32(pe.RichHeader.DansOffset) 169 170 // First, calculate the sum of the DOS header bytes each rotated left the 171 // number of times their position relative to the start of the DOS header e.g. 172 // second byte is rotated left 2x using rol operation. 173 for i := 0; i < pe.RichHeader.DansOffset; i++ { 174 // skip over dos e_lfanew field at offset 0x3C 175 if i >= 0x3C && i < 0x40 { 176 continue 177 } 178 b := uint32(pe.data[i]) 179 checksum += ((b << (i % 32)) | (b>>(32-(i%32)))&0xff) 180 checksum &= 0xFFFFFFFF 181 } 182 183 // Next, take summation of each Rich header entry by combining its ProductId 184 // and BuildNumber into a single 32 bit number and rotating by its count. 185 for _, compid := range pe.RichHeader.CompIDs { 186 checksum += (compid.Unmasked<<(compid.Count%32) | 187 compid.Unmasked>>(32-(compid.Count%32))) 188 checksum &= 0xFFFFFFFF 189 } 190 191 return checksum 192 } 193 194 // RichHeaderHash calculate the Rich Header hash. 195 func (pe *File) RichHeaderHash() string { 196 if !pe.HasRichHdr { 197 return "" 198 } 199 200 richIndex := bytes.Index(pe.RichHeader.Raw, []byte(RichSignature)) 201 if richIndex == -1 { 202 return "" 203 } 204 205 key := make([]byte, 4) 206 binary.LittleEndian.PutUint32(key, pe.RichHeader.XORKey) 207 208 rawData := pe.RichHeader.Raw[:richIndex] 209 clearData := make([]byte, len(rawData)) 210 for idx, val := range rawData { 211 clearData[idx] = val ^ key[idx%len(key)] 212 } 213 return fmt.Sprintf("%x", md5.Sum(clearData)) 214 } 215 216 // ProdIDtoStr maps product ids to MS internal names. 217 // list from: https://github.com/kirschju/richheader 218 func ProdIDtoStr(prodID uint16) string { 219 220 prodIDtoStrMap := map[uint16]string{ 221 0x0000: "Unknown", 222 0x0001: "Import0", 223 0x0002: "Linker510", 224 0x0003: "Cvtomf510", 225 0x0004: "Linker600", 226 0x0005: "Cvtomf600", 227 0x0006: "Cvtres500", 228 0x0007: "Utc11_Basic", 229 0x0008: "Utc11_C", 230 0x0009: "Utc12_Basic", 231 0x000a: "Utc12_C", 232 0x000b: "Utc12_CPP", 233 0x000c: "AliasObj60", 234 0x000d: "VisualBasic60", 235 0x000e: "Masm613", 236 0x000f: "Masm710", 237 0x0010: "Linker511", 238 0x0011: "Cvtomf511", 239 0x0012: "Masm614", 240 0x0013: "Linker512", 241 0x0014: "Cvtomf512", 242 0x0015: "Utc12_C_Std", 243 0x0016: "Utc12_CPP_Std", 244 0x0017: "Utc12_C_Book", 245 0x0018: "Utc12_CPP_Book", 246 0x0019: "Implib700", 247 0x001a: "Cvtomf700", 248 0x001b: "Utc13_Basic", 249 0x001c: "Utc13_C", 250 0x001d: "Utc13_CPP", 251 0x001e: "Linker610", 252 0x001f: "Cvtomf610", 253 0x0020: "Linker601", 254 0x0021: "Cvtomf601", 255 0x0022: "Utc12_1_Basic", 256 0x0023: "Utc12_1_C", 257 0x0024: "Utc12_1_CPP", 258 0x0025: "Linker620", 259 0x0026: "Cvtomf620", 260 0x0027: "AliasObj70", 261 0x0028: "Linker621", 262 0x0029: "Cvtomf621", 263 0x002a: "Masm615", 264 0x002b: "Utc13_LTCG_C", 265 0x002c: "Utc13_LTCG_CPP", 266 0x002d: "Masm620", 267 0x002e: "ILAsm100", 268 0x002f: "Utc12_2_Basic", 269 0x0030: "Utc12_2_C", 270 0x0031: "Utc12_2_CPP", 271 0x0032: "Utc12_2_C_Std", 272 0x0033: "Utc12_2_CPP_Std", 273 0x0034: "Utc12_2_C_Book", 274 0x0035: "Utc12_2_CPP_Book", 275 0x0036: "Implib622", 276 0x0037: "Cvtomf622", 277 0x0038: "Cvtres501", 278 0x0039: "Utc13_C_Std", 279 0x003a: "Utc13_CPP_Std", 280 0x003b: "Cvtpgd1300", 281 0x003c: "Linker622", 282 0x003d: "Linker700", 283 0x003e: "Export622", 284 0x003f: "Export700", 285 0x0040: "Masm700", 286 0x0041: "Utc13_POGO_I_C", 287 0x0042: "Utc13_POGO_I_CPP", 288 0x0043: "Utc13_POGO_O_C", 289 0x0044: "Utc13_POGO_O_CPP", 290 0x0045: "Cvtres700", 291 0x0046: "Cvtres710p", 292 0x0047: "Linker710p", 293 0x0048: "Cvtomf710p", 294 0x0049: "Export710p", 295 0x004a: "Implib710p", 296 0x004b: "Masm710p", 297 0x004c: "Utc1310p_C", 298 0x004d: "Utc1310p_CPP", 299 0x004e: "Utc1310p_C_Std", 300 0x004f: "Utc1310p_CPP_Std", 301 0x0050: "Utc1310p_LTCG_C", 302 0x0051: "Utc1310p_LTCG_CPP", 303 0x0052: "Utc1310p_POGO_I_C", 304 0x0053: "Utc1310p_POGO_I_CPP", 305 0x0054: "Utc1310p_POGO_O_C", 306 0x0055: "Utc1310p_POGO_O_CPP", 307 0x0056: "Linker624", 308 0x0057: "Cvtomf624", 309 0x0058: "Export624", 310 0x0059: "Implib624", 311 0x005a: "Linker710", 312 0x005b: "Cvtomf710", 313 0x005c: "Export710", 314 0x005d: "Implib710", 315 0x005e: "Cvtres710", 316 0x005f: "Utc1310_C", 317 0x0060: "Utc1310_CPP", 318 0x0061: "Utc1310_C_Std", 319 0x0062: "Utc1310_CPP_Std", 320 0x0063: "Utc1310_LTCG_C", 321 0x0064: "Utc1310_LTCG_CPP", 322 0x0065: "Utc1310_POGO_I_C", 323 0x0066: "Utc1310_POGO_I_CPP", 324 0x0067: "Utc1310_POGO_O_C", 325 0x0068: "Utc1310_POGO_O_CPP", 326 0x0069: "AliasObj710", 327 0x006a: "AliasObj710p", 328 0x006b: "Cvtpgd1310", 329 0x006c: "Cvtpgd1310p", 330 0x006d: "Utc1400_C", 331 0x006e: "Utc1400_CPP", 332 0x006f: "Utc1400_C_Std", 333 0x0070: "Utc1400_CPP_Std", 334 0x0071: "Utc1400_LTCG_C", 335 0x0072: "Utc1400_LTCG_CPP", 336 0x0073: "Utc1400_POGO_I_C", 337 0x0074: "Utc1400_POGO_I_CPP", 338 0x0075: "Utc1400_POGO_O_C", 339 0x0076: "Utc1400_POGO_O_CPP", 340 0x0077: "Cvtpgd1400", 341 0x0078: "Linker800", 342 0x0079: "Cvtomf800", 343 0x007a: "Export800", 344 0x007b: "Implib800", 345 0x007c: "Cvtres800", 346 0x007d: "Masm800", 347 0x007e: "AliasObj800", 348 0x007f: "PhoenixPrerelease", 349 0x0080: "Utc1400_CVTCIL_C", 350 0x0081: "Utc1400_CVTCIL_CPP", 351 0x0082: "Utc1400_LTCG_MSIL", 352 0x0083: "Utc1500_C", 353 0x0084: "Utc1500_CPP", 354 0x0085: "Utc1500_C_Std", 355 0x0086: "Utc1500_CPP_Std", 356 0x0087: "Utc1500_CVTCIL_C", 357 0x0088: "Utc1500_CVTCIL_CPP", 358 0x0089: "Utc1500_LTCG_C", 359 0x008a: "Utc1500_LTCG_CPP", 360 0x008b: "Utc1500_LTCG_MSIL", 361 0x008c: "Utc1500_POGO_I_C", 362 0x008d: "Utc1500_POGO_I_CPP", 363 0x008e: "Utc1500_POGO_O_C", 364 0x008f: "Utc1500_POGO_O_CPP", 365 0x0090: "Cvtpgd1500", 366 0x0091: "Linker900", 367 0x0092: "Export900", 368 0x0093: "Implib900", 369 0x0094: "Cvtres900", 370 0x0095: "Masm900", 371 0x0096: "AliasObj900", 372 0x0097: "Resource", 373 0x0098: "AliasObj1000", 374 0x0099: "Cvtpgd1600", 375 0x009a: "Cvtres1000", 376 0x009b: "Export1000", 377 0x009c: "Implib1000", 378 0x009d: "Linker1000", 379 0x009e: "Masm1000", 380 0x009f: "Phx1600_C", 381 0x00a0: "Phx1600_CPP", 382 0x00a1: "Phx1600_CVTCIL_C", 383 0x00a2: "Phx1600_CVTCIL_CPP", 384 0x00a3: "Phx1600_LTCG_C", 385 0x00a4: "Phx1600_LTCG_CPP", 386 0x00a5: "Phx1600_LTCG_MSIL", 387 0x00a6: "Phx1600_POGO_I_C", 388 0x00a7: "Phx1600_POGO_I_CPP", 389 0x00a8: "Phx1600_POGO_O_C", 390 0x00a9: "Phx1600_POGO_O_CPP", 391 0x00aa: "Utc1600_C", 392 0x00ab: "Utc1600_CPP", 393 0x00ac: "Utc1600_CVTCIL_C", 394 0x00ad: "Utc1600_CVTCIL_CPP", 395 0x00ae: "Utc1600_LTCG_C", 396 0x00af: "Utc1600_LTCG_CPP", 397 0x00b0: "Utc1600_LTCG_MSIL", 398 0x00b1: "Utc1600_POGO_I_C", 399 0x00b2: "Utc1600_POGO_I_CPP", 400 0x00b3: "Utc1600_POGO_O_C", 401 0x00b4: "Utc1600_POGO_O_CPP", 402 0x00b5: "AliasObj1010", 403 0x00b6: "Cvtpgd1610", 404 0x00b7: "Cvtres1010", 405 0x00b8: "Export1010", 406 0x00b9: "Implib1010", 407 0x00ba: "Linker1010", 408 0x00bb: "Masm1010", 409 0x00bc: "Utc1610_C", 410 0x00bd: "Utc1610_CPP", 411 0x00be: "Utc1610_CVTCIL_C", 412 0x00bf: "Utc1610_CVTCIL_CPP", 413 0x00c0: "Utc1610_LTCG_C", 414 0x00c1: "Utc1610_LTCG_CPP", 415 0x00c2: "Utc1610_LTCG_MSIL", 416 0x00c3: "Utc1610_POGO_I_C", 417 0x00c4: "Utc1610_POGO_I_CPP", 418 0x00c5: "Utc1610_POGO_O_C", 419 0x00c6: "Utc1610_POGO_O_CPP", 420 0x00c7: "AliasObj1100", 421 0x00c8: "Cvtpgd1700", 422 0x00c9: "Cvtres1100", 423 0x00ca: "Export1100", 424 0x00cb: "Implib1100", 425 0x00cc: "Linker1100", 426 0x00cd: "Masm1100", 427 0x00ce: "Utc1700_C", 428 0x00cf: "Utc1700_CPP", 429 0x00d0: "Utc1700_CVTCIL_C", 430 0x00d1: "Utc1700_CVTCIL_CPP", 431 0x00d2: "Utc1700_LTCG_C", 432 0x00d3: "Utc1700_LTCG_CPP", 433 0x00d4: "Utc1700_LTCG_MSIL", 434 0x00d5: "Utc1700_POGO_I_C", 435 0x00d6: "Utc1700_POGO_I_CPP", 436 0x00d7: "Utc1700_POGO_O_C", 437 0x00d8: "Utc1700_POGO_O_CPP", 438 0x00d9: "AliasObj1200", 439 0x00da: "Cvtpgd1800", 440 0x00db: "Cvtres1200", 441 0x00dc: "Export1200", 442 0x00dd: "Implib1200", 443 0x00de: "Linker1200", 444 0x00df: "Masm1200", 445 0x00e0: "Utc1800_C", 446 0x00e1: "Utc1800_CPP", 447 0x00e2: "Utc1800_CVTCIL_C", 448 0x00e3: "Utc1800_CVTCIL_CPP", 449 0x00e4: "Utc1800_LTCG_C", 450 0x00e5: "Utc1800_LTCG_CPP", 451 0x00e6: "Utc1800_LTCG_MSIL", 452 0x00e7: "Utc1800_POGO_I_C", 453 0x00e8: "Utc1800_POGO_I_CPP", 454 0x00e9: "Utc1800_POGO_O_C", 455 0x00ea: "Utc1800_POGO_O_CPP", 456 0x00eb: "AliasObj1210", 457 0x00ec: "Cvtpgd1810", 458 0x00ed: "Cvtres1210", 459 0x00ee: "Export1210", 460 0x00ef: "Implib1210", 461 0x00f0: "Linker1210", 462 0x00f1: "Masm1210", 463 0x00f2: "Utc1810_C", 464 0x00f3: "Utc1810_CPP", 465 0x00f4: "Utc1810_CVTCIL_C", 466 0x00f5: "Utc1810_CVTCIL_CPP", 467 0x00f6: "Utc1810_LTCG_C", 468 0x00f7: "Utc1810_LTCG_CPP", 469 0x00f8: "Utc1810_LTCG_MSIL", 470 0x00f9: "Utc1810_POGO_I_C", 471 0x00fa: "Utc1810_POGO_I_CPP", 472 0x00fb: "Utc1810_POGO_O_C", 473 0x00fc: "Utc1810_POGO_O_CPP", 474 0x00fd: "AliasObj1400", 475 0x00fe: "Cvtpgd1900", 476 0x00ff: "Cvtres1400", 477 0x0100: "Export1400", 478 0x0101: "Implib1400", 479 0x0102: "Linker1400", 480 0x0103: "Masm1400", 481 0x0104: "Utc1900_C", 482 0x0105: "Utc1900_CPP", 483 0x0106: "Utc1900_CVTCIL_C", 484 0x0107: "Utc1900_CVTCIL_CPP", 485 0x0108: "Utc1900_LTCG_C", 486 0x0109: "Utc1900_LTCG_CPP", 487 0x010a: "Utc1900_LTCG_MSIL", 488 0x010b: "Utc1900_POGO_I_C", 489 0x010c: "Utc1900_POGO_I_CPP", 490 0x010d: "Utc1900_POGO_O_C", 491 0x010e: "Utc1900_POGO_O_CPP", 492 } 493 494 if val, ok := prodIDtoStrMap[prodID]; ok { 495 return val 496 } 497 498 return "?" 499 } 500 501 // ProdIDtoVSversion retrieves the Visual Studio version from product id. 502 // list from: https://github.com/kirschju/richheader 503 func ProdIDtoVSversion(prodID uint16) string { 504 if prodID > 0x010e { 505 return "" 506 } else if prodID >= 0x00fd && prodID < 0x010e+1 { 507 return "Visual Studio 2015 14.00" 508 } else if prodID >= 0x00eb && prodID < 0x00fd { 509 return "Visual Studio 2013 12.10" 510 } else if prodID >= 0x00d9 && prodID < 0x00eb { 511 return "Visual Studio 2013 12.00" 512 } else if prodID >= 0x00c7 && prodID < 0x00d9 { 513 return "Visual Studio 2012 11.00" 514 } else if prodID >= 0x00b5 && prodID < 0x00c7 { 515 return "Visual Studio 2010 10.10" 516 } else if prodID >= 0x0098 && prodID < 0x00b5 { 517 return "Visual Studio 2010 10.00" 518 } else if prodID >= 0x0083 && prodID < 0x0098 { 519 return "Visual Studio 2008 09.00" 520 } else if prodID >= 0x006d && prodID < 0x0083 { 521 return "Visual Studio 2005 08.00" 522 } else if prodID >= 0x005a && prodID < 0x006d { 523 return "Visual Studio 2003 07.10" 524 } else if prodID == 1 { 525 return "Visual Studio" 526 } else { 527 return "<unknown>" 528 } 529 }