zoukankan      html  css  js  c++  java
  • linux ptrace II

    第一篇 linux ptrace I

    在之前的文章中我们用ptrace函数实现了查看系统调用参数的功能。在这篇文章中,我们会用ptrace函数实现设置断点,跟代码注入功能。

    参考资料

    Playing with ptrace, Part I

    Playing with ptrace, Part II

    英文好的推荐直接看老外的文章。但是其代码是运行在x86系统上的,本文中将其移植到了x86_64系统。

    进程附加

    在之前的文章中,我们都是trace自己程序fork出来的子进程,现在我们来看一下如何trace一个正在运行的进程。

    trace一个正在运行的进程称为进程附加(attach)。使用的是ptrace函数的PTRACE_ATTACH参数。当一个进程成功附加到一个正在运行的进程时,此进程会成为被附加进程的父进程,同时向被附加的进程发送一个SIGSTOP信号,让其停止,这时我们就可以对其进行操纵。当我们完成对tracee的操作后就可以使用ptrace的PTRACE_DETACH参数停止附加。

    我们用一个循环来模拟一个正在运行的进程,下边称此程序为hello

    int main()
    {   int i;
        for(i = 0;i < 10; ++i) {
            printf("My counter: %d
    ", i);
            sleep(2);
        }
        return 0;
    }

    本文之后所有的程序都以此程序当作被附加的进程。在其运行之后我们可以使用 ps -h 命令查看其进程号(pid),以便我们通过进程号对其附加。

    接下来看一个简单的进程附加的例子

    #include <sys/types.h>
    #include <sys/reg.h>
    #include <sys/user.h>
    #include <sys/wait.h>
    #include <sys/ptrace.h>
    #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>
    
    int main(int argc, char *argv[])
    {
        pid_t traced_process;
        struct user_regs_struct regs;
        long ins;
        if(argc != 2)
        {
            puts("no pid input");
            exit(1);
        }
        traced_process = atoi(argv[1]);
        printf("try to trace pid :%u
    ",traced_process);
        if(ptrace(PTRACE_ATTACH,traced_process,NULL,NULL)==-1)
        {
            perror("trace error:");
        }
        wait(NULL);
        if(ptrace(PTRACE_GETREGS,traced_process,NULL,&regs)==-1)
        {
            perror("trace error:");
        }
        ins = ptrace(PTRACE_PEEKTEXT,traced_process,regs.rip,NULL);
        if(ins == -1)
        {
            perror("trace error:");
        }
        printf("EIP:%llx Instruction executed: %lx
    ",regs.rip,ins);
        if(ptrace(PTRACE_DETACH,traced_process,NULL,NULL)==-1)
        {
            perror("trace error:");
        }
        return 0;
    }

    上边的程序对hello进行了附加,等其停下来以后,读取hello要运行的下一条指令的内容(地址存在rip中)。读取之后停止附加,让hello继续运行。

    设置断点

    下面要实现的是许多调试器都拥有的设置断点功能。

    设置断点的原理:假如要在A地址处设置断点,可以把A地址处的指令替换为一条trap指令,就是说当tracee运行完这条被替换的指令后会自动停止,然后告知tracer自己已停止。

    代码如下:

    #include <sys/ptrace.h>
    #include <sys/reg.h>
    #include <sys/user.h>
    #include <sys/wait.h>
    #include <unistd.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    #define LONG_SIZE 8
    
    void getdata(pid_t child, long addr,char *str,int len)
    {
        char *laddr = str;
        int i = 0,j = len/LONG_SIZE;
        union u{
            long val;
            char chars[LONG_SIZE];
        } word;
        while(i<j)
        {
            word.val = ptrace(PTRACE_PEEKDATA,child,addr + i*LONG_SIZE,NULL);
            if(word.val == -1)
                perror("trace error");
            memcpy(laddr,word.chars,LONG_SIZE);
            ++i;
            laddr += LONG_SIZE;
        }
        j = len %LONG_SIZE;
        if(j!=0)
        {
            word.val == ptrace(PTRACE_PEEKDATA,child,addr + i*LONG_SIZE,NULL);
            if(word.val == -1)
                perror("trace error");
        }
        str[len] = '';
    }
    
    
    void putdata(pid_t child,long addr,char *str,int len)
    {
        char *laddr = str;
        int i = 0, j = len/LONG_SIZE;
        union u{
            long val;
            char chars[LONG_SIZE];
        }word;
        while(i<j)
        {
            memcpy(word.chars,laddr,LONG_SIZE);
            if(ptrace(PTRACE_POKEDATA,child,addr+i*LONG_SIZE,word.val) == -1)
                perror("trace error");
            ++i;
            laddr += LONG_SIZE;
        }
        j = len % LONG_SIZE;
        if(j != 0)
        {
            word.val = 0;
            memcpy(word.chars,laddr,j);
            if(ptrace(PTRACE_POKEDATA,child,addr+i*LONG_SIZE,word.val) == -1)
                perror("trace error");
        }
    }
    
    
    void printBytes(const char* tip,char* codes,int len)
    {
        int i;
        printf("%s :",tip);
        for(i = 0;i<len;++i)
        {
            printf("%02x ",(unsigned char)codes[i]);
        }
        puts("");
    }
    
    #define CODE_SIZE 8
    
    int main(int argc ,char *argv[])
    {
        if(argc != 2)
        {
            puts("no pid input");
            exit(1);
        }
        pid_t traced_process;
        struct user_regs_struct regs;
        long ins;
        char code[LONG_SIZE] = {0xcc};
        char backup[LONG_SIZE];
        traced_process = atoi(argv[1]);
        printf("try to attach pid:%u
    ",traced_process);
        if(ptrace(PTRACE_ATTACH,traced_process,NULL,NULL) == -1)
        {
            perror("trace attach error");
        }
        wait(NULL);
        if(ptrace(PTRACE_GETREGS,traced_process,NULL,&regs) == -1)
        {
            perror("trace get regs error");
        }
        //copy instructions into backup variable
        getdata(traced_process,regs.rip,backup,CODE_SIZE);
        printBytes("get tracee instuction",backup,LONG_SIZE);
        puts("try to set breakpoint");
        printBytes("set breakpoint instruction",code,LONG_SIZE);
        putdata(traced_process,regs.rip,code,CODE_SIZE);
        if(ptrace(PTRACE_CONT,traced_process,NULL,NULL) == -1)
        {
            perror("trace continue error");
        }
        wait(NULL);
        puts("the process stopped Press <Enter> to continue");
        getchar();
        printBytes("place breakpoint instruction with tracee instruction",backup,LONG_SIZE);
        putdata(traced_process,regs.rip,backup,CODE_SIZE);
        ptrace(PTRACE_SETREGS,traced_process,NULL,&regs);
        ptrace(PTRACE_DETACH,traced_process,NULL,NULL);
        return 0;
    
    }

    这里在hello程序停下来后将rip指向的指令备份在了backup数组中,同时把rip指向的指令替换为code数组中的指令。

    code数组中0xcc对应为汇编指令的

    int3 ;软中断指令,执行后程序会陷入中断停止,同时发送signal,可被tracer接收

    当tracer接收到软中断指令发送的signal时,从等待(wait)中被唤醒,把被替换的指令还原回去,同时也将寄存器还原,最后停止附加,让hello继续执行。

    示意图如下

    代码注入

    接下来我们实现最后一个功能,进行代码注入,让hello程序打印出一行 “hello world"

    要进行代码注入,首先我们要知道被注入代码的机器指令是什么。

    先写出要进行注入代码的汇编代码

    section .text
    
    global main
    
    main:
        jmp forward
    backward:
        pop rsi
        mov rax, 1
        mov rdi, 1
        mov rdx, 13
        syscall
        int3
    forward:
        call backward
    db "Hello world",0xa

    注意,这里为什么要跳来跳去呢,因为我们要定位字符串的地址,在执行 call backward 时,会把其下一条指令的地址,也就是字符串的地址压入栈中。然后 pop rsi 将其取出。

    使用nasm工具对其进行汇编

    nasm -f  elf64 inject.asm

    再使用objdump工具查看对应的机器码

    objdump -d inject.o

    现在就可以将其注入到hello的进程里了。方法同设置断点一样,这里只贴出主函数代码

    #define CODE_SIZE 48
    int
    main(int argc ,char *argv[]) { if(argc<2) { puts("no pid input"); exit(1); } pid_t tracee = atoi(argv[1]); char code_inject[CODE_SIZE] ={0xeb,0x13,0x5e,0xb8,0x01,0x00,0x00,0x00,0xbf,0x01,0x00,0x00,0x00,0xba,0x0d,0x00,0x00,0x00,0x0f,0x05,0xcc,0xe8,0xe8,0xff,0xff,0xff,0x48,0x65,0x6c,0x6c,0x6f,0x20,0x77,0x6f,0x72,0x6c,0x64,0xa}; char code_backup[CODE_SIZE]; struct user_regs_struct oldregs,regs; long ins; if(ptrace(PTRACE_ATTACH,tracee,NULL,NULL) == -1) { perror("attach error"); } wait(NULL); puts("attach success"); ptrace(PTRACE_GETREGS,tracee,NULL,&regs); long addr = freeSpaceAddr(tracee); // long addr = regs.rip; printf("find free addr %lx ",addr); getdata(tracee,addr,code_backup,CODE_SIZE); putdata(tracee,addr,code_inject,CODE_SIZE); memcpy(&oldregs,&regs,sizeof(regs)); regs.rip = addr; printf("new rip :%llx ",regs.rip); if(ptrace(PTRACE_SETREGS,tracee,NULL,&regs) == -1) { perror("set regs error"); } puts("replace instructions success, continue tracee"); if(ptrace(PTRACE_CONT,tracee,NULL,NULL) == -1) { perror("continue tracee error"); } wait(NULL); ptrace(PTRACE_GETREGS,tracee,NULL,&regs); printf("tracee end at rip: %llx ",regs.rip); puts("tracee has stopped, putting back original instructions"); putdata(tracee,addr,code_backup,CODE_SIZE); if(ptrace(PTRACE_SETREGS,tracee,NULL,&oldregs) == -1) { perror("put original instuctions error"); } ptrace(PTRACE_DETACH,tracee,NULL,NULL); return 0; }

    以上我们完成了设置断点和代码注入的功能。

  • 相关阅读:
    剑指 Offer——13. 调整数组顺序使奇数位于偶数前面
    剑指 Offer——3. 从尾到头打印链表
    剑指 Offer——2. 替换空格
    剑指 Offer——1. 二维数组中的查找
    LeetCode 905. Sort Array By Parity 按奇偶校验排列数组
    LeetCode 448. Find All Numbers Disappeared in an Array找到所有数组中消失的元素
    SSH 代码笔记
    anaconda3安装caffe
    opencv多版本安装
    人脸文章与数据库
  • 原文地址:https://www.cnblogs.com/mmmmar/p/6048711.html
Copyright © 2011-2022 走看看