CRC improvements, faster and efficient borland seed bruteforce, cleanup borland/java seeds, custom key material CRC

This commit is contained in:
DataHoarder 2023-10-09 14:59:43 +02:00
parent 2391151938
commit 3a25cc15bc
Signed by: DataHoarder
SSH key fingerprint: SHA256:OLTRf6Fl87G52SiR7sWLGNzlJt4WOX+tfI2yxo0z7xk
13 changed files with 514 additions and 166 deletions

View file

@ -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 {

View file

@ -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.

View file

@ -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)
}
}

View file

@ -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

View file

@ -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
View 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
}

View file

@ -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
}

View 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\"")
}
}

View file

@ -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
View 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
}

View file

@ -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 {

View file

@ -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
View 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
}