ptrace TRACE_PEEKER:当被跟踪文件是共享对象文件时访问虚拟地址内容的输入/输出错误

逆向工程 小精灵
2021-06-16 23:21:03

我正在使用更新的 Kali 并将其编译为 64 位

学习 Linux 二进制分析,第 57 页上的“基于 ptrace 的简单调试器”。

包含要调试的源代码和测试文件。

问题是从共享对象文件“test”访问 lookup_symbol 的返回值。

关于给定的程序:我唯一更改的是检查 ET_EXEC 的错误检查,我将其更改为 ET_DYN,以便我可以尝试跟踪共享对象文件,这在我为打印函数导入 stdio.h 时似乎不可避免. 另外,更改了检查 ELF 文件的部分,使用了:

h.mem[0] != 0x7f || strcmp((char *)&h.mem 1 , "ELF" 但我不认为第二部分是必要的,除非我在 strcmp 中使用 "ELF\002\001\001",否则我的电脑会报错。

父程序的工作在lookup_symbol 函数中完成,该函数在字符串表中搜索符号名称并返回所需符号的符号表st_value(虚拟地址)。

来自TIS ELF 规范

在可执行文件和共享目标文件中,st_value 保存一个虚拟地址。为了使这些文件的符号对动态链接器更有用,段偏移量(文件解释)让位于与段号无关的虚拟地址(内存解释)。

在 'test' 中搜索print_string

./tracer ./test print_string

目标是在每次 print_string 调用时中断并在该点打印寄存器,然后可以按任意键继续执行。

运行源代码时,lookup_symbol 函数在我的 comp 上返回 0x6b0,作为 symtab->st_value 的值并将其分配给 h.symaddr。

0x6b0是符号表中print_string的虚拟地址(value of),通过检查readelf确认

58: 00000000000006b0 27 FUNC 全局默认值 14 print_string

书中的原文直接在以下函数中使用了 0x6b0:

如果 ((orig = ptrace(PTRACE_PEEKTEXT, pid, h.symaddr, NULL)) < 0)

当它尝试使用 h.symaddr=0x6b0 时,此函数在我的计算机上失败并出现错误:

Beginning analysis of pid: 2462 at 6b0
PTRACE_PEEKER: Input/output error
hello 1
hello 2

问题不在于返回的是虚拟地址,因为如上所述,可执行文件和共享目标文件都在符号表-> st_value 中包含虚拟地址。我不知道为什么,但是这个 ptracing 父程序如何处理共享对象和可执行文件是有区别的。

这是测试文件:

#include <stdio.h>

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

int main(int argc, char ** argv)
{
    print_string("hello 1");
    print_string("hello 2");
    return 0;
}

这是源文件:

#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>




typedef struct handle
{
    Elf64_Ehdr *ehdr;
    Elf64_Phdr *phdr;
    Elf64_Shdr *shdr;
    uint8_t *mem;
    char *symname;
    Elf64_Addr symaddr;
    struct user_regs_struct pt_reg;
    char *exec;
} 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]; 

    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)
    {
        printf("%s is not an ELF file\n", h.exec);
        exit(-1);
    }

    if (h.ehdr->e_type != ET_DYN)
    {
        printf("%s is not an ELF dynamic\n", h.exec);
        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);
        exit(0);
    }

    wait(&status);
    printf("Beginning analysis of pid: %d at %lx\n", pid, h.symaddr);


    if ((orig = ptrace(PTRACE_PEEKTEXT, pid, h.symaddr, NULL)) < 0)
    {
        perror("PTRACE_PEEKER");
        exit(-1);
    }
    trap = (orig & ~0xff | 0xcc);


    printf("Made it here");


    if (ptrace(PTRACE_POKETEXT, pid, h.symaddr, trap) < 0)
    {
        perror("PTRACE_POKETEXT");
        exit(-1);
    }
trace:

    if (ptrace(PTRACE_CONT, pid, NULL, NULL) < 0)
    {
        perror("PTRACE_CONT");
        exit(-1);
    }
    wait(&status);


    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", 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\n%%");
    printf("\nHit any key to continue: ");
    getchar();
    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;
        Elf64_Sym *symtab;
        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)
                        //printf("symtab->st_value is 0x%lx\n", symtab->st_value);  
                        return (symtab->st_value);
                    symtab++;
                }
            }
        }
        return 0;
    }
1个回答

问题是当我用 gcc 编译 test.c 时,gcc 创建了一个共享对象文件。

即使是最简单的程序也是如此: //simplereturn.c int main(void) { return 22; }

来自阅读:

Type:                              DYN (Shared object file)

当我尝试使用跟踪器解析它时,之前我更改了错误检查部分,以确保我使用 ptrace 解析的文件是 ET_EXEC(一个可执行文件)。我更改了它,以便它检查跟踪文件是否为 ET_DYN(共享对象文件)。

这样做允许错误检查通过我想要跟踪的简单 C 程序,但产生了意想不到的(至少对我而言)结果。当lookup_symbol 返回时,它实际上返回了一个虚拟地址(0x6b0),它与readelf 的符号表和objdump -D 中的函数地址一致。甚至可以在 gdb 中检查它。然而,有趣的是,print_string 处的中断位于 0x6bc,而 print_string 函数的开始位于 0x6b0。可能不是无关紧要的。

经过大量研究和斗争,问题很容易解决。

我创建了一个可执行文件而不是共享对象文件。据我所知,必要条件是它不使用外部库或函数,或者我想它可能是自包含的。无论哪种方式,gcc 都不会制作它们。

;test.asm
section .text
    global _start

_start:
    mov rsi, hello1
    call print_string
    mov rsi, hello2
    call print_string

    xor rdi,rdi
    mov rax, 60
    syscall

print_string:
    mov rax, 1
    mov rdi, 1
    mov rdx, 6
    syscall
    ret

section .data
    hello1 db "Hello1"
    hello2 db "Hello2"

用 nasm -g -f elf64 test.asm && ld -m elf_x86_64 -o testasm testasm.o 编译

现在readelf -a testasm确认它是一个可执行文件

ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
 Type:                              EXEC (Executable file) //EXEC NOW
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x4000b0
  Start of program headers:          64 (bytes into file)
  Start of section headers:          616 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         2
  Size of section headers:           64 (bytes)
  Number of section headers:         6
  Section header string table index: 5

我在原来的后面切换了错误检查:

#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>


typedef struct handle
{
    Elf64_Ehdr *ehdr;
    Elf64_Phdr *phdr;
    Elf64_Shdr *shdr;
    uint8_t *mem;
    char *symname;
    Elf64_Addr symaddr;
    struct user_regs_struct pt_reg;
    char *exec;
} 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];

    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) || strcmp((char *)&h.mem[1], "ELF\002\001\001"))
    {
        printf("%s is not an ELF file\n", h.exec);
        exit(-1);
    }

    if (h.ehdr->e_type != ET_EXEC) 
    {
        printf("%s is not an ELF executable\n", h.exec);
        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);
        exit(0);
    }




    wait(&status);
    printf("Beginning analysis of pid: %d at %lx\n", pid, h.symaddr);

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

    if (ptrace(PTRACE_POKETEXT, pid, h.symaddr, trap) < 0)
    {
        perror("PTRACE_POKETEXT");
        exit(-1);
    }
trace:
    if (ptrace(PTRACE_CONT, pid, NULL, NULL) < 0)
    {
        perror("PTRACE_CONT");
        exit(-1);
    }
    wait(&status);

    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", 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\n%%");
    printf("\nHit any key to continue: ");
    getchar();
    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;
        Elf64_Sym *symtab;
        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 ./testasm print_string

Beginning analysis of pid: 5378 at 4000d8

Executable ./testasm (pid: 5378) has hit breakpoint 0x4000d8
%rcx: 564ed7b07050
%rdx: 7fb9525a9760
%rbx: 7fffffc2
%rax: 1
%rdi: 3e
%rsi: 1958ac0
%r8: 7ffcd2b98848
%r9: 7ffcd2b98828
%r10: 3d2bea1a8
%%r11: 564ed7b07010
%r12: 0
%r13: 7fb9527d2728
%%r14: 57f00000001
%r15: 801
%rsp: 203bec
%
Hit any key to continue: 
Hello1
Executable ./testasm (pid: 5378) has hit breakpoint 0x4000d8
%rcx: 564ed7b07050
%rdx: 7fb9525a9760
%rbx: 7fffffc2
%rax: 1
%rdi: 3e
%rsi: 1958ac0
%r8: 7ffcd2b98848
%r9: 7ffcd2b98828
%r10: 3d2bea1a8
%%r11: 564ed7b07010
%r12: 0
%r13: 7fb9527d2728
%%r14: 57f00000001
%r15: 801
%rsp: 203bec
%
Hit any key to continue: 
Hello2Completed tracing pid: 5378

检查 gdb 和 readelf 查找符号返回 0x4000d8 处的值,因为readelf -a testasm的符号表说它应该:

Symbol table '.symtab' contains 11 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 00000000004000b0     0 SECTION LOCAL  DEFAULT    1 
     2: 00000000006000ec     0 SECTION LOCAL  DEFAULT    2 
     3: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS test.asm
     4: 00000000004000d8     0 NOTYPE  LOCAL  DEFAULT    1 print_string
     5: 00000000006000ec     0 NOTYPE  LOCAL  DEFAULT    2 hello1
     6: 00000000006000f2     0 NOTYPE  LOCAL  DEFAULT    2 hello2
     7: 00000000004000b0     0 NOTYPE  GLOBAL DEFAULT    1 _start
     8: 00000000006000f8     0 NOTYPE  GLOBAL DEFAULT    2 __bss_start
     9: 00000000006000f8     0 NOTYPE  GLOBAL DEFAULT    2 _edata
    10: 00000000006000f8     0 NOTYPE  GLOBAL DEFAULT    2 _end

因此,在这种情况下跟踪可执行文件或共享对象文件之间的差异是破坏它的原因。