1

I'm using updated Kali and compiling this for 64- bit

Working through Learning Linux Binary Analysis,'simple ptrace-based debugger' on page 57.

Source code and testfile to debug included.

Problem is accessing the return value of lookup_symbol from the shared object file 'test'.

Regarding the given program: The only thing I've changed is the error check that checks for ET_EXEC, I changed it to ET_DYN so that I can try to trace shared object files, which seem unavoidable when I import stdio.h for the print function. Also, changed the part that checks for ELF files, used use:

h.mem[0] != 0x7f || strcmp((char *)&h.mem1, "ELF" But I don't think the second part is necessary, and my computer spits out an error unless I use "ELF\002\001\001" in the strcmp.

The work of the parent program is done in the lookup_symbol function which searches for the symbol name in the string table and returns the symbol table st_value (the virtual address) of the desired symbol.

From TIS ELF Specification

In executable and shared object files, st_value holds a virtual address. To make these files' symbols more useful for the dynamic linker, the section offset (file interpretation) gives way to a virtual address (memory interpretation) for which the section number is irrelevant.

To search for print_string in 'test':

./tracer ./test print_string

The goal is to break at each print_string call and print the registers at that point, then any key can be pressed to continue execution.

When running the source code the lookup_symbol function returns 0x6b0 on my comp for the value of symtab->st_value and assigns it to h.symaddr.

0x6b0 is the virtual address (value of) of print_string in the symbol table, confirmed by checking readelf

58: 00000000000006b0 27 FUNC GLOBAL DEFAULT 14 print_string

The original from the book uses 0x6b0 directly in the following function:

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

This function fails with an error on my computer when it attempts to use h.symaddr=0x6b0:

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

The problem is not that it's a virtual address being returned, because as quoted above, both executables and shared object files contain virtual addresses in symbol_table->st_value. I'm not sure why but there is a difference in how this ptracing parent program treats shared objects and executables.

This is the test file:

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

This is the source file:

#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

The problem is that when I compile test.c with gcc, gcc creates a shared object file.

This is the case with even the simplest of programs: //simplereturn.c int main(void) { return 22; }

from readelf:

Type:                              DYN (Shared object file)

When I try to parse this with tracer, previously I had changed the error-checking section that ensured that the file that I'm parsing with ptrace is ET_EXEC (an executable file). I changed it so it checked if the traced file was ET_DYN (shared object file).

Doing this allowed the error-check to pass for the simple C programs that I wanted to trace, but had unexpected (at least for me) consequences. When lookup_symbol was returning it was in fact returning a virtual address (0x6b0), which agrees with the symbol table of the readelf, and the function address in objdump -D. Can even check it in gdb. However, interestingly, a break at print_string is placed at 0x6bc, while the start of the print_string function is at 0x6b0. Probably not irrelevant.

After a lot of research and struggle, the problem was an easy fix.

I created an executable instead of a shared object file. The requisite as far as I can tell is that it uses no external libraries or functions, or I guess probably that it's self-contained. Either way, gcc won't make them.

;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"

Compiled with nasm -g -f elf64 test.asm && ld -m elf_x86_64 -o testasm testasm.o

Now readelf -a testasm confirms that it's an executable

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

And I switched the error checking in the original back:

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

And now when run it it works $ ./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

checking gdb and readelf Lookup symbol returns value at 0x4000d8, as the symbol table from readelf -a testasm says it should:

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

So the differences between tracing an executable or shared object file in this case were what broke it.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.