从某小电影下载工具破解入手逆向实操

移动安全 1年前 (2023) admin
488 0 0


从某小电影下载工具破解入手逆向实操

本文章中所有内容仅供学习交流使用,不用于其他任何目的,若有侵权,请联系作者立即删除!

本次我们做一个相对入门级的教程,通过对一个安卓端下载工具(ZTQgYjggOGIgZTggYmQgYmQgZTUgYjcgYTUgZTUgODUgYjcgZTcgYWUgYjE=)的分析和逆向,把常见的简单分析方案都过一下,修改方案也都尝试一下,包括重打包、Hook等,那么废话不多说,现在就开始。

目标APP是一个下载工具,支持短视频、磁链等,但是对于非会员的下载有次数限制,非会员每天只能下载5次,本次我们的目的就是绕过非会员下载次数限制。

使用工具

  1. 1. jadx

  2. 2. frida

  3. 3. MT管理器

  4. 4. IDA

jadx分析VIP逻辑

拿到APK后第一步就应该先jadx打开看一下有木有加固,然后再去分析,如果加固了就会涉及到脱壳修复,当前样本并未加固?,故可以直接进行分析。

1. 搜索关键字
常见的关键字如Vip、getVip,搜索后可发现如下信息
从某小电影下载工具破解入手逆向实操
搜索结果
2. 分析getVip方法
可以看到,搜索到的方法中有部分为Native方法,那么类似这种App就存在两种修改方案,一个是修改Native,一个是修改Java(好像是废话),从难度来讲肯定还是Java层更容易修改和调试,所以我们就先从Java层入手,先去看上面的第二个方法
从某小电影下载工具破解入手逆向实操
  1. 分析结果
    可以看到,此方法的调用位置很有限,仅存在一处调用,方法名为h(一处调用了三次)

    3. 分析h方法

  2. 可以看到,h方法中对于getVip的调用为Info.getVip,info为y2.a().e()的返回值,可以查看一下此返回值是什么
  3. 从某小电影下载工具破解入手逆向实操

  4. 目标方法
  5. 4. Hook e方法,打印info的结果

    从某小电影下载工具破解入手逆向实操

  6. UserInfo
let C4836y2 = Java.use("g.d0.a.n.g.y2");C4836y2["e"].implementation = function () {  console.log('e is called');  let ret = this.e();  let mjson = Gson.$new().toJson(ret);  console.log('e ret value is ' + mjson2);  return ret2;};
从某小电影下载工具破解入手逆向实操
返回信息
  1. 5. 分析到现在,就可以开始思考修改思路了

修改思路

从上面的简单分析就可以看出,应用对于VIP的判断是基于info中的vip字段,则可以通过修改info中的vip字段来实现绕过vip,也可以通过修改非会员每日下载次数来实现下载次数绕过,两种方式都可以实现无限制下载的目的。
所以就可以从以下几个角度来进行修改:
  1. 1. 那么我们就可以通过修改UserInfo对象或其返回值,使其vip信息为11-13中的一个

  2. 2. 修改h方法的smail,修改第一个if 可以修改第一个if判断,将info.getVip==11修改为info.getVip==0如此就可以实现非会员走会员逻辑(0为非会员,数值可以通过Hook对应方法发现)

  3. 3. 向APP注入frida-gadget,同上一个方案一样修改UserInfo实现获取VIP

  4. 4. 修改todayCount的值,使其一直为0 从代码中可以看到,客户端会判断todayCount的值是否小于count,那么可以认为count就是非会员每日下载限额,可以试count变大或者todayCount变小也可以绕过下载次数限制,而不需要修改会员身份。

  5. 5. 修改h方法调用位置,将其参数中的count改为更大的值 例如在h方法被调用时,将其实参固定为9999

以上几个思路,每个思路都可以通过重打包或者Hook来实现,下面我们就开始实践一下,主要针对前三种方法,其他两种有兴趣的小伙伴可自行尝试

Hook(第一种方案)

  1. 1. Hook e方法,打印返回值

let C4836y2 = Java.use("g.d0.a.n.g.y2");C4836y2["e"].implementation = function () {  console.log('e is called');  let ret = this.e();  let mjson = Gson.$new().toJson(ret);  console.log('e ret value is ' + mjson2);  return ret2;};
2. 修改返回值
  1. 修改UserInfo的返回值,步骤就是先将UserInfo序列化为JSON,修改后再用Gson反序列化为UserInfo对象,frida脚本如下
let Gson = Java.use("com.google.gson.Gson");let Cy2 = Java.use("g.d0.a.n.g.y2");Cy2["e"].implementation = function () {  console.log('e is called');  let ret = this.e();  let mjson = Gson.$new().toJson(ret);  let mjson2 = JSON.parse(mjson);  mjson2["vip"] = 11;  mjson2["vipTime"] = "2999-01-01 08:00:00";  mjson2["vipType"] = "永久会员";  let ret2 = Gson.$new().fromJson(JSON.stringify(mjson2),Java.use("xxx.UserInfo").class)  console.log('e ret value is ' + ret2);  return ret2;};
3. 验证结果
  1. 执行后可以发现,已经是永久会员了

  2. 从某小电影下载工具破解入手逆向实操

  3. 验证结果
    为了防止仅仅是修改了显示实际仍然是非会员,所以需要再尝试一下解析次数,也可以发现非会员的解析次数限制也木有了

重打包(第二种方案)

  1. 1. MT管理器解包找到对应的方法

  2. 2. 修改smail并保存,在h方法中看到getVip调用的位置,将其中的一个0xb或0xc修改为0x0即可

    从某小电影下载工具破解入手逆向实操

  3. 修改校验值
  4. 3. 反编译为java看看修改效果

  5. 4. 重新打包安装

  6. 5. 验证修改结果

  7. 6. 另外需要注意,if中有两个判断,一个是userinfo 不为空,一个是是否为VIP,此修改方法仅改了VIP状态,所以还是需要登录的,如果想不登录使用可以尝试修改第一个判断,eqz修改为nez

不过目前Apk重打包目前存在一些问题,APP内置了一个native-security,会校验客户端的完整性,若被篡改,则会强制要求更新,绕过方法可以是配合httpcanary之类的抓包工具将api/security/upload这个接口的请求丢掉就可以继续走下去,但是此方法需要额外操作且对设备有一定的要求,所以不太够通用,我们继续看第三种方案。

重打包(第三种方案)

若想实现在非Root设备上直接使用,方案1和方案2都存在一定的问题,所以我们现在再来看下方案三:通过注入frida-gadget.so来实现在非Root设备上运行,此方法和方案二一样需要过掉APP的完整性验证方法,不过执行起来会相对简单一些(最起码不需要去改汇编?,使用时也无需多余操作)

完整性校验分析(IDA)

1. 在进行第二种方案时我们发现,App具备完整性验证,重新打包APP后会出现APP退出并跳转至更新网址的情况,所以我们需要继续分析其校验逻辑
2. 通过抓包,我们可以看到,是收到了api/security/upload的返回值之后才弹出的错误提示,那么可以先去搜索一下请求的url、更新网址或者host
3. 经过搜索,在jadx中并未搜索到对应的host或url信息,那么我们就可以怀疑是native层进行的校验
  1. 从某小电影下载工具破解入手逆向实操

  2. upload搜索结果
4. 现在我们可以直接找一下security相关的代码,也可以直接Hook System.loadLibrary看看是加载到哪个so时APP退出,也可以直接去lib看一下有没有相关的so,于是就可以找到libnative-security.so 
let System = Java.use('java.lang.System');let Runtime = Java.use('java.lang.Runtime');let SystemLoad_2 = System.loadLibrary.overload('java.lang.String');let VMStack = Java.use('dalvik.system.VMStack');SystemLoad_2.implementation = function(library) {  console.log("Loading library =====> " + library);  try {    let loaded = Runtime.getRuntime().loadLibrary0(VMStack.getCallingClassLoader(), library);    return loaded;  } catch(ex) {    console.log(ex);  }};
  1. 从某小电影下载工具破解入手逆向实操

  2. 加载so
5. 之后找到so的加载位置在SecurityJNI.Java中
6. 然后我们发现其中有一个方法叫做nativeInit(Context context);,追踪这个方法的调用,然后就看到了这个方法,通过Hook调试等方法确定了是nativeInit之后APP才退出的,于是锁定此方法,继续分析
public void f(Context context, g.d0.c.d.a call) {  this.b = context;  if (this.a) {    if (call != null) {      call.a();      return;    }    return;  }  this.a = true;  SecurityJNI.nativeInit(context);  g.d0.c.g.a.a("init finish");  e.g();  if (call != null) {    call.a();  }}
7. 分析一下native-security中的nativeInit,IDA伪代码如下
char *v15; v19 = *(_QWORD *)(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40);__android_log_print(6, "SecurityJNI", "checkApk %s", "before");std::string::basic_string<decltype(nullptr)>();v15 = (char *)sub_F63F0((__int64)v18);v2 = (char *)ping(a1, v15);if ( ((unsigned __int8)v2 & 1) != 0 ){  v3 = "adverts.indabai.com";  v2 = v18;}
8. 一眼看过来,sub_F63F0应该是我们的目标,之前我们已经分析出了upload接口是校验完整性的,并且在nativeInit中并未发现对应的更新地址,且ping函数的参数中有sub_F63F0返回的V15,有可能就是接收的更新地址
9. 直接Hook sub_F63F0
let sub_F63F0 = get_func_addr("libnative-security.so",0xF63F0);console.log('sub_F63F0=' + sub_F63F0);Interceptor.attach(sub_F63F0, { onEnter: function (args) { console.log('onEnter'); console.log('sub_F63F0 args[a1]=' + hexdump(args[0])); }, onLeave: function (retval) { console.log('sub_F63F0 onLeave' + hexdump(ret); }});
10. 可以看到如下信息
  1. 从某小电影下载工具破解入手逆向实操

  2. 打印返回值

    11. 修改返回值

let sub_F63F0 = get_func_addr("libnative-security.so",0xF63F0);console.log('sub_F63F0=' + sub_F63F0);Interceptor.attach(sub_F63F0, {  onEnter: function (args) {    console.log('onEnter');    console.log('sub_F63F0 args[a1]=' + hexdump(args[0]));  },  onLeave: function (retval) {    let ret = Memory.readCString(retval);    if(ret.indexOf("isCrack") != -1){      console.log('===============');      ret = ret.replace(true,false)    }    console.log('sub_F63F0 onLeave' + ret);    Memory.writeUtf8String(retval,ret)  }});
12. 正常运行
  1. 从某小电影下载工具破解入手逆向实操

  2. 修改后的返回值

开始重打包

分析完完整性验证方法后,我们就可以正式开始修改工作了
  1. 1. 下载frida-gadget.so,选一个适合自己的版本,Releases · frida/frida (github.com),若出现在部分设备上可用部分设备不可用的情况,则可以更换一下gadget版本,目前遇到过14版本的gadget在Android12不可用的情况

  2. 2. 编写脚本,增加js脚本将方案1的UserInfo修改脚本和绕过完整性校验脚本写入文件(eg:命名为DDD.js)

    3. 编写配置文件(配置文件名称需要和frida-gadget.so的名字相同,如frida-gadget.so的配置文件名称应该为frida-gadget.config.so)

{  "interaction": {    "type": "script",    "path": "/sdcard/Download/Script/DDD.js",    "on_change":"reload"  }}
  1. 4. 将frida-gadget.so和frida-gadget.config.so放入lib目录对应文件夹,64位的就放arm64_v8a,可以用MT管理器操作,直接增加进去即可

  2. 5. 挑选合适的时机加载frida-gadget.so,加载的早了,可能security.so还没加载,会导致Hook失败,加载的晚了,nativeInit执行完了,也会失败,所以我们选择在security.so load完成之后立即load frida-gadget.so

    6. MT管理器打开apk找到,在 System.loadLibrary("native-security");对应的smail代码下增加如下内容

const-string v0, "frida-gadget" invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V
  1. 7. 将DDD.js文件放到配置文件中配置的位置

  2. 8. 打包、签名、安装、给读写外置存储权限、启动

  3. 9. 进入APP后注册、登录,若显示为永久会员则说明已成功

最后

本帖仅用于交流技术随机选择APP进行分析,但是这个工具箱我个人用下来还非常好用,可以下载几十种类型的链接,价格也很便宜,二十来块就可以买永久会员,建议大家去支持一下?。


历史文章
互联网黑灰产业链解读
Android 13源码中添加系统服务
过某加固Frida检测
AOSP编译保姆级教程(二)
从某小电影下载工具破解入手逆向实操从某小电影下载工具破解入手逆向实操
随手分享、点赞、在看是对我们最大的支持从某小电影下载工具破解入手逆向实操

原文始发于微信公众号(移动安全星球):从某小电影下载工具破解入手逆向实操

版权声明:admin 发表于 2023年2月21日 上午8:01。
转载请注明:从某小电影下载工具破解入手逆向实操 | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...