目录

充电学习中...

X

浅谈安卓逆向工程-注入篇 置顶!

浅谈安卓逆向工程-注入篇

写在前面

何为注入(Inject)?

不同于Spring框架中常常提到的依赖注入,逆向工程中的注入往往更贴合其原本的含义,即将代码注入到目标进程中去,这些被注入的代码可能用于分析、监控目标进程行为,同时也有可能被用于其他恶意目的。

本文将结合市面流行的注入工具框架,通过注入时机、方式、目标、范围等维度对Android平台的注入做一个更加全面的剖析。另外,需要说明的是,通过修改Rom重新编译镜像的方式进行代码注入只能适用于特定机型,一般很少用于生产阶段,因此本文将排除这种方式。

内核态的注入

与用户态的各种应用服务相比,内核态更接近设备的硬件层(HAL),配合驱动程序可以直接操作硬件,同时也往往拥有更高级别的权限。Android使用的是较老版本并且精简过的Linux内核,内核和Android系统是相互独立的,因此正确的内核刷写并不会影响Android系统的使用,但是由于不同机型使用的内核不尽相同,错误的刷机操作可能导致设备变砖,通用性较差,因此本文不讨论重新编译内核刷入的注入手段,而是着眼于从内核驱动角度进行注入。

二进制补丁+内核驱动

如何退出fastboot模式?
既然注入的宗旨即注入代码到目标进程中,那我们是否可以尝试编写自己的内核驱动,然后自行刷入?答案是否定的,谷歌显然已经考虑到这一点,内核会对尝试刷入的驱动进行验证,只有通过验证的驱动才能够被成功刷入。为了绕过这一限制,我们可以通过将内核打上二进制补丁的方式来绕过验证。

通过查阅内核源代码,发现涉及内核驱动加载验证的函数为check_version和module_sig_check,分别进行版本校验和签名校验,那么只要对这两个函数打补丁,patch掉驱动加载校验就能够刷入自定义的驱动,进而实现内核态的代码注入了。

下图总结了这种方式进行注入的流程。(思路来自于《Android内核攻击面临的挑战》-张延清)流程21.png

内核态的注入对于当下的检测手段可谓是降为打击,但是较高的上手难度、繁琐的注入流程和国内较为封闭的开发社区环境使其还无法成为主流方式。

用户态的注入

Zygote注入

Xposed

Avatar

作为Android平台Hook框架鼻祖级的存在,Xposed直接替换了系统的app_process和libart以实现注入的目的,相比于当下的注入工具,它真实地替换了系统文件,简单粗暴,却在当时也十分有效。通过修改Zygote,使得每一个进程都被无差别注入。原生Xposed在实际应用中早已经退出舞台,其源码不再维护,并且不再适配安卓7以上的Android系统。但其留下的思想被传承,被拓展,产生了深远的影响,如今的逆向领域新作品层出不穷,但或多或少都能看到其影子。关于原生Xposed的解析可见此文章,本文将不再重复。

Magisk

随着谷歌对Android的不断优化迭代,大量的提权漏洞被修复了,当年那个“一键Root”的时代一去不复返,取而代之的是Magisk迅速崛起。早期的Magisk本身并未对应用做注入操作,但是其Systemless的特性使得开发者可以在不修改system分区的情况下修改系统。Magisk通过对boot.img进行patch,然后将其挂载到自己目录下,同时镜像出一个文件系统,所有的修改都是在其构建的虚拟文件系统上进行的。Magisk极大地降低了开发者提权、注入的成本,当下的很多流行工具依然依赖于Magisk。

在Magisk24.0以上的版本中,Magisk集成了Zygisk,类比于原生"Xposed的Zygote",Zygisk可以看作是“Magisk的Zygote”,其前身是Riru模块,由于Riru迁移后注入部分并未做大幅修改,故本文将单独讲讲Riru模块。

Riru

Riru即Zygisk的前身,其本质也是一个Magisk模块,在原生Xposed淡去后的很长一段时间,Riru+EdXposed的组合成为无数Android逆向工作者和搞机玩家必备的工具,Riru+EdXposed即注入+Hook组合。Riru的注入思想依然和Xposed有着相似之处,都替换了app_process来实现全局注入。那么Riru是如何实现对Zygote进程的注入的呢?下图是其官方的解释

image.png

分别回答了如何注入孵化器进程以及如何区别用户进程和系统服务进程,在这里我们重点看第一点。在Riru 22.0版本以前,采用替换系统库的方式即libmemtrack.so,当Zygote进程启动时会自动加载这个so来实现注入。而在后续的版本中则是通过修改 ro.dalvik.vm.native.bridge属性,让系统自动dlopen特定的so来实现,特定的so即libriruloader.so。另外,值得一提的是,Riru并非只能为”Xposed的继承者们“服务,其本身也支持模块化开发,例如你可以利用Riru注入frida-gadget来实现frida的持久化,具体实现见文章

EdXposed&LSPosed

严格来说,这两者跟注入并没有太大关系,但是其跟Riru、Magisk的关系过于密切,所以本文还是做简单介绍。

EdXposed

@ElderDrivers

作为Xposed的继承者,EdXposed在兼容丰富的Xposed模块的同时做了许多优化,例如官方支持免重启(是的,原生Xposed时代的模块开发者每次修改代码都要重启手机,当然这个弊端可以通过动态加载模块apk来解决)、Xposed Hide、黑白名单等。在Hook框架方面,EdXposed依赖Sandhook或YAHFA,开发者可以根据需要自行切换。

LSposed

@LSPosed

网传与EdXposed原同属于一个团队,但因理念不合而自立门户,得益于与Magisk的深度合作,LSposed大有后来居上的势头。与EdXposed不同,LSposed诞生之初就非常注重其Hook作用域,细腻的作用域管理在最大程度上保证了整体的系统安全和稳定。在Hook框架方面,早期版本沿用SandHook,后期切换为自研的LSPlant。

总的来说,Xposed和它的后继者们沿用了相似的注入思路,即对Zygote进行注入,由于安卓的所有应用都由Zygote孵化而来,那么fork出来的进程便都具有被注入的代码。这种方式使用门槛较低,即使是小白用户也能按照教程成功刷入,但同时也会暴露出较多的检测点。


重打包注入

顾名思义,重打包注入即对apk进行解包,修改内容物再重新打包的注入手段。其常见实现的手段有两种

1.替换Application

即修改AndroidManifest.xml中的Application,替换为注入者自定义的继承于目标应用原Application类的子类。个人猜想,Application替换注入的灵感可能首先来自于应用加固,既然加固厂商能够使用壳代码在原apk上加上一层保护,那么我们也能够使用同样的手段进行代码注入。当重打包目标是已经加固后的apk时,替换Application重打包便会形成一种”壳中壳“的神奇现象。

2.静态代码块注入

相比于替换Application,静态代码块更加符合暴力美学,这种方式直接修改反编译后的smali,插入需要注入的代码,而插入的位置则视情况而定,常用的位置是Application的static代码段,由于static代码块会先被执行,这样的注入方式往往能够使得注入者比目标应用的壳代码更早获得app的控制权。

需要注意的是,在安卓P以后的版本中,谷歌引入了AppComponentFactory,其执行时机比Application更早,通过对AppComponentFactory的控制能够识别重打包的应用进而进行对抗,事实上国内某些加固厂商已经在这么做了。

AndroidKiller

这是一款老旧到不进行手动配置无法使用的工具,但由于集解包、打包、查壳、反编译、修改为一身,其易用性仍值得一提。AndroidKiller的核心能力来自于各种插件插件,例如其重打包能力来自于Apktool,反编译能力来自于dex2jar,反编译后的jar包查看能力来自于jd-gui。在加固横行的今天,AndroidKiller重打包的app虽然可能连签名验证都无法通过,但是并不影响其在重打包领域占有一席之地。

Xpatch

Xpatch可能是当下最流行的重打包注入工具之一,旨在通过重打包手段,注入Xposed相关代码,使得目标app主动加载Xposed插件,从而达到免Root Hook应用的目的。与Zygote注入的方式相比,这种方式只作用域目标进程,也不需要Root权限。Xpatch支持Application替换和静态代码块注入两种方式,默认使用Application替换。

在重打包检测对抗方面,Xpatch内置了签名校验破解功能,具体实现逻辑为保存原apk的签名结果,Hook签名校验相关方法,填充原apk签名数据,此方式能够过掉一些简单的签名校验。此外,Xpatch还提供完整性校验破解功能,其内置了一个Xposed模块,通过hook libc的一些文件IO方法来将修改后的apk路径重定向到原apk中。

在Xposed检测对抗方面,由于Xpatch和其依赖的hook框架Sandhook开源,开发者可以自行修改特征重新编译来躲避Xposed检测。

通过重打包方式进行代码注入有着免Root、只作用于目标进程等优点,其弊端也很明显,对于签名校验,高纬度的对抗方可以通过直接和AMS通讯来获取签名,这使得所有针对签名相关方法的Hook都失效了。对于完整性校验,防御方可以通过内联SVC进行文件IO,从而绕过以hook libc的文件IO函数为目标的重定向攻击,以确保IO的真实性。

市面上还有其他的重打包注入工具,例如Ratel、LSPatch,其在代码注入的思路上与Xpatch大同小异,由于本文主要讨论注入,因此不再一一介绍。


Ptrace注入

Frida

@frida

在Ptrace注入领域,Frida已然是最流行的注入工具,在C/S工作模式下,其依赖Linux提供的Ptrace能力来实现注入。除此之外,Frida还提供了Inject模式及Gadget方式进行工作,其中Inject模式仍然依赖Ptrace,但是可以脱离PC使用。而Gadget方式只提供hook能力,需要用户自行实现注入,例如上文提到的利用Riru模块实现Frida-Gadget注入。在Ptrace时机方面,Frida支持attach模式和swapn模式,attach模式将注入一个正在运行的进程,swapn则fork一个新进程并注入。

Ptrace方式注入能够摆脱对系统文件例如app_process的入侵,也不需要对目标应用进行重打包工作,但是其暴露了大量的Ptrace痕迹,已经成为各大大安全厂商的重点防御对象。常见的防御手段如下

  1. 目标应用Ptrace自身,让调试工具无法再次Ptrace
  2. 通过/proc/self/status来读取Tracepid,以此判断是否被调试
  3. 检测特定的端口号,例如Frida、IDA、GDB调试时使用的端口号

虚拟环境注入

不同于Xposed入侵系统文件的思路,虚拟环境通过代理系统服务、IO重定向等手段,实现“在一个应用里运行其他应用”,而后的注入动作也就水到渠成了。

VirtualApp

iclauncher.png

作为Android平台虚拟环境的开山之作,VirtualApp(后简称VA)为后续的一系列衍生产品定下了基调,它在Android系统和用户APP之间包装了一层VA Framework,通过一系列代理手段“安装”应用到VA中,并拦截和篡改应用与系统间的交互来使应用正常启动。在文件存储方面,VA在Native层Hook了一系列文件IO方法将每个Client应用的私有目录映射到VA下的独立空间。在以上能力的加持下,VA不仅可以用于代码注入和Hook,还能用于应用多开、应用行为审计等场景。

VA发布后,诞生了如Virtual Xposed的二创作品,增加了一种新的注入Xposed的姿势。(ps:Xposed对于Android逆向界影响颇深,无论是其模块的代码风格还是繁多的模块仓库都被后继者们尽力兼容,尽管原生Xposed甚至已经无法应对如今稍有难度的逆向对抗场景。)如今VA早已商业化,网上流传的源代码连64位应用都无法支持,但是仍然具有借鉴意义。

针对虚拟环境的检测,可以通过以下两个方向进行

  1. 内联SVC进行文件IO(同重打包检测)来确认文件是否被重定向
  2. 检测动态代理痕迹

两仪

两仪是由太极框架作者开发的一个Android 系统级容器,和VA相比,两仪支持HAL层的虚拟化,这意味着两仪拥有虚拟硬件的能力,但是其ROM还未开源,仍旧处于半成品状态,在此不做过多介绍。

ELF文件感染注入

ELF文件感染是一种古老的注入手段,其核心思路是二进制补丁,直接将代码植入被感染的文件中

LIEF

LIEF是一款文件解析工具,它的作用范围不仅仅局限于ELF文件,但由于我们本文讨论的是Android平台,将只介绍so库文件的感染。LIEF能够自动化地将某个ELF文件链接到目标文件中,当目标文件被dlopen时,系统将优先加载其链接库(执行链接库的init_arry段)。

以Riru的so为例,感染前

image.png

使用LIEF将名为libcube.so的库链接到libriru,感染后

image.png

同时LIEF也官方支持Frida注入,见文章。ELF文件感染方式适用面广,上手难度较高(注入后的so逻辑根据需要自己实现),其最致命的弊端是感染后的ELF文件于原文件的md5不同,作用于安卓apk的结果就是无法通过文件一致性校验(与重打包方式相同)。因此,可以考虑在不破坏目标apk的情况下做感染,例如感染一些系统so库。

总结

注入与反注入对应于逆向中的攻防,没有绝对的安全防护,也没有普适的攻击手段。上文从原生Xposed出发,介绍了发展至今的各种“Xposeder”们,下面用一张图理清他们之间的关系。

一张图理清Xposed们的关系.png

下图对上文提到的注入手段做个整理

image.png


标题:浅谈安卓逆向工程-注入篇
作者:Cubeeeee
地址:http://blog.nps.fuguicun.com/articles/2022/08/23/1661250695450.html