Inject RO data into existing executables

3 min read Original article ↗

Description

This document describes inner workings of sui, an injection tool for embedding arbitrary data into precompiled executables (ELF, PE and Mach-O).

It is cross-platform and supports cross-“patching” for all formats. Under the hood sui uses format-specific features described in Supported formats.

It produces valid executables that can be code-signed and distributed.

Emebedded data can be accessed by the executable at runtime using libsui library. Using sui

It is available as a Rust crate and a command-line tool.

cargo add libsui

Run-time

Find and extract the embedded data from the executable:

use libsui::find_section;

if let Some(data) = find_section("mydata") {
// found
}

Name of the section is the same as the one used during injection. It refers to the section name in Mach-O, resource name in PE and tag in ELF.

Injecting

Build and sign a new Mach-O executable:

use sui::Macho;

Macho::from(input)? // load an existing executable
.write_section("mydata", data)? // write a new section with auxiliary data
.build_and_sign(output)?; // build and sign the executable

Build a new Portable Executable:

use sui::PortableExecutable;

PortableExecutable::from(input)? // load an existing executable
.write_resource("mydata", data)? // inject a new resource (RCDATA) with auxiliary data
.build(output)?; // build and sign the executable

Build a new ELF executable:

use sui::Elf;

Elf::from(input)? // load an existing executable
.append("mydata", data)? // tag and append auxiliary data
.build(output)?; // build

Build a new PE with icon:

use sui::PortableExecutable;

PortableExecutable::from(input)? // load an existing executable
.write_resource("mydata", data)? // inject a new resource (RCDATA) with auxiliary data
.write_icon("icon.ico")? // inject an icon resource (RT_ICON)
.build(output)?; // build

Ad-hoc codesign a modified arm64 Mach-O executable:

use sui::apple_codesign::MachoSigner;

MachoSigner::new(input)? // load an existing executable
.sign(output)?; // sign the executable with ad-hoc signature

Refer to the API documentation for more up-to-date documentation.

Supported formats

ELF, PE and Mach-O are supported.

Mach-O

Resource is added as section in a new segment, load commands are updated and offsets are adjusted. __LINKEDIT is kept at the end of the file.

It is similar to linker’s -sectcreate,__FOO,__foo,hello.txt option.

Note that Macho::build will invalidate existing code signature. on Apple sillicon, kernel refuses to run executables with bad signatures.

Use Macho::build_and_sign to re-sign the binary with ad-hoc signature. See apple_codesign.rs for details. This is similar to codesign -s - ./out command.

Macho::from(exe)?
.write_section("__sect", data)?
.build_and_sign(&mut out)?;
$ codesign -d -vvv ./out

Executable=/Users/divy/gh/sui/out
Identifier=a.out
Format=Mach-O thin (arm64)
CodeDirectory v=20400 size=10238 flags=0x20002(adhoc,linker-signed) hashes=317+0 location=embedded
Hash type=sha256 size=32
CandidateCDHash sha256=6b1abb20f2291dd9b0dbcd0659a918cb2d0e6b18
CandidateCDHashFull sha256=6b1abb20f2291dd9b0dbcd0659a918cb2d0e6b1876153efa17f90dc8b3a8f177
Hash choices=sha256
CMSDigest=6b1abb20f2291dd9b0dbcd0659a918cb2d0e6b1876153efa17f90dc8b3a8f177
CMSDigestType=2
CDHash=6b1abb20f2291dd9b0dbcd0659a918cb2d0e6b18
Signature=adhoc
Info.plist=not bound
TeamIdentifier=not set
Sealed Resources=none
Internal requirements=none

ELF

Data is simply appended to the end of the file with a tag and magic descriptor. Extracted from current_exe() at run-time.

This is subject to change and may use ELF linker notes (PT_NOTE) in the future.

PE

Resource is added into a new PE resource directory as RT_RCDATA type and extracted using FindResource and LoadResource at run-time.

Comparison with postject

Sui supports ad-hoc codesigning for Mach-O executables and Icon resources for PE executables which postject does not.

Sui is much faster and lightweight compared to postject which is built on top of LIEF, a heavy C++ dependency.

$ time sui ./node test.txt ./out
0m0.151s

$ time postject ./node name test.txt
0m8.993s

Conclusion

sui is open-source on Github: https://github.com/denoland/sui and is the tech behind deno compile, converts JavaScript and TypeScript code into a single executable.