From 1552a667f6635fa674374df1b94205260d035e0b Mon Sep 17 00:00:00 2001 From: MengYX Date: Mon, 13 Dec 2021 20:42:07 +0800 Subject: [PATCH] init QMCv2: RC4 (512 Byte Key) --- algo/qmc/key_dec.go | 111 ++++++++++++++++++++ algo/qmc/key_dec_test.go | 52 +++++++++ algo/qmc/qmc_512.go | 125 ++++++++++++++++++++++ algo/qmc/qmc_512_test.go | 48 +++++++++ algo/qmc/rc4.go | 133 ++++++++++++++++++++++++ algo/qmc/rc4_test.go | 72 +++++++++++++ algo/qmc/testdata/rc4_key.bin | 1 + algo/qmc/testdata/rc4_key_raw.bin | 1 + algo/qmc/testdata/rc4_raw.bin | Bin 0 -> 20480 bytes algo/qmc/testdata/rc4_suffix_mflac0.bin | Bin 0 -> 724 bytes algo/qmc/testdata/rc4_target.bin | Bin 0 -> 20480 bytes 11 files changed, 543 insertions(+) create mode 100644 algo/qmc/key_dec.go create mode 100644 algo/qmc/key_dec_test.go create mode 100644 algo/qmc/qmc_512.go create mode 100644 algo/qmc/qmc_512_test.go create mode 100644 algo/qmc/rc4.go create mode 100644 algo/qmc/rc4_test.go create mode 100644 algo/qmc/testdata/rc4_key.bin create mode 100644 algo/qmc/testdata/rc4_key_raw.bin create mode 100644 algo/qmc/testdata/rc4_raw.bin create mode 100644 algo/qmc/testdata/rc4_suffix_mflac0.bin create mode 100644 algo/qmc/testdata/rc4_target.bin diff --git a/algo/qmc/key_dec.go b/algo/qmc/key_dec.go new file mode 100644 index 0000000..2ed86f1 --- /dev/null +++ b/algo/qmc/key_dec.go @@ -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] + } +} diff --git a/algo/qmc/key_dec_test.go b/algo/qmc/key_dec_test.go new file mode 100644 index 0000000..de419b0 --- /dev/null +++ b/algo/qmc/key_dec_test.go @@ -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])) + } + }) + } +} diff --git a/algo/qmc/qmc_512.go b/algo/qmc/qmc_512.go new file mode 100644 index 0000000..e0eff66 --- /dev/null +++ b/algo/qmc/qmc_512.go @@ -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 +} diff --git a/algo/qmc/qmc_512_test.go b/algo/qmc/qmc_512_test.go new file mode 100644 index 0000000..218e13f --- /dev/null +++ b/algo/qmc/qmc_512_test.go @@ -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]) + } + }) +} diff --git a/algo/qmc/rc4.go b/algo/qmc/rc4.go new file mode 100644 index 0000000..f886d17 --- /dev/null +++ b/algo/qmc/rc4.go @@ -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] + } + } +} diff --git a/algo/qmc/rc4_test.go b/algo/qmc/rc4_test.go new file mode 100644 index 0000000..780ec8f --- /dev/null +++ b/algo/qmc/rc4_test.go @@ -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)") + } + }) +} diff --git a/algo/qmc/testdata/rc4_key.bin b/algo/qmc/testdata/rc4_key.bin new file mode 100644 index 0000000..3bd0914 --- /dev/null +++ b/algo/qmc/testdata/rc4_key.bin @@ -0,0 +1 @@ +dRzX3p5ZYqAlp7lLSs9Zr0rw1iEZy23bB670x4ch2w97x14Zwpk1UXbKU4C2sOS7uZ0NB5QM7ve9GnSrr2JHxP74hVNONwVV77CdOOVb807317KvtI5Yd6h08d0c5W88rdV46C235YGDjUSZj5314YTzy0b6vgh4102P7E273r911Nl464XV83Hr00rkAHkk791iMGSJH95GztN28u2Nv5s9Xx38V69o4a8aIXxbx0g1EM0623OEtbtO9zsqCJfj6MhU7T8iVS6M3q19xhq6707E6r7wzPO6Yp4BwBmgg4F95Lfl0vyF7YO6699tb5LMnr7iFx29o98hoh3O3Rd8h9Juu8P1wG7vdnO5YtRlykhUluYQblNn7XwjBJ53HAyKVraWN5dG7pv7OMl1s0RykPh0p23qfYzAAMkZ1M422pEd07TA9OCKD1iybYxWH06xj6A8mzmcnYGT9P1a5Ytg2EF5LG3IknL2r3AUz99Y751au6Cr401mfAWK68WyEBe5 \ No newline at end of file diff --git a/algo/qmc/testdata/rc4_key_raw.bin b/algo/qmc/testdata/rc4_key_raw.bin new file mode 100644 index 0000000..39c8a3b --- /dev/null +++ b/algo/qmc/testdata/rc4_key_raw.bin @@ -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 \ No newline at end of file diff --git a/algo/qmc/testdata/rc4_raw.bin b/algo/qmc/testdata/rc4_raw.bin new file mode 100644 index 0000000000000000000000000000000000000000..af0e06e8da35e6acb3f8a4e505ec26e3f72a607a GIT binary patch literal 20480 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

t6rsLzvALz3hzq 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 O+%NO<^XG36X3al!=I|~6 literal 0 HcmV?d00001 diff --git a/algo/qmc/testdata/rc4_target.bin b/algo/qmc/testdata/rc4_target.bin new file mode 100644 index 0000000000000000000000000000000000000000..0d16010a547e97832cc7f28a11299d70a05b7224 GIT binary patch literal 20480 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 SMflzvrVptB2ZuiRy8Z{2x*%u( literal 0 HcmV?d00001