
Polymorphic binary encoder for offensive security payloads. Encodes shellcode with LFSR-based feedback loop, garbage instruction injection, and multi-iteration obfuscation to evade static detection.
SGN is a polymorphic binary encoder for offensive security purposes such as generating statically undetecable binary payloads. It uses a additive feedback loop to encode given binary instructions similar to LFSR. This project is the reimplementation of the original Shikata ga nai in golang with many improvements.
[!WARNING]
The project recently ported to Rust. This port keeps the original design and behaviour but replaces the keystone text assembler with the pure-Rusticed-x86assembler, so there are no native library dependencies — it builds with a plaincargo build. Check out the sgn-go branch for legacy Go version.
For offensive security community, the original implementation of shikata ga nai encoder is considered to be the best shellcode encoder(until now). But over the years security researchers found several pitfalls for statically detecing the decoder stub(related work FireEye article). The main motive for this project was to create a better encoder that encodes the given binary to the point it is identical with totally random data and not possible to detect the presence of a decoder.
Finally properly encoded x64 shellcodes !LFSR key reduced to 1 byteDecoder stub is also encoded with a psudo random schemaStub decodes itself WITHOUT using any loop conditions !!Random garbage instruction generator added with keystoneNon of the registers are clobbered (optional preable, may reduce polimorphism)Each encoding pass:
--plain, encrypts the stub itself with a random per-run schema
cipher (XOR/ADD/SUB/ROL/ROR/NOT over DWORDs) and prepends a self-locating
schema decoder, so even the decoder looks like random data;--enc times with fresh seeds;cargo install sgn
You can also get the pre-compiled binaries HERE.
Usage
-h is pretty self explanatory use -v if you want to see what's going on behind the scenes ( ͡° ͜ʖ ͡°)_/¯
__ _ __ __ _
___ / / (_) /_____ _/ /____ _ ___ ____ _ ___ ___ _(_)
(_-</ _ \/ / '_/ _ `/ __/ _ `/ / _ `/ _ `/ / _ \/ _ `/ /
/___/_//_/_/_/\_\\_,_/\__/\_,_/ \_, /\_,_/ /_//_/\_,_/_/
========[Author:-Ege-Balcı-]====/___/=======v2.0.2=========
┻━┻ ︵ヽ(`Д´)ノ︵ ┻━┻ (ノ ゜Д゜)ノ ︵ 仕方がない
sgn [OPTIONS]
Options:
-i, --input <INPUT> Input binary path
-o, --out <OUT> Encoded output binary name (default: <input>.sgn)
-a, --arch <ARCH> Binary architecture (32/64) [default: 64]
-c, --enc <ENC> Number of times to encode the binary [default: 1]
-M, --max <MAX> Maximum bytes per garbage block [default: 50]
--plain Do not encode the decoder stub
--ascii Generate a fully ASCII-printable payload (slow)
-S, --safe Preserve all register values (no clobber)
--badchars <BADCHARS> Avoid these bytes, hex format (e.g. \x00\x0a)
-v, --verbose Verbose mode
-h, --help Print help
-V, --version Print version
Example:
sgn -i shellcode.bin -o encoded.bin -a 64 --badchars '\x00\x0a\x0d'
The following image is a basic workflow diagram for the encoder. But keep in mind that the sizes, locations and orders will change for garbage instructions, decoders and schema decoders on each iteration.
LFSR itself is pretty powerful in terms of probability space. For even more polimorphism garbage instructions are appended at the begining of the unencoded raw payload. Below image shows the the companion matrix of the characteristic polynomial of the LFSR and denoting the seed as a column vector, the state of the register in Fibonacci configuration after k steps.
use sgn::Encoder;
let shellcode = std::fs::read("payload.bin")?;
let mut encoder = sgn::Encoder::new(64)?;
let encoded = encoder.encode(&shellcode)?;
println!("encoded {} bytes", encoded.len());
See examples/encode_binary.rs.
cargo test
The suite includes cipher round-trip unit tests plus real execution tests:
encoded x64 shellcode is mapped executable and run in-process, and x86 shellcode
is executed via a cc -m32 helper harness (skipped automatically if no 32-bit
toolchain is present). Both architectures are exercised across the plain,
schema, multi-layer and safe-register modes, including a randomized stress loop.
iced-x86's typed assembler API rather than
keystone assembly text; the decoder stubs were rebuilt around RIP-relative
addressing (x64) and a call/pop two-pass scheme (x86).random_byte now spans the full 0..=255 range, and the
garbage-size budget (--max) is applied consistently as a per-block cap.