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

时间:2021-07-04 18:56:20

  距离我上一篇研究ptrace的随笔http://www.cnblogs.com/zealotrouge/p/3544147.html已经过去半年了,最近不忙的时候抽空继续研究了下。同样,参考了Pradeep Padala的博文http://www.linuxjournal.com/article/6210,对其中断点部分比较感兴趣。因为从开始学习编程之日起,调试就是我们不可或缺的重要工具,而调试的基础就在于断点,那么,断点是如何让一个运行中的程序暂停的呢?背后的机制又是什么?为了探寻这个问题,我研究了上面的文章,并在自己的机器上(环境为Ubuntu12.04 + Intel x86_64 i5)动手实现了一下x64版本。

  下面是整个问题研究的思路,按这个思路来给大家展示代码。首先,我们有一个简单的源程序tracedProcess.c,简单到仅仅是每隔1s输出一行"I'm running",这种简单的程序比较适合初学者分析汇编代码的语义,并找到容易设置断点的地方:

 

 1 /*
 2   tracedProcess.c
 3   author: pengyiming
 4 */
 5 
 6 #include <stdio.h>
 7 
 8 void main()
 9 {
10   while(1)
11   {
12     printf("I'm running\n");
13 
14     sleep(1);
15   }
16 }

 

  gcc编译如上代码:

gcc -o tracedProcess.o tracedProcess.c

  objdump分析目标文件tracedProcess.o,得到如下输出(截取main部分):

objdump -d tracedProcess.o

0000000000400544 <main>:
  400544:    55                       push   %rbp
  400545:    48 89 e5                 mov    %rsp,%rbp
  400548:    bf 5c 06 40 00           mov    $0x40065c,%edi
  40054d:    e8 de fe ff ff           callq  400430 <puts@plt>
  400552:    bf 01 00 00 00           mov    $0x1,%edi
  400557:    b8 00 00 00 00           mov    $0x0,%eax
  40055c:    e8 ef fe ff ff           callq  400450 <sleep@plt>
  400561:    eb e5                    jmp    400548 <main+0x4>

  简单分析下:

  0x400544  这个虚地址是main函数的入口

  0x400544~0x400547  是所有函数的默认动作,新建一个函数栈

  0x400548~0x40054c  将0x40065c传给edi寄存器,edi是字符串操作寄存器,存储的是字符串地址,0x40065c是只读区地址,用后面的getData()函数可以打出来,发现就是"I'm running"这个字符串

  0x40054d~0x400551  callq执行一个函数调用,0x400430是此函数的入口,不难看出就是printf()函数链接到此目标文件的地址

  0x400552~0x400556  清空edi寄存器

      0x400557~0x40055b  printf()无返回值,无需传递返回值地址给eax

      0x40055c~0x400560  callq执行一个函数调用,sleep()

      0x400561  跳转到0x400548进入下一个循环

  分析完后,可以分析出,如果想把断点加在printf("I'm running\n");这条语句上,我们可以把0x400548作为断点。

 

  合适打断点的地址找到后,我们就要想想如何让程序暂停,继续参考上一篇博客中提到的Intel处理器开发手册,发现可以使用Trap指令使程序暂停运行。具体是使用int 0x80进入内核态,然后调用Trap指令——int3,只要CPU执行了这个指令,即可让程序暂停并处于一直等待状态,所以我们需要用ptrace在tracedProcess.o运行时,操作CPU寄存器和注入int 0x80 int3指令,一旦程序执行完int3,即可断点成功;当然,在断点后,我们希望程序能恢复运行,我们还需要备份CPU寄存器和原来代码段中的指令,以便之后的恢复。

  小结一下,断点+恢复需要两次注入来实现:

  一、断点注入步骤

  (1)PTRACE_ATTACH附着被注入进程(会暂停被注入进程),备份当前的寄存器值

  (2)备份注入地址处指令

  (3)替换注入地址处指令为Trap指令

  (4)PTRACE_CONT使被注入进程继续执行,直到执行完Trap指令

  二、恢复注入步骤

  (1)恢复之前的寄存器值(注:为了方便,所有的寄存器都备份了,实质上是为了恢复栈指针rsp&rbp和指令指针rip)

  (2)恢复注入地址处指令

  (3)PTRACE_DETACH使被注入进程继续执行,并脱离被注入进程

  代码如下:

  1 /*
  2   ptrace4.c
  3   author: pengyiming
  4   description:
  5   1, attach a test process, insert a break point
  6   2, sleep for 5s then continue it
  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 #define WORD_SIZE sizeof(long)
 21 
 22 static unsigned long injectAddress = 0x400548;
 23 
 24 // converter long to char[]
 25 union
 26 {
 27   long rawData;
 28   char strData[WORD_SIZE];
 29 } converter;
 30 
 31 void getData(pid_t pid, unsigned long dataAddr, unsigned long dataLen, char * const p_data)
 32 {
 33   // PEEKDATA counter
 34   int counter = 0;
 35   // PEEKDATA max count
 36   int maxCount = dataLen / WORD_SIZE;
 37   if (dataLen % WORD_SIZE != 0)
 38   {
 39     maxCount++;
 40   }
 41   // moving pointer
 42   void * p_moving = p_data;
 43 
 44   while (counter < maxCount)
 45   {
 46     memset(&converter, 0, WORD_SIZE);
 47     converter.rawData = ptrace(PTRACE_PEEKDATA, pid, dataAddr + counter * WORD_SIZE, NULL);
 48 
 49     memcpy(p_moving, converter.strData, WORD_SIZE);
 50     p_moving += WORD_SIZE;
 51     counter++;
 52   }
 53   p_data[dataLen] = '\0';
 54 }
 55 
 56 void setData(pid_t pid, unsigned long dataAddr, unsigned long dataLen, char * const p_data)
 57 {
 58   // POKEDATA counter
 59   int counter = 0;
 60   // POKEDATA max count
 61   int maxCount = dataLen / WORD_SIZE;
 62   // data left length (prevent out of range in memory when written)
 63   int dataLeftLen = dataLen % WORD_SIZE;
 64   // moving pointer
 65   void * p_moving = p_data;
 66 
 67   // write part of data which align to WORD_SIZE
 68   int ret;
 69   while (counter < maxCount)
 70   {
 71     memset(&converter, 0, WORD_SIZE);
 72     memcpy(converter.strData, p_moving, WORD_SIZE);
 73     ret = ptrace(PTRACE_POKEDATA, pid, dataAddr + counter * WORD_SIZE, converter.rawData);
 74 
 75     p_moving += WORD_SIZE;
 76     counter++;
 77   }
 78 
 79   // write data left
 80   if (dataLeftLen != 0)
 81   {
 82     memset(&converter, 0, WORD_SIZE);
 83     memcpy(converter.strData, p_moving, dataLeftLen);
 84     ret = ptrace(PTRACE_POKEDATA, pid, dataAddr + counter * WORD_SIZE, converter.rawData);
 85   }
 86 }
 87 
 88 void debugRegs(char * pTag, pid_t pid)
 89 {
 90     struct user_regs_struct regs;
 91   memset(&regs, 0, sizeof(struct user_regs_struct));
 92   ptrace(PTRACE_GETREGS, pid, NULL, &regs);
 93 
 94   printf("----%s -----\n", pTag);
 95   printf("regs.cs = 0x%lx\n", regs.cs);
 96   printf("regs.rip = 0x%lx\n", regs.rip);
 97   printf("regs.rsp = 0x%lx\n", regs.rsp);
 98   printf("regs.rbp = 0x%lx\n", regs.rbp);
 99 
100   printf("regs.rax = 0x%lx\n", regs.rax);
101   printf("regs.rbx = 0x%lx\n", regs.rbx);
102   printf("regs.rcx = 0x%lx\n", regs.rcx);
103   printf("regs.rdx = 0x%lx\n", regs.rdx);
104   printf("regs.rsi = 0x%lx\n", regs.rsi);
105   printf("regs.rdi = 0x%lx\n", regs.rdi);
106   printf("regs.orig_rax = 0x%lx\n", regs.orig_rax);
107   printf("regs.eflags = 0x%lx\n", regs.eflags);
108   printf("regs.ds = 0x%lx\n", regs.ds);
109   printf("regs.es = 0x%lx\n", regs.es);
110   printf("regs.fs = 0x%lx\n", regs.fs);
111   printf("regs.gs = 0x%lx\n", regs.gs);
112   printf("regs.fs_base = 0x%lx\n", regs.fs_base);
113   printf("regs.gs_base = 0x%lx\n", regs.gs_base);
114   printf("----%s -----\n", pTag);
115 }
116 
117 void debugInstructionByAddr(char * pTag, pid_t pid, unsigned long addr)
118 {
119   char instruction[WORD_SIZE];
120   memset(instruction, 0, WORD_SIZE);
121   getData(pid, addr, WORD_SIZE, instruction);
122 
123   printf("0x%lx, %s instruction =", addr, pTag);
124   int index;
125   for (index = 0; index < WORD_SIZE; index++)
126   {
127     printf(" 0x%02x ", (unsigned char) instruction[index]);
128   }
129   printf("\n");
130 }
131 
132 int main()
133 {
134   pid_t pid = 0;
135   int waitStatus = -1;
136 
137   // enter the pid of the process you want attach
138   printf("enter pid that you want attach : ");
139   scanf("%d", &pid);
140 
141   int ret = ptrace(PTRACE_ATTACH, pid, NULL, NULL);
142   if (ret == -1)
143   {
144     printf("attach error\n");
145     return 1;
146   }
147   printf("attach success\n");
148 
149   // wait
150   waitpid(pid, &waitStatus, 0);
151 
152   /* pause the process */
153   // 1, backup the orignal regs
154   struct user_regs_struct backupRegs;
155   memset(&backupRegs, 0, sizeof(struct user_regs_struct));
156   ptrace(PTRACE_GETREGS, pid, NULL, &backupRegs);
157   // 2, backup the instruction
158   char backupInstruction[WORD_SIZE];
159   memset(backupInstruction, 0, WORD_SIZE);
160   getData(pid, injectAddress, WORD_SIZE, backupInstruction);
161     // 3, insert break instruction —— "int 0x80, int3"
162   char breakInstruction[WORD_SIZE] = { 0xcd, 0x80, 0xcc, 0x0, 0x0, 0x0, 0x0, 0x0 };
163   setData(pid, injectAddress, WORD_SIZE, breakInstruction);
164   // 4, PTRACE_CONT for execute "int 0x80, int3" instruction
165   ret = ptrace(PTRACE_CONT, pid, NULL, NULL);
166   if (ret == -1)
167   {
168     printf("continue error\n");
169     return 1;
170   }
171   printf("continue success\n");
172 
173   // wait
174   waitpid(pid, &waitStatus, 0);
175   /* pause the process */
176 
177   // wait for 5 seconds
178   printf("the process is paused for 5s...\n");
179   sleep(5);
180 
181   /* continue the process */
182   // 1, restore regs to the orignal address
183   ptrace(PTRACE_SETREGS, pid, NULL, &backupRegs);
184   // 2, restore instruction
185   setData(pid, injectAddress, WORD_SIZE, backupInstruction);
186   // 3, PTRACE_DETACH for execute the orignal code
187   ret = ptrace(PTRACE_DETACH, pid, NULL, NULL);
188   if (ret == -1)
189   {
190     printf("detach error\n");
191     return 1;
192   }
193   printf("detach success\n");
194   /* continue the process */
195 
196   return 0;
197 }

  代码执行结果:被注入进程输出"I'm running",ptrace4进程注入后,被注入进程暂停输出"I'm running",5s后恢复。

  最后说下调试过程中遇到的一个问题,在恢复被注入进程的运行时,总是会导致被注入进程segment fault,代码中加入了大量的debug函数用于分析问题,最后利用dmesg工具发现原因是恢复时写入注入地址指令有误,根本原因是之前认为breakInstruction只有3bytes,所以备份指令也只需要3bytes,于是便存储在char backupInstruction[3]中,而ptrace的读写单位都是word,在x64下是8bytes,所以在恢复时写入的指令为了3bytes的正确指令+5bytes的0x00空指令所致~