{"id":13435,"date":"2016-10-02T23:00:08","date_gmt":"2016-10-02T21:00:08","guid":{"rendered":"http:\/\/www.codilime.com\/?p=13435"},"modified":"2016-12-08T16:03:00","modified_gmt":"2016-12-08T15:03:00","slug":"tumctf-2016-zwiebel","status":"publish","type":"post","link":"https:\/\/codisec.com\/tumctf-2016-zwiebel\/","title":{"rendered":"Zwiebel"},"content":{"rendered":"

Points:\u00a050
\nCategory:\u00a0re<\/p>\n

Description<\/h2>\n

I found this onion in my kitchen, may I ask you to dissect it?<\/p>\n

https:\/\/www.youtube.com\/watch?v=LowwCyZHBBk<\/a><\/p>\n

download<\/a><\/p><\/blockquote>\n

Solution<\/h2>\n

The file we downloaded is an ELF 64-bit executable. First, let’s try running it:<\/p>\n

$ .\/zwiebel\r\nInput key: hxp{test}\r\n:(\r\n<\/pre>\n

So our task will be getting the flag out of binary. Next step is running it in gdb. The binary is not stripped, so we can start by looking at the list of functions.<\/p>\n

gdb-peda$ info functions \r\nAll defined functions:\r\n\r\nNon-debugging symbols:\r\n0x00000000004005f8  _init\r\n0x0000000000400630  _exit@plt\r\n0x0000000000400640  puts@plt\r\n0x0000000000400650  mmap@plt\r\n0x0000000000400660  printf@plt\r\n0x0000000000400670  __libc_start_main@plt\r\n0x0000000000400680  fgets@plt\r\n0x0000000000400690  memcpy@plt\r\n0x00000000004006a0  fflush@plt\r\n0x00000000004006b0  ptrace@plt\r\n0x00000000004006d0  _start\r\n0x0000000000400700  deregister_tm_clones\r\n0x0000000000400740  register_tm_clones\r\n0x0000000000400780  __do_global_dtors_aux\r\n0x00000000004007a0  frame_dummy\r\n0x00000000004007d0  __printf\r\n0x0000000000400800  main\r\n0x0000000000400880  __libc_csu_init\r\n0x00000000004008f0  __libc_csu_fini\r\n0x00000000004008f4  _fini\r\n<\/pre>\n

First thing to notice is that ptrace is on the list of functions. Binaries in CTF tasks commonly use this function to detect if they’re being run in debugger. Calling ptrace(PTRACE_TRACEME, …) will return -1 if the process is being run in debugger, so it’s a very simple check to implement. Indeed if we run the binary in gdb it will just print “:(“, without even asking us for password. Fortunately the check is also very easy\u00a0to work around it – just put a breakpoint in ptrace, step out of it and swap the return value to 0 (by overriding register).<\/p>\n

gdb-peda$ b ptrace\r\nfin\r\ngdb-peda$ x\/5i $rip\r\n=> 0x4007e0 <__printf+16>:\ttest   rax,rax\r\n   0x4007e3 <__printf+19>:\tjne    0x4007e9 <__printf+25>\r\n   0x4007e5 <__printf+21>:\txor    eax,eax\r\n   0x4007e7 <__printf+23>:\tpop    rdx\r\n   0x4007e8 <__printf+24>:\tret\r\nset $rax=0\r\nsi\r\nsi\r\n=> 0x4007e5 <__printf+21>:\txor    eax,eax\r\n   0x4007e7 <__printf+23>:\tpop    rdx\r\n   0x4007e8 <__printf+24>:\tret\r\n<\/pre>\n

Now that we have taken care of that let’s take a look at main function:<\/p>\n

gdb-peda$ disas main\r\nDump of assembler code for function main:\r\n   0x0000000000400800 <+0>:\tpush   r15\r\n   0x0000000000400802 <+2>:\tpush   r14\r\n   0x0000000000400804 <+4>:\tpush   rbx\r\n   0x0000000000400805 <+5>:\tmov    edi,0x400907\r\n   0x000000000040080a <+10>:\txor    eax,eax\r\n   0x000000000040080c <+12>:\tcall   0x400660 <printf@plt>\r\n   0x0000000000400811 <+17>:\tmov    rdi,QWORD PTR [rip+0x225788]        # 0x625fa0 <stdout@@GLIBC_2.2.5>\r\n   0x0000000000400818 <+24>:\tcall   0x4006a0 <fflush@plt>\r\n   0x000000000040081d <+29>:\tmov    rdx,QWORD PTR [rip+0x22578c]        # 0x625fb0 <stdin@@GLIBC_2.2.5>\r\n   0x0000000000400824 <+36>:\tmov    r15d,0x601280\r\n   0x000000000040082a <+42>:\tmov    edi,0x601280\r\n   0x000000000040082f <+47>:\tmov    esi,0x90\r\n   0x0000000000400834 <+52>:\tcall   0x400680 <fgets@plt>\r\n   0x0000000000400839 <+57>:\tmov    edi,0x0\r\n   0x000000000040083e <+62>:\tmov    esi,0x24c8d\r\n   0x0000000000400843 <+67>:\tmov    edx,0x7\r\n   0x0000000000400848 <+72>:\tmov    ecx,0x22\r\n   0x000000000040084d <+77>:\tmov    r8d,0xffffffff\r\n   0x0000000000400853 <+83>:\txor    r9d,r9d\r\n   0x0000000000400856 <+86>:\tcall   0x400650 <mmap@plt>\r\n   0x000000000040085b <+91>:\tmov    r14,rax\r\n   0x000000000040085e <+94>:\tmov    esi,0x601310\r\n   0x0000000000400863 <+99>:\tmov    edx,0x24c8d\r\n   0x0000000000400868 <+104>:\tmov    rdi,r14\r\n   0x000000000040086b <+107>:\tcall   0x400690 <memcpy@plt>\r\n   0x0000000000400870 <+112>:\tmov    rbx,r15\r\n   0x0000000000400873 <+115>:\txor    eax,eax\r\n   0x0000000000400875 <+117>:\tcall   r14\r\n   0x0000000000400878 <+120>:\txor    eax,eax\r\n   0x000000000040087a <+122>:\tpop    rbx\r\n   0x000000000040087b <+123>:\tpop    r14\r\n   0x000000000040087d <+125>:\tpop    r15\r\n   0x000000000040087f <+127>:\tret    \r\nEnd of assembler dump.<\/pre>\n

Not much going on in here – it asks for password, reads user input allocates some memory and copies a whole bunch of something (about 150KB) into it. Finally it jumps into whatever it is that it copied.\u00a0Let’s set a breakpoint at jump location and see what’s going on in there.<\/p>\n

gdb-peda$ x\/20i $rip\r\n=> 0x7ffff7fad000:\tlea    esi,[rsi+0x0]\r\n   0x7ffff7fad006:\tlea    esi,[rsi+riz*1+0x0]\r\n   0x7ffff7fad00a:\tlea    esi,[rsi+riz*1+0x0]\r\n   0x7ffff7fad011:\tlea    edi,[rdi+riz*1+0x0]\r\n   0x7ffff7fad018:\tmov    rax,rbx\r\n   0x7ffff7fad01b:\tmov    al,BYTE PTR [rax+0x0]\r\n   0x7ffff7fad01e:\tand    al,0x40\r\n   0x7ffff7fad020:\tje     0x7ffff7fad038\r\n   0x7ffff7fad022:\tlea    rsi,[rip+0x34]        # 0x7ffff7fad05d\r\n   0x7ffff7fad029:\tlods   eax,DWORD PTR ds:[rsi]\r\n   0x7ffff7fad02a:\tmov    rcx,rax\r\n   0x7ffff7fad02d:\tlods   eax,DWORD PTR ds:[rsi]\r\n   0x7ffff7fad02e:\txor    DWORD PTR [rsi],eax\r\n   0x7ffff7fad030:\tadd    rsi,0x4\r\n   0x7ffff7fad034:\tloop   0x7ffff7fad02e\r\n   0x7ffff7fad036:\tjmp    0x7ffff7fad065\r\n   0x7ffff7fad038:\tpush   0xa283a\r\n   0x7ffff7fad03d:\tmov    eax,0x1\r\n   0x7ffff7fad042:\tmov    edi,0x0\r\n   0x7ffff7fad047:\tmov    rsi,rsp\r\ngdb-peda$ x\/5i 0x7ffff7fad065\r\n   0x7ffff7fad065:\tpop    sp\r\n   0x7ffff7fad067:\t(bad)  \r\n   0x7ffff7fad068:\tcmp    al,0xa3\r\n   0x7ffff7fad06a:\tmovabs eax,ds:0x3e9f3e6235abb69e\r\n   0x7ffff7fad073:\t(bad)\r\n<\/pre>\n

We can see that the code takes the first character of our input (rax+0x0), then takes a single bit out of it and checks it value. If the 7th bit is 0 the result of ‘and’ operation will be 0 and the conditional jump in 8th line will take us to a short piece of code that print the sad face (“:(“) and terminates the program. That’s not what we want to happen, so let’s examine the other branch. We can easily pass the check by setting up a breakpoint in the line with\u00a0and al, 0x40\u00a0<\/em>and just changing the value of al register.
\nAfter checking the single bit of our input it runs some loop. Afterwards it performs a jump into\u00a0total garbage. It seems that the loop must be decrypting the code before we jump into it. Let’s see what’s going on in there. The loop translated into pseudocode looks roughly like this:<\/p>\n

eax = encrypted[rsi++]\r\nrcx = eax\r\neax = encrypted[rsi++]\r\nfor (; rcx > 0; --rcx) {\r\n  encrypted[rsi++] ^= eax\r\n}\r\n<\/pre>\n

So basically it reads number of bytes to decrypt and a key and then performs a simple xor using that key. Ok, let’s set a breakpoint in the jump location and see how the code looks after decrypting.<\/p>\n

gdb-peda$ x\/20i $rip\r\n=> 0x7ffff7fad065:\tlea    esi,[rsi+riz*1+0x0]\r\n   0x7ffff7fad069:\tmov    rax,rbx\r\n   0x7ffff7fad06c:\tmov    al,BYTE PTR [rax+0x1d]\r\n   0x7ffff7fad06f:\tand    al,0x2\r\n   0x7ffff7fad071:\tje     0x7ffff7fad089\r\n   0x7ffff7fad073:\tlea    rsi,[rip+0x34]        # 0x7ffff7fad0ae\r\n   0x7ffff7fad07a:\tlods   eax,DWORD PTR ds:[rsi]\r\n   0x7ffff7fad07b:\tmov    rcx,rax\r\n   0x7ffff7fad07e:\tlods   eax,DWORD PTR ds:[rsi]\r\n   0x7ffff7fad07f:\txor    DWORD PTR [rsi],eax\r\n   0x7ffff7fad081:\tadd    rsi,0x4\r\n   0x7ffff7fad085:\tloop   0x7ffff7fad07f\r\n   0x7ffff7fad087:\tjmp    0x7ffff7fad0b6\r\n   0x7ffff7fad089:\tpush   0xa283a\r\n   0x7ffff7fad08e:\tmov    eax,0x1\r\n   0x7ffff7fad093:\tmov    edi,0x0\r\n   0x7ffff7fad098:\tmov    rsi,rsp\r\n   0x7ffff7fad09b:\tmov    edx,0x3\r\n   0x7ffff7fad0a0:\tsyscall \r\n   0x7ffff7fad0a2:\tmov    eax,0x3c\r\n<\/pre>\n

Turns out this chunk\u00a0looks pretty much the same as the previous one. Except it checks a different bit. We can repeat the whole process and get yet another chunk of similar code, checking yet another bit. And so on, probably for at least a few hundred times. Theoretically we could get the whole password that way, but it would take ages to do that manually. Instead I decided to automate the process by writing a python script.<\/p>\n

First I dumped the memory from gdb to a file (dump binary memory packed.dump 0x7ffff7fad000 0x7ffff7fad000+0x24c8d<\/em>). This dump can be deassembled using objdump (objdump -D -b binary -mi386 -Mintel,x86-64 packed.dump > packed.objdump<\/em>). Of course only the already decrypted code will make sense. However, the code is very repetitive and has a clear pattern. For example we just need to look for\u00a0mov \u00a0al,BYTE PTR [rax+0x{offset}]\u00a0<\/em>to see which character is being examined by a given chunk of a code. And we can easily parse the following line to get specific bit.<\/p>\n

So I wrote a python script that runs a loop, parsing a chunk, using the collected data to decrypt the next chunk and calling objdump to deassemble it.<\/p>\n

import os                                                                       \r\nimport struct                                                                   \r\n                                                                                \r\n                                                                                \r\ndef write_data(fout, data, written, write_offset):                              \r\n    skip = write_offset - written                                               \r\n    fout.write(data[skip:])                                                     \r\n    return written + len(data)                                                  \r\n                                                                                \r\n                                                                                \r\ndef decode(fin, fout, start_offset, write_offset):                              \r\n    assert start_offset < write_offset                                          \r\n                                                                                \r\n    written = 0                                                                 \r\n    data = fin.read(start_offset)                                               \r\n    written = write_data(fout, data, written, write_offset)                     \r\n                                                                                \r\n    data = fin.read(4)                                                          \r\n    written = write_data(fout, data, written, write_offset)                     \r\n    data = fin.read(4)                                                          \r\n    key = struct.unpack('@I', data)[0]                                          \r\n    print 'key=', hex(key)                                                      \r\n    written = write_data(fout, data, written, write_offset)                     \r\n                                                                                \r\n    while fin:                                                                  \r\n        data = fin.read(4)                                                      \r\n        try:                                                                    \r\n            x = struct.unpack('@I', data)                                       \r\n        except:                                                                 \r\n            # looks like the remainder of file was less than 4 bytes            \r\n            # we're likely already after the part we wanted to decrypt          \r\n            # so who cares?                                                     \r\n            return                                                              \r\n        data = struct.pack('@I', x[0] ^ key)                                    \r\n        written = write_data(fout, data, written, write_offset)                 \r\n                                                                                \r\ndef decode_next(fin, objdumped):                                                \r\n    STATE_NONE = 1                                                              \r\n    STATE_READ_BIT = 2                                                          \r\n    STATE_READ_VALUE = 3                                                        \r\n    STATE_GOT_DATA = 4                                                          \r\n    STATE_READ_JMP = 5                                                          \r\n    STATE_GOT_DECODE = 6                                                        \r\n                                                                                \r\n    next_decode_start = None                                                    \r\n    next_jump = None                                                            \r\n    char_index = None                                                           \r\n    char_bit = None                                                             \r\n    expected_value = None                                                       \r\n                                                                                \r\n    state = STATE_NONE                                                          \r\n    reading_header = True                                                       \r\n                                                                                \r\n    for l in objdumped:                                                         \r\n        if reading_header:                                                      \r\n            if '' in l:                                                  \r\n                reading_header = False                                          \r\n            continue                                                            \r\n        parts = l.split()                                                       \r\n                                                                                \r\n        if state == STATE_NONE:                                                 \r\n            if parts[-2] == 'PTR' and parts[-3] == 'al,BYTE':                   \r\n                char_index = int(parts[-1][5:-1], 16)                           \r\n                state = STATE_READ_BIT                                          \r\n                                                                                \r\n        elif state == STATE_READ_BIT:                                           \r\n            assert parts[-2] == 'and'                                           \r\n            char_bit = int(parts[-1][3:], 16)                                   \r\n            state = STATE_READ_VALUE                                            \r\n                                                                                \r\n        elif state == STATE_READ_VALUE:                                         \r\n            if parts[-2] == 'je':                                               \r\n                expected_value = 1                                              it\r\n            elif parts[-2] == 'jne':                                            \r\n                expected_value = 0                                              \r\n            else:                                                               \r\n                raise ValueError(\"STATE_READ_VALUE failed\")                     \r\n            state = STATE_GOT_DATA                                              \r\n                                                                                \r\n        elif state == STATE_GOT_DATA:                                           \r\n            if parts[-2] == '#':                                                \r\n                next_decode_start = int(parts[-1], 16)                          \r\n                state = STATE_GOT_DECODE\r\n                                                                                \r\n        elif state == STATE_GOT_DECODE:                                         \r\n            if parts[-2] == 'loop':                                             \r\n                state = STATE_READ_JMP                                          \r\n                                                                                \r\n        elif state == STATE_READ_JMP:                                           \r\n            assert parts[-2] == 'jmp'                                           \r\n            next_jump = int(parts[-1], 16)                                      \r\n            break                                                               \r\n                                                                                \r\n    return char_index, char_bit, expected_value, next_jump, next_decode_start\r\n                                                                                \r\ndef get_flag(data):                                                             \r\n    result = [0] * 100                                                          \r\n    for byte, bit, val in data:                                                 \r\n        if val:                                                                 \r\n            result[byte] |= bit                                                 \r\n    return [chr(x) for x in result]                                             \r\n                                                                                \r\nif __name__ == '__main__':                                                      \r\n    fin = open('packed.dump', 'r')                                              \r\n    objfile = 'code.objdump'                                                    \r\n    total_offset = 0                                                            \r\n                                                                                \r\n    info = []                                                                   \r\n                                                                                \r\n    try:                                                                        \r\n        for index in xrange(10000):                                             \r\n            fin.seek(0)                                                         \r\n            outfile = 'haxed_{}'.format(index)                                  \r\n                                                                                \r\n            objdumped = open(objfile, 'r')                                      \r\n            fout = open(outfile, 'w')                                           \r\n                                                                                \r\n            result = decode_next(fin, objdumped)                                \r\n            if any(x is None for x in result):                                  \r\n                raise ValueError(\"Failed to parse some data in decode_next\")    \r\n            print [hex(x) for x in result]                                      \r\n            info.append(result[:3])                                             \r\n                                                                                \r\n            decode(fin, fout, total_offset + result[4], total_offset + result[3])\r\n            total_offset += result[3]                                           \r\n                                                                                \r\n            objdumped.close()                                                   \r\n            fout.close()                                                        \r\n                                                                                \r\n            objfile = 'objdump_{}'.format(index)                                \r\n            os.system('objdump -D -b binary -mi386 -Mintel,x86-64 {} > {}'.format(outfile, objfile))\r\n            infile = outfile                                                    \r\n    finally:                                                                    \r\n        print info                                                              \r\n        print get_flag(info)                                                    \r\n                                                                                \r\n    fin.close()\r\n<\/pre>\n

After ~1600 loops the script produced the flag: hxp{1_h0p3_y0u_d1dnt_p33l_th3_0ni0n_by_h4nd}<\/code>. Very appropriate \ud83d\ude42
\nThis was a very cool task, big thanks to organisers\u00a0for preparing it.<\/p>\n","protected":false},"excerpt":{"rendered":"

Points:\u00a050 Category:\u00a0re Description I found this onion in my kitchen, may I ask you to dissect it? https:\/\/www.youtube.com\/watch?v=LowwCyZHBBk download Solution The file we downloaded is an ELF 64-bit executable. First, let’s try running it: $ .\/zwiebel Input key: hxp{test} \ud83d\ude41…<\/span> <\/p>\n

Read more ›<\/div>\n

<\/a><\/p>\n","protected":false},"author":5,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[3,11],"tags":[10],"yoast_head":"\n\n\n\n\n\n\n\n\n\n\n\n\t\n