一、前言以及环境配置

    PS:突然想起来好久没在看雪发过啦,这次就同步一下吧!!!

    PS:该文已经首发于某公众号,介意者勿喷!!!

    安卓的加固方案是从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的尾部,我又画了一张图,应该可以看图就懂了:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qSnvXdvK-1648472591872)(https://cdn.jsdelivr.net/gh/windy-purple/blog_picture_bed//androidshell/5.png)]

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

import binascii
import hashlib
import zlibdef 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 >> 8shell.seek(0x20)shell.write(b)def IntToHex(num):b = bytearray()for i in range(4):number = int(num % 256)b.append(number)num = num >> 8b.reverse()return bdef 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 + 4tmpByteArray = unshellArray + sourceArraynewdex = 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文件,那么现在清楚了,我们可以通过编写so文件调用openDexFile函数加载dex字节数组,值得注意的是,openDexFile函数返回值为一个int类型的cookie,可以简单理解成一个dex文件的’身份码’,通过该’身份码’即可操控这个dex文件,至于怎么调用该函数,可以通过dlopendlsym函数调用,相关代码如下所示:

2、实际操作

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gcgbn684-1648472591875)(https://cdn.jsdelivr.net/gh/windy-purple/blog_picture_bed//androidshell/11.png)]

    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文件中,该函数返回值为一个类结构指针,第二个参数为类名字,通过该参数我们就可以指定类进行指令还原,如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9JidA4cO-1648472591879)(https://cdn.jsdelivr.net/gh/windy-purple/blog_picture_bed//androidshell/18.png)]

    上面我们得到的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系统函数修改程序运行时内存指令逻辑
    Android逆向之旅—运行时修改内存中的Dalvik指令来改变代码逻辑
    Android中免root的hook框架Legend原理解析
    https://github.com/asLody/legend
    https://github.com/ele7enxxh/Android-Inline-Hook
    Android APK加固-完善内存dex
    利用动态加载技术加固APK原理解析
    Android插件化框架之动态加载Activity(一)
    Android APK 加固之动态加载dex(一)
    Android中实现「类方法指令抽取方式」加固方案原理解析
    Android中apk加固完善篇之内存加载dex方案实现原理(不落地方式加载)

安卓加固方案从落地加载到类指令抽取编写报告相关推荐

  1. 018 Android加固之实现dex加载器

    文章目录 Android APK加固-安全人员角度 关于类加载器 类加载器 类加载器的种类和个数 创建类加载实例 类加载器DexClassLoader和PathClassLoader 使用类加载器动态 ...

  2. 安卓:Fragment的懒加载

    Android应用开发过程中,ViewPager同时加载多个fragment,以实现多tab页面快速切换, 但是fragment初始化时若加载的内容较多,就可能导致整个应用启动速度缓慢,影响用户体验. ...

  3. Android进阶:十三、自定义类加载器加载加密类文件

    之前面试的时候有许多面试官问类加载器相关的问题,所以这是一个很重要的知识点.而且对于高级Android研发来讲,懂得更多类加载相关的东西,对开发也会有很多的帮助,比如热更新,类加密等. 其实笔者对类加 ...

  4. 错误: 找不到或无法加载主类 Test4解决方案

    在windows下可以用javac命令编译java文件,但是却出现找不到或则无法加载主类,情况如下图. 出现这种结果情况,首先看自己的java环境变量是否配置正确.环境配置链接如下: java环境变量 ...

  5. php自动加载基类文件

    原文地址 问题描述 偶尔我们可能不使用php框架写一些代码,会自己写一个框架或者包,这时一般会涉及多个文件. 这种情况下,如果只使用命名空间是不能加载其他需要的文件的. 使用php的 spl_auto ...

  6. 错误: 找不到或无法加载主类 helloworld_你还不知道Java的类加载机制?

    上篇分析完一个class文件后,我们再来回答几个问题 一.面试题                    1.什么是类的加载                                        ...

  7. [logback] 关于logback.xml加载非类路径配置文件 - 问题排查与解决

    前言 之前我的所有配置文件放在Resource目录下,现在我想移到dc_server目录读取配置文件,然后就: 自定义配置文件读取的坑-- 探索 项目启动后发布加载不到根目录下的配置文件,一直报这种错 ...

  8. 【异常】记录“错误: 找不到或无法加载主类 com.iotsoft.back.iotauthority.ApplicationRun”的解决方案

    本文目录 一.背景描述 二.报错原因 三.解决方案 方案一:把中文目录改成英文即可 方案二:maven --> install 一.背景描述 今天从 Git 上新 checkout 下来一个项目 ...

  9. java加载找不到类_java提示找不到或无法加载主类怎么办

    java提示找不到或无法加载主类怎么办 java提示找不到或无法加载主类的解决方法:1.打开高级系统设置,进入环境变量设置:2.新建JAVA_HOME变量,设置jdk安装路径:3.编辑PATH变量,添 ...

最新文章

  1. 从C#到Objective-C,循序渐进学习苹果开发(7)--使用FMDB对Sqlite数据库进行操作
  2. Xamarin.Forms单元控件Cell
  3. Django资源大全
  4. 3.6-3.8 分布式环境启动、测试
  5. 武汉火神山医院正式交付,华为提供多项技术支持
  6. __asm__ __volatile__ 嵌入式内嵌汇编语法解构
  7. eclipse还原默认窗口_第3天 | 12天搞定Python,用Eclipse编写代码
  8. OpenCV-python学习笔记(四)——smoothing and blurring平滑和模糊
  9. 像冠军一样创建报告! Reporting Services的提示和技巧
  10. 其实我的直觉告诉了我的未来
  11. 认识端口与计算机入侵防范
  12. AI的螺旋式上升?今日头条AI掌门人马维英离职,“重返”清华从事培育科研工作
  13. Fiddler-本机抓包模拟器
  14. 考研高等数学张宇30讲笔记——第一讲 高等数学预备知识
  15. Cacti使用安装详解
  16. 【廖雪峰】写一个验证Email地址的正则表达式
  17. unsupported GNU version! gcc versions later than 6 are not supported!
  18. 信息化及计算机相关岗位职责,关于印发《信息化管理工作人员岗位职责》的通知...
  19. 【Mac】anaconda创建虚拟环境+jupyter虚拟环境配置
  20. 操作系统μC/OS-Ⅱ读书笔记(2)

热门文章

  1. php 下载doc文档
  2. DIODES,AL3353,异步恒流驱动升压芯片
  3. 【ASP.net文档】用C#实现HTTP协议下的多线程文件传输
  4. Pop Sequence
  5. Java编程那些事儿104——网络编程技术3
  6. web 3.0 的个人理解总结
  7. 选择短视频营销外包公司的优势有哪些?
  8. 结合源码和日志分析 mesos agent recover 过程
  9. UE4 UMG入门——创建和显示游戏菜单
  10. ANDORID~支持的设备