코드게이트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