Had some spare time during the Christmas holidays to make a couple reversing challenges in the 35C3 CTF.

For now on I will write my write-ups on this site.

Sanity check (1p)

Number of solves: 612.

#!/usr/bin/env python3

import codecs

codecs.encode('35P3_hfr_guvf_gb_REDACTED', 'ROT13')

35C3_use_this_to_ERQNPGRQ

Maybe not worth mentioning… but may be helpful for someone :)

0pack (49p)

Download

Number of solves: 167.

strace ./0pack.elf
# ...
getpid()                                = 98520
open("/proc/98520/maps", O_RDONLY)      = 21
read(21, "560ba2215000", 12)            = 12
close(21)                               = 0
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 1), ...}) = 0
brk(NULL)                               = 0x560ca262e000
brk(0x560ca264f000)                     = 0x560ca264f000
fstat(0, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 1), ...}) = 0
write(1, "Input password: ", 16Input password: )        = 16
read(0, PASSWORD
"PASSWORD\n", 1024)             = 9
write(1, "\n", 1
)                       = 1
write(1, "Awwww \x154\x106\xf0_\x154\x106\xf0\n", 14Awwww ಠ_ಠ
) = 14
exit_group(0)                           = ?
+++ exited with 0 +++

The binary appears to map executable code into memory. I loaded the binary into radare2. The binary does have security mechanisms, but isn’t stripped:

[0x55b9f1fd3a1e] > i ~pic,nx,canary,relro,stripped
canary   true
nx       true
pic      true
relro    partial
stripped false

Continued until system call write at “Input password:” using dcs write, and stepped from libc code to binary code in the rwx memory map.

[0x55b9f1fd3a1e]> dm.
0x000055b9f1fc1000 - 0x000055b9f1fd5000 * usr    80K s rwx /root/Downloads/0pack/0pack.elf /root/Downloads/0pack/0pack.elf ; r12

To analyze I dumped the memory map: dmd dump.bin and imported it into Hopper debugger, as raw binary with base address 0x0 and offset 0x0.

The function at address 0x55b9f1fd3a1e is sub_129a0, starting at address 0x55b9f1fd39a0.

The function sub_12927 is just a anti-debugging method.

Register r15 points to the start of memory map (0x000055b9f1fc1000).

[0x55b9f1fd3a1e]> dr r15
0x000055b9f1fc1000

Each flag byte is calculated at statement, e.g. the first byte: r15 + 0x12475.

int sub_129a0(int arg0, int arg1, int arg2, int arg3) {
    rcx = arg3;
    var_83 = 0x1;
    sym.imp.printf();
    sym.imp.fgets();
    sym.imp.putchar();
    if (((var_80 & 0xff & 0xff) != (*(int8_t *)(r15 + 0x12475) & 0xff)) || (sub_12927(0xa, 0xf, *0x214060, rcx) != 0x0)) {
            var_83 = 0x0;
    }
    if (((var_7F & 0xff & 0xff) != (*(int8_t *)(r15 + 0x124d8) & 0xff)) || (sub_12927(0xa, 0xf, *0x214060, rcx) != 0x0)) {
            var_83 = 0x0;
    }
    if (((var_7E & 0xff & 0xff) != (*(int8_t *)(r15 + 0x1223a) & 0xff)) || (sub_12927(0xa, 0xf, *0x214060, rcx) != 0x0)) {
            var_83 = 0x0;
    }
    if (((var_7D & 0xff & 0xff) != (*(int8_t *)(r15 + 0x1224f) & 0xff)) || (sub_12927(0xa, 0xf, *0x214060, rcx) != 0x0)) {
            var_83 = 0x0;
    }
    if (((var_7C & 0xff & 0xff) != (*(int8_t *)(r15 + 0x12474) & 0xff)) || (sub_12927(0xa, 0xf, *0x214060, rcx) != 0x0)) {
            var_83 = 0x0;
    }
    if (((var_7B & 0xff & 0xff) != (*(int8_t *)(r15 + 0x1224f) & 0xff)) || (sub_12927(0xa, 0xf, *0x214060, rcx) != 0x0)) {
            var_83 = 0x0;
    }
    if (((var_7A & 0xff & 0xff) != (*(int8_t *)(r15 + 0x123a8) & 0xff)) || (sub_12927(0xa, 0xf, *0x214060, rcx) != 0x0)) {
            var_83 = 0x0;
    }
    if (((var_79 & 0xff & 0xff) != (*(int8_t *)(r15 + 0x12475) & 0xff)) || (sub_12927(0xa, 0xf, *0x214060, rcx) != 0x0)) {
            var_83 = 0x0;
    }
    if (((var_78 & 0xff & 0xff) != (*(int8_t *)(r15 + 0x1247a) & 0xff)) || (sub_12927(0xa, 0xf, *0x214060, rcx) != 0x0)) {
            var_83 = 0x0;
    }
    if (((var_77 & 0xff & 0xff) != (*(int8_t *)(r15 + 0x1223a) & 0xff)) || (sub_12927(0xa, 0xf, *0x214060, rcx) != 0x0)) {
            var_83 = 0x0;
    }
    if (((var_76 & 0xff & 0xff) != (*(int8_t *)(r15 + 0x12245) & 0xff)) || (sub_12927(0xa, 0xf, *0x214060, rcx) != 0x0)) {
            var_83 = 0x0;
    }
    if (((var_75 & 0xff & 0xff) != (*(int8_t *)(r15 + 0x124ca) & 0xff)) || (sub_12927(0xa, 0xf, *0x214060, rcx) != 0x0)) {
            var_83 = 0x0;
    }
    if (((var_74 & 0xff & 0xff) != (*(int8_t *)(r15 + 0x12428) & 0xff)) || (sub_12927(0xa, 0xf, *0x214060, rcx) != 0x0)) {
            var_83 = 0x0;
    }
    if (((var_73 & 0xff & 0xff) != (*(int8_t *)(r15 + 0x124d8) & 0xff)) || (sub_12927(0xa, 0xf, *0x214060, rcx) != 0x0)) {
            var_83 = 0x0;
    }
    if (var_83 != 0x0) {
            sym.imp.printf();
    }
    else {
            sym.imp.printf();
    }
    rax = 0x0;
    rcx = *0x28 ^ *0x28;
    if (rcx != 0x0) {
            rax = sub_12750();
    }
    return rax;
}

Solution:

#!/usr/bin/env python3

import sys

f = open("dump.bin", "r")
for o in [0x12475, 0x124d8, 0x1223a, 0x1224f, 0x12474, 0x1224f, 0x123a8, 0x12475, 0x1247a, 0x1223a, 0x12245, 0x124ca, 0x12428, 0x124d8]:
  f.seek(o)
  sys.stdout.write(f.read(1))

35C3_ThisIsATriumph

permute (140p)

Download

Number of solves: 33.

ldd program
linux-gate.so.1 (0xf7f9f000)
libcapstone.so.3 => /lib/i386-linux-gnu/libcapstone.so.3 (0xf7cd1000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7af3000)
/lib/ld-linux.so.2 (0xf7fa1000)

The binary loads shared library libcapstone, most likely to to disassemble and assemble a new, patched version of the binary after a execution.

Lets try.

sha1sum program; ./program; sha1sum program
501191144bf7984825aa1a472bcce4391a1ce3a7  program
Usage: ./program <flag byte>
9fcac879cff4619a969a20f907eff7358b066403  program

As I assumed.

Lets open the binary in Hopper debugger.

The function call at address 0x08049f9f checks the input argument.

0x080489b9 E8E1150000             call       sub_8049f9f ; 0x08049f9f

The XOR operation at 0x0804a013 performs the check of arg1 byte “A”, in the example.

0x0804a013 31D0                   xor        eax, edx

Lets check the registers… eax is correct byte and edx is the input byte.

[0x0804a013]> dr eax,edx
0x00000033
0x00000041

Due to the self-modifying binary, the address of the XOR changes. Below is a r2pipe1 script solving the challenge.

#!/usr/bin/env python3

import r2pipe
import sys
import subprocess

def main(argv):
  while True:
    subprocess.call("cp program /tmp/prev", shell=True)
    r2 = r2pipe.open("./program")
    r2.cmd("doo A; dcu main;")
    r2.cmd("dcu %s" % r2.cmd("/a xor eax,edx"))

    eax = int(r2.cmd("dr eax").split('\n')[0], 16)
    r2.quit()

    if eax == 0:
      break

    sys.stdout.write(chr(eax))
    subprocess.call("cp /tmp/prev program", shell=True)
    subprocess.call("./program %c" % chr(eax), shell=True)

if __name__ == '__main__':
  main(sys.argv)

35C3_tempuemr_temupre

NOTE: Note that using radare2 development branch from github can be buggy if the script does not work…

References
  1. https://radare.gitbooks.io/radare2book/content/scripting/r2pipe.html