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("Embedded Key Pad!");
  puts("Enter which button to press [1-9] and the flag may reveal itself");
  setup_keypad();
  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 *
import threading

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])
    sla('itself\n', payload)
    answer = r.recv()
    if b'>>' in answer:
        print(key, answer)
    r.close()

def run_10000(i):
    fname = f'./keypad{i}'
    context.log_level = 'error'
    print(f'Starting thread {i}')
    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)
        
threads = list()
for i in range(10):
    x = threading.Thread(target=run_10000, args=(i,))
    threads.append(x)
    x.start()
    
for thread in threads:
    thread.join()

print('All finished!')

This yields the flag and the keycode 8418946942.

flag