APK逆向分析入门-以某扫描软件为例
hjsz 发表于 山东 移动安全 1251浏览 · 2024-01-24 03:33

分析

源码

  • 拖入Jadx,发现是加固后的。使用MT管理器查看是BB加固。

脱壳

  • 使用脱壳神器BlackDex
    • 原项目中只有一个dex,从脱壳后的apk中找出和原有dex相同的删除,然后看看右边脱壳后的里面的内容,直接用dex编辑器即可。将一些重复或者没用的dex删除。

  • 然后对留下来的dex进行修复(np管理器),重命名为classes1.dex-classesS.dex.。并复制到原有的项目中,记得不要自动签名。

  • 然后在lib和assert文件下找一下加固的特征码,删除,最后使用np管理器重新签名即可,MT管理器显示无加固。

  • 安装后发现打不开,回顾了一下之前的流程,才发现没有改应用的正确入口。
    • 打开com.SecShell.SecShell.H类,查看appname那行,是真实入口

  • 把这个入口替换成上面那个真实入口

  • 同时,查看XML里是否有android:appComponentFactory="com.SecShell.SecShell.AP" 有就删除

  • 经过上面的脱壳后,app安装后可以正常打开。

安装

  • 安装后可以用,但是有些功能需要开通VIP。

跳过应用启动界面

  • 用Jadx打开我们脱壳后的apk。使用Activity记录器观察应用启动情况,从下图可以看出,应用第一个启动的Activity是NewLaunchActivity,然后才是MainActivity。

  • 经过分析,可以得知NewLaunchActivity需要经过一系列跳转判断才可以跳转到MainActivity,比如下面判断是否有更新消息的跳转。

  • 我们尝试直接修改AMF.xml文件中的启动项来跳过这一过程。发现可以直接跳过。直接进入主界面,节省时间。

会员功能

  • 先尝试一个需要会员的翻译功能。通过抓包分析,这里的翻译直接调用的是百度翻译的api。但是这里会先向应用的服务器发送一个请求,确定可以翻译了再向百度翻译api提起翻译的请求。第一个向app服务器发送的请求,应该是记录翻译的字符数。

  • 从下面代码分析得知,app也是先向自己的服务器发送翻译的请求。

  • 同时当HttpProxyUtils.baiduTranslateContent(str, **new** AnonymousClass3(str)); 中的AnonymousClass3(str) 有响应的时候,即请求一成功的时候会发起百度翻译的请求,

  • 现在来看一下这个成功回调函数的完整代码,可以看出先判断translationBean.isIssucc() 如果可行的话就会进行百度翻译,并不会出现购买会员的对话框。

  • 下面是isIssucc()函数的定义,我们需要改的是让其一直返回true。

    public boolean isIssucc() {
               return this.issucc;
           }
    • 先测试一下,当超字数的时候是否可以翻译,结果是不可以,出现了购买会员的Dialog对话框。

  • 修改smali代码

    .method public isIssucc()Z
           .registers 2
    
           .line 20
           iget-boolean v0, p0, Lcom/jtjsb/paitushizi/bean/TranslationBean;->issucc:Z
    
           return v0
       .end method
       上面是原始的smali代码,下面是修改后的
       .method public isIssucc()Z
           .registers 2
    
           .line 20
           const v0, 0x1
    
           return v0
       .end method
    • 通过结果可以看出任何字数的文字都可以正常翻译而不会有字数限制。

  • 使用frida Hook isIssucc() 函数,也可以实现上面的效果。原理都是让 isIssucc() 函数返回固定的true值。
function carhook3(){
            let TranslationBean = Java.use("com.jtjsb.paitushizi.bean.TranslationBean");
            TranslationBean["isIssucc"].implementation = function () {
            console.log('isIssucc is called');
            let ret = this.isIssucc();
            console.log('isIssucc ret value is ' + ret);
            return ret;
        };
        };
  • 会员身份。上面的修改虽然可以解除翻译时的字数限制,但是忽略了会员这一身份,可以从下面这一段看出,在开始之前会先判断免费次数或者会员身份。

  • 这里可以直接修改判断语句,将AppConfigs.getFreeTimes() <= 0 改为AppConfigs.getFreeTimes() > 0 ,但是这里可能会开始的时候因为这个判断不能用,一定要免费次数用完再改。所以这里想到了修改DataBeanUtils.isVipExpired() 的值。

  • 从上面的代码可以看出,这里的isVipExpired() 也是返回boolean值的类型,也可以修改smali,返回一个固定的值。从下面vip==null的判断可以看出,当不是vip身份的时候返回的事true。所以应该可以判断是,当是vip可用的时候返回False。这里要修改两个地方,一个是为null的时候返回

    if (vip == null) {
                   return true;
               }
    • 修改smali代码

  • 第二处修改:return之前,给v0复制0,即False。

  • 经过多次测试,使用完次数之后也没有提示让购买会员,可以一直使用。证明我们的修改是有效的。
0 条评论
某人
表情
可输入 255