吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 16395|回复: 178
收起左侧

[Android 原创] 一个QQ音乐源无损音质下载软件逆向浅要分析

    [复制链接]
QiuChenly 发表于 2022-7-30 13:58
本帖最后由 QiuChenly 于 2022-8-12 00:08 编辑

逆向一个QMD QQ音乐源下载软件

这个Apk主要是用来下载QQ音乐的无损数字音频文件,我为了把我iMac上的mp3音质音乐替换为flac或者HiRes无损,一个个去网上找文件。偶然间在网上发现了这个app,有些好奇怎么实现的,于是做本篇分析文章。
样本APK:QMD 1.7.2.apk
https://wwb.lanzoub.com/iX5Lr08oc3be

写在前面 - Bugs修复 - 2022.08.12

  1. 已修复写入config文件时Windows提示GBK编码无法解码UTF-8编码的错误
  2. 第一次使用时缓存路径自动初始化为执行当前代码所在的路径,直接删除config.json会重新初始化
    3. 修复了更换下载目录时目录后缀不带‘/’导致目录访问报错的bug
    4. 如果有其他问题可以github提issues。因为代码我本地测试并不严谨,本地测试使用正常但是可能在其他机器上会发生一些奇怪的现象,请回帖或github提issues。

第一步 反射大师脱壳

对于类似于这种神奇的软件作者总会加个壳加加固保护一下源代码,我有种直觉这玩意应该也是加了固的,果然打开zip文件一看:
16589910239841.png
libjiagu.so赫然在目。。。得,直接打开反射大师先来扒一层皮看看能不能看到里面。
反射大师脱壳过程不再赘述,直接导出内存dex即可。

第二步 JEB静态分析

可喜可贺,2021年初还只有Jeb 3.24,坐了一年牢出来发现竟然有4.x的版本更新了,好,很有精神!

首先我们打开app开始下载高解析度音频,看看加密如何。
16589915329732.jpg

我们关注一下上图的/api/Download请求。
这个包是用来获取QQ音乐的文件实际下载地址,我们来看看这个请求:
一个迷之数据,一个朴实无华的http请求,再无其他。
只有下面的一个http://ws.stream.qqmusic.qq.com/RS01003zLSuX07Z0LB.flac
接口关联他。那么为了得到这个qqmusic的源数据,我们要批量下载这些音乐就需要逆向出这个迷之数据到底是什么东西,我们如何生成它。

接口表

获取音乐的高解析度下载地址
/api/MusicLink/link

那么话不多说,jeb直接打开导出的dex看函数,搜索这个字符串。
16589918835312.jpg

直接可以看到这个搜索结果了,很好,看来不需要再去找其他的dex文件了。
tab一下看看。
16589919873070.jpg
关注以下函数:

public String getMusicLink(String arg4) {
    String v4 = EncryptAndDecrypt.encryptText(arg4);
    String v4_1 = new HttpManager("http://8.136.185.193/api/MusicLink/link").postDataWithResult("\"" + v4 + "\"");
    Logger.e(v4_1, new Object[0]);
    return v4_1;
}

v4=arg4,arg4则是一个String不足为惧,先看看这个写的非常漂亮的Encryption函数:

    public static String encryptText(String arg1) {
        return EncryptAndDecrypt.encryptText(arg1, Cookie.getQQ());
    }

    public static String encryptText(String arg4, String arg5) {
        if(!TextUtils.isEmpty(arg4) && !TextUtils.isEmpty(arg5)) {
            int v1 = 0;
            StringBuilder v5 = new StringBuilder(EncryptAndDecrypt.encryptDES(arg4, "QMD" + arg5.substring(0, 8)));
            Random v4 = new Random(((long)Calendar.getInstance().get(5)));
            int v0 = v4.nextInt(4) + 1;
            while(v1 < v0) {
                v5.insert(v4.nextInt(v5.length()), "-");
                ++v1;
            }

            return v5.toString();
        }

        return "";
    }

他调用了encryptText(String arg4, String arg5)函数,那么我们就可知道arg5是作为密码而存在的,arg4则是String的原文,那么Cookie.getQQ()这个代码则就相当的可疑。
跳转一下看看:

public class Cookie {
    private static String Mkey;
    private static String QQ;
    public static String getMkey() { return Cookie.Mkey; }
    public static String getQQ() { return Cookie.QQ; }
    public static void setCookie(String arg0, String arg1) {
        Cookie.Mkey = arg0;
        Cookie.QQ = arg1; 
    }
}

一个静态实体类,直接看setCookie的交叉引用看看是从decryptAndSetCookie函数设置密码的:

    public static boolean decryptAndSetCookie(String arg5) {
        String v5 = arg5.replace("-", "").replace("|", "");
        if(v5.length() >= 10 && (v5.contains("%"))) {
            String[] v5_1 = v5.split("%");
            String v0 = v5_1[0];
            String v5_2 = EncryptAndDecrypt.decryptDES(v5_1[1], v0.substring(0, 8));
            if(v5_2.length() < 8) {
                v5_2 = v5_2 + "QMD";
            }

            Cookie.setCookie(EncryptAndDecrypt.decryptDES(v0, v5_2.substring(0, 8)), v5_2);//v5_2就是密码,由arg5参数分解而来。
            return true;
        }

        return false;
    }

继续跟踪交叉引用:

    public boolean getCookie() {
        String v0 = new HttpManager("http://8.136.185.193/api/Cookies").postDataWithResult(new Gson().toJson(SystemInfoUtil.getDeviceInfo()));
        return TextUtils.isEmpty(v0) ? false : EncryptAndDecrypt.decryptAndSetCookie(v0);
    }

找到了,看来是从这个接口获取的数据,但是这个接口居然是POST提交,那么我们就有必要看看这个提交的数据SystemInfoUtil.getDeviceInfo()到底是什么东西:

    public static final DeviceInfo getDeviceInfo() {
        DeviceInfo v10 = new DeviceInfo(SystemInfoUtil.getUID(), SystemInfoUtil.getSystemModel(), SystemInfoUtil.getDeviceBrand(), SystemInfoUtil.getAppVersionName(), SystemInfoUtil.getSystemVersion(), SystemInfoUtil.getAppVersionCode() + "", null, 0x40, null);
        v10.setIp(EncryptAndDecrypt.encryptText(v10.getUid() + v10.getDeviceModel() + v10.getDeviceBrand() + v10.getSystemVersion() + v10.getAppVersion() + v10.getVersionCode(), "F*ckYou!"));//密码是F*ckYou!,emmmm....
        return v10;
    }

获取了设备的一些信息,然后调用了一个setIp函数,这个加密看起来像是一个接口签名防止被抓包调用接口,提高逆向成本。
16589927417533.jpg
直接抓包看数据,可以看出来其实就是几个字符串appand一起后加上密码des,下面我们就开始先用python实现一下。

不过在此之前我们还要看一下EncryptAndDecrypt.encryptText函数,里面是如何处理的:

    public static String encryptText(String arg4, String arg5) {
        if(!TextUtils.isEmpty(arg4) && !TextUtils.isEmpty(arg5)) {
            int v1 = 0;
            StringBuilder v5 = new StringBuilder(EncryptAndDecrypt.encryptDES(arg4, ("QMD" + arg5).substring(0, 8)));
            Random v4 = new Random(((long)Calendar.getInstance().get(5)));
            int v0 = v4.nextInt(4) + 1;
            while(v1 < v0) {
                v5.insert(v4.nextInt(v5.length()), "-");
                ++v1;
            }

            return v5.toString();
        }

        return "";
    }

清晰地看到又调用了EncryptAndDecrypt.encryptDES函数,还加上了“QMD”作为密码前置字符串,我们继续跟踪:

    public static String encryptDES(String arg5, String arg6) {
        if(arg5 != null && arg6 != null) {
            try {
                Cipher v0 = Cipher.getInstance("DES/CBC/PKCS5Padding");
                v0.init(1, new SecretKeySpec(arg6.getBytes(), "DES"), new IvParameterSpec(arg6.getBytes()));
                return Base64.encodeToString(v0.doFinal(arg5.getBytes()), 0).trim();
            }
            catch(Exception v5) {
                return v5.getMessage();
            }
        }

        return null;
    }

到这里我们已经很清晰了,密码作为iv,加密方式为des/cbc/pkcs5padding方式填充结果,那么我们用python实现一下这个加密函数:
16591253982291.jpg
16591257371138.jpg

到这里我们就用python写出了加密算法,接下来就可以用这个算法生成数据去请求http数据了。

第三步 测试接口访问

16591588597095.jpg

16591604097922.jpg

写在最后 - 总结

于是为了下载flac,我直接写了一个python脚本。
项目地址:点我访问项目地址


免费评分

参与人数 50威望 +2 吾爱币 +142 热心值 +47 收起 理由
codezhao + 1 热心回复!
anydaicn + 1 + 1 谢谢@Thanks!
dc201 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
懵懂的挖掘机 + 1 + 1 我很赞同!
yuxs520 + 1 + 1 牛啊
junjia215 + 1 + 1 用心讨论,共获提升!
kkskman + 1 我很赞同!
yyff666 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
pengpai2022 + 1 + 1 我很赞同!
Liu12152251 + 1 + 1 我很赞同!
右手写放弃 + 1 + 1 我很赞同!
handhzc + 1 谢谢@Thanks!
dalongwapj + 1 我很赞同!
落花时节又逢君 + 1 + 1 我很赞同!
st790211 + 1 + 1 谢谢@Thanks!
lyn2018 + 1 + 1 用心讨论,共获提升!
2333y + 1 谢谢@Thanks!
abadc_666 + 1 + 1 用心讨论,共获提升!
levelhan + 1 + 1 热心回复!
Breathless2677 + 1 + 1 谢谢@Thanks!
18895411830 + 1 + 1 热心回复!
gaosld + 1 + 1 谢谢@Thanks!
alade1139322744 + 1 + 1 我很赞同!
Lucrebon + 1 + 1 谢谢@Thanks!
62070F + 1 + 1 用心讨论,共获提升!
jiazhibin1314 + 1 + 1 很喜欢
project0ne + 1 谢谢@Thanks!
cscxqe + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
hiplease + 1 + 1 谢谢@Thanks!
wu92460607 + 1 + 1 谢谢@Thanks!
djsz + 1 我很赞同!
andacn + 1 + 1 我很赞同!
viafromwwf + 1 + 1 谢谢@Thanks!
zmy98 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
coco007 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
exiahan + 1 + 1 用心讨论,共获提升!
superFlyMan + 1 热心回复!
朝闻道夕死可矣 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
带俗 + 1 + 1 用心讨论,共获提升!
qtfreet00 + 2 + 100 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
oecho + 1 我很赞同!
cww18862144459 + 1 + 1 牛啊牛啊
HLG + 1 + 1 我很赞同!现在这样搞得垄断才有市场啊
YZXHZY + 1 + 1 谢谢@Thanks!
2218486635 + 1 + 1 我很赞同!
774258 + 1 鼓励转贴优秀软件安全工具和文档!
qq63 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
lmk021 + 1 + 1 谢谢@Thanks!
首席鉴淫师 + 2 + 1 我很赞同!
星星之夜 + 1 热心回复!

查看全部评分

本帖被以下淘专辑推荐:

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

kickbirds 发表于 2022-8-2 12:23
貌似只看最后一句就可以了、
小白网络com 发表于 2022-8-10 16:50
==== Welcome to QQMusic Digit High Quality Music Download Center ====
Traceback (most recent call last):
  File "/workspace/python_down_jaychou-main/main.py", line 509, in <module>
    _main(searchKey)
  File "/workspace/python_down_jaychou-main/main.py", line 371, in _main
    os.mkdir(f"{download_home}")
FileNotFoundError: [Errno 2] No such file or directory: '/Volumes/data/music/'
小白网络com 发表于 2022-8-10 16:32
Traceback (most recent call last):
  File "/workspace/python_down_jaychou-main/main.py", line 3, in <module>
    import requests
ModuleNotFoundError: No module named 'requests'
侃遍天下无二人 发表于 2022-8-2 12:25
这些垃圾公司搞得我对听歌都没兴趣了
soloeco 发表于 2022-8-2 13:39
侃遍天下无二人 发表于 2022-8-2 12:25
这些垃圾公司搞得我对听歌都没兴趣了

你这个跟帖图有点年代了吧
lmk021 发表于 2022-8-2 16:27
有些想下到车载里,就挺不方便的
MuSTAR 发表于 2022-8-2 16:57
支持大佬!
emptynullnill 发表于 2022-8-2 17:42
kickbirds 发表于 2022-8-2 12:23
貌似只看最后一句就可以了、

哈哈哈,早应该看到你这句话的。我从头看到尾才突然发现后面的github地址
Avalon0021 发表于 2022-8-2 18:37
厉害!必须支持这种技术贴
Lirak 发表于 2022-8-2 18:38
省流:项目地址
https://github.com/QiuChenly/python_down_jaychou
zhangsos12 发表于 2022-8-2 18:39
多谢大佬,看了半天看到github了
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-24 11:59

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表