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

      随着Android设备上的隐私安全问题越来越被公众重视,恶意软件对用户隐私,尤其是对电话、短信等私密信息的威胁日益突出,各大主流安全软件均推出了自己的隐私行为监控功能,在root情况下能有效防止恶意软件对用户隐私的窃取,那么这背后的技术原理是什么?我带着疑问开始一步步探索,如果要拦截恶意软件对电话、短信等API的调用,在Java或者Dalvik层面是不好进行的,因为这些层面都没有提供Hook的手段,而在Native层面,我认为可行的方案是对电话、短信的运行库so进行Hook(比如系统运行库systemliblibreference-ril.so或systemliblibril.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命令的结果。

      代码如下:

      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] = '';
     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 '
    '
    114   if (*p_tail == '
    ')
    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
    ", p_regs->ds);
    134   printf("syscall param RSI = %llu
    ", p_regs->rsi);
    135   printf("syscall param ES = %llu
    ", p_regs->es);
    136   printf("syscall param RDI = %llu
    ", p_regs->rdi);
    137 
    138   printf("syscall return RAX = %llu
    ", p_regs->rax);
    139   printf("syscall param RBX = %llu
    ", p_regs->rbx);
    140   printf("syscall param RCX = %llu
    ", p_regs->rcx);
    141   printf("syscall param RDX = %llu
    ", 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表示数据段内偏移地址,与基址相加即可得出数据的绝对地址,使用此地址直接访问内存就可以取出数据。

  • 相关阅读:
    对C#中的Close()和Dispose()的浅显理解
    SqlParameter类中的两对好基友:SqlDbType与DbType、SqlValue与Value
    C#通过获取快捷方式指向目标的小示例触碰WMI
    小心UAC
    【TSQL】获取指定日期的常用前后节点(月初月末周一周末等等)
    弹出移动设备时报正在使用肿么办
    再获殊荣!霍格沃兹荣获腾讯金课堂「教育突破奖」
    实战 | 电商业务的性能测试(一): 必备基础知识
    接口测试框架实战(二)| 接口请求断言
    测试老鸟总结的 16 个测试改进 Tips ,让你少走弯路!
  • 原文地址:https://www.cnblogs.com/zealotrouge/p/3544147.html
Copyright © 2011-2022 走看看