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

查看 140|回复 10
作者:谶焮   
从某小电影下载工具破解入手逆向实操
本文章中所有内容仅供学习交流使用,不用于其他任何目的,若有侵权,请联系作者立即删除!
本次我们做一个相对入门级的教程,通过对一个安卓端下载工具(ZTQgYjggOGIgZTggYmQgYmQgZTUgYjcgYTUgZTUgODUgYjcgZTcgYWUgYjE=)的分析和逆向,把常见的简单分析方案都过一下,修改方案也都尝试一下,包括重打包、Hook等,那么废话不多说,现在就开始。
目标APP是一个下载工具,支持短视频、磁链等,但是对于非会员的下载有次数限制,非会员每天只能下载5次,本次我们的目的就是绕过非会员下载次数限制。
使用工具
[ol]
  • jadx
  • frida
  • MT管理器
  • IDA
    [/ol]
    jadx分析VIP逻辑
    拿到APK后第一步就应该先jadx打开看一下有木有加固,然后再去分析,如果加固了就会涉及到脱壳修复,当前样本并未加固,故可以直接进行分析。
    [ol]

  • 搜索关键字
    常见的关键字如Vip、getVip,搜索后可发现如下信息


    1.png (138.18 KB, 下载次数: 0)
    下载附件
    搜索结果
    2023-3-1 13:33 上传

  • 分析getVip方法
    可以看到,搜索到的方法中有部分为Native方法,那么类似这种App就存在两种修改方案,一个是修改Native,一个是修改Java(好像是废话),从难度来讲肯定还是Java层更容易修改和调试,所以我们就先从Java层入手,先去看上面的第二个方法,getVip


    2.png (63.22 KB, 下载次数: 0)
    下载附件
    分析结果
    2023-3-1 13:33 上传

    可以看到,此方法的调用位置很有限,仅存在一处调用,方法名为h(一处调用了三次)

  • 分析h方法
    可以看到,h方法中对于getVip的调用为Info.getVip,info为y2.a().e()的返回值,可以查看一下此返回值是什么


    3.png (131.21 KB, 下载次数: 0)
    下载附件
    目标方法
    2023-3-1 13:33 上传

  • Hooke方法,打印info的结果如下


    4.png (37.77 KB, 下载次数: 0)
    下载附件
    UserInfo
    2023-3-1 13:33 上传

    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;
    };


    5.png (138.74 KB, 下载次数: 0)
    下载附件
    返回信息
    2023-3-1 13:33 上传

  • 分析到现在,就可以开始思考修改思路了
    [/ol]
    修改思路
    从上面的简单分析就可以看出,应用对于VIP的判断是基于info中的vip字段,则可以通过修改info中的vip字段来实现绕过vip,也可以通过修改非会员每日下载次数来实现下载次数绕过,两种方式都可以实现无限制下载的目的。
    所以就可以从以下几个角度来进行修改:
    [ol]

  • 那么我们就可以通过修改UserInfo对象或其返回值,使其vip信息为11-13中的一个

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

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

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

  • 修改h方法调用位置,将其参数中的count改为更大的值
    例如在h方法被调用时,将其实参固定为9999
    [/ol]
    以上几个思路,每个思路都可以通过重打包或者Hook来实现,下面我们就开始实践一下,主要针对前三种方法,其他两种有兴趣的小伙伴可自行尝试
    Hook(第一种方案)
    [ol]

  • 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;
    };

  • 修改返回值
    修改UserInfo的返回值,步骤就是先将UserInfo序列化为JSON,修改后再用Gson反序列化为UserInfo对象,frida脚本如下
    let Gson = Java.use("com.google.gson.Gson");
    let Cy2 = Java.use("g.d0.a.n.g.y2");
    y2["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;
    };

  • 验证结果
    执行后可以发现,已经是永久会员了


    6.png (66.54 KB, 下载次数: 0)
    下载附件
    验证结果
    2023-3-1 13:33 上传

    为了防止仅仅是修改了显示实际仍然是非会员,所以需要再尝试一下解析次数,也可以发现非会员的解析次数限制也木有了
    [/ol]
    重打包(第二种方案)
    [ol]
  • MT管理器解包找到对应的方法
  • 修改smail并保存,在h方法中看到getVip调用的位置,将其中的一个0xb或0xc修改为0x0即可


    7.png (122.6 KB, 下载次数: 0)
    下载附件
    修改校验值
    2023-3-1 13:33 上传
  • 反编译为java看看修改效果
  • 重新打包安装
  • 验证修改结果
  • 另外需要注意,if中有两个判断,一个是userinfo 不为空,一个是是否为VIP,此修改方法仅改了VIP状态,所以还是需要登录的,如果想不登录使用可以尝试修改第一个判断,eqz修改为nez
    [/ol]
    不过目前Apk重打包目前存在一些问题,APP内置了一个native-security,会校验客户端的完整性,若被篡改,则会强制要求更新,绕过方法可以是配合httpcanary之类的抓包工具将api/security/upload这个接口的请求丢掉就可以继续走下去,但是此方法需要额外操作且对设备有一定的要求,所以不太够通用,我们继续看第三种方案。
    重打包(第三种方案)
    若想实现在非Root设备上直接使用,方案1和方案2都存在一定的问题,所以我们现在再来看下方案三:通过注入frida-gadget.so来实现在非Root设备上运行,此方法和方案二一样需要过掉APP的完整性验证方法,不过执行起来会相对简单一些(最起码不需要去改汇编,使用时也无需多余操作)
    完整性校验分析(IDA)
    [ol]

  • 在进行第二种方案时我们发现,App具备完整性验证,重新打包APP后会出现APP退出并跳转至更新网址的情况,所以我们需要继续分析其校验逻辑

  • 通过抓包,我们可以看到,是收到了api/security/upload的返回值之后才弹出的错误提示,那么可以先去搜索一下请求的url、更新网址或者host

  • 经过搜索,在jadx中并未搜索到对应的host或url信息,那么我们就可以怀疑是native层进行的校验
    [/ol]


    8.png (57.81 KB, 下载次数: 0)
    下载附件
    upload搜索结果
    2023-3-1 13:33 上传

  • 现在我们可以直接找一下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);
    }
    };
    [/ol]


    9.png (172.71 KB, 下载次数: 0)
    下载附件
    加载so
    2023-3-1 13:33 上传

  • 之后找到so的加载位置在SecurityJNI.Java中

  • 然后我们发现其中有一个方法叫做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();
    }
    }

  • 分析一下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();
    v15 = (char *)sub_F63F0((__int64)v18);
    v2 = (char *)ping(a1, v15);
    if ( ((unsigned __int8)v2 & 1) != 0 )
    {
    v3 = "adverts.indabai.com";
    v2 = v18;
    }

  • 一眼看过来,sub_F63F0应该是我们的目标,之前我们已经分析出了upload接口是校验完整性的,并且在nativeInit中并未发现对应的更新地址,且ping函数的参数中有sub_F63F0返回的V15,有可能就是接收的更新地址

  • 直接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);
                   }
    });

  • 可以看到如下信息
    [/ol]


    10.png (716.72 KB, 下载次数: 0)
    下载附件
    打印返回值
    2023-3-1 13:33 上传

  • 修改返回值
    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)
      }
    });

  • 正常运行
    [/ol]


    11.png (396.47 KB, 下载次数: 0)
    下载附件
    修改后的返回值
    2023-3-1 13:34 上传

    开始重打包
    分析完完整性验证方法后,我们就可以正式开始修改工作了
    [ol]

  • 下载frida-gadget.so,选一个适合自己的版本,Releases · frida/frida (github.com),若出现在部分设备上可用部分设备不可用的情况,则可以更换一下gadget版本,目前遇到过14版本的gadget在Android12不可用的情况

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

  • 编写配置文件(配置文件名称需要和frida-gadget.so的名字相同,如frida-gadget.so的配置文件名称应该为frida-gadget.config.so)
    {
    "interaction": {
       "type": "script",
       "path": "/sdcard/Download/Script/DDD.js",
       "on_change":"reload"
    }
    }

  • 将frida-gadget.so和frida-gadget.config.so放入lib目录对应文件夹,64位的就放arm64_v8a,可以用MT管理器操作,直接增加进去即可

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

  • MT管理器打开apk找到,在System.loadLibrary("native-security");对应的smail代码下增加如下内容
    const-string v0, "frida-gadget" invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V

  • 将DDD.js文件放到配置文件中配置的位置

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

  • 进入APP后注册、登录,若显示为永久会员则说明已成功
    [/ol]
    最后
    本帖仅用于交流技术随机选择APP进行分析,但是这个工具箱我个人用下来还非常好用,可以下载几十种类型的链接,基本上包含了所有短视频,价格也很便宜,二十来块就可以买永久会员,建议大家去支持一下。

    下载次数, 方法

  • 我为52pojie狂   

    小电影是什么意思?是两三个人一个场景的那种人体艺术片吗?
    佚名RJ   

    这个软件直接逆向是很好破解的,但是就是检验安装包完整性,直接跳转到浏览器强制更新下载,每个版本改的都是这样。具体软件叫什么,从第一种方案的图片用过的,一般都看出来了
    limingdemingzi   

    谢谢分享
    huduke   

    谢谢分享,有没有ios app破解实操。
    adolphin   

    可以啊,这样子就ok了
    fangxiaolong   

    谢谢分享
    yboopp   

    先码住   进收藏吃灰吧   
    破凤凰   

    感谢老师无私分享。
    bpzm1987   

    厉害,加油,前来学习下!
    您需要登录后才可以回帖 登录 | 立即注册

    返回顶部