From fe5403cdb75bcf43e1e2a2778fd5b15e9d67a720 Mon Sep 17 00:00:00 2001 From: Emmm Monster <58943012+emmmx@users.noreply.github.com> Date: Sat, 26 Dec 2020 00:49:28 +0800 Subject: [PATCH] Add QMC Decoder --- algo/qmc/consts.go | 72 ++++++++++++++ algo/qmc/mask_key256.go | 211 ++++++++++++++++++++++++++++++++++++++++ algo/qmc/qmc.go | 97 ++++++++++++++++++ 3 files changed, 380 insertions(+) create mode 100644 algo/qmc/consts.go create mode 100644 algo/qmc/mask_key256.go create mode 100644 algo/qmc/qmc.go diff --git a/algo/qmc/consts.go b/algo/qmc/consts.go new file mode 100644 index 0000000..1de93e5 --- /dev/null +++ b/algo/qmc/consts.go @@ -0,0 +1,72 @@ +package qmc + +var oggPublicHeader1 = []byte{ + 0x4f, 0x67, 0x67, 0x53, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, + 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x01, 0x1e, 0x01, 0x76, 0x6f, 0x72, + 0x62, 0x69, 0x73, 0x00, 0x00, 0x00, 0x00, 0x02, 0x44, 0xac, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xee, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb8, 0x01, 0x4f, 0x67, 0x67, 0x53, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xff, 0xff} + +var oggPublicHeader2 = []byte{ + 0x03, 0x76, 0x6f, 0x72, 0x62, 0x69, 0x73, 0x2c, 0x00, 0x00, 0x00, 0x58, 0x69, 0x70, 0x68, 0x2e, + 0x4f, 0x72, 0x67, 0x20, 0x6c, 0x69, 0x62, 0x56, 0x6f, 0x72, 0x62, 0x69, 0x73, 0x20, 0x49, 0x20, + 0x32, 0x30, 0x31, 0x35, 0x30, 0x31, 0x30, 0x35, 0x20, 0x28, 0xe2, 0x9b, 0x84, 0xe2, 0x9b, 0x84, + 0xe2, 0x9b, 0x84, 0xe2, 0x9b, 0x84, 0x29, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x54, + 0x49, 0x54, 0x4c, 0x45, 0x3d} + +var oggPublicConfidence1 = []uint{ + 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, + 0, 0, 9, 9, 9, 9, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 6, 3, 3, 3, 3, 6, 6, 6, 6, + 3, 3, 3, 3, 6, 6, 6, 6, 6, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 9, 9, 9, 9, + 0, 0, 0, 0} + +var oggPublicConfidence2 = []uint{ + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 0, 1, 3, 3, 0, 1, 3, 3, 3, + 3, 3, 3, 3, 3} + +var ( + defaultKey256Mask44 = []byte{ + 0xde, 0x51, 0xfa, 0xc3, 0x4a, 0xd6, 0xca, 0x90, + 0x7e, 0x67, 0x5e, 0xf7, 0xd5, 0x52, 0x84, 0xd8, + 0x47, 0x95, 0xbb, 0xa1, 0xaa, 0xc6, 0x66, 0x23, + 0x92, 0x62, 0xf3, 0x74, 0xa1, 0x9f, 0xf4, 0xa0, + 0x1d, 0x3f, 0x5b, 0xf0, 0x13, 0x0e, 0x09, 0x3d, + 0xf9, 0xbc, 0x00, 0x11} + headerFlac = []byte{'f', 'L', 'a', 'C'} + headerOgg = []byte{'O', 'g', 'g', 'S'} +) +var key256MappingAll [][]int //[idx256][idx128]idx44 +var key256Mapping128to44 map[int]int + +func init() { + { // init all mapping + key256MappingAll = make([][]int, 256) + for i := 0; i < 128; i++ { + realIdx := (i*i + 27) % 256 + if key256MappingAll[realIdx] == nil { + key256MappingAll[realIdx] = []int{i} + } else { + key256MappingAll[realIdx] = append(key256MappingAll[realIdx], i) + } + } + } + { // init + key256Mapping128to44 = make(map[int]int, 128) + idx44 := 0 + for _, all128 := range key256MappingAll { + if all128 != nil { + for _, _i128 := range all128 { + key256Mapping128to44[_i128] = idx44 + } + idx44++ + } + } + } + +} diff --git a/algo/qmc/mask_key256.go b/algo/qmc/mask_key256.go new file mode 100644 index 0000000..bb50393 --- /dev/null +++ b/algo/qmc/mask_key256.go @@ -0,0 +1,211 @@ +package qmc + +import ( + "bytes" + "errors" + "github.com/umlock-music/cli/internal/logging" + "go.uber.org/zap" +) + +var ( + ErrFailToMatchMask = errors.New("can not match at least one key") + ErrTestDataLength = errors.New("invalid length of test file") + ErrMaskLength128 = errors.New("incorrect mask length 128") + ErrMaskLength44 = errors.New("incorrect mask length 44") + ErrMaskDecode = errors.New("decode mask-128 to mask-58 failed") + ErrDetectFlacMask = errors.New("can not detect mflac mask") + ErrDetectMggMask = errors.New("can not detect mgg mask") +) + +type Key256Mask struct { + matrix []byte // Mask 128 +} + +func NewKey256FromMask128(mask128 []byte) (*Key256Mask, error) { + if len(mask128) != 128 { + return nil, ErrMaskLength128 + } + q := &Key256Mask{matrix: mask128} + return q, nil +} + +func NewKey256FromMask44(mask44 []byte) (*Key256Mask, error) { + mask128, err := convertKey256Mask44to128(mask44) + if err != nil { + return nil, err + } + q := &Key256Mask{matrix: mask128} + return q, nil +} + +func (q *Key256Mask) getMatrix44() (mask44 []byte, err error) { + if len(q.matrix) != 128 { + return nil, ErrMaskLength128 + } + matrix44 := make([]byte, 44) + idx44 := 0 + for _, it256 := range key256MappingAll { + if it256 != nil { + it256Len := len(it256) + for i := 1; i < it256Len; i++ { + if q.matrix[it256[0]] != q.matrix[it256[i]] { + return nil, ErrMaskDecode + } + } + q.matrix[idx44] = q.matrix[it256[0]] + idx44++ + } + } + return matrix44, nil +} + +func (q *Key256Mask) Decrypt(data []byte) []byte { + dst := make([]byte, len(data)) + index := -1 + maskIdx := -1 + for cur := 0; cur < len(data); cur++ { + index++ + maskIdx++ + if index == 0x8000 || (index > 0x8000 && (index+1)%0x8000 == 0) { + index++ + maskIdx++ + } + if maskIdx >= 128 { + maskIdx -= 128 + } + dst[cur] = data[cur] ^ q.matrix[maskIdx] + } + return dst +} + +func convertKey256Mask44to128(mask44 []byte) ([]byte, error) { + if len(mask44) != 44 { + return nil, ErrMaskLength44 + } + mask128 := make([]byte, 128) + idx44 := 0 + for _, it256 := range key256MappingAll { + if it256 != nil { + for _, idx128 := range it256 { + mask128[idx128] = mask44[idx44] + } + idx44++ + } + } + return mask128, nil +} + +func getDefaultMask() *Key256Mask { + y, _ := NewKey256FromMask44(defaultKey256Mask44) + return y +} + +func detectMflac256Mask(input []byte) (*Key256Mask, error) { + var q *Key256Mask + var rtErr = ErrDetectFlacMask + + lenData := len(input) + lenTest := 0x8000 + if lenData < 0x8000 { + lenTest = lenData + } + + for blockIdx := 0; blockIdx < lenTest; blockIdx += 128 { + var err error + q, err = NewKey256FromMask128(input[blockIdx : blockIdx+128]) + if err != nil { + continue + } + if bytes.Equal(headerFlac, q.Decrypt(input[:len(headerFlac)])) { + rtErr = nil + break + } + } + return q, rtErr +} + +func detectMgg256Mask(input []byte) (*Key256Mask, error) { + if len(input) < 0x100 { + return nil, ErrTestDataLength + } + + matrixConf := make([]map[uint8]uint, 44) //meaning: [idx58][value]confidence + for i := uint(0); i < 44; i++ { + matrixConf[i] = make(map[uint8]uint) + } + + page2size := input[0x54] ^ input[0xC] ^ oggPublicHeader1[0xC] + spHeader, spConf := generateOggFullHeader(int(page2size)) + lenTest := len(spHeader) + + for idx128 := 0; idx128 < lenTest; idx128++ { + confidence := spConf[idx128] + if confidence > 0 { + mask := input[idx128] ^ spHeader[idx128] + + idx44 := key256Mapping128to44[idx128%128] + if _, ok2 := matrixConf[idx44][mask]; ok2 { + matrixConf[idx44][mask] += confidence + } else { + matrixConf[idx44][mask] = confidence + } + } + } + + matrix := make([]uint8, 44) + var err error + for i := uint(0); i < 44; i++ { + matrix[i], err = decideMgg256MaskItemConf(matrixConf[i]) + if err != nil { + return nil, err + } + } + q, err := NewKey256FromMask44(matrix) + if err != nil { + return nil, err + } + if bytes.Equal(headerOgg, q.Decrypt(input[:len(headerOgg)])) { + return q, nil + } + return nil, ErrDetectMggMask +} + +func generateOggFullHeader(pageSize int) ([]byte, []uint) { + spec := make([]byte, pageSize+1) + + spec[0], spec[1], spec[pageSize] = uint8(pageSize), 0xFF, 0xFF + for i := 2; i < pageSize; i++ { + spec[i] = 0xFF + } + specConf := make([]uint, pageSize+1) + specConf[0], specConf[1], specConf[pageSize] = 6, 0, 0 + for i := 2; i < pageSize; i++ { + specConf[i] = 4 + } + allConf := append(oggPublicConfidence1, specConf...) + allConf = append(allConf, oggPublicConfidence2...) + + allHeader := bytes.Join( + [][]byte{oggPublicHeader1, spec, oggPublicHeader2}, + []byte{}, + ) + return allHeader, allConf +} + +func decideMgg256MaskItemConf(confidence map[uint8]uint) (uint8, error) { + lenConf := len(confidence) + if lenConf == 0 { + return 0xff, ErrFailToMatchMask + } else if lenConf > 1 { + logging.Log().Warn("there are 2 potential value for the mask", zap.Any("confidence", confidence)) + } + result := uint8(0) + conf := uint(0) + for idx, item := range confidence { + if item > conf { + result = idx + conf = item + } + } + return result, nil +} diff --git a/algo/qmc/qmc.go b/algo/qmc/qmc.go new file mode 100644 index 0000000..45da8a8 --- /dev/null +++ b/algo/qmc/qmc.go @@ -0,0 +1,97 @@ +package qmc + +import ( + "encoding/base64" + "encoding/binary" + "errors" + "github.com/umlock-music/cli/algo/common" + "github.com/umlock-music/cli/internal/logging" + "go.uber.org/zap" +) + +var ( + ErrQmcFileLength = errors.New("invalid qmc file length") + ErrQmcKeyDecodeFailed = errors.New("base64 decode qmc key failed") + ErrQmcKeyLength = errors.New("unexpected decoded qmc key length") +) + +type Decoder struct { + file []byte + maskDetector func(encodedData []byte) (*Key256Mask, error) + mask *Key256Mask + audioExt string + key []byte + audio []byte +} + +func NewDefaultDecoder(data []byte) *Decoder { + return &Decoder{file: data, mask: getDefaultMask()} +} + +func NewMflac256Decoder(data []byte) *Decoder { + return &Decoder{file: data, maskDetector: detectMflac256Mask, audioExt: "flac"} +} + +func NewMgg256Decoder(data []byte) *Decoder { + return &Decoder{file: data, maskDetector: detectMgg256Mask, audioExt: "ogg"} +} + +func (d *Decoder) Validate() bool { + if nil != d.mask { + return true + } + if nil != d.maskDetector { + if err := d.validateKey(); err != nil { + logging.Log().Error("detect file failed", zap.Error(err)) + return false + } + d.mask, _ = d.maskDetector(d.file) + } + return d.mask != nil +} + +func (d *Decoder) validateKey() error { + lenData := len(d.file) + if lenData < 4 { + return ErrQmcFileLength + } + + keyLen := binary.LittleEndian.Uint32(d.file[lenData-4:]) + if lenData < int(keyLen+4) { + return ErrQmcFileLength + } + var err error + d.key, err = base64.StdEncoding.DecodeString( + string(d.file[lenData-4-int(keyLen) : lenData-4])) + if err != nil { + return ErrQmcKeyDecodeFailed + } + + if len(d.key) != 272 { + return ErrQmcKeyLength + } + d.file = d.file[:lenData-4-int(keyLen)] + return nil + +} + +func (d *Decoder) Decode() error { + d.audio = d.mask.Decrypt(d.file) + return nil +} + +func (d Decoder) GetCoverImage() []byte { + return nil +} + +func (d Decoder) GetAudioData() []byte { + return d.audio +} + +func (d Decoder) GetAudioExt() string { + return d.audioExt +} + +func (d Decoder) GetMeta() common.Meta { + return nil +}