Secured Transfer
7 minutos de lectura
Se nos proporciona un archivo binario llamado securetransfer
:
$ file securetransfer
securetransfer: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=0457997eda987eb100de85a2954fc8b8fc660a53, for GNU/Linux 3.2.0, stripped
También tenemos un archivo de captura de red llamado trace.pcap
.
Análisis de tráfico
Comenzando por trace.pcap
, podemos usar Wireshark para analizar la captura de red:
Solamente hay un segmento TCP que contiene datos:
5f558867993dccc99879f7ca39c5e406972f84a3a9dd5d48972421ff375cb18c
Ingeniería inversa
Podemos usar Ghidra para leer el código descompilado en C. Esta es la función main
:
int main(int param_1, long param_2) {
OPENSSL_init_crypto(2,0);
OPENSSL_init_crypto(0xc, 0);
OPENSSL_init_crypto(0x80, 0);
if (param_1 == 3) {
printf("Sending File: %s to %s\n", *(undefined8 *) (param_2 + 0x10), *(undefined8 *) (param_2 + 8));
send_encrypt(*(undefined8 *) (param_2 + 8), *(undefined8 *) (param_2 + 0x10));
} else if (param_1 == 1) {
puts("Receiving File");
recv_decrypt();
} else {
puts("Usage ./securetransfer [<ip> <file>]");
}
return 0;
}
Básicamente, ofrece dos opciones: cifrar un archivo y enviarlo, o recibir un archivo y descifrarlo. También vemos que utiliza OpenSSL para construir el cifrado.
Función de cifrado
Vamos a echar un vistazo a send_encrypt
:
undefined8 send_encrypt(char *param_1, char *param_2) {
int __fd;
int iVar1;
undefined8 uVar2;
size_t sVar3;
long in_FS_OFFSET;
size_t local_50;
FILE *local_48;
ulong local_40;
void *local_38;
void *local_30;
undefined local_28[4];
undefined local_24[20];
long local_10;
local_10 = *(long *) (in_FS_OFFSET + 0x28);
__fd = socket(2, 1, 0);
if (__fd == -1) {
puts("ERROR: Socket creation failed");
uVar2 = 0;
} else {
memset(local_28, 0, 0x10);
local_28._0_2_ = 2;
iVar1 = inet_pton(2, param_1, local_24);
if (iVar1 == 0) {
printf("ERROR: Invalid input address \'%s\'\n", param_1);
uVar2 = 0;
} else {
local_28._2_2_ = htons(0x539);
iVar1 = connect(__fd, (sockaddr *) local_28, 0x10);
if (iVar1 == 0) {
local_48 = fopen(param_2, "rb");
if (local_48 == NULL) {
printf("ERROR: Can\'t open the file \'%s\'\n", param_2);
close(__fd);
uVar2 = 0;
} else {
fseek(local_48, 0, 2);
local_40 = ftell(local_48);
fseek(local_48, 0, 0);
if (local_40 < 0x10) {
puts("ERROR: File too small");
fclose(local_48);
close(__fd);
uVar2 = 0;
} else if (local_40 < 0x1001) {
local_38 = malloc(local_40);
local_30 = malloc(local_40 * 2);
sVar3 = fread(local_38, 1, local_40, local_48);
if (local_40 == sVar3) {
iVar1 = encrypt(local_38, local_40 & 0xffffffff, local_30);
local_50 = (size_t) iVar1;
write(__fd, &local_50, 8);
write(__fd, local_30, local_50);
puts("File send...");
free(local_30);
free(local_38);
fclose(local_48);
close(__fd);
uVar2 = 1;
} else {
puts("ERROR: Failed reading the file");
free(local_30);
free(local_38);
fclose(local_48);
close(__fd);
uVar2 = 0;
}
} else {
puts("ERROR: File too large");
fclose(local_48);
close(__fd);
uVar2 = 0;
}
}
} else {
puts("ERROR: Connection failed");
uVar2 = 0;
}
}
}
if (local_10 != *(long *) (in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return uVar2;
}
Esta función lee un archivo de entrada, lo cifra y lo envía a través de un socket TCP. Esta es la función encrypt
:
int encrypt(uchar *param_1, int param_2, uchar *param_3) {
int iVar1;
EVP_CIPHER *cipher;
long in_FS_OFFSET;
int local_50;
int local_4c;
char *local_48;
EVP_CIPHER_CTX *local_40;
uchar local_38;
undefined local_37;
undefined local_36;
undefined local_35;
undefined local_34;
undefined local_33;
undefined local_32;
undefined local_31;
undefined local_30;
undefined local_2f;
undefined local_2e;
undefined local_2d;
undefined local_2c;
undefined local_2b;
undefined local_2a;
undefined local_29;
undefined local_28;
undefined local_27;
undefined local_26;
undefined local_25;
undefined local_24;
undefined local_23;
undefined local_22;
undefined local_21;
undefined local_20;
undefined local_1f;
undefined local_1e;
undefined local_1d;
undefined local_1c;
undefined local_1b;
undefined local_1a;
undefined local_19;
long local_10;
local_10 = *(long *) (in_FS_OFFSET + 0x28);
local_38 = 's';
local_2f = 0x65;
local_2e = 0x74;
local_2d = 0x6b;
local_1d = 0x74;
local_1c = 0x69;
local_37 = 0x75;
local_36 = 0x70;
local_22 = 0x6e;
local_21 = 99;
local_1b = 0x6f;
local_32 = 0x65;
local_31 = 99;
local_33 = 0x73;
local_20 = 0x72;
local_1f = 0x79;
local_30 = 0x72;
local_26 = 0x66;
local_25 = 0x6f;
local_24 = 0x72;
local_1a = 0x6e;
local_2c = 0x65;
local_2b = 0x79;
local_2a = 0x75;
local_29 = 0x73;
local_28 = 0x65;
local_27 = 100;
local_23 = 0x65;
local_35 = 0x65;
local_34 = 0x72;
local_1e = 0x70;
local_19 = 0x21;
local_48 = "someinitialvalue";
local_40 = EVP_CIPHER_CTX_new();
if (local_40 == NULL) {
iVar1 = 0;
} else {
cipher = EVP_aes_256_cbc();
iVar1 = EVP_EncryptInit_ex(local_40, cipher, NULL, &local_38, (uchar *) local_48);
if (iVar1 == 1) {
iVar1 = EVP_EncryptUpdate(local_40, param_3, &local_50, param_1, param_2);
if (iVar1 == 1) {
local_4c = local_50;
iVar1 = EVP_EncryptFinal_ex(local_40,param_3 + local_50, &local_50);
if (iVar1 == 1) {
local_4c = local_4c + local_50;
EVP_CIPHER_CTX_free(local_40);
iVar1 = local_4c;
} else {
iVar1 = 0;
}
} else {
iVar1 = 0;
}
} else {
iVar1 = 0;
}
}
if (local_10 != *(long *) (in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return iVar1;
}
Es una función un tanto larga, pero lo único que hace es configurar una clave byte por byte, y un valor inicial (IV) para crear un cifraro AES CBC. En este punto, cogí todos los valores local_xx
y creé la clave AES. Traté de usar esta clave AES con el IV para descifrar los datos del segmento TCP, pero no era correcto.
Función de descifrado
Entonces eché un vistazo a la función decrypt
(llamada por recv_decrypt
):
int decrypt(uchar *param_1, int param_2, uchar *param_3) {
int iVar1;
EVP_CIPHER *cipher;
long in_FS_OFFSET;
int local_50;
int local_4c;
char *local_48;
EVP_CIPHER_CTX *local_40;
uchar local_38;
undefined local_37;
undefined local_36;
undefined local_35;
undefined local_34;
undefined local_33;
undefined local_32;
undefined local_31;
undefined local_30;
undefined local_2f;
undefined local_2e;
undefined local_2d;
undefined local_2c;
undefined local_2b;
undefined local_2a;
undefined local_29;
undefined local_28;
undefined local_27;
undefined local_26;
undefined local_25;
undefined local_24;
undefined local_23;
undefined local_22;
undefined local_21;
undefined local_20;
undefined local_1f;
undefined local_1e;
undefined local_1d;
undefined local_1c;
undefined local_1b;
undefined local_1a;
undefined local_19;
long local_10;
local_10 = *(long *) (in_FS_OFFSET + 0x28);
local_38 = 's';
local_2f = 0x65;
local_37 = 0x75;
local_36 = 0x70;
local_26 = 0x66;
local_25 = 0x6f;
local_24 = 0x72;
local_21 = 99;
local_2e = 0x74;
local_2d = 0x6b;
local_1d = 0x74;
local_1b = 0x6f;
local_32 = 0x65;
local_31 = 99;
local_33 = 0x73;
local_20 = 0x72;
local_2b = 0x79;
local_2a = 0x75;
local_29 = 0x73;
local_1c = 0x69;
local_28 = 0x65;
local_27 = 100;
local_23 = 0x65;
local_1f = 0x79;
local_30 = 0x72;
local_34 = 0x72;
local_1e = 0x70;
local_19 = 0x21;
local_1a = 0x6e;
local_2c = 0x65;
local_35 = 0x65;
local_22 = 0x6e;
local_48 = "someinitialvalue";
local_40 = EVP_CIPHER_CTX_new();
if (local_40 == NULL) {
iVar1 = 0;
} else {
cipher = EVP_aes_256_cbc();
iVar1 = EVP_DecryptInit_ex(local_40, cipher, NULL, &local_38, (uchar *) local_48);
if (iVar1 == 1) {
iVar1 = EVP_DecryptUpdate(local_40, param_3, &local_50, param_1, param_2);
if (iVar1 == 1) {
local_4c = local_50;
iVar1 = EVP_DecryptFinal_ex(local_40, param_3 + local_50, &local_50);
if (iVar1 == 1) {
local_4c = local_4c + local_50;
EVP_CIPHER_CTX_free(local_40);
iVar1 = local_4c;
} else {
iVar1 = 0;
}
} else {
iVar1 = 0;
}
} else {
iVar1 = 0;
}
}
if (local_10 != *(long *) (in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return iVar1;
}
Se parece bastante a encrypt
. De hecho, AES es un algoritmo de cifrado simétrico, por lo que la clave AES es la misma para cifrar que para descifrar.
Si comparamos las funciones encrypt
y decrypt
, veremmos que los valores local_xx
no están en el mismo orden, pero sabemos que ambas claves AES son iguales. Entonces, me di cuenta de que los valoes local_xx
simplemente estaban desordenados y tenemos que ordenarlos para construir la clave AES. Este sería el orden correcto:
local_19 = 0x21;
local_1a = 0x6e;
local_1b = 0x6f;
local_1c = 0x69;
local_1d = 0x74;
local_1e = 0x70;
local_1f = 0x79;
local_20 = 0x72;
local_21 = 99;
local_22 = 0x6e;
local_23 = 0x65;
local_24 = 0x72;
local_25 = 0x6f;
local_26 = 0x66;
local_27 = 100;
local_28 = 0x65;
local_29 = 0x73;
local_2a = 0x75;
local_2b = 0x79;
local_2c = 0x65;
local_2d = 0x6b;
local_2e = 0x74;
local_2f = 0x65;
local_30 = 0x72;
local_31 = 99;
local_32 = 0x65;
local_33 = 0x73;
local_34 = 0x72;
local_35 = 0x65;
local_36 = 0x70;
local_37 = 0x75;
local_38 = 's';
Flag
En este punto, podemos coger estos valores y ponerlos en Python. También, nótese que el orden anterior es inverso debido al endianness (little-endian):
$ python3 -q
>>> from Crypto.Cipher import AES
>>> key = bytes([
... 0x21,
... 0x6e,
... 0x6f,
... 0x69,
... 0x74,
... 0x70,
... 0x79,
... 0x72,
... 99,
... 0x6e,
... 0x65,
... 0x72,
... 0x6f,
... 0x66,
... 100,
... 0x65,
... 0x73,
... 0x75,
... 0x79,
... 0x65,
... 0x6b,
... 0x74,
... 0x65,
... 0x72,
... 99,
... 0x65,
... 0x73,
... 0x72,
... 0x65,
... 0x70,
... 0x75,
... ord('s'),
... ])[::-1]
>>> iv = b'someinitialvalue'
>>> cipher = AES.new(key, AES.MODE_CBC, iv)
>>> data = bytes.fromhex('5f558867993dccc99879f7ca39c5e406972f84a3a9dd5d48972421ff375cb18c')
>>> cipher.decrypt(data)
b'HTB{vryS3CuR3_F1L3_TR4nsf3r}\x04\x04\x04\x04'