Skip to content

Latest commit

 

History

26 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Boo

Crates.io Docs.rs License


Boo is a workspace of two crates for hardening a compiled Rust binary against static and runtime analysis:

  • boo-rs: compile-time literal encryption, opaque-predicate branching, and runtime value mangling.
  • boo-nodebug: cross-platform debugger-presence detection.

boo-rs

Boo encrypts literal data in the final binary, at compile time, preventing static analysis tools from reading values.
At runtime, decrypted data is exposed in memory only for the shortest possible time.

It supports many literal types beyond strings, including numbers, characters, byte strings, C-strings, arrays, tuples, nested structures...
Use the boo!() macro to effortlessly encrypt your data.

Usage

Add the dependency:

[dependencies]
boo-rs = "0.1"

Set an optional encryption key (or fallback to a 64-byte randomly generated one):

export BOO_KEY="secret-key"

Example:

extern crate alloc;
#[macro_use]
extern crate boo_rs;

boo_init!();

#[allow(unused_variables)]
fn main() {
    let n = boo!(3);
    let text = boo!("hello");
    let bytes = boo!(b"\x01\x02\x03");
    let pair = boo!(("host", 443));
    let nested = boo!([[1, 2], [3, 4]]);
}

boo_init!() must be called once before using the boo!() macro. After that, the macro can be used anywhere to encrypt almost all Rust literal values.

Boo supports:

  • booleans
  • bytes
  • integers and floats
  • characters
  • strings
  • byte strings (b"...")
  • C-strings (c"...")
  • tuples (containing any mix of supported types)
  • arrays and nested arrays (containing any of supported types)

For a full reference, see the showcase file.

boo_branch!()

Branches on a boolean condition through an opaque-predicate dispatch instead of a plain if/else: the taken branch is decided by an XOR-masked comparison salted per call site, not a direct test of the condition. else if chains aren't supported.

# extern crate alloc;
# #[macro_use] extern crate boo_rs;
boo_init!();

fn main() {
    let licensed = true;
    assert_eq!(boo_branch!(licensed { "full" } else { "trial" }), "full");
}

An optimizing compiler may fold the mask back to an equivalent plain branch in a release build, and this offers no resistance to a live debugger stepping through the code, same limits as the literal encryption above.

boo_mangle_init!()

Publishes Mangled, mangling a fixed-size value against this process's cookie and a fresh per-value nonce for storage: Mangled::new(value) to store, mangled.reveal_with(|plain| ..) to read it back, plaintext never outliving that closure. Mirrors glibc's PTR_MANGLE and Windows' EncodePointer. The cookie is fresh every process start: a value a live memory-editing tool saves in one run decodes to garbage after a restart.

# extern crate alloc;
# #[macro_use] extern crate boo_rs;
boo_init!();
boo_mangle_init!();

fn main() {
    let original = [1u8, 2, 3, 4];
    let mangled = Mangled::new(original);
    mangled.reveal_with(|plain| assert_eq!(*plain, original));
}

boo_mangle_init!() must be called once, at the crate root, before using Mangled.

Performance

Decryption happens on the stack. The cost is O(n), where n is the length of the data in bytes.

  • All decrypted types except String and CStr are stored on the stack without performance overhead.
  • &str and &CStr decryption are stored into their heap-allocated variants.
  • Special case: binary strings are decrypted into owned [u8] arrays.

Roadmap

  • Stack allocated str using a wrapper struct around a fixed u8 array.
  • Stack allocated Cstr using a wrapper struct around a fixed u8 array.
  • Numeric suffix support (ex. 1u8, 1u16, 0f32) using syn::Lit::suffix().
  • Wide string support using the custom syntax: w"Wide null terminated".

boo-nodebug

Cross-platform debugger-presence detection primitives. Every function returns a best-effort signal, never a verdict: fold the result into a poison or perturbation input, never branch accept/reject on it directly, a self-check is patchable by the same skill that defeats any other branch in a shipped binary.

[dependencies]
boo-nodebug = { git = "https://github.com/agmbk/boo" }
Module Platform Detects
ld_preload Linux LD_PRELOAD set in this process's own environment, one bypass of a TracerPid-style check.
parent_process Linux, Windows The parent process's executable name matching a caller-supplied denylist (gdb, windbg, ...).
trap_flag Windows, x86_64 The EFLAGS trap-flag single-step not firing as expected, indicating a debugger's own single-step machinery or an earlier exception handler.
use boo_nodebug::parent_process;

let suspicious = parent_process::matches_denylist(&["gdb", "windbg", "x64dbg"]);

Each module documents the specific bypass it does not cover, see the module-level docs in boo-nodebug/src.

License

MIT - See LICENSE for details.

Credits

  • LITCRYPT, for being the first crate proposing compile-time string encryption

About

Encrypt Rust literal types at compile time

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Contributors

Languages