玩轉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年的博文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] = '\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表示數據段內偏移地址,與基址相加即可得出數據的絕對地址,使用此地址直接訪問內存就可以取出數據。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM