CRC improvements, faster and efficient borland seed bruteforce, cleanup borland/java seeds, custom key material CRC
This commit is contained in:
parent
2391151938
commit
3a25cc15bc
41
crc/crc.go
41
crc/crc.go
|
@ -1,6 +1,13 @@
|
|||
package crc
|
||||
|
||||
var CRCTable = func() [256]uint32 {
|
||||
import (
|
||||
"encoding/binary"
|
||||
)
|
||||
|
||||
const Polynomial uint32 = 0x04C11DB7
|
||||
const InitialValue CRC = 0xFFFFFFFF
|
||||
|
||||
var Table = func(polynomial uint64) [256]uint32 {
|
||||
polynomialDivision := func(polynomial, input uint64, len uint) uint64 {
|
||||
mask := uint64(1)<<len - 1
|
||||
|
||||
|
@ -17,19 +24,15 @@ var CRCTable = func() [256]uint32 {
|
|||
}
|
||||
var table [256]uint32
|
||||
for i := 0; i < 256; i++ {
|
||||
table[i] = uint32(polynomialDivision(0x04C11DB7, uint64(i), 32))
|
||||
table[i] = uint32(polynomialDivision(polynomial, uint64(i), 32))
|
||||
}
|
||||
return table
|
||||
}()
|
||||
}(uint64(Polynomial))
|
||||
|
||||
type CRC struct {
|
||||
crc uint32
|
||||
}
|
||||
type CRC uint32
|
||||
|
||||
func NewCRC() *CRC {
|
||||
return &CRC{
|
||||
crc: 0xFFFFFFFF,
|
||||
}
|
||||
func NewCRC() CRC {
|
||||
return InitialValue
|
||||
}
|
||||
|
||||
func (c *CRC) Update(buf []byte) {
|
||||
|
@ -39,10 +42,7 @@ func (c *CRC) Update(buf []byte) {
|
|||
|
||||
for chunksLeft := chunks; chunksLeft > 0; chunksLeft-- {
|
||||
index := (chunks - chunksLeft) * chunkSize
|
||||
c.crc = CRCTable[((buf[index+3]&255)^byte(c.crc>>24))] ^ (c.crc << 8)
|
||||
c.crc = CRCTable[((buf[index+2]&255)^byte(c.crc>>24))] ^ (c.crc << 8)
|
||||
c.crc = CRCTable[((buf[index+1]&255)^byte(c.crc>>24))] ^ (c.crc << 8)
|
||||
c.crc = CRCTable[((buf[index]&255)^byte(c.crc>>24))] ^ (c.crc << 8)
|
||||
c.UpdateUint32(binary.LittleEndian.Uint32(buf[index:]))
|
||||
}
|
||||
|
||||
if remainder != 0 {
|
||||
|
@ -52,8 +52,19 @@ func (c *CRC) Update(buf []byte) {
|
|||
}
|
||||
}
|
||||
|
||||
func (c *CRC) UpdateUint32(data uint32) {
|
||||
crc := uint32(*c)
|
||||
|
||||
crc = Table[(byte(data>>24)^byte(crc>>24))] ^ (crc << 8)
|
||||
crc = Table[(byte(data>>16)^byte(crc>>24))] ^ (crc << 8)
|
||||
crc = Table[(byte(data>>8)^byte(crc>>24))] ^ (crc << 8)
|
||||
crc = Table[(byte(data)^byte(crc>>24))] ^ (crc << 8)
|
||||
|
||||
*c = CRC(crc)
|
||||
}
|
||||
|
||||
func (c *CRC) Sum32() uint32 {
|
||||
return c.crc
|
||||
return uint32(*c)
|
||||
}
|
||||
|
||||
func CalculateCRC(buf []byte) uint32 {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
# CRC notes
|
||||
|
||||
Uses 32-bit "MPEG2" polynomial 0x04C11DB7, data is fed in chunks of 4 bytes, little endian (swap order of each chunk bytes).
|
||||
Uses 32-bit "MPEG2" polynomial _0x04C11DB7_, data is fed in chunks of 4 bytes,
|
||||
little endian (swap order of each chunk bytes). There is no pre or post inversion. Start value is _0xFFFFFFFF_.
|
||||
|
||||
End chunk has to be padded with \x00 to fit into 4.
|
|
@ -3,6 +3,7 @@ package encryption
|
|||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"git.gammaspectra.live/WeebDataHoarder/PhytonUtils/crc"
|
||||
)
|
||||
|
||||
|
@ -50,18 +51,23 @@ func (b EncryptedBlock) Reset() {
|
|||
clear(b)
|
||||
}
|
||||
|
||||
func (b EncryptedBlock) generateKeyBlock(generator KeyGenerator) (mangleIndex uint32) {
|
||||
func (b EncryptedBlock) generateKeyBlock(material KeyMaterial) (mangleIndex uint32) {
|
||||
_ = b[EncryptedBlockKeySize-1]
|
||||
|
||||
crcValue := crc.CalculateCRC(b.DataBlock())
|
||||
var crcValue uint32
|
||||
if material.CRC != nil {
|
||||
crcValue = material.CRC(b.DataBlock())
|
||||
} else {
|
||||
crcValue = crc.CalculateCRC(b.DataBlock())
|
||||
}
|
||||
|
||||
binary.LittleEndian.PutUint32(b[EncryptedBlockCRC1Offset:], crcValue)
|
||||
binary.LittleEndian.PutUint32(b[EncryptedBlockCRC2Offset:], crcValue)
|
||||
|
||||
generator.Fill(b[EncryptedBlockMangleKeyOffset:EncryptedBlockCRC1Offset])
|
||||
generator.Fill(b[EncryptedBlockPaddingKeyOffset:EncryptedBlockKeySize])
|
||||
material.Generator.FillKeyBlock(b[EncryptedBlockMangleKeyOffset:EncryptedBlockCRC1Offset])
|
||||
material.Generator.FillKeyBlock(b[EncryptedBlockPaddingKeyOffset:EncryptedBlockKeySize])
|
||||
|
||||
return generator.MangleIndex()
|
||||
return material.Generator.MangleIndex()
|
||||
}
|
||||
|
||||
const (
|
||||
|
@ -88,7 +94,7 @@ const (
|
|||
|
||||
func (b EncryptedBlock) Encrypt(material KeyMaterial) error {
|
||||
|
||||
mangleIndex := b.generateKeyBlock(material.Generator)
|
||||
mangleIndex := b.generateKeyBlock(material)
|
||||
|
||||
// Mangle of data
|
||||
b.MangleKey().Encrypt(b.DataBlock())
|
||||
|
@ -166,10 +172,15 @@ func (b EncryptedBlock) Decrypt(material KeyMaterial, verifyCrc bool) (err error
|
|||
}
|
||||
|
||||
if verifyCrc {
|
||||
calculatedCrc := crc.CalculateCRC(b.DataBlock())
|
||||
var calculatedCrc uint32
|
||||
if material.CRC != nil {
|
||||
calculatedCrc = material.CRC(b.DataBlock())
|
||||
} else {
|
||||
calculatedCrc = crc.CalculateCRC(b.DataBlock())
|
||||
}
|
||||
|
||||
if calculatedCrc != crc1 {
|
||||
return errors.New("data CRC invalid")
|
||||
return fmt.Errorf("data CRC not matching: expected %08x, got %08x", crc1, calculatedCrc)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -97,7 +97,7 @@ func TestEncryptedBlock_Decrypt_FirmwareCompressed(t *testing.T) {
|
|||
t.Logf("Change (exhaustive): %.08f%%", (customRatioExhaustive-originalRatio)*100)
|
||||
}
|
||||
|
||||
func TestEncryptedBlock_Decrypt_MemoryData(t *testing.T) {
|
||||
func TestEncryptedBlock_Decrypt_MemoryData_Empty(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
data := slices.Clone(sampleBlockMemoryEmpty)
|
||||
|
@ -107,6 +107,20 @@ func TestEncryptedBlock_Decrypt_MemoryData(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestEncryptedBlock_Decrypt_MemoryData_Empty_BrokenCRC(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
data := slices.Clone(sampleBlockMemoryEmptyBrokenCRC)
|
||||
material := NewMemoryKeyMaterial(nil)
|
||||
material.CRC = func(data []byte) uint32 {
|
||||
return crc.CalculateCRC(data) & 0x0000FFFF
|
||||
}
|
||||
err := data.Decrypt(material, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncryptedBlock_EncryptDecrypt(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
@ -259,41 +273,6 @@ func TestEncryptedBlock_Encrypt_Zero(t *testing.T) {
|
|||
t.Logf("DATA %s", utils.HexOctets(b.DataBlock()))
|
||||
}
|
||||
|
||||
func TestBruteforceSeed_Short(t *testing.T) {
|
||||
data := slices.Clone(sampleBlockMemoryEmpty)
|
||||
|
||||
seeds, err := BruteforceBorlandSeed(data, NewMemoryKeyMaterial(nil))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, seed := range seeds {
|
||||
t.Logf("seed = 0x%08x", seed)
|
||||
}
|
||||
|
||||
if len(seeds) != 2 {
|
||||
t.Fatal("seeds not found")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBruteforceSeed_FirmwareUncompressed(t *testing.T) {
|
||||
data := slices.Clone(sampleBlockFirmwareUncompressed)
|
||||
|
||||
_, 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
|
||||
const keyOffset = OuterMangleKeyOffsetMemory
|
||||
|
|
|
@ -19,6 +19,25 @@ var sampleBlockMemoryEmpty = EncryptedBlock{
|
|||
0x02, 0xBE, 0x06, 0xF7, 0x6E, 0xCB, 0x5B, 0x46, 0x45, 0xDD, 0x8A, 0x2D, 0xF1, 0xD3, 0x78, 0x92, 0x61, 0x03, 0x00, 0x40, 0x64, 0x72, 0xC4, 0xA5, 0x16, 0x25, 0x57, 0x75, 0x97, 0xF1, 0x07, 0x88,
|
||||
}
|
||||
|
||||
var sampleBlockMemoryEmptyBrokenCRC = EncryptedBlock{
|
||||
0x2d, 0x01, 0x3e, 0x61, 0x52, 0x5a, 0xdf, 0xed, 0xf4, 0x1f, 0xa0, 0x40, 0xe3, 0xc2, 0x24, 0xdf, 0x0d, 0xb1, 0x33, 0xee, 0x3e, 0x19, 0xc6, 0x34, 0x15, 0x42, 0x59, 0x25, 0x32, 0x2b, 0xab, 0x39,
|
||||
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,
|
||||
0x05, 0x83, 0xa1, 0x21, 0x3c, 0xbf, 0xd7, 0x2e, 0x4d, 0x0a, 0xad, 0x0d, 0xbc, 0xdf, 0xba, 0x61, 0x08, 0x6b, 0xdd, 0xde, 0x7a, 0x4a, 0xc9, 0xc9, 0x38, 0x34, 0xdc, 0x4e, 0xd0, 0x10, 0x82, 0x45,
|
||||
0x28, 0x6f, 0x7b, 0xde, 0xb4, 0xb9, 0x2a, 0xdd, 0x63, 0x1a, 0x71, 0x91, 0x6f, 0x13, 0x59, 0x10, 0x7d, 0x21, 0xa5, 0xf4, 0x63, 0x31, 0x1b, 0xd7, 0xdb, 0x8c, 0x68, 0xee, 0x2e, 0x28, 0x34, 0x4c,
|
||||
0x99, 0xc3, 0xcc, 0x58, 0xca, 0x22, 0x4d, 0xb3, 0x54, 0xd5, 0x64, 0x3b, 0x04, 0x5d, 0x94, 0x6c, 0x8e, 0x78, 0x80, 0x01, 0x25, 0x0e, 0x0f, 0xa2, 0xfe, 0xaa, 0x9d, 0xf9, 0xe1, 0x6c, 0x5e, 0x9e,
|
||||
0x6e, 0xb1, 0xc5, 0x83, 0x6e, 0xda, 0x5e, 0x58, 0xf9, 0xe1, 0x2d, 0x5d, 0xe4, 0x53, 0xaf, 0x4d, 0xcd, 0xce, 0x4e, 0x8c, 0xd8, 0x6a, 0x7c, 0x24, 0x6d, 0xec, 0x5c, 0xf6, 0x90, 0x32, 0xf7, 0x89,
|
||||
0x43, 0x67, 0x09, 0xbb, 0xbb, 0x88, 0x28, 0xdb, 0xd1, 0xab, 0x81, 0x22, 0xd3, 0x16, 0x5b, 0x5e, 0xef, 0xbe, 0x9f, 0xac, 0x6b, 0x7b, 0x97, 0x37, 0x48, 0x89, 0x9f, 0x09, 0x09, 0x88, 0x35, 0xca,
|
||||
0x28, 0x15, 0x25, 0xda, 0xcf, 0xae, 0xb0, 0x4d, 0xa3, 0x82, 0xd3, 0x70, 0x89, 0x35, 0x3c, 0x50, 0xb2, 0x05, 0x92, 0xf6, 0xca, 0xf5, 0xc9, 0x08, 0xfb, 0xe8, 0x4e, 0x89, 0x1d, 0x06, 0x65, 0xe5,
|
||||
0x7b, 0x36, 0x1c, 0x11, 0xa5, 0x63, 0x51, 0x99, 0x5d, 0x8c, 0x93, 0xac, 0x22, 0x56, 0x2e, 0x56, 0x88, 0xe0, 0x6c, 0x85, 0x35, 0xee, 0xa0, 0xda, 0x2a, 0x1c, 0x66, 0xc8, 0x6b, 0x16, 0x87, 0xf9,
|
||||
0x05, 0xb5, 0x1b, 0x3b, 0x8f, 0x92, 0xf6, 0xe4, 0xfd, 0x53, 0x23, 0xda, 0x07, 0x1d, 0x4d, 0x7b, 0x06, 0x0f, 0x1e, 0xd1, 0x0b, 0x5c, 0x01, 0xa8, 0xb3, 0x79, 0x6f, 0x4d, 0x5b, 0xd6, 0xc5, 0x54,
|
||||
0x70, 0x34, 0x24, 0xc6, 0x4f, 0x4d, 0x2b, 0x66, 0xbf, 0x01, 0x73, 0xf4, 0x01, 0x50, 0x5e, 0x8e, 0xe2, 0xd6, 0xbc, 0x68, 0x9b, 0xdb, 0xe2, 0xcb, 0x43, 0x26, 0x50, 0x1f, 0x2e, 0x2a, 0xa8, 0x4f,
|
||||
0xab, 0x42, 0x25, 0x0a, 0x5d, 0x0f, 0x8f, 0x8b, 0xdf, 0xbd, 0x01, 0x7c, 0x5d, 0x93, 0xfe, 0xa8, 0xa1, 0x9b, 0xeb, 0x5a, 0xcb, 0xe0, 0x01, 0x89, 0x0c, 0x42, 0x3b, 0x7a, 0x1b, 0x16, 0x5c, 0x9e,
|
||||
0xb6, 0xd5, 0x4a, 0x96, 0x06, 0xc0, 0xca, 0x19, 0xb2, 0x9b, 0x5c, 0x57, 0xbf, 0x01, 0x10, 0x7a, 0xc3, 0x1f, 0xf9, 0xfd, 0x18, 0xe1, 0x20, 0x3b, 0xca, 0xf8, 0xc0, 0xce, 0x59, 0x30, 0x15, 0xda,
|
||||
0x70, 0x62, 0x3e, 0x39, 0xe9, 0xca, 0x90, 0xbe, 0xc1, 0x7f, 0x0a, 0x93, 0xf2, 0xd7, 0xcc, 0xac, 0xa1, 0xf8, 0x7f, 0x31, 0xba, 0xc4, 0x05, 0x69, 0xe3, 0x3c, 0x3f, 0x35, 0x33, 0x72, 0xbf, 0xaf,
|
||||
0xe7, 0xc0, 0x99, 0x8d, 0xd7, 0xef, 0x32, 0xb9, 0xdf, 0x77, 0x08, 0xd0, 0x77, 0x8b, 0x71, 0x4f, 0x05, 0x7f, 0x37, 0x5f, 0x55, 0x28, 0x2f, 0xc7, 0x8d, 0xaf, 0x79, 0xb3, 0x9a, 0xef, 0x76, 0x65,
|
||||
0x02, 0xbe, 0x06, 0xf7, 0x6e, 0xcb, 0x5b, 0x46, 0x45, 0xdd, 0x8a, 0x2d, 0xf1, 0xd3, 0x78, 0x92, 0x61, 0x03, 0x00, 0x40, 0x64, 0x72, 0xc4, 0xa5, 0x16, 0x25, 0x57, 0x75, 0x97, 0xf1, 0x07, 0x88,
|
||||
}
|
||||
|
||||
var sampleBlockFirmwareCompressedSize = len(sampleBlockFirmwareCompressed) - EncryptedBlockKeySize - 3
|
||||
var sampleBlockFirmwareCompressed = EncryptedBlock{
|
||||
0x4E, 0xA1, 0x65, 0x14, 0xE4, 0xD8, 0xEE, 0xB6, 0x99, 0x58, 0xF3, 0x54, 0x81, 0x49, 0x15, 0x51, 0x0C, 0xD9, 0x88, 0xB8, 0x75, 0x00, 0x96, 0xAC, 0xC8, 0x11, 0x4C, 0x96, 0x12, 0x23, 0x5B, 0xAC,
|
||||
|
|
57
encryption/borlandrand.go
Normal file
57
encryption/borlandrand.go
Normal file
|
@ -0,0 +1,57 @@
|
|||
package encryption
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
)
|
||||
|
||||
// BorlandRandMultiplier 22695477
|
||||
const BorlandRandMultiplier uint32 = 0x015A4E35
|
||||
|
||||
const BorlandRandAddend uint32 = 1
|
||||
|
||||
const BorlandRandOutputShift = 16
|
||||
const BorlandRandOutputMask uint32 = 0x7FFF
|
||||
|
||||
const BorlandRandModulus = 0xFFFFFFFF
|
||||
|
||||
// BorlandRandMultiplierInverse Calculated using math.ModularMultiplicativeInverseFixed(uint32(BorlandRandMultiplier))
|
||||
const BorlandRandMultiplierInverse uint32 = 0x2925141D
|
||||
|
||||
// BorlandRand Borland C++ rand()
|
||||
func BorlandRand(seed uint32) (newSeed uint32, output uint16) {
|
||||
newSeed = BorlandRandNextSeed(seed)
|
||||
return newSeed, BorlandRandOutput(newSeed)
|
||||
}
|
||||
|
||||
func BorlandRandOutput(seed uint32) uint16 {
|
||||
return uint16(seed>>BorlandRandOutputShift) & uint16(BorlandRandOutputMask)
|
||||
}
|
||||
|
||||
func BorlandRandNextSeed(seed uint32) uint32 {
|
||||
return (seed*BorlandRandMultiplier + BorlandRandAddend) & BorlandRandModulus
|
||||
}
|
||||
|
||||
func BorlandRandPreviousSeed(seed uint32) uint32 {
|
||||
return ((seed - BorlandRandAddend) * BorlandRandMultiplierInverse) & BorlandRandModulus
|
||||
}
|
||||
|
||||
func BorlandRandXORBytes(src, dst []byte, seed uint32) (newSeed uint32) {
|
||||
_ = dst[len(src)-1]
|
||||
var output uint16
|
||||
for i := range src {
|
||||
seed, output = BorlandRand(seed)
|
||||
dst[i] = src[i] ^ byte(output)
|
||||
}
|
||||
return seed
|
||||
}
|
||||
|
||||
func BorlandRandXORInPlace(data []byte, seed uint32) (newSeed uint32) {
|
||||
return BorlandRandXORBytes(data, data, seed)
|
||||
}
|
||||
|
||||
func BorlandRandXORUint32(value, seed uint32) (newValue, newSeed uint32) {
|
||||
var buf [4]byte
|
||||
binary.LittleEndian.PutUint32(buf[:], value)
|
||||
newSeed = BorlandRandXORInPlace(buf[:], seed)
|
||||
return binary.LittleEndian.Uint32(buf[:]), newSeed
|
||||
}
|
|
@ -3,10 +3,8 @@ package encryption
|
|||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"math"
|
||||
"runtime"
|
||||
"math/bits"
|
||||
"slices"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// IsKeyBorlandSeedLikely The generator used on BorlandRand does not set the highest bit, as such it can be detected
|
||||
|
@ -33,7 +31,6 @@ func IsKeyBorlandSeedLikely(b EncryptedBlock, material KeyMaterial) bool {
|
|||
}
|
||||
|
||||
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")
|
||||
|
@ -44,76 +41,159 @@ func BruteforceBorlandSeed(b EncryptedBlock, material KeyMaterial) ([]uint32, er
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Efficient search as LCG leaks 15 bits each time, by backwards looping and solving across uint17 range
|
||||
|
||||
numCpu := runtime.NumCPU()
|
||||
const dataSize = 2
|
||||
const stateDataStart = EncryptedBlockMangleKeyOffset
|
||||
const stateDataEnd = EncryptedBlockCRC1Offset - dataSize
|
||||
validStateBits := bits.OnesCount32(BorlandRandOutputMask)
|
||||
validStateInverseBits := bits.OnesCount32(^BorlandRandOutputMask)
|
||||
var stateOutputMask = BorlandRandOutputMask << BorlandRandOutputShift
|
||||
|
||||
perCpu := math.MaxUint32 / numCpu
|
||||
statesBuf := make([]uint32, 0, 32-validStateBits)
|
||||
getPossibleStates := func(state uint32, previousOutput uint16) (states []uint32) {
|
||||
var seed, prevSeed uint32
|
||||
|
||||
firstValue := binary.LittleEndian.Uint16(data[EncryptedBlockMangleKeyOffset:])
|
||||
states = statesBuf[:0]
|
||||
|
||||
secondValue := binary.LittleEndian.Uint16(data[EncryptedBlockMangleKeyOffset+2:])
|
||||
var limit uint32 = 1 << validStateInverseBits
|
||||
for n := uint32(0); n < limit; n++ {
|
||||
// Fills the output part of the state
|
||||
seed = (state & stateOutputMask) | ((n & (^uint32(0xFFFF))) << validStateBits) | (n & 0xFFFF)
|
||||
|
||||
var foundSeeds []uint32
|
||||
var foundSeedsLock sync.Mutex
|
||||
prevSeed = BorlandRandPreviousSeed(seed)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for cpu := 0; cpu < numCpu; cpu++ {
|
||||
wg.Add(1)
|
||||
go func(cpu int) {
|
||||
defer wg.Done()
|
||||
seed := uint32(cpu * perCpu)
|
||||
endSeed := seed + uint32(perCpu)
|
||||
if cpu == (numCpu - 1) {
|
||||
endSeed = math.MaxUint32
|
||||
if BorlandRandOutput(prevSeed) == previousOutput {
|
||||
states = append(states, prevSeed)
|
||||
}
|
||||
|
||||
for {
|
||||
currentSeed, output := BorlandRand(seed)
|
||||
seedA := output
|
||||
currentSeed, output = BorlandRand(currentSeed)
|
||||
seedB := output
|
||||
|
||||
if seedA == firstValue && seedB == secondValue {
|
||||
if func() bool {
|
||||
for i := EncryptedBlockMangleKeyOffset + 4; i < EncryptedBlockCRC1Offset; i += 2 {
|
||||
currentSeed, output = BorlandRand(currentSeed)
|
||||
if binary.LittleEndian.Uint16(data[i:]) != output {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
for i := EncryptedBlockPaddingKeyOffset; i < EncryptedBlockKeySize; i += 2 {
|
||||
currentSeed, output = BorlandRand(currentSeed)
|
||||
|
||||
if binary.LittleEndian.Uint16(data[i:]) != output {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}() {
|
||||
func() {
|
||||
foundSeedsLock.Lock()
|
||||
defer foundSeedsLock.Unlock()
|
||||
foundSeeds = append(foundSeeds, seed)
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
if seed == endSeed {
|
||||
break
|
||||
}
|
||||
seed++
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
}(cpu)
|
||||
}
|
||||
return states
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
var possibleStates []uint32
|
||||
|
||||
slices.Sort(foundSeeds)
|
||||
stateIndex := stateDataEnd
|
||||
{
|
||||
previousOutput := binary.LittleEndian.Uint16(data.KeyBlock()[stateIndex-dataSize:])
|
||||
var nextStates []uint32
|
||||
previousState := (uint32(binary.LittleEndian.Uint16(data.KeyBlock()[stateIndex:])) << BorlandRandOutputShift) & stateOutputMask
|
||||
states := getPossibleStates(previousState, previousOutput)
|
||||
if len(nextStates) == 0 {
|
||||
nextStates = states
|
||||
states = slices.Compact(states)
|
||||
nextStates = slices.Clone(states)
|
||||
} else {
|
||||
for i := len(nextStates) - 1; i >= 0; i-- {
|
||||
if !slices.Contains(states, nextStates[i]) {
|
||||
nextStates = slices.Delete(nextStates, i, i+1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return foundSeeds, nil
|
||||
slices.Sort(nextStates)
|
||||
nextStates = slices.Compact(nextStates)
|
||||
possibleStates = nextStates
|
||||
|
||||
stateIndex -= dataSize
|
||||
}
|
||||
|
||||
//Last rounds backwards with checks
|
||||
for ; stateIndex >= stateDataStart; stateIndex -= dataSize {
|
||||
for j := len(possibleStates) - 1; j >= 0; j-- {
|
||||
prevState := BorlandRandPreviousSeed(possibleStates[j])
|
||||
if _, output := BorlandRand(prevState); output != binary.LittleEndian.Uint16(data.KeyBlock()[stateIndex:]) {
|
||||
possibleStates = slices.Delete(possibleStates, j, j+1)
|
||||
continue
|
||||
}
|
||||
possibleStates[j] = prevState
|
||||
}
|
||||
}
|
||||
|
||||
slices.Sort(possibleStates)
|
||||
possibleStates = slices.Compact(possibleStates)
|
||||
|
||||
return possibleStates, err
|
||||
}
|
||||
|
||||
func BruteforceBorlandSeedBytes(b EncryptedBlock, material KeyMaterial) ([]uint32, error) {
|
||||
|
||||
data := slices.Clone(b)
|
||||
err := data.Decrypt(material, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Efficient search as LCG leaks 8 bits each time, by backwards looping and solving across uint24 range
|
||||
|
||||
const dataSize = 1
|
||||
const stateDataStart = EncryptedBlockMangleKeyOffset
|
||||
const stateDataEnd = EncryptedBlockCRC1Offset - dataSize
|
||||
const byteOutputMask = BorlandRandOutputMask & 0xFF
|
||||
validStateBits := bits.OnesCount32(byteOutputMask)
|
||||
validStateInverseBits := bits.OnesCount32(^byteOutputMask)
|
||||
var stateOutputMask = byteOutputMask << BorlandRandOutputShift
|
||||
|
||||
statesBuf := make([]uint32, 0, 32-validStateBits)
|
||||
getPossibleStates := func(state uint32, previousOutput uint8) (states []uint32) {
|
||||
var seed, prevSeed uint32
|
||||
|
||||
states = statesBuf[:0]
|
||||
|
||||
var limit uint32 = 1 << validStateInverseBits
|
||||
for n := uint32(0); n < limit; n++ {
|
||||
// Fills the output part of the state
|
||||
seed = (state & stateOutputMask) | ((n & (^uint32(0xFFFF))) << validStateBits) | (n & 0xFFFF)
|
||||
|
||||
prevSeed = BorlandRandPreviousSeed(seed)
|
||||
|
||||
if uint8(BorlandRandOutput(prevSeed)) == previousOutput {
|
||||
states = append(states, prevSeed)
|
||||
}
|
||||
}
|
||||
return states
|
||||
}
|
||||
|
||||
var possibleStates []uint32
|
||||
|
||||
stateIndex := stateDataEnd
|
||||
|
||||
{
|
||||
previousOutput := data.KeyBlock()[stateIndex-dataSize]
|
||||
var nextStates []uint32
|
||||
previousState := (uint32(data.KeyBlock()[stateIndex]) << BorlandRandOutputShift) & stateOutputMask
|
||||
states := getPossibleStates(previousState, previousOutput)
|
||||
if len(nextStates) == 0 {
|
||||
slices.Sort(states)
|
||||
states = slices.Compact(states)
|
||||
nextStates = slices.Clone(states)
|
||||
} else {
|
||||
for i := len(nextStates) - 1; i >= 0; i-- {
|
||||
if !slices.Contains(states, nextStates[i]) {
|
||||
nextStates = slices.Delete(nextStates, i, i+1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
slices.Sort(nextStates)
|
||||
nextStates = slices.Compact(nextStates)
|
||||
possibleStates = nextStates
|
||||
|
||||
stateIndex -= dataSize
|
||||
}
|
||||
|
||||
//Last rounds backwards with checks
|
||||
for ; stateIndex >= stateDataStart; stateIndex -= dataSize {
|
||||
for j := len(possibleStates) - 1; j >= 0; j-- {
|
||||
prevState := BorlandRandPreviousSeed(possibleStates[j])
|
||||
if _, output := BorlandRand(prevState); uint8(output) != data.KeyBlock()[stateIndex] {
|
||||
possibleStates = slices.Delete(possibleStates, j, j+1)
|
||||
continue
|
||||
}
|
||||
possibleStates[j] = prevState
|
||||
}
|
||||
}
|
||||
|
||||
slices.Sort(possibleStates)
|
||||
possibleStates = slices.Compact(possibleStates)
|
||||
|
||||
return possibleStates, err
|
||||
}
|
||||
|
|
123
encryption/bruteforce_test.go
Normal file
123
encryption/bruteforce_test.go
Normal file
|
@ -0,0 +1,123 @@
|
|||
package encryption
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"slices"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBruteforceSeed_Short(t *testing.T) {
|
||||
data := slices.Clone(sampleBlockMemoryEmpty)
|
||||
|
||||
seeds, err := BruteforceBorlandSeed(data, NewMemoryKeyMaterial(nil))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
seed := uint32(0)
|
||||
|
||||
t.Logf("original seed = 0x%08x", seed)
|
||||
|
||||
for _, seed := range seeds {
|
||||
t.Logf("possible seed = 0x%08x", seed)
|
||||
}
|
||||
|
||||
if !slices.Contains(seeds, seed) {
|
||||
t.Fatal("seed not found")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBruteforceSeed_Random(t *testing.T) {
|
||||
|
||||
t.Parallel()
|
||||
|
||||
var err error
|
||||
|
||||
b := NewEncryptedBlock(0)
|
||||
|
||||
var seedBuf [4]byte
|
||||
_, err = io.ReadFull(rand.Reader, seedBuf[:])
|
||||
seed := binary.LittleEndian.Uint32(seedBuf[:])
|
||||
generator := BorlandRandKeyGenerator(seed)
|
||||
material := NewMemoryKeyMaterial(&generator)
|
||||
err = b.Encrypt(material)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Logf("original seed = 0x%08x", seed)
|
||||
|
||||
data := slices.Clone(b)
|
||||
|
||||
seeds, err := BruteforceBorlandSeed(data, material)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Logf("original seed = 0x%08x", seed)
|
||||
|
||||
for _, seed := range seeds {
|
||||
t.Logf("possible seed = 0x%08x", seed)
|
||||
}
|
||||
|
||||
if !slices.Contains(seeds, seed) {
|
||||
t.Fatal("seed not found")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBruteforceSeed_Byte_Random(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var err error
|
||||
|
||||
b := NewEncryptedBlock(0)
|
||||
|
||||
var seedBuf [4]byte
|
||||
_, err = io.ReadFull(rand.Reader, seedBuf[:])
|
||||
seed := binary.LittleEndian.Uint32(seedBuf[:])
|
||||
generator := BorlandRandByteKeyGenerator(seed)
|
||||
material := NewMemoryKeyMaterial(&generator)
|
||||
err = b.Encrypt(material)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Logf("original seed = 0x%08x", seed)
|
||||
|
||||
data := slices.Clone(b)
|
||||
|
||||
seeds, err := BruteforceBorlandSeedBytes(data, material)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Logf("original seed = 0x%08x", seed)
|
||||
|
||||
for _, seed := range seeds {
|
||||
t.Logf("possible seed = 0x%08x", seed)
|
||||
}
|
||||
|
||||
if !slices.Contains(seeds, seed) {
|
||||
t.Fatal("seed not found")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBruteforceSeed_FirmwareUncompressed(t *testing.T) {
|
||||
data := slices.Clone(sampleBlockFirmwareUncompressed)
|
||||
|
||||
_, 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\"")
|
||||
}
|
||||
}
|
|
@ -7,14 +7,14 @@ import (
|
|||
)
|
||||
|
||||
type KeyGenerator interface {
|
||||
Fill(data []byte)
|
||||
FillKeyBlock(data []byte)
|
||||
MangleIndex() uint32
|
||||
}
|
||||
|
||||
// SecureRandomKeyGenerator Generates random numbers using the system secure random number generator
|
||||
type SecureRandomKeyGenerator struct{}
|
||||
|
||||
func (g *SecureRandomKeyGenerator) Fill(data []byte) {
|
||||
func (g *SecureRandomKeyGenerator) FillKeyBlock(data []byte) {
|
||||
io.ReadFull(rand.Reader, data)
|
||||
}
|
||||
|
||||
|
@ -27,7 +27,7 @@ func (g *SecureRandomKeyGenerator) MangleIndex() uint32 {
|
|||
// BorlandRandKeyGenerator Generates random numbers with its value as current seed
|
||||
type BorlandRandKeyGenerator uint32
|
||||
|
||||
func (g *BorlandRandKeyGenerator) Fill(data []byte) {
|
||||
func (g *BorlandRandKeyGenerator) FillKeyBlock(data []byte) {
|
||||
var output uint16
|
||||
seed := uint32(*g)
|
||||
for i := 0; i < len(data); i += 2 {
|
||||
|
@ -47,10 +47,33 @@ func (g *BorlandRandKeyGenerator) MangleIndex() uint32 {
|
|||
return mangleIndex
|
||||
}
|
||||
|
||||
// BorlandRandByteKeyGenerator Generates random numbers with its value as current seed
|
||||
type BorlandRandByteKeyGenerator uint32
|
||||
|
||||
func (g *BorlandRandByteKeyGenerator) FillKeyBlock(data []byte) {
|
||||
var output uint16
|
||||
seed := uint32(*g)
|
||||
for i := 0; i < len(data); i++ {
|
||||
seed, output = BorlandRand(seed)
|
||||
data[i] = uint8(output)
|
||||
}
|
||||
*g = BorlandRandByteKeyGenerator(seed)
|
||||
}
|
||||
|
||||
func (g *BorlandRandByteKeyGenerator) MangleIndex() uint32 {
|
||||
var output uint16
|
||||
seed := uint32(*g)
|
||||
seed, output = BorlandRand(seed)
|
||||
mangleIndex := uint32(output & 0x7)
|
||||
*g = BorlandRandByteKeyGenerator(seed)
|
||||
|
||||
return mangleIndex
|
||||
}
|
||||
|
||||
// ZeroKeyGenerator Always outputs zero
|
||||
type ZeroKeyGenerator struct{}
|
||||
|
||||
func (g *ZeroKeyGenerator) Fill(data []byte) {
|
||||
func (g *ZeroKeyGenerator) FillKeyBlock(data []byte) {
|
||||
clear(data)
|
||||
}
|
||||
|
||||
|
@ -63,8 +86,8 @@ type mangleIndexGeneratorWrapper struct {
|
|||
mangleIndex uint32
|
||||
}
|
||||
|
||||
func (w *mangleIndexGeneratorWrapper) Fill(data []byte) {
|
||||
w.generator.Fill(data)
|
||||
func (w *mangleIndexGeneratorWrapper) FillKeyBlock(data []byte) {
|
||||
w.generator.FillKeyBlock(data)
|
||||
}
|
||||
|
||||
func (w *mangleIndexGeneratorWrapper) MangleIndex() uint32 {
|
||||
|
@ -83,8 +106,8 @@ type mangleIndexOffsetGeneratorWrapper struct {
|
|||
offset uint32
|
||||
}
|
||||
|
||||
func (w *mangleIndexOffsetGeneratorWrapper) Fill(data []byte) {
|
||||
w.generator.Fill(data)
|
||||
func (w *mangleIndexOffsetGeneratorWrapper) FillKeyBlock(data []byte) {
|
||||
w.generator.FillKeyBlock(data)
|
||||
}
|
||||
|
||||
func (w *mangleIndexOffsetGeneratorWrapper) MangleIndex() uint32 {
|
||||
|
|
36
encryption/javarand.go
Normal file
36
encryption/javarand.go
Normal file
|
@ -0,0 +1,36 @@
|
|||
package encryption
|
||||
|
||||
// JavaRandMultiplier 25214903917
|
||||
const JavaRandMultiplier uint64 = 0x5DEECE66D
|
||||
|
||||
const JavaRandAddend uint64 = 11
|
||||
|
||||
const JavaRandOutputShift = 48 - 16
|
||||
const JavaRandOutputMask uint64 = 0xFFFF
|
||||
|
||||
const JavaRandModulus = (1 << 48) - 1
|
||||
|
||||
// JavaRandMultiplierInverse Calculated using math.ModularMultiplicativeInverseBits(uint64(JavaRandMultiplier), 48)
|
||||
const JavaRandMultiplierInverse uint64 = 0xdfe05bcb1365
|
||||
|
||||
// JavaRand Borland java.util.Random
|
||||
func JavaRand(seed uint64) (newSeed uint64, output uint16) {
|
||||
newSeed = JavaRandNextSeed(seed)
|
||||
return newSeed, JavaRandOutput(newSeed)
|
||||
}
|
||||
|
||||
func JavaRandOutput(seed uint64) uint16 {
|
||||
return uint16(seed >> JavaRandOutputShift)
|
||||
}
|
||||
|
||||
func JavaRandOutputBits(seed uint64, bits int) uint64 {
|
||||
return seed >> (48 - bits)
|
||||
}
|
||||
|
||||
func JavaRandNextSeed(seed uint64) uint64 {
|
||||
return (seed*JavaRandMultiplier + JavaRandAddend) & JavaRandModulus
|
||||
}
|
||||
|
||||
func JavaRandPreviousSeed(seed uint64) uint64 {
|
||||
return ((seed - JavaRandAddend) * JavaRandMultiplierInverse) & JavaRandModulus
|
||||
}
|
|
@ -6,6 +6,9 @@ type KeyMaterial struct {
|
|||
OuterKeyOffset OuterMangleKeyOffset
|
||||
DeviceKey *MangleKeyData
|
||||
AlternateKeyTable *MangleKeyTable
|
||||
|
||||
// CRC method to calculate CRC. If none is set, default is used.
|
||||
CRC func(data []byte) uint32
|
||||
}
|
||||
|
||||
func NewFlashKeyMaterial(generator KeyGenerator) KeyMaterial {
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
package encryption
|
||||
|
||||
import "encoding/binary"
|
||||
|
||||
// BorlandRandMultiplier 22695477
|
||||
const BorlandRandMultiplier = 0x015A4E35
|
||||
|
||||
// BorlandRand Borland C++ rand()
|
||||
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, output = BorlandRand(seed)
|
||||
dst[i] = src[i] ^ byte(output)
|
||||
}
|
||||
return seed
|
||||
}
|
||||
|
||||
func BorlandRandXORInPlace(data []byte, seed uint32) (newSeed uint32) {
|
||||
return BorlandRandXORBytes(data, data, seed)
|
||||
}
|
||||
|
||||
func BorlandRandXORUint32(value, seed uint32) (newValue, newSeed uint32) {
|
||||
var buf [4]byte
|
||||
binary.LittleEndian.PutUint32(buf[:], value)
|
||||
newSeed = BorlandRandXORInPlace(buf[:], seed)
|
||||
return binary.LittleEndian.Uint32(buf[:]), newSeed
|
||||
}
|
38
math/modular.go
Normal file
38
math/modular.go
Normal file
|
@ -0,0 +1,38 @@
|
|||
package math
|
||||
|
||||
func ModularMultiplicativeInverse(number, modulo uint64) uint64 {
|
||||
for x := uint64(1); x < modulo && x != 0; x++ {
|
||||
if ((number%modulo)*(x%modulo))%modulo == 1 {
|
||||
return x
|
||||
}
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func ModularMultiplicativeInverseBits(number uint64, bits int) uint64 {
|
||||
modulo := (uint64(1) << bits) - 1
|
||||
for x := uint64(1); x < modulo && x != 0; x++ {
|
||||
if ((number&modulo)*(x&modulo))&modulo == 1 {
|
||||
return x
|
||||
}
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func ModularMultiplicativeInverseFixed[T ~int32 | ~uint32 | ~int64 | ~uint64](number T) T {
|
||||
var modulo T
|
||||
modulo--
|
||||
|
||||
var one T
|
||||
one++
|
||||
|
||||
for x := one; x < modulo && x != 0; x++ {
|
||||
if number*x == 1 {
|
||||
return x
|
||||
}
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
Loading…
Reference in a new issue