
sgn v1.0.0-rs
Encodeur binaire polymorphe pour payloads de sécurité offensive. Encode du shellcode avec une boucle de rétroaction basée sur LFSR, une injection d'instructions parasites et une obfuscation multi-itérations pour échapper à la détection statique.
SGN est un encodeur binaire polymorphe destiné à des fins offensives en sécurité, telles que la génération de charges utiles binaires statiquement indétectables. Il utilise une boucle de rétroaction additive pour encoder les instructions binaires fournies, de manière similaire au LFSR. Ce projet est la réimplémentation de l'original Shikata ga nai en golang, avec de nombreuses améliorations.
[!WARNING]
Le projet a récemment été porté vers Rust. Ce port conserve la conception et le comportement d'origine, mais remplace l'assembleur textuel keystone par l'assembleur purement Rusticed-x86, donc il n'y a aucune dépendance native à des bibliothèques — il se compile avec un simplecargo build. Consultez la branche sgn-go pour l'ancienne version Go.
Pourquoi ?
Pour la communauté de la sécurité offensive, l'implémentation originale de l'encodeur shikata ga nai est considérée comme le meilleur encodeur de shellcode (jusqu'à présent). Mais au fil des années, les chercheurs en sécurité ont découvert plusieurs failles permettant de détecter statiquement le stub de décodage (travail connexe article FireEye). La principale motivation de ce projet était de créer un meilleur encodeur, qui encode le binaire fourni au point de le rendre identique à des données totalement aléatoires et de rendre impossible la détection de la présence d'un décodeur.
- Support 64 bits.
Enfin des shellcodes x64 correctement encodés ! - Nouveau stub de décodeur plus petit.
Clé LFSR réduite à 1 octet - Stub encodé avec un schéma pseudo-aléatoire.
Le stub du décodeur est également encodé avec un schéma pseudo-aléatoire - Aucune condition de boucle visible
Le stub se décode lui-même SANS utiliser de condition de boucle !! - Obfuscation du stub de décodeur.
Générateur aléatoire d'instructions factices ajouté avec keystone - Option de registres sûrs.
Aucun des registres n'est écrasé (préambule optionnel, peut réduire le polymorphisme)
Comment ça fonctionne
Chaque passe d'encodage :
- (optionnel) ajoute un suffixe de restauration des registres (mode sûr) ;
- préfixe des instructions factices aléatoires qui préservent les valeurs ;
- chiffre la charge utile avec le chiffrement ADFL (boucle de rétroaction additive) et préfixe un stub de décodeur sans boucle qui l'inverse à l'exécution ;
- sauf avec
--plain, chiffre le stub lui-même avec un chiffrement de schéma aléatoire par exécution (XOR/ADD/SUB/ROL/ROR/NOTsur des DWORD) et préfixe un décodeur de schéma auto-localisant, de sorte que même le décodeur ressemble à des données aléatoires ; - répète éventuellement
--encfois avec de nouvelles graines ; - (optionnel) préfixe une sauvegarde des registres (mode sûr).
Installation
cargo install sgn
Vous pouvez également obtenir les binaires pré-compilés ICI.
Utilisation
-h est assez explicite ; utilisez -v si vous voulez voir ce qui se passe en coulisses ( ͡° ͜ʖ ͡°)_/¯
__ _ __ __ _
___ / / (_) /_____ _/ /____ _ ___ ____ _ ___ ___ _(_)
(_-</ _ \/ / '_/ _ `/ __/ _ `/ / _ `/ _ `/ / _ \/ _ `/ /
/___/_//_/_/_/\_\\_,_/\__/\_,_/ \_, /\_,_/ /_//_/\_,_/_/
========[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
Exemple :
sgn -i shellcode.bin -o encoded.bin -a 64 --badchars '\x00\x0a\x0d'
Flux d'exécution
L'image suivante est un schéma de flux de travail de base pour l'encodeur. Mais gardez à l'esprit que les tailles, les emplacements et les ordres changeront pour les instructions factices, les décodeurs et les décodeurs de schéma à chaque itération.
Le LFSR lui-même est assez puissant en termes d'espace de probabilité. Pour encore plus de polymorphisme, des instructions factices sont ajoutées au début de la charge utile brute non encodée. L'image ci-dessous montre la matrice compagnon du polynôme caractéristique du LFSR et, en désignant la graine comme un vecteur colonne, l'état du registre en configuration de Fibonacci après k étapes.
Utilisation en tant que bibliothèque
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());
Voir examples/encode_binary.rs.
Tests
cargo test
La suite comprend des tests unitaires de chiffrement aller-retour ainsi que des tests d'exécution réels : les shellcodes x64 encodés sont mappés en mémoire exécutable et exécutés dans le processus, et les shellcodes x86 sont exécutés via un harnais d'assistance cc -m32 (ignoré automatiquement si aucune chaîne d'outils 32 bits n'est présente). Les deux architectures sont testées dans les modes plain, schema, multi-couche et safe-register, y compris une boucle de stress aléatoire.
Notes sur le portage
- La génération d'instructions utilise l'API d'assembleur typé de
iced-x86plutôt que le texte d'assemblage de keystone ; les stubs de décodeur ont été reconstruits autour de l'adressage relatif à RIP (x64) et d'un schéma en deux passescall/pop(x86). - Le chiffrement de schéma est exprimé directement dans la vue DWORD native little-endian du processeur, ce qui est équivalent mais plus clair que l'arithmétique mixte big/little-endian de l'original.
- Le code mort de l'original (la table d'instructions de ~3 000 lignes inutilisée et le générateur de « déchets non sécurisés » qui n'était appelé par rien) a été supprimé.
- Corrections mineures :
random_bytecouvre désormais toute la plage0..=255, et le budget de taille des déchets (--max) est appliqué de manière cohérente comme une limite par bloc.