• 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; }

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

  • 相关阅读:
    webservice的简单介绍
    如何在page_load方法判断是服务器端控件引发的page_load方法
    android架构概述
    页面传递数组参数
    XML与DataTable/DataSet互转
    jquery调用asp.net 页面后台方法
    asp.net缓存机制
    jQuery操作radio,checkbox,select
    jquery选择器
    android开发环境的搭建过程
  • 原文地址:https://www.cnblogs.com/mmmmar/p/6048711.html
Copyright © 2020-2023  润新知