Leçon 8.2 · Encodage, crypto & signatures· 50 min
Identifier les algorithmes cryptographiques
Distinguez chiffrement et encodage, identifiez AES, RC4, ChaCha20, TEA, hashs et CRC32 par constantes, boucles et imports, puis trouvez la clé et l’IV.
Objectifs
- Distinguer chiffrement et encodage à l’aide de la dépendance à la clé, de l’entropie et de la longueur
- Reconnaître les algorithmes courants à partir de leurs constantes, de l’organisation de leurs tables et de la structure de leurs boucles
- Identifier l’usage de la cryptographie à partir des imports CryptoAPI et CNG et des bibliothèques liées statiquement
- Utiliser capa, FindCrypt, signsrch et les signatures crypto YARA, et expliquer ce que chacun peut manquer
- Localiser la clé, l’IV et le mode dans le code ou en mémoire, et les rapporter de façon exploitable
Reconnaître XOR, Base64 et les encodages personnalisés traitait de transformations que l’on peut défaire à force d’observation. Les vrais chiffrements sont différents : sans la clé, leur sortie est conçue pour être indiscernable de l’aléatoire, et aucune analyse de fréquence n’y changera rien. Mais le malware (logiciel malveillant) doit transporter ou dériver sa clé quelque part où vous pouvez l’atteindre, et les algorithmes de chiffrement comptent parmi le code le plus facile à identifier, car ils regorgent de constantes fixes.
Votre travail ici n’est pas la cryptanalyse. Il s’agit de répondre rapidement à quatre questions : quel algorithme, dans quel mode, avec quelle clé et quel IV, et d’où viennent-ils ? Ces réponses permettent à quelqu’un d’écrire un déchiffreur, un extracteur de configuration ou une signature réseau.
Encodage ou chiffrement ?
Avant de nommer un algorithme, déterminez à quelle catégorie vous avez affaire. La distinction est pratique, pas académique :
| Question | Encodage | Chiffrement |
|---|---|---|
| La sortie dépend-elle d’une clé secrète ? | Non, ou d’une clé trivialement courte | Oui, généralement de 128 à 256 bits |
| Entropie du texte encodé | Proche de celle du texte clair, ou plafonnée (hex 4, Base64 6) | Proche de 8 bits par octet, quelle que soit l’entrée |
| Un texte clair connu révèle-t-il la clé ? | Généralement, par simple XOR ou soustraction | Non, pour tout chiffrement moderne |
| Longueur de la sortie | Identique, ou selon un ratio fixe (4/3 pour Base64) | Identique (chiffrement par flot) ou arrondie à 8 ou 16 octets (chiffrement par bloc) |
| Taille du code | Quelques instructions dans une boucle | Tables, nombreux tours, cadencement de clé |
Deux cas limites comptent. RC4 est un vrai chiffrement, mais assez petit pour
ressembler à un décodeur. Et une clé XOR répétée de 16 octets reste un encodage,
aussi aléatoire qu’elle paraisse : si ciphertext XOR known plaintext se
répète, c’est du XOR, pas de l’AES.
Astuce : Une longueur de texte chiffré toujours multiple de 16 indique un chiffrement par bloc de 128 bits (AES), et un multiple de 8 un chiffrement de 64 bits (DES, 3DES, Blowfish, TEA). Des blocs de 16 octets identiques au sein d’un même texte chiffré suggèrent le mode ECB, qui laisse fuir les blocs de texte clair répétés.
Reconnaître les algorithmes à leurs constantes
La plupart des algorithmes ont besoin de nombres fixes : S-boxes, valeurs
initiales de hash, constantes de tour ou mots magiques, stockés sous forme de
tables dans .rdata ou de valeurs immédiates dans le code. Cherchez dans les
deux ordres d’octets, car x86 stocke les mots de 32 bits en little-endian (voir
endianness).
| Algorithme | Constante à rechercher | Remarques |
|---|---|---|
| AES | S-box 63 7C 77 7B F2 6B 6F C5 ... ; S-box inverse 52 09 6A D5 ... ; constantes de tour 01 02 04 08 10 20 40 80 1B 36 | Les implémentations rapides utilisent à la place quatre « T-tables » de 1 Ko ; les instructions AESENC/AESDEC n’ont besoin d’aucune table |
| MD5 | 0x67452301 0xEFCDAB89 0x98BADCFE 0x10325476 ; constantes par étape commençant par 0xD76AA478 | SHA-1 partage les quatre premiers mots |
| SHA-1 | Les mots de MD5 plus 0xC3D2E1F0 ; constantes de tour 0x5A827999 0x6ED9EBA1 0x8F1BBCDC 0xCA62C1D6 | |
| SHA-256 | Valeurs initiales 0x6A09E667 0xBB67AE85 ... ; 64 constantes de tour commençant par 0x428A2F98 | SHA-224 et SHA-512 ont leurs propres tables |
| CRC32 | Polynôme 0xEDB88320 (réfléchi) ou 0x04C11DB7 ; table commençant par 0x00000000 0x77073096 0xEE0E612C | Une somme de contrôle, pas de la crypto, mais utilisée pour le hachage d’API et les contrôles d’intégrité |
| RC4 | Aucune constante | Identifié par sa structure (ci-dessous) |
| ChaCha20 / Salsa20 | "expand 32-byte k" (mots 0x61707865 0x3320646E 0x79622D32 0x6B206574) | "expand 16-byte k" pour les clés de 128 bits |
| TEA / XTEA | Delta 0x9E3779B9, ou 0x61C88647 lorsque le compilateur transforme sum -= delta en addition | Le déchiffrement TEA part souvent de 0xC6EF3720 (32 tours fois delta) |
| Blowfish | Tableau P commençant par 0x243F6A88 0x85A308D3 (décimales de pi) | Quatre S-boxes de 1 Ko suivent |
| RSA | Exposant public 0x10001 ; magic de blob RSA1/RSA2 ; en-têtes PEM -----BEGIN | L’algorithme lui-même est de l’arithmétique sur grands nombres |
Une constante qui correspond est une piste, pas un verdict : 0x67452301 seul
peut désigner MD5, SHA-1 ou RIPEMD-160. Cherchez la table complète ou plusieurs
constantes dans une même fonction.
Structure : RC4, grands nombres et compagnie
RC4 n’a pas de constantes, mais il a une forme caractéristique. Le
cadencement de clé (KSA) remplit un tableau de 256 octets avec les valeurs 0 à
255, puis exécute une seconde boucle de 256 itérations qui échange des paires
d’entrées sous le contrôle de la clé. Le générateur (PRGA) est une courte boucle
qui incrémente i, ajoute S[i] à j, échange de nouveau et applique un XOR
entre les données et S[(S[i] + S[j]) & 0xFF]. Deux boucles de 256 itérations
consécutives, l’une avec une seule écriture d’octet et l’autre avec deux, plus
un usage intensif de movzx pour le & 0xFF : c’est du RC4 jusqu’à preuve du
contraire.
Le code RSA et à courbes elliptiques n’a pas de S-boxes, seulement des
boucles sur des tableaux de limbs (mots) de 32 ou 64 bits utilisant mul,
adc et sbb, plus une exponentiation par carré-multiplication ou une
réduction de Montgomery. Des constantes de courbe comme le nombre premier de
P-256 ou 121665 (Curve25519) nomment la courbe. Les malwares écrivent
rarement ce code à la main ; ils utilisent CryptoAPI, CNG ou une bibliothèque
liée.
AES avec AES-NI n’a pas de table non plus : cherchez aesenc,
aesenclast et aeskeygenassist dans les registres SIMD.
Reconnaître les algorithmes à leurs imports et bibliothèques
Le cas le plus simple est celui du malware qui demande à Windows de faire la crypto : les séquences d’appels sont reconnaissables et les paramètres nomment l’algorithme.
| Famille d’API | Séquence typique | Où l’algorithme est nommé |
|---|---|---|
CryptoAPI (advapi32) | CryptAcquireContext, CryptImportKey ou CryptCreateHash + CryptHashData + CryptDeriveKey, puis CryptEncrypt / CryptDecrypt | Constantes ALG_ID : CALG_RC4 0x6801, CALG_AES_128 0x660E, CALG_AES_256 0x6610, CALG_MD5 0x8003, CALG_SHA1 0x8004, CALG_SHA_256 0x800C |
CNG (bcrypt) | BCryptOpenAlgorithmProvider, BCryptSetProperty, BCryptGenerateSymmetricKey ou BCryptImportKeyPair, BCryptEncrypt / BCryptDecrypt | Chaînes UTF-16 telles que L"AES", L"RSA", L"SHA256" et L"ChainingModeCBC" |
Si ces API sont résolues dynamiquement, la table des imports ne les montrera pas. Cherchez les noms de DLL et de fonctions sous forme de chaînes, ou de hashs (voir hachage d’API), et revenez à la leçon Lire les capacités à partir des imports pour la méthode générale.
Les bibliothèques liées statiquement constituent l’autre cas courant.
OpenSSL, mbedTLS, Crypto++, libsodium, les paquets crypto/* de Go et les
crates Rust apportent leurs propres chaînes d’erreur et de version (en Go, les
chemins de paquets survivent au strip). Identifiez la bibliothèque, renommez
ses fonctions et ne la rétro-ingéniérez pas ; concentrez-vous sur
l’appelant. FLIRT dans IDA, Function ID dans Ghidra et les signatures de
bibliothèques de capa automatisent cela.
Outils
| Outil | Fonctionnement | Points forts | Angles morts |
|---|---|---|---|
| capa | Règles sur des caractéristiques du désassemblage : constantes, boucles, appels d’API, mnémoniques | Nomme l’algorithme et la fonction ; KSA/PRGA de RC4 par la structure | Du code optimisé ou inhabituel peut échapper à la structure d’une règle |
| FindCrypt (versions Ghidra et IDA) | Parcourt le programme chargé à la recherche de tables de constantes connues et les étiquette | Montre les références croisées de la table, qui mènent au code | Seulement les constantes, pas la structure |
| signsrch | Recherche par signatures d’octets de milliers de tables de crypto et de compression | Couverture très large, y compris d’algorithmes obscurs | Offsets bruts uniquement ; bruyant sur les gros fichiers |
| Signatures crypto YARA | Chaînes hexadécimales pour les tables et valeurs magiques (par exemple le jeu crypto_signatures.yar de Yara-Rules) | Rapide, scriptable, fonctionne sur les dumps mémoire | Comme signsrch ; c’est vous qui maintenez les règles |
Exécutez ensemble un scanner de constantes et capa : une correspondance de table vous dit quoi, et les références croisées ou l’adresse de fonction donnée par capa vous disent où.
Attention : Les constantes peuvent être cachées. Les auteurs calculent les tables à l’exécution, les stockent encodées par XOR ou modifient une constante (un delta TEA modifié, une S-box non standard). Un outil qui ne trouve « rien » a seulement prouvé que les tables standard ne sont pas stockées en clair.
Trouver la clé et l’IV
Une fois la fonction connue, la clé est l’un de ses arguments. Remontez depuis le site d’appel :
- Suivez les arguments. Dans l’ABI Windows x64, les quatre premiers
arguments sont dans
rcx,rdx,r8etr9. Unlea rcx, [rip+...]pointant dans.rdataou.data, suivi d’une petite longueur constante dansrdx, correspond souvent à la clé et à sa taille. Pour les appels d’API, la clé est le blob passé àCryptImportKeyou le tampon passé àBCryptGenerateSymmetricKey. - Vérifiez s’il y a dérivation. La valeur stockée peut être un mot de passe
haché au préalable (
CryptCreateHash+CryptDeriveKey, PBKDF2 ou une routine maison). Rapportez le secret stocké et la dérivation, car un déchiffreur a besoin des deux. - Trouvez l’IV et le mode. Cherchez un tampon de 16 octets passé avec la
clé, un appel à
BCryptSetPropertynommant le mode de chaînage, ou unCryptSetKeyParamavecKP_IVouKP_MODE. L’IV est souvent placé en tête de chaque texte chiffré ; vérifiez donc si les 16 premiers octets de chaque message sont consommés séparément. - Passez en dynamique quand le statique est difficile. Dans un
débogueur, placez un point d’arrêt
sur la fonction de déchiffrement,
CryptImportKeyouBCryptGenerateSymmetricKeyet lisez la clé dans les arguments. Dans les dumps mémoire,aeskeyfindrecherche les cadencements de clé AES étendus.
Confirmez toujours une clé récupérée en déchiffrant des données réelles avec
une implémentation indépendante (le paquet Python cryptography ou CyberChef).
Une sortie identique est la seule preuve.
Que rapporter
Une découverte crypto doit permettre à un collègue de reproduire le déchiffrement sans ouvrir l’échantillon :
| Champ | Exemple |
|---|---|
| Algorithme et variante | RC4 ; AES-256 ; ChaCha20 avec nonce de 12 octets ; CRC32 standard |
| Mode et padding | CBC avec PKCS#7 ; CTR ; sans objet pour les chiffrements par flot |
| Finalité | Déchiffrement de configuration, trafic C2, déchiffrement de chaînes, chiffrement de fichiers |
| Implémentation | En ligne (adresse de la fonction), CryptoAPI, CNG, bibliothèque liée statiquement et sa version |
| Emplacement de la clé | RVA de la clé, site d’appel qui la passe, ou mode de dérivation |
| Matériel de clé | La clé et l’IV en hexadécimal, s’ils ont été récupérés et peuvent être partagés sans risque |
| Preuves | Constantes correspondantes, règles capa, sortie des outils, déchiffrement de test vérifié |
Si la clé est générée pour chaque victime et enveloppée par une clé publique RSA embarquée, comme dans beaucoup de ransomwares, dites-le : cela détermine ce que les équipes de réponse peuvent récupérer.
Lab : trouver RC4 et CRC32 dans un binaire
Vous allez compiler un programme inoffensif contenant un RC4 écrit à la main et
un CRC32 fondé sur une table, puis les retrouver tous les deux avec un scan de
constantes YARA, un scanner d’idiomes Capstone et capa. Il vous faut mingw-w64
et un environnement virtuel avec yara-python, capstone, pefile et,
éventuellement, flare-capa.
-
Compilez le programme. Enregistrez sous
cryptolab.c. La table CRC32 est précalculée, comme le fait zlib ; le listing ci-dessous la tronque après les premières entrées, générez donc la table complète de 256 entrées avec quelques lignes de Python (l’algorithme réfléchi standard avec le polynôme0xEDB88320) et collez-la.c // cryptolab.c - RC4 and a table-driven CRC32 over a demo string (benign lab program) #include <stdio.h> #include <string.h> #include <stdint.h> /* Precomputed like zlib's crc32.c: reflected polynomial 0xEDB88320 */ static const uint32_t crc_table[256] = { 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, /* ... 250 more entries ... */ }; static uint32_t crc32(const unsigned char *p, size_t n) { uint32_t c = 0xFFFFFFFFu; while (n--) c = crc_table[(c ^ *p++) & 0xFF] ^ (c >> 8); return c ^ 0xFFFFFFFFu; } static void rc4(const unsigned char *key, size_t klen, unsigned char *buf, size_t n) { unsigned char S[256]; for (int i = 0; i < 256; i++) /* KSA: identity permutation */ S[i] = (unsigned char)i; for (int i = 0, j = 0; i < 256; i++) { /* KSA: key-driven swaps */ j = (j + S[i] + key[i % klen]) & 0xFF; unsigned char t = S[i]; S[i] = S[j]; S[j] = t; } for (size_t k = 0, i = 0, j = 0; k < n; k++) { /* PRGA */ i = (i + 1) & 0xFF; j = (j + S[i]) & 0xFF; unsigned char t = S[i]; S[i] = S[j]; S[j] = t; buf[k] ^= S[(S[i] + S[j]) & 0xFF]; } } int main(void) { static const unsigned char key[] = "lab-rc4-key"; unsigned char msg[] = "server=update.example.com;port=8080;id=lab"; size_t n = strlen((char *)msg); printf("crc32(plain) = %08x\n", crc32(msg, n)); rc4(key, sizeof(key) - 1, msg, n); printf("rc4 first bytes = %02x %02x %02x %02x\n", msg[0], msg[1], msg[2], msg[3]); rc4(key, sizeof(key) - 1, msg, n); printf("round trip: %s\n", msg); return 0; }bash x86_64-w64-mingw32-gcc -O1 -s -o cryptolab.exe cryptolab.c x86_64-w64-mingw32-gcc -O2 -s -o cryptolab_O2.exe cryptolab.cCompilé nativement (
clang -O2 cryptolab.c) et exécuté, il affiche :text crc32(plain) = 65f3938f rc4 first bytes = 7b 88 57 44 round trip: server=update.example.com;port=8080;id=lab -
Recherchez les constantes avec YARA. Enregistrez un petit jeu de règles sous
crypto_consts.yar. Chaque règle est un fragment d’une table de la section sur les constantes ci-dessus :text rule CRC32_table_reflected { strings: $t = { 00 00 00 00 96 30 07 77 2C 61 0E EE BA 51 09 99 } condition: $t } rule CRC32_poly_immediate { strings: $p = { 20 83 B8 ED } // 0xEDB88320 as a little-endian dword condition: $p } rule SHA256_initial_values { strings: $h = { 67 E6 09 6A 85 AE 67 BB 72 F3 6E 3C 3A F5 4F A5 } condition: $h } rule AES_forward_sbox { strings: $s = { 63 7C 77 7B F2 6B 6F C5 30 01 67 2B FE D7 AB 76 } condition: $s } rule ChaCha_Salsa_sigma { strings: $s = "expand 32-byte k" condition: $s } rule TEA_delta { strings: $d1 = { B9 79 37 9E } // 0x9E3779B9 $d2 = { 47 86 C8 61 } // 0x61C88647 (= -0x9E3779B9) condition: any of them }Associez ensuite chaque correspondance à une section et à une adresse virtuelle avec
scan_consts.py:python # scan_consts.py - run the crypto-constant YARA rules and map hits to PE sections import sys, pefile, yara rules = yara.compile(filepath="crypto_consts.yar") for path in sys.argv[1:]: pe = pefile.PE(path) data = open(path, "rb").read() print(f"== {path}") for m in rules.match(data=data): for s in m.strings: for inst in s.instances: sec = next((x.Name.rstrip(b"\0").decode() for x in pe.sections if x.PointerToRawData <= inst.offset < x.PointerToRawData + x.SizeOfRawData), "?") rva = pe.get_rva_from_offset(inst.offset) print(f" {m.rule:24} {s.identifier:4} offset=0x{inst.offset:05x} " f"va=0x{pe.OPTIONAL_HEADER.ImageBase + rva:x} {sec}")bash python scan_consts.py cryptolab.exe cryptolab_O2.exetext == cryptolab.exe CRC32_table_reflected $t offset=0x02460 va=0x140004060 .rdata CRC32_poly_immediate $p offset=0x02660 va=0x140004260 .rdata == cryptolab_O2.exe CRC32_table_reflected $t offset=0x02460 va=0x140004060 .rdata CRC32_poly_immediate $p offset=0x02660 va=0x140004260 .rdataLa table est trouvée au même endroit dans les deux builds. Notez que la correspondance « polynôme » n’est pas une valeur immédiate dans le code : elle se trouve 0x200 octets après le début de la table, car l’entrée 128 de la table CRC32 réfléchie est égale au polynôme lui-même. Vérifiez toujours où tombe une correspondance. Aucune règle ne trouve RC4, car RC4 n’a pas de constantes.
-
Trouvez RC4 par sa structure avec Capstone. Enregistrez sous
find_rc4.py. Le script cherche des boucles courtes qui se terminent par un saut conditionnel arrière, comparent à0xFFou0x100et écrivent des octets isolés :python # find_rc4.py - flag tight 256-iteration loops that write bytes (RC4 KSA shape) import sys, pefile from capstone import Cs, CS_ARCH_X86, CS_MODE_64 from capstone.x86 import X86_OP_IMM, X86_OP_MEM pe = pefile.PE(sys.argv[1]) text = next(s for s in pe.sections if s.Name.startswith(b".text")) base = pe.OPTIONAL_HEADER.ImageBase + text.VirtualAddress md = Cs(CS_ARCH_X86, CS_MODE_64) md.detail = True insns = list(md.disasm(text.get_data(), base)) index = {i.address: n for n, i in enumerate(insns)} def byte_store(i): ops = i.operands return (i.mnemonic == "mov" and len(ops) == 2 and ops[0].type == X86_OP_MEM and ops[0].size == 1) loops = [] for n, i in enumerate(insns): # a conditional jump backwards = the bottom of a loop if not i.mnemonic.startswith("j") or i.mnemonic == "jmp": continue tgt = i.operands[0].imm if i.operands[0].type == X86_OP_IMM else None if tgt is None or tgt >= i.address or tgt not in index: continue body = insns[index[tgt]:n + 1] if len(body) > 25: continue bound = [x for x in body if x.mnemonic == "cmp" and x.operands[-1].type == X86_OP_IMM and x.operands[-1].imm in (0xFF, 0x100)] stores = [x for x in body if byte_store(x)] if bound and stores: loops.append((tgt, i.address, len(body), len(stores))) for start, end, size, nst in loops: print(f"loop 0x{start:x}-0x{end:x}: {size:2} insns, 256 iterations, {nst} byte store(s)") # RC4 KSA: an init loop (1 store) followed closely by a swap loop (>= 2 stores) for a, b in zip(loops, loops[1:]): if a[3] == 1 and b[3] >= 2 and b[0] - a[1] < 0x40: print(f"RC4 KSA candidate: init 0x{a[0]:x}, swap 0x{b[0]:x}") for x in insns[index[a[0]]:index[a[1]] + 1]: print(f" {x.address:x}: {x.mnemonic:6} {x.op_str}")bash python find_rc4.py cryptolab.exetext loop 0x1400014e0-0x1400014ee: 5 insns, 256 iterations, 1 byte store(s) loop 0x140001500-0x140001539: 16 insns, 256 iterations, 2 byte store(s) RC4 KSA candidate: init 0x1400014e0, swap 0x140001500 1400014e0: mov byte ptr [rdx], al 1400014e2: add eax, 1 1400014e5: add rdx, 1 1400014e9: cmp eax, 0x100 1400014ee: jne 0x1400014e0La boucle de cinq instructions correspond à
S[i] = i, et la boucle d’échange suit immédiatement. Exécutez maintenant le même script surcryptolab_O2.exe: il n’affiche rien. En-O2, GCC vectorise la boucle d’identité en instructions SSE qui remplissent seize octets par itération ; il ne reste donc plus decmp eax, 0x100à détecter. Gardez cela en tête chaque fois qu’une heuristique écrite à la main ne renvoie rien. -
Exécutez capa. Installé avec
pip install flare-capa, capa 9.4 a besoin de ses règles (le dépôtcapa-rules) et du répertoiresigsdu dépôt capa, au même tag :bash capa -r capa-rules -s capa/sigs cryptolab.exeRéduit aux lignes pertinentes :
text │ CRYPTOGRAPHY │ Encrypt Data::RC4 [C0027.009] │ │ │ Encryption Key::RC4 KSA [C0028.002] │ │ │ Generate Pseudo-random Sequence::RC4 PRGA [C0021.004] │ │ DATA │ Checksum::CRC32 [C0032.001] │ │ │ Encode Data::XOR [C0026.002] │ ... │ hash data with CRC32 │ data-manipulation/checksum/crc32 │ │ encode data using XOR (2 matches) │ data-manipulation/encoding/xor │ │ encrypt data using RC4 KSA │ data-manipulation/encryption/rc4 │ │ encrypt data using RC4 PRGA │ data-manipulation/encryption/rc4 │Avec
-vv, capa montre pourquoi. La règle RC4 KSA a reconnu la fonction0x1400014C0grâce à deux boucles serrées bornées par0x100(à0x1400014E9et0x140001532, les mêmes boucles que votre script a trouvées) plus le motifmovzxdu « modulo 256 ». La règle CRC32 a reconnu les 32 premiers octets de la table. Exécutez aussi capa surcryptolab_O2.exe: il signale toujours CRC32 et RC4 PRGA, mais « RC4 KSA » disparaît, pour la même raison que votre script est resté muet. -
Trouvez la clé au site d’appel. capa vous a donné la fonction RC4,
0x1400014c0. Regardez comment elle est appelée :bash x86_64-w64-mingw32-objdump -d -M intel cryptolab.exe | grep -B6 "call 0x1400014c0"text 14000168b: 48 8d 3d be 29 00 00 lea rdi,[rip+0x29be] # 0x140004050 140001692: 49 89 d9 mov r9,rbx 140001695: 49 89 f0 mov r8,rsi 140001698: ba 0b 00 00 00 mov edx,0xb 14000169d: 48 89 f9 mov rcx,rdi 1400016a0: e8 1b fe ff ff call 0x1400014c0Le premier argument (
rcx) vaut0x140004050et le second (rdx) 11 : une clé de 11 octets dans.rdata. Affichez-la avecx86_64-w64-mingw32-objdump -s -j .rdata cryptolab.exeet vous verrezlab-rc4-keyà cette adresse, juste avant la table CRC32. -
Confirmez avec une implémentation indépendante. Enregistrez sous
rc4_check.py:python # rc4_check.py - confirm the identification: same key, same keystream as the binary import pefile, zlib def rc4(key: bytes, data: bytes) -> bytes: S = list(range(256)); j = 0 for i in range(256): j = (j + S[i] + key[i % len(key)]) & 0xFF S[i], S[j] = S[j], S[i] out, i, j = bytearray(), 0, 0 for b in data: i = (i + 1) & 0xFF; j = (j + S[i]) & 0xFF S[i], S[j] = S[j], S[i] out.append(b ^ S[(S[i] + S[j]) & 0xFF]) return bytes(out) pe = pefile.PE("cryptolab.exe") key = pe.get_data(0x4050, 11) # RVA of the lea rcx target, length from edx=0xb msg = b"server=update.example.com;port=8080;id=lab" print("key :", key) print("rc4 first :", rc4(key, msg)[:4].hex(" ")) print("crc32 :", f"{zlib.crc32(msg):08x}")text key : b'lab-rc4-key' rc4 first : 7b 88 57 44 crc32 : 65f3938fLes deux valeurs correspondent à ce que le programme a affiché à l’étape 1. Vous avez maintenant identifié les deux algorithmes, localisé la clé et vérifié le résultat : tout ce que demande le tableau du rapport.
Pour comparaison, refaites l’étape 5 sur
cryptolab_O2.exe. Les deux appels à0x1400014c0ne passent plus que le tampon (lea rcx,[rsp+0x30]) et sa longueur (mov edx,0x2a). GCC a spécialisé la fonction pour son unique clé constante : l’adresse de la clé0x140004050et une multiplication par un nombre magique pouri % 11ont été déplacées à l’intérieur. La clé est toujours là, mais vous la trouvez en lisant le corps de la fonction, pas le site d’appel.
Questions : Pourquoi le scan YARA a-t-il trouvé CRC32 mais pas RC4, et
quelle approche a trouvé RC4 à la place ? Quelle règle capa a disparu en -O2,
et qu’est-ce que cela vous apprend sur la confiance accordée à un seul outil ?
Si la clé à 0x140004050 avait été encodée par XOR et décodée juste avant
l’appel, quelles étapes de ce lab fonctionneraient encore et lesquelles
remplaceriez-vous par un point d’arrêt dans le débogueur ? Rédigez le tableau
« Que rapporter » pour ce binaire.
À retenir
- Le chiffrement dépend d’une vraie clé et produit une sortie quasi aléatoire ; l’encodage, non. Un texte clair connu qui révèle une clé répétée signifie que vous êtes encore face à un encodage.
- Les constantes identifient la plupart des algorithmes : S-boxes AES, valeurs
initiales MD5/SHA, tables CRC32, la chaîne
expand 32-byte kde ChaCha20 et le delta de TEA. Cherchez dans les deux ordres d’octets et vérifiez où tombe chaque correspondance. - RC4, RSA et AES-NI n’ont pas de tables révélatrices ; reconnaissez-les à leur structure. CryptoAPI et CNG nomment l’algorithme dans leurs paramètres, et les bibliothèques liées doivent être identifiées plutôt que rétro-ingéniérées.
- capa, FindCrypt, signsrch et YARA se complètent, et tous peuvent manquer du code optimisé ou modifié. Confirmez chaque identification en déchiffrant avec une implémentation indépendante.
- Rapportez l’algorithme, le mode, la finalité, l’implémentation, l’emplacement de la clé et le matériel de clé, avec les preuves qui les établissent.