Skip to content

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’encodageCe que le décodage vous apporteQui l’utilise
Configuration embarquéeHôtes C2, ports, identifiants de campagne et de bot, intervalles de sommeil, noms de mutexSOC (IOC), threat intel (regroupement)
Chaînes individuellesNoms d’API, chemins de registre, commandes, noms de fichiersVous (évaluation des capacités), ingénieurs détection
Trafic réseauFormat du beacon, identifiants de commande, champs exfiltrésDétection réseau (voir Écrire des signatures réseau, plus loin dans ce module)
Fichiers préparésCe 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

EncodageAlphabetRègle de longueurAutres indices
Hex (Base16)0-9a-f ou 0-9A-FPaireNe mélange jamais les casses dans un même blob
Base32A-Z2-7Multiple de 8 avec paddingPadding = jusqu’à six caractères
Base64 (RFC 4648)A-Za-z0-9+/Multiple de 4 avec paddingZéro, un ou deux =, uniquement à la fin
Base64urlA-Za-z0-9-_Souvent sans paddingCourant dans les URL et les jetons
Base64 personnalisé64 caractères imprimables quelconquesComme Base64Le décodage standard « réussit » mais donne du charabia
XOR, ADD, ROL, substitution par tableToute valeur d’octetIdentique au texte clairLa 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éesEntropie typique (bits par octet)Pourquoi
Texte anglais ou de configuration4 à 5Peu de symboles, fréquences inégales
Texte hexadécimalau plus 4,0Seuls 16 symboles sont possibles
Texte Base64au plus 6,0Seuls 64 symboles (plus =) sont possibles
XOR, ADD ou substitution par table sur un octet appliqué à du texteIdentique au texte clairL’histogramme des octets est seulement réétiqueté
XOR à clé répétée appliqué à du texteUn peu plus élevée que le texte clairChaque octet de clé réétiquette une colonne différente
Données compressées ou chiffrées7,5 et plusRedondance 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 :

asm
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 :

asm
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 :

asm
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],al

Substitution 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 :

asm
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],al

Quelques règles vous évitent de poursuivre les mauvaises boucles :

  • xor eax,eax et 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],0x37a21f4b est stockée en little-endian ; les octets de la clé sont donc 4b 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 :

ObjectifRecette
XOR à clé répétéeFrom Hex, puis XOR avec la clé en Hex (par exemple 4b1fa237)
Trouver une clé XOR courteXOR 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’alphabetSubstitute (personnalisé vers standard), puis un From Base64 ordinaire
Base64 autour d’une compressionFrom 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 :

bash
python3 -m venv venv && . venv/bin/activate
  1. Générez les blobs. Enregistrez sous make_blobs.py et 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.

  2. 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 connu server= :

    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.bin
    text
    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=lab

    Les 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.

  3. 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=lab

    Le 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.

  4. 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 sous roll_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.

  5. 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.