Create a binary program that can duplicate itself with new data included in the new binary?

This is a little bit of a peculiar use-case, but I'm curious how I would go about doing this.

I want to create a binary program "lua-bundle" that when executed, can create a copy of itself with user-supplied data included in the new binary. I am using mlua to run Lua code with Rust. Instead of running the Lua scripts directly, I actually want the user to be able to "bundle" their Lua code into a new static binary that they can then just run directly. I don't want to require them to use Cargo toolchain (include_str!("...")) etc.

Example usage would be:

$ lua-bundle --include script.lua --output new-bin
$ ./new-bin # Runs the Lua code

One idea I had was to declare a static "__PLACEHOLDER__" somewhere in the source lua-bundle binary and then just patch the output binary with the user's included Lua script. But it's really super weird and I don't even know if that would work. I was hoping there was some kind of mechanism with like .data sections or a crate I overlooked.

The include_bytes macro might do what you want, but I'm not sure you could update the binary without a full Rust compiler available.

The object crate can read and write object files and executables, but I don’t know if it can copy an executable and still have it work afterwards.

If I wanted to get started with this, I would look into putting #[link_section=".luadata"] on my Lua variable, and see whether I could use objcopy --update-section to change that and still have a working program. If you can get that working then you can see if there is some Rust code that can do it, even if it’s just a wrapper around libbfd.

the binrary file format by its nature is platform specific, so you must do it differently on different platforms. for windows, I have used the pelite crate before, and it's not that diffcult, to locate and load data from a custom section, you simply create a PeView for your ".exe" file though PeView::module and locate the section you want:

// you need unsafe for most of these operations
// use winapi or windows-sys
let hinst= GetModuleHandleA(ptr::null());
let exe = PeView::module(hinst);
let section = exe
        .section_headers()
        .by_name(".my_special_data_section")
        .unwrap();
let data = exe.get_section_bytes(&section).unwrap();
// you can use the data however you like

use special attributes to place the data into custom sections:

#[used]
#[link_section = ".my_special_data_section"]
static DATA: [u8: 65536] = [0; 65536];

however, if you want to support arbitrary sized data, you must use other more involved libraries that can edit pe file headers (instead of a simple parser), which unfortunately I don't have experience of.

there's this crate called goblin which looks very promising, but I didn't have the need to use such complicated libraries yet, (thankfully).

just want to add, since you mentioned Lua, I suppose you can use the same trick used by srlua, it's really simple but works well in practice.

simply concatenate the data to the executable file, and then put a footer at the end, which contains information needed for the loader, notably: a magic number, the binary size, the data size, the data type (Lua source code or compiled byte code). the loader just need to open argv[0], (in rust, we have std::env::current_executable()), seek to the end of file, parse and verify the footer, and then load the data.

2 Likes

When faced with a similar problem I just appended the extra data to the end of the binary file then appended a "header" that describes the additional data. I know that works with PE files (Windows). I suspect it will also work with ELF (Linux).

You won't be able to directly modify the running program using that method. But, a clone and two renames gets past that problem.

Thanks, I think this is what I will end up doing. It's a little bit brittle just because anti-virus is pretty much always going to flag this binary, and even just running strip on it will break it. But the more "correct" options are probably going to be to involved for me too do.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.