I'm trying to conduct a little experiment which basically consists of changing the EP of an ELF file and executing an exit(9) syscall, without returning to the OEP. As shown in the image everything seems fine enter image description here

However when I run the original program it segfaults rather than exiting via the exit(9), I can't figure out why. What's really happening here?

EDIT: Here's how I'm changing the entry-point:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <elf.h>
#include <stdint.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>

#define INVALID_IDX -32
#define PAGE_SIZE   4096

int check_elf(uint8_t *, char **);

// exit(9) 
unsigned char shellcode[] = "\x48\x31\xc0"

unsigned char nop_sled[] = "\x90\x90\x90\x90\x90"

int main(int argc, char **argv)
    int fd, i;
    uint8_t *mapped_file;
    struct stat st;
    char *interp;
    int magic = 1337;

    Elf64_Ehdr *ehdr = NULL;
    Elf64_Phdr *phdr = NULL;
    Elf64_Shdr *shdr = NULL;

    if (argc < 2)
        printf("Usage: %s <executable>\n", argv[0]);

    fd = open(argv[1], O_RDWR);

    if (fd < 0)

    if (fstat(fd, &st) < 0)

    /* map whole executable into memory */
    mapped_file = mmap(NULL, st.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);

    if (mapped_file < 0)

    check_elf(mapped_file, argv);

    ehdr = (Elf64_Ehdr *) mapped_file;
    phdr = (Elf64_Phdr *) &mapped_file[ehdr->e_phoff];
    shdr = (Elf64_Shdr *) &mapped_file[ehdr->e_shoff];

    if (ehdr->e_type != ET_EXEC)
        fprintf(stderr, "%s is not an ELF executable.\n", argv[1]);

    printf("Program entry point: %08x\n", ehdr->e_entry);

    char jmp_code[7];
    int text_found = 0;
    uint64_t parasite_addr;
    uint64_t text_end;
    size_t parasite_len = strlen(shellcode);
    int text_idx = INVALID_IDX;
    for (i = 0; i < ehdr->e_phnum; ++i)

        if (text_found)
            phdr[i].p_offset += PAGE_SIZE;

        if (phdr[i].p_type == PT_LOAD && phdr[i].p_flags == ( PF_R | PF_X))
            // set parasite  to the end of the text segment
            parasite_addr = phdr[i].p_vaddr + phdr[i].p_filesz;
            text_end = phdr[i].p_vaddr + phdr[i].p_filesz;

            printf("TEXT SEGMENT ends at 0x%x\n", text_end);
            text_idx = i;

            puts("Changing entry point...");
            ehdr->e_entry = (Elf64_Addr)parasite_addr;

            memmove(mapped_file + phdr[i].p_offset + phdr[i].p_filesz, 
                    shellcode, parasite_len);

            phdr[i].p_filesz += parasite_len;
            phdr[i].p_memsz += parasite_len;    



    //patch sections

    for (i = 0; i < ehdr->e_shnum; ++i)
        if (shdr->sh_offset >= parasite_addr)
            shdr->sh_offset += PAGE_SIZE;

            if (shdr->sh_size + shdr->sh_addr == parasite_addr)
                shdr->sh_size += parasite_len;

    ehdr->e_shoff += PAGE_SIZE;


int check_elf(uint8_t *mapped_file, char **argv)
    const uint8_t magic[] = {0x7F, 'E', 'L', 'F'};

    if (memcmp(magic, mapped_file, sizeof(magic)))
        fprintf(stderr, "%s is not an ELF file.\n", argv[1]);

    return 1;

As for the program whose entry-point i'm trying to change it's just a simple hello world.

  • 1
    Have you stepped through the program instructions using a debugger? – julian May 30 '19 at 7:10
  • @julian I can't, gdb won't disassemble the file, due to lack of the symbol table – Trey May 30 '19 at 7:15
  • 1
    The symbol table should not be required for disassembly, even by a tool as brittle as gdb. There are tools besides gdb that can be used for dynamic analysis. – julian May 30 '19 at 8:10
  • @julian like what? radare2 also fails to set a bp – Trey May 31 '19 at 21:24
  • 1
    Since the problem is at runtime, please share the binary so that we can analyze it ourselves and hopefully reproduce the behavior you are observing . Besides the large array of debuggers and similar tools available, there are also dynamic instrumentation frameworks such as pintool and pyrebox that can be leveraged. Pintool could be used to log memory accesses to pinpoint the instruction causing the segmentation fault, for example. – julian Jun 1 '19 at 10:34

Comment out or simply remove lines

ehdr->e_shoff += PAGE_SIZE;

if (text_found)
    phdr[i].p_offset += PAGE_SIZE;

and change shdr->sh_offset's to shdr[i].sh_offset's.

Explanation: You may run readelf -a hello for more details. It will output following problems:

readelf: Error: Reading 1856 bytes extends past end of file for section headers
readelf: Error: Section headers are not available!
readelf: Warning: note with invalid namesz and/or descsz found at offset 0xc
readelf: Warning:  type: 0x600ff0, namesize: 0x00000000, descsize: 0x00150003, alignment: 4

It means that your section headers address was increased by PAGE_SIZE too. Indeed, after modification it equals:

Start of section headers:          10544 (bytes into file)

And before:

Start of section headers:          6448 (bytes into file)

So you don't want to add PAGE_SIZE to ehdr->e_shoff, since otherwise the start of section headers now points to some random data.

Regarding phdr[i].p_offset's, I don't think you have to change them at all, since they are just segment offsets.

Note: GCC by default compiles to position independent executable, so it will have e_type equal 3 (ET_DYN instead of ET_EXEC), but will still be an ELF executable.

| improve this answer | |
  • Thank you @bart1e! That worked perfectly. About changing phdr[i].p_offset i though I had to, since I'm injecting code there – Trey Jun 15 '19 at 3:59

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.