Leçon 8.1 · Encodage, crypto & signatures· 45 min
Reconnaître XOR, Base64 et les encodages personnalisés
Repérez XOR, clés roulantes et Base64 personnalisé dans les données et le code, retrouvez les clés par longueur de clé et texte clair connu, puis décodez.
Objectifs
- Classer un blob inconnu en hexadécimal, Base64, alphabet personnalisé, XOR ou transformation plus forte d’après son jeu de caractères, sa longueur et son entropie
- Reconnaître les boucles de décodage XOR, à clé roulante, ADD/SUB/ROL et par table de correspondance dans du désassemblage optimisé
- Retrouver une clé XOR multi-octets par des tests de longueur de clé et un texte clair connu
- Décoder un Base64 à alphabet personnalisé et casser par force brute une clé roulante en Python et dans CyberChef
- Éviter les pièges classiques : XOR qui préserve les nuls, multiples de la longueur de clé et encodage superposé à une compression
Un encodage modifie l’apparence des données sans exiger de secret difficile
à obtenir. Les malwares (logiciels malveillants) utilisent des encodages partout
où un défenseur pourrait sinon lire du texte en clair : dans le bloc de
configuration qui contient les hôtes C2 et les identifiants de campagne, dans
les chaînes que le code décode juste avant de les utiliser, dans les fichiers
qu’il prépare sur le disque et dans les octets qu’il envoie sur le réseau. Rien
de tout cela n’est une protection solide. Le but est de tromper strings, les
signatures naïves et l’analyste pressé.
Chaînes et chaînes obfusquées a montré comment remarquer qu’un texte est caché, comment FLOSS retrouve les chaînes décodées par émulation et comment casser par force brute une clé XOR d’un octet. Cette leçon va un cran plus loin : identifier quel encodage vous avez sous les yeux, à partir des données et du code, et retrouver ses paramètres quand une force brute sur 255 clés ne suffit plus. La leçon suivante, Identifier les algorithmes cryptographiques, prend le relais lorsque la transformation est un véritable chiffrement.
Pourquoi l’analyste s’y intéresse
Le décodage est rarement une fin en soi. C’est l’étape qui débloque les résultats dont vos destinataires ont besoin :
| Où se trouve l’encodage | Ce que le décodage vous apporte | Qui l’utilise |
|---|---|---|
| Configuration embarquée | Hôtes C2, ports, identifiants de campagne et de bot, intervalles de sommeil, noms de mutex | SOC (IOC), threat intel (regroupement) |
| Chaînes individuelles | Noms d’API, chemins de registre, commandes, noms de fichiers | Vous (évaluation des capacités), ingénieurs détection |
| Trafic réseau | Format du beacon, identifiants de commande, champs exfiltrés | Détection réseau (voir Écrire des signatures réseau, plus loin dans ce module) |
| Fichiers préparés | Ce qui a été collecté avant l’exfiltration | Équipes de réponse à incident |
Il y a aussi un angle détection. Une fois que vous connaissez le schéma et la clé d’une famille, la forme encodée d’une chaîne connue fait une excellente ancre YARA, et la boucle de décodage elle-même constitue souvent une signature plus stable que n’importe quel IOC (indicateur de compromission).
Reconnaître les encodages dans les données
Commencez par le blob lui-même. Quatre mesures tranchent la plupart des cas : le jeu de caractères utilisé, la longueur, le remplissage (padding) et l’entropie.
Jeu de caractères et longueur
| Encodage | Alphabet | Règle de longueur | Autres indices |
|---|---|---|---|
| Hex (Base16) | 0-9a-f ou 0-9A-F | Paire | Ne mélange jamais les casses dans un même blob |
| Base32 | A-Z2-7 | Multiple de 8 avec padding | Padding = jusqu’à six caractères |
| Base64 (RFC 4648) | A-Za-z0-9+/ | Multiple de 4 avec padding | Zéro, un ou deux =, uniquement à la fin |
| Base64url | A-Za-z0-9-_ | Souvent sans padding | Courant dans les URL et les jetons |
| Base64 personnalisé | 64 caractères imprimables quelconques | Comme Base64 | Le décodage standard « réussit » mais donne du charabia |
| XOR, ADD, ROL, substitution par table | Toute valeur d’octet | Identique au texte clair | La structure du texte clair subsiste |
Base64 transforme chaque groupe de 3 octets d’entrée en 4 caractères de sortie,
d’où une longueur multiple de 4 avec padding. Les malwares suppriment souvent
le padding = pour moins ressembler à du Base64. Avant de conclure qu’une
chaîne n’est pas du Base64, ajoutez du padding jusqu’à obtenir une longueur
multiple de 4 et réessayez.
Un alphabet personnalisé est le cas le plus intéressant. Comme l’auteur se
contente de permuter la table de 64 caractères, la sortie utilise toujours des
lettres, des chiffres et deux symboles, et un validateur Base64 strict
l’acceptera sans broncher. Ce qui trahit l’alphabet, c’est que les octets
décodés ne sont pas ceux attendus : du binaire d’apparence aléatoire là où
devrait se trouver une configuration. Dans le binaire, cherchez la table
elle-même : une chaîne imprimable de 64 caractères sans répétition, souvent à
côté d’un = isolé ou de la constante 0x3D dans l’encodeur.
Entropie et fréquence des octets
L’entropie se mesure de la même façon que dans Détecter les packers et l’entropie, mais ici c’est le plafond qui vous en apprend le plus :
| Données | Entropie typique (bits par octet) | Pourquoi |
|---|---|---|
| Texte anglais ou de configuration | 4 à 5 | Peu de symboles, fréquences inégales |
| Texte hexadécimal | au plus 4,0 | Seuls 16 symboles sont possibles |
| Texte Base64 | au plus 6,0 | Seuls 64 symboles (plus =) sont possibles |
| XOR, ADD ou substitution par table sur un octet appliqué à du texte | Identique au texte clair | L’histogramme des octets est seulement réétiqueté |
| XOR à clé répétée appliqué à du texte | Un peu plus élevée que le texte clair | Chaque octet de clé réétiquette une colonne différente |
| Données compressées ou chiffrées | 7,5 et plus | Redondance supprimée |
Deux conséquences méritent d’être retenues. D’abord, une substitution simple ne modifie jamais la forme de l’histogramme des octets. Si une valeur d’octet domine dans un blob de 4 Ko, il s’agit probablement d’une suite encodée de zéros ou d’espaces, et cet octet est probablement la clé. Ensuite, un blob Base64 dont les octets décodés ont une entropie proche de 8 n’est pas « des données encodées en Base64 ». C’est du Base64 enveloppant quelque chose de compressé ou de chiffré, et il vous reste du travail.
Les motifs qui trahissent la clé
XOR possède une propriété que l’auteur ne peut pas désactiver : 0x00 XOR k = k.
Partout où le texte clair contient des zéros, la clé apparaît en clair.
- XOR sur un octet : des suites d’un même octet répété là où se trouvaient des zéros ou du remplissage.
- XOR à clé répétée : la clé elle-même, répétée, sur ces mêmes zones. Un fichier PE encodé montre la clé encore et encore dans son en-tête et dans le remplissage de ses sections, car les fichiers PE regorgent d’octets nuls.
- Texte encodé avec une clé répétée : des caractères identiques du texte clair situés dans la même colonne de clé produisent des octets chiffrés identiques, si bien que les sous-chaînes répétées réapparaissent à des distances multiples de la longueur de la clé.
Ce dernier point est à la base de la détection de la longueur de clé. Faites un XOR entre deux blocs chiffrés encodés avec les mêmes octets de clé : la clé s’annule et il reste le XOR de deux blocs de texte clair. Pour du texte, cette différence comporte peu de bits à 1. La longueur de clé candidate présentant la plus faible distance de Hamming normalisée entre blocs consécutifs est donc généralement la bonne, ou un multiple de celle-ci.
Reconnaître les encodages dans le code
Dans le désassembleur, les décodeurs ont une forme commune : une courte boucle
qui lit un octet, le transforme avec une à trois instructions arithmétiques et
le réécrit. Reconnaître les idiomes du compilateur
vous a appris à lire les boucles. Voici à quoi ressemble généralement la
transformation qu’elles contiennent, compilée avec GCC 15 en -O2 (nop
d’alignement retirés).
XOR à clé répétée. L’indice est réduit modulo la longueur de la clé avec
and quand cette longueur est une puissance de deux ; les autres longueurs
produisent un div ou une multiplication par un nombre magique :
xor4:
20: mov r9,rax
23: and r9d,0x3 ; i & 3 -> 4-byte key
27: movzx r9d,BYTE PTR [r8+r9*1] ; key[i & 3]
2c: xor BYTE PTR [rcx+rax*1],r9b
30: add rax,0x1
34: cmp rdx,rax
37: jne 20 <xor4+0x20>Clé roulante. La clé est mise à jour à chaque itération. Ici, le décodeur soustrait une graine qui augmente de 3 à chaque octet :
roll_sub:
50: sub BYTE PTR [rcx],r8b ; b[i] -= seed
53: add rcx,0x1
57: add r8d,0x3 ; seed += 3
5b: cmp rcx,rdx
5e: jne 50 <roll_sub+0x10>Combinaisons ADD/SUB et rotation. ADD et SUB s’annulent mutuellement, tout
comme rol et ror ; le décodeur contient donc l’inverse de ce qu’a fait
l’encodeur, dans l’ordre inverse :
rol_add:
80: movzx eax,BYTE PTR [rcx]
83: add rcx,0x1
87: sub eax,0x21
8a: rol al,0x3
8d: mov BYTE PTR [rcx-0x1],alSubstitution par table de correspondance. Chaque octet sert d’indice dans
une table de 256 octets. Les décodeurs Base64 personnalisés utilisent le même
motif movzx avec une table inverse de 64 ou 256 entrées, plus des décalages
de 2, 4 et 6 et des masques 0x3f :
subst:
c0: movzx eax,BYTE PTR [rcx]
c3: add rcx,0x1
c7: movzx eax,BYTE PTR [r8+rax*1] ; table[b[i]]
cc: mov BYTE PTR [rcx-0x1],alQuelques règles vous évitent de poursuivre les mauvaises boucles :
xor eax,eaxet les autres XOR d’un registre avec lui-même ne font que le remettre à zéro. Cherchez un XOR avec deux opérandes différents à l’intérieur d’une boucle. La règle capa « encode data using XOR » applique exactement ce filtre.- Un schéma chaîné (ou « loopback ») utilise l’octet chiffré ou clair précédent comme clé suivante. Repérez la valeur écrite lors d’une itération et relue comme clé à l’itération suivante.
- Une clé immédiate de 32 bits comme
xor DWORD PTR [rax],0x37a21f4best stockée en little-endian ; les octets de la clé sont donc4b 1f a2 37. Voir endianness. - Les décodeurs sont généralement appelés depuis de nombreux endroits avec un
pointeur et une longueur. Une fonction avec des dizaines d’appelants, chacun
passant une adresse différente de
.data, correspond à l’organisation classique du chiffrement de chaînes par XOR, et les sites d’appel vous indiquent où se trouve chaque chaîne encodée.
Retrouver la clé
Quand vous disposez du décodeur, y lire la clé est la méthode la plus fiable. Quand vous n’avez que les données, ou que vous voulez confirmer votre lecture, trois techniques couvrent la plupart des schémas simples.
Texte clair connu. Vous savez presque toujours quelque chose du texte
clair : un exécutable commence par MZ et contient This program cannot be run in DOS mode, une URL contient http, une configuration peut contenir = ou ; à
intervalles réguliers, et une famille déjà rencontrée utilise des noms de champs
connus. Pour XOR, ciphertext XOR plaintext = key stream. Si le flux obtenu se
répète avec une période n, vous avez la clé et sa longueur en une seule étape.
Détection de la longueur de clé. Sans fragment connu (crib) à une position connue, attribuez un score à chaque longueur candidate. Le test de distance de Hamming ci-dessus est le test classique. Sur des blobs courts, un second test est plus robuste : découpez les données en n colonnes et vérifiez si chaque colonne peut être décodée en caractères plausibles par un seul octet de clé. Seule la bonne longueur (et ses multiples) passe pour toutes les colonnes.
Force brute. Une clé d’un octet compte 255 candidats, et une clé roulante avec une graine et un pas seulement 65 536. Évaluez chaque candidat selon la proportion de caractères imprimables ou avec un fragment connu. En Python, cela prend quelques millisecondes.
Astuce : Quand un test de longueur de clé retient à la fois 4 et 8, la réponse est 4. Tout multiple de la vraie longueur de clé aligne aussi chaque colonne sur un seul octet de clé. Prenez toujours la plus petite longueur qui fonctionne, puis confirmez en décodant.
Recettes CyberChef
Pour un décodage ponctuel, CyberChef est plus rapide que du code, et il fournit un bon contrôle croisé pour vos scripts :
| Objectif | Recette |
|---|---|
| XOR à clé répétée | From Hex, puis XOR avec la clé en Hex (par exemple 4b1fa237) |
| Trouver une clé XOR courte | XOR Brute Force avec un fragment connu comme http ou server= |
| Base64 personnalisé | From Base64 en remplaçant le champ alphabet par les 64 caractères issus du binaire |
| Traduire d’abord l’alphabet | Substitute (personnalisé vers standard), puis un From Base64 ordinaire |
| Base64 autour d’une compression | From Base64, puis Zlib Inflate, Raw Inflate ou Gunzip |
| « Qu’est-ce que c’est ? » | Magic en mode intensif, puis vérifiez son hypothèse à la main |
Les clés roulantes avec un pas, et tout ce qui conserve un état d’un octet à l’autre, sont malcommodes à exprimer dans CyberChef. Écrivez plutôt dix lignes de Python. C’est exactement ce que fait le lab, et la leçon Scripter le déchiffrement des chaînes, plus loin dans ce module, transforme cette approche en décodeur réutilisable.
Pièges
XOR qui préserve les nuls. Certains encodeurs ignorent les octets valant
0x00 ou égaux à la clé : les zéros restent des zéros et la clé ne fuit jamais.
Le décodeur comporte les deux mêmes comparaisons devant le xor. Si vous
décodez ces données avec un XOR simple, chaque octet qui aurait dû valoir zéro
ou la clé ressort faux, et votre recherche par fragment connu peut échouer sur
les chaînes courtes. Reproduisez exactement la condition d’exclusion.
Encodage par-dessus une compression. Les auteurs compressent souvent
d’abord et encodent ensuite, car les données compressées sont plus petites et
l’encodage masque les en-têtes du compresseur. Après le décodage, examinez les
premiers octets avant de supposer un chiffrement : 78 9C ou 78 DA indique
zlib, 1F 8B gzip, PK ZIP. Un appel à RtlDecompressBuffer dans les imports
signale LZNT1 ou Xpress. Une entropie élevée après un décodage correct est
normale pour des données compressées.
Superposition de couches en général. Les vraies configurations empilent deux ou trois étapes : Base64 d’un XOR d’un zlib, ou hex d’un RC4. Retirez une couche, mesurez de nouveau le jeu de caractères et l’entropie, et recommencez. L’ordre dans le code est l’inverse de l’ordre dans lequel vous les appliquez.
Hex et Base64 trompeurs. Une chaîne Base64 peut, par hasard, ne contenir que des caractères hexadécimaux, et les GUID, hashs et empreintes de certificats sont de l’hexadécimal légitime. C’est le contexte (qui lit la valeur, et ce que le code en fait) qui tranche, pas le jeu de caractères seul.
Lab : trois encodages, trois récupérations
Vous allez générer trois copies encodées inoffensives d’une fausse configuration et récupérer chacune d’elles. Travaillez dans un répertoire temporaire avec un environnement virtuel :
python3 -m venv venv && . venv/bin/activate-
Générez les blobs. Enregistrez sous
make_blobs.pyet exécutez-le :python # make_blobs.py - produce three harmless encoded copies of a demo config import base64 CONFIG = b"server=update.example.com;port=8080;id=lab" # 1. Repeating 4-byte XOR key XOR_KEY = bytes([0x4B, 0x1F, 0xA2, 0x37]) xor_blob = bytes(b ^ XOR_KEY[i % len(XOR_KEY)] for i, b in enumerate(CONFIG)) # 2. Base64 with a custom (shuffled) alphabet STD = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" CUSTOM = b"ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210+/" b64_blob = base64.b64encode(CONFIG).translate(bytes.maketrans(STD, CUSTOM)) # 3. Rolling ADD: each byte gets (seed + 3*i) added, modulo 256 SEED = 0x5C roll_blob = bytes((b + SEED + 3 * i) & 0xFF for i, b in enumerate(CONFIG)) open("xor.bin", "wb").write(xor_blob) open("b64.txt", "wb").write(b64_blob) open("roll.bin", "wb").write(roll_blob) print("xor :", xor_blob.hex()) print("b64 :", b64_blob.decode()) print("roll:", roll_blob.hex())text xor : 387ad0412e6d9f423b7bc3432e31c74f2a72d25b2e31c1582624d258396b9f0f7b27920c227b9f5b2a7d b64 : x7EbwnEbKCEdATU9AH4ovTUgxTcoOnMeyGgdy6Q9KGtdLWZ2zDJ0yTUr roll: cfc4d4dbcdddabe6e4dbdbf1e5b1eb01edfc0201fdc9011011e21a1c2227f3f1ecf7f200312f0b3d3539À partir de maintenant, faites comme si vous n’aviez pas écrit ce script. Vous n’avez que les trois fichiers et ce que l’« échantillon » vous apprendrait.
-
Retrouvez la clé XOR. Enregistrez sous
xor_recover.py. Le script évalue les longueurs de clé de 1 à 8 avec les deux tests, puis applique le fragment connuserver=:python # xor_recover.py - key length + known-plaintext recovery for repeating-key XOR import string, sys from itertools import combinations data = open(sys.argv[1], "rb").read() CRIB = sys.argv[2].encode() if len(sys.argv) > 2 else b"server=" ALLOWED = set((string.ascii_letters + string.digits + "=;.:/_-&?").encode()) def hamming(a: bytes, b: bytes) -> int: return sum(bin(x ^ y).count("1") for x, y in zip(a, b)) # 1. Key length, method A: normalised Hamming distance between blocks print("keylen hamming/bit columns-with-a-clean-key") for k in range(1, 9): blocks = [data[i:i + k] for i in range(0, len(data) - k + 1, k)][:4] pairs = list(combinations(blocks, 2)) ham = sum(hamming(a, b) for a, b in pairs) / len(pairs) / k # method B: can every column be decoded to "config-like" bytes by one key byte? clean = 0 for c in range(k): col = data[c::k] if any(all((x ^ kb) in ALLOWED for x in col) for kb in range(256)): clean += 1 print(f"{k:>6} {ham:>11.2f} {clean}/{k}") # 2. Known plaintext: ciphertext XOR crib = keystream stream = bytes(c ^ p for c, p in zip(data, CRIB)) print("\nkeystream under crib", CRIB, "=", stream.hex(" ")) for k in range(1, len(stream)): if all(stream[i] == stream[i % k] for i in range(len(stream))): key = stream[:k] break print("smallest period:", len(key), "key =", key.hex()) plain = bytes(b ^ key[i % len(key)] for i, b in enumerate(data)) print("decoded:", plain.decode(errors="replace"))bash python xor_recover.py xor.bintext keylen hamming/bit columns-with-a-clean-key 1 3.83 0/1 2 4.17 0/2 3 4.33 0/3 4 2.71 4/4 5 4.10 0/5 6 4.17 2/6 7 4.38 0/7 8 2.65 8/8 keystream under crib b'server=' = 4b 1f a2 37 4b 1f a2 smallest period: 4 key = 4b1fa237 decoded: server=update.example.com;port=8080;id=labLes deux tests désignent 4 et son multiple 8. La longueur 8 obtient même un score de Hamming légèrement plus faible, et c’est pourquoi on retient la plus petite longueur qui fonctionne. Le fragment connu tranche : le flux de clé se répète avec une période de 4.
-
Reconnaissez et décodez le Base64 personnalisé. Enregistrez sous
b64_recover.py:python # b64_recover.py - spot a Base64-shaped blob, then decode it with a custom alphabet import base64, string, sys blob = open(sys.argv[1], "rb").read().strip() STD = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" # 1. Does it *look* like Base64? chars = set(blob.decode()) print("length", len(blob), "| length % 4 =", len(blob) % 4, "| distinct chars", len(chars), "| outside std alphabet:", sorted(chars - set(STD.decode() + "=")) or "none") # 2. Standard decoding "works" but produces garbage: a hint the alphabet is custom std = base64.b64decode(blob + b"=" * (-len(blob) % 4)) printable = sum(chr(b) in string.printable for b in std) / len(std) print(f"standard decode: {printable:.0%} printable -> {std[:24]!r}") # 3. Partial alphabet from a crib: each 3 plaintext bytes fix 4 index->char pairs crib = b"server=update" # expected start of the config std_enc = base64.b64encode(crib[: len(crib) // 3 * 3]) mapping = {chr(s): chr(c) for s, c in zip(std_enc, blob)} print("crib gives", len(mapping), "of 64 alphabet slots:", " ".join(f"{k}->{v}" for k, v in sorted(mapping.items()))) # 4. Full alphabet recovered from the binary (the 64-char string near the decoder) CUSTOM = b"ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210+/" assert len(set(CUSTOM)) == 64 plain = base64.b64decode(blob.translate(bytes.maketrans(CUSTOM, STD)) + b"=" * (-len(blob) % 4)) print("decoded:", plain.decode())text length 56 | length % 4 = 0 | distinct chars 36 | outside std alphabet: none standard decode: 60% printable -> b'\xc7\xb1\x1b\xc2q\x1b(!\x1d\x015=\x00~(\xbd5 \xc57(:s\x1e' crib gives 13 of 64 alphabet slots: 0->9 2->7 F->U G->T P->K V->E X->C Z->A c->x d->w m->n w->d y->b decoded: server=update.example.com;port=8080;id=labLe blob passe tous les tests de forme Base64, et pourtant le décodage standard produit du binaire inexploitable. Le fragment connu ne retrouve que 13 des 64 positions, mais elles montrent déjà le motif (
Z->A,X->C,V->E) : chaque plage de caractères est inversée. Sur un vrai échantillon, vous le confirmeriez en trouvant la table de 64 caractères dans le binaire. Collez ensuite cette table dans le champ alphabet de l’opération From Base64 de CyberChef et vérifiez que vous obtenez la même sortie. -
Cassez la clé roulante par force brute. Vous avez vu un décodeur de la forme
roll_sub(une graine qui augmente d’une constante), mais pas ses constantes. Enregistrez sousroll_brute.py:python # roll_brute.py - brute-force a rolling ADD key: out[i] = in[i] + seed + step*i import string, sys data = open(sys.argv[1], "rb").read() OK = set((string.ascii_letters + string.digits + "=;.:/_-").encode()) def decode(seed: int, step: int) -> bytes: return bytes((b - seed - step * i) & 0xFF for i, b in enumerate(data)) hits = [] for seed in range(256): for step in range(256): out = decode(seed, step) score = sum(c in OK for c in out) / len(out) if score == 1.0: hits.append((seed, step, out)) print(f"tried 65536 (seed, step) pairs, {len(hits)} fully printable") for seed, step, out in hits[:5]: print(f"seed=0x{seed:02x} step={step:<3} {out.decode()}") print("with crib 'server=':", [(hex(s), st) for s, st, o in hits if o.startswith(b"server=")])text tried 65536 (seed, step) pairs, 1 fully printable seed=0x5c step=3 server=update.example.com;port=8080;id=lab with crib 'server=': [('0x5c', 3)]La recherche complète prend environ 0,3 seconde. Un seul des 65 536 candidats décode chaque octet en un caractère plausible de configuration.
-
Vérifiez dans CyberChef. Collez l’hexadécimal du blob XOR, appliquez From Hex puis XOR avec la clé
4b1fa237(Hex). Collez ensuite le blob Base64, appliquez From Base64 et remplacez l’alphabet par la table personnalisée. Les deux doivent restituer la configuration.
Questions : Pourquoi le test de Hamming a-t-il donné un score légèrement meilleur à la longueur 8 qu’à la longueur 4, et quelle règle vous en protège ? Le blob Base64 ne contient que 36 caractères distincts. L’appelleriez-vous encore Base64 s’il ne faisait que 12 caractères, et que vérifieriez-vous d’autre ? Comment la récupération XOR changerait-elle si la configuration avait été compressée avant l’encodage ? Si le pas de la clé roulante dépendait de l’octet de texte clair précédent, pourquoi la force brute sur 65 536 candidats cesserait-elle de fonctionner ?
À retenir
- Les encodages cachent configurations, chaînes et trafic à l’inspection superficielle. Les décoder produit des IOC, une connaissance du protocole et du contenu de détection.
- Le jeu de caractères, la longueur, le padding et l’entropie suffisent à classer la plupart des blobs : l’hexadécimal plafonne à 4 bits par octet, Base64 à 6, et les substitutions simples conservent la forme de l’histogramme du texte clair.
- Dans le code, les décodeurs sont de courtes boucles avec un XOR à deux opérandes, un ADD/SUB, une rotation ou une consultation de table. L’arithmétique de l’indice révèle la longueur de la clé, et un registre de clé qui change révèle un schéma roulant.
- Un texte clair connu (
MZ,http, noms de champs) transforme des données XOR en leur flux de clé. Les tests de longueur de clé acceptent aussi les multiples de la vraie longueur : prenez la plus petite. - Méfiez-vous du XOR qui préserve les nuls, du padding Base64 supprimé et de la compression sous l’encodage. Mesurez de nouveau après chaque couche retirée.