ptrace函数深入分析
Posted 黑箱
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ptrace函数深入分析相关的知识,希望对你有一定的参考价值。
ptrace函数:进程跟踪。
形式:#include<sys/ptrace.h>
Int ptrace(int request,int pid,int addr,int data);
概述:
父进程控制子进程运行,检查和改变它的核心Image。Ptrace主要用来实现断点调试。当进程被中止,通知父进程,进程的内存空间可以被读写,父进程可以选择是子进程继续执行,还是中止。
根据ptrace的函数原形 int ptrace(int request, int pid, int addr, int data);
request的不同参数决定了系统调用的功能
PTRACE_TRACEME |
本进程被其父进程所跟踪。其父进程应该希望跟踪子进程 |
PTRACE_PEEKTEXT, PTRACE_PEEKDATA |
从内存地址中读取一个字节,内存地址由addr给出。 |
PTRACE_PEEKUSR |
从USER区域中读取一个字节,偏移量为addr。 |
PTRACE_POKETEXT, PTRACE_POKEDATA |
往内存地址中写入一个字节。内存地址由addr给出。 |
PTRACE_POKEUSR |
往USER区域中写入一个字节。偏移量为addr。 |
PTRACE_SYSCALL, PTRACE_CONT |
重新运行。 |
PTRACE_KILL |
杀掉子进程,使它退出。 |
PTRACE_SINGLESTEP |
设置单步执行标志 |
PTRACE_ATTACH |
跟踪指定pid 进程。 |
PTRACE_DETACH |
结束跟踪 |
Intel386特有:
PTRACE_GETREGS |
读取寄存器 |
PTRACE_SETREGS |
设置寄存器 |
PTRACE_GETFPREGS |
读取浮点寄存器 |
PTRACE_SETFPREGS |
设置浮点寄存器 |
init进程不可以使用此函数
返回值:
成功返回0。错误返回-1。errno被设置
错误:
EPERM |
特殊进程不可以被跟踪或进程已经被跟踪。 |
ESRCH |
指定的进程不存在 |
EIO |
请求非法 |
Ptrace功能:
1) PTRACE_TRACEME
形式:ptrace(PTRACE_TRACEME,0 ,0 ,0)
描述:本进程被其父进程所跟踪。其父进程应该希望跟踪子进程。
2) PTRACE_PEEKTEXT, PTRACE_PEEKDATA
形式:ptrace(PTRACE_PEEKTEXT, pid, addr, data)
ptrace(PTRACE_PEEKDATA, pid, addr, data)
描述:从内存地址中读取一个字节,pid表示被跟踪的子进程,内存地址由addr给出,data为用户变量地址用于返回读到的数据。在Linux(i386)中用户代码段与用户数据段重合所以读取代码段和数据段数据处理是一样的。
3) PTRACE_POKETEXT, PTRACE_POKEDATA
形式:ptrace(PTRACE_POKETEXT, pid, addr, data)
ptrace(PTRACE_POKEDATA, pid, addr, data)
描述:往内存地址中写入一个字节。pid表示被跟踪的子进程,内存地址由addr给出,data为所要写入的数据。
4) PTRACE_PEEKUSR
形式:ptrace(PTRACE_PEEKUSR, pid, addr, data)
描述:从USER区域中读取一个字节,pid表示被跟踪的子进程,USER区域地址由addr给出,data为用户变量地址用于返回读到的数据。USER结构为core文件的前面一部分,它描述了进程中止时的一些状态,如:寄存器值,代码、数据段大小,代码、数据段开始地址等。在Linux(i386)中通过PTRACE_PEEKUSER和PTRACE_POKEUSR可以访问USER结构的数据有寄存器和调试寄存器。
5) PTRACE_POKEUSR
形式:ptrace(PTRACE_POKEUSR, pid, addr, data)
描述:往USER区域中写入一个字节,pid表示被跟踪的子进程,USER区域地址由addr给出,data为需写入的数据。
6) PTRACE_CONT
形式:ptrace(PTRACE_CONT, pid, 0, signal)
描述:继续执行。pid表示被跟踪的子进程,signal为0则忽略引起调试进程中止的信号,若不为0则继续处理信号signal。
7) PTRACE_SYSCALL
形式:ptrace(PTRACE_SYS, pid, 0, signal)
描述:继续执行。pid表示被跟踪的子进程,signal为0则忽略引起调试进程中止的信号,若不为0则继续处理信号signal。与PTRACE_CONT不同的是进行系统调用跟踪。在被跟踪进程继续运行直到调用系统调用开始或结束时,被跟踪进程被中止,并通知父进程。
8) PTRACE_KILL
形式:ptrace(PTRACE_KILL,pid)
描述:杀掉子进程,使它退出。pid表示被跟踪的子进程。
9) PTRACE_SINGLESTEP
形式:ptrace(PTRACE_KILL, pid, 0, signle)
描述:设置单步执行标志,单步执行一条指令。pid表示被跟踪的子进程。signal为0则忽略引起调试进程中止的信号,若不为0则继续处理信号signal。当被跟踪进程单步执行完一个指令后,被跟踪进程被中止,并通知父进程。
10) PTRACE_ATTACH
形式:ptrace(PTRACE_ATTACH,pid)
描述:跟踪指定pid 进程。pid表示被跟踪进程。被跟踪进程将成为当前进程的子进程,并进入中止状态。
11) PTRACE_DETACH
形式:ptrace(PTRACE_DETACH,pid)
描述:结束跟踪。 pid表示被跟踪的子进程。结束跟踪后被跟踪进程将继续执行。
12) PTRACE_GETREGS
形式:ptrace(PTRACE_GETREGS, pid, 0, data)
描述:读取寄存器值,pid表示被跟踪的子进程,data为用户变量地址用于返回读到的数据。此功能将读取所有17个基本寄存器的值。
13) PTRACE_SETREGS
形式:ptrace(PTRACE_SETREGS, pid, 0, data)
描述:设置寄存器值,pid表示被跟踪的子进程,data为用户数据地址。此功能将设置所有17个基本寄存器的值。
14) PTRACE_GETFPREGS
形式:ptrace(PTRACE_GETFPREGS, pid, 0, data)
描述:读取浮点寄存器值,pid表示被跟踪的子进程,data为用户变量地址用于返回读到的数据。此功能将读取所有浮点协处理器387的所有寄存器的值。
15) PTRACE_SETFPREGS
形式:ptrace(PTRACE_SETREGS, pid, 0, data)
描述:设置浮点寄存器值,pid表示被跟踪的子进程,data为用户数据地址。此功能将设置所有浮点协处理器387的所有寄存器的值。
代码分析
与Ptrace函数相关的代码:
1. sys_ptrace函数,完成ptrace系统调用的代码。
2. 为完成sys_ptrace功能所需调用的一些辅助函数,寄存器读写函数和内存读写函数。
3. 信号处理函数中,对被调试进程的处理(中止其运行、继续运行)。
4. syscall_trace函数,完成了系统调用调试下的处理。
5. 调试陷阱处理(异常1处理),完成单步执行和断点中断处理。
6. execve系统调用中对被调试进程装入后中止的实现。
1.sys_ptrace函数
sys_ptrace函数完成了ptrace系统调用功能。源码位置/linux/arch/i386/kernel/ptrace.c
sys_ptrace函数执行流程
1. asmlinkage int sys_ptrace(long request, long pid, long addr, long data) 2. { 3. struct task_struct *child; 4. struct user * dummy; 5. int i; 6. 7. dummy = NULL; 8. 9. if (request == PTRACE_TRACEME) { 10. /* are we already being traced? */ 11. if (current->flags & PF_PTRACED) 12. return -EPERM; 13. /* set the ptrace bit in the process flags. */ 14. current->flags |= PF_PTRACED; 15. return 0; 16. } 17. if (pid == 1) /* 进程不能被调试 */ 18. return -EPERM; 19. if (!(child = get_task(pid))) 20. return -ESRCH; 21. if (request == PTRACE_ATTACH) { 22. if (child == current) 23. return -EPERM; 24. if ((!child->dumpable || 25. (current->uid != child->euid) || 26. (current->uid != child->suid) || 27. (current->uid != child->uid) || 28. (current->gid != child->egid) || 29. (current->gid != child->sgid) || 30. (current->gid != child->gid)) && !suser()) 31. return -EPERM; 32. /* 同一进程不能多次附加 */ 33. if (child->flags & PF_PTRACED) 34. return -EPERM; 35. child->flags |= PF_PTRACED; 36. if (child->p_pptr != current) { 37. REMOVE_LINKS(child); 38. child->p_pptr = current; 39. SET_LINKS(child); 40. } 41. send_sig(SIGSTOP, child, 1); 42. return 0; 43. } 44. if (!(child->flags & PF_PTRACED)) 45. return -ESRCH; 46. if (child->state != TASK_STOPPED) { 47. if (request != PTRACE_KILL) 48. return -ESRCH; 49. } 50. if (child->p_pptr != current) 51. return -ESRCH; 52. 53. switch (request) { 54. /* when I and D space are separate, these will need to be fixed. */ 55. case PTRACE_PEEKTEXT: /* read word at location addr. */ 56. case PTRACE_PEEKDATA: { 57. unsigned long tmp; 58. int res; 59. 60. res = read_long(child, addr, &tmp); 61. if (res < 0) 62. return res; 63. res = verify_area(VERIFY_WRITE, (void *) data, sizeof(long)); 64. if (!res) 65. put_fs_long(tmp,(unsigned long *) data); 66. return res; 67. } 68. 69. /* read the word at location addr in the USER area. */ 70. case PTRACE_PEEKUSR: { 71. unsigned long tmp; 72. int res; 73. 74. if ((addr & 3) || addr < 0 || 75. addr > sizeof(struct user) - 3) 76. return -EIO; 77. 78. res = verify_area(VERIFY_WRITE, (void *) data, sizeof(long)); 79. if (res) 80. return res; 81. tmp = 0; /* Default return condition */ 82. if(addr < 17*sizeof(long)) { 83. addr = addr >> 2; /* temporary hack. */ 84. 85. tmp = get_stack_long(child, sizeof(long)*addr - MAGICNUMBER); 86. if (addr == DS || addr == ES || 87. addr == FS || addr == GS || 88. addr == CS || addr == SS) 89. tmp &= 0xffff; 90. }; 91. if(addr >= (long) &dummy->u_debugreg[0] && 92. addr <= (long) &dummy->u_debugreg[7]){ 93. addr -= (long) &dummy->u_debugreg[0]; 94. addr = addr >> 2; 95. tmp = child->debugreg[addr]; 96. }; 97. put_fs_long(tmp,(unsigned long *) data); 98. return 0; 99. } 100. 101. /* when I and D space are separate, this will have to be fixed. */ 102. case PTRACE_POKETEXT: /* write the word at location addr. */ 103. case PTRACE_POKEDATA: 104. return write_long(child,addr,data); 105. 106. case PTRACE_POKEUSR: /* write the word at location addr in the USER area */ 107. if ((addr & 3) || addr < 0 || 108. addr > sizeof(struct user) - 3) 109. return -EIO; 110. 111. addr = addr >> 2; /* temporary hack. */ 112. 113. if (addr == ORIG_EAX) 114. return -EIO; 115. if (addr == DS || addr == ES || 116. addr == FS || addr == GS || 117. addr == CS || addr == SS) { 118. data &= 0xffff; 119. if (data && (data & 3) != 3) 120. return -EIO; 121. } 122. if (addr == EFL) { /* flags. */ 123. data &= FLAG_MASK; 124. data |= get_stack_long(child, EFL*sizeof(long)-MAGICNUMBER) & ~FLAG_MASK; 125. } 126. /* Do not allow the user to set the debug register for kernel 127. address space */ 128. if(addr < 17){ 129. if (put_stack_long(child, sizeof(long)*addr-MAGICNUMBER, data)) 130. return -EIO; 131. return 0; 132. }; 133. 134. /* We need to be very careful here. We implicitly 135. want to modify a portion of the task_struct, and we 136. have to be selective about what portions we allow someone 137. to modify. */ 138. 139. addr = addr << 2; /* Convert back again */ 140. if(addr >= (long) &dummy->u_debugreg[0] && 141. addr <= (long) &dummy->u_debugreg[7]){ 142. 143. if(addr == (long) &dummy->u_debugreg[4]) return -EIO; 144. if(addr == (long) &dummy->u_debugreg[5]) return -EIO; 145. if(addr < (long) &dummy->u_debugreg[4] && 146. ((unsigned long) data) >= 0xbffffffd) return -EIO; 147. 148. if(addr == (long) &dummy->u_debugreg[7]) { 149. data &= ~DR_CONTROL_RESERVED; 150. for(i=0; i<4; i++) 151. if ((0x5f54 >> ((data >> (16 + 4*i)) & 0xf)) & 1) 152. return -EIO; 153. }; 154. 155. addr -= (long) &dummy->u_debugreg; 156. addr = addr >> 2; 157. child->debugreg[addr] = data; 158. return 0; 159. }; 160. return -EIO; 161. 162. case PTRACE_SYSCALL: /* continue and stop at next (return from) syscall */ 163. case PTRACE_CONT: { /* restart after signal. */ 164. long tmp; 165. 166. if ((unsigned long) data > NSIG) 167. return -EIO; 168. if (request == PTRACE_SYSCALL) 169. child->flags |= PF_TRACESYS; 170. else 171. child->flags &= ~PF_TRACESYS; 172. child->exit_code = data; 173. wake_up_process(child); 174. /* make sure the single step bit is not set. */ 175. tmp = get_stack_long(child, sizeof(long)*EFL-MAGICNUMBER) & ~TRAP_FLAG; 176. put_stack_long(child, sizeof(long)*EFL-MAGICNUMBER,tmp); 177. return 0; 178. } 179. 180. /* 181. * make the child exit. Best I can do is send it a sigkill. 182. * perhaps it should be put in the status that it wants to 183. * exit. 184. */ 185. case PTRACE_KILL: { 186. long tmp; 187. 188. if (child->state == TASK_ZOMBIE) /* already dead */ 189. return 0; 190. wake_up_process(child); 191. child->exit_code = SIGKILL; 192. /* make sure the single step bit is not set. */ 193. tmp = get_stack_long(child, sizeof(long)*EFL-MAGICNUMBER) & ~TRAP_FLAG; 194. put_stack_long(child, sizeof(long)*EFL-MAGICNUMBER,tmp); 195. return 0; 196. } 197. 198. case PTRACE_SINGLESTEP: { /* set the trap flag. */ 199. long tmp; 200. 201. if ((unsigned long) data > NSIG) 202. return -EIO; 203. child->flags &= ~PF_TRACESYS; 204. tmp = get_stack_long(child, sizeof(long)*EFL-MAGICNUMBER) | TRAP_FLAG; 205. put_stack_long(child, sizeof(long)*EFL-MAGICNUMBER,tmp); 206. wake_up_process(child); 207. child->exit_code = data; 208. /* give it a chance to run. */ 209. return 0; 210. } 211. 212. case PTRACE_DETACH: { /* detach a process that was attached. */ 213. long tmp; 214. 215. if ((unsigned long) data > NSIG) 216. return -EIO; 217. child->flags &= ~(PF_PTRACED|PF_TRACESYS); 218. wake_up_process(child); 219. child->exit_code = data; 220. REMOVE_LINKS(child); 221. child->p_pptr = child->p_opptr; 222. SET_LINKS(child); 223. /* make sure the single step bit is not set. */ 224. tmp = get_stack_long(child, sizeof(long)*EFL-MAGICNUMBER) & ~TRAP_FLAG; 225. put_stack_long(child, sizeof(long)*EFL-MAGICNUMBER,tmp); 226. return 0; 227. } 228. 229. default: 230. return -EIO; 231. } 232. }
1) PTRACE_TRACEME处理
说明:此处理使当前进程进入调试状态。进程是否为调试状态由进程的标志PF_PTRACED表示。
流程:
1. 9. if (request == PTRACE_TRACEME) { 2. 10. /* 是否被跟踪 */ 3. 11. if (current->flags & PF_PTRACED) 4. 12. return -EPERM; 5. 13. /* 设置跟踪标志 */ 6. 14. current->flags |= PF_PTRACED; 7. 15. return 0; 8. 16. }
2) PTRACE_ATTACH处理
说明:此处理设置开始调试某一进程,此进程可以是任何进程(init 进程除外)。对某一进程的调试需有对这一进程操作的权限。不能调试自身进程。一个进程不能ATTACH多次。为完成对一个进程的调试设置,首先设置进程标志置PF_PTRACED。再将需调试的进程设置为当前进程的子进程。最后向它发信号SIGSTOP中止它的运行,使它进入调试状态。
流程:
1. 21. if (request == PTRACE_ATTACH) { 2. 22. if (child == current) // 不能调试自身进程 3. 23. return -EPERM; 4. 24. if ((!child->dumpable || 5. 25. (current->uid != child->euid) || 6. 26. (current->uid != child->suid) || 7. 27. (current->uid != child->uid) || 8. 28. (current->gid != child->egid) || 9. 29. (current->gid != child->sgid) || 10. 30. (current->gid != child->gid)) && !suser()) // 检验用户权限 11. 31. return -EPERM; 12. 32. /* 同一进程不能多次附加 */ 13. 33. if (child->flags & PF_PTRACED) 14. 34. return -EPERM; 15. 35. child->flags |= PF_PTRACED; // 设置进程标志位 16. 36. if (child->p_pptr != current) { // 设置进程为当前进程的子进程 17. 37. REMOVE_LINKS(child); 18. 38. child->p_pptr = current; 19. 39. SET_LINKS(child); 20. 40. } 21. 41. send_sig(SIGSTOP, child, 1); // 发送SIGSTOP信号中止运行 22. 42. return 0; 23. 43. }
4) PTRACE_POKETEXT,PTRACE_POKEDATA处理
说明:与PTRACE_PEEKTEXT,PTRACE_PEEKDATA处理相反,此处理为写进程内存
流程:
1. 102. case PTRACE_POKETEXT: /* write the word at location addr. */ 2. 103. case PTRACE_POKEDATA: 3. 104. return write_long(child,addr,data);
5) PTRACE_PEEKUSR处理
说明:在Linux(i386)中,读写USER区域的数据值有用户寄存器和调试寄存器的值。用户寄存器包括17个寄存器,它们分别是EBX、ECX、EDX、ESI、EDI、EBP、EAX、DS、ES、FS、GS、ORIG_EAX、EIP、CS、EFLAGS、ESP、SS。这些寄存器的读写由辅助函数putreg()和getreg()函数完成。调试寄存器为DR0—DR7。其中DR4和DR5为系统保留的寄存器,不可以写。DR0—DR3中的断点地址必须在用户的3G空间内,在核心内存设置断点非法。DR7中的RWE与LEN数据位必须合法(LEN≠10保留、RWE≠10保留、RWE=00时LEN=00指令断点为一字节)。
流程:
1. 70. case PTRACE_PEEKUSR: { 2. 71. unsigned long tmp; 3. 72. intAndroid 逆向代码调试器开发 ( ptrace 函数 | 读取进程内存数据 )Android 逆向代码调试器开发 ( ptrace 函数 | 读寄存器 | 写寄存器 )
Android 逆向代码调试器开发 ( ptrace 函数 | 向进程内存写出数据 )