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  }