Android游戏Ptrace注入介绍

发表于2015-08-19
评论1 5.8k浏览

一 外挂简介

1) 针对游戏:热门的Android游戏

2) 外挂版本:xx助手Android2.0.3beta版。

3) 外挂功能:实现外挂模块的加载及挂钩。

4) 外挂类型:通用的注入功能实现。

 

二 功能分析

1. 安装包解析

使用apktool工具反编译出xx助手apk的内容,其中在assets目录中发现有名为“injectso”的可执行文件,根据文件名称,猜测此为xx助手向其它进程注入so库的功能库。

 

2. injectso注入功能库解析

使用IDA打开injectso文件,找到main函数的代码,使用F5查看C代码:


可以看到,进入main函数后,先检查参数数量是否为5,然后输出日志到/mnt/sdcard/xxlog.log文件中。我们先启动xx助手,然后再打开此日志文件,发现内容为:


可以看到,调用此模块功能的后4个参数为:10229、/data/data/com.xxAssistant/lib/libxxghost.so、loadResAndPlugin、/data/app/com.xxAssistant-2.apk。即1个ID、1个模块全路径、函数符号和1个Apk包路径。

再往下看代码:


这里把10229转换成整型,在模块全路径中搜索'/',即找出模块的名称,再调用sub_9288(10229,"libxxghost.so")得到一个值v20,根据72行的日志可以得出,这就是获取一个进程中的模块基址,也就得出10229就是一个进程号。那么,10229是一个普通进程吗?

通过在手机终端的命令行下输入ps | grep 10229,得出的结果是zygote进程。

再看下zygote的进程空间,输入cat /proc/10229/maps | grep data(需要root权限):


发现确实有libxxghost.so模块,即xx助手确实往zygote进程注入了自己的模块。

继续往下看代码:

首先判断模块基址是否找到,防止重复注入;sub_BEF4、sub_C21C、sub_C444是检查Android的SeLinux功能是否启用,如果启用,则关闭,等注入模块后再打开。注入模块的功能代码即在sub_94AC函数中,原理即基于ptrace函数簇。

 

3. ptrace函数簇介绍

ptrace is a system call found in several Unix and Unix-like operating systems. By using ptrace (the name is an abbreviation of "process trace") one process can control another, enabling the controller to inspect and manipulate the internal state of its target. ptrace is used by debuggers and other code-analysis tools, mostly as aids to software development.——摘自维基百科。

翻译过来就是,ptrace函数簇是类unix系统自带的系统调用,提供控制调试其它进程的功能。而linux也是类unix系统的一员,所以也包含ptrace系统调用。linux上的很多调试工具如gdb、strace等都是通过ptrace函数簇实现的。

 

详细的功能描述:

提供父进程观察和控制另一个进程执行的机制,同时提供查询和修改另一进程的核心影像与寄存器的能力。主要用于执行断点调试和系统调用跟踪。

父进程可通过调用fork,接着指定所产生的子进程的PTRACE_TRACEME行为,最后使用exec等操作来初始化一个进程跟踪。可替代的做法是,父进程通过PTRACE_ATTACH请求跟踪一个现存进程的执行。

当子进程被跟踪时,每次接收到信号都会停止执行,即使不对信号进行处理(SIGKILL信号除外)。父进程下次执行wait调用时,会接收到核心的通告,

并可能检查和修改已停止的子进程。父进程使子进程继续执行,并有可能忽略接收到的信号。此外,有root权限的进程可以调试任何非root进程。

 

用法:

#include <sys/ptrace.h>

long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data);

参数:

request:请求执行的行为,可能选择有

PTRACE_TRACEME(0) //指示父进程跟踪某个子进程的执行。任何传给子进程的信号将导致其停止执行,同时父进程调用wait()时会得到通告。之后,子进程

调用exec()时,核心会给它传送SIGTRAP信号,在新程序开始执行前,给予父进程控制的机会。pid, addr, 和 data参数被忽略。

以上是唯一由子进程使用的请求,剩下部分将由父进程使用的请求。

PTRACE_PEEKTEXT(1), PTRACE_PEEKDATA(2) //从子进程内存空间addr指向的位置读取一个字,并作为调用的结果返回。Linux内部对文本段和数据段不加区分,

所以目前这两个请求相等。data参数被忽略。

PTRACE_PEEKUSR(3) //从子进程的用户区addr指向的位置读取一个字,并作为调用的结果返回。

PTRACE_POKETEXT(4), PTRACE_POKEDATA(5) //将data指向的字拷贝到子进程内存空间由addr指向的位置。

PTRACE_POKEUSR(6) //将data指向的字拷贝到子进程用户区由addr指向的位置。

PTRACE_GETREGS(12), PTRACE_GETFPREGS(14) //将子进程通用和浮点寄存器的值拷贝到父进程内由data指向的位置。addr参数被忽略。

PTRACE_GETSIGINFO(0x4202) //获取导致子进程停止执行的信号信息,并将其存放在父进程内由data指向的位置。addr参数被忽略。

PTRACE_SETREGS(13), PTRACE_SETFPREGS(15) //从父进程内将data指向的数据拷贝到子进程的通用和浮点寄存器。addr参数被忽略。

PTRACE_SETSIGINFO(0x4203) //将父进程内由data指向的数据作为siginfo_t结构体拷贝到子进程。addr参数被忽略。

PTRACE_SETOPTIONS(0x4200) //将父进程内由data指向的值设定为ptrace选项,data作为位掩码来解释,由下面的标志指定

PTRACE_O_TRACESYSGOOD //当转发syscall陷阱(traps)时,在信号编码中设置位7,即第一个字节的最高位。例如:SIGTRAP | 0x80。这有利于追踪者

识别一般的陷阱和那些由syscall引起的陷阱。

PTRACE_O_TRACEFORK //通过 (SIGTRAP | PTRACE_EVENT_FORK << 8) 使子进程下次调用fork()时停止其执行,并自动跟踪开始执行时就已设置SIGSTOP

信号的新进程。新进程的PID可以通过PTRACE_GETEVENTMSG获取。

PTRACE_O_TRACEVFORK //通过 (SIGTRAP | PTRACE_EVENT_VFORK << 8) 使子进程下次调用vfork()时停止其执行,并自动跟踪开始执行时就已设置SIGSTOP

信号的新进程。新进程的PID可以通过PTRACE_GETEVENTMSG获取。

PTRACE_O_TRACECLONE //通过 (SIGTRAP | PTRACE_EVENT_CLONE << 8) 使子进程下次调用clone()时停止其执行,并自动跟踪开始执行时就已设置SIGSTOP

信号的新进程。新进程的PID可以通过PTRACE_GETEVENTMSG获取。

PTRACE_O_TRACEEXEC //通过 (IGTRAP | PTRACE_EVENT_EXEC << 8) 使子进程下次调用exec()时停止其执行。

PTRACE_O_TRACEVFORKDONE //通过 (SIGTRAP | PTRACE_EVENT_VFORK_DONE << 8) 使子进程下次调用exec()并完成时停止其执行。

PTRACE_O_TRACEEXIT //通过 (SIGTRAP | PTRACE_EVENT_EXIT << 8) 使子进程退出时停止其执行。子进程的退出状态可通过PTRACE_GETEVENTMSG

PTRACE_GETEVENTMSG(0x4201) //获取刚发生的ptrace事件消息,并存放在父进程内由data指向的位置。addr参数被忽略。

PTRACE_CONT(7) //重启动已停止的进程。如果data指向的数据并非0,同时也不是SIGSTOP信号,将会作为传递给子进程的信号来解释。那样,父进程可以控制是否将

一个信号发送给子进程。 addr参数被忽略。

PTRACE_SYSCALL(24), PTRACE_SINGLESTEP(9) //如同PTRACE_CONT一样重启子进程的执行,但指定子进程在下个入口或从系统调用退出时,或者执行单个指令后停止

执行,这可用于实现单步调试。addr参数被忽略。

PTRACE_SYSEMU, PTRACE_SYSEMU_SINGLESTEP //用于用户模式的程序仿真子进程的所有系统调用。

PTRACE_KILL(8) //给子进程发送SIGKILL信号,从而终止其执行。data,addr参数被忽略。

PTRACE_ATTACH(0x10) //衔接到pid指定的进程,从而使其成为当前进程的追踪目标。

PTRACE_DETACH(0x11) //PTRACE_ATTACH的反向操作。

pid:目标进程标识。

addr:执行peek和poke操作的目标地址。

data:对于poke操作,存放数据的地方。对于peek操作,获取数据的地方。

返回说明:

成功执行时,PTRACE_PEEK*请求返回所请求的数据,其它返回0。失败返回-1,errno被设为以下的某个值。由于一个成功的PTRACE_PEEK*请求可能返回-1,

决定错误是否发生前,调用者应检查errno。

EBUSY:分配和释放调试寄存器时出错

EFAULT:读写不可访问的内存空间

EINVAL:尝试设置无效选项

EIO:请求无效,或者尝试读写父子进程不可访问的空间

EPERM:没有权限追踪指定的进程

ESRCH:指定的子进程不存在,或者当前正由调用者追踪

 

4. injectso的ptrace功能分析

上面讲到,xx助手的模块加载功能是在sub_94AC函数中,它的调用如下:

其中,v18是zygote进程的pid,*(char **)(v15 + 8)是加载模块的全路径,*(char **)(v15+12)是加载模块的初始化函数,*(char **)(v15 + 16)是apk路径。它的函数原型为:

完整的流程如下:

a. attach上目标进程。

16即PTRACE_ATTACH的常量值,v102是参数a2,即模块全路径。经过attach,则可以对目标进程做更多深入的操作了,后面的操作是出错处理。

 

b. wait进程状态更改,然后获取当前的寄存器状态。

获取进程状态是前面attach操作的收尾,保证attach成功。12是PTRACE_GETREGS的常量值,把目标寄存器列表(在linux系统中是一个struct pt_regs结构)保存到dword_1003c,这是一个全局变量指针。寄存器和地址的对应关系如下图:

到此步,表示已经能够正常调试目标进程了。

 

c. 准备在目标进程中分配出一块内存空间,以存放shellcode。

In computer security, a shellcode is a small piece of code used as the payload in the exploitation of a software vulnerability. It is called "shellcode" because it typically starts a command shell from which the attacker can control the compromised machine, but any piece of code that performs a similar task can be called shellcode. Because the function of a payload is not limited to merely spawning a shell, some have suggested that the name shellcode is insufficient.[1] However, attempts at replacing the term have not gained wide acceptance. Shellcode is commonly written in machine code.-摘自维基百科。


翻译过来,就是shellcode是一小块代码,被用作目标进程的负载,加载自己想要执行的程序功能。它一般是用机器码实现的。

首先把前面获取到的寄存器状态备份一份到dword_10084,备份寄存器和地址的对应关系如下:

然后通过libc.so共享库句柄获取mmap、munmap、mprotect函数指针,然后通过sub_9288分别获取当前进程(即调用injectso的root进程)和目标进程的libc.so的模块基址,结合函数指针和模块的相对位置计算出目标进程的mmap、munmap、mprotect函数地址。


之后,把mmap的函数地址赋给寄存器r15,根据munmap函数地址末尾是否为1判断是否是thumb,如果是,则把最后的1去掉,同时设置cpsr状态寄存器为thumb状态,否则清除cpsr的thumb标记们;构造mmap函数的其它参数,分别为r0=0,r1=0x4000,r2=7,r3=34,lr=0,sp-=4,*sp=0,sp-=4,*sp=-1。这里只所有要改变sp寄存器并赋值,是因为arm的函数调用最多只能用4个寄存器,其余需要通过堆栈传递;赋值是通过PTRACE_POKEDATA(常量值是5)完成的。


最后,通过PTRACE_SETREGS(常量值是13)把修改好的寄存器设置到目标进程(注意,前面的堆栈赋值已经生效了)。到这一步,调用mmap函数分配内存空间的准备工作就完成了。

 

d. 在目标进程中调用mmap函数,分配出内存空间。


这里,是通过2个PTRACE_SYSCALL(常量值是24)和sub_9440完成的,sub_9440是waitpid的包裹函数。为什么是两个呢?因为mmap是系统调用,所以使用SYSCALL的操作,而系统调用分进入和退出两种状态,所以需要做2次操作。

之后,保存mmap的调用结果到v98,即目标进程中分配出来的内存空间地址。注意,如果调用分配的地址为0,就把地址设到了sp-0x4000的位置。

 

e. shellcode的准备。



先获取目标进程中dlopen,dlsym,dlclose,dlerror函数地址,方法和前面的mmap一致,如果其中任意1个地址为0,则不进行注入。这是因为shellcode中使用到了这些函数。最后面1句是把初始的寄存器重新拷贝到要使用的寄存器中。

从这里看出,shellcode是通过汇编嵌入到injectso中的,然后拷贝到堆栈v130中。然后,把前面获取的dlopen、dlsym、dlclose、dlerror等地址写入shellcode中的相对位置。并把sub_94AC参数(即模块全路径、符号名、apk名称)拷贝进入shellcode中的相对位置。最后,设置sp=v98+0x3c00,pc=v98+0x3c00,cpsr清除thumb标记,通过循环PTRACE_POKEDATA操作把堆栈中的shellcode内容写入目标进程。之所以,这里sp和pc值相同,是因为在arm处理器中,默认堆栈sp是满递减的,不会和指令发生重叠。这里注意一点,如果前面mmap分配内存空间失败,是使用初始的sp-0x4000作为shellcode的空间的,而为了让shellcode有执行权限,通过mprotect设置该空间的权限为可读可写可执行,同时让lr=shellcode地址,这样在执行完mprotect后接着执行shellcode。

 

shellcode很简单,就是通过前面获取的dlopen打开模块"/data/data/com.xxAssistant/lib/libxxghost.s",同时调用模块的初始化函数"loadResAndPlugin", 参数是"/data/app/com.xxAssistant-2.apk",如果出错,通过dlerror返回错误字符串。

 

f. 调用shellcode。

调用shellcode很简单,先通过PTRACE_SETREGS(常量值是13)把设置好的寄存器设置到目标进程,再通过PTRACE_CONT(常量值是7)操作让目标进程继续执行,然后再以一个waitpid结尾。这里之所以用PTRACE_CONT,是因为shellcode是一段代码,并不是系统调用。

 

g. 获取shellcode的执行结果。

这里通过PTRACE_GETREGS获取到执行后的寄存器,如果R1不为0,表示调用失败,同时R2保存的是dlerror的错误字符串地址,通过PTRACE_PEEKTEXT(常量值是1)获取内容。

 

h. ptrace调用处理。

如果前面调用mmap分配内存空间不成功的处理。

如果调用mmap分配内存空间成功,则调用munmap释放内存空间,方法和前面调用mmap相同。

处理完后,调用PTRACE_SETREGS(常量值为13)恢复目标进程的原始寄存器值,然后通过sub_B89C函数停止对目标进程的调试,也解除了目标进程的调试状态。至此,ptrace的模块注入功能已经完整结束。

 

四,分析及检测思路总结

本外挂分析报告通过IDA静态反汇编xx助手android安装包assets目录下的injectso和injectso_21文件得出。

 

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引