BFD56
5 minutos de lectura
Se nos proporciona el código fuente en Python para cifrar la flag:
from secret import pt,key,block_length
import random
alph = "ABCDEFGHIKLMNOPQRSTUVWXYZ"
def strmask(msg,mask):
mask = (mask * ((len(msg)//len(mask)) + 1))
return "".join([alph[(alph.index(i) + alph.index(j)) % 25] for i,j in zip(msg, mask)])
def strunmask(msg,mask):
mask = (mask * ((len(msg)//len(mask)) + 1))
return "".join([alph[(alph.index(i) - alph.index(j)) % 25] for i,j in zip(msg, mask)])
def encrypt_block(pt, indices, characters):
res = [-1] * len(pt) * 2
for i,c in enumerate(pt):
res[i],res[i+len(pt)] = indices[c][0],indices[c][1]
ret = ""
for i in range(0,len(res),2):
ret += characters[str(res[i]) + str(res[i+1])]
return ret
def encrypt(plaintext, key, block_length):
iv = "".join([random.sample(list(alph), 1)[0] for i in range(block_length)])
indices = {}
characters = {}
for i,c in enumerate(key):
indices[c] = str(i//5) + str(i%5)
characters[str(i//5) + str(i%5)] = c
plaintext_blocks = [plaintext[i : i + block_length] for i in range(0, len(plaintext), block_length)]
ciphertext = ""
cmask = iv
for block in plaintext_blocks:
block_enc = encrypt_block(strmask(block,cmask),indices,characters)
ciphertext += block_enc
cmask = block_enc
return iv,ciphertext
with open('ciphertext.txt', 'w') as f:
f.write(str(encrypt(pt,key,block_length)))
También tenemos el archivo ciphertext.txt
:
('GOAQXASQ', 'UMAYACISLGYWQWEFDSWAZPEXCVZMGLDVPDHNKWBOHQBFLLIQXVGGIGSCTCYDBTOQZGUWIVMNYHFZNONRBZPGYEWSBRHHEXENIWPTVELRGREXCXGXYZAYGBXGCTDMCOLXPOWRHUFLOLWUDMAGBYEMBOLIMNRICBPIUSQIHSNAWEPGKKKFHGQUWAWVYCXAHKLOFBZNSOKUBXTNMIQGODSHPOAAUMXCEHSGSFMPPTDDKLUINZFFHIPBIAGBLXVUOKRSIVUUBDUGMGZTXTXVHNTIBMUGURFGXBVNQFETEHXAWXMORWDCHBCLYETCYXMKEPYT')
Análisis del cifrado
En primer lugar, notamos que aunque block_length
(también conocido como periodo) se define en un módulo secret
, sabemos que es 8
porque el IV tiene 8
caracteres:
iv = "".join([random.sample(list(alph), 1)[0] for i in range(block_length)])
Además, después de leer por encima el código, podemos estar seguros de que la clave debe contener todas las letras mayúsculas (25, la J
no aparece) para crear mapas llamados indices
y characters
.
De hecho, estamos tratando con un cifrado Bifid (véanse los ejemplos para comprender el cifrado). Hay algunos proyectos sobre criptoanálisis de Bifid, Pero probablemente no serán suficientes para resolver el reto. Además, la descripción del reto habla de Delastelle, que fue quien diseñó el cifrado Bifid.
Cifrado
En realidad, la implementación del cifrado no es un cifrado Bifid puro, porque tiene cierta inspiración de CBC. De hecho, podemos dibujar el proceso de cifrado de la siguiente manera:
La función strmask
altera el bloque de texto claro que ingresará al cifrador Bifid, y el bloque de texto cifrado resultante alimenta el siguiente bloque strmask
.
Descifrado
Aunque no hay implementación de descifrado, simplemente podemos revertir el algoritmo de cifrado y cambiar strmask
por strunmask
:
El ataque
Como se dijo antes, hay un proyecto que intenta romper el cifrado Bifid. Está escrito en C (para que sea lo más rápido posible).
El cifrado Bifid se ve caótico a primera vista, pero la forma en que funciona es en realidad simple. Sin embargo, las técnicas de criptoanálisis para este cifrado se basan en análisis de frecuencia y enfoques probabilísticos (para más información, este artículo se explica con detalle el proceso de criptoanálisis).
Para resolver este reto, debemos implementar el “cifrado Bifid CBC” en C y luego usar el resto del proyecto para aplicar las técnicas de criptoanálisis. Para aquellos que ya saben cómo programar en C (presumiblemente reversers y pwners), esto debería ser fácil.
Estas son las funciones relevantes:
void strunmask(char* message, char* mask) {
int i;
int a, b;
for (i = 0; i < PERIOD; i++) {
a = (int) (index(ALPHABET, message[i]) - ALPHABET);
b = (int) (index(ALPHABET, mask[i]) - ALPHABET);
message[i] = ALPHABET[(25 + a - b) % 25];
}
}
void decrypt_block(char* key, char* ct_block, char* pt_block) {
int i;
int a_ind, b_ind;
int a_row, b_row;
int a_col, b_col;
char a, b;
for (i = 0; i < PERIOD; i++) {
a = ct_block[i / 2];
b = ct_block[(PERIOD + i) / 2];
a_ind = (int) (index(key, a) - key);
b_ind = (int) (index(key, b) - key);
a_row = a_ind / 5;
b_row = b_ind / 5;
a_col = a_ind % 5;
b_col = b_ind % 5;
if (i % 2 == 0) {
pt_block[i] = key[5 * a_row + b_row];
} else {
pt_block[i] = key[5 * a_col + b_col];
}
}
}
void bifid_decrypt(char* key, char* iv, char* ct, char* pt) {
while (*ct) {
decrypt_block(key, ct, pt);
strunmask(pt, iv);
iv = ct;
ct += PERIOD;
pt += PERIOD;
}
}
Siento si se ve un poco raro… Usé algo de magia negra de punteros para ser más eficiente, aunque el código ha perdido legibilidad…
De todos modos, esta es la función main
:
int main(int argc, char** argv) {
char* iv;
char* ciphertext;
if (argc != 3) {
printf("[-] Usage: %s <iv> <ciphertext>\n", argv[0]);
return 1;
}
iv = argv[1];
ciphertext = argv[2];
int length = strlen(ciphertext);
char* plaintext = malloc(length + 1);
char key[] = "ABCDEFGHIKLMNOPQRSTUVWXYZ";
bifid_crack(key, iv, ciphertext, length);
bifid_decrypt(key, iv, ciphertext, plaintext);
printf("[*] Key: %s\n[+] Plaintext: %s\n", key, plaintext);
free(plaintext);
return 0;
}
Para compilarlo, debemos tener qgr.h
:
$ gcc solve.c -O3 -o bifid -lm
$ ./bifid
[-] Usage: ./bifid <iv> <ciphertext>
Flag
Si ejecutamos el binario, veremos el mensaje en texto claro al cabo de unos pocos segundos:
$ ./bifid GOAQXASQ UMAYACISLGYWQWEFDSWAZPEXCVZMGLDVPDHNKWBOHQBFLLIQXVGGIGSCTCYDBTOQZGUWIVMNYHFZNONRBZPGYEWSBRHHEXENIWPTVELRGREXCXGXYZAYGBXGCTDMCOLXPOWRHUFLOLWUDMAGBYEMBOLIMNRICBPIUSQIHSNAWEPGKKKFHGQUWAWVYCXAHKLOFBZNSOKUBXTNMIQGODSHPOAAUMXCEHSGSFMPPTDDKLUINZFFHIPBIAGBLXVUOKRSIVUUBDUGMGZTXTXVHNTIBMUGURFGXBVNQFETEHXAWXMORWDCHBCLYETCYXMKEPYT
[*] Key: CUQHEYKWZSFMOBILTAXGDVRPN
[+] Plaintext: BIFIDISPRETTYCOOLBUTINTWENTYTWENTYWEKNOWTHATELECTRONICCODEBOOKMODEISPRETTYWEAKGOODTHINGWEHAVECIPHERBLOCKCHAININGTOSAVETHEDAYIBETBECAUSEIUSEDSUCHANAWESOMEANDMODERNTECHNIQUENOBODYWILLEVERBEABLETOCRACKTHISBUTIFTHEYDOISUPPOSETHEYDESERVEANHTBFLAGWHICHISTHESEWORDSSEPARATEDBYUNDERSCORESANYTHINGISABLOCKCIPHERIFYOUTRYHARDENOUGH
Después de analizar el texto, tenemos esto:
BIFID IS PRETTY COOL BUT IN TWENTY TWENTY WE KNOW THAT ELECTRONIC CODEBOOK
MODE IS PRETTY WEAK GOOD THING WE HAVE CIPHER BLOCK CHAINING TO SAVE THE DAY
I BET BECAUSE I USED SUCH AN AWESOME AND MODERN TECHNIQUE NOBODY WILL EVER
BE ABLE TO CRACK THIS BUT IF THEY DO I SUPPOSE THEY DESERVE AN HTB FLAG
WHICH IS THESE WORDS SEPARATED BY UNDERSCORES ANYTHING IS A BLOCK CIPHER IF
YOU TRY HARD ENOUGH
Y la flag es:
HTB{ANYTHING_IS_A_BLOCK_CIPHER_IF_YOU_TRY_HARD_ENOUGH}
El script completo se puede encontrar aquí: solve.c
.