Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Offset of global const variable in executable

Would like to have an executable save its state by modifying its own global constants. Just for the kicks of having a totally self-contained executable.

A few solutions/hacks that come to mind:

  1. Use libelf and have the program parse itself to find the offset.
  2. Add a specific marker and just search for it in the executable file. I guess this might even be somewhat cross-platform?
  3. Use object dumping utils to determine the address in the executable file. This probably needs to be always done as a post-process to project build..

It would be neat to have the linker provide this info.

Is it possible to have the linker provide the offset of a read-only section in the executable file?

Thanks

like image 387
Sussch Avatar asked Sep 23 '12 10:09

Sussch


2 Answers

What you want to do is tricky and unportable.

You could however study GNU emacs unexec function e.g. in src/unexelf.c (for Linux; other OSes have a similar file).

You could also play linker tricks, e.g. with your own ld script.

Beware that these tricks may be processor-, compiler-, kernel-, and libc- version specific.

Perhaps you want application checkpointing. In particular, BLCR might be useful to you.

like image 121
Basile Starynkevitch Avatar answered Sep 30 '22 15:09

Basile Starynkevitch


You are essentially talking about binary rewriting. One method to achieve this without fiddling with the compile process is to map a virtual address to a physical one and then patch it. Interestingly, this is something I covered in my master's thesis. The following images and text are pulled from that document:

http://localhostr.com/file/hyB1iFuiL0nV/Loading_Binary.jpg

Note that the concept behind my original project was to rewrite code in other binaries assuming that the compile process could not be modified. If your requirements and assumptions are different, this may well not be the easiest and best approach.

The most important idea here is that a section in the disk representation is preserved (not split) when it is mapped into memory. This means data that is at a certain offset into the section in the disk representation will be offset by the same amount after loaded into memory.

In libelf, similarly to libbfd, an executable contains a set of sections in which both code and data can reside. When the operating system loads the executable into memory, each section is based at some base address. We can reverse this to map a virtual memory address to a physical file offset. If a physical file offset can be found, the bytes can be patched as a regular file.

  • First, the section header of the executable is parsed with libelf. This allows us to obtain a set of sections and most importantly, for each section libelf can tell us three things:
    1. Section size The size of the section.
    2. Section base address The address that the section will be based at when the executable on disk is loaded into memory.
    3. Section disk offset The disk offset of the section.
  • By iterating through the section information extracted in the previous step, it is possible to find out what section an arbitrary virtual memory address is contained in. During patching, the memory address we are interested in is the address of the code at which a detour is to be written. The offset of the virtual memory address into the section can be calculated by (virtual_memory_address - section_base_address).
  • Therefore, the disk offset of the virtual memory address can be calculated by (section_disk_offset + (virtual_memory_address - section_base_address)).

This process allows an arbitrary virtual memory address to be mapped to its corresponding disk file offset. This offset can then be patched with regular C file IO functions such as fopen/fseek/fwrite/fclose.

This is my code for mapping a virtual address to a physical file offset using the above steps:

/*
 * Returns the corresponding 32 bit executable file offset of a virtual memory
 * address.
 */
uint32_t vaddr32_to_file_offset(char * filepath, uint32_t vaddr)
{
    int      fd     = open(filepath, O_RDONLY);
    Elf *    e      = elf_begin(fd, ELF_C_READ, NULL);
    uint32_t offset = 0;

    Elf_Scn * scn = NULL;
    while((scn = elf_nextscn(e, scn)) != NULL) {
        Elf32_Shdr * shdr = elf32_getshdr(scn);
        if(vaddr >= shdr->sh_addr &&
                (vaddr <= (shdr->sh_addr + shdr->sh_size))) {
            offset = shdr->sh_offset + (vaddr - shdr->sh_addr);
            break;
        }
    }

    elf_end(e);
    close(fd);
    return offset;
}

/*
 * Returns the corresponding 64 bit executable file offset of a virtual memory
 * address.
 */
uint64_t vaddr64_to_file_offset(char * filepath, uint64_t vaddr)
{
    int      fd     = open(filepath, O_RDONLY);
    Elf *    e      = elf_begin(fd, ELF_C_READ, NULL);
    uint64_t offset = 0;

    Elf_Scn * scn = NULL;
    while((scn = elf_nextscn(e, scn)) != NULL) {
        Elf64_Shdr * shdr = elf64_getshdr(scn);
        if(vaddr >= shdr->sh_addr &&
                (vaddr <= (shdr->sh_addr + shdr->sh_size))) {
            offset = shdr->sh_offset + (vaddr - shdr->sh_addr);
            break;
        }
    }

    elf_end(e);
    close(fd);
    return offset;
}

This is the code to patch an ELF executable given an offset:

/*
 * Sets the bytes at an arbitrary offset of a file to the contents of buffer.
 */
static bool patch_file(char * filepath, uint64_t offset, void * buffer,
        size_t size)
{
    FILE * pFile = fopen(filepath, "r+");

    if(pFile == NULL) {
        return FALSE;
    }

    fseek(pFile, offset, SEEK_SET);
    fwrite(buffer, 1, size, pFile);
    fclose(pFile);
    return TRUE;
}

More detailed information can be found in the report itself which is publicly available here.

like image 23
Mike Kwan Avatar answered Sep 30 '22 14:09

Mike Kwan