一个QQ音乐源无损音质下载软件逆向浅要分析

VSole2022-08-10 14:45:58

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

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

样本APK:QMD 1.7.2.apk https://wwb.lanzoub.com/iX5Lr08oc3be

第一步 反射大师脱壳

对于类似于这种神奇的软件作者总会加个壳加加固保护一下源代码,我有种直觉这玩意应该也是加了固的,果然打开zip文件一看:

libjiagu.so赫然在目。

得,直接打开反射大师先来扒一层皮看看能不能看到里面。

反射大师脱壳过程不再赘述,直接导出内存dex即可。

第二步 JEB静态分析

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

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

我们关注一下上图的/api/Download请求。

这个包是用来获取QQ音乐的文件实际下载地址,我们来看看这个请求:

一个迷之数据,一个朴实无华的http请求,再无其他。

只有下面的一个http://ws.stream.qqmusic.qq.com/RS01003zLSuX07Z0LB.flac

接口关联他。那么为了得到这个qqmusic的源数据,我们要批量下载这些音乐就需要逆向出这个迷之数据到底是什么东西,我们如何生成它。

 复制代码 隐藏代码
接口表
获取音乐的高解析度下载地址
/api/MusicLink/link

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

直接可以看到这个搜索结果了,很好,看来不需要再去找其他的dex文件了。

tab一下看看。

关注以下函数:

 复制代码 隐藏代码
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函数,这个加密看起来像是一个接口签名防止被抓包调用接口,提高逆向成本。

直接抓包看数据,可以看出来其实就是几个字符串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实现一下这个加密函数:

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

第三步 测试接口访问

于是为了下载flac,我直接写了一个python脚本。

项目地址

https://github.com/QiuChenly/python_down_jaychou
stringqq音乐
本作品采用《CC 协议》,转载必须注明作者和本文链接
逆向一个QMD QQ音乐源下载软件这个Apk主要是用来下载QQ音乐的无损数字音频文件,我为了把我iMac上的mp3音质音乐替换为flac或者HiRes无损,一个个去网上找文件。反射大师脱壳过程不再赘述,直接导出内存dex即可。
crawlergo是一个使用chrome headless模式进行URL收集的浏览器爬虫。它对整个网页的关键位置与DOM渲染阶段进行HOOK,自动进行表单填充并提交,配合智能的JS事件触发,尽可能的收集网站暴露出的入口。内置URL去重模块,过滤掉了大量伪静态URL,对于大型网站仍保持较快的解析与抓取速度,最后得到高质量的请求结果集合。调研1.
冰蝎流量免杀初探
2022-12-20 09:11:06
本文仅针对冰蝎流量改造进行初步探讨,熟悉一下整个流程,真的要绕流量设备,估计还需要其他的技巧。
可想而知,如果 Spring 城门失火,Java 必定遭殃。根据官方文档,Spring Cloud Function 是基于 Spring Boot 的函数计算框架,它可以:通过函数促进业务逻辑的实现。
Java Agent到内存马
2021-12-14 14:52:22
前言今天看到一篇文章,写的是关于JAVA Agent相关的资料(附1),里面提到了Java Agent的两种实现方法:实现premain方法,在JVM启动前加载实现agentmain方法,在JVM启动后attach加载因为最近流行破解CobaltStrike不再直接使用反编译打包源码了,而是使用JAVA Agent进行提前字节码修改。
本文来自“白帽子社区”知识星球作者:想看云飞却没风01环境搭建composer create-project topthink/think=6.0.x-dev thinkphp-v6.0. 在 ThinkPHP5.x 的POP链中,入口都是 think\process\pipes\Windows 类,通过该类触发任意类的__toString 方法。
对这段时间做的一次攻防演练做一个记录,这次给我们分了三个目标,一个目标是甲方单位自己的一个自建系统,其余两个是甲方的下级单位的系统。开始之前觉得不好做,因为攻防演练跟HW有些差别,HW可以不限制攻击手法,可以从上游供应链,社工、钓鱼多种角度出发来挖掘漏洞。这次攻防演练给我们三个目标、两个web系统、一个app,可以利用的点非常少,不可以攻击其他的系统,只能搞这几个目标,要不是这次运气好真的就拉垮了
由于测试过程中很多系统我们能接触到的只有一个登陆界面,所以要充分挖掘漏洞,进行深入操作登录 注册万能密码绕过登录存在 SQL 注入的情况下,有可能使用万能密码直接登录admin' or '1'='1'--. 有超级多登录口 SQL 注入存在 SQL 注入的话直接注出账密有些可以构造用户注入登录,比如熊海 cms 后台登录处存在 sql 注入$login=$_POST['login'];
Redis系列漏洞总结
2023-06-19 10:29:18
前言Redis的未授权漏洞一直都是一个很火的漏洞,最近看许多前辈的文章自己复现后,根据自己的实践再次总结一下,为日后复习方便回顾。Redis简介redis是一个key-value存储系统。和Memcached类似,它支持存储的value类型相对更多,包括string、list、set、zset和hash。这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,redis支持各种不同方式的排序。
burp0_data = {"name": username, "pw": password, "repw": password, "email": email, "submit": ''}
VSole
网络安全专家