Support Device mangle keys for code, custom mangle index or offset generators, general cleanup of names and notes

This commit is contained in:
DataHoarder 2023-10-08 12:11:55 +02:00
parent ae683087bb
commit 2391151938
Signed by: DataHoarder
SSH key fingerprint: SHA256:OLTRf6Fl87G52SiR7sWLGNzlJt4WOX+tfI2yxo0z7xk
16 changed files with 442 additions and 130 deletions

View file

@ -9,6 +9,7 @@ These utilities allow researchers to inspect the devices and work with them dire
* Decoding of Firmware files, both `Phyton` format and `AlmaCode` format
* Decryption of Firmware Blocks
* Encryption of Firmware Blocks
* Decryption of device unique id encrypted code blocks in ROM
* Decompression of Custom compressed data
* Compression to Custom compressed data
* Calculate custom CRC of data

View file

@ -56,6 +56,9 @@ func decryptRound(roundKey uint32, dataA, dataB, round uint32) (uint32, uint32)
}
```
Each round is composed of modular addition / subtractions mod 2^32, XOR, and logical shift left / right.
Each round has a round constant (the round number) added to the round key, a very simple key scheduling.
### Mode of operation
The cipher operates in ECB mode. As such, blocks containing the same value will encrypt equally.
@ -63,15 +66,18 @@ Attacking this cipher without knowledge of key material or algorithm is trivial,
given access to an Encryption Oracle and starting knowledge of a full 64-bit block in target material.
### Mangle Index
Mangle index is used to select the different key schedules from the tables below for the full Mangle construct.
### Hardcoded Mangle Key Table
This table is hardcoded in firmware, and seems to be used across all devices known.
This table is hardcoded in firmware, and seems to be the same across all devices known.
It gets used on the Outer Mangle and the Inner Mangle, depending on the selected Mangle Index.
| Offset | Identifier | Key Data |
|:------:|:----------:|:------------------------------------------------:|
| 0 | _Firmware_ | `0x0539c06f, 0x3a235801, 0x1bb4da80, 0x44916a65` |
| 0 | _Flash_ | `0x0539c06f, 0x3a235801, 0x1bb4da80, 0x44916a65` |
| 1 | _DeviceId_ | `0x5b01cb35, 0xb498a4fb, 0xe9486d82, 0xf4945010` |
| 2 | | `0xe8babcec, 0x2aa73df8, 0x4cf9f79c, 0x886d73e7` |
| 3 | | `0x25483503, 0xb1a0a8af, 0x24a745b2, 0xf5e21339` |
@ -81,7 +87,7 @@ It gets used on the Outer Mangle and the Inner Mangle, depending on the selected
| 7 | | `0x52f33e6f, 0x4d69a2f9, 0x77ab77c4, 0x468f4508` |
Several offsets from this table are used for either the outer mangle or other purposes.
* #0: _Firmware Outer Mangle Key_
* #0: _Flash Outer Mangle Key_
* #1: _Device Id Outer Mangle Key_
* #6: _Memory Outer Mangle Key_
@ -107,6 +113,11 @@ It gets used on the Inner Mangle, depending on the selected Mangle Index.
This key is randomly generated via various methods. It is 512 bytes long,
and includes the CRC of the original data being encrypted (before compression) twice.
The device generated Mangle key has an effective key size of 32-bit or lower,
due to the usage of a BorlandC LCG as entropy source, seeded with an on-device counter.
Additionally, some devices can be caused to fault into a static counter, generating the same key always.
Structure is as follows:
```
Mangle Index (2 bytes)
@ -142,4 +153,4 @@ func DeviceKey(deviceId1, deviceId2, deviceId3 uint32) (key Key) {
key[3] = outerKey[3]
return key
}
```
```

View file

@ -65,51 +65,65 @@ func (b EncryptedBlock) generateKeyBlock(generator KeyGenerator) (mangleIndex ui
}
const (
mangleIndexNormalKey0 = iota
mangleIndexNormalKey1
mangleIndexNormalKey2
mangleIndexNormalKey3
mangleIndexNormalKey4
mangleIndexNormalKey5
mangleIndexNormalKey6
mangleIndexNormalKey7
MangleIndexNormalKey0 = iota
MangleIndexNormalKey1
MangleIndexNormalKey2
MangleIndexNormalKey3
MangleIndexNormalKey4
MangleIndexNormalKey5
MangleIndexNormalKey6
MangleIndexNormalKey7
mangleIndexAlternateKey0
mangleIndexAlternateKey1
mangleIndexAlternateKey2
mangleIndexAlternateKey3
mangleIndexAlternateKey4
mangleIndexAlternateKey5
mangleIndexAlternateKey6
mangleIndexAlternateKey7
MangleIndexAlternateKey0
MangleIndexAlternateKey1
MangleIndexAlternateKey2
MangleIndexAlternateKey3
MangleIndexAlternateKey4
MangleIndexAlternateKey5
MangleIndexAlternateKey6
MangleIndexAlternateKey7
mangleIndexDeviceKey = 0xffff
MangleIndexDeviceKey = 0xffff
)
func (b EncryptedBlock) Encrypt(material KeyMaterial) {
func (b EncryptedBlock) Encrypt(material KeyMaterial) error {
mangleIndex := b.generateKeyBlock(material.Generator)
// Mangle of data
b.MangleKey().Encrypt(b.DataBlock())
var keyData MangleKeyData
// Inner mangle of key
if material.AlternateMangleKey != nil {
material.AlternateMangleKey[mangleIndex].Encrypt(b.MangleKeyBlock())
binary.LittleEndian.PutUint16(b[EncryptedBlockMangleIndexOffset:], uint16(mangleIndex+mangleIndexAlternateKey0))
} else if material.DeviceKey != nil {
material.DeviceKey.Encrypt(b.MangleKeyBlock())
binary.LittleEndian.PutUint16(b[EncryptedBlockMangleIndexOffset:], mangleIndexDeviceKey)
if mangleIndex <= MangleIndexNormalKey7 {
keyData = HardcodedMangleTable[mangleIndex]
} else if mangleIndex <= MangleIndexAlternateKey7 {
if material.AlternateKeyTable == nil {
keyData = AlternateMangleTable[mangleIndex-MangleIndexAlternateKey0]
} else {
keyData = material.AlternateKeyTable[mangleIndex-MangleIndexAlternateKey0]
}
} else {
HardcodedMangleTable[mangleIndex].Encrypt(b.MangleKeyBlock())
if mangleIndex != MangleIndexDeviceKey {
return errors.New("invalid key number")
}
binary.LittleEndian.PutUint16(b[EncryptedBlockMangleIndexOffset:], uint16(mangleIndex))
if material.DeviceKey == nil {
return errors.New("unsupported device key")
}
keyData = *material.DeviceKey
}
keyData.Encrypt(b.MangleKeyBlock())
binary.LittleEndian.PutUint16(b[EncryptedBlockMangleIndexOffset:], uint16(mangleIndex))
// Outer mangle of key
HardcodedMangleTable[material.OuterKeyOffset].Encrypt(b.KeyBlock())
return nil
}
func (b EncryptedBlock) Decrypt(material KeyMaterial, verifyCrc bool) (err error) {
@ -119,21 +133,21 @@ func (b EncryptedBlock) Decrypt(material KeyMaterial, verifyCrc bool) (err error
mangleIndex := b.MangleIndex()
var keyData MangleKeyData
if mangleIndex <= mangleIndexNormalKey7 {
if mangleIndex <= MangleIndexNormalKey7 {
keyData = HardcodedMangleTable[mangleIndex]
} else if mangleIndex <= mangleIndexAlternateKey7 {
if material.AlternateMangleKey != nil {
keyData = material.AlternateMangleKey[mangleIndex-mangleIndexAlternateKey0]
} else if mangleIndex <= MangleIndexAlternateKey7 {
if material.AlternateKeyTable == nil {
keyData = AlternateMangleTable[mangleIndex-MangleIndexAlternateKey0]
} else {
keyData = AlternateMangleTable[mangleIndex-mangleIndexAlternateKey0]
keyData = material.AlternateKeyTable[mangleIndex-MangleIndexAlternateKey0]
}
} else {
if mangleIndex != mangleIndexDeviceKey {
if mangleIndex != MangleIndexDeviceKey {
return errors.New("invalid key number")
}
if material.DeviceKey == nil {
return errors.New("unsupported hardware key")
return errors.New("unsupported device key")
}
keyData = *material.DeviceKey

View file

@ -6,6 +6,7 @@ import (
"encoding/binary"
"git.gammaspectra.live/WeebDataHoarder/PhytonUtils/compression"
"git.gammaspectra.live/WeebDataHoarder/PhytonUtils/crc"
"git.gammaspectra.live/WeebDataHoarder/PhytonUtils/utils"
"io"
"slices"
"testing"
@ -15,23 +16,33 @@ func TestEncryptedBlock_Decrypt_FirmwareUncompressed(t *testing.T) {
t.Parallel()
data := slices.Clone(sampleBlockFirmwareUncompressed)
err := data.Decrypt(NewFirmwareKeyMaterial(nil), true)
err := data.Decrypt(NewFlashKeyMaterial(nil), true)
if err != nil {
t.Fatal(err)
}
for i := 0; i < EncryptedBlockKeySize; i += 64 {
t.Logf("key %03x: %s", i, utils.HexOctets(data.KeyBlock()[i:i+64]))
}
}
func TestEncryptedBlock_Decrypt_FirmwareCompressed(t *testing.T) {
t.Parallel()
data := slices.Clone(sampleBlockFirmwareCompressed)
err := data.Decrypt(NewFirmwareKeyMaterial(nil), false)
err := data.Decrypt(NewFlashKeyMaterial(nil), false)
if err != nil {
t.Fatal(err)
}
for i := 0; i < EncryptedBlockKeySize; i += 64 {
t.Logf("key %03x: %s", i, utils.HexOctets(data.KeyBlock()[i:i+64]))
}
rawCompressedData := data.DataBlock()[:sampleBlockFirmwareCompressedSize]
t.Logf("Compression data padding: %x", data.DataBlock()[sampleBlockFirmwareCompressedSize:])
decompressedData, err := compression.FirmwareBlockDecompress(rawCompressedData)
if err != nil {
t.Fatal(err)
@ -109,7 +120,10 @@ func TestEncryptedBlock_EncryptDecrypt(t *testing.T) {
var seedBuf [4]byte
_, err = io.ReadFull(rand.Reader, seedBuf[:])
material := NewMemoryKeyMaterial(&SecureRandomKeyGenerator{})
b.Encrypt(material)
err = b.Encrypt(material)
if err != nil {
t.Fatal(err)
}
dec := slices.Clone(b)
err = dec.Decrypt(material, true)
@ -122,6 +136,40 @@ func TestEncryptedBlock_EncryptDecrypt(t *testing.T) {
}
}
func TestEncryptedBlock_EncryptDecrypt_WithAlternateKey(t *testing.T) {
t.Parallel()
b := NewEncryptedBlock(64)
_, err := io.ReadFull(rand.Reader, b.DataBlock())
if err != nil {
t.Fatal(err)
}
data := slices.Clone(b.DataBlock())
var seedBuf [4]byte
_, err = io.ReadFull(rand.Reader, seedBuf[:])
generator := BorlandRandKeyGenerator(binary.LittleEndian.Uint32(seedBuf[:]))
material := NewFlashKeyMaterial(NewMangleIndexOffsetGeneratorWrapper(&generator, MangleIndexAlternateKey0))
err = b.Encrypt(material)
if err != nil {
t.Fatal(err)
}
dec := slices.Clone(b)
err = dec.Decrypt(material, true)
if err != nil {
t.Fatal(err)
}
if bytes.Compare(data, dec.DataBlock()) != 0 {
t.Fatal("data does not match")
}
if dec.MangleIndex() < MangleIndexAlternateKey0 {
t.Fatal("data did not use alternate key")
}
}
func TestEncryptedBlock_EncryptDecrypt_WithDeviceKey(t *testing.T) {
t.Parallel()
@ -135,9 +183,12 @@ func TestEncryptedBlock_EncryptDecrypt_WithDeviceKey(t *testing.T) {
var seedBuf [4]byte
_, err = io.ReadFull(rand.Reader, seedBuf[:])
generator := BorlandRandKeyGenerator(binary.LittleEndian.Uint32(seedBuf[:]))
material := NewFirmwareKeyMaterial(&generator)
material.DeviceKey = sampleDeviceMangleKey
b.Encrypt(material)
material := NewFlashKeyMaterial(NewMangleIndexGeneratorWrapper(&generator, MangleIndexDeviceKey))
material.DeviceKey = sampleDeviceMangleKeyOffset6
err = b.Encrypt(material)
if err != nil {
t.Fatal(err)
}
dec := slices.Clone(b)
err = dec.Decrypt(material, true)
@ -148,6 +199,10 @@ func TestEncryptedBlock_EncryptDecrypt_WithDeviceKey(t *testing.T) {
if bytes.Compare(data, dec.DataBlock()) != 0 {
t.Fatal("data does not match")
}
if dec.MangleIndex() < MangleIndexAlternateKey0 {
t.Fatal("data did not use alternate key")
}
}
func TestEncryptedBlock_Encrypt(t *testing.T) {
@ -158,7 +213,10 @@ func TestEncryptedBlock_Encrypt(t *testing.T) {
b := NewEncryptedBlock(0)
generator := BorlandRandKeyGenerator(0)
material := NewMemoryKeyMaterial(&generator)
b.Encrypt(material)
err = b.Encrypt(material)
if err != nil {
t.Fatal(err)
}
d1 := slices.Clone(b)
d2 := slices.Clone(sampleBlockMemoryEmpty)
@ -185,6 +243,22 @@ func TestEncryptedBlock_Encrypt(t *testing.T) {
}
}
func TestEncryptedBlock_Encrypt_Zero(t *testing.T) {
t.Parallel()
b := NewEncryptedBlock(8)
material := NewMemoryKeyMaterial(&ZeroKeyGenerator{})
err := b.Encrypt(material)
if err != nil {
t.Fatal(err)
}
for i := 0; i < EncryptedBlockKeySize; i += 64 {
t.Logf("%s", utils.HexOctets(b.KeyBlock()[i:i+64]))
}
t.Logf("DATA %s", utils.HexOctets(b.DataBlock()))
}
func TestBruteforceSeed_Short(t *testing.T) {
data := slices.Clone(sampleBlockMemoryEmpty)
@ -202,24 +276,23 @@ func TestBruteforceSeed_Short(t *testing.T) {
}
}
/*
func TestBruteforceSeed_FirmwareUncompressed(t *testing.T) {
data := slices.Clone(sampleBlockFirmwareUncompressed)
seeds, err := BruteforceBorlandSeed(data, NewFirmwareKeyMaterial(nil))
if err != nil {
t.Fatal(err)
}
for _, seed := range seeds {
t.Logf("found seed = 0x%08x", seed)
}
if len(seeds) != 2 {
t.Fatal("seeds not found")
_, err := BruteforceBorlandSeed(data, NewFlashKeyMaterial(nil))
if err == nil || err.Error() != "not a borland rand seed" {
t.Fatal("error expected: \"not a borland rand seed\"")
}
}
func TestBruteforceSeed_FirmwareCompressed(t *testing.T) {
data := slices.Clone(sampleBlockFirmwareCompressed)
_, err := BruteforceBorlandSeed(data, NewFlashKeyMaterial(nil))
if err == nil || err.Error() != "not a borland rand seed" {
t.Fatal("error expected: \"not a borland rand seed\"")
}
}
*/
func BenchmarkEncryptedBlock_Encrypt(b *testing.B) {
const dataSize = 64
@ -243,7 +316,10 @@ func BenchmarkEncryptedBlock_Encrypt(b *testing.B) {
for pb.Next() {
buf.Reset()
copy(buf.DataBlock(), data)
buf.Encrypt(material)
err := buf.Encrypt(material)
if err != nil {
panic(err)
}
}
})
}
@ -268,7 +344,10 @@ func BenchmarkEncryptedBlock_Decrypt(b *testing.B) {
generator := BorlandRandKeyGenerator(binary.LittleEndian.Uint32(seedBuf[:]))
material.Generator = &generator
encryptedBlock.Encrypt(material)
err = encryptedBlock.Encrypt(material)
if err != nil {
b.Fatal(err)
}
buf := NewEncryptedBlock(len(encryptedBlock.DataBlock()))
for pb.Next() {

View file

@ -1,7 +1,5 @@
package encryption
var sampleDeviceMangleKey = DeviceMangleKey(0x003B0056, 0x4D4B5002, 0x20323455)
var sampleBlockMemoryEmpty = EncryptedBlock{
0x2D, 0x01, 0x3E, 0x61, 0x52, 0x5A, 0xDF, 0xED, 0xF4, 0x1F, 0xA0, 0x40, 0xE3, 0xC2, 0x24, 0xDF, 0x4C, 0xD6, 0xF6, 0x98, 0x5B, 0xB7, 0x99, 0x27, 0xBE, 0x18, 0x88, 0xE8, 0xA5, 0x78, 0x29, 0xEB,
0x2C, 0x3F, 0xB6, 0x21, 0xD8, 0x62, 0x9A, 0x21, 0x1B, 0x42, 0xD0, 0xD9, 0x44, 0x04, 0xEB, 0xE3, 0x31, 0x7E, 0xBE, 0xD4, 0x47, 0x6D, 0xA8, 0xE1, 0x2F, 0xE4, 0x81, 0x84, 0x7B, 0x04, 0xC3, 0x60,

View file

@ -2,13 +2,43 @@ package encryption
import (
"encoding/binary"
"errors"
"math"
"runtime"
"slices"
"sync"
)
// IsKeyBorlandSeedLikely The generator used on BorlandRand does not set the highest bit, as such it can be detected
func IsKeyBorlandSeedLikely(b EncryptedBlock, material KeyMaterial) bool {
data := slices.Clone(b)
err := data.Decrypt(material, false)
if err != nil {
return false
}
for i := EncryptedBlockMangleKeyOffset; i < EncryptedBlockCRC1Offset; i += 2 {
if binary.LittleEndian.Uint16(data[i:])&0x8000 > 0 {
return false
}
}
for i := EncryptedBlockPaddingKeyOffset; i < EncryptedBlockKeySize; i += 2 {
if binary.LittleEndian.Uint16(data[i:])&0x8000 > 0 {
return false
}
}
return true
}
func BruteforceBorlandSeed(b EncryptedBlock, material KeyMaterial) ([]uint32, error) {
//TODO: this can be done more efficiently as LCG leaks 16 bits each time, by backwards looping and solving across uint16 range
if !IsKeyBorlandSeedLikely(b, material) {
return nil, errors.New("not a borland rand seed")
}
data := slices.Clone(b)
err := data.Decrypt(material, false)
if err != nil {
@ -19,9 +49,9 @@ func BruteforceBorlandSeed(b EncryptedBlock, material KeyMaterial) ([]uint32, er
perCpu := math.MaxUint32 / numCpu
firstValue := binary.LittleEndian.Uint16(data[2:])
firstValue := binary.LittleEndian.Uint16(data[EncryptedBlockMangleKeyOffset:])
secondValue := binary.LittleEndian.Uint16(data[4:])
secondValue := binary.LittleEndian.Uint16(data[EncryptedBlockMangleKeyOffset+2:])
var foundSeeds []uint32
var foundSeedsLock sync.Mutex
@ -38,24 +68,24 @@ func BruteforceBorlandSeed(b EncryptedBlock, material KeyMaterial) ([]uint32, er
}
for {
currentSeed := BorlandRand(seed)
seedA := uint16((currentSeed << 1) >> 0x11)
currentSeed = BorlandRand(currentSeed)
seedB := uint16((currentSeed << 1) >> 0x11)
currentSeed, output := BorlandRand(seed)
seedA := output
currentSeed, output = BorlandRand(currentSeed)
seedB := output
if seedA == firstValue && seedB == secondValue {
if func() bool {
for i := 4; i < 16; i += 2 {
currentSeed = BorlandRand(currentSeed)
if binary.LittleEndian.Uint16(data[2+i:]) != uint16((currentSeed<<1)>>0x11) {
for i := EncryptedBlockMangleKeyOffset + 4; i < EncryptedBlockCRC1Offset; i += 2 {
currentSeed, output = BorlandRand(currentSeed)
if binary.LittleEndian.Uint16(data[i:]) != output {
return false
}
}
for i := 0; i < 0xf3; i++ {
currentSeed = BorlandRand(currentSeed)
for i := EncryptedBlockPaddingKeyOffset; i < EncryptedBlockKeySize; i += 2 {
currentSeed, output = BorlandRand(currentSeed)
if binary.LittleEndian.Uint16(data[0x1a+i*2:]) != uint16((currentSeed<<1)>>0x11) {
if binary.LittleEndian.Uint16(data[i:]) != output {
return false
}
}

47
encryption/deviceid.go Normal file
View file

@ -0,0 +1,47 @@
package encryption
import "encoding/binary"
type DeviceId [3]uint32
func DeviceMangleKeyOffset6(deviceId DeviceId) *MangleKeyData {
var d MangleKeyData
outerKey := HardcodedMangleTable[OuterMangleKeyOffsetDeviceId]
binary.LittleEndian.PutUint32(d[:], deviceId[0]^outerKey.RoundKey(0))
binary.LittleEndian.PutUint32(d[4:], deviceId[1]^outerKey.RoundKey(1))
binary.LittleEndian.PutUint32(d[8:], deviceId[2]^outerKey.RoundKey(2))
binary.LittleEndian.PutUint32(d[12:], outerKey.RoundKey(3))
return &d
}
func DeviceMangleKeyOffset0(deviceId DeviceId) *MangleKeyData {
var d MangleKeyData
outerKey := HardcodedMangleTable[OuterMangleKeyOffsetDefault]
binary.LittleEndian.PutUint32(d[:], deviceId[0]^outerKey.RoundKey(0))
binary.LittleEndian.PutUint32(d[4:], deviceId[1]^outerKey.RoundKey(1))
binary.LittleEndian.PutUint32(d[8:], deviceId[2]^outerKey.RoundKey(2))
binary.LittleEndian.PutUint32(d[12:], (^deviceId[0])^outerKey.RoundKey(3))
return &d
}
// DecryptDeviceCode Decrypts code specifically encrypted to only work on a specific device id
func DecryptDeviceCode(deviceId DeviceId, code []byte) []byte {
if len(code)%8 != 0 {
panic("len must be % 8")
}
DeviceMangleKeyOffset0(deviceId).Decrypt(code)
return code
}
// EncryptDeviceCode Encrypt code specifically encrypted to only work on a specific device id
func EncryptDeviceCode(deviceId DeviceId, code []byte) []byte {
if len(code)%8 != 0 {
panic("len must be % 8")
}
DeviceMangleKeyOffset0(deviceId).Encrypt(code)
return code
}

View file

@ -0,0 +1,86 @@
package encryption
import (
"git.gammaspectra.live/WeebDataHoarder/PhytonUtils/crc"
"git.gammaspectra.live/WeebDataHoarder/PhytonUtils/utils"
"slices"
"testing"
)
var testDeviceId = DeviceId{0x003B0056, 0x4D4B5002, 0x20323455}
var sampleDeviceMangleKeyOffset6 = DeviceMangleKeyOffset6(testDeviceId)
var sampleEncryptedCodeA = []byte{
0xce, 0xc4, 0xb1, 0x88, 0x85, 0xcb, 0x4f, 0x56, 0x8b, 0x1c, 0xc6, 0x01, 0x00, 0x43, 0x9b, 0x61,
0x3b, 0xb4, 0x37, 0x37, 0x40, 0xe3, 0x73, 0x96, 0xdd, 0x8e, 0xa2, 0xab, 0x89, 0x51, 0x14, 0xcf,
0xfe, 0x05, 0x99, 0xee, 0xd6, 0xec, 0xb3, 0x69, 0xce, 0x57, 0xe4, 0xc3, 0xd9, 0xea, 0x18, 0x35,
0x2c, 0x64, 0xee, 0x2e, 0x77, 0x98, 0xc5, 0x63, 0x24, 0xf9, 0xf3, 0x29, 0x09, 0x57, 0xef, 0x14,
0x2e, 0xa7, 0x76, 0x3e, 0x7a, 0x3d, 0x8b, 0x7c, 0xc4, 0xe7, 0x49, 0x87, 0x2f, 0x6c, 0x36, 0x30,
0xb2, 0x2b, 0x81, 0x8e, 0x67, 0x04, 0xbb, 0x7b, 0x67, 0xac, 0x8d, 0x12, 0x72, 0xac, 0x52, 0xcf,
0xf3, 0xcc, 0xd1, 0x4c, 0x64, 0x88, 0x17, 0xdf, 0x04, 0x2e, 0x86, 0xa9, 0x5e, 0xca, 0x1b, 0xf4,
0xd3, 0xa7, 0xdd, 0x87, 0x83, 0x9a, 0x36, 0x02, 0x82, 0xef, 0xac, 0xaa, 0xae, 0x62, 0x2a, 0xec,
0x20, 0x84, 0xe4, 0xc7, 0x06, 0xa7, 0x2b, 0x88, 0x76, 0xd6, 0xd4, 0x6b, 0xfa, 0xdd, 0xfb, 0x76,
0xe3, 0x56, 0xf2, 0xeb, 0x7b, 0x0e, 0x55, 0x09, 0x42, 0x57, 0xe5, 0xf8, 0x6d, 0x64, 0x23, 0x3f,
0x92, 0x01, 0xd7, 0x1d, 0x5f, 0x45, 0xb9, 0x04, 0xca, 0x6f, 0x17, 0xd8, 0x0d, 0x1d, 0x7f, 0x14,
0xb7, 0xf3, 0x02, 0x42, 0x90, 0x2a, 0x99, 0x32, 0xf5, 0xe6, 0x1d, 0xf6, 0x8f, 0xab, 0xd8, 0x45,
0xde, 0x4b, 0x0b, 0x0a, 0x58, 0x5d, 0x74, 0xdf, 0x13, 0x43, 0x88, 0xdd, 0xdb, 0xb3, 0xdc, 0x7a,
0x94, 0xb9, 0xfb, 0x21, 0x44, 0xd7, 0x86, 0x08, 0xc4, 0xdb, 0x1f, 0x64, 0x8f, 0x73, 0x11, 0x0e,
0xf7, 0x65, 0x92, 0x0a, 0xa5, 0x43, 0x70, 0xf7, 0xba, 0x38, 0xeb, 0x1f, 0x6b, 0x19, 0x66, 0x79,
0x43, 0x80, 0x4e, 0xbb, 0x9e, 0x7b, 0x96, 0x61, 0x86, 0x72, 0xb4, 0xfe, 0xba, 0x14, 0xfa, 0xbd,
0x9f, 0x0b, 0x2a, 0x3c, 0x06, 0x9c, 0xfa, 0x9e, 0x93, 0x9f, 0x92, 0xc1, 0x09, 0x69, 0x0d, 0x94,
}
var terminator1 = []byte{0xc7, 0x5f, 0x36, 0x28, 0xd6, 0x08, 0xad, 0x1b}
var sampleEncryptedCodeB = []byte{
0x55, 0x62, 0x55, 0x07, 0x46, 0x8d, 0xa2, 0xdd, 0xed, 0xa5, 0xda, 0x56, 0xbd, 0x35, 0x4d, 0x86,
0x65, 0x7b, 0xd1, 0x31, 0xc4, 0x31, 0xa2, 0x43, 0xa1, 0x91, 0x5f, 0xf7, 0x21, 0x2d, 0xbf, 0x21,
0x4f, 0x59, 0xd1, 0x4e, 0xa7, 0x99, 0xc3, 0xcc, 0x28, 0x46, 0x7f, 0x68, 0xbc, 0xe2, 0x1b, 0x12,
0x12, 0xea, 0xb1, 0xf9, 0x7b, 0xb9, 0x44, 0x7d, 0x91, 0xab, 0xbe, 0xc7, 0xb1, 0x03, 0x15, 0xe4,
0x79, 0x09, 0xaa, 0xdc, 0x0e, 0x75, 0xdf, 0x4d, 0xe0, 0xc9, 0x26, 0xe0, 0x47, 0xb6, 0x81, 0xfa,
0xba, 0x80, 0xa0, 0x37, 0xcd, 0xde, 0x7a, 0x4b, 0x1f, 0x89, 0x2f, 0x01, 0x76, 0x2e, 0x0b, 0xcc,
0x55, 0xc2, 0xfc, 0xbe, 0xfc, 0xbd, 0x53, 0xf1, 0x6d, 0x7a, 0xf5, 0x2e, 0xc7, 0x63, 0x01, 0x66,
0xf9, 0xe8, 0xbc, 0x20, 0x50, 0x7c, 0xde, 0x06, 0x37, 0xad, 0x9b, 0xf0, 0xca, 0x76, 0x8b, 0x3f,
0x4a, 0xe8, 0xbf, 0x5f, 0x99, 0xf5, 0x9a, 0x7f, 0x1b, 0x9a, 0x72, 0x8b, 0x4a, 0x5f, 0x29, 0x09,
0x86, 0x72, 0xb4, 0xfe, 0xba, 0x14, 0xfa, 0xbd, 0xde, 0xe4, 0xa3, 0x70, 0x96, 0xee, 0xe4, 0x0f,
}
var terminator2 = []byte{0x31, 0xa2, 0x8b, 0xe6, 0xfc, 0x3e, 0xd0, 0x94}
func TestDecryptDeviceCode_A(t *testing.T) {
t.Parallel()
code := slices.Clone(sampleEncryptedCodeA)
DecryptDeviceCode(testDeviceId, code)
for i := 0; i < len(code); i += 16 {
t.Logf("%s", utils.HexOctets(code[i:min(len(code), i+16)]))
}
const expectedCRC = 0x39f8c16e
crcValue := crc.CalculateCRC(code)
if crcValue != expectedCRC {
t.Fatalf("expected CRC %08x, got %08x", expectedCRC, crcValue)
}
}
func TestDecryptDeviceCode_B(t *testing.T) {
t.Parallel()
code := slices.Clone(sampleEncryptedCodeB)
DecryptDeviceCode(testDeviceId, code)
for i := 0; i < len(code); i += 16 {
t.Logf("%s", utils.HexOctets(code[i:min(len(code), i+16)]))
}
const expectedCRC = 0x475f4cc9
crcValue := crc.CalculateCRC(code)
if crcValue != expectedCRC {
t.Fatalf("expected CRC %08x, got %08x", expectedCRC, crcValue)
}
}

View file

@ -1,12 +0,0 @@
package encryption
func DecryptFirmwareData(buf []byte, readAddressSize bool) {
//TODO
seed := uint32(0x2009)
var blockBuf [8]byte
for i := 0; i < len(buf); i += 8 {
copy(blockBuf[:], buf[i:])
seed = BorlandRandXORInPlace(blockBuf[:], seed)
}
}

View file

@ -28,19 +28,72 @@ func (g *SecureRandomKeyGenerator) MangleIndex() uint32 {
type BorlandRandKeyGenerator uint32
func (g *BorlandRandKeyGenerator) Fill(data []byte) {
var output uint16
seed := uint32(*g)
for i := 0; i < len(data); i += 2 {
seed = BorlandRand(seed)
binary.LittleEndian.PutUint16(data[i:], uint16((seed<<1)>>0x11))
seed, output = BorlandRand(seed)
binary.LittleEndian.PutUint16(data[i:], output)
}
*g = BorlandRandKeyGenerator(seed)
}
func (g *BorlandRandKeyGenerator) MangleIndex() uint32 {
var output uint16
seed := uint32(*g)
seed = BorlandRand(seed)
mangleIndex := (seed >> 0x10) & 0x7
seed, output = BorlandRand(seed)
mangleIndex := uint32(output & 0x7)
*g = BorlandRandKeyGenerator(seed)
return mangleIndex
}
// ZeroKeyGenerator Always outputs zero
type ZeroKeyGenerator struct{}
func (g *ZeroKeyGenerator) Fill(data []byte) {
clear(data)
}
func (g *ZeroKeyGenerator) MangleIndex() uint32 {
return 0
}
type mangleIndexGeneratorWrapper struct {
generator KeyGenerator
mangleIndex uint32
}
func (w *mangleIndexGeneratorWrapper) Fill(data []byte) {
w.generator.Fill(data)
}
func (w *mangleIndexGeneratorWrapper) MangleIndex() uint32 {
return w.mangleIndex
}
func NewMangleIndexGeneratorWrapper(generator KeyGenerator, mangleIndex uint32) KeyGenerator {
return &mangleIndexGeneratorWrapper{
generator: generator,
mangleIndex: mangleIndex,
}
}
type mangleIndexOffsetGeneratorWrapper struct {
generator KeyGenerator
offset uint32
}
func (w *mangleIndexOffsetGeneratorWrapper) Fill(data []byte) {
w.generator.Fill(data)
}
func (w *mangleIndexOffsetGeneratorWrapper) MangleIndex() uint32 {
return w.generator.MangleIndex() + w.offset
}
func NewMangleIndexOffsetGeneratorWrapper(generator KeyGenerator, offset uint32) KeyGenerator {
return &mangleIndexOffsetGeneratorWrapper{
generator: generator,
offset: offset,
}
}

View file

@ -13,14 +13,14 @@ const MangleKeyRounds = 48
// MangleKeyData Key entries of 128-bit length
type MangleKeyData [MangleKeyDataSize]byte
func (d MangleKeyData) RoundKey(round int32) uint32 {
return binary.LittleEndian.Uint32(d[(round%4)*4:])
func (d MangleKeyData) RoundKey(round int) uint32 {
return binary.LittleEndian.Uint32(d[(round&3)<<2:])
}
type MangleKeyTable [8]MangleKeyData
var HardcodedMangleTable = MangleKeyTable{
/* Slot 0 is used by OuterMangleKeyOffsetFirmware for outer key mangle */
/* Slot 0 is used by OuterMangleKeyOffsetFlash for outer key mangle */
/*0x00*/ {0x6f, 0xc0, 0x39, 0x05, 0x01, 0x58, 0x23, 0x3a, 0x80, 0xda, 0xb4, 0x1b, 0x65, 0x6a, 0x91, 0x44},
/* Slot 1 is used by OuterMangleKeyOffsetDeviceId for outer key mangle */
/*0x01*/ {0x35, 0xcb, 0x01, 0x5b, 0xfb, 0xa4, 0x98, 0xb4, 0x82, 0x6d, 0x48, 0xe9, 0x10, 0x50, 0x94, 0xf4},

View file

@ -1,50 +1,36 @@
package encryption
import (
"encoding/binary"
)
type KeyMaterial struct {
// Generator Random source to generate the inline material
Generator KeyGenerator
OuterKeyOffset OuterMangleKeyOffset
DeviceKey *MangleKeyData
AlternateMangleKey *MangleKeyTable
Generator KeyGenerator
OuterKeyOffset OuterMangleKeyOffset
DeviceKey *MangleKeyData
AlternateKeyTable *MangleKeyTable
}
func NewFirmwareKeyMaterial(generator KeyGenerator) KeyMaterial {
func NewFlashKeyMaterial(generator KeyGenerator) KeyMaterial {
return KeyMaterial{
Generator: generator,
OuterKeyOffset: OuterMangleKeyOffsetFirmware,
DeviceKey: nil,
AlternateMangleKey: nil,
Generator: generator,
OuterKeyOffset: OuterMangleKeyOffsetFlash,
DeviceKey: nil,
AlternateKeyTable: nil,
}
}
func NewMemoryKeyMaterial(generator KeyGenerator) KeyMaterial {
return KeyMaterial{
Generator: generator,
OuterKeyOffset: OuterMangleKeyOffsetMemory,
DeviceKey: nil,
AlternateMangleKey: nil,
Generator: generator,
OuterKeyOffset: OuterMangleKeyOffsetMemory,
DeviceKey: nil,
AlternateKeyTable: nil,
}
}
type OuterMangleKeyOffset int
const (
OuterMangleKeyOffsetFirmware = 0x00
OuterMangleKeyOffsetDeviceId = 0x01
OuterMangleKeyOffsetMemory = 0x06
OuterMangleKeyOffsetDefault = 0
OuterMangleKeyOffsetFlash = OuterMangleKeyOffsetDefault
OuterMangleKeyOffsetDeviceId = 1
OuterMangleKeyOffsetMemory = 6
)
func DeviceMangleKey(deviceId1, deviceId2, deviceId3 uint32) *MangleKeyData {
var d MangleKeyData
outerKey := HardcodedMangleTable[OuterMangleKeyOffsetDeviceId]
binary.LittleEndian.PutUint32(d[:], deviceId1^outerKey.RoundKey(0))
binary.LittleEndian.PutUint32(d[4:], deviceId2^outerKey.RoundKey(1))
binary.LittleEndian.PutUint32(d[8:], deviceId3^outerKey.RoundKey(2))
binary.LittleEndian.PutUint32(d[12:], outerKey.RoundKey(3))
return &d
}

View file

@ -6,15 +6,17 @@ import "encoding/binary"
const BorlandRandMultiplier = 0x015A4E35
// BorlandRand Borland C++ rand()
func BorlandRand(seed uint32) uint32 {
return seed*BorlandRandMultiplier + 1
func BorlandRand(seed uint32) (newSeed uint32, output uint16) {
newSeed = seed*BorlandRandMultiplier + 1
return newSeed, uint16(newSeed>>16) & 0x7FFF
}
func BorlandRandXORBytes(src, dst []byte, seed uint32) (newSeed uint32) {
_ = dst[len(src)-1]
var output uint16
for i := range src {
seed = BorlandRand(seed)
dst[i] = src[i] ^ byte(seed>>16)
seed, output = BorlandRand(seed)
dst[i] = src[i] ^ byte(output)
}
return seed
}

View file

@ -87,7 +87,7 @@ func (entry Entry) Code() []byte {
for _, b := range entry.Blocks {
decBlock := slices.Clone(b.Block)
err := decBlock.Decrypt(encryption.NewFirmwareKeyMaterial(nil), !entry.compressed)
err := decBlock.Decrypt(encryption.NewFlashKeyMaterial(nil), !entry.compressed)
if err != nil {
panic(err)
}

View file

@ -10,7 +10,10 @@ import (
"io"
)
const FlashAreaPublicSignature = 0x19601217
type FlashAreaData struct {
// PublicSignature Should always be FlashAreaPublicSignature
PublicSignature uint32
StructLen uint32
Data []byte

14
utils/hex.go Normal file
View file

@ -0,0 +1,14 @@
package utils
import "encoding/hex"
func HexOctets(data []byte) (result string) {
for i := 0; i < len(data); i += 8 {
if len(result) != 0 {
result += " "
}
result += hex.EncodeToString(data[i : i+8])
}
return result
}