zoukankan      html  css  js  c++  java
  • 玩转Hook——Android权限管理功能探讨(二)

      距离我上一篇研究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
    ");
    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 ");这条语句上,我们可以把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] = '';
     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 -----
    ", pTag);
     95   printf("regs.cs = 0x%lx
    ", regs.cs);
     96   printf("regs.rip = 0x%lx
    ", regs.rip);
     97   printf("regs.rsp = 0x%lx
    ", regs.rsp);
     98   printf("regs.rbp = 0x%lx
    ", regs.rbp);
     99 
    100   printf("regs.rax = 0x%lx
    ", regs.rax);
    101   printf("regs.rbx = 0x%lx
    ", regs.rbx);
    102   printf("regs.rcx = 0x%lx
    ", regs.rcx);
    103   printf("regs.rdx = 0x%lx
    ", regs.rdx);
    104   printf("regs.rsi = 0x%lx
    ", regs.rsi);
    105   printf("regs.rdi = 0x%lx
    ", regs.rdi);
    106   printf("regs.orig_rax = 0x%lx
    ", regs.orig_rax);
    107   printf("regs.eflags = 0x%lx
    ", regs.eflags);
    108   printf("regs.ds = 0x%lx
    ", regs.ds);
    109   printf("regs.es = 0x%lx
    ", regs.es);
    110   printf("regs.fs = 0x%lx
    ", regs.fs);
    111   printf("regs.gs = 0x%lx
    ", regs.gs);
    112   printf("regs.fs_base = 0x%lx
    ", regs.fs_base);
    113   printf("regs.gs_base = 0x%lx
    ", regs.gs_base);
    114   printf("----%s -----
    ", 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("
    ");
    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
    ");
    145     return 1;
    146   }
    147   printf("attach success
    ");
    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
    ");
    169     return 1;
    170   }
    171   printf("continue success
    ");
    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...
    ");
    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
    ");
    191     return 1;
    192   }
    193   printf("detach success
    ");
    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空指令所致~

  • 相关阅读:
    20210312
    20210311
    20210310
    例5-1
    例5-2
    例4-12-2
    例4-12
    例4-11
    例4-10
    例4-9
  • 原文地址:https://www.cnblogs.com/zealotrouge/p/4107976.html
Copyright © 2011-2022 走看看