Attention all UTCTF players, asper is in great danger, and he needs YOUR help to reverse engineer this binary and figure out the password. To do this, he needs IDA Pro and a couple of breakpoints. To help him, all he needs is your credit card number, the three numbers on the back, and the expiration month and date. But you gotta be quick so that asper can secure the flag, and achieve the epic victory R O Y A L.

This was a 1200 point (tied for highest) reversing challenge. I'm guessing they over-estimated the difficulty as there were 21 separate solves. Also, given the challenge description, it was meant to be solved via breakpoints. However, I mostly used angr.

 

crackme: 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

 

It's not stripped and dynamically linked, so that's pretty helpful already. Running it:

 

./crackme
Please enter the correct password.
>blerg
Incorrect password.
utflag{wrong_password_btw_this_is_not_the_flag_and_if_you_submit_this_i_will_judge_you}

 

So it's your standard, prompt for input and check for correct password. Attempting to ltrace it we get the following:

 

[pid 125078] [0x400a78] ptrace(0, 0, 1, 0 <unfinished ...>
[pid 125078] [0x7f07d2d4393f]      SYS_ptrace(0, 0, 1, 0)                                                                                                                                              = -1
[pid 125078] [0x400a78] <... ptrace resumed> )                                                                                                                                                         = -1
[pid 125078] [0x400a8c] exit(1 <unfinished ...>
[pid 125078] [0x7f07d2d10e06]      SYS_exit_group(1 <no return ...>
[pid 125078] [0xffffffffffffffff] +++ exited (status 1) +++

 

So there's anti-debugging here. Using radare2 to look back at that address we find:

 

╭ (fcn) entry.init1 46
│   entry.init1 ();
│           0x00400a60      55             push rbp
│           0x00400a61      4889e5         mov rbp, rsp
│           0x00400a64      31c0           xor eax, eax
│           0x00400a66      ba01000000     mov edx, 1                  ; void*addr
│           0x00400a6b      89c7           mov edi, eax                ; __ptrace_request request
│           0x00400a6d      89c6           mov esi, eax                ; pid_t pid
│           0x00400a6f      89c1           mov ecx, eax                ; void*data
│           0x00400a71      b000           mov al, 0
│           0x00400a73      e8b8feffff     call sym.imp.ptrace         ;[1] ; long ptrace(__ptrace_request request, pid_t pid, void*addr, void*data)
│           0x00400a78      4883f8ff       cmp rax, 0xffffffffffffffff
│       ╭─< 0x00400a7c      0f850a000000   jne 0x400a8c                ;[2]
│       │   0x00400a82      bf01000000     mov edi, 1                  ; int status
│       │   0x00400a87      e8d4feffff     call sym.imp.exit           ;[3] ; void exit(int status)
│       │   ; CODE XREF from entry.init1 (0x400a7c)
│       ╰─> 0x00400a8c      5d             pop rbp
╰           0x00400a8d      c3             ret
            0x00400a8e      6690           nop

 

At this point, I simply NOP'd out that ptrace call so that it wouldn't keep bothering me. Re-running ltrace:

 

[pid 125199] [0x400b26] setbuf(0x7f175bd3aa00, 0)                                                                                                                                                      = <void>
[pid 125199] [0x400b39] setbuf(0x7f175bd3b760, 0)                                                                                                                                                      = <void>
[pid 125199] [0x400b4f] printf("Please enter the correct password.\n>" <unfinished ...>
[pid 125199] [0x7f175ba5f154]      SYS_write(1, "Please enter the correct password.\n>", 36Please enter the correct password.
>)                                                                                                           = 36
[pid 125199] [0x400b4f] <... printf resumed> )                                                                                                                                                         = 36
[pid 125199] [0x400b78] fgets( <unfinished ...>
[pid 125199] [0x7f175ba5f081]      SYS_read(0blerg
, "b", 1)                                                                                                                                                 = 1
[pid 125199] [0x7f175ba5f081]      SYS_read(0, "l", 1)                                                                                                                                                 = 1
[pid 125199] [0x7f175ba5f081]      SYS_read(0, "e", 1)                                                                                                                                                 = 1
[pid 125199] [0x7f175ba5f081]      SYS_read(0, "r", 1)                                                                                                                                                 = 1
[pid 125199] [0x7f175ba5f081]      SYS_read(0, "g", 1)                                                                                                                                                 = 1
[pid 125199] [0x7f175ba5f081]      SYS_read(0, "\n", 1)                                                                                                                                                = 1
[pid 125199] [0x400b78] <... fgets resumed> "blerg\n", 64, 0x7f175bd3aa00)                                                                                                                             = 0x7ffcf616e520
[pid 125199] [0x400b8b] strlen("blerg\n")                                                                                                                                                              = 6
[pid 125199] [0x400ab4] __cxa_allocate_exception(4, 0, 0x7ffcf616e520, 32 <unfinished ...>
[pid 125199] [0x7f175ba654b9]      SYS_brk(0)                                                                                                                                                          = 0x1251000
[pid 125199] [0x7f175ba654b9]      SYS_brk(0x1272000)                                                                                                                                                  = 0x1272000
[pid 125199] [0x400ab4] <... __cxa_allocate_exception resumed> )                                                                                                                                       = 0x12512e0
[pid 125199] [0x400ad8] __cxa_throw(0x12512e0, 0x601df0, 0, 0 <unfinished ...>
[pid 125199] [0x7f175b73f84e]      SYS_futex(0x7f175bf6f238, 129, 0x7fffffff, 0)                                                                                                                       = 0
[pid 125199] [0x7f175b73f84e]      SYS_futex(0x7f175b72f1a0, 129, 0x7fffffff, 0)                                                                                                                       = 0
[pid 125199] [0x7f175b52894b]      __gxx_personality_v0(1, 1, 0x434c4e47432b2b00, 0x12512c0)                                                                                                           = 6
[pid 125199] [0x7f175b528553]      __gxx_personality_v0(1, 6, 0x434c4e47432b2b00, 0x12512c0)                                                                                                           = 7
[pid 125199] [0x400c4c]      __cxa_begin_catch(0x12512c0, 0x400c35, 1, 1)                                                                                                                              = 0x12512e0
[pid 125199] [0x400c96]      __cxa_end_catch(0, 68, 68, 68)                                                                                                                                            = 0
[pid 125199] [0x400da3]      memcmp(0x602230, 0x7ffcf616e520, 64, 64)                                                                                                                                  = 0xffffffaf
[pid 125199] [0x400dd9]      printf("Incorrect password.\n" <unfinished ...>
[pid 125199] [0x7f175ba5f154]           SYS_write(1, "Incorrect password.\n", 20Incorrect password.
)                                                                                                                      = 20
[pid 125199] [0x400dd9]      <... printf resumed> )                                                                                                                                                    = 20
[pid 125199] [0x400df0]      printf("utflag{wrong_password_btw_this_is_not_the_flag_and_if_you_submit_this_i_will_judge_you}\n" <unfinished ...>
[pid 125199] [0x7f175ba5f154]           SYS_write(1, "utflag{wrong_password_btw_this_is_not_the_flag_and_if_you_submit_this_i_will_judge_you}\n", 88utflag{wrong_password_btw_this_is_not_the_flag_and_if_you_submit_this_i_will_judge_you}
)                                                  = 88
[pid 125199] [0x400df0]      <... printf resumed> )                                                                                                                                                    = 88
[pid 125199] [0x7f175ba33e06]      SYS_exit_group(0 <no return ...>
[pid 125199] [0xffffffffffffffff] +++ exited (status 0) +++

 

So this time around, we can see that it calls fgets, strlen, then runs into an exception and, importantly, catches it. Looking for some more information, it's time to use the shiny new Ghidra decompiler:

 

undefined8 main(undefined4 argc,undefined8 argv)

{
  int iVar1;
  size_t iMyInputLen;
  double dVar2;
  int local_98;
  int local_88;
  byte bufMyInput [45];
  
  setbuf(stdin,(char *)0x0);
  setbuf(stdout,(char *)0x0);
  printf("Please enter the correct password.\n>");
  fgets((char *)bufMyInput,0x40,stdin);
  iMyInputLen = strlen((char *)bufMyInput);
                    /* try { // try from 00400bc1 to 00400bce has its CatchHandler @ 00400c35 */
  dVar2 = (double)divide(0x20,0);
  bufMyInput[(long)(int)dVar2] = bufMyInput[(long)(int)dVar2] ^ 8;
  local_88 = 0;
  while ((ulong)(long)local_88 < iMyInputLen) {
    bufMyInput[(long)local_88] = bufMyInput[(long)local_88] ^ 0x27;
    local_88 = local_88 + 1;
  }
  local_98 = 0;
  while (local_98 < 0xcb) {
    stuff[(long)local_98] = stuff[(long)local_98] - 1 ^ stuff2[(long)(0xca - local_98)];
    local_98 = local_98 + 1;
  }
  (*(code *)stuff)(bufMyInput,iMyInputLen);
  iVar1 = memcmp(test,bufMyInput,0x40);
  if (iVar1 == 0) {
    printf("Correct Password!");
  }
  else {
    printf("Incorrect password.\n");
    printf(
          "utflag{wrong_password_btw_this_is_not_the_flag_and_if_you_submit_this_i_will_judge_you}\n"
          );
  }
  return 0;
}

 

The code is pretty readable. One thing that jumps out is that static call to the function "divide". It's getting called with two static arguments, 0x20 and 0. Let's look at the divide code:

 

/* divide(int, int) */

double divide(int param_1,int param_2)
{
  undefined4 *puVar1;
  
  if (param_2 == 0) {
    puVar1 = (undefined4 *)__cxa_allocate_exception(4);
    *puVar1 = 8;
                    /* WARNING: Subroutine does not return */
    __cxa_throw(puVar1,typeinfo,0,0);
  }
  return (double)(param_1 / param_2);
}

 

So given that there is always a static call to divide with the second argument of 0, divide will always throw an exception. Ghidra was also nice enough to tell us in it's C comments that the exception catcher is at 0x400c35. This is obviously intended to throw people off since you generally don't have your exception handler jump into the middle of your function, but that's what is going on here.

 

At this point I attempted to manually reverse the crypt, starting from the end going forward. It does a bunch of xor's but some point during my reverse process I clearly got off the rails and my algorithm didn't work properly. After deciding to stop attempting to manually reverse it, I wanted to give angr a go.

 

While it's possible angr would be able to execute through from the beginning, that seemed like a tricky endeavor. Instead, I wanted to use angr Symbion but ran into a bug in it which the angr devs are looking into. Then, I tried r2angrdb, which similarly ran into bugs (with their own implementation). Finally, I decided to give angrgdb a shot and it worked as follows:

 

First off, looking back at the decompiled code we can see that a function (named 'stuff') is decrypted in memory and then called. For angr to be able to handle this, it needs to specifically enable support for self modifying code. To do this, we need to open the constructor python source for angrgdb. Open up "angrdbg/context.py" and edit line 30 to add the option "support_selfmodifying_code=True".

 

With that completed, using angr to solve the challenge becomes strait forward and fairly fast.

 

# Break AFTER all that setup and exception catching is done
break *0x400C96
# Start it up
r
# Give it a buffer of 63 "A"s
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
# Tell angr to find the solution and avoid the failures
angrgdb find 0x400DAC
angrgdb avoid 0x400DC8
# Tell angr to mark our 64 bytes as symbolic
angrgdb sim $rbp-0x50 0x40
# Run!
angrgdb run

 

The flag pops out quickly, though 2 bytes specifically are wrong. Easy enough to correct and you get the flag:

 

utflag{1_hav3_1nf0rmat10n_that_w1ll_lead_t0_th3_arr3st_0f_c0pp3rstick6}