APK逆向分析-以某音频转文字工具为例
hjsz 发表于 山东 移动安全 942浏览 · 2024-04-06 10:39

简单分析

  • 查壳(PKID)
    • 也可以用MT管理器,或者jadx简单看下代码结构。
    • 上述分析得出此app并没有经过加固。
  • 分析源代码
    • 拖入Jadx查看源码,开始看到下面的代码结构,应该是混淆了。没加固但是混淆后的app也有一定的分析难度。
    • 那就只能先动态的找一下程序入口。使用MT管理器的Activity记录器看一下app的Activity跳转。发现刚才自己并没有仔细看,主要逻辑的代码并没有混淆。
    • 根据上面Activity的提示,找到主要代码的源码。

问题分析

  • 上面经过简单的分析以及可以查看app的源码,并已经将app安装好了,下面就打开app,按照正常使用逻辑找一下要解决的问题。
  • 问题一:打开app会直接进入开通会员的界面,比较讨厌,主要是比较贵。
  • 问题二:就是很多东西需要开会员,而且可以怀疑他们的音频转文字只是调用的一些api。

逆向,解决上述问题

  • 首先是问题一,app启动Activity是关于开通会员的。根据分析中的图4,先找到相关Activity:XsrdVipChargeActivity。
    • 然后去AMF文件找一下这个Activity,但是并没有发现这个Activity就是启动Activity,再回顾一下之前的分析,发现是先经过了XsrdSplashActivity又进入了XsrdVipChargeActivity,之前这个Splash并没有之前常见的广告,所以就忽略了。
    • 看一下XsrdSplashActivity的具体逻辑。由于进行了代码混淆,所以简单看下去只发现了下面的关键代码,应该是判断是不是会员然后进行跳转的。
    • 先忽略具体的跳转逻辑,先尝试直接在AMF文件中修改启动Activity为:XsrdTabPageActivity。
    • 结果证明这是可以的,效果如下图,直接启动主界面Activity。
  • app启动Activity修改方法二:还记得之前暂时忽略了SplashActivity中的挑战逻辑,这里就仔细看一下,通过修改这一逻辑,让其判断为会员,从而不用跳转到开会员的界面。
    public final void zr() {
          XsrdTabPageActivity.w.z(XsrdTabPageActivity.f24770wW, this, null, null, 6, null);
          if (!w.f1188w.u()) {
              if (aI.w.f1098w.l()) {
                  int y2 = com.xinshang.recording.config.z.f24764w.y();
                  if (y2 == 1) {
                      XsrdVipChargeActivity.w.z(XsrdVipChargeActivity.f26193wG, this, "start_up_2", 0, 4, null);
                  } else if (y2 == 2) {
                      XsrdVipChargeActivity.w.z(XsrdVipChargeActivity.f26193wG, this, "start_up_2", 0, 4, null);
                  }
              } else {
                  int k2 = com.xinshang.recording.config.z.f24764w.k();
                  if (k2 == 1) {
                      XsrdVipChargeActivity.w.z(XsrdVipChargeActivity.f26193wG, this, "start_up_1", 0, 4, null);
                  } else if (k2 == 2) {
                      XsrdVipChargeActivity.w.z(XsrdVipChargeActivity.f26193wG, this, "start_up_1", 0, 4, null);
                  }
              }
          }
          pJ.z.q(this);
          overridePendingTransition(R.anim.xs_anim_transition_fade_in, R.anim.xs_anim_transition_fade_out);
      }
    
    • 首先会根据w.f1188w.u()的结果进行if判断。先看一下这个是什么。f2是vip信息的结构体,包括一些会员时间,会员状态等信息。
    • 我们先通过frida看一下w.f1188w.u()的返回值。坏了,遇到了frida检测,无法进行正常的hook,先简单尝试一下TracerPid的检测,借鉴r0trace大佬的方案进行绕过。
      var fgetsPtr = Module.findExportByName("libc.so", "fgets");
      var fgets = new NativeFunction(fgetsPtr, 'pointer', ['pointer', 'int', 'pointer']);
      Interceptor.replace(fgetsPtr, new NativeCallback(function (buffer, size, fp) {
        var retval = fgets(buffer, size, fp);
        var bufstr = Memory.readUtf8String(buffer);
        if (bufstr.indexOf("TracerPid:") > -1) {
            Memory.writeUtf8String(buffer, "TracerPid:\t0");
            console.log("tracerpid replaced: " + Memory.readUtf8String(buffer));
        }
        return retval;
      }, 'pointer', ['pointer', 'int', 'pointer']));
      
    • 发现还是不行,然后看一下哪里调用了pthread_create,可以看到很多地方调用了,而且是当libart.so调用后出现了错误。这里再进行so的分析就会比较耗时。那就直接按照代码逻辑修改smali代码,不再挨个判断一些跳转的返回值。
  • 分析w.f1188w.u()的调用逻辑,u()会先判断一些VIP信息为空的话就返回false,然后!w.f1188w.u()为true,进入对应逻辑,而当信息不为空的情况下,返回的是XsRecordUserVIPInfo.a()函数。
    • 接着上面的分析继续看XsRecordUserVIPInfo.a()函数。
    • 接着上面的分析继续看h() && !x()函数。其中h函数是判断当前vip状态的函数,x函数是判断会员时间未到期的函数。

    • 那要进行smali修改时先要注释掉u()对vipinfo是不是为空的判断,然后再修改a的返回值为true。或者直接将u()的返回值全改为1.注意这里不能直接写return 1,要return变量,在return之前将变量赋值为1.
    • 修改之前反编译签名安装,效果也达到了,但是还是会现有一两秒的Splash界面。
      #### vip功能
  • 根据之前分析的基础,可以知道哪里是vipInfo的定义函数,接下来尝试直接修改主要函数的返回值。
  • a()应该是判断当前vip是否可以继续使用的关键函数,因为它包括了是不是vip以及vip的剩余时间还有多少。那就先把这个函数的返回值改为true。修改方法同上。
    • 经过上面的修改,发现虽然不显示vip,但是原来需要vip的功能现在都可以直接使用。具体请看下面的对比,



  • 大致看了一下,应该是直接调用的baidu的api。
    #### 本文仅供参考学习使用!
0 条评论
某人
表情
可输入 255