During your usual crop field stroll you were abducted by aliens. Luckily you were able to escape their grip and flee to an escape pod, but alas starting it requires a key code. Figure out how this strange mechanism works and return to earth.

Category: reversing

Solver: t0b1

Flag: `HTB{_3st3r31K3yP4d_}`

## Writeup#

In this challenge we get a binary. We start by analyzing it in Ghidra and find the following main function (we already renamed the functions to be more readable).

``````void main(void)
{
int iVar1;
uint input;
puts("Enter which button to press [1-9] and the flag may reveal itself");
do {
input = 0;
while (((int)input < 1 || (9 < (int)input))) {
iVar1 = getchar();
input = iVar1 - 0x30;
}
if (input == 1) { press_1(); }
if (input == 2) { press_2(); }
if (input == 3) { press_3(); }
if (input == 4) { press_4(); }
if (input == 5) { press_5(); }
if (input == 6) { press_6(); }
if (input == 7) { press_7(); }
if (input == 8) { press_8(); }
if (input == 9) { press_9(); }
printf("> %d\n",(ulong)input);
big_fucking_long_if_function();
} while( true );
}
``````

First the `setup_keypad` function is called which resets the corresponding global variables. Then we get to enter a number between `1` and `9` which is stored in the corresponding global variable. Finally the function we named `big_fucking_long_if_function` is called. The function literally deserves this name, as the decompiled function in Ghidra is 1668 lines long and consists of mostly `if` conditions.

Luckily, the first 35 lines of the function are sufficient.

``````int big_fucking_long_if_function(void)
{
bool bVar1;
if ((bit10 == '\0') || (bit0 != '\0')) {
bit10_and_not_bit0 = '\0';
}
else {
bit10_and_not_bit0 = '\x01';
}
if ((bit10_and_not_bit0 == '\0') || (key1_pressed != 0)) {
DAT_003050a1 = '\0';
}
else {
DAT_003050a1 = '\x01';
}
if ((DAT_003050a1 == '\0') || (key2_pressed == 0)) {
DAT_003050a2 = '\0';
}
else {
DAT_003050a2 = '\x01';
}
if (DAT_003050a2 != '\0') {
_xor_key_input = 2;
set_xor_key_to_param(&xor_key_input);
}
if (DAT_003050a2 != '\0') {
decode_flag();
}
// ... a lot of lines coming like this ...
}
``````

The main functionality consists of many checks and variables being set to either `0` or `1` and in some cases, a position in a global array which we indentified as a key is set to a a character between `1` and `9`.

In one case, the `decode_flag` function is called.

``````void decode_flag(void)
{
int local_c;
DAT_0030515a = 0;
printf(">> %s \n");
local_c = 0;
while (local_c < 0x14) {
(&flag_out)[local_c] = PTR_encrypted_flag_00305020[local_c] ^ (&xor_key)[local_c % 10];
local_c = local_c + 1;
}
DAT_00305134 = 0;
puts(&flag_out);
return;
}
``````

It prints the final keycode and decrypts the flag using the XOR operator. Now we know, that this is a simple XOR encryption. We also notice, that the key is only 10 digits long, as the `% 10` is used.

As we know the flag format, we can determine 5 of 10 key digits, by simply reversing the XOR operation. That leaves 5 digits open which results in `10^5` combinations. As the function tells us when the key is correct, we can use it to verify our key and bruteforce the other digits. This is feasible, as its only `10^5` combinations.

To speed things up a little, we run the bruteforce in 10 different threads, each with its own executable.

``````#!/usr/bin/python3
from pwn import *

def test_key(fname, i, ii, iii, iv, v):
r = process(fname)
rl = lambda : r.recvline()
ru = lambda x : r.recvuntil(x)
sla = lambda x,y : r.sendlineafter(x,y)

key = [8, 4, 1, 8, i, ii, iii, iv, v, 2]

payload = '\n'.join([str(k) for k in key])
r.close()

def run_10000(i):
context.log_level = 'error'
for ii in range(10):
print(f'[{i}] {ii}')
for iii in range(10):
for iv in range(10):
for v in range(10):
test_key(fname, i, ii, iii, iv, v)

for i in range(10):
This yields the flag and the keycode `8418946942`. 