Support Device mangle keys for code, custom mangle index or offset generators, general cleanup of names and notes
This commit is contained in:
parent
ae683087bb
commit
2391151938
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
```
|
||||
```
|
|
@ -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
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
47
encryption/deviceid.go
Normal 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
|
||||
}
|
86
encryption/deviceid_test.go
Normal file
86
encryption/deviceid_test.go
Normal 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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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},
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
14
utils/hex.go
Normal 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
|
||||
}
|
Loading…
Reference in a new issue