|
在android系统中,进程之间是相互隔离的,两个进程之间是没办法直接跨进程访问其他进程的空间信息的。那么在android平台中要对某个app进程进行内存操作,并获取目标进程的地址空间内信息或者修改目标进程的地址空间内的私有信息,就需要涉及到注入技术。
通过注入技术可以将指定so模块或代码注入到目标进程中,只要注入成功后,就可以进行访问和篡改目标进程空间内的信息,包括数据和代码。
Android的注入技术的应用场景主要是进行一些非法的操作和实现如游戏辅助功能软件、恶意功能软件。
下面主要进行对zygote注入、ptrace注入、修改so文件注入,这三种注入方式进行详细解析。
zygote注入
zygote是一个在android系统中是非常重要的一个进程,因为在android中绝大部分的应用程序进程都是由它孵化(fork)出来的,fork是一种进程复用技术。也就是说在android系统中普通应用APP进程的父亲都是zygote进程。
zygote注入目的就是将指定的so模块注入到指定的APP进程中,这个注入过程不是直接向指定进程进程注入so模块,而是先将so模块注入到zygote进程。
在so模块注入到zygote进程后,在点击操作android系统中启动的应用程序APP进程,启动的App进程中包括需要注入到指定进程的so模块,太都是由zygote进程fork生成,因而在新创建的进程中都会包含已注入zygote进程的so模块。
这种的注入是通过间接注入方式完成的,也是一种相对安全的注入so模块方式。目前xposed框架就是基于zygote注入。
zygote注入so模块流程
1.通过注入器将要注入的so模块注入到zygote进程;
2.手动启动要注入so模块的APP进程,由于APP进程是通过zygote进程fork出来的,所以启动的APP进程都包含zygote进程中所有模块;
3.注入的so模块劫持被注入APP进程的控制权,执行注入so模块的代码;
4.注入so模块归还APP进程的控制权,被注入进程正常运行。
Zygote注入器的实现流程
(注入器主要是基于ptrace注入shellcode方式的进程注入)
通过ptrace进行附加到zygote进程。
调用mmap申请目标进程空间,用于保存注入的shellcode汇编代码。
执行注入shellcode代码(shellcode代码是注入目标进程中并执行的汇编代码)。
调用munmap函数释放申请的内存。
通过ptrace进行剥离zygote进程。
ptrace注入
ptrace注入实现上分类:
通过利用ptrace函数将shellcode注入远程进程的内存空间中,然后通过执行shellcode加载远程进程so模块。
通过直接远程调用dlopen、dlsym、dlclose等函数加载被注入so模块,并执行指定的代码。
ptrace直接调用函数注入流程:
通过利用ptrace进行附加到要注入的进程;
保存寄存环境;
远程调用mmap函数分配内存空间;
向远程进程内存空间写入加载模块名称和函数名称;
远程调用dlopen函数打开注入模块;
远程调用dlsym函数或需要调用的函数地址;
远程调用被注入模块的函数;
恢复寄存器环境;
利用ptrace从远程进程剥离。
关键的ptrace直接调用系统函数实现
ptrace的shellcode注入原理
shellcode注入就是通过将dlopen/dlsym库函数的操作放在shellcode代码中,注入函数只是通过对远程APP进程进行内存空间申请,接着修改shellcode 代码中有关dlopen、dlsymdlclose等函数使用到的参数信息,然后将shellcode代码注入到远程APP进程申请的空间中,最后通过修改PC寄存器的方式来执行shellcode 的代码。
ptrace注入shellcode的详细步骤
1.在shellcode中编写好dlopen、dlsym等函数的调用,来加载so模块和执行函数,但需要将参数地址、函数地址、寄存器地址先随便填充值为我们真实地址保留;
2.附加到远程APP进程、保存APP进程中寄存器的数据,为后面恢复远程进程的继续执行准备;
3.向远程APP进程申请内存空间,选好shellcode存放的具体位置,准备存放shellcode和参数数据;
4.计算本地so模块函数对应到,远程APP进程中的so模块函数地址,填充到shellcdoe中的参数中。计算好库函数参数、寄存器存值相对shellcode起始位置的偏移再加上远程进程中shellcode存放的起始位置,得到的结果就是远程进程的内存空间中这些参数存放的位置,将这些地址填充到shellcode的参数中;
5.设置寄存器的值来让执行库函数;
6.恢复寄存器的值让远程进程继续正常执行。
修改ELF文件注入
在android平台Native层的可执行文件SO文件,它是属于ELF文件格式,通过修改ELF文件格式可以实现对so文件的注入。
通过修改ELF二进制的可执行文件,并在ELF文件中添加自己的代码,使得可执行文件在运行时会先执行自定义添加的代码,最后在执行ELF文件的原始逻辑。
修改二进制ELF文件需要关注两个重要的结构体:
ELF Header、Program Header Table
其中ELF Header 它是ELF文件中唯一的,一个固定位置的文件结构,它保存着Program Header Table和Section Header Table的位置和大小信息。
Program Header Table 它保存ELF文件的加载过程中各Section的内存映射和依赖库相关信息,用来告诉android系统中如何创建进程映像。
修改ELF文件实现so文件注入实现原理为:通过修改 Program Header Table中的依赖库信息,添加自定义的so文件信息,APP进程运行加载被该修改过的ELF文件,它也同时会加载并运行自定义的so文件。
Program Header Table表项结构
typedef struct{
Elf32 Word p_type; P Segment type /
Elf32_0ff p_offset; / Segment file offset "/
Elf32 Addr p_vaddr; /* Seoment virtual address */
Elf32 Addr p_paddr; Seqment physicat address sr
Elf32 Word p_filesz; Segment size in file/
Elf32 Word p_memsz; / Segment size in memory */
Elf32 Word p_flags; *segment. flags
程序头表项中的类型选项有如下
/* Legal values for p type (segment type)./
#define PT NULL / Program header table entry unused*
#define PT LOAD 1 LoadabLe program seament *y
#define PT DYNAMIC 2 /* Dynamic Linking information */
#define pT INTERP3 /*:program interpretep */
4
#define PT NOTE /* Auxiliary information/
#define PT SHLIB /* Reserved /
#define PT PHDR 6 * Entry for header table itself */
#define PT TLS /*:Thrent lornlstonnne senmente/
当程序头表项结构中的类型为PT_DYNAMIC也就是动态链接信息的时候,它是由程序头表项的偏移(p_offset)和p_filesz(大小)指定的数据块指向.dynamic段。这个.dynamic段包含程序链接和加载时的依赖库信息。
修改ELF文件的注入实现过程
1.修改.dynamic段指向的字符串表中添加 自定义的so模块名称;
2.通过修改Program Header Table中添加PT_LOAD表项,新添加的表项将保护so模块名称的字符串表数据映射到内存中。同时将Program Header Table移动到文件末尾;
3.修改.dynamic段的数组数据,使得指向新的字符串表,并指向自定义的so模块名称;
4.修改ELF HEADER结构中 Program Header Table的位置信息,并指向新的Program Header Table。
关键ELF文件修改代码实现
uint32 t ModifyelfInjectFile(char *pbyElffile, uint32 t ulfileSize){
Elf32_Ehdr* pEhdr =(Elf32_Ehdr*)pbyElffile;//将程序头表移动到文件尾以便新增表项
Elf32 Phdr* pPhdr=(Elf32 Phdr*)(pbyElffile + pEhdr->e phoff); uint32.t ulPhdrSize = pEhdr->e phnum * pEhdr->e phentsize: memcpy(pbyElfFile + ulfileSize,pPhdr,ulPhdrsize); pPhdr=(Elf32_Phdr*)(pbyElfFile + ulfileSize);//增加一个程序头表项
Elf32 Phdr *pPTLoad =pPhdr+pEhdr->e phnum;
pEhdr->e_phoff =ulFileSize;pEhdr->e_phnum +=1 ulPhdrSize +=pEhdr->e_phentsize;
//将DT STRTAB移到文件尾并且添加新so的名字
char *p0ldstrTab=NULL: uint32 t uloldstrTabLen=0; int i=0: for(i=0;i<pEhdr->e_phnum;i++){
if(pPhdr[il.p type ==PT DYNAMIC) {
Elf32_Dyn *pDyn =(Elf32_Dyn*)(pbyElfFile + pPhdr[i].p_offset);
for(;(char *)pDyn-(pbyElfFile + pPhdr[i].p_offset + pPhdr[i].p filesz)< 0;pDyn++){
if(pDyn->d tag ==DT STRTAB){
poldstrTab =pbyElfFile + pDyn->d un.d ptr;}else if(pDyn->d tag == DT STRSZ){
ul0ldStrTabLen =pDyn->d_un.d_val;}}
break;}
}
char *pNewStrTab=(char *)(pPhdr+pEhdr->e_phnum); uint32 t ulNewstrTabLen=uloldstrTabLen;
/计算文件尾映射时的虚拟地址 uint32 t ulvA0ffset=0;
uint32_t ulPagesize=sysconf(_SC_PAGESIZE); for(i =0;i<pEhdr->e phnum;i++){
if(pPhdr[i].p_type ==PT_LOAD){
ulvAOffset =pPhdr[i].p_vaddr-pPhdr[i].p_offset;
ulvAoffset +=(pPhdr[i].p_memsz-pPhdr[i].p_filesz)&~(ulPagesize-1); ulvAoffset += ulPageSize;}}
pPTLoad->p_type=PT LOAD:
pPTLoad->p_offset =ulFileSize;
pPTLoad->pvaddr=pPTLoad->p_paddr =pPTLoad->p offset + ulVAOffset; pPTLoad->p_filesz=pPTLoad->pmemsz=ulPhdrSize + ulNewStrTabLen; pPTLoad->p_flags =PFX|PF_W|PF_R; pPTLoad->p_align =ulPageSize;
7/修改PT_PHDR中文件偏移和虚拟地址
for(i=0; i<pEhdr->e phnum; i++){
if(pPhdr[il.p type ==PT PHDR){
pPhdr[i].p_offset =ulfileSize:
pPhdr[i].p paddr =pPhdr[il.p vaddr = ulfilesize + ulvaoffset;
pPhdr[il.p filesz =pPhdr[il.p memsz = pEhdr->e phnum * pEhdr->e phentsize?} }
/修改DT STRTAB和DT STRSZ
for(i=0:i<pEhdr->e phnum;i++) {
if(pPhdr[il.p type == PT DYNAMIC) {
Elf32_Dyn *pDyn =(Elf32_Dyn*)(pbyElfFile + pPhdr[i].p_offset);
for(;(char *)pDyn-(pbyElfFile + pPhdr[i].p_offset + pPhdr[i].p filesz)< 0;pDyn++){
if(pDyn->d tag ==DT STRTAB){
pDyn->d un.d ptr =pNewstrTab-pbyelffile + ulvaoffset; felse if(pDvn->d tag ==DTSTRSZ){
pDyn->d un.d val =ulNewStrTabLen; felse if(pDyn->d tag ==DTNULL){
pDyn->d_tag =DT_NEEDED;
pDyn->d_un.d_ptr =pNewModuleName-pNewStrTab; break;}}
hreak. |