66

GitHub - jinningli/system2018-project1: Fantasy Ptrace

 6 years ago
source link: https://github.com/jinningli/system2018-project1
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

README.md

Project 1: Fantasy Ptrace

概述

ptrace是process和trace的简写,直译为进程跟踪。它提供了一种使父进程得以监视和控制其子进程的方式,它还能够改变子进程中的寄存器和内核映像,因而可以实现系统调用的跟踪和断点调试。

Deadline

3.28日下午18:00

另外

可以顺便在右上角面帮咱点个Star哦~

使用方法

64位 Ubuntu

妥。可以直接运行

64位 MAC OSX

不妥。解决方案:

1. 虚拟机

2. 悄悄使用本可爱的助教实验室的服务器

使用ssh链接到服务器,密码是system2018

[注意]

禁止查看和编辑服务器上别人的代码

跑完后记得清理掉自己的代码哦rm -r ~/system/Yourname

密码不要外传

不要运行project1以外的程序

ssh [email protected]
mkdir ~/system/Yourname

然后exit回到本机 传送你的文件到服务器上的文件夹

scp -r YourFilePath [email protected]:~/system/Yourname

然后连接到服务器上开始玩耍

ssh [email protected]
cd ~/system/Yourname

64位 Windows

不妥。解决方案:

1. 虚拟机

  • 自己找找看安装包哦 paralells 或 VMWare

2. 悄悄使用本可爱的助教实验室的服务器

32位机器

不妥。请想办法使用本可爱的助教实验室的服务器,或者把代码改写成32位机子能运行的代码。

Task 1 输出反转

准备知识

进程

程序是一个静态的概念,它只是一些预先编译好的指令和数据集合的一个文件;而进程是一个动态的概念,它是程序运行时的一个过程。

创建进程

#include <unistd.h>
pid_t fork(void);

// 返回值:子进程返回0,父进程返回子进程ID,出错返回-1

fork函数被调用一次,却能够返回两次,它可能有三种不同的返回值:

  • 在父进程中,fork返回新创建子进程的进程ID;
  • 在子进程中,fork返回0;
  • 如果出现错误,fork返回-1;

在fork函数执行完毕后,如果创建新进程成功,则出现两个进程,一个是子进程,一个是父进程。可以通过fork返回的值来判断当前进程是子进程还是父进程。

系统调用

在现代的操作系统里,程序运行的时候,本身是没有权利访问多少系统资源的。由于系统有限的资源有可能被多个不同的应用程序同时访问,因此,如果不加以保护,那么各个应用程序难免产生冲突。所以现代操作系统都将可能产生冲突的系统资源给保护起来,阻止应用程序直接访问。这些资源包括文件、网络、IO、各种设备等。 每个操作系统都会提供一套接口,来封装对系统资源的调用,这套接口就是系统调用。

操作系统把进程空间分为了用户空间和内核空间,系统调用是运行在内核空间的,而应用程序基本都是运行在用户空间的。应用程序想要访问系统资源,就必须通过系统调用。用户空间的应用程序要想调用内核空间的系统调用,就需要从用户空间切换到内核空间,这一般是通过中断来实现的。什么是中断呢?中断是一个硬件或者软件发出的请求,要求CPU暂停当前的工作转手去处理更加重要的事情。中断一般具有两个属性,中断号和中断处理程序。在内核中,有一个叫做中断向量表的数组来存放中断号和中断处理程序。当中断到来的时候,CPU会根据中断号找到对应的中断处理程序,并调用它。中断处理程序执行完成后,CPU会继续执行之前的代码。

通常意义上,中断有两种类型,一种称为硬件中断,这种中断来自于硬件的异常或其他事件的发生;另一种称为软件中断,软件中断通常是一条指令(i386下是int),带有一个参数记录中断号。linux系统使用int 0x80来触发所有的系统调用,和中断一样,系统调用带有一个系统调用号,这个系统调用就像身份标识一样来表明是哪一个系统调用。32位下,这个系统调用号会放在eax寄存器中。64位下,这个系统调用号会放在rax寄存器中。

系统调用号表:http://blog.csdn.net/sinat_26227857/article/details/44244433

32位下如果系统调用有一个参数,那么参数通过ebx寄存器传入,x86下linux支持的系统调用参数至多有6个,分别使用6个寄存器来传递,它们分别是ebx、ecx、edx、esi、edi和ebp。64位下略有不同。

例如对于write调用,ebx(64位为rdi)存放的是fd; ecx(64位为rsi)存放的是字符串数据的地址; edx(64位为rdx)存放的是字符串长度。

触发系统调用后,CPU首先需要切换堆栈,当程序的当前栈从用户态切换到内核态后,会找到系统调用号对应的调用函数,它们都是以"sys_"开头的,当执行完调用函数后,返回值会存放在eax(rax)寄存器返回到用户态。

信号

信号是在软件层次上对中断机制的一种模拟,它是一种进程间异步通信的机制。

一个进程要发信号给另一个进程,可以使用这些函数:kill()、raise()、 sigqueue()、alarm()、setitimer()以及abort()。它其实是通过系统调用把信号先发给内核。当另一个进程从内核态回用户态的时候,它会先去找一下有没有发给自己的信号,如果有,就处理掉。

进程可以通过三种方式来响应一个信号:

  • 忽略信号,即对信号不做任何处理;
  • 捕捉信号。通过signal()定义信号处理函数,当信号发生时,执行相应的处理函数;
  • 执行缺省操作,Linux对每种信号都规定了默认操作。

ptrace函数

函数原型如下:

#include <sys/ptrace.h>
long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data);

ptrace有四个参数:

  • enum __ptrace_request request:指示了ptrace要执行的命令。
  • pid_t pid: 指示ptrace要跟踪的进程。
  • void *addr: 指示要监控的内存地址。
  • void *data: 存放读取出的或者要写入的数据。 request参数决定了ptrace的具体功能:

1.PTRACE_TRACEME ptrace(PTRACE_TRACEME,0 ,0 ,0) 描述:子进程使用,使得本进程被其父进程所跟踪。

2.PTRACE_PEEKTEXT, PTRACE_PEEKDATA ptrace(PTRACE_PEEKDATA, pid, addr, data) 描述:从内存地址中读取一个字,数据由函数返回,pid表示被跟踪的子进程,内存地址由addr给出,data参数被忽略。

3.PTRACE_POKETEXT, PTRACE_POKEDATA ptrace(PTRACE_POKEDATA, pid, addr, data) 描述:往内存地址中写入一个字。pid表示被跟踪的子进程,内存地址由addr给出,data为所要被写入的数据。

4.PTRACE_PEEKUSER ptrace(PTRACE_PEEKUSER, pid, addr, data) 描述:从USER区域中读取一个字节,pid表示被跟踪的子进程,addr表示读取数据在USER区域的偏移量,返回值为函数返回值,data参数被忽略。

5.PTRACE_POKEUSER ptrace(PTRACE_POKEUSER, 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_SYSCALL, pid, 0, signal) 描述:继续执行。pid表示被跟踪的子进程,signal为0则忽略引起调试进程中止的信号,若不为0则继续处理信号signal。与PTRACE_CONT不同的是进行系统调用跟踪。在被跟踪进程继续运行直到调用系统调用开始或结束时,被跟踪进程被中止,并通知父进程。

8.PTRACE_KILL ptrace(PTRACE_KILL,pid) 描述:杀掉子进程,使它退出。pid表示被跟踪的子进程。

9.PTRACE_SINGLESTEP ptrace(PTRACE_SINGLESTEP, pid, 0, signal) 描述:设置单步执行标志,单步执行一条指令。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为用户变量地址用于返回读到的数据。

13.PTRACE_SETREGS ptrace(PTRACE_SETREGS, pid, 0, data) 描述:设置寄存器值,pid表示被跟踪的子进程,data为用户数据地址。

更多详细的信息可以参考linux下man手册: http://linux.die.net/man/2/ptrace

Task 1

fantasy.c是一个简单的打印字符串的程序:

#include "stdio.h"

int main()
{
    printf("Oh, Fantasy!\n");
    return 0;
}

它将打印出 Oh, Fantasy!\n

现在你要做的是按照文件中的提示补全reverse.c中的代码(有TODO的地方),使得reverse.c在通过ptrace追踪fantasy.c时,一旦检测到fantasy.c调用了 SYS_write 系统调用的时候,通过修改系统调用的参数把write要输出的字符串反转,即\n!ysatnaF ,hO,再进行系统调用。当然,你也可以不按照reverse.c的模版来写,自由发挥是资瓷的。

补全好以后运行的步骤是先编译fantasy.c

gcc fantasy.c -o fantasy

然后在同一目录下编译运行reverse.c:

gcc reverse.c -o reverse
./reverse

Task 2 单步调试

准备知识

GDB调试工具

GDB是GNU开源组织发布的一个强大的UNIX下的程序调试工具。一般来说,GDB主要帮忙你完成下面四个方面的功能:

  • 启动你的程序,可以按照你的自定义的要求随心所欲的运行程序。
  • 可让被调试的程序在你所指定的调置的断点处停住。
  • 当程序被停住时,可以检查此时你的程序中所发生的事。
  • 动态的改变你程序的执行环境。

Task 2

可爱的助教当然不会让你手写GDB啦。你只需要写一个简单的调试工具,可以跟踪程序执行,监控程序每一条指令的运行,并输出当前指令、指令的值、EAX EBX ECX EDX 寄存器的状态。原理是通过PTRACE_PEEKUSER 和 PTRACE_GETREGS来访问寄存器,用PTRACE_PEEKTEXT来访问内存,通过PTRACE_SINGLESTEP来实现单步调试。

interesting.s 是要被调试的汇编程序,他的功能是先打印Oh, \n,然后打印interesting!\n。你可以打开它来看看有哪些代码。

step.c是不完整的单步调试工具的代码。你要做的是根据step.c中的要求将其补充完整,然后进行测试。step.c的一个输入参数argv是要被调试的程序路径。当然,你也可以不按照step.c的模版来写,自由发挥是资瓷的。

补充完整以后,首先编译一下interesting.s:

as interesting.s -o interesting.o
ld -o interesting interesting.o

然后编译一下step.c:

gcc step.c -o step

然后使用step来单步调试interesting:

./step interesting

这时应该会输出这样的结果:

[1] RIP = 0x00000000004000b0, Instruction = 0x00000000000004b8
EAX: 0x00000000 EBX: 0x00000000
ECX: 0x00000000 EDX: 0x00000000
Press any key to continue...
......

然后就一直按回车就行啦,可以观察到这个过程中四个寄存器的变化,看看和interesting.s中的是不是对应了呢:

...
movl $4, %eax
movl $1, %ebx
movl $s1, %ecx
movl $5, %edx
int $0x80
...

然后可以运行:

objdump -d interesting

来检查 RIPInstruction 输出的是不是正确。只要对比末位是不是一样就行了,因为X86-64是CISC,指令的长度不确定,而我们固定只取64位,超出指令的部分可能会是其他指令。

尝试设置断点

为了让大家减轻负担,这部分不用提交。不需要你写代码,只要看看代码,跑一跑,体验一下就行了。断点的代码位于Task2文件夹里

通过int 3指令在调试器中设定断点

要在被调试进程中的某个目标地址上设定一个断点,调试器需要做下面两件事情:

  • 保存目标地址上的数据
  • 目标地址上的第一个字节替换为int 3指令(0xcc)

然后,当调试器向操作系统请求开始运行进程时,进程最终一定会碰到int 3指令。此时进程停止,操作系统将发送一个信号。这时就是调试器再次出马的时候了,接收到一个其子进程(或被跟踪进程)停止的信号,然后调试器要做下面几件事:

  • 在目标地址上用原来的指令替换掉int 3

  • 将被跟踪进程中的指令指针向后递减1。这么做是必须的,因为现在指令指针指向的是已经执行过的int 3之后的下一条指令。

  • 由于进程此时仍然是停止的,用户可以同被调试进程进行某种形式的交互。这里调试器可以让你查看变量的值,检查调用栈等等。

  • 当用户希望进程继续运行时,调试器负责将断点再次加到目标地址上(由于在第一步中断点已经被移除了),除非用户希望取消断点。

运行方法

  1. 编译breakpoint.cinteresting.s
gcc breakpoint.c -o breakpoint
as interesting.s -o interesting.o
ld -o interesting interesting.o
  1. 使用objdump -d interesting来查看汇编指令对应的指令地址,看看你想在哪停下来。输出可能长这样
interesting:     file format elf64-x86-64


Disassembly of section .text:

00000000004000b0 <_start>:
  4000b0:	b8 04 00 00 00       	mov    $0x4,%eax
  4000b5:	bb 01 00 00 00       	mov    $0x1,%ebx
  4000ba:	b9 e3 00 60 00       	mov    $0x6000e3,%ecx
  4000bf:	ba 05 00 00 00       	mov    $0x5,%edx
  4000c4:	cd 80                	int    $0x80
  4000c6:	b8 04 00 00 00       	mov    $0x4,%eax
  4000cb:	bb 01 00 00 00       	mov    $0x1,%ebx
  4000d0:	b9 e9 00 60 00       	mov    $0x6000e9,%ecx
  4000d5:	ba 0d 00 00 00       	mov    $0xd,%edx
  4000da:	cd 80                	int    $0x80
  4000dc:	b8 01 00 00 00       	mov    $0x1,%eax
  4000e1:	cd 80                	int    $0x80

比如我想要在指令地址为0x4000c6的地方设置断点,也就是第一次系统调用(int $0x80)执行完后。那么运行:

./breakpoint interesting 0x4000c6

然后就可以看到进程在0x4000c6停下了:

target started. will run 'interesting'
Child started. EIP = 0x004000b0
Original data at 0x004000c6: 0x000004b8
After trap, data at 0x004000c6: 0x000004cc
Oh,
Child got a signal: Trace/breakpoint trap
Child stopped at EIP = 0x004000c6

interesting!
Child exited

提交

把改好的project1整个文件夹压缩成学号-姓名-project1.zip发送到[email protected],邮件名学号-姓名-project1.

加油!

有任何困难或不明白的地方,请联系我。

黎先生

Tel: 17621782336

QQ: 137645534

email: [email protected] / [email protected]


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK