튜기's blogggg

코드게이트2017 본선 easycrack writeup

by St1tch

Challenge

코드게이트 예선때는 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

활동하기