type
status
date
slug
summary
tags
category
icon
password
 
 
 

0x00 参考

  1. https://blog.seeflower.dev/archives/84/#title-7
  1. https://evilpan.com/2021/12/26/art-internal/
  1. https://zhuanlan.zhihu.com/p/523692715
  1. https://blog.csdn.net/weixin_47668107/article/details/114251185
  1. https://juejin.cn/post/7045575502991458340
  1. https://juejin.cn/post/7384992816906747913
  1. https://blog.quarkslab.com/dji-the-art-of-obfuscation.html
 
💡
如果在Android12+ 出现无法解析libart.so或自己也无法找到对应符号的情况可能需要进行art回滚,参考https://devblog.lac.co.jp/entry/20221021

0x01 引言

eBPF作为运行在内核的的一大追踪利器,其有着隐蔽性高,不需要重新编译内核或系统等优点。而在Android平台中,字节码主要在ART虚拟机中被执行,那么其实只需要追踪ART虚拟机执行过程中的一些关键函数即可被动的捕捉到DexFile,从而实现脱壳。因此实现此小Demo,旨在抛砖引玉。
该工具可作为FRIDA-DEXDump等被动式脱壳工具的替代,并且有着更好的隐蔽性,完全不需要PTRACE附加调试目标程序或注入动态链接库。但是由于eBPF的局限性,其无法替代FART等基于主动调用的脱壳工具。除此之外,对于代码抽取等情况并未实现,各位大佬有需要可以自行实现。
 

0x02设计与实现

1. ART虚拟机解释器

ART 虚拟机执行字节码有两种方式:一是解释执行,二是执行编译后得到的机器码。而在解释执行这部分又可以分为两种,以Android13为例,一种是走Nterp 高效解释器,另外一种走Switch解释器,一般都是走前一种,后一种多用于指令追踪等情景。
  • Nterp是ART为优化字节码解释执行而引入的一种改进型解释器,在Android 11分支上就被引入了,只不过仅实现了x86_64架构,而在Android S上实现了arm和arm64架构。Nterp采用了和Native方法一样的栈帧结构,并且译码和翻译执行全程都由汇编代码实现,进一步拉进解释器和compiled code的性能差距。如果采用这种方法,执行路径直接到 interpreter::ExecuteNterpImpl
  • 如果不支持Nterp,则一个方法的执行路径从art_quick_to_interpreter_bridgeartQuickToInterpreterBridge再到EnterInterpreterFromEntryPoint 再进入interpreter::Execute最终走到interpreter::ExecuteSwitch
 
利用eBPF对这两条路径进行追踪,除此之外,加入对art::verifier::ClassVerifier::VerifyClass 的追踪,防止漏掉一些动态加载的DexFile(经过测试其实还是走这两条路径,但是可能出于其他原因没办法追踪到),能Dump的更全。
下面的结构为Android 13.0.0ArtMethod的结构,其中entry_point_from_quick_compiled_code_ 保存了这个方法的执行地址。
当要执行某个方法时,运行时必须首先检查该方法所属的类是否已加载。如果未加载,则Runtime将加载并链接该类。在这个过程中ART会调用 UpdateClassAfterVerification更新已经验证过的类中所有方法的入口点。如果能够使用 nterp,则将原先使用switch interpreter的方法切换到 nterp
具体做法是遍历该类的所有方法,检查它们当前的入口点是否为QuickToInterpreterBridge,如果是,则调用 Instrumentation::InitializeMethodsCode 来更新入口点。这样,完成验证的类就可以切换到更高效的 nterp 解释器执行
 

2. 设计与实现

因此在设计上,通过eBPFUprobe去动态追踪libart.so三个关键函数,并从参数中提取ArtMethod指针,进一步从中获取DexFile的指针并且获取DexFile的起始地址和size,即可实现被动式脱壳,即当方法被执行时即可获取到其所在DexFile。
具体而言,在内核层通过eBPF追踪三个关键函数,提取到DexFile 的起始地址和size信息,然后将其传回用户态go程序。
notion image
go程序读取到事件缓存区中的数据后,立即通过远程进程内存访问获取到dex文件并进行Dump。Linux远程进程内存访问可通过 process_vm_readvprocess_vm_writev来进行。但是调用这两个 syscall 来实现远程进程访问会被目标检测到。原理是通过是否内存缺页(例如通过mincore)来判断特定内存是否被访问过。但是在脱壳场景中是没有任何问题的。
💡
踩坑:前面在没有想到通过process_vm_readv 读取内存时,试图在eBPF程序内直接读取用户内存,并且将其分片带出到用户态(缓存区有大小限制),但是这个过程中会有大量的数据传输,极易丢失数据,从而使dump出的dex可用性较差。
notion image
 

3. 现有不足

  • 非主动调用脱壳
    • 因为是基于eBPF的脱壳方式,只能实现被动的脱壳,无法实现类似于FART的主动调用脱壳,这是一个遗憾。需要哪部分的代码就必须使虚拟机执行到对应的部分才可以成功Dump,但是这对于一般的场景已经够用了。
  • 暂未实现抽取壳被抽取代码的部分
    • 本项目只做一个小的demo,旨在抛砖引玉,对于代码抽取等情况并未实现,各位大佬有需要可以自行实现。
 

0x03 测试与使用说明

1. 环境说明

需要有一台支持eBPF的设备,最好内核版本为5.10及以上,需要获取root权限,保证其Uprobe是可用的(一般是开启的)
notion image
 

2. 删除OAT优化文件

如果不删除优化文件 ,代码将会以其他方式执行从而无法在上述两个函数位置进行拦截,或获得的dump文件是cdex结构,不方便从中提取dex。因此,阻止代码的优化执行是一切的开端。
💡
注意可以选择(非必要)关闭dex文件的checksum校验以防止出现不可预期的校验错误,以JADX 1.5.1为例
notion image
 

3. (可选)提取偏移

程序自动解析并获取所需要的三个函数的偏移,但是如果失败了则需要手动指定。
这一步需要获取设备上libart.so并进行反编译,从中提取三个函数的偏移。
以测试设备Android13为例,其他版本类似
  • 首先需要找到ExecuteNterpImpl 函数的偏移0x200090 ,这是最主要的解释器,大部分方法都会走这里。
    • notion image
  • 其次还需要art::interpreter::Execute 的偏移,防止漏掉走Switch解释的方法。
    • notion image
  • 还需要art::verifier::ClassVerifier::VerifyClass 的偏移,防止漏掉其他动态加载的dex
    • notion image
 

4. 开始Dump

 
notion image
notion image
 

5. 编译方法

拉取代码后简单修改Makefile
notion image
然后make即可,在这之前需要有go的编译环境
 
MRCTF2022 Stuuuuub WPLakeCTF At your Service 题解
Loading...
LLeaves
LLeaves
Happy Hacking
最新发布
基于eBPF实现一个简单的隐蔽脱壳工具-eBPFDexDumper
2025-1-9
LakeCTF At your Service 题解
2024-12-13
PendingIntent-security
2024-12-1
Android grantUriPermission与StartAnyWhere
2024-11-30
eBPF实践之修改bpf_probe_write_user以对抗某加固Frida检测
2024-11-10
CVE-2024-31317 Zygote命令注入提权system分析
2024-11-10
公告