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函数执行流程

clip_image002[4]

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. }  
sys_ptrace函数

 

1) PTRACE_TRACEME处理

说明:此处理使当前进程进入调试状态。进程是否为调试状态由进程的标志PF_PTRACED表示。

流程:

clip_image004[4]

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.     } 
PTRACE_TRACEME处理

 

2) PTRACE_ATTACH处理

说明:此处理设置开始调试某一进程,此进程可以是任何进程(init 进程除外)。对某一进程的调试需有对这一进程操作的权限。不能调试自身进程。一个进程不能ATTACH多次。为完成对一个进程的调试设置,首先设置进程标志置PF_PTRACED。再将需调试的进程设置为当前进程的子进程。最后向它发信号SIGSTOP中止它的运行,使它进入调试状态。

流程:

clip_image006[4]

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.     }    
PTRACE_ATTACH处理

 

4) PTRACE_POKETEXT,PTRACE_POKEDATA处理

说明:与PTRACE_PEEKTEXT,PTRACE_PEEKDATA处理相反,此处理为写进程内存

流程:

clip_image007[4]

1. 102.            case PTRACE_POKETEXT: /* write the word at location addr. */

2. 103.            case PTRACE_POKEDATA:    

3. 104.                return write_long(child,addr,data);    
PTRACE_POKETEXT,PTRACE_POKEDATA处理

 

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指令断点为一字节)。

流程:

clip_image008[4]