{"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
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>\nSo 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>\nFirst 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>\nNow 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>\nNot 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>\nWe 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>\neax = encrypted[rsi++]\r\nrcx = eax\r\neax = encrypted[rsi++]\r\nfor (; rcx > 0; --rcx) {\r\n encrypted[rsi++] ^= eax\r\n}\r\n<\/pre>\nSo 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>\nTurns 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>\nAfter ~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