Unidbg学习笔记(九):系统调用层补环境 Unidbg学习笔记(九):系统调用层补环境系统调用层的问题和 JNI、文件层有一个本质不同:前两者是 Unidbg 明确把责任交给你,你不补就肯定不行;系统调用层是 Unidbg 自己想干却没干好。理解这个区别,是从“补环境工人”晋级到“模拟器贡献者”的分水岭。上一篇把你留在了哪里第八篇讲完文件系统之后,你已经掌握了三个通道:JNI(90% 工作量)、文件(~8%)、系统调用(剩下的小部分但很扎心)。这一篇专门讲系统调用通道。需要先调整一个心态预期:这一篇不是教你“怎么补一堆系统调用”,而是教你“什么时候该出手、什么时候该绕开”。系统调用层的好消息是问题数量不多,坏消息是每一个都很硬核。心态切换:你不是在补环境,你是在帮 Unidbg 打补丁回想一下前面三个通道的角色定位:通道谁的责任你扮演什么JNI你的责任—— Unidbg 直接把请求交给 AbstractJniJava 替身演员文件你的责任—— Unidbg 通过 IOResolver 把请求交给你虚拟文件系统提供者系统调用Unidbg 的责任—— 它自己有 SyscallHandler 试图处理???到了系统调用层,分工发生了变化。Unidbg 对系统调用的态度是“我自己来”:它内置了一个ARM32SyscallHandler和ARM64SyscallHandler实现了大约 100+ 个常见系统调用(read / write / mmap / open / brk / clock_gettime / …)大部分时候 SO 调系统调用,你完全感知不到那为什么还要补?因为 Unidbg 不是 Linux 内核,它只是一个有限的近似。这个近似有三种“漏洞”:漏洞 1:有些系统调用根本没实现(比如getrusage),SO 一调就崩。漏洞 2:有些系统调用只实现了一半(比如clock_gettime只支持CLOCK_REALTIME和CLOCK_MONOTONIC,不支持CLOCK_BOOTTIME),SO 传错参数就崩。漏洞 3:有些系统调用看似正常返回,但返回的值和真机不一致(比如stat64返回的 inode、getcpu始终返回 0),SO 不崩,但拿到的数据是假的。所以你的角色变了:在 JNI / 文件层,你是演员:从空舞台开始演 Android 系统在系统调用层,你是修理工:Unidbg 自己想演但演不好的地方,你拿胶带补一下这个心态变化非常关键。它意味着:不要主动出击。如果 Unidbg 默认行为已经够用,碰都不要碰系统调用层报错才介入。syscall NR=xxx not implemented这种明确报错才是你的工单优先考虑绕开。后面会讲,很多系统调用可以在库函数层hook 掉,根本不用碰 syscall明白这一点后,下面看具体的三类问题。三类系统调用问题按“危险程度从低到高”排列。前两类看得见摸得着,第三类是隐形杀手。类型一:完全未实现 — 最容易发现的现象:java.lang.UnsupportedOperationException: syscall NR=165 not implemented at com.github.unidbg.linux.ARM64SyscallHandler.hook(ARM64SyscallHandler.java:227) at com.github.unidbg.arm.backend.UnicornBackend$11.hook(...) ...NR=165这个数字就是系统调用号。查一下 ARM64 syscall 表(man 2 syscall 或者 [chromium.googlesource.com 的 syscalls.h](https://chromium.googlesource.com/linux-syscall-support/+/refs/heads/master/linux_syscall_support.h)),165 对应的是getrusage。为什么 Unidbg 没实现 getrusage?因为它在普通 App 里基本用不到。getrusage是查询进程资源使用情况(CPU 时间、最大内存占用、缺页次数)的接口,主要用在性能分析、运行时统计场景。Unidbg 的设计哲学是“覆盖最常用的 80%”,剩下的 20% 留给用户自己补。两种处理思路(这里是关键):思路 A:在 SyscallHandler 加 casepublicclassMySyscallHandlerextendsARM64SyscallHandler{publicMySyscallHandler(SvcMemorysvcMemory){super(svcMemory);}@Overridepublicvoidhook(Backendbackend,intintno,intswi,Objectuser){Emulator?emulator=(Emulator?)user;if(intno==2){// 软中断, 进入 syscall 流程// ARM64 用 x8 传 syscall numberintNR=backend.reg_read(Arm64Const.UC_ARM64_REG_X8).intValue();switch(NR){case165:{// getrusagehandleGetrusage(emulator,backend);return;}}}// 其它情况交给父类默认处理super.hook(backend,intno,swi,user);}privatevoidhandleGetrusage(Emulator?emulator,Backendbackend){// x0 = who (RUSAGE_SELF=0 / RUSAGE_CHILDREN=-1 / RUSAGE_THREAD=1)// x1 = struct rusage* 用户空间指针intwho=backend.reg_read(Arm64Const.UC_ARM64_REG_X0).intValue();PointerrusagePtr=UnidbgPointer.register(emulator,Arm64Const.UC_ARM64_REG_X1);// 简化处理: 把整个 struct rusage 全部置零, 表示资源占用为 0// struct rusage 大小: ARM64 上是 144 字节if(rusagePtr!=null){rusagePtr.write(0,newbyte[144],