专栏首页信安之路安卓 APP 三代加壳方案的研究报告

安卓 APP 三代加壳方案的研究报告

安卓的加固方案是从 19 年底开始写的,到现在为止差不多快一年了,写这个目的还是学习怎么脱壳,前几个月再看雪看到有人直接分析壳来学习,不过我感觉从加壳写起也是一种浪漫。因为个人原因,在类指令抽取壳那里为半完成状态,在今年大概率没有时间接着修改了,在 java 层的加固就止于此吧!!!(PS: 以后有时间会接着修改)

环境配置:

Android studio v3.5.3

华为G621-TL00 android v4.4.4

第一代壳:落地加载

1、原理

a、原理很简单,就是首先将我们的 dex 文件或者 apk 文件解密,然后利用DexClassLoader加载器将其加载进内存中,然后利用反射加载待加固的 apk 的 appkication,然后运行待加固程序即可,我画了个流程图详细说明如下:

b、上面说了大概原理,现在来说明一下具体细节,我们知道,在一个 app 开始运行的时候,第一个加载的类是ActivityThread,该类有个关键属性currentActivityThread,通过该属性能够获取到一系列其他关键的属性,例如mPackages,通过该属性,我们可以获取到mClassLoader属性,通过替换该属性我们可以替换系统加载器,如下所示:

接着来说怎么获取待加固 apk 的 application,这个通过在脱壳 apk 的 AndroidManifest.xml 中使用meta-data来获取,如下所示:

在然后就是怎么替换 application,我们可以知道在 android.app.LoadedApk 类中有一个方法makeApplication可以生成一个 application,通过该方法生成一个 application,然后通过替换android.content.ContentProvider类中的mContext属性完成 application 的替换,如下图所示:

2、实际操作

ps:因为第一代壳网上一大堆,所以讲得很粗略,同时这也不是本文的重点!!!

通过上面的代码我们可以得到脱壳 apk,然鹅待加固的 apk 放在哪里,网上大多放在脱壳 dex 的尾部,我又画了一张图,应该可以看图就懂了:

这个我采用通过 python 读取二进制然后重新计算 chunksum 和签名字段实现,代码如下:

import binascii
import hashlib
import zlib
def fixCheckSum(shell):
    shell.seek(0x0c)
    data = shell.read()
    checksum = zlib.adler32(data)
    strchecksum = str(hex(checksum))
    strchecksum = strchecksum.replace('0x','')
    b = bytes(strchecksum.encode('utf-8'))
    a = bytearray(b)
    c = binascii.hexlify(binascii.unhexlify(bytes(a))[::-1])
    dataCheckSum = bytearray(c)
    shell.seek(0x08)
    shell.write(dataCheckSum)
def fixSHA1(shell):
    shell.seek(0x20)
    signBytes = shell.read()
    sha1 = hashlib.sha1()
    sha1.update(signBytes)
    sign = sha1.hexdigest()
    tmp = bytes(sign.encode('utf-8'))
    b = bytearray(tmp)
    shell.seek(0x0c)
    shell.write(b)
def fixFileSize(shell,num):
    b = bytearray()
    for i in range(4):
        number = int(num % 256)
        b.append(number)
        num = num >> 8
    shell.seek(0x20)
    shell.write(b)
def IntToHex(num):
    b = bytearray()
    for i in range(4):
        number = int(num % 256)
        b.append(number)
        num = num >> 8
    b.reverse()
    return b
def main():
    sourceApk = open('sourceApk.apk','rb+',True)
    unshell = open('unshell.dex','rb+',True)
    filename = 'classes.dex'
    
    tmpApk = sourceApk.read()
    print('[*] 成功读取待加壳的APK文件')
    sourceArray = bytearray(tmpApk)
    tmpDex = unshell.read()
    print('[*] 成功读取脱壳DEX文件')
    unshellArray = bytearray(tmpDex)
    print('[-] 待加壳APK文件开始加密,加密类型为:未加密')
    
    sourceApkLen = len(sourceArray)
    unshellLen = len(unshellArray)
    print('[+] 加密后的APK大小为' + str(sourceApkLen) + 'Byte')
    totalLen = sourceApkLen + unshellLen + 4
    
    tmpByteArray = unshellArray + sourceArray
    newdex = tmpByteArray + IntToHex(sourceApkLen)
    print('[+] 所有二进制数据合成完毕')
    
    shellTmp = open(filename,'wb+',True)
    shellTmp.write(newdex)
    shellTmp.close()
    print('[+] 数据写入' + filename + '完毕')
    
    shell = open(filename,'rb+',True)
    fixFileSize(shell,len(newdex))
    print('[+] 文件大小修改完毕')
    fixSHA1(shell)
    print('[+] 文件SHA-1签名头部修改完毕')
    fixCheckSum(shell)
    print('[+] 文件校验头头部修改完毕')
    print('[+] 待加壳APK文件sourceApk.apk加壳完毕,加壳后DEX文件' + filename + '生成完毕')
    shell.close()
    
if __name__ == '__main__':
    main()

将上述 apk 重新签名后,安装运行,如下图所示:

3、遇到的问题

运行时报错如下所示:

解决方案:报错显示无法实例化 activity,经过检查是无法加载到正确格式的 dex 文件,检查你的解密代码,即使是你加密是象征型的异或了一个 0xff,解密时也不能因为异或 0xff 值不变而不异或 0xff。其次是打包成 apk 之前删除签名文件之后在签名!!!

第二代壳:不落地加载

1、原理

大体原理和第一代壳相同,和第一代壳不同的是,第一代壳将 dex 文件解密出来会保存到文件中,在通过 DexClassLoader 加载进内存中,而不落地加载直接重写DexClassLoader使其可以直接加载字节数组,避免写入文件中。我们要做的是重写DexClassLoader,而这涉及到三个函数defineClassfindClassloadClass,在一个类被加载的时候,会先后调用这三个函数加载一个类,所以我们需要重写这三个函数,但是我们怎么在重写的过程中操控 dex 中的类(通过字节数组加载进来的并不能直接操控)?

其实系统的 DexClassLoader 加载 dex 进入内存的也必然是通过字节加载的,而在系统 so 中的libdvm.so中的openDexFile可以直接加载 dex 文件,那么现在清楚了,我们可以通过编写 s o文件调用openDexFile函数加载dex字节数组,值得注意的是,openDexFile函数返回值为一个int类型的 cookie,可以简单理解成一个 dex 文件的'身份码',通过该'身份码'即可操控这个 dex 文件,至于怎么调用该函数,可以通过dlopendlsym函数调用,相关代码如下所示:

2、实际操作

a、首先编写样本,这里我写了一个类和一个方法,作用就是打印一个特征字符串,如下所示:

b、将上面的样本打包成 apk 后提取出 dex 文件然后放置到 assest 文件夹下(该文件夹需要自己建立)供程序调用(ps:我这里图方便,没有对 dex 文件加密然后解密,有需要的可以加上),然后脱壳 apk 和上面的第一代壳没什么区别,唯一不同的是就是我们使用的是我们自己重写的DexClassLoader,如下图所示:

c、运行截图如下:

3、遇到的问题

a、报错java.lang.UnsatisfiedLinkError: Native method not found

解决方案:在配置文件中添加

packagingOptions{
        pickFirst "lib/armeabi-v7a/libtwoshell.so"
        pickFirst "lib/arm64-v8a/libtwoshell.so"
        pickFirst "lib/x86/libtwoshell.so"
        pickFirst "lib/x86_64/libtwoshell.so"
    }

如下所示:

b、运行到加载 dex 文件中的方法时,app 直接闪退

解决方案:重写的loadClass方法有问题,不能通过直接 super 调用父类方法,而是应该通过反射调用defineClassNative方法,如下所示:

第三代壳:类指令抽取壳

1、原理

a、什么是类指令抽取壳,从名字就能看出来,就是把dex文件中的方法指令抽空,变成nop,然后在运行时再将指令还原!!!

b、指令抽取可以通过 010 修改,现在来说指令还原,其余代码和第二代基本一样,不一样的地方在加载完 dex 之后执行指令还原函数,指令还原现在有两种方法,第一种是通过读取maps文件获取加载的 dex 文件地址,然后对 dex 文件进行解析,找到被 nop 的指令处进行还原(ps:该种方法需要及其熟悉 dex 文件格式,不了解的可以看我之前的文章关于解析 dex 文件,因为我之前解析的时候用的是 python,改成 c 要大量时间,所以我选择了第二种方法);第二种方法就是通过免 root hook 系统函数(最简单的就是 deFindClass 函数)然后进行指令还原!!!

c、接下来就讲一下怎么通过 hook dexFindClass 函数来进行指令还原(PS:看懂下面的内容需要理解 dex 文件格式)。dexFindClass函数在libdvm.so库中,如下所示:

免 root hook 框架有点多,我选择的是 android inline hook,原因很简单,很适合在so层使用,其他的经过我测试不知道为啥我写出来的没反应,该框架 github 地址:

https://github.com/ele7enxxh/Android-Inline-Hook

用法可以参考作者 github,该 inline hook 框架需要原函数地址、新函数地址和原始函数的二级指针,用法如下所示(怎么使用不是重点,接下来的才是重点,所以这里比较粗略):

我们要 hook 的是dexFindClass函数,该函数定义在DexFile.h文件中,该函数返回值为一个类结构指针,第二个参数为类名字,通过该参数我们就可以指定类进行指令还原,如下所示:

上面我们得到的classDataOff,我们可以通过该地址获取到类数据,该偏移地址指向的是一个DexClassData结构,该结构的header存储了相关类信息,该结构的directMethods指针指向的方法的结构体,如下所示:

通过directMethods指针我们可以顺着找到DexMethod结构体,通过该结构体的methodIdx调用系统函数dexGetMethodIddexStringById可以获取到方法名字,精确还原方法指令,通过该结构的codOff(这是个偏移地址)可获取方法指令,该偏移地址指向DexCode结构,该结构即存储了方法指令,利用memcpy替换即可达到指令还原的效果,如下所示:

2、实践操作

java 层基本和第二代壳一样,只是多了一个调用 hook 的函数,so 层关键代码如下所示:(ps:不知道为啥 Android inline hook 稳定性很差,上一个测试 app 还得行,下一个就疯狂报错了,所以代码是基本完成了,但是 android inline hook 报错未解决,有时间我会修改)

3、遇到的问题

报错未定义函数,如下所示:

解决方案:在 CmakeLists.txt 文件中将 jni 文件夹下面所有引用到的文件都包含进去,如下所示:

后记及其相关链接

我个人习惯了通过写加固来学习脱壳,可能时间比直接分析壳来得慢,但是这其中体验真的酸爽到爆炸,因为个人原因,最后的类指令抽取壳最后一点没弄完,算是一个小遗憾吧,20 年应该没时间来弥补这个遗憾了,希望 21 年我有时间来把这个遗憾补上吧!!!

源码 github 链接:

https://github.com/windy-purple/androidshell

参考链接:

Android 免 Root 权限通过 Hook 系统函数修改程序运行时内存指令逻辑:

http://www.520monkey.com/archives/1115

Android 逆向之旅—运行时修改内存中的 Dalvik. 指令来改变代码逻辑:

http://www.520monkey.com/archives/815

Android 中免 root 的 hook 框架 Legend 原理解析:

https://www.jianshu.com/p/c5580215ee26

https://github.com/asLody/legend

https://github.com/ele7enxxh/Android-Inline-Hook

Android APK 加固-完善内存 dex:

https://www.cnblogs.com/ltyandy/p/11644056.html

利用动态加载技术加固APK原理解析:

https://www.jianshu.com/p/ce20fa304e1e

Android 插件化框架之动态加载 Activity(一):

https://www.jianshu.com/p/1035ffd9e9cf

Android APK 加固之动态加载dex(一):

https://blog.csdn.net/weixin_44045581/article/details/89811868

Android 中实现「类方法指令抽取方式」加固方案原理解析:

http://www.520monkey.com/archives/1118

Android 中 apk 加固完善篇之内存加载 dex 方案实现原理(不落地方式加载):

http://www.520monkey.com/archives/629

文章分享自微信公众号:
信安之路

本文参与 腾讯云自媒体分享计划 ,欢迎热爱写作的你一起参与!

作者:windy_ll
原始发表时间:2020-09-09
如有侵权,请联系 cloudcommunity@tencent.com 删除。
登录 后参与评论
0 条评论

相关文章

  • 安卓逆向从 0 到 1 学习总结

    原本打算在入门之后弄个安卓逆向教程作为总结,但是吧,写文章教程,各大论坛都有,而且还写得挺好,例如 52 论坛的 《教我兄弟学逆向》教程,自己再去写就没多大意思...

    信安之路
  • 常见android app加固厂商脱壳方法研究

    目录简述(脱壳前学习的知识、壳的历史、脱壳方法) 第一代壳 第二代壳 第三代壳 第N代壳 简述Apk文件结构Dex文件结构壳史壳的识别Apk文件结构

    砸漏
  • 什么是App加壳,以及App加壳的利与弊

    什么是App加壳,以及App加壳的利与弊 ? 目前针对移动应用市场上安卓APP被破解、反编译、盗版丛生的现象,很多APP开发人员已经意识到保护APP的重要性。...

    非著名程序员
  • 什么是App加壳,以及App加壳的利与弊

    ? 目前针对移动应用市场上安卓APP被破解、反编译、盗版丛生的现象,很多APP开发人员已经意识到保护APP的重要性。而对于移动应用APP加密保护的问题,如何对...

    非著名程序员
  • 「docker实战篇」python的docker-打造多任务端app应用数据抓取系统(终结)(36)

    PS:最后docker的实践,关于爬虫这块高级docker的承诺,我也兑现了,其实很多时候就是缺少一个思路,工具真的是一大把,条条大路通罗马,多学多问,通过爬虫...

    IT架构圈
  • iOS逆向-ipa包重签名及非越狱手机安装多个微信

    czjwarrior
  • 用安卓 WebView 做一个“套壳”应用

    本文主要讲解如何制作一个安卓原生的“壳”来加载我们的 H5 网页,最终实现一个简单的 Hybrid App(套壳应用)。

    陈皮皮
  • ios逆向-frida&环境&破解appSign算法

    比WIFI响应速度快,网络环境无限制 usbmuxd是网上开源社区,貌似是国外牛人倾力打造的一个专门针对该功能开源库 通过brew来安装brew install...

    吾爱小白
  • 这就是鸿蒙系统?

    我手头使用的是一部华为Mate 20 pro手机,快三年时间了。作为一名数码爱好者,对于系统升级非常积极,每次收到系统更新通知,都会在第一时间升级。这次鸿蒙系统...

    云水木石
  • 手把手教你进行JS逆向并去除App开屏广告

    Hi,大家好,我是码农星期八,今天来搞点关于逆向相关的,如何去除app的开屏广告。

    Python进阶者
  • 安卓实现安卓-光速虚拟机技术内幕

    光速虚拟机是基于安卓系统和ARM处理器架构实现的一套虚拟化技术,在安卓系统的用户态空间无需特殊权限实现了一套完整的安卓内核和硬件抽象层,能够在安卓APP内部运行...

    用户6237893
  • 前端视角看HarmonyOS

    公元 2021 年 6 月 2 日,【 HarmonyOS2.0 】正式发布,以 JavaScript 作为 IoT 应用开发的架构语言,这是继 SpaceX ...

    公众号@魔术师卡颂
  • 如何进行APP抓包 ? – 学习/实践

    发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/139295.html原文链接:https://javaforall.cn

    全栈程序员站长
  • 初探Android逆向:通过游戏APP破解引发的安全思考

    如今移动互联网已经完全融入到我们的生活中,各类APP也是层出不穷,因此对于安卓APP安全的研究也尤为重要。本文通过对一款安卓APP的破解实例,来引出对于APP安...

    FB客服
  • RSSHelper正式开源

    试过一些RSS订阅app,有些重要源无法解析,例如FEX周刊、奇舞周刊、国外站点等等。另外,对于没有提供RSS的网页,也没有办法订阅,所以决定自己搓一个:

    ayqy贾杰
  • 鸿蒙3.0将删除谷歌代码,只是为让国产系统更纯粹

    作为“聚光灯下诞生的国产系统”,华为鸿蒙系统从一出生就引发了激烈的争论。尽管鸿蒙系统都已经更新到了3.0版本,而关于“鸿蒙系统究竟是不是安卓套壳”的问题,却还是...

    xiangzhihong

扫码关注腾讯云开发者

领取腾讯云代金券