코드게이트2017 본선 easycrack writeup
by St1tchChallenge
코드게이트 예선때는 101개의 바이너리를 리버싱해서 키를 찾는 문제가 있었는데 모두 같은 bit 였었고, 비교적 규칙적이여서 간단한 angr코드로도 문제를 풀 수 있었다.
이번 본선에는 300개의 바이너리를 리버싱해서 키를 찾는 문제가 나왔는데, 300개의 바이너리를 보니 32bit,64bit 가 랜덤으로 섞여있었고, 키를 찾는 루틴 전에 몇가지 패턴(socket, argv check 등)이 있었고, 이를 코드패치를 해주지 않으면 절대로 키를 체크하는 루틴에 도달할 수 없는 그러한 바이너리가 랜덤하게 300개 있었다.
Solution
키를 체크하는 함수 직전에 nop + 프로그램 실행 시, 첫번째 인자를 키 체크 함수의 인자로 넣는 어셈블리어를 32bit, 64bit 두 가지 경우로 짠 뒤, main함수 시작부터 키를 체크하는 함수 직전까지 코드패치를 진행하였다.
300개를 일일이 할 수 없기 때문에, 자동으로 해주는 코드를 작성한 뒤, 원래 바이너리를 패치한 후, 그 바이너리에 대해서 angr을 돌리는 방법으로 풀 수 있었다.
패치 전
패치 후
이런식으로 패치를 해주면 angr을 이용해서 첫번째 인자를 통해 key값을 찾을 수 있다.
Code
import angr import claripy import subprocess import re import string from pwn import * check = lambda s : ''.join([ i for i in s if i in string.printable[:-6]]) check_out = lambda cmd : subprocess.check_output(cmd, shell=True) grep = lambda s, pattern : re.findall(r'^.*%s.*?$'%pattern,s,flags=re.M) tmp = ''' push ebp mov ebp, esp mov eax, DWORD PTR [ebp+0xc] add eax, 0x4 mov eax, DWORD PTR [eax] push eax ''' asm32 = asm(tmp, arch='i386', os='linux') tmp = ''' push rbp mov rbp, rsp add rsi, 0x8 mov rdi, QWORD PTR [rsi] ''' asm64 = asm(tmp, arch='amd64', os='linux') for i in range(57, 58): print '--------------------{}-----------------'.format(i) if '32-bit' in check_out('file prob{}'.format(i)): bit = 32 else: bit = 64 fd = open('prob{}'.format(i), 'rb').read() dat = check_out('objdump -S -M intel prob{}'.format(i)) puts = grep(dat, '<puts@plt>')[1].split('\t')[1].replace(' ','') puts_idx = fd.find(puts.decode('hex')) if bit == 32: cmd = 'objdump -S -M intel ./prob{} | grep "libc_start" -B 1 | grep push'.format(i) main = eval(re.findall('0x[\w]{6,8}', check_out(cmd))[0]) start = main - 0x08048000 end = puts_idx - 17 pay = '\x90' * (end - start - len(asm32)) pay += asm32 else: cmd = 'objdump -S -M intel ./prob{} | grep "libc_start" -B 1 | grep mov'.format(i) main = eval(re.findall('0x[\w]{6,8}', check_out(cmd))[0]) start = main - 0x400000 end = puts_idx - 15 pay = '\x90' * (end - start - len(asm64)) pay += asm64 out = fd[:start] out += pay out += fd[end:] prob = 'prob{}_edit'.format(i) open(prob, 'wb').write(out) print '[+] create editted binary' target_ = int(grep(dat, '<puts@plt>')[1].split()[0].strip(':'), 16) print '[*] main = {}, target = {}'.format(hex(main), hex(target_)) avoid_ = target_ + 0x7 p = angr.Project(prob) key_str = claripy.BVS("key_str", 100*8) initial_state = p.factory.entry_state(args=[prob, key_str]) initial_state.libc.buf_symbolic_bytes = 100 + 1 for byte in key_str.chop(8)[:30]: initial_state.add_constraints(byte != '\x00') initial_state.add_constraints(byte >= ' ') initial_state.add_constraints(byte <= '~') pg = p.factory.path_group(initial_state, immutable=False) pg.explore(find=target_, avoid=avoid_) if hasattr(pg, 'found'): print '[+] good!' fs = pg.found[0].state key = fs.se.any_str(key_str) open('out{}'.format(i),'wb').write(check(str(key))) print '[*] key : {}'.format(check(str(key))) else: print '[-] Not Found!' pause()
블로그의 정보
튜기's blogg(st1tch)
St1tch