美文网首页
Android 简单脱壳

Android 简单脱壳

作者: v1gor | 来源:发表于2019-11-02 14:19 被阅读0次

    通过在 dvmDexFileOpenPartial函数入口点下断点,动态dump出完整dex

    准备工作

    • 使用avd

      我用的是轻量级的sdk-tools(被集成在android studio中),里面带了如ddms,android设备管理器等调试apk的必要工具。

    • 题目来源和下载地址

      题目来自2016年厦门美亚柏科信息安全邀请赛crackme8-9

      下载地址:

      链接: https://pan.baidu.com/s/1gfJAtb1 密码: 7eh9

    • 下载安装合适版本的AVD

      由于android5.0以上(包括5.0)使用libart.so代替libdvm.so来优化dex加载和启动过程,而dvmDexFileOpenPartial函数在libdvm.so中被调用,因此我装了一个android版本为4.3.1的虚拟机,配置如下:

      {% asset_img markdown-img-paste-20171216104905646.png android4.3.1 %}

    原理分析

    这种方法只能脱一些比较简单的壳,而且不适用于使用libart.so的安卓系统。脱壳原理是,简单壳程序一般使用自定义的类,在apk运行之后动态解密加载真实dex文件到内存(dvm)中执行,但是加载dex过程中需要调用系统函数dvmDexFileOpenPartial来解析dex到dvm可运行的形式,dvmDexFileOpenPartial源码如下:

      int dvmDexFileOpenPartial(const void* addr, int len, DvmDex** ppDvmDex)
      {
          DvmDex* pDvmDex;
          DexFile* pDexFile;
          int parseFlags = kDexParseDefault;
          int result = -1;
    
          /* — file is incomplete, new checksum has not yet been calculated
          if (gDvm.verifyDexChecksum)
              parseFlags |= kDexParseVerifyChecksum;
          */
    
          pDexFile = dexFileParse((u1*)addr, len, parseFlags);
          if (pDexFile == NULL) {
              ALOGE(“DEX parse failed”);
              goto bail;
          }
          pDvmDex = allocateAuxStructures(pDexFile);
          if (pDvmDex == NULL) {
              dexFileFree(pDexFile);
              goto bail;
          }
    
          pDvmDex->isMappedReadOnly = false;
          *ppDvmDex = pDvmDex;
          result = 0;
    
          bail:
            return result;
        }
    

    可以看到这个函数的作用是解析dex文件,第一个参数是dex文件的基址,第二个参数是dex文件的大小,如果在加载主类前断在这个函数中,就可以dump出完整的dex。

    题目简述

    用jeb打开看到主antivity被隐藏,判断加了壳

    markdown-img-paste-20171216120958373.png

    开始脱壳

    1. 安装apk

    2. 将ida dbgsrv目录下的android server adb push 到虚拟机中,启动运行

    3. 终端下运行 adb forward tcp:23946 tcp:23946 将本机的23946端口转发到虚拟机的23946端口,方便ida附加调试。

    4. 运行 adnroid-sdk/tools 中的ddms,这里运行ddms的作用是因为之后需要使用jdb继续运行程序,ddms可以方便的查看jdb需要连接的端口。(注:ddms最好在ida attach操作之前打开,因为我好几次在之后打开都把ida挤掉了,原因还不知道)

    5. 终端下运行 adb shell ”am start -D -n 包名/入口activity“,以调试模式运行apk,运行结果会在android虚拟机上看到”waitting for debugger“的字样。

      markdown-img-paste-20171216110255820.png markdown-img-paste-20171216110314602.png

      一个apk的包名和入口activity名可以在apk的AndroidManifest.xml中看到,这个文件存储的是一个apk的所有配置信息。

      用apktools解包apk,可以在解压后的根目录下看到这个文件,打开之后是这样:

      markdown-img-paste-20171216110651161.png

      关注红框中的元素,android系统中,使用包名来区分各个apk,因此package名称可以定位到具体的某个apk,而这个apk的入口activity则是带有android.intent.action.MAIN的name属性的action标签对应的activity。

      根据这两个信息,上述命令就可以以调试模式断在系统加载这个apk的主类对应的dex之前。

    6. 使用ida远程attach相应进程,attach上之后在modules中搜索libdvm.so,找到之后双击,再搜索dvmDexFileOpenPartial这个函数,找到之后双击进入,在函数的第一句下断点。

    markdown-img-paste-20171216111615166.png

    设置调试选项debugger option如下(很重要,不设置会断不下来):

    markdown-img-paste-2017121611380083.png
    1. F9继续运行,此时发现android虚拟机的程序还没有继续运行,因此需要jdb触发其自动执行(具体分析见 http://blog.a7vinx.me/2016/10/25/android_debug_analysis/ ),在ddms中选中这个apk:
    markdown-img-paste-20171216112022271.png

    会发现最后一列的端口号后面多了一个8700.这是ddms设置的端口转发,ddms会将选定的apk的端口转发到8700端口,这样调试器就可以附加到这个特定端口来调试程序,而不需要寻找这个程序使用的端口。

    如果嫌jdb麻烦的话,可以用一条端口转发命令来手动转发到8700:

    adb forward tcp:8700 jdwp:<pid>其中pid是待attach的进程的pid。

    终端下运行jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8700发现ida断下来了,但是不是断在刚刚下断点的地方,而是段在linker段:

    markdown-img-paste-20171216112448681.png

    再次F9运行:

    markdown-img-paste-20171216113424181.png

    发现断在了我们之前下的断点处,这时看R0寄存器的值(第一个参数)就是dex在内存中的基址,R1寄存器的值就是dex的大小。

    markdown-img-paste-20171216113625632.png
    1. 写idapython脚本,dump出这段内存中的数据,就是完整的dex,dump脚本如下:

       import idaapi
      
       data = idaapi.dbg_read_memory(0xB7D080B8, 0xA40C)
       fp = open('/home/vccxx1337/Desktop/dump.dex', 'wb')
       fp.write(data)
       fp.close()
      

      在ida中运行后得到完整dex。

    脱壳结果

    aftershell.png

    多次实验验证,当程序已经在android虚拟机上运行过之后,再使用上述方法进行附加之后是不能在dvmDexFileOpenPartial函数处断下的,原因猜想是上次运行时解析的dex还驻留在dvm中,再次运行时就不会调用这个函数来再次解析dex了。具体原理还得看看android源码。比较粗暴的解决方法是卸载重装apk。

    相关文章

      网友评论

          本文标题:Android 简单脱壳

          本文链接:https://www.haomeiwen.com/subject/rjocbctx.html