Artificial truth

The more you see, the less you believe.

[archives] [latest] | [homepage] | [atom/rss]

Screwing elf header for fun and profit
Thu 10 January 2013 — download

Since I recently reversed Thellurik, and I stumbled upon the Striking Back GDB and IDA debuggers through malformed ELF executables article from ioactive, I wanted to play around with ELF header, and the capacity of GNU tools to handle malformed ones.

ELF header

This is what an ELF header looks like:

typedef struct{
unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */
Elf32_Half e_type; /* Object file type */
Elf32_Half e_machine; /* Architecture */
Elf32_Word e_version; /* Object file version */
Elf32_Addr e_entry; /* Entry point virtual address */
Elf32_Off e_phoff; /* Program header table file offset */
Elf32_Off e_shoff; /* Section header table file offset */
Elf32_Word e_flags; /* Processor-specific flags */
Elf32_Half e_ehsize; /* ELF header size in bytes */
Elf32_Half e_phentsize; /* Program header table entry size */
Elf32_Half e_phnum; /* Program header table entry count */
Elf32_Half e_shentsize; /* Section header table entry size */
Elf32_Half e_shnum; /* Section header table entry count */
Elf32_Half e_shstrndx; /* Section header string table index */
} Elf32_Ehdr;

I wrote a (quick and dirty) fuzzer to see what GDB could handle. Only three fields could be modified in order to be still able to run the binary outside GDB, while crashing it when runned into it:

  • e_shoff
  • e_shnum
  • e_shstrndx

Screwer

This lead to the creation of a simple binary screwer:

#include <stdio.h>
#include <sys mman.h>
#include <unistd.h>
#include <stdlib.h>
#include <elf.h>
#include <sys stat.h>
#include <sys types.h>
#include <sys procfs.h>
#include <fcntl.h>

int main(int argc, char** argv){
    printf(".: Elf corrupt :.n");

    if(argc < 2){
        printf("Usage: %s file", argv[0]);
        return 1;
    }
    else{

        int f;
        static Elf32_Ehdr* header;

        if((f = open(argv[1], O_RDWR)) < 0){
            perror("open");
            return 1;
        }

        if((header = (Elf32_Ehdr *) mmap(NULL, sizeof(header), PROT_READ |
                        PROT_WRITE, MAP_SHARED, f, 0)) == MAP_FAILED){
            perror("mmap");
            close(f);
            return 1;
        }
        printf("[*] Current e_shnum: %dn", header->e_shnum);

        header->e_shoff = 0xffff;
        header->e_shnum = 0xffff;
        header->e_shstrndx = 0xffff;

        printf("[*] Patched e_shnum: %dn", header->e_shnum);

        if(msync(NULL, 0, MS_SYNC) == -1){
            perror("msync");
            close(f);
            return 1;
        }

        close(f);
        munmap(header, 0);
    }
    return 0;
}

Results

$ gdb testfile
GNU gdb (Ubuntu/Linaro 7.4-2012.04-0ubuntu2) 7.4-2012.04
Copyright (C) 2012 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later
<http: gnu.org licenses gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show
copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
For bug reporting instructions, please see:
<http: bugs.launchpad.net gdb-linaro></http:>...
"~testfile": not in executable format: File truncated

But the file can still be launched outside of a debugger :)

Conclusions

This is a simple way to protect your program from GDB, ltrace, strace, objdump, elfsh, hte and even IDA ("internal error 511" and crash), ..., but of course, this can trivially be fixed, so don't rely on this !

edit: radare2 works on it!

edit2: Arg, someone wrote a better proof of concept than mine for IDA. I didn't think about using random values at all, since I didn't focus on steal.