4——Linux二进制分析——学习——ptrace调试器

书中第3.4节贴了一个ptrace使用方法的例子,没有过多的解释,并且test.c代码也欠缺。我用了很长的一段时间进行了反复调试,虽然还不是非常的熟悉,但应该可以写这篇文章了。

gdb是我们所有嵌入式软件工程师都会用到的一个工具,我也不例外。曾经也想过看一看gdb内部是怎么实现的,大家应该都有过这种想法吧。除了gdb还有strace,它们是如何能跟踪自己写的进程呢?怎么就能看系统调用、设置断点、查看栈信息,甚至修改参数呢?没有等到我去下载gdb的代码分析,这一节就简单的告诉了我gdb大概是如何实现的,后面的章节肯定还会让我对此有更深入的理解。薄薄的二百多页书,读下来花费的精力肯定是巨大的,提升应该页会有很多吧,真的很期待读完的那一天。#哈哈#

言归正传,我将这个调试器命名为tracer,上一篇的elf分析器命名为elfparse。tracer是建立的elfparse基础上,做了进一步的功能开发,就是加入了ptrace()接口的多次使用

下面这段200行左右的代码就是tracer的所有源码,和书中的印刷版一致,甚至比那个好,包括后面我所有贴出来的代码和编译都是经过我实际测试的,均能够正常顺利的使用。如果你使用我下面的代码调试出现问题,请留言,我再调试修改。不要被我的排版吓到,排版不好可能导致读起来不是那么方便,我尽力做好。

代码中所有带有XBter的注释都是我添加的,原文中没有,都是我认为关键的地方。原代码无法正常编译通过,缺少 <sys/wait.h>头文件,wait()接口无法找到定义。

/* tracer.c gcc tracer.c -o tracer */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#include <elf.h>
#include <sys/types.h>
#include <sys/user.h>
#include <sys/stat.h>
#include <sys/ptrace.h>
#include <sys/mman.h>
#include <sys/wait.h>//The sample of the book don't include this lib, it's wrong. XBter

typedef struct handle{
    char *exec;
    char *symname;
    Elf64_Addr symaddr;//symname's address  XBter
    Elf64_Ehdr *ehdr;
    Elf64_Phdr *phdr;
    Elf64_Shdr *shdr;
    uint8_t *mem;
    struct user_regs_struct pt_reg;
}handle_t;

Elf64_Addr lookup_symbol(handle_t *, const char *);
int main(int argc, char **argv, char **envp)
{
    int fd;
    handle_t h;
    struct stat st;
    long trap, orig;
    int status, pid;
    char * args[2];

    /* I think almost all tools have this judgement. XBter */
    if(argc < 3){
        printf("Usage: %s <program> <function>\n", argv[0]);
        exit(0);
    }

    if((h.exec = strdup(argv[1])) == NULL){
        perror("strdup");
        exit(-1);
    }

    args[0] = h.exec;
    args[1] = NULL;

    if((h.symname = strdup(argv[2])) == NULL){
        perror("strdup");
        exit(-1);
    }

    if((fd = open(argv[1], O_RDONLY)) < 0){
        perror("open");
        exit(-1);
    }

    if(fstat(fd, &st) < 0){
        perror("fstat");
        exit(-1);
    }

    h.mem = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
    if(h.mem == MAP_FAILED){
        perror("mmap");
        exit(-1);
    }

    h.ehdr = (Elf64_Ehdr*)h.mem;
    h.phdr = (Elf64_Phdr*)(h.mem + h.ehdr->e_phoff);
    h.shdr = (Elf64_Shdr*)(h.mem + h.ehdr->e_shoff);

    if(h.mem[0] != 0x7f || strncmp((char*)&h.mem[1], "ELF", 3)){
        printf("%s is not an ELF file\n", h.exec);
        exit(-1);
    }

    /* I use gcc, so I should add the -no-pie flag. XBter */
    if(h.ehdr->e_type != ET_EXEC){
        printf("%s is not an ELF executable. The type: %d\n", h.exec, h.ehdr->e_type);
        exit(-1);
    }

    if(h.ehdr->e_shstrndx == 0 || h.ehdr->e_shoff == 0 || h.ehdr->e_shnum == 0){
        printf("Section header table not found\n");
        exit(-1);
    }

    if((h.symaddr = lookup_symbol(&h, h.symname)) == 0){
        printf("Unable to find symbol: %s not found in executable\n", h.symname);
        exit(-1);
    }

    close(fd);

    if((pid = fork()) < 0){
        perror("fork");
        exit(-1);
    }

    if(pid == 0){
        if(ptrace(PTRACE_TRACEME, pid, NULL, NULL) < 0){
            perror("PTRACE_TRACEME");
            exit(-1);
        }
        execve(h.exec, args, envp);//run the child pid. XBter
        exit(0);
    }

    wait(&status);
    printf("\nXBter\n\nBeginning analysis of pid: %d at %lx\n", pid, h.symaddr);
    if((orig = ptrace(PTRACE_PEEKTEXT, pid, h.symaddr, NULL)) < 0){
        perror("PTRCE_PEEKTEXT");
        exit(-1);
    }

    /* Set breakpoint, 0xcc. XBter*/
    trap = (orig & ~0xff) | 0xcc;
    if(ptrace(PTRACE_POKETEXT, pid, h.symaddr, trap) < 0){
        perror("PTRACE_PEEKTEXT");
        exit(-1);
    }

trace:
    if(ptrace(PTRACE_CONT, pid, NULL, NULL) < 0){
        perror("PTRACE_CONT");
        exit(-1);
    }
    wait(&status);
    /**
     * WIFSTOPPED(status): 如果进程在被ptrace调用监控的时候被信号暂停/停止,返回True
     * WSTOPSIG(status): WIFSTOPPED为true时,返回导致子进程停止的信号类型
     * WIFEXITED(status):子进程正常退出情况下为true             XBter
     */
    if(WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP)
    {
        if(ptrace(PTRACE_GETREGS, pid, NULL, &h.pt_reg) < 0)
        {
            perror("PTRACE_GETREGS");
            exit(-1);
        }

        printf("\nExecutable %s (pid: %d) has hit breakpoint 0x%lx\n\n", h.exec, pid, h.symaddr);

        printf("%%rcx: %llx\n%%rdx: %llx\n%%rbx: %llx\n"
               "%%rax: %llx\n%%rdi: %llx\n%%rsi: %llx\n"
               "%%r8: %llx\n%%r9: %llx\n%%r10: %llx\n"
               "%%r11: %llx\n%%r12: %llx\n%%r13: %llx\n"
               "%%r14: %llx\n%%r15: %llx\n%%rsp: %llx", h.pt_reg.rcx, h.pt_reg.rdx, h.pt_reg.rbx, h.pt_reg.rax,
               h.pt_reg.rdi, h.pt_reg.rsi, h.pt_reg.r8, h.pt_reg.r9, h.pt_reg.r10, h.pt_reg.r11, h.pt_reg.r12,
               h.pt_reg.r13, h.pt_reg.r14, h.pt_reg.r15, h.pt_reg.rsp);
        printf("\nPlease hit any key to continue: ");
        int ret = getchar();

        /* restore the program, delete the breakpoint. XBter*/
        if(ptrace(PTRACE_POKETEXT, pid, h.symaddr, orig) < 0)
        {
            perror("PTRACE_POKETEXT");
            exit(-1);
        }

        h.pt_reg.rip = h.pt_reg.rip - 1;
        if(ptrace(PTRACE_SETREGS, pid, NULL, &h.pt_reg) < 0)
        {
            perror("PTRACE_SETREGS");
            exit(-1);
        }
        if(ptrace(PTRACE_SINGLESTEP, pid, NULL, NULL) < 0)
        {
            perror("PTRACE_SINGLESTEP");
            exit(-1);
        }
        wait(NULL);
        if(ptrace(PTRACE_POKETEXT, pid, h.symaddr, trap) < 0)
        {
            perror("PTRACE_POKETEXT");
            exit(-1);
        }
        goto trace;
    }
    if(WIFEXITED(status))
        printf("Completed tracing pid: %d\n", pid);
    exit(0);
}

Elf64_Addr lookup_symbol(handle_t *h, const char *symname)
{
    int i, j;
    char *strtab;      //.strtab section  XBter
    Elf64_Sym *symtab; //.symtab section  XBter
    for(i = 0; i <h->ehdr->e_shnum; i++){
        if(h->shdr[i].sh_type == SHT_SYMTAB){
            strtab = (char*)&h->mem[h->shdr[h->shdr[i].sh_link].sh_offset];
            symtab = (Elf64_Sym*)&h->mem[h->shdr[i].sh_offset];
            for(j = 0; j < h->shdr[i].sh_size/sizeof(Elf64_Sym); j++){
                if(strcmp(&strtab[symtab->st_name], symname) == 0)
                    return (symtab->st_value);
                symtab++;
            }
        }
    }
    return 0;
}

tracer.c解析

  1. 第1行:按第1行去编译即可:gcc tracer.c -o tracer。
  2. 第15行:父进程等待子进程状态的时候需要使用wait()函数,原文没有包含相应头文件。
  3. 第82行:这一行比较重要,是关于test文件类型的问题。我最开始的时候这一步是走不通的,最后在编译test的时候加入了-no-pie选项后通过。详情参阅https://blog.csdn.net/langqunxianfeng/article/details/87775869
  4. 第105行:这是第一次实际使用和初步理解fork()函数。实际运行的时候pid的值会打印两次,第一次为0,也就是父进程tracer,第二次为父进程pid+1,也就是fork()出来的子进程test,通过ps能够查看到两个不同的pid值。当pid等于0的时候调用了execve()接口,这个接口是实际启动test程序的方法,第一个参数h.exec此时指定的就是字符串test。
  5. 第114行:这行的wait(&status)可以替换为wait(NULL),作用只是简单的等待子进程的运行,用不到status,所以可以替换。
  6. 第116行:第一次调用ptrace()接口,PTRACE_PEEKTEXT指定是test段(代码段?),pid指定关于子进程test,h.symaddr指定是获取print_string()函数。返回值orig就是print_string的首地址。
  7. 第122行:把orig的后两位改成0xcc,这是设置断点的意思,在orig指向的print_string的开头的地方设置断点。0xcc就是设置断点的意思,就像0x80是系统调用一样,就是这么规定的,不要问为什么。
  8. 第123行:PTRACE_POKETEXT,poke,把改完的加上了断点的首地址传进去,就这样修改了test在内存中的镜像,执行到这里的时候就按改完的内容去执行。
  9. 第129行:PTRACE_CONT顺序执行的意思,没什么要说的。
  10. 第133行:这里的wait就是等子进程test的信号,父进程阻塞直到子进程产生信号。
  11. 第130行:检查子进程产生的信号是不是断点,也就是SIGTRAP。
  12. 第141行:PTRACE_GETREGS获取寄存器的值,都保存在h.pt_reg这个结构体中。
  13. 第157行:getchar的意思自行查找即可。其实也是很关键的一个步骤,因为按a键和按enter键的实际效果是不一样的,虽然说是hit any key to continue,但建议按enter,这样分析起来更顺畅。
  14. 第160行:通俗一点就是删除断点,gdb肯定也是这么实现的。
  15. 第166行:这里修改寄存器的值,所有工具肯定都用到PTRACE_SETREGS了。
  16. 第172行:PTRACE_SINGLESTEP单步执行,不甚理解。
  17. 第178行:设置断点。
  18. 第183行:返回trace:继续等待断点。
  19. 第190——第207行:lookup_symbol()接口是在elf文件中symname指定的内容的地址,根源还是分析elf文件的格式。很复杂,不常使用容易忘记,这里就不再深入研究它了,不是本文的重点,放在上一篇文章elfparse更合适。

test程序

下面的代码是test.c的内容,由于过于简单,书中并没有给出。

#include <stdio.h>

void print_string(char *);

int main(void){
    print_string("Hello 1");
    print_string("Hello 2");
    return 0;
}

void print_string(char *str){
    printf("%s\n",str);
}

按gcc test.c -o test -no-pie去编译,详情参阅https://blog.csdn.net/langqunxianfeng/article/details/87775869

执行结果

执行结果如下面代码所示。是不是很神奇?

X@ubuntu:~/Desktop/Xbter/learn/elfparse$ ./tracer ./testelf print_string

XBter

Beginning analysis of pid: 29234 at 40050a

Executable ./testelf (pid: 29234) has hit breakpoint 0x40050a

%rcx: 400530
%rdx: 7ffe9515c1e8
%rbx: 0
%rax: 4004e7
%rdi: 4005b4
%rsi: 7ffe9515c1d8
%r8: 7f085ffeed80
%r9: 7f085ffeed80
%r10: 0
%r11: 0
%r12: 400400
%r13: 7ffe9515c1d0
%r14: 0
%r15: 0
%rsp: 7ffe9515c0e8
Please hit any key to continue: 
Hello 1

Executable ./testelf (pid: 29234) has hit breakpoint 0x40050a

%rcx: 7f085fd12154
%rdx: 7f085ffef8c0
%rbx: 0
%rax: 8
%rdi: 4005bc
%rsi: f45260
%r8: 0
%r9: 0
%r10: f45010
%r11: 246
%r12: 400400
%r13: 7ffe9515c1d0
%r14: 0
%r15: 0
%rsp: 7ffe9515c0e8
Please hit any key to continue: 
Hello 2
Completed tracing pid: 29234
X@ubuntu:~/Desktop/Xbter/learn/elfparse$ 

 

就写道这里吧,欢迎提出意见。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 11
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值