github.com/saferwall/pe@v1.5.2/security.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" 10 "crypto/x509" 11 "crypto/x509/pkix" 12 "encoding/asn1" 13 "encoding/binary" 14 "encoding/hex" 15 "errors" 16 "fmt" 17 "hash" 18 "io" 19 "os" 20 "os/exec" 21 "path/filepath" 22 "reflect" 23 "runtime" 24 "sort" 25 "strings" 26 "time" 27 28 "go.mozilla.org/pkcs7" 29 ) 30 31 // The options for the WIN_CERTIFICATE Revision member include 32 // (but are not limited to) the following. 33 const ( 34 // WinCertRevision1_0 represents the WIN_CERT_REVISION_1_0 Version 1, 35 // legacy version of the Win_Certificate structure. 36 // It is supported only for purposes of verifying legacy Authenticode 37 // signatures 38 WinCertRevision1_0 = 0x0100 39 40 // WinCertRevision2_0 represents the WIN_CERT_REVISION_2_0. Version 2 41 // is the current version of the Win_Certificate structure. 42 WinCertRevision2_0 = 0x0200 43 ) 44 45 // The options for the WIN_CERTIFICATE CertificateType member include 46 // (but are not limited to) the items in the following table. Note that some 47 // values are not currently supported. 48 const ( 49 // Certificate contains an X.509 Certificate (Not Supported) 50 WinCertTypeX509 = 0x0001 51 52 // Certificate contains a PKCS#7 SignedData structure. 53 WinCertTypePKCSSignedData = 0x0002 54 55 // Reserved. 56 WinCertTypeReserved1 = 0x0003 57 58 // Terminal Server Protocol Stack Certificate signing (Not Supported). 59 WinCertTypeTSStackSigned = 0x0004 60 ) 61 62 var ( 63 // ErrSecurityDataDirInvalidCertHeader is reported when the certificate 64 // header in the security directory is invalid. 65 ErrSecurityDataDirInvalid = errors.New( 66 `invalid certificate header in security directory`) 67 ) 68 69 // Certificate directory. 70 type Certificate struct { 71 Header WinCertificate `json:"header"` 72 Content pkcs7.PKCS7 `json:"-"` 73 SignatureContent AuthenticodeContent `json:"-"` 74 SignatureValid bool `json:"signature_valid"` 75 Raw []byte `json:"-"` 76 Info CertInfo `json:"info"` 77 Verified bool `json:"verified"` 78 } 79 80 // WinCertificate encapsulates a signature used in verifying executable files. 81 type WinCertificate struct { 82 // Specifies the length, in bytes, of the signature. 83 Length uint32 `json:"length"` 84 85 // Specifies the certificate revision. 86 Revision uint16 `json:"revision"` 87 88 // Specifies the type of certificate. 89 CertificateType uint16 `json:"certificate_type"` 90 } 91 92 // CertInfo wraps the important fields of the pkcs7 structure. 93 // This is what we what keep in JSON marshalling. 94 type CertInfo struct { 95 // The certificate authority (CA) that charges customers to issue 96 // certificates for them. 97 Issuer string `json:"issuer"` 98 99 // The subject of the certificate is the entity its public key is associated 100 // with (i.e. the "owner" of the certificate). 101 Subject string `json:"subject"` 102 103 // The certificate won't be valid before this timestamp. 104 NotBefore time.Time `json:"not_before"` 105 106 // The certificate won't be valid after this timestamp. 107 NotAfter time.Time `json:"not_after"` 108 109 // The serial number MUST be a positive integer assigned by the CA to each 110 // certificate. It MUST be unique for each certificate issued by a given CA 111 // (i.e., the issuer name and serial number identify a unique certificate). 112 // CAs MUST force the serialNumber to be a non-negative integer. 113 // For convenience, we convert the big int to string. 114 SerialNumber string `json:"serial_number"` 115 116 // The identifier for the cryptographic algorithm used by the CA to sign 117 // this certificate. 118 SignatureAlgorithm x509.SignatureAlgorithm `json:"signature_algorithm"` 119 120 // The Public Key Algorithm refers to the public key inside the certificate. 121 // This certificate is used together with the matching private key to prove 122 // the identity of the peer. 123 PublicKeyAlgorithm x509.PublicKeyAlgorithm `json:"public_key_algorithm"` 124 } 125 126 type RelRange struct { 127 Start uint32 128 Length uint32 129 } 130 131 type byStart []RelRange 132 133 func (s byStart) Len() int { return len(s) } 134 func (s byStart) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 135 func (s byStart) Less(i, j int) bool { 136 return s[i].Start < s[j].Start 137 } 138 139 type Range struct { 140 Start uint32 141 End uint32 142 } 143 144 func (pe *File) parseLocations() (map[string]*RelRange, error) { 145 location := make(map[string]*RelRange, 3) 146 147 fileHdrSize := uint32(binary.Size(pe.NtHeader.FileHeader)) 148 optionalHeaderOffset := pe.DOSHeader.AddressOfNewEXEHeader + 4 + fileHdrSize 149 150 var ( 151 oh32 ImageOptionalHeader32 152 oh64 ImageOptionalHeader64 153 154 optionalHeaderSize uint32 155 ) 156 157 switch pe.Is64 { 158 case true: 159 oh64 = pe.NtHeader.OptionalHeader.(ImageOptionalHeader64) 160 optionalHeaderSize = oh64.SizeOfHeaders 161 case false: 162 oh32 = pe.NtHeader.OptionalHeader.(ImageOptionalHeader32) 163 optionalHeaderSize = oh32.SizeOfHeaders 164 } 165 166 if optionalHeaderSize > pe.size-optionalHeaderOffset { 167 msgF := "the optional header exceeds the file length (%d + %d > %d)" 168 return nil, fmt.Errorf(msgF, optionalHeaderSize, optionalHeaderOffset, pe.size) 169 } 170 171 if optionalHeaderSize < 68 { 172 msgF := "the optional header size is %d < 68, which is insufficient for authenticode" 173 return nil, fmt.Errorf(msgF, optionalHeaderSize) 174 } 175 176 // The location of the checksum 177 location["checksum"] = &RelRange{optionalHeaderOffset + 64, 4} 178 179 var rvaBase, certBase, numberOfRvaAndSizes uint32 180 switch pe.Is64 { 181 case true: 182 rvaBase = optionalHeaderOffset + 108 183 certBase = optionalHeaderOffset + 144 184 numberOfRvaAndSizes = oh64.NumberOfRvaAndSizes 185 case false: 186 rvaBase = optionalHeaderOffset + 92 187 certBase = optionalHeaderOffset + 128 188 numberOfRvaAndSizes = oh32.NumberOfRvaAndSizes 189 } 190 191 if optionalHeaderOffset+optionalHeaderSize < rvaBase+4 { 192 pe.logger.Debug("The PE Optional Header size can not accommodate for the NumberOfRvaAndSizes field") 193 return location, nil 194 } 195 196 if numberOfRvaAndSizes < uint32(5) { 197 pe.logger.Debugf("The PE Optional Header does not have a Certificate Table entry in its "+ 198 "Data Directory; NumberOfRvaAndSizes = %d", numberOfRvaAndSizes) 199 return location, nil 200 } 201 202 if optionalHeaderOffset+optionalHeaderSize < certBase+8 { 203 pe.logger.Debug("The PE Optional Header size can not accommodate for a Certificate Table" + 204 "entry in its Data Directory") 205 return location, nil 206 } 207 208 // The location of the entry of the Certificate Table in the Data Directory 209 location["datadir_certtable"] = &RelRange{certBase, 8} 210 211 var address, size uint32 212 switch pe.Is64 { 213 case true: 214 dirEntry := oh64.DataDirectory[ImageDirectoryEntryCertificate] 215 address = dirEntry.VirtualAddress 216 size = dirEntry.Size 217 case false: 218 dirEntry := oh32.DataDirectory[ImageDirectoryEntryCertificate] 219 address = dirEntry.VirtualAddress 220 size = dirEntry.Size 221 } 222 223 if size == 0 { 224 pe.logger.Debug("The Certificate Table is empty") 225 return location, nil 226 } 227 228 if int64(address) < int64(optionalHeaderSize)+int64(optionalHeaderOffset) || 229 int64(address)+int64(size) > int64(pe.size) { 230 pe.logger.Debugf("The location of the Certificate Table in the binary makes no sense and "+ 231 "is either beyond the boundaries of the file, or in the middle of the PE header; "+ 232 "VirtualAddress: %x, Size: %x", address, size) 233 return location, nil 234 } 235 236 // The location of the Certificate Table 237 location["certtable"] = &RelRange{address, size} 238 return location, nil 239 } 240 241 // Authentihash generates the SHA256 pe image file hash. 242 // The relevant sections to exclude during hashing are: 243 // - The location of the checksum 244 // - The location of the entry of the Certificate Table in the Data Directory 245 // - The location of the Certificate Table. 246 func (pe *File) Authentihash() []byte { 247 results := pe.AuthentihashExt(crypto.SHA256.New()) 248 if len(results) > 0 { 249 return results[0] 250 } 251 return nil 252 } 253 254 // AuthentihashExt generates pe image file hashes using the given hashers. 255 // The relevant sections to exclude during hashing are: 256 // - The location of the checksum 257 // - The location of the entry of the Certificate Table in the Data Directory 258 // - The location of the Certificate Table. 259 func (pe *File) AuthentihashExt(hashers ...hash.Hash) [][]byte { 260 261 locationMap, err := pe.parseLocations() 262 if err != nil { 263 return nil 264 } 265 266 locationSlice := make([]RelRange, 0, len(locationMap)) 267 for k, v := range locationMap { 268 if stringInSlice(k, []string{"checksum", "datadir_certtable", "certtable"}) { 269 locationSlice = append(locationSlice, *v) 270 } 271 } 272 sort.Sort(byStart(locationSlice)) 273 274 ranges := make([]*Range, 0, len(locationSlice)) 275 start := uint32(0) 276 for _, r := range locationSlice { 277 ranges = append(ranges, &Range{Start: start, End: r.Start}) 278 start = r.Start + r.Length 279 } 280 ranges = append(ranges, &Range{Start: start, End: pe.size}) 281 282 var rd io.ReaderAt 283 if pe.f != nil { 284 rd = pe.f 285 } else { 286 rd = bytes.NewReader(pe.data) 287 } 288 289 for _, v := range ranges { 290 for _, hasher := range hashers { 291 sr := io.NewSectionReader(rd, int64(v.Start), int64(v.End)-int64(v.Start)) 292 io.Copy(hasher, sr) 293 sr.Seek(0, io.SeekStart) 294 } 295 } 296 297 var ret [][]byte 298 for _, hasher := range hashers { 299 ret = append(ret, hasher.Sum(nil)) 300 } 301 302 return ret 303 } 304 305 // The security directory contains the authenticode signature, which is a digital 306 // signature format that is used, among other purposes, to determine the origin 307 // and integrity of software binaries. Authenticode is based on the Public-Key 308 // Cryptography Standards (PKCS) #7 standard and uses X.509 v3 certificates to 309 // bind an Authenticode-signed file to the identity of a software publisher. 310 // This data are not loaded into memory as part of the image file. 311 func (pe *File) parseSecurityDirectory(rva, size uint32) error { 312 313 var pkcs *pkcs7.PKCS7 314 var certValid bool 315 certInfo := CertInfo{} 316 certHeader := WinCertificate{} 317 certSize := uint32(binary.Size(certHeader)) 318 signatureContent := AuthenticodeContent{} 319 var signatureValid bool 320 var certContent []byte 321 322 // The virtual address value from the Certificate Table entry in the 323 // Optional Header Data Directory is a file offset to the first attribute 324 // certificate entry. 325 fileOffset := rva 326 327 // PE file can be dual signed by applying multiple signatures, which is 328 // strongly recommended when using deprecated hashing algorithms such as MD5. 329 for { 330 err := pe.structUnpack(&certHeader, fileOffset, certSize) 331 if err != nil { 332 return ErrOutsideBoundary 333 } 334 335 if fileOffset+certHeader.Length > pe.size { 336 return ErrOutsideBoundary 337 } 338 339 if certHeader.Length == 0 { 340 return ErrSecurityDataDirInvalid 341 } 342 343 certContent = pe.data[fileOffset+certSize : fileOffset+certHeader.Length] 344 pkcs, err = pkcs7.Parse(certContent) 345 if err != nil { 346 pe.Certificates = Certificate{Header: certHeader, Raw: certContent} 347 pe.HasCertificate = true 348 return err 349 } 350 351 // The pkcs7.PKCS7 structure contains many fields that we are not 352 // interested to, so create another structure, similar to _CERT_INFO 353 // structure which contains only the important information. 354 serialNumber := pkcs.Signers[0].IssuerAndSerialNumber.SerialNumber 355 for _, cert := range pkcs.Certificates { 356 if !reflect.DeepEqual(cert.SerialNumber, serialNumber) { 357 continue 358 } 359 360 certInfo.SerialNumber = hex.EncodeToString(cert.SerialNumber.Bytes()) 361 certInfo.PublicKeyAlgorithm = cert.PublicKeyAlgorithm 362 certInfo.SignatureAlgorithm = cert.SignatureAlgorithm 363 364 certInfo.NotAfter = cert.NotAfter 365 certInfo.NotBefore = cert.NotBefore 366 367 // Issuer infos 368 if len(cert.Issuer.Country) > 0 { 369 certInfo.Issuer = cert.Issuer.Country[0] 370 } 371 372 if len(cert.Issuer.Province) > 0 { 373 certInfo.Issuer += ", " + cert.Issuer.Province[0] 374 } 375 376 if len(cert.Issuer.Locality) > 0 { 377 certInfo.Issuer += ", " + cert.Issuer.Locality[0] 378 } 379 380 certInfo.Issuer += ", " + cert.Issuer.CommonName 381 382 // Subject infos 383 if len(cert.Subject.Country) > 0 { 384 certInfo.Subject = cert.Subject.Country[0] 385 } 386 387 if len(cert.Subject.Province) > 0 { 388 certInfo.Subject += ", " + cert.Subject.Province[0] 389 } 390 391 if len(cert.Subject.Locality) > 0 { 392 certInfo.Subject += ", " + cert.Subject.Locality[0] 393 } 394 395 if len(cert.Subject.Organization) > 0 { 396 certInfo.Subject += ", " + cert.Subject.Organization[0] 397 } 398 399 certInfo.Subject += ", " + cert.Subject.CommonName 400 401 break 402 } 403 404 // Let's mark the file as signed, then we verify if the signature is valid. 405 pe.IsSigned = true 406 407 // Let's load the system root certs. 408 if !pe.opts.DisableCertValidation { 409 var certPool *x509.CertPool 410 if runtime.GOOS == "windows" { 411 certPool, err = loadSystemRoots() 412 } else { 413 certPool, err = x509.SystemCertPool() 414 } 415 416 // Verify the signature. This will also verify the chain of trust of the 417 // the end-entity signer cert to one of the root in the trust store. 418 if err == nil { 419 err = pkcs.VerifyWithChain(certPool) 420 if err == nil { 421 certValid = true 422 } else { 423 certValid = false 424 } 425 } 426 } 427 428 signatureContent, err = parseAuthenticodeContent(pkcs.Content) 429 if err != nil { 430 pe.logger.Errorf("could not parse authenticode content: %v", err) 431 signatureValid = false 432 } else if !pe.opts.DisableSignatureValidation { 433 authentihash := pe.AuthentihashExt(signatureContent.HashFunction.New())[0] 434 signatureValid = bytes.Equal(authentihash, signatureContent.HashResult) 435 } 436 437 // Subsequent entries are accessed by advancing that entry's dwLength 438 // bytes, rounded up to an 8-byte multiple, from the start of the 439 // current attribute certificate entry. 440 nextOffset := certHeader.Length + fileOffset 441 nextOffset = ((nextOffset + 8 - 1) / 8) * 8 442 443 // Check if we walked the entire table. 444 if nextOffset == fileOffset+size { 445 break 446 } 447 448 fileOffset = nextOffset 449 } 450 451 pe.Certificates = Certificate{Header: certHeader, Content: *pkcs, 452 Raw: certContent, Info: certInfo, Verified: certValid, 453 SignatureContent: signatureContent, SignatureValid: signatureValid} 454 pe.HasCertificate = true 455 return nil 456 } 457 458 // loadSystemsRoots manually downloads all the trusted root certificates 459 // in Windows by spawning certutil then adding root certs individually 460 // to the cert pool. Initially, when running in windows, go SystemCertPool() 461 // used to enumerate all the certificate in the Windows store using 462 // (CertEnumCertificatesInStore). Unfortunately, Windows does not ship 463 // with all of its root certificates installed. Instead, it downloads them 464 // on-demand. As a consequence, this behavior leads to a non-deterministic 465 // results. Go team then disabled the loading Windows root certs. 466 func loadSystemRoots() (*x509.CertPool, error) { 467 468 needSync := true 469 roots := x509.NewCertPool() 470 471 // Create a temporary dir in the OS temp folder 472 // if it does not exists. 473 dir := filepath.Join(os.TempDir(), "certs") 474 info, err := os.Stat(dir) 475 if os.IsNotExist(err) { 476 if err = os.Mkdir(dir, 0755); err != nil { 477 return roots, err 478 } 479 } else { 480 now := time.Now() 481 modTime := info.ModTime() 482 diff := now.Sub(modTime).Hours() 483 if diff < 24 { 484 needSync = false 485 } 486 } 487 488 // Use certutil to download all the root certs. 489 if needSync { 490 cmd := exec.Command("certutil", "-syncWithWU", dir) 491 hideWindow(cmd) 492 out, err := cmd.Output() 493 if err != nil { 494 return roots, err 495 } 496 if !strings.Contains(string(out), "command completed successfully") { 497 return roots, err 498 } 499 } 500 501 files, err := os.ReadDir(dir) 502 if err != nil { 503 return roots, err 504 } 505 506 for _, f := range files { 507 if !strings.HasSuffix(f.Name(), ".crt") { 508 continue 509 } 510 certPath := filepath.Join(dir, f.Name()) 511 certData, err := os.ReadFile(certPath) 512 if err != nil { 513 return roots, err 514 } 515 516 if crt, err := x509.ParseCertificate(certData); err == nil { 517 roots.AddCert(crt) 518 } 519 } 520 521 return roots, nil 522 } 523 524 type SpcIndirectDataContent struct { 525 Data SpcAttributeTypeAndOptionalValue 526 MessageDigest DigestInfo 527 } 528 529 type SpcAttributeTypeAndOptionalValue struct { 530 Type asn1.ObjectIdentifier 531 Value SpcPeImageData `asn1:"optional"` 532 } 533 534 type SpcPeImageData struct { 535 Flags asn1.BitString 536 File asn1.RawValue 537 } 538 539 type DigestInfo struct { 540 DigestAlgorithm pkix.AlgorithmIdentifier 541 Digest []byte 542 } 543 544 // Translation of algorithm identifier to hash algorithm, copied from pkcs7.getHashForOID 545 func parseHashAlgorithm(identifier pkix.AlgorithmIdentifier) (crypto.Hash, error) { 546 oid := identifier.Algorithm 547 switch { 548 case oid.Equal(pkcs7.OIDDigestAlgorithmSHA1), oid.Equal(pkcs7.OIDDigestAlgorithmECDSASHA1), 549 oid.Equal(pkcs7.OIDDigestAlgorithmDSA), oid.Equal(pkcs7.OIDDigestAlgorithmDSASHA1), 550 oid.Equal(pkcs7.OIDEncryptionAlgorithmRSA): 551 return crypto.SHA1, nil 552 case oid.Equal(pkcs7.OIDDigestAlgorithmSHA256), oid.Equal(pkcs7.OIDDigestAlgorithmECDSASHA256): 553 return crypto.SHA256, nil 554 case oid.Equal(pkcs7.OIDDigestAlgorithmSHA384), oid.Equal(pkcs7.OIDDigestAlgorithmECDSASHA384): 555 return crypto.SHA384, nil 556 case oid.Equal(pkcs7.OIDDigestAlgorithmSHA512), oid.Equal(pkcs7.OIDDigestAlgorithmECDSASHA512): 557 return crypto.SHA512, nil 558 } 559 return crypto.Hash(0), pkcs7.ErrUnsupportedAlgorithm 560 } 561 562 // AuthenticodeContent provides a simplified view on SpcIndirectDataContent, which specifies the ASN.1 encoded values of 563 // the authenticode signature content. 564 type AuthenticodeContent struct { 565 HashFunction crypto.Hash 566 HashResult []byte 567 } 568 569 func parseAuthenticodeContent(content []byte) (AuthenticodeContent, error) { 570 var authenticodeContent SpcIndirectDataContent 571 content, err := asn1.Unmarshal(content, &authenticodeContent.Data) 572 if err != nil { 573 return AuthenticodeContent{}, err 574 } 575 _, err = asn1.Unmarshal(content, &authenticodeContent.MessageDigest) 576 if err != nil { 577 return AuthenticodeContent{}, err 578 } 579 hashFunction, err := parseHashAlgorithm(authenticodeContent.MessageDigest.DigestAlgorithm) 580 if err != nil { 581 return AuthenticodeContent{}, err 582 } 583 return AuthenticodeContent{ 584 HashFunction: hashFunction, 585 HashResult: authenticodeContent.MessageDigest.Digest, 586 }, nil 587 }