Feature: QMC Decoder v2 #23

Merged
ix64 merged 7 commits from feature/qmc-v2 into master 2021-12-13 20:09:17 +00:00
11 changed files with 543 additions and 0 deletions
Showing only changes of commit 1552a667f6 - Show all commits

111
algo/qmc/key_dec.go Normal file
View File

@ -0,0 +1,111 @@
package qmc
import (
"encoding/base64"
"errors"
"math"
"golang.org/x/crypto/tea"
)
func simpleMakeKey(salt byte, length int) []byte {
keyBuf := make([]byte, length)
for i := 0; i < length; i++ {
tmp := math.Tan(float64(salt) + float64(i)*0.1)
keyBuf[i] = byte(math.Abs(tmp) * 100.0)
}
return keyBuf
}
func DecryptKey(rawKey []byte) ([]byte, error) {
rawKeyDec := make([]byte, base64.StdEncoding.DecodedLen(len(rawKey)))
_, err := base64.StdEncoding.Decode(rawKeyDec, rawKey)
if err != nil {
return nil, err
}
simpleKey := simpleMakeKey(106, 8)
teaKey := make([]byte, 16)
for i := 0; i < 8; i++ {
teaKey[i<<1] = simpleKey[i]
teaKey[i<<1+1] = rawKeyDec[i]
}
rs, err := decryptTencentTea(rawKeyDec[8:], teaKey)
if err != nil {
return nil, err
}
return append(rawKeyDec[:8], rs...), nil
}
func decryptTencentTea(inBuf []byte, key []byte) ([]byte, error) {
const saltLen = 2
const zeroLen = 7
if len(inBuf)%8 != 0 {
return nil, errors.New("inBuf size not a multiple of the block size")
}
if len(inBuf) < 16 {
return nil, errors.New("inBuf size too small")
}
blk, err := tea.NewCipherWithRounds(key, 32)
if err != nil {
return nil, err
}
destBuf := make([]byte, 8)
blk.Decrypt(destBuf, inBuf)
padLen := int(destBuf[0] & 0x7)
outLen := len(inBuf) - 1 - padLen - saltLen - zeroLen
if padLen+saltLen != 8 {
return nil, errors.New("invalid pad len")
}
out := make([]byte, outLen)
ivPrev := make([]byte, 8)
ivCur := inBuf[:8]
inBufPos := 8
destIdx := 1 + padLen
cryptBlock := func() {
ivPrev = ivCur
ivCur = inBuf[inBufPos : inBufPos+8]
xor8Bytes(destBuf, destBuf, inBuf[inBufPos:inBufPos+8])
blk.Decrypt(destBuf, destBuf)
inBufPos += 8
destIdx = 0
}
for i := 1; i <= saltLen; {
if destIdx < 8 {
destIdx++
i++
} else if destIdx == 8 {
cryptBlock()
}
}
outPos := 0
for outPos < outLen {
if destIdx < 8 {
out[outPos] = destBuf[destIdx] ^ ivPrev[destIdx]
destIdx++
outPos++
} else if destIdx == 8 {
cryptBlock()
}
}
for i := 1; i <= zeroLen; i++ {
if destBuf[destIdx] != ivPrev[destIdx] {
return nil, errors.New("zero check failed")
}
}
return out, nil
}
func xor8Bytes(dst, a, b []byte) {
for i := 0; i < 8; i++ {
dst[i] = a[i] ^ b[i]
}
}

52
algo/qmc/key_dec_test.go Normal file
View File

@ -0,0 +1,52 @@
package qmc
import (
"os"
"reflect"
"testing"
)
func TestSimpleMakeKey(t *testing.T) {
expect := []byte{0x69, 0x56, 0x46, 0x38, 0x2b, 0x20, 0x15, 0x0b}
t.Run("106,8", func(t *testing.T) {
if got := simpleMakeKey(106, 8); !reflect.DeepEqual(got, expect) {
t.Errorf("simpleMakeKey() = %v, want %v", got, expect)
}
})
}
func TestDecryptKey(t *testing.T) {
rc4Raw, err := os.ReadFile("./testdata/rc4_key_raw.bin")
if err != nil {
t.Error(err)
}
rc4Dec, err := os.ReadFile("./testdata/rc4_key.bin")
if err != nil {
t.Error(err)
}
tests := []struct {
name string
rawKey []byte
want []byte
wantErr bool
}{
{
"512",
rc4Raw,
rc4Dec,
false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := DecryptKey(tt.rawKey)
if (err != nil) != tt.wantErr {
t.Errorf("DecryptKey() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("DecryptKey() got = %v..., want %v...", string(got[:32]), string(tt.want[:32]))
}
})
}
}

125
algo/qmc/qmc_512.go Normal file
View File

@ -0,0 +1,125 @@
package qmc
import (
"encoding/binary"
"errors"
"io"
"strconv"
"strings"
)
type Mflac0Decoder struct {
r io.ReadSeeker
audioLen int
decodedKey []byte
rc4 *rc4Cipher
offset int
rawMetaExtra1 int
rawMetaExtra2 int
}
func (d *Mflac0Decoder) Read(p []byte) (int, error) {
n := len(p)
if d.audioLen-d.offset <= 0 {
return 0, io.EOF
} else if d.audioLen-d.offset < n {
n = d.audioLen - d.offset
}
m, err := d.r.Read(p[:n])
if m == 0 {
return 0, err
}
d.rc4.Process(p[:m], d.offset)
d.offset += m
return m, err
}
func NewMflac0Decoder(r io.ReadSeeker) (*Mflac0Decoder, error) {
d := &Mflac0Decoder{r: r}
if err := d.searchKey(); err != nil {
return nil, err
}
if len(d.decodedKey) > 300 {
var err error
d.rc4, err = NewRC4Cipher(d.decodedKey)
if err != nil {
return nil, err
}
} else {
panic("not implement") //todo: impl
}
_, err := d.r.Seek(0, io.SeekStart)
if err != nil {
return nil, err
}
return d, nil
}
func (d *Mflac0Decoder) searchKey() error {
if _, err := d.r.Seek(-4, io.SeekEnd); err != nil {
return err
}
buf, err := io.ReadAll(io.LimitReader(d.r, 4))
if err != nil {
return err
}
if string(buf) == "QTag" {
if err := d.readRawMetaQTag(); err != nil {
return err
}
} // todo: ...
return nil
}
func (d *Mflac0Decoder) readRawMetaQTag() error {
// get raw meta data len
if _, err := d.r.Seek(-8, io.SeekEnd); err != nil {
return err
}
buf, err := io.ReadAll(io.LimitReader(d.r, 4))
if err != nil {
return err
}
rawMetaLen := int64(binary.BigEndian.Uint32(buf))
// read raw meta data
audioLen, err := d.r.Seek(-(8 + rawMetaLen), io.SeekEnd)
if err != nil {
return err
}
d.audioLen = int(audioLen)
rawMetaData, err := io.ReadAll(io.LimitReader(d.r, rawMetaLen))
if err != nil {
return err
}
items := strings.Split(string(rawMetaData), ",")
if len(items) != 3 {
return errors.New("invalid raw meta data")
}
{
d.decodedKey, err = DecryptKey([]byte(items[0]))
if err != nil {
return err
}
}
d.rawMetaExtra1, err = strconv.Atoi(items[1])
if err != nil {
return err
}
d.rawMetaExtra2, err = strconv.Atoi(items[2])
if err != nil {
return err
}
return nil
}

48
algo/qmc/qmc_512_test.go Normal file
View File

@ -0,0 +1,48 @@
package qmc
import (
"bytes"
"io"
"os"
"reflect"
"testing"
)
func loadTestDataRC4Mflac0() ([]byte, []byte, error) {
encBody, err := os.ReadFile("./testdata/rc4_raw.bin")
if err != nil {
return nil, nil, err
}
encSuffix, err := os.ReadFile("./testdata/rc4_suffix_mflac0.bin")
if err != nil {
return nil, nil, err
}
target, err := os.ReadFile("./testdata/rc4_target.bin")
if err != nil {
return nil, nil, err
}
return bytes.Join([][]byte{encBody, encSuffix}, nil), target, nil
}
func TestMflac0Decoder_Read(t *testing.T) {
raw, target, err := loadTestDataRC4Mflac0()
if err != nil {
t.Fatal(err)
}
t.Run("mflac0-file", func(t *testing.T) {
d, err := NewMflac0Decoder(bytes.NewReader(raw))
if err != nil {
t.Error(err)
}
buf := make([]byte, len(target))
if _, err := io.ReadFull(d, buf); err != nil {
t.Errorf("read bytes from decoder error = %v", err)
return
}
if !reflect.DeepEqual(buf, target) {
t.Errorf("Process() got = %v, want %v", buf[:32], target[:32])
}
})
}

133
algo/qmc/rc4.go Normal file
View File

@ -0,0 +1,133 @@
package qmc
import (
"errors"
)
// A rc4Cipher is an instance of RC4 using a particular key.
type rc4Cipher struct {
box []byte
key []byte
hash uint32
boxTmp []byte
}
// NewRC4Cipher creates and returns a new rc4Cipher. The key argument should be the
// RC4 key, at least 1 byte and at most 256 bytes.
func NewRC4Cipher(key []byte) (*rc4Cipher, error) {
n := len(key)
if n == 0 {
return nil, errors.New("crypto/rc4: invalid key size")
}
var c = rc4Cipher{key: key}
c.box = make([]byte, n)
c.boxTmp = make([]byte, n)
for i := 0; i < n; i++ {
c.box[i] = byte(i)
}
var j = 0
for i := 0; i < n; i++ {
j = (j + int(c.box[i]) + int(key[i%n])) % n
c.box[i], c.box[j] = c.box[j], c.box[i]
}
c.getHashBase()
return &c, nil
}
func (c *rc4Cipher) getHashBase() {
c.hash = 1
for i := 0; i < len(c.key); i++ {
v := uint32(c.key[i])
if v == 0 {
continue
}
nextHash := c.hash * v
if nextHash == 0 || nextHash <= c.hash {
break
}
c.hash = nextHash
}
}
const rc4SegmentSize = 5120
func (c *rc4Cipher) Process(src []byte, offset int) {
toProcess := len(src)
processed := 0
markProcess := func(p int) (finished bool) {
offset += p
toProcess -= p
processed += p
return toProcess == 0
}
if offset < 128 {
blockSize := toProcess
if blockSize > 128-offset {
blockSize = 128 - offset
}
c.encFirstSegment(src[:blockSize], offset)
if markProcess(blockSize) {
return
}
}
if offset%rc4SegmentSize != 0 {
blockSize := toProcess
if blockSize > rc4SegmentSize-offset%rc4SegmentSize {
blockSize = rc4SegmentSize - offset%rc4SegmentSize
}
k := src[processed : processed+blockSize]
c.encASegment(k, offset)
if markProcess(blockSize) {
return
}
}
for toProcess > rc4SegmentSize {
c.encASegment(src[processed:processed+rc4SegmentSize], offset)
markProcess(rc4SegmentSize)
}
if toProcess > 0 {
c.encASegment(src[processed:], offset)
}
}
func (c *rc4Cipher) encFirstSegment(buf []byte, offset int) {
n := len(c.box)
for i := 0; i < len(buf); i++ {
idx1 := offset + i
segmentID := int(c.key[idx1%n])
idx2 := int(float64(c.hash) / float64((idx1+1)*segmentID) * 100.0)
buf[i] ^= c.key[idx2%n]
}
}
func (c *rc4Cipher) encASegment(buf []byte, offset int) {
n := len(c.box)
copy(c.boxTmp, c.box)
segmentID := (offset / rc4SegmentSize) & 0x1FF
if n <= segmentID {
return
}
idx2 := int64(float64(c.hash) /
float64((offset/rc4SegmentSize+1)*int(c.key[segmentID])) *
100.0)
skipLen := int((idx2 & 0x1FF) + int64(offset%rc4SegmentSize))
j, k := 0, 0
for i := -skipLen; i < len(buf); i++ {
j = (j + 1) % n
k = (int(c.boxTmp[j]) + k) % n
c.boxTmp[j], c.boxTmp[k] = c.boxTmp[k], c.boxTmp[j]
if i >= 0 {
buf[i] ^= c.boxTmp[int(c.boxTmp[j])+int(c.boxTmp[k])%n]
}
}
}

72
algo/qmc/rc4_test.go Normal file
View File

@ -0,0 +1,72 @@
package qmc
import (
"os"
"reflect"
"testing"
)
func loadTestData() (*rc4Cipher, []byte, []byte, error) {
key, err := os.ReadFile("./testdata/rc4_key.bin")
if err != nil {
return nil, nil, nil, err
}
raw, err := os.ReadFile("./testdata/rc4_raw.bin")
if err != nil {
return nil, nil, nil, err
}
target, err := os.ReadFile("./testdata/rc4_target.bin")
if err != nil {
return nil, nil, nil, err
}
c, err := NewRC4Cipher(key)
if err != nil {
return nil, nil, nil, err
}
return c, raw, target, nil
}
func Test_rc4Cipher_Process(t *testing.T) {
c, raw, target, err := loadTestData()
if err != nil {
t.Errorf("load testing data failed: %s", err)
}
t.Run("overall", func(t *testing.T) {
c.Process(raw, 0)
if !reflect.DeepEqual(raw, target) {
t.Error("overall")
}
})
}
func Test_rc4Cipher_encFirstSegment(t *testing.T) {
c, raw, target, err := loadTestData()
if err != nil {
t.Errorf("load testing data failed: %s", err)
}
t.Run("first-block(0~128)", func(t *testing.T) {
c.Process(raw[:128], 0)
if !reflect.DeepEqual(raw[:128], target[:128]) {
t.Error("first-block(0~128)")
}
})
}
func Test_rc4Cipher_encASegment(t *testing.T) {
c, raw, target, err := loadTestData()
if err != nil {
t.Errorf("load testing data failed: %s", err)
}
t.Run("align-block(128~5120)", func(t *testing.T) {
c.Process(raw[128:5120], 128)
if !reflect.DeepEqual(raw[128:5120], target[128:5120]) {
t.Error("align-block(128~5120)")
}
})
t.Run("simple-block(5120~10240)", func(t *testing.T) {
c.Process(raw[5120:10240], 5120)
if !reflect.DeepEqual(raw[5120:10240], target[5120:10240]) {
t.Error("align-block(128~5120)")
}
})
}

1
algo/qmc/testdata/rc4_key.bin vendored Normal file
View File

@ -0,0 +1 @@
dRzX3p5ZYqAlp7lLSs9Zr0rw1iEZy23bB670x4ch2w97x14Zwpk1UXbKU4C2sOS7uZ0NB5QM7ve9GnSrr2JHxP74hVNONwVV77CdOOVb807317KvtI5Yd6h08d0c5W88rdV46C235YGDjUSZj5314YTzy0b6vgh4102P7E273r911Nl464XV83Hr00rkAHkk791iMGSJH95GztN28u2Nv5s9Xx38V69o4a8aIXxbx0g1EM0623OEtbtO9zsqCJfj6MhU7T8iVS6M3q19xhq6707E6r7wzPO6Yp4BwBmgg4F95Lfl0vyF7YO6699tb5LMnr7iFx29o98hoh3O3Rd8h9Juu8P1wG7vdnO5YtRlykhUluYQblNn7XwjBJ53HAyKVraWN5dG7pv7OMl1s0RykPh0p23qfYzAAMkZ1M422pEd07TA9OCKD1iybYxWH06xj6A8mzmcnYGT9P1a5Ytg2EF5LG3IknL2r3AUz99Y751au6Cr401mfAWK68WyEBe5

1
algo/qmc/testdata/rc4_key_raw.bin vendored Normal file
View File

@ -0,0 +1 @@
ZFJ6WDNwNVrjEJZB1o6QjkQV2ZbHSw/2Eb00q1+4z9SVWYyFWO1PcSQrJ5326ubLklmk2ab3AEyIKNUu8DFoAoAc9dpzpTmc+pdkBHjM/bW2jWx+dCyC8vMTHE+DHwaK14UEEGW47ZXMDi7PRCQ2Jpm/oXVdHTIlyrc+bRmKfMith0L2lFQ+nW8CCjV6ao5ydwkZhhNOmRdrCDcUXSJH9PveYwra9/wAmGKWSs9nemuMWKnbjp1PkcxNQexicirVTlLX7PVgRyFyzNyUXgu+R2S4WTmLwjd8UsOyW/dc2mEoYt+vY2lq1X4hFBtcQGOAZDeC+mxrN0EcW8tjS6P4TjOjiOKNMxIfMGSWkSKL3H7z5K7nR1AThW20H2bP/LcpsdaL0uZ/js1wFGpdIfFx9rnLC78itL0WwDleIqp9TBMX/NwakGgIPIbjBwfgyD8d8XKYuLEscIH0ZGdjsadB5XjybgdE3ppfeFEcQiqpnodlTaQRm3KDIF9ATClP0mTl8XlsSojsZ468xseS1Ib2iinx/0SkK3UtJDwp8DH3/+ELisgXd69Bf0pve7wbrQzzMUs9/Ogvvo6ULsIkQfApJ8cSegDYklzGXiLNH7hZYnXDLLSNejD7NvQouULSmGsBbGzhZ5If0NP/6AhSbpzqWLDlabTDgeWWnFeZpBnlK6SMxo+YFFk1Y0XLKsd69+jj

BIN
algo/qmc/testdata/rc4_raw.bin vendored Normal file

Binary file not shown.

BIN
algo/qmc/testdata/rc4_suffix_mflac0.bin vendored Normal file

Binary file not shown.

BIN
algo/qmc/testdata/rc4_target.bin vendored Normal file

Binary file not shown.