17 August 2024
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.
It is available as a Rust crate and a command-line tool.
cargo add libsui
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.
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.
ELF, PE and Mach-O are supported.
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
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.
Resource is added into a new PE resource directory as
RT_RCDATA
type and extracted using
FindResource
and LoadResource
at run-time.
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
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.