一、函数定义

long ptrace(enum __ptrace_request request,pid_t pid,void *addr,void *data);

request:决定要执行的操作;

pid:是目标进程的进程id;

addr:地址值

data:根据request值变化作用,存放数据。

头文件在:

#include <sys/ptrace.h>

request值有哪些?

0--PTRACE_TRACEME:指示此进程将由其父进程跟踪(换而言之,子进程运执行此操作就会允许别人来跟踪他自己),子进程进入阻塞态。如果进程的父进程不期望跟踪它,那么进程不应该发出此请求。使用示例:ptrace(PTRACE_TRACEME,0,0,0)。
1--PTRACE_PEEKTEXT, 2-- PTRACE_PEEKDATA:在目标进程的内存中读取地址地址处的一个字的内容,并返回该内容。
3--PTRACE_PEEKUSER:在目标进程的用户区域的偏移地址处(参数addr指定)读入一个字的内容。关于偏移大小对应的内容,更多信息可以在sys/user.h中了解。
4--PTRACE_POKETEXT, 5--PTRACE_POKEDATA:将参数data的内容复制到参数addr所指的地址处。两个相同。
6--PTRACE_POKEUSER:将参数data的内容复制到参数addr所指用户区域中的偏移。
12--PTRACE_GETREGS, 14--PTRACE_GETFPREGS:将目标进程(常指子进程)的通用寄存器或浮点寄存器的值复制到跟踪程序(常指父进程)的地址空间中(参数addr设置)。
7--PTRACE_CONT:让已阻塞的目标进程继续运行。如果数据非零,它被解释为要发送到示踪物的信号的数目;否则,没有信号交付。
24--PTRACE_SYSCALL, 9--PTRACE_SINGLESTEP:
8--PTRACE_KILL:向目标进程发送一个SIGKILL信号来终止它
16--PTRACE_ATTACH:附加到pid中指定的进程,跟踪(或调试)该进程。
17--PTRACE_DETACH:断开附加的进程。

更多可以看sys/user.h了解。

  ptrace函数(系统调用)提供了一种跟踪另一个进程的方法,控制该进程执行流程的方法,并获取和更改该进程的内存和寄存器的值。其主要用于实现断点调试和系统调用跟踪。ptrace是以线程为单位,若子进程中存在多个线程,则父进程需要ptrace所有线程。

二、用法

  操作系统通过系统调用的标准机制提供服务。它们为访问底层硬件和低级服务(例如文件系统)提供了标准 API。当进程想要调用系统调用时,它会将系统调用的参数放入寄存器并调用软中断 0x80。这个软中断就像是通往内核模式的大门,内核在检查完参数后会执行系统调用。

系统调用号放在寄存器 %eax 中。系统调用的参数按顺序放入寄存器 %ebx、%ecx、%edx、%esi 和 %edi。

ptrace的调用效果什么时候在子进程触发呢?

  在执行系统调用之前,内核会检查进程是否正在被跟踪。如果是,内核停止进程并将控制权交给跟踪进程,以便它可以检查和修改被跟踪进程的寄存器。我们可以使用 PTRACE_CONT 作为第一个参数使子进程继续执行。

  ptrace函数读写数据是在进入和离开call时触发。

示例:

#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <linux/user.h>
#include <sys/syscall.h>
int main()
{   pid_t child;
    long orig_eax, eax;
    long params[3];
    int status;
    int insyscall = 0;
    struct user_regs_struct regs;
    child = fork();
    if(child == 0) {
        ptrace(PTRACE_TRACEME, 0, NULL, NULL);
        execl("/bin/ls", "ls", NULL);
    }
    else {
       while(1) {
          wait(&status);
          if(WIFEXITED(status))
              break;
          orig_eax = ptrace(PTRACE_PEEKUSER,
                            child, 4 * ORIG_EAX,
                            NULL);
          if(orig_eax == SYS_write) {
              if(insyscall == 0) {
                 /* Syscall entry */
                 insyscall = 1;
                 ptrace(PTRACE_GETREGS, child,
                        NULL, ®s);
                 printf("Write called with "
                        "%ld, %ld, %ld\n",
                        regs.ebx, regs.ecx,
                        regs.edx);
             }
             else { /* Syscall exit */
                 eax = ptrace(PTRACE_PEEKUSER,
                              child, 4 * EAX,
                              NULL);
                 printf("Write returned "
                        "with %ld\n", eax);
                 insyscall = 0;
             }
          }
          ptrace(PTRACE_SYSCALL, child,
                 NULL, NULL);
       }
   }
   return 0;
}

告诉内核该进程正在被跟踪,并且当子进程执行下一步函数调用时,它将控制权交给其父进程。父进程通过 wait函数去等待来自内核的通知,然后父进程可以检查查看子进程的寄存器使用情况。

我们在调用 ptrace(PTRACE_TRACEME, ..) 后将要跟踪的进程作为子进程运行。如果只是想查看进程如何进行系统调用并跟踪程序,这就足够了。如果要跟踪或调试已经运行的进程,则应使用 ptrace(PTRACE_ATTACH, ..)。

使用 PTRACE_POKEDATA 调用 ptrace 来更改数据值。它的工作方式与 PTRACE_PEEKDATA 完全相同。

  wait函数用于检查子进程是否已经退出。

  使用PTRACE_GETREGS 作为第一个参数调用ptrace函数,将在进入一个call中时获取所有的寄存器的值。

  使用PTRACE_SYSCALL 作为第一个参数调用ptrace函数,使内核恢复子进程执行并在下一个系统调用进入/退出时停止子进程。

  我们可以使用 PTRACE_POKEDATA 作为第一个参数调用ptrace函数来更改寄存器或内存空间的数据值,它的工作方式与 PTRACE_PEEKDATA 完全相同。

提供了单步执行子进程代码的功能。使用 ptrace(PTRACE_SINGLESTEP, child,NULL, NULL) 告诉内核在每条指令处停止子进程,并让父进程控制。

三、相关函数

fork():创建子进程,返回不等于0时是在父进程代码块中,为0时是在子进程中。

wait(&status):等待子进程结束,检测到结束或成为僵尸进程(先把它杀了)就会返回。返回值为子进程pid(正常返回)或-1(没有子进程,fork前调用造成)。

四、代码注入

思路:我们先将shellcode注入到目标进程的内存空间中,再通过ptrace函数跟踪该进程,在遇到即将进入目标函数时断下进程,保存执行时的上下文信息(寄存器信息),并修改eip指向的地址为注入代码的地址。

代码如下:

int main(int argc, char *argv[])
{   pid_t traced_process;
    struct user_regs_struct oldregs, regs;
    long ins;
    int len = 41;
    char insertcode[] =
"\xeb\x15\x5e\xb8\x04\x00"
        "\x00\x00\xbb\x02\x00\x00\x00\x89\xf1\xba"
        "\x0c\x00\x00\x00\xcd\x80\xcc\xe8\xe6\xff"
        "\xff\xff\x48\x65\x6c\x6c\x6f\x20\x57\x6f"
        "\x72\x6c\x64\x0a\x00";
    char backup[len];
    long addr;
    if(argc != 2) {
        printf("Usage: %s <pid to be traced>\n",
               argv[0], argv[1]);
        exit(1);
    }
    traced_process = atoi(argv[1]);
    ptrace(PTRACE_ATTACH, traced_process,
           NULL, NULL);
    wait(NULL);
    ptrace(PTRACE_GETREGS, traced_process,
           NULL, ®s);
    addr = freespaceaddr(traced_process);
    getdata(traced_process, addr, backup, len);
    putdata(traced_process, addr, insertcode, len);
    memcpy(&oldregs, ®s, sizeof(regs));
    regs.eip = addr;
    ptrace(PTRACE_SETREGS, traced_process,
           NULL, ®s);
    ptrace(PTRACE_CONT, traced_process,
           NULL, NULL);
    wait(NULL);
    printf("The process stopped, Putting back "
           "the original instructions\n");
    putdata(traced_process, addr, backup, len);
    ptrace(PTRACE_SETREGS, traced_process,
           NULL, &oldregs);
    printf("Letting it continue with "
           "original flow\n");
    ptrace(PTRACE_DETACH, traced_process,
           NULL, NULL);
    return 0;
}