Search code examples
gccbinutils

Is there a way to create a a stripped binary with correct offsets?


I'm attempting to convert an assembly file to C++ for use as a small and easy to insert "trampoline" loader for another library. It is injected into another program at runtime, then loads a library, runs a function inside of it, and frees it. This is simply to avoid needing multiple lengthy calls to WriteProccessMemory, and to allow certain runtime checks if needed.

Originally, I wrote the code in assembly as it gave me a high degree of control over the structure of the file. I ended up with a ~128 byte file structured as followed:

<Relocation Header> // Table of function pointers filled in by the loading code
<Code>
<Static Data>

The size/structure of the header is known at compile-time, also allowing the entry point to be calculated, so there is very little code needed to load this.

The problem is that sharing the structure of the header between my assembler (NASM) and compiler (GCC) is... difficult, hence the rewrite.

I've come up with this series of commands to compile/link the C++ code:

g++ -c -O3 -fpic Loader.cpp

g++ -O3 -shared -nostdlib Loader.o

Running objcopy -O binary -j .text a.exe then gives a binary file only about 95 bytes in size (I manually inserted some padding in the assembly version to make it clear when debugging where "sections" are).

Only one problem (at least for this question), the variable offsets haven't been relocated (obviously). Viewing the binary, I can see lines like mov rcx, QWORD PTR [rip+0x4fc9]. Clearly, this will not be valid in a 95 byte file. Is there a way (preferably using GCC or a program in Binutils) that I can get a stripped binary with correct offsets? The solution doesn't have to be a post-process like objcopy, it can happen during any part of the build proccess.

I'd really like to avoid any unneeded information in the file, it wouldn't necessarily be detrimental, but this is meant to be super lightweight. The file does not need to be directly runnable (the entry-point does not have to be 0).

Also to be clear, I'm not asking for a simple addition/subtraction to all pointers, GCC's generated addresses are spread across memory, they should be up against the code.


Solution

  • Although incomplete and needing some changes, I think I've come up with a functioning solution for now.

    I compile as before, but link with a slightly different command: g++ -T lnkscrpt.txt -O3 -nostdlib Loader.o (-shared just makes the linker complain about missing a DllMain).

    lnkscrpt.txt is an ld linker script (https://ftp.gnu.org/old-gnu/Manuals/ld-2.9.1/html_node/ld_5.html#SEC5) as follows:

    SECTIONS
    {
        . = 0x00;
        .bss : { *(.bss) }
        .text : { *(.text) }
        .data : { *(.rdata) *(.data) }
        /DISCARD/ : {*(*)}
    }
    

    This preserves the order I want and discards any other default sections.

    Finally I run objcopy -O binary -j .* --set-section-flags .bss=alloc,load,contents a.exe to copy over the remaining sections to a flat binary. The --set-section-flags option simply insures that the binary contains space allocated for the .bss section.

    This results in a 128 byte binary, laid out in the exact same way as my custom assembly version, using correct offsets, and not containing any unneeded data.