Understanding the challenge
The challenge comes with a single ELF64 bit binary, which doesn’t seem to do anything:
$ ./erasure
$ file erasure
erasure: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, not stripped
We need to look under the hood to understand what’s happening. The following is the decompiled main
function, using Ghidra:
undefined8 main(EVP_PKEY_CTX *param_1,uchar *param_2,size_t *param_3,uchar *param_4,size_t param_5)
{
int iVar1;
undefined4 extraout_var;
FILE *__s;
size_t sVar2;
int local_c;
iVar1 = decrypt(param_1,param_2,param_3,param_4,param_5);
__s = fopen("flag.txt","w");
if (__s == (FILE *)0x0) {
perror("Couldn\'t open file");
exit(-1);
}
fwrite((char *)CONCAT44(extraout_var,iVar1), 0x1e, 1, __s);
fclose(__s);
sVar2 = strlen((char *)CONCAT44(extraout_var,iVar1));
local_c = (int)sVar2;
while (local_c != 0) {
truncate("flag.txt",(long)(local_c + -1));
local_c = local_c + -1;
}
return 0;
}
Basically the main
function will decrypt something, and write it to the flag.txt
file. The decrypt
function is short and straightforward too:
int decrypt(EVP_PKEY_CTX *ctx,uchar *out,size_t *outlen,uchar *in,size_t inlen)
{
char *pcVar1;
uint local_c;
pcVar1 = strdup(secret);
for (local_c = 0; local_c < 0x1d; local_c = local_c + 1) {
pcVar1[(int)local_c] = pcVar1[(int)local_c] ^ 0x41;
}
return (int)pcVar1;
}
It takes a string of bytes - secret
, and applies a simple XOR-in routine. The secret looks like this:
secret
00402010 09 undefined109h [0]
00402011 15 undefined115h [1]
00402012 03 undefined103h [2]
00402013 3a undefined13Ah [3]
00402014 72 undefined172h [4]
00402015 33 undefined133h [5]
00402016 75 undefined175h [6]
00402017 32 undefined132h [7]
00402018 72 undefined172h [8]
00402019 25 undefined125h [9]
0040201a 1e undefined11Eh [10]
...
Solution
The simplest way to solve this is using a debugger. Set up a breakpoint after the call to decrypt
function, and examine the result. The function will allocate a string for the decrypted text, and return that in RAX:
We can solve this programatically as well, using a simple Python script:
secret = [0x09, 0x15, 0x03, 0x3a, 0x72, 0x33, 0x75, 0x32, 0x72, 0x25, 0x1e, 0x27, 0x33, 0x71, 0x2c, 0x1e, 0x72, 0x39, 0x70, 0x32, 0x35, 0x72, 0x2f, 0x22, 0x72, 0x6f, 0x6f, 0x7e, 0x3c]
print("[*] Secret length: %d (0x%x)" % (len(secret), len(secret)))
decr = "".join([chr(n^0x41) for n in secret])
print("[*] Decrypted text: %s" % decr)
And we get the result straight away:
$ python decr.py
[*] Secret length: 29 (0x1d)
[*] Decrypted text: HTB{3r4s3d_fr0m_3x1st3nc3..?}