玩转Hook——Android权限管理功能揭秘(一)

  题记一:各大安全软件公司对此项技术可能都有一定的技术保密,在老东家也不例外,由于种种原因未能参与此项技术研发甚是遗憾,也未能接触到其源码,只能自行研究并与各位共享并探讨一下技术方案,所以在此也提醒各位,由于本文所述均为自己在有限机型上实验的结果,不保证对其他机型上的兼容性,也不保证可能引发的无限重启等技术风险。

  题记二:谨以此文献给前同事huzhong、zhongjihong两位大牛,感谢二位对我技术上,尤其是Android
Native方面的点拨!

  随着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年的博文http://www.linuxjournal.com/article/6100(国内很多博客有这篇文章的译文,不过本着获取“一手”知识的想法,还是细读了原版英文,确实发现了一些翻译得不够到位的地方,在此还是推荐各位能读原文就不要读译文),由于02年时还是ia32(32位Intel
Architecture)时代,时至今日,在我ia64也就是x64的机器已经无法运行了,所以自己动手实现了x64版本。代码主要功能是注入子进程的地址空间,Hook住子进程执行系统调用时的参数,并反转其参数,从而逆序输出ls命令的结果。

  代码如下:(由于在vim编辑器下不方便用中文注释,所以源码中均为本人“中式”英语注释,囧,不过几个关键点我稍后用中文说明)


  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 {
45 maxCount++;
46 }
47 // moving pointer
48 void * p_moving = p_data;
49
50 while (counter < maxCount)
51 {
52 memset(&converter, 0, sizeof(long));
53 converter.rawData = ptrace(PTRACE_PEEKDATA, child, dataAddr + counter * sizeof(long), NULL);
54 if (converter.rawData < 0)
55 {
56 perror("ptrace peek data error : ");
57 }
58
59 memcpy(p_moving, converter.strData, sizeof(long));
60 p_moving += sizeof(long);
61 counter++;
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 {
81 memset(&converter, 0, sizeof(long));
82 memcpy(converter.strData, p_moving, sizeof(long));
83 ret = ptrace(PTRACE_POKEDATA, child, dataAddr + counter * sizeof(long), converter.rawData);
84 if (ret < 0)
85 {
86 perror("ptrace poke data error : ");
87 }
88
89 p_moving += sizeof(long);
90 counter++;
91 }
92
93 // write data left
94 if (dataLeftLen != 0)
95 {
96 memset(&converter, 0, sizeof(long));
97 memcpy(converter.strData, p_moving, dataLeftLen);
98 ret = ptrace(PTRACE_POKEDATA, child, dataAddr + counter * sizeof(long), converter.rawData);
99 if (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 {
116 p_tail--;
117 }
118
119 //exchange char
120 while (p_head < p_tail)
121 {
122 tempCh = *p_head;
123 *p_head = *p_tail;
124 *p_tail = tempCh;
125
126 p_head++;
127 p_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 {
149 ptrace(PTRACE_TRACEME, 0, NULL, NULL);
150
151 // make a syscall(SYS_write)
152 execl("/bin/ls", "ls", NULL);
153 }
154 else
155 {
156 int status;
157 // SYS_write will be called twice, one is entry, another is exit, so we mark it
158 unsigned int calledCount = 0;
159
160 while (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 {
184 return;
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表示数据段内偏移地址,与基址相加即可得出数据的绝对地址,使用此地址直接访问内存就可以取出数据。

  好了,至此本文就已经结束了,相信各位看完以上对关键点的解释部分可以对ptrace有一个初步了解,下一篇会参考Pradeep
Padala的博文进一步学习ptrace相关技术,实现一个更有意思的小程序,不过最近有很多事情有待处理,时间就待定吧~

时间: 2024-10-02 19:12:57

玩转Hook——Android权限管理功能揭秘(一)的相关文章

Android权限管理原理(含6.0-4.3)

Android 4.3-5.1 AppOpsManager动态权限管理(官方不成熟的权限管理) AppOpsManager 是Google在Android4.3-Android5.0引入的动态权限管理方式,但是又与Google觉得不成熟,所以在每个发行版的时候,总是会将这个功能给屏蔽掉.国内一些早期版本的权限动态管理的表现类似,这里用CyanogenMod12里面的实现讲述一下,国内的ROM源码拿不到,不过从表现来看,实现应该类似. 在一开始,其实Google将权限的动态管理放在每个服务内部,类

Android权限管理PermissionsDispatcher2.3.2使用+原生6.0权限使用

PermissionsDispatcher2.3.2使用 Android6.0权限官网https://developer.android.com/about/versions/marshmallow/android-6.0-changes.html?hl=zh-cn系统权限:https://developer.android.com/training/permissions/index.html?hl=zh-cn权限的最佳做法:https://developer.android.com/trai

PHP实现权限管理功能

权限管理系统,它主要是为了给不同的用户设定不同的权限,从而实现不同权限的用户登录之后使用的功能不一样. 首先先看下数据库 总共有5张表,users,roles和roleswork 3张表与另外2张表形成"w"型的关系,也是比较常见的一种权限数据库的方式,首先先做权限的设定,也就是管理层给不同用户设定不同权限. 1.管理员页面RBAC.php <!DOCTYPE html> <html> <head> <meta charset="UT

Android 权限管理(持续整理)

1. Android 6.0之后,APP直接安卓,运行时询问用户授予相关权限,此时系统弹出一个对话框,(这个对话框不能由开发者定制) 同时用户也可以在手机的"设置"中对于某个App进行权限管理 注意:这个对话框不是系统自动弹出,而是开发者在调用必须用户授权的方法时,在调用之前检查(如果有授权直接调用)并请求用户权限,否则可能因没有授权而直接程序崩溃 重点:在某一个需要用户权限的地方检查并请求用户权限,否则程序崩溃 2. 权限分类 是否涉及用户隐私,权限分为两类:第一类不涉及隐私,在Ma

Android权限管理知识学习记录

一.Android权限背景知识 在Android 6.0之前,所申请的权限只需要在AndroidManifest.xml列举就可以了,从而容易导致一些安全隐患,因此,在Android 6.0时,Google为了更好的保护用户隐私提出了新的权限管理机制,同时将其分为两大类: (1)Normal Permissions Normal Permission一般不涉及用户隐私,是不需要用户进行授权的,比如手机震动,访问网络等. (2)Dangerous Permission Dangerous Perm

关于Android 权限管理的几点认识

作为Android的应用开发者,对于android的权限机制总是感觉很奇怪,为什么要有权限这个东西?为什么要在AndroidManifest里面写uses-permission 这样东西?以前一直困惑,但是用着没什么问题也就认了,没去好好深究过,这回就来好好看下吧. 原来在设备上有这么个文件/system/etc/permissions/platform.xml 打开来看 <permission name="android.permission.INTERNET" > &l

django项目后台权限管理功能。

对后台管理员进行分角色,分类别管理,每个管理员登录账号后只显示自己负责的权限范围. 创建后台管理数据库 models.py文件内 # 管理员表 class Superuser(models.Model): super_id=models.AutoField(primary_key=True) super_name=models.CharField(max_length=255) super_pwd=models.CharField(max_length=255) role = models.Ma

TP thinkphp 权限管理功能

目前,在tp框架中做权限管理 分rbac(老)与auth(推荐)认证方式: 老的tp版本中封装的是rbac认证: 新一点的都开始使用auth方式管理了.推荐使用此方式: 实现步骤一:引入类库Auth.class.php 实现步骤二:创建数据表 a.菜单表 CREATE TABLE `wifi_admin_nav` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '菜单表', `pid` int(11) unsigned DEFAUL

android 权限管理和签名 实现静默卸载

为了实现静默卸载, 学了下android的安全体系,记录如下 最近在做个东西,巧合碰到了sharedUserId的问题,所以收集了一些资料,存存档备份. 安装在设备中的每一个apk文件,Android给每个APK进程分配一个单独的用户空间,其manifest中的userid就是对应一个Linux用户都会被分配到一个属于自己的统一的Linux用户ID,并且为它创建一个沙箱,以防止影响其他应用程序(或者其他应用程序影响它).用户ID 在应用程序安装到设备中时被分配,并且在这个设备中保持它的永久性.