700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > 玩转Hook——Android权限管理功能探讨(一)

玩转Hook——Android权限管理功能探讨(一)

时间:2020-12-29 20:06:53

相关推荐

玩转Hook——Android权限管理功能探讨(一)

随着Android设备上的隐私安全问题越来越被公众重视,恶意软件对用户隐私,尤其是对电话、短信等私密信息的威胁日益突出,各大主流安全软件均推出了自己的隐私行为监控功能,在root情况下能有效防止恶意软件对用户隐私的窃取,那么这背后的技术原理是什么?我带着疑问开始一步步探索,如果要拦截恶意软件对电话、短信等API的调用,在Java或者Dalvik层面是不好进行的,因为这些层面都没有提供Hook的手段,而在Native层面,我认为可行的方案是对电话、短信的运行库so进行Hook(比如系统运行库\system\lib\libreference-ril.so或\system\lib\libril.so),如果注入自己的so到上述进程后,并通过dlopen()和dlsym()获取原有API地址,替换原有API地址为自己so中的API地址就可以达到Hook的目的。

Hook的前提是进程注入,而Linux下最便捷的进程注入手段——ptrace,是大名鼎鼎的调试工具GDB的关键技术点;本文参考自Pradeep Padala于2002年的博文/article/6100(国内很多博客有这篇文章的译文,不过本着获取“一手”知识的想法,还是细读了原版英文,确实发现了一些翻译得不够到位的地方,在此还是推荐各位能读原文就不要读译文),由于02年时还是ia32(32位Intel Architecture)时代,时至今日,在我ia64也就是x64的机器已经无法运行了,所以自己动手实现了x64版本。代码主要功能是注入子进程的地址空间,Hook住子进程执行系统调用时的参数,并反转其参数,从而逆序输出ls命令的结果。

代码如下:

1 /* 2 ptrace3.c 3 author: pengyiming 4 description: 5 1, child process need be traced by father process 6 2, father process reserve the result of "ls" command which executed by child process 7 */ 8 9 #include <stdio.h> 10 #include <stdlib.h> 11 #include <string.h> 12 #include <sys/ptrace.h> 13 #include <sys/types.h> 14 #include <sys/wait.h> 15 #include <sys/reg.h> 16 #include <sys/user.h> 17 #include <sys/syscall.h> 18 #include <unistd.h> 19 20 #ifdef __x86_64__ 21 22 #define OFFSET_UNIT 8 23 24 #else 25 26 #define OFFSET_UNIT 4 27 28 #endif 29 30 // converter long to char[] 31 union 32 { 33 long rawData; 34 char strData[sizeof(long)]; 35 } converter; 36 37 void getData(pid_t child, unsigned long long dataAddr, unsigned long long dataLen, char * const p_data) 38 { 39 // PEEKDATA counter 40 int counter = 0; 41 // PEEKDATA max count 42 int maxCount = dataLen / sizeof(long); 43 if (dataLen % sizeof(long) != 0) 44 { 45maxCount++; 46 } 47 // moving pointer 48 void * p_moving = p_data; 49 50 while (counter < maxCount) 51 { 52memset(&converter, 0, sizeof(long)); 53converter.rawData = ptrace(PTRACE_PEEKDATA, child, dataAddr + counter * sizeof(long), NULL); 54if (converter.rawData < 0) 55{ 56 perror("ptrace peek data error : "); 57} 58 59memcpy(p_moving, converter.strData, sizeof(long)); 60p_moving += sizeof(long); 61counter++; 62 } 63 p_data[dataLen] = '\0'; 64 } 65 66 void setData(pid_t child, unsigned long long dataAddr, unsigned long long dataLen, char * const p_data) 67 { 68 // POKEDATA counter 69 int counter = 0; 70 // POKEDATA max count 71 int maxCount = dataLen / sizeof(long); 72 // data left length (prevent out of range in memory when written) 73 int dataLeftLen = dataLen % sizeof(long); 74 // moving pointer 75 void * p_moving = p_data; 76 77 // write part of data which align to sizeof(long) 78 int ret; 79 while (counter < maxCount) 80 { 81memset(&converter, 0, sizeof(long)); 82memcpy(converter.strData, p_moving, sizeof(long)); 83ret = ptrace(PTRACE_POKEDATA, child, dataAddr + counter * sizeof(long), converter.rawData); 84if (ret < 0) 85{ 86 perror("ptrace poke data error : "); 87} 88 89p_moving += sizeof(long); 90counter++; 91 } 92 93 // write data left 94 if (dataLeftLen != 0) 95 { 96memset(&converter, 0, sizeof(long)); 97memcpy(converter.strData, p_moving, dataLeftLen); 98ret = ptrace(PTRACE_POKEDATA, child, dataAddr + counter * sizeof(long), converter.rawData); 99if (ret < 0)100{101 perror("ptrace poke data error : ");102}103 }104 }105 106 void reverseStr(char * p_str)107 {108 int strLen = strlen(p_str);109 char * p_head = p_str;110 char * p_tail = p_str + strLen - 1;111 char tempCh;112 113 // skip '\n'114 if (*p_tail == '\n')115 {116p_tail--;117 }118 119 //exchange char120 while (p_head < p_tail)121 {122tempCh = *p_head;123*p_head = *p_tail;124*p_tail = tempCh;125 126p_head++;127p_tail--;128 }129 }130 131 void debugRegs(struct user_regs_struct * p_regs ) 132 {133 printf("syscall param DS = %llu\n", p_regs->ds);134 printf("syscall param RSI = %llu\n", p_regs->rsi);135 printf("syscall param ES = %llu\n", p_regs->es);136 printf("syscall param RDI = %llu\n", p_regs->rdi);137 138 printf("syscall return RAX = %llu\n", p_regs->rax);139 printf("syscall param RBX = %llu\n", p_regs->rbx);140 printf("syscall param RCX = %llu\n", p_regs->rcx);141 printf("syscall param RDX = %llu\n", p_regs->rdx);142 }143 144 int main()145 {146 pid_t child = fork();147 if(child == 0)148 {149ptrace(PTRACE_TRACEME, 0, NULL, NULL);150 151// make a syscall(SYS_write)152execl("/bin/ls", "ls", NULL);153 }154 else155 {156int status;157// SYS_write will be called twice, one is entry, another is exit, so we mark it158unsigned int calledCount = 0;159 160while (1)161{162 wait(&status);163 if (WIFEXITED(status))164 {165 break;166 }167 168 // PEEK regs to find the syscall(SYS_execve)169 struct user_regs_struct regs;170 ptrace(PTRACE_GETREGS, child, NULL, &regs);171 172 // catch it!173 if (regs.orig_rax == SYS_write)174 {175 if (calledCount == 0)176 {177 calledCount = 1;178 179 // debugRegs(&regs);180 181 char * p_dataStr = (char *) malloc((regs.rdx + 1) * sizeof(char));182 if (p_dataStr == NULL)183 {184return;185 }186 187 getData(child, regs.ds * OFFSET_UNIT + regs.rsi, regs.rdx, p_dataStr);188 reverseStr(p_dataStr);189 setData(child, regs.ds * OFFSET_UNIT + regs.rsi, regs.rdx, p_dataStr);190 }191 else if (calledCount == 1)192 {193 // debugRegs(&regs);194 }195 }196 197 ptrace(PTRACE_SYSCALL, child, NULL, NULL);198}199 }200 201 return 0;202 }

代码执行结果:

可以看到工程目录下的文件名均被反转输出,已达到想要的效果,那么接下来解释代码中的几个关键点:

1,SYSCALL与orig_rax寄存器

不论是ia32还是ia64,orig_rax寄存器都存放着每一次系统调用的ID,为了方便开发和调试,我们可以在/usr/include/x86_64-linux-gnu/sys/syscall.h中找到系统调用的定义,比如#define SYS_write __NR_write,但是我们无法得知__NR_write具体代表的ID,进一步搜索,可以在/usr/include/x86_64-linux-gnu/asm/unistd_64.h中找到ia64下对__NR_write的定义,#define __NR_write 1,这样一来我们打印出orig_rax寄存器中的值就可以判断此时子进程正在进行何种操作了。

2,PTRACE_PEEKDATA与PTRACE_PEEKTEXT参数的选取

Linux进程的地址空间不存在独立的数据段和代码段(或叫正文段),二者位于同一空间,所以上述两个参数并无实际意义上的区别,不过为了标识我们是在读取数据段中的数据,还是使用PTRACE_PEEKDATA比较好,同理对应于PTRACE_POKEDATA和PTRACE_POKETEXT。

3,联合体converter

由于执行PTRACE_PEEKDATA操作时,返回值的二进制代表内存中的实际数据,我们可以利用“联合体中的变量有相同的初始地址”这一特性来帮助我们完成从二进制到字符串的转换。(这是一个做过嵌入式开发的人基本都知道的小技巧,考虑到做Android开发对这段代码可能会有疑惑,容我啰嗦两句)

4,数据段寻址

这是在实现x64版本时遇到的最大的困难,在getData()与setData()函数中,第二个参数表示数据在数据段中的地址,由于和ia32时寻址方式不一致,苦苦搜索几天,发现国内很多博客上的说法并不一致,最终在Intel官网上下载了Intel处理器开发手册《64-ia-32-architectures-software-developer-vol-1-manual.pdf》方才解答我的问题,寻址方式涉及两个寄存器,DS和RSI,参考手册的说法,DS表示数据段的selector,其实这个selector就是index索引的意思,由于x64下字长64bit,即8个字节,索引乘以8即得数据段在内存中的基址,RSI表示数据段内偏移地址,与基址相加即可得出数据的绝对地址,使用此地址直接访问内存就可以取出数据。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。