Skip to content

Latest commit

 

History

History
52 lines (30 loc) · 9.38 KB

File metadata and controls

52 lines (30 loc) · 9.38 KB

APP攻防博弈

以下内容摘自《安卓Frida逆向与抓包实战》第5章

#查看文件类型
file [APK文件]

#解压
unzip

#查看压缩文件
tree

#安装
adb install
#有时会报错INSTALL_PARSE_FAILED_NO_CERTIFICATES这种没有签名的错误,而META-INF文件夹主要就是用于存储签名文件的。这里值得一提的是,Android本身的签名十分简单,和Apple签名机制严格由Apple官方控制不同,由于Android本身的开源导致Android市场的碎片化,Android最终也就没有限制App的签名方身份,而这一点在促进Android发展的同时也为重打包的技术创造了便利。

Smali语言,因为Android中的代码是运行在 Dalvik或Art虚拟机中的,与ARM汇编类似,而Smali语言可以当作 Android虚拟机的汇编语言。

目前apktool是Android逆向领域一个具有里程碑的神器,像Jadx、Jeb。没有保护的情况下想要破解一个软件,只要使用apktool等反编译工具就可以清楚且完整地看到一款产品的原始代码,观察到的代码几乎和源码一模一样。

应对这样的破解手段,开发人员开始利用各种各样的手段保 护自己的源代码。其中最常用的手段就是“代码混淆” ,例如使用 Google自带的混淆器ProGuard。ProGuard对安全领域来说只有一 个作用,就是更改类名、函数名和变量名。这样做的结果主要有两个 好处:一个是符号混淆,将开发者为了代码可读性所编写的类似于 SplashActivity、LoginActivity这种有意义的名称改成a、b这样无意 义的名称,以增加攻击者在破解软件时猜到相应类的实际作用的难 度;另一个是压缩文件大小,毕竟对于动辄成千上万的类,其符号字 符串在dex文件中的字节占比并不小。在Android Studio中要启用 ProGuard,只需要修改项目根目录下的App/build.gradle文件将其中buildTypes层级中minifyEnabled对应的值从 false更改为true即可,这样最终编译生成的release版本便会启用代 码混淆了。

还有一个具有代表性的“代码混淆”工具——DexGuard。 DexGuard与ProGuard是同一个开发者开发的,与ProGuard相比, DexGuard功能更加丰富,不过它是一个收费的商业软件。同时, DexGuard的混淆功能更加强大,不仅支持ProGuard的所有功能, 还支持字符串加密、花指令、资源加密等。发展到现在,DexGuard 甚至还添加了一些运行库的防护,已经不是一个单纯的代码优化器 了。

动态加载方案。所谓 动态加载,就是将需要保护的代码单独编译成一个二进制文件,将其 进行加密后保存在一个外部的二进制文件中。在外部程序运行的过程中,再将被保护的二进制文件进行解密并使用ClassLoader类加载器 来动态加载和运行被保护的代码。so库。

鉴于在Android中使用Java编写的App很容易被破解, 故反其道而行之,将所有的核心代码使用NDK套件(在Android中, NDK实际上是一个工具集,可以让开发者使用C/C++语言实现应用 的各个部分)进行开发,这样的结果往往是外部的Java代码最终只是 充当了一个二进制文件装载器的角色,实际的业务逻辑都被放置在更 难破解的so文件中。当然,基于“放置在客户端”都不可靠的原则, 为了App的安全,有的App甚至将重要的功能和数据都放置到云端, 在客户端尽量只进行结果的展示。

动态分析,在上面提到的最直接有效且低成本的动态加载保 护就变成最脆弱的一种保护方式。只要通过动态分析,不管是在加载 后的函数中设置断点以便从内存dump(转储)出来被保护的内容, 还是直接搜索进程的内存空间以便根据特征找到真实的dex文件并 dump下来,都能很轻松地应对这种保护方式。另外,针对其他静态 保护方式,由于动态分析是基于进程所处的运行状态,因此相比于在 静态分析时得到的无意义代码而在动态分析时包含的都是真实的数据 信息,这使得破解者在逆向分析的过程中有了很多真实的数据,从而 大大削减了代码保护的作用。

在动态分析过程中,首先需要将调试器附加上进程或者通过注入 将指令代码和数据注入目标进程中,然后才能对目标进程进行调试与 内存监控。要做到这一步,最基本的方法就是调用ptrace()函数对进 程进行附加或者是基于二次打包的方式对程序进行修改,而对抗的手 段主要分为两种:运行时检测和事先阻止。

还可以根据调试器在附加手机上进程的时候需要运行Server端进 行配合通信的这一特征,通过判断系统进程中是否存在Server相关的 进程名进行检测,或者利用Server和调试器之间需要进行通信的特性 监控这些Server打开时需要默认监听的端口进行检测。这种反调试手 段不仅仅针对IDA这类调试器有效,而且针对Frida也同样有效。

除此之外,针对调试器还可以通过指令执行时间差进行检测,因 为如果一段代码正在被调试,那么这段代码执行的耗时比正常执行所 消耗的时间会更长。

经典的双进程保护。双进程保护是利用一 个进程最多只能被一个进程ptrace附加的特性,事先在代码中自己 fork一个子进程ptrace,ptrace自己就可以防止再被其他进程 ptrace,也就变相地阻止了进程被调试。

在App逆向分析过程中最难绕过的保护手段就是App 加固。所谓App加固,其逻辑和上面讲述的动态加载类似,用加固厂 商的壳程序包裹真实的App,在真实动态运行时再通过壳程序执行释 放出来的真正App。App加固发展出各种各样的加固手段。在业界曾 有人根据加固手法将加固分成了五个不同的阶段,近年来已逐渐将加 固的五个阶段进一步按照不同阶段加固的特性重新分为三个不同阶 段。

第一个阶段被认为是DEX整体加固,这是App加固的初期。这时 App加固的核心原理是将DEX整体加密后动态加载,这一点在上面讲 动态加载保护技术时介绍过。刚开始,App整体加固是需要先解密文 件并在解密完成后写入到另外一个文件中,在解密完毕后调用 DexClassLoader或者其他类加载函数来加载解密后的文件。DEX整体加固的致命之处在于,代码数据总是结构完整地存储在 一段内存中,一旦反注入、反调试等措施被破解,这种保护就门户洞 开了。于是出现了第二代代码保护机制。

第二代代码保护习惯上被称为代码抽取保护。这一阶段App加固 的关键在于真正的代码数据并不与DEX的整体结构数据存储在一起, 就算DEX被完整地从内存中dump(转储)出来,也无法看到真正的 函数代码。

比如:某DEX整体加固被dump出来之后,使 用Jadx查看关键函数,发现其代码被nop这一无意义的代码填充了。 这种加固的核心原理是利用私有函数,通过对自身进程的Hook来拦 截函数被调用时的路径,在抽取的函数被真实调用之前,将无意义的 代码数据填充到对应的代码区中。代码抽取技术的出现从根本上解决了第一代整体加固保护的缺 陷,同时也正式宣告全手工单步调试脱壳时代的彻底终结。代码抽取 技术存在一定的兼容性和性能损耗的问题,为了顾及这一问题,正常 的加固手段并不会对App中所有函数进行抽取保护,特别是对一些无 关核心的第三方库的代码就不必进行代码抽取保护。另外,考虑到性 能问题,代码抽取保护通常在函数被第一次调用后就不再将函数内容 重新置空,这也正是如今依然存在一些从内存中把DEX整体dump出 来的方案,因为只需要在App运行时多触发几次程序逻,然后再进行 DEX的dump,即可得到更加完整的DEX文件

DexHunter,其原理就是通过主动加载DEX中的所有类并dump出所 有方法对应的代码,最后将代码重构再填充回被抽取的DEX中。为了 对抗DexHunter,有的代码抽取方案在类加载的时并不恢复函数的代 码内容,而将恢复的时机进一步延后,这也就引出了后来的主动调用 方案——FUPK3/FART。FUPK3/FART的原理是对执行方法的入口 函数进行插桩操作,并在入口函数开始处判断是否带有主动调用的标 志,若属于主动调用则dump出相应函数的内容,再进行DEX文件的 重构。为了对抗这类脱壳的方式,加固厂商也采取过一些反制手段, 比如为App添加一个垃圾类,一旦这个垃圾类被加载就退出进程的执 行;亦或是采取监控特定文件读写的方式,比如一旦监控到进程要把 以dex035开头的文件内容dump出,就杀死进程,诸如此类。

第三代保护手段有一个重要的特点,就是将所有的Java代码变成 最终的Native层代码。两者不同的是VMP加固技术最早起源于PC的 虚拟机加固,其核心逻辑是将所有的代码使用自定义的解释器执行。 这时的代码不再依赖于系统本身,即使获得了所有的函数内容,也是 貌合神离、不知所云。这时唯一的解决方案可能是逆向对应的解释 器,以找到与系统解释器的映射关系。