A Binary Ninja plugin that recovers obfuscated strings by emulating code with Binary Ninja's LLIL emulator and scanning what it writes to memory.
Static strings only sees plaintext. Malware routinely hides its key strings
(C2 domains, API names, config) as stack-built or runtime-decoded data. BOSS
drives the emulator to reconstruct them.
Inspired by Mandiant's flare-floss, with string extraction adapted from it. BOSS is an independent Binary Ninja implementation and is not affiliated with or endorsed by the FLOSS project.
| Technique | How it works |
|---|---|
| Stack strings | Emulate each function on its own (skipping calls) and scan the bytes it writes to a scratch stack. Catches strings assembled one byte at a time. |
| Decoded strings | Identify candidate decoder routines (loop + byte arithmetic + memory writes + callers), emulate each caller up to the real call site so the true arguments are in place, then step_over the call to run the whole decoder and scan the bytes it wrote. Catches XOR/RC4/etc. output. |
Both ASCII and UTF-16LE strings are recovered (extraction adapted from flare-floss).
Right-click / command palette → BOSS:
- Find all obfuscated strings — scan the whole binary (background task, cancelable).
- Decode strings in this function — treat the current function as a decoder, emulate its call sites.
- Extract stack strings from this function — emulate just this function.
Results are printed to the log and shown as a markdown report (grouped by technique, with clickable addresses).
- A Binary Ninja build that ships the
emulatorcore plugin, withcorePlugins.emulatorenabled in Settings. (The plugin degrades gracefully and logs an error if the emulator API is not importable.) - Binary Ninja 5.0+.
Copy or symlink this folder into your Binary Ninja user plugins directory:
ln -s "$PWD/boss" ~/.binaryninja/plugins/boss- Emulator memory starts empty, so the plugin maps the binary's readable
segments plus a scratch stack before running (
driver.py). - A memory-write hook records every byte written; contiguous runs are then scanned for strings — this is the emulator analogue of flare-floss's memory-diffing.
step_overruns an entire callee (the decoder) through the write hook with the caller's real arguments, which is what makes decoded-string recovery work.- All emulation is bounded by
set_max_instructions, so decoder loops can't hang. - The scratch stack is placed at an address whose bytes are non-printable and that fits the binary's pointer size (32- vs 64-bit). This matters: a 32-bit stack pointer can't hold a 48-bit address, and a printable stack base makes stored stack pointers deposit printable bytes next to buffers, corrupting strings and creating short false positives.
Like flare-floss's -n/--minimum-length, the minimum string length is
configurable (Settings → BOSS):
boss.minStringLength(default 4) — minimum length for decoded strings.boss.minStackStringLength(default 5) — minimum length for stack strings.
Stack strings are noisier than decoded strings: a coincidentally-printable 4-byte
value on the stack passes at length 4. Raising boss.minStackStringLength suppresses
these while keeping real strings (API names, paths) intact.
Other tunables live at the top of analysis.py (instruction caps, max call sites
per decoder, candidate limit).
- Calls made by a caller before the decoder (e.g.
strlento compute a length) are skipped, so decoders whose arguments come from helper calls may decode a wrong length. Constant/lea-style argument setup works. - Call sites reachable only under specific branch conditions may not be reached from a zero-argument caller emulation; those contexts are skipped.
- Emulation stops at the first unsupported LLIL instruction, so a string built or
copied via an unsupported op — e.g.
rep movs/rep stos, which Binary Ninja lifts to an intrinsic — is missed, along with anything the function does after it. Stack strings assembled with ordinarymovstores (the common case) are unaffected. - This is a starting point, not a full flare-floss port — tight strings and language-specific string extraction are not implemented.
Apache-2.0. String extraction is adapted from flare-floss (Apache-2.0).
