r2con-ctf writeup: Egypt

This is a write-up for the Egypt challenge of r2con 2018 CTF.

Are you a master in lockpicking?
Try to open this lock bruteforcing the combination!
Put the flag inside r2con{flag} and check if you got the right flag.

binary: forceme_9f132ba529bfd562380d448f4904c76f

Open the binary with radare2 and list the functions

$ radare2 forceme_9f132ba529bfd562380d448f4904c76f
[0x004004c0]> aaa
[0x004004c0]> e asm.pseudo = 1
[0x004004c0]> e asm.bytes = 0
[0x004004c0]> afl
0x00400047    1 170          fcn.00400047
0x00400438    3 26           sym._init
0x00400470    1 6            sym.imp.putchar
0x00400480    1 6            sym.imp.puts
0x00400490    1 6            sym.imp.strlen
0x004004a0    1 6            sym.imp.__libc_start_main
0x004004b0    1 6            sub.__gmon_start_4b0
0x004004c0    1 41           entry0
0x004004f0    4 50   -> 41   sym.deregister_tm_clones
0x00400530    4 58   -> 55   sym.register_tm_clones
0x00400570    3 28           sym.__do_global_dtors_aux
0x00400590    4 38   -> 35   entry1.init
0x004005b6    1 53           sym.code
0x004005eb    8 62           sym.check
0x00400629    1 117          sym.banner
0x0040069e    1 117          sym.goodboy
0x00400713    9 543          main
0x00400940    4 101          sym.__libc_csu_init
0x004009b0    1 2            sym.__libc_csu_fini
0x004009b4    1 9            sym._fini

We quickly see that we want to end up in sym.goodboy which is called from the main function and prints some ascii art.

[0x004004c0]> axt sym.goodboy
main 0x40091a [CALL] call sym.goodboy
[0x0040069e]> s main
[0x00400713]> pdf

Looking at the main function we see that the password need to be at least of length 9

0x00400762      rdi = rax                                  ; const char *s
0x00400765      sym.imp.strlen ()                          ; size_t strlen(const char *s)
0x0040076a      var = rax - 9                              ; 9
0x0040076e      if (var) goto 0x40092b

And that there are two end paths

0x00400909      if (!var) goto 0x400921
0x0040090b      edi = str.Success__You_Got_it              ; 0x400b05 ; "Success! You Got it!" ; const char *s
0x00400910      sym.imp.puts ()                            ; int puts(const char *s)
0x00400915      eax = 0
0x0040091a      sym.goodboy ()
0x0040091f      goto 0x40092b
0x00400921      edi = str.Try_harder                       ; 0x400b1a ; "Try harder" ; const char *s
0x00400926      sym.imp.puts ()                            ; int puts(const char *s)
0x0040092b      eax = 0
0x00400930
0x00400931      return 0

Address 0x0040091a brings us to a success through the function sym.goodboy while address 0x00400921 (or any below) brings us to failure with a "Try harder".

This is a typical job for angr.

We know:

  • the password needs to be at least 9 bytes long
  • a success path is reachable at 0x0040091a
  • a failure path is reachable at 0x00400921

Finding the password took less than 5s to angr

$ time ./forceme-angr.py
<SimulationManager with 1 found, 19 deadended, 1 avoid>
esilrulez
./forceme-angr.py  3.61s user 0.12s system 99% cpu 3.742 total

Below the angr script used

!/usr/bin/env python2
#
# Egypt - lockpicking - 100 pts
# type: flag
# category: easy
#

import angr
import claripy

path = './forceme_9f132ba529bfd562380d448f4904c76f'
p = angr.Project(path, load_options={'auto_load_libs':False})
state = p.factory.entry_state()

# construct the argument
argv = [p.filename]
size = 9 # password size
pwd = claripy.BVS('sym_arg', 8*size)
argv.append(pwd)

state = p.factory.entry_state(args=argv)
sm = p.factory.simulation_manager(state)
sm.explore(find = 0x0040091a, avoid = 0x00400921)
print sm
if len(sm.found) > 0:
    # we have a valid path to success
    found = sm.found[0]
    result = found.solver.eval(argv[1], cast_to=str)
    print result