Secured Transfer
7 minutes to read
We are given a binary file called 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
We also have a network capture file called trace.pcap
.
Traffic analysis
Starting with trace.pcap
, we can use Wireshark to analyze the network capture file:
There is only one TCP segment that has data:
5f558867993dccc99879f7ca39c5e406972f84a3a9dd5d48972421ff375cb18c
Reverse engineering
We can use Ghidra to read the decompiled source code in C. This is the main
function:
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;
}
Basically, there are two options: encrypt a file and send it, or receive a file and decrypt it. We also see that it uses OpenSSL to construct the cipher.
Encryption function
Let’s take a look at 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;
}
This function reads the input file, encrypts it and sends it through a TCP socket. This is 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;
}
It is quite a long function, but it only sets up a key byte by byte, and an initial value (IV) to create an AES CBC cipher. At this point, I took all the local_xx
values and built the AES key. I tried to use this AES key along with the IV to decrypt the data from the TCP segment, but it was not correct.
Decryption function
Then I took a look at decrypt
(called by 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;
}
It looks very similar to encrypt
. Indeed, AES is a symmetric algorithm, so the AES key is the same for encryption as for decryption.
If you compare encrypt
and decrypt
, you will see that the local_xx
values are not in the same order, but we know that both AES key must be the same. Therefore, I realized that the local_xx
values where just unsorted, so we need to sort them to build the AES key. This would be the correct ordering:
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
At this point, we can take those values and switch to Python. Also, notice that the above ordering is in reverse order, because of 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'