
sgn v1.0.0-rs
Codificador binario polimórfico para payloads de seguridad ofensiva. Codifica shellcode con bucle de retroalimentación basado en LFSR, inyección de instrucciones basura y ofuscación multi-iteración para evadir la detección estática.
SGN es un codificador binario polimórfico con fines de seguridad ofensiva, como la generación de payloads binarios estáticamente indetectables. Utiliza un bucle de realimentación aditiva para codificar las instrucciones binarias proporcionadas, similar a LFSR. Este proyecto es la reimplementación de la Shikata ga nai original en golang con muchas mejoras.
[!WARNING]
El proyecto se ha portado recientemente a Rust. Este port mantiene el diseño y el comportamiento originales, pero sustituye el ensamblador de texto keystone por el ensambladoriced-x86escrito en Rust puro, por lo que no hay dependencias nativas de bibliotecas: se compila con un simplecargo build. Consulta la rama sgn-go para la versión heredada en Go.
¿Por qué?
Para la comunidad de seguridad ofensiva, la implementación original del codificador shikata ga nai está considerada como el mejor codificador de shellcode (hasta ahora). Pero con los años, los investigadores de seguridad han encontrado varias trampas a la hora de detectar estáticamente el stub del decodificador (trabajo relacionado artículo de FireEye). La principal motivación de este proyecto fue crear un codificador mejor que codifique el binario proporcionado hasta el punto de que sea idéntico a datos totalmente aleatorios y no sea posible detectar la presencia de un decodificador.
- Compatibilidad con 64 bits.
¡Por fin shellcodes x64 codificados correctamente! - Nuevo stub de decodificador más pequeño.
Clave LFSR reducida a 1 byte - Stub codificado con esquema pseudoaleatorio.
El stub del decodificador también se codifica con un esquema pseudoaleatorio - Sin condición de bucle visible.
¡El stub se decodifica a sí mismo SIN usar ninguna condición de bucle! - Ofuscación del stub del decodificador.
Generador de instrucciones basura aleatorias añadido con keystone - Opción de registro seguro.
Ninguno de los registros se sobrescribe (preámbulo opcional, puede reducir el polimorfismo)
Cómo funciona
Cada pasada de codificación:
- (opcional) añade un sufijo de restauración de registros (modo seguro);
- antepone instrucciones basura aleatorias que preservan el valor;
- cifra el payload con el cifrado ADFL (bucle de realimentación aditiva) y antepone un stub de decodificador sin bucles que lo revierte en tiempo de ejecución;
- a menos que se use
--plain, cifra el propio stub con un cifrado de esquema aleatorio por ejecución (XOR/ADD/SUB/ROL/ROR/NOTsobre DWORDs) y antepone un decodificador de esquema autoubicado, de modo que incluso el decodificador parece datos aleatorios; - opcionalmente repite
--encveces con semillas nuevas; - (opcional) antepone un prefijo de guardado de registros (modo seguro).
Instalación
cargo install sgn
También puedes obtener los binarios precompilados AQUÍ.
Uso
-h se explica por sí solo; usa -v si quieres ver lo que ocurre entre bastidores ( ͡° ͜ʖ ͡°)_/¯
__ _ __ __ _
___ / / (_) /_____ _/ /____ _ ___ ____ _ ___ ___ _(_)
(_-</ _ \/ / '_/ _ `/ __/ _ `/ / _ `/ _ `/ / _ \/ _ `/ /
/___/_//_/_/_/\_\\_,_/\__/\_,_/ \_, /\_,_/ /_//_/\_,_/_/
========[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
Ejemplo:
sgn -i shellcode.bin -o encoded.bin -a 64 --badchars '\x00\x0a\x0d'
Flujo de ejecución
La siguiente imagen es un diagrama básico del flujo de trabajo del codificador. Pero ten en cuenta que los tamaños, ubicaciones y órdenes cambiarán para las instrucciones basura, los decodificadores y los decodificadores de esquema en cada iteración.
El propio LFSR es bastante potente en términos de espacio de probabilidad. Para aún más polimorfismo, se añaden instrucciones basura al principio del payload bruto sin codificar. La siguiente imagen muestra la matriz compañera del polinomio característico del LFSR y, denotando la semilla como un vector columna, el estado del registro en configuración de Fibonacci tras k pasos.
Uso como librería
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());
Consulta examples/encode_binary.rs.
Pruebas
cargo test
La suite incluye pruebas unitarias de ida y vuelta del cifrado además de pruebas de ejecución reales: el shellcode x64 codificado se mapea como ejecutable y se ejecuta en el propio proceso, y el shellcode x86 se ejecuta mediante un harness auxiliar cc -m32 (se omite automáticamente si no hay cadena de herramientas de 32 bits). Ambas arquitecturas se prueban en los modos plain, schema, multicapa y safe-register, incluido un bucle de estrés aleatorizado.
Notas sobre el port
- La generación de instrucciones utiliza la API de ensamblador tipado de
iced-x86en lugar del texto de ensamblador de keystone; los stubs del decodificador se reconstruyeron en torno al direccionamiento relativo a RIP (x64) y a un esquema de dos pasadascall/pop(x86). - El cifrado de esquema se expresa directamente en la vista DWORD little-endian nativa de la CPU, lo que es equivalente pero más claro que la aritmética mixta big/little-endian del original.
- Se eliminó el código muerto del original (la tabla de conjuntos de instrucciones sin usar de ~3k líneas y el generador de "basura insegura" que nada invocaba).
- Correcciones menores:
random_byteahora abarca todo el rango0..=255, y el presupuesto de tamaño de basura (--max) se aplica de forma consistente como límite por bloque.