796 lines
22 KiB
Go
796 lines
22 KiB
Go
package binsnoop
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
// ---- AES descriptions ----
|
|
|
|
const (
|
|
DescriptionAESSBox = "AES S-Box"
|
|
DescriptionAESFT = "AES Forward Table"
|
|
DescriptionAESRT = "AES Reverse Table"
|
|
DescriptionAESRCON = "AES RCON"
|
|
DescriptionAESAcc = "AES Acceleration Table"
|
|
)
|
|
|
|
// aesSBoxMagic returns magic bytes for AES S-Box (forward and reverse).
|
|
func aesSBoxMagic() [][]byte {
|
|
return [][]byte{
|
|
{0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5}, // Forward S-Box
|
|
{0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38}, // Reverse S-Box
|
|
}
|
|
}
|
|
|
|
// aesSBoxParser validates AES S-Box signatures.
|
|
func aesSBoxParser(data []byte, offset int) (*SignatureResult, error) {
|
|
return &SignatureResult{
|
|
Offset: offset,
|
|
Description: DescriptionAESSBox,
|
|
Confidence: ConfidenceLow,
|
|
}, nil
|
|
}
|
|
|
|
// aesForwardTableMagic returns magic bytes for AES Forward Table.
|
|
func aesForwardTableMagic() [][]byte {
|
|
return [][]byte{
|
|
{0xC6, 0x63, 0x63, 0xA5, 0xF8, 0x7C, 0x7C, 0x84, 0xEE, 0x77, 0x77, 0x99, 0xF6, 0x7B, 0x7B, 0x8D},
|
|
}
|
|
}
|
|
|
|
// aesForwardTableParser validates AES Forward Table signatures.
|
|
func aesForwardTableParser(data []byte, offset int) (*SignatureResult, error) {
|
|
return &SignatureResult{
|
|
Offset: offset,
|
|
Description: DescriptionAESFT,
|
|
Confidence: ConfidenceLow,
|
|
}, nil
|
|
}
|
|
|
|
// aesReverseTableMagic returns magic bytes for AES Reverse Table.
|
|
func aesReverseTableMagic() [][]byte {
|
|
return [][]byte{
|
|
{0x51, 0xF4, 0xA7, 0x50, 0x7E, 0x41, 0x65, 0x53, 0x1A, 0x17, 0xA4, 0xC3, 0x3A, 0x27, 0x5E, 0x96},
|
|
}
|
|
}
|
|
|
|
// aesReverseTableParser validates AES Reverse Table signatures.
|
|
func aesReverseTableParser(data []byte, offset int) (*SignatureResult, error) {
|
|
return &SignatureResult{
|
|
Offset: offset,
|
|
Description: DescriptionAESRT,
|
|
Confidence: ConfidenceLow,
|
|
}, nil
|
|
}
|
|
|
|
// aesRconMagic returns magic bytes for AES RCON.
|
|
func aesRconMagic() [][]byte {
|
|
return [][]byte{
|
|
{0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1B, 0x36},
|
|
{0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
|
|
0x10, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
|
|
0x1B, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00},
|
|
}
|
|
}
|
|
|
|
// aesRconParser validates AES RCON signatures.
|
|
func aesRconParser(data []byte, offset int) (*SignatureResult, error) {
|
|
return &SignatureResult{
|
|
Offset: offset,
|
|
Description: DescriptionAESRCON,
|
|
Confidence: ConfidenceLow,
|
|
}, nil
|
|
}
|
|
|
|
// aesAccelerationTableMagic returns magic bytes for AES Acceleration Table.
|
|
func aesAccelerationTableMagic() [][]byte {
|
|
return [][]byte{
|
|
// combined sbox x2
|
|
{0xA5, 0x84, 0x99, 0x8D, 0x0D, 0xBD, 0xB1, 0x54, 0x50, 0x03, 0xA9, 0x7D, 0x19, 0x62, 0xE6, 0x9A},
|
|
// combined sbox x3
|
|
{0xC6, 0xF8, 0xEE, 0xF6, 0xFF, 0xD6, 0xDE, 0x91, 0x60, 0x02, 0xCE, 0x56, 0xE7, 0xB5, 0x4D, 0xEC},
|
|
// Galois mult 2
|
|
{0x00, 0x02, 0x04, 0x06, 0x08, 0x0a, 0x0c, 0x0e, 0x10, 0x12, 0x14, 0x16, 0x18, 0x1a, 0x1c, 0x1e,
|
|
0x20, 0x22, 0x24, 0x26, 0x28, 0x2a, 0x2c, 0x2e},
|
|
// Galois mult 3
|
|
{0x00, 0x03, 0x06, 0x05, 0x0c, 0x0f, 0x0a, 0x09, 0x18, 0x1b, 0x1e, 0x1d, 0x14, 0x17, 0x12, 0x11,
|
|
0x30, 0x33, 0x36, 0x35, 0x3c, 0x3f, 0x3a, 0x39},
|
|
// Galois mult 9
|
|
{0x00, 0x09, 0x12, 0x1b, 0x24, 0x2d, 0x36, 0x3f, 0x48, 0x41, 0x5a, 0x53, 0x6c, 0x65, 0x7e, 0x77,
|
|
0x90, 0x99, 0x82, 0x8b, 0xb4, 0xbd, 0xa6, 0xaf},
|
|
// Galois mult 11
|
|
{0x00, 0x0b, 0x16, 0x1d, 0x2c, 0x27, 0x3a, 0x31, 0x58, 0x53, 0x4e, 0x45, 0x74, 0x7f, 0x62, 0x69,
|
|
0xb0, 0xbb, 0xa6, 0xad, 0x9c, 0x97, 0x8a, 0x81},
|
|
// Galois mult 13
|
|
{0x00, 0x0d, 0x1a, 0x17, 0x34, 0x39, 0x2e, 0x23, 0x68, 0x65, 0x72, 0x7f, 0x5c, 0x51, 0x46, 0x4b,
|
|
0xd0, 0xdd, 0xca, 0xc7, 0xe4, 0xe9, 0xfe, 0xf3},
|
|
// Galois mult 14
|
|
{0x00, 0x0e, 0x1c, 0x12, 0x38, 0x36, 0x24, 0x2a, 0x70, 0x7e, 0x6c, 0x62, 0x48, 0x46, 0x54, 0x5a,
|
|
0xe0, 0xee, 0xfc, 0xf2, 0xd8, 0xd6, 0xc4, 0xca},
|
|
}
|
|
}
|
|
|
|
// aesAccelerationTableParser validates AES Acceleration Table signatures.
|
|
func aesAccelerationTableParser(data []byte, offset int) (*SignatureResult, error) {
|
|
return &SignatureResult{
|
|
Offset: offset,
|
|
Description: DescriptionAESAcc,
|
|
Confidence: ConfidenceLow,
|
|
}, nil
|
|
}
|
|
|
|
// ---- RSA ----
|
|
|
|
const DescriptionRSA = "RSA encrypted session key"
|
|
|
|
type rsaKeyDefinition struct {
|
|
magic []byte
|
|
keySize int
|
|
usageOffset int
|
|
keyIDOffset int
|
|
validChecks map[int][][]byte
|
|
terminatorOffset int
|
|
}
|
|
|
|
func rsaKeyDefinitions() []rsaKeyDefinition {
|
|
return []rsaKeyDefinition{
|
|
// 1024b RSA key
|
|
{
|
|
magic: []byte{0x84, 0x8C, 0x03},
|
|
keySize: 1024,
|
|
keyIDOffset: 3,
|
|
terminatorOffset: 142,
|
|
usageOffset: 11,
|
|
validChecks: map[int][][]byte{
|
|
12: {
|
|
{0x04, 0x00}, {0x03, 0xff}, {0x03, 0xfe}, {0x03, 0xfd},
|
|
{0x03, 0xfc}, {0x03, 0xfb}, {0x03, 0xfa}, {0x03, 0xf9},
|
|
},
|
|
},
|
|
},
|
|
// 2048b RSA key
|
|
{
|
|
magic: []byte{0x85, 0x01, 0x0c, 0x03},
|
|
keySize: 2048,
|
|
keyIDOffset: 4,
|
|
terminatorOffset: 271,
|
|
usageOffset: 12,
|
|
validChecks: map[int][][]byte{
|
|
13: {
|
|
{0x08, 0x00}, {0x07, 0xff}, {0x07, 0xfe}, {0x07, 0xfd},
|
|
{0x07, 0xfc}, {0x07, 0xfb}, {0x07, 0xfa}, {0x07, 0xf9},
|
|
},
|
|
},
|
|
},
|
|
// 3072b RSA key
|
|
{
|
|
magic: []byte{0x85, 0x01, 0x8c, 0x03},
|
|
keySize: 3072,
|
|
keyIDOffset: 4,
|
|
terminatorOffset: 399,
|
|
usageOffset: 12,
|
|
validChecks: map[int][][]byte{
|
|
13: {
|
|
{0x0c, 0x00}, {0x0b, 0xff}, {0x0b, 0xfe}, {0x0b, 0xfd},
|
|
{0x0b, 0xfc}, {0x0b, 0xfb}, {0x0b, 0xfa}, {0x0b, 0xf9},
|
|
},
|
|
},
|
|
},
|
|
// 4096b RSA key
|
|
{
|
|
magic: []byte{0x85, 0x02, 0x0c, 0x03},
|
|
keySize: 4096,
|
|
keyIDOffset: 4,
|
|
terminatorOffset: 527,
|
|
usageOffset: 12,
|
|
validChecks: map[int][][]byte{
|
|
13: {
|
|
{0x10, 0x00}, {0x0f, 0xff}, {0x0f, 0xfe}, {0x0f, 0xfd},
|
|
{0x0f, 0xfc}, {0x0f, 0xfb}, {0x0f, 0xfa}, {0x0f, 0xf9},
|
|
},
|
|
},
|
|
},
|
|
// 8192b RSA key
|
|
{
|
|
magic: []byte{0x85, 0x04, 0x0c, 0x03},
|
|
keySize: 8192,
|
|
keyIDOffset: 4,
|
|
terminatorOffset: 1039,
|
|
usageOffset: 12,
|
|
validChecks: map[int][][]byte{
|
|
13: {
|
|
{0x20, 0x00}, {0x1f, 0xff}, {0x1f, 0xfe}, {0x1f, 0xfd},
|
|
{0x1f, 0xfc}, {0x1f, 0xfb}, {0x1f, 0xfa}, {0x1f, 0xf9},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
// rsaMagic returns magic bytes for all known RSA key sizes.
|
|
func rsaMagic() [][]byte {
|
|
var magics [][]byte
|
|
for _, def := range rsaKeyDefinitions() {
|
|
m := make([]byte, len(def.magic))
|
|
copy(m, def.magic)
|
|
magics = append(magics, m)
|
|
}
|
|
return magics
|
|
}
|
|
|
|
type rsaKeyInfo struct {
|
|
bits int
|
|
canSign bool
|
|
canEncrypt bool
|
|
keyID int
|
|
dataSize int
|
|
}
|
|
|
|
func rsaKeyParse(rawData []byte, def *rsaKeyDefinition) (*rsaKeyInfo, error) {
|
|
const terminatorByte byte = 0xD2
|
|
const encryptOnly byte = 2
|
|
const signAndEncrypt byte = 1
|
|
const validBytesSize = 2
|
|
|
|
keyIDFields := []StructField{{"id", "u64"}}
|
|
|
|
keyDataLen := def.terminatorOffset + 1
|
|
if len(rawData) < keyDataLen {
|
|
return nil, fmt.Errorf("insufficient RSA key data")
|
|
}
|
|
keyData := rawData[:keyDataLen]
|
|
|
|
if keyData[def.terminatorOffset] != terminatorByte {
|
|
return nil, fmt.Errorf("invalid RSA terminator byte")
|
|
}
|
|
|
|
if def.keyIDOffset+8 > len(keyData) {
|
|
return nil, fmt.Errorf("key ID offset out of bounds")
|
|
}
|
|
keyIDParsed, err := Parse(keyData[def.keyIDOffset:], keyIDFields, "big")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to parse RSA key ID")
|
|
}
|
|
|
|
info := &rsaKeyInfo{keyID: keyIDParsed["id"]}
|
|
info.canEncrypt = keyData[def.usageOffset] == encryptOnly
|
|
info.canSign = keyData[def.usageOffset] == signAndEncrypt
|
|
if info.canSign {
|
|
info.canEncrypt = true
|
|
}
|
|
|
|
if !info.canSign && !info.canEncrypt {
|
|
return nil, fmt.Errorf("RSA key cannot sign or encrypt")
|
|
}
|
|
|
|
for checkOffset, expectedList := range def.validChecks {
|
|
if checkOffset+validBytesSize > len(keyData) {
|
|
continue
|
|
}
|
|
actual := keyData[checkOffset : checkOffset+validBytesSize]
|
|
for _, expected := range expectedList {
|
|
if len(expected) == validBytesSize && actual[0] == expected[0] && actual[1] == expected[1] {
|
|
info.bits = def.keySize
|
|
info.dataSize = keyDataLen
|
|
return info, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil, fmt.Errorf("RSA key validation bytes mismatch")
|
|
}
|
|
|
|
// rsaParser validates RSA encrypted session key signatures.
|
|
func rsaParser(data []byte, offset int) (*SignatureResult, error) {
|
|
result := &SignatureResult{
|
|
Offset: offset,
|
|
Description: DescriptionRSA,
|
|
Confidence: ConfidenceMedium,
|
|
}
|
|
|
|
for _, def := range rsaKeyDefinitions() {
|
|
magicEnd := offset + len(def.magic)
|
|
if magicEnd > len(data) {
|
|
continue
|
|
}
|
|
match := true
|
|
for i, b := range def.magic {
|
|
if data[offset+i] != b {
|
|
match = false
|
|
break
|
|
}
|
|
}
|
|
if !match {
|
|
continue
|
|
}
|
|
|
|
info, err := rsaKeyParse(data[offset:], &def)
|
|
if err != nil {
|
|
break
|
|
}
|
|
result.Size = info.dataSize
|
|
result.Description = fmt.Sprintf("%s, %d bits, can sign: %t, can encrypt: %t, key ID: %#018X",
|
|
result.Description, info.bits, info.canSign, info.canEncrypt, info.keyID)
|
|
return result, nil
|
|
}
|
|
|
|
return nil, fmt.Errorf("invalid RSA signature")
|
|
}
|
|
|
|
// ---- OpenSSL ----
|
|
|
|
const DescriptionOpenSSL = "OpenSSL encryption"
|
|
|
|
// opensslMagic returns magic bytes for OpenSSL encrypted files.
|
|
func opensslMagic() [][]byte {
|
|
return [][]byte{
|
|
[]byte("Salted__"),
|
|
}
|
|
}
|
|
|
|
func isSaltInvalid(salt int) bool {
|
|
const saltLen = 8
|
|
badByteCount := 0
|
|
for i := 0; i < saltLen; i++ {
|
|
saltByte := byte((salt >> (8 * i)) & 0xFF)
|
|
if saltByte == 0 || IsPrintableASCII(saltByte) {
|
|
badByteCount++
|
|
}
|
|
}
|
|
return badByteCount == saltLen
|
|
}
|
|
|
|
// opensslParser validates OpenSSL encryption signatures.
|
|
func opensslParser(data []byte, offset int) (*SignatureResult, error) {
|
|
sslFields := []StructField{{"magic", "u64"}, {"salt", "u64"}}
|
|
|
|
result := &SignatureResult{
|
|
Offset: offset,
|
|
Description: DescriptionOpenSSL,
|
|
Confidence: ConfidenceLow,
|
|
}
|
|
|
|
if offset+StructSize(sslFields) > len(data) {
|
|
return nil, fmt.Errorf("insufficient OpenSSL data")
|
|
}
|
|
|
|
sslHeader, err := Parse(data[offset:], sslFields, "big")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to parse OpenSSL header")
|
|
}
|
|
|
|
salt := sslHeader["salt"]
|
|
if salt == 0 {
|
|
return nil, fmt.Errorf("OpenSSL salt is zero")
|
|
}
|
|
|
|
if isSaltInvalid(salt) {
|
|
return nil, fmt.Errorf("OpenSSL salt is invalid")
|
|
}
|
|
|
|
if offset == 0 {
|
|
result.Confidence = ConfidenceMedium
|
|
}
|
|
|
|
result.Description = fmt.Sprintf("%s, salt: %#X", result.Description, salt)
|
|
return result, nil
|
|
}
|
|
|
|
// ---- GPG ----
|
|
|
|
const DescriptionGPGSigned = "GPG signed file"
|
|
|
|
// gpgSignedMagic returns magic bytes for GPG signed files.
|
|
func gpgSignedMagic() [][]byte {
|
|
return [][]byte{
|
|
{0xA3, 0x01},
|
|
}
|
|
}
|
|
|
|
// gpgSignedParser validates GPG signed file signatures.
|
|
// The Rust version does a zlib decompression dry run; here we do a simplified validation.
|
|
func gpgSignedParser(data []byte, offset int) (*SignatureResult, error) {
|
|
result := &SignatureResult{
|
|
Offset: offset,
|
|
Confidence: ConfidenceHigh,
|
|
Description: DescriptionGPGSigned,
|
|
}
|
|
|
|
// GPG signed files are zlib compressed with magic bytes replaced.
|
|
// Validate there is enough data and the zlib-like stream follows.
|
|
if offset+4 > len(data) {
|
|
return nil, fmt.Errorf("insufficient GPG data")
|
|
}
|
|
|
|
// The third byte should be a valid zlib CMF byte (deflate method = 8)
|
|
cmf := data[offset+2]
|
|
if cmf&0x0F != 8 {
|
|
return nil, fmt.Errorf("GPG: invalid deflate method in underlying zlib stream")
|
|
}
|
|
|
|
result.Description = fmt.Sprintf("%s, compressed data at offset %d", result.Description, offset)
|
|
return result, nil
|
|
}
|
|
|
|
// ---- PEM ----
|
|
|
|
const (
|
|
DescriptionPEMCertificate = "PEM certificate"
|
|
DescriptionPEMPublicKey = "PEM public key"
|
|
DescriptionPEMPrivateKey = "PEM private key"
|
|
)
|
|
|
|
// pemCertificateMagic returns magic bytes for PEM certificates.
|
|
func pemCertificateMagic() [][]byte {
|
|
return [][]byte{
|
|
[]byte("-----BEGIN CERTIFICATE-----"),
|
|
}
|
|
}
|
|
|
|
// pemPublicKeyMagic returns magic bytes for PEM public keys.
|
|
func pemPublicKeyMagic() [][]byte {
|
|
return [][]byte{
|
|
[]byte("-----BEGIN PUBLIC KEY-----"),
|
|
[]byte("-----BEGIN RSA PUBLIC KEY-----"),
|
|
[]byte("-----BEGIN DSA PUBLIC KEY-----"),
|
|
[]byte("-----BEGIN ECDSA PUBLIC KEY-----"),
|
|
}
|
|
}
|
|
|
|
// pemPrivateKeyMagic returns magic bytes for PEM private keys.
|
|
func pemPrivateKeyMagic() [][]byte {
|
|
return [][]byte{
|
|
[]byte("-----BEGIN PRIVATE KEY-----"),
|
|
[]byte("-----BEGIN EC PRIVATE KEY-----"),
|
|
[]byte("-----BEGIN RSA PRIVATE KEY-----"),
|
|
[]byte("-----BEGIN DSA PRIVATE KEY-----"),
|
|
[]byte("-----BEGIN OPENSSH PRIVATE KEY-----"),
|
|
[]byte("-----BEGIN ANY PRIVATE KEY-----"),
|
|
[]byte("-----BEGIN ENCRYPTED PRIVATE KEY-----"),
|
|
[]byte("-----BEGIN TSS2 PRIVATE KEY-----"),
|
|
}
|
|
}
|
|
|
|
func decodePEMData(pemData []byte) (int, error) {
|
|
const delim = "--"
|
|
|
|
pemStr := string(pemData)
|
|
delimCount := 0
|
|
var base64Str strings.Builder
|
|
|
|
for _, line := range strings.Split(pemStr, "\n") {
|
|
line = strings.TrimRight(line, "\r")
|
|
if strings.HasPrefix(line, delim) {
|
|
delimCount++
|
|
if delimCount == 2 {
|
|
break
|
|
}
|
|
continue
|
|
}
|
|
base64Str.WriteString(line)
|
|
}
|
|
|
|
if base64Str.Len() == 0 {
|
|
return 0, fmt.Errorf("no PEM base64 data found")
|
|
}
|
|
|
|
decoded, err := base64.StdEncoding.DecodeString(base64Str.String())
|
|
if err != nil {
|
|
return 0, fmt.Errorf("PEM base64 decode failed: %v", err)
|
|
}
|
|
|
|
return len(decoded), nil
|
|
}
|
|
|
|
// pemFindEnd locates the end delimiter and returns the total PEM size.
|
|
func pemFindEnd(data []byte, offset int) (int, bool) {
|
|
const endPrefix = "-----END "
|
|
pemData := string(data[offset:])
|
|
idx := strings.Index(pemData, endPrefix)
|
|
if idx < 0 {
|
|
return 0, false
|
|
}
|
|
// Find end of the delimiter line
|
|
rest := pemData[idx:]
|
|
nlIdx := strings.Index(rest, "\n")
|
|
if nlIdx < 0 {
|
|
return idx + len(rest), true
|
|
}
|
|
return idx + nlIdx + 1, true
|
|
}
|
|
|
|
// pemParser validates PEM certificate and key signatures.
|
|
func pemParser(data []byte, offset int) (*SignatureResult, error) {
|
|
const minPEMLen = 26
|
|
|
|
result := &SignatureResult{
|
|
Offset: offset,
|
|
Confidence: ConfidenceHigh,
|
|
}
|
|
|
|
if offset+minPEMLen > len(data) {
|
|
return nil, fmt.Errorf("insufficient PEM data")
|
|
}
|
|
|
|
pemMagic := data[offset : offset+minPEMLen]
|
|
pemMagicStr := string(pemMagic)
|
|
|
|
// Determine type based on first 26 bytes
|
|
matched := false
|
|
for _, m := range pemPublicKeyMagic() {
|
|
if len(m) >= minPEMLen && strings.HasPrefix(string(m), pemMagicStr[:minPEMLen]) {
|
|
result.Description = DescriptionPEMPublicKey
|
|
matched = true
|
|
break
|
|
}
|
|
}
|
|
if !matched {
|
|
for _, m := range pemPrivateKeyMagic() {
|
|
if len(m) >= minPEMLen && strings.HasPrefix(string(m), pemMagicStr[:minPEMLen]) {
|
|
result.Description = DescriptionPEMPrivateKey
|
|
matched = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if !matched {
|
|
for _, m := range pemCertificateMagic() {
|
|
if len(m) >= minPEMLen && strings.HasPrefix(string(m), pemMagicStr[:minPEMLen]) {
|
|
result.Description = DescriptionPEMCertificate
|
|
matched = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if !matched {
|
|
return nil, fmt.Errorf("PEM magic not recognised")
|
|
}
|
|
|
|
// Find the end of PEM data
|
|
pemSize, found := pemFindEnd(data, offset)
|
|
if !found || pemSize == 0 {
|
|
return nil, fmt.Errorf("PEM end delimiter not found")
|
|
}
|
|
|
|
// Validate base64 content
|
|
if offset+pemSize > len(data) {
|
|
pemSize = len(data) - offset
|
|
}
|
|
if _, err := decodePEMData(data[offset : offset+pemSize]); err != nil {
|
|
return nil, fmt.Errorf("PEM validation failed: %v", err)
|
|
}
|
|
|
|
if offset == 0 && pemSize == len(data) {
|
|
result.ExtractionDeclined = true
|
|
}
|
|
|
|
result.Size = pemSize
|
|
return result, nil
|
|
}
|
|
|
|
// ---- PKCS DER ----
|
|
|
|
const DescriptionDERHash = "PKCS DER hash"
|
|
|
|
func derHashLookups() map[string][]byte {
|
|
return map[string][]byte{
|
|
"MD5": {0x30, 0x20, 0x30, 0x0c, 0x06, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x05, 0x05, 0x00, 0x04, 0x10},
|
|
"SHA1": {0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x05, 0x00, 0x04, 0x14},
|
|
"SHA256": {0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20},
|
|
"SHA384": {0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05, 0x00, 0x04, 0x30},
|
|
"SHA512": {0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, 0x00, 0x04},
|
|
}
|
|
}
|
|
|
|
// derHashMagic returns magic bytes for all DER hash types.
|
|
func derHashMagic() [][]byte {
|
|
var magics [][]byte
|
|
for _, m := range derHashLookups() {
|
|
mc := make([]byte, len(m))
|
|
copy(mc, m)
|
|
magics = append(magics, mc)
|
|
}
|
|
return magics
|
|
}
|
|
|
|
// derHashParser validates PKCS DER hash signatures.
|
|
func derHashParser(data []byte, offset int) (*SignatureResult, error) {
|
|
result := &SignatureResult{
|
|
Offset: offset,
|
|
Description: DescriptionDERHash,
|
|
Confidence: ConfidenceMedium,
|
|
}
|
|
|
|
for name, magic := range derHashLookups() {
|
|
hashEnd := offset + len(magic)
|
|
if hashEnd > len(data) {
|
|
continue
|
|
}
|
|
match := true
|
|
for i, b := range magic {
|
|
if data[offset+i] != b {
|
|
match = false
|
|
break
|
|
}
|
|
}
|
|
if match {
|
|
result.Size = len(magic)
|
|
result.Description = fmt.Sprintf("%s, %s", result.Description, name)
|
|
return result, nil
|
|
}
|
|
}
|
|
|
|
return nil, fmt.Errorf("DER hash validation failed")
|
|
}
|
|
|
|
// ---- LUKS ----
|
|
|
|
const DescriptionLUKS = "LUKS header"
|
|
|
|
// luksMagic returns magic bytes for LUKS headers.
|
|
func luksMagic() [][]byte {
|
|
return [][]byte{
|
|
{0x4C, 0x55, 0x4B, 0x53, 0xBA, 0xBE}, // "LUKS\xBA\xBE"
|
|
}
|
|
}
|
|
|
|
// luksParser validates LUKS header signatures.
|
|
func luksParser(data []byte, offset int) (*SignatureResult, error) {
|
|
const cipherAlgoStart = 8
|
|
const cipherAlgoEnd = 40
|
|
const cipherModeStart = 40
|
|
const cipherModeEnd = 72
|
|
const hashFnStart = 72
|
|
const hashFnEnd = 104
|
|
const luks2MinHeaderSize = 4032
|
|
|
|
luksBaseFields := []StructField{
|
|
{"magic_1", "u32"},
|
|
{"magic_2", "u16"},
|
|
{"version", "u16"},
|
|
{"header_size", "u64"},
|
|
}
|
|
|
|
result := &SignatureResult{
|
|
Offset: offset,
|
|
Name: "luks",
|
|
Description: DescriptionLUKS,
|
|
Confidence: ConfidenceMedium,
|
|
}
|
|
|
|
if offset+StructSize(luksBaseFields) > len(data) {
|
|
return nil, fmt.Errorf("insufficient LUKS data")
|
|
}
|
|
|
|
luksBase, err := Parse(data[offset:], luksBaseFields, "big")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to parse LUKS header")
|
|
}
|
|
|
|
version := luksBase["version"]
|
|
|
|
// Hash function string
|
|
if offset+hashFnEnd > len(data) {
|
|
return nil, fmt.Errorf("LUKS hash function data out of bounds")
|
|
}
|
|
hashFn := GetCString(data[offset+hashFnStart : offset+hashFnEnd])
|
|
if hashFn == "" {
|
|
return nil, fmt.Errorf("LUKS hash function string empty")
|
|
}
|
|
|
|
if version == 1 {
|
|
if offset+cipherAlgoEnd > len(data) {
|
|
return nil, fmt.Errorf("LUKS cipher data out of bounds")
|
|
}
|
|
cipherAlgo := GetCString(data[offset+cipherAlgoStart : offset+cipherAlgoEnd])
|
|
cipherMode := GetCString(data[offset+cipherModeStart : offset+cipherModeEnd])
|
|
if cipherAlgo == "" || cipherMode == "" {
|
|
return nil, fmt.Errorf("LUKS v1 cipher strings empty")
|
|
}
|
|
result.Description = fmt.Sprintf("%s, version: %d, cipher algorithm: %s, cipher mode: %s, hash fn: %s",
|
|
result.Description, version, cipherAlgo, cipherMode, hashFn)
|
|
return result, nil
|
|
} else if version == 2 {
|
|
headerSize := luksBase["header_size"]
|
|
availableData := len(data) - offset
|
|
if headerSize <= luks2MinHeaderSize || headerSize >= availableData {
|
|
return nil, fmt.Errorf("LUKS v2 header size invalid")
|
|
}
|
|
result.Description = fmt.Sprintf("%s, version: %d, header size: %d bytes, hash fn: %s",
|
|
result.Description, version, headerSize, hashFn)
|
|
return result, nil
|
|
}
|
|
|
|
return nil, fmt.Errorf("unsupported LUKS version: %d", version)
|
|
}
|
|
|
|
// ---- Hash constants ----
|
|
|
|
const hashMagicLen = 16
|
|
|
|
const (
|
|
DescriptionCRC32 = "CRC32 polynomial table"
|
|
DescriptionSHA256 = "SHA256 hash constants"
|
|
DescriptionMD5 = "MD5 hash constants"
|
|
)
|
|
|
|
// crc32Magic returns magic bytes for CRC32 polynomial tables.
|
|
// Order matters: index 0 is big endian, index 1 is little endian.
|
|
func crc32ConstMagic() [][]byte {
|
|
return [][]byte{
|
|
// Big endian
|
|
{0x00, 0x00, 0x00, 0x00, 0x77, 0x07, 0x30, 0x96, 0xEE, 0x0E, 0x61, 0x2C, 0x99, 0x09, 0x51, 0xBA},
|
|
// Little endian
|
|
{0x00, 0x00, 0x00, 0x00, 0x96, 0x30, 0x07, 0x77, 0x2C, 0x61, 0x0E, 0xEE, 0xBA, 0x51, 0x09, 0x99},
|
|
}
|
|
}
|
|
|
|
// sha256Magic returns magic bytes for SHA256 hash constants.
|
|
func sha256ConstMagic() [][]byte {
|
|
return [][]byte{
|
|
// Big endian
|
|
{0x42, 0x8a, 0x2f, 0x98, 0x71, 0x37, 0x44, 0x91, 0xb5, 0xc0, 0xfb, 0xcf, 0xe9, 0xb5, 0xdb, 0xa5},
|
|
// Little endian
|
|
{0x98, 0x2f, 0x8a, 0x42, 0x91, 0x44, 0x37, 0x71, 0xcf, 0xfb, 0xc0, 0xb5, 0xa5, 0xdb, 0xb5, 0xe9},
|
|
}
|
|
}
|
|
|
|
// md5Magic returns magic bytes for MD5 hash constants.
|
|
func md5ConstMagic() [][]byte {
|
|
return [][]byte{
|
|
// Big endian
|
|
{0xd7, 0x6a, 0xa4, 0x78, 0xe8, 0xc7, 0xb7, 0x56, 0x24, 0x20, 0x70, 0xdb, 0xc1, 0xbd, 0xce, 0xee},
|
|
// Little endian
|
|
{0x78, 0xa4, 0x6a, 0xd7, 0x56, 0xb7, 0xc7, 0xe8, 0xdb, 0x70, 0x20, 0x24, 0xee, 0xce, 0xbd, 0xc1},
|
|
}
|
|
}
|
|
|
|
func hashEndianness(data []byte, offset int, magics [][]byte) string {
|
|
if offset+hashMagicLen > len(data) {
|
|
return "little"
|
|
}
|
|
thisMagic := data[offset : offset+hashMagicLen]
|
|
if len(magics) > 0 && len(magics[0]) == hashMagicLen {
|
|
match := true
|
|
for i := 0; i < hashMagicLen; i++ {
|
|
if thisMagic[i] != magics[0][i] {
|
|
match = false
|
|
break
|
|
}
|
|
}
|
|
if match {
|
|
return "big"
|
|
}
|
|
}
|
|
return "little"
|
|
}
|
|
|
|
// crc32Parser validates CRC32 polynomial table signatures.
|
|
func crc32Parser(data []byte, offset int) (*SignatureResult, error) {
|
|
return &SignatureResult{
|
|
Offset: offset,
|
|
Size: hashMagicLen,
|
|
Description: fmt.Sprintf("%s, %s endian", DescriptionCRC32, hashEndianness(data, offset, crc32ConstMagic())),
|
|
}, nil
|
|
}
|
|
|
|
// sha256Parser validates SHA256 hash constant signatures.
|
|
func sha256Parser(data []byte, offset int) (*SignatureResult, error) {
|
|
return &SignatureResult{
|
|
Offset: offset,
|
|
Size: hashMagicLen,
|
|
Description: fmt.Sprintf("%s, %s endian", DescriptionSHA256, hashEndianness(data, offset, sha256ConstMagic())),
|
|
}, nil
|
|
}
|
|
|
|
// md5Parser validates MD5 hash constant signatures.
|
|
func md5Parser(data []byte, offset int) (*SignatureResult, error) {
|
|
return &SignatureResult{
|
|
Offset: offset,
|
|
Size: hashMagicLen,
|
|
Description: fmt.Sprintf("%s, %s endian", DescriptionMD5, hashEndianness(data, offset, md5ConstMagic())),
|
|
}, nil
|
|
}
|