mirror of
https://git.um-react.app/um/cli.git
synced 2025-11-28 03:33:02 +00:00
feat: mmkv 加密数据库解析
This commit is contained in:
@@ -1,17 +1,20 @@
|
||||
package qmc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
"git.unlock-music.dev/awalol/go-mmkv"
|
||||
"github.com/samber/lo"
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/exp/slices"
|
||||
"golang.org/x/text/unicode/norm"
|
||||
"unlock-music.dev/mmkv"
|
||||
)
|
||||
|
||||
var streamKeyVault mmkv.Vault
|
||||
@@ -80,6 +83,45 @@ func readKeyFromMMKV(file string, logger *zap.Logger) ([]byte, error) {
|
||||
return deriveKey(buf)
|
||||
}
|
||||
|
||||
func readKeyFromMMKVCustom(d *Decoder) ([]byte, error) {
|
||||
logger := d.logger
|
||||
filePath, fileName := filepath.Split(VaultPath)
|
||||
|
||||
if streamKeyVault == nil {
|
||||
mgr, err := mmkv.NewManager(filepath.Dir(filePath))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("init mmkv manager: %w", err)
|
||||
}
|
||||
|
||||
streamKeyVault, err = mgr.OpenVaultCrypto(fileName, VaultKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("open mmkv vault: %w", err)
|
||||
}
|
||||
|
||||
logger.Debug("mmkv vault opened", zap.Strings("keys", streamKeyVault.Keys()))
|
||||
}
|
||||
|
||||
// 获取mid即数据库键值
|
||||
_, err := d.raw.Seek(-128, io.SeekEnd)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get mid error: %w", err)
|
||||
}
|
||||
mid, err := io.ReadAll(io.LimitReader(d.raw, 64)) // 取64字节确保完全取完
|
||||
mid = bytes.ReplaceAll(mid, []byte{0x00}, []byte{}) // clean NUL
|
||||
mid = bytes.Trim(mid, "\0000") // maybe a little stupid
|
||||
|
||||
// 从数据库获取eKey
|
||||
eKey, err := streamKeyVault.GetBytes(string(mid))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get eKey error: %w", err)
|
||||
}
|
||||
n, err := base64.StdEncoding.Decode(eKey, eKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("base64 error: %w", err)
|
||||
}
|
||||
return deriveKeyV1(eKey[:n])
|
||||
}
|
||||
|
||||
func getRelativeMMKVDir(file string) (string, error) {
|
||||
mmkvDir := filepath.Join(filepath.Dir(file), "../mmkv")
|
||||
if _, err := os.Stat(mmkvDir); err != nil {
|
||||
|
||||
@@ -16,6 +16,9 @@ import (
|
||||
"unlock-music.dev/cli/internal/sniff"
|
||||
)
|
||||
|
||||
var VaultPath = ""
|
||||
var VaultKey = ""
|
||||
|
||||
type Decoder struct {
|
||||
raw io.ReadSeeker // raw is the original file reader
|
||||
params *common.DecoderParams
|
||||
@@ -140,11 +143,32 @@ func (d *Decoder) searchKey() (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
switch string(suffixBuf) {
|
||||
switch string(bytes.ReplaceAll(suffixBuf, []byte{0x00}, []byte{})) {
|
||||
case "QTag":
|
||||
return d.readRawMetaQTag()
|
||||
case "STag":
|
||||
return errors.New("qmc: file with 'STag' suffix doesn't contains media key")
|
||||
case "cex":
|
||||
d.decodedKey, err = readKeyFromMMKVCustom(d)
|
||||
if err == nil {
|
||||
suffix := []byte{0x63, 0x65, 0x78, 0x00} // cex
|
||||
for i := 0; i <= 3; i++ {
|
||||
// 末尾的信息数据每192字节出现一次,故只要循环判断末尾不为musicex时即为歌曲数据
|
||||
musicexLen, err := d.raw.Seek(int64(-(192*i)-4), io.SeekEnd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get musicexLen error: %w", err)
|
||||
}
|
||||
buf, err := io.ReadAll(io.LimitReader(d.raw, 4))
|
||||
if err != nil {
|
||||
return fmt.Errorf("get musicex error: %w", err)
|
||||
}
|
||||
if !bytes.Equal(buf, suffix) {
|
||||
d.audioLen = int(musicexLen) + 4
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return err
|
||||
default:
|
||||
size := binary.LittleEndian.Uint32(suffixBuf)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user