튜기's blogggg

2017 SCTF Bad

by St1tch

Prob

Category : Defense
Score : 300 

Challenge


- 이 문제는 취약점을 찾아서 해당 취약점을 코드패치한 뒤, 서버로 바이너리를 보내주면 되는 문제이다. 

Solution


- Stage3까지 있으며, stage1은 취약점이 한 개이고, stage가 증가할때마다 취약점이 한 개씩 늘어난다. 여기서 각 취약점마다 2byte만 수정해서 취약하지 않게 패치하는 것이 문제이다. 각 Stage는 30단계까지 있었다. 취약점은 IDA를 이용해서 찾았고, 코드패치를 할 때는 objdump를 이용해서 정확한 포인트를 찾아서 해당부분을 패치해서 보내주었다.

Stage1 vulnerability



- 바이너리를 보면 read를 통해 받는 값을 ebp-8C에 저장하는데, 입력은 0xC4만큼 받을 수 있기 때문에, overflow가 발생한다. 

Stage2 vulnerability



- 두 번째 취약점또한 ebp-112에 저장이 되는데 길이를 128만큼 받고 있다. 따라서 이 부분에서 overflow가 발생한다. 

Stage3 vulnerability



- 세 번째 취약점은 create_file함수에서는 힙영역에 크기가 332만큼 할당을 하는데, modify_file함수에서 수정을 할 때는 이 크기보다 큰 516바이트를 쓸 수 있기 때문에, 이 부분에서 overflow가 발생한다. 

- 위 세 개의 취약점은 매번 바이너리를 받을때마다 패치해야 할 값은 달라지지만, 다른 루틴은 전부 그대로이기 때문에, objdump로 정확한 포인트만 찾으면 비교적 간단하게 패치를 할 수 있다. 

Solve


from pwn import *
import subprocess

def execmd(cmd):
    return subprocess.check_output(cmd, shell=True)

def recv_binary():
    dat = s.recvuntil('Send',  drop=True).split()[-1]
    open('problem.bin', 'wb').write(dat.decode('base64'))

def stage1():
    recv_binary()
    file_dat = open('problem.bin', 'rb').read()

    cmd_out = execmd('objdump -S problem.bin -M intel | grep get_int -A 10 | grep "push   0x[0-9a-f]"')
    target_opcode = (''.join(re.findall('[0-9a-f]{2} ', cmd_out.split('\n')[0])).replace(' ', '').decode('hex'))
    idx = file_dat.index(target_opcode)

    cmd_out = execmd('objdump -S problem.bin -M intel | grep get_int -A 10 | grep "lea"')
    target_size = eval(re.findall('0x[a-f0-9]{2}', cmd_out)[0])

    if len(target_opcode) == 5:
        new_file = file_dat[:idx+1] + p16(target_size-0xc) + '\x00\x00' + file_dat[idx+len(target_opcode):]
    else:
        new_file = file_dat[:idx+1] + p8(target_size-0xc) + file_dat[idx+len(target_opcode):]

    open('problem_fixed.bin', 'wb').write(new_file)
    return base64.b64encode(new_file)

def stage2():
    tmp = stage1()
    file_dat = open('problem_fixed.bin','rb').read()

    target_addr = execmd('objdump -S problem_fixed.bin -M intel | grep read_len').split('\n')[-4].split(':')[0].strip()
    cmd_out = execmd('objdump -S problem_fixed.bin -M intel | grep {} -B 3'.format(target_addr)).split('\n')
    target_opcode = (''.join(re.findall('[0-9a-f]{2} ', cmd_out[0])).replace(' ', '').decode('hex'))
    target_size = eval(re.findall('0x[a-f0-9]{2,3}', cmd_out[1])[0])
    idx = file_dat.index(target_opcode)

    if len(target_opcode) == 5:
        new_file = file_dat[:idx+1] + p16(target_size - 0xc) + '\x00\x00' + file_dat[idx+len(target_opcode):]
    else:
        new_file = file_dat[:idx+1] + p8(target_size - 0xc) + file_dat[idx+len(target_opcode):]

    open('problem_fixed2.bin','wb').write(new_file)
    return base64.b64encode(new_file)

def stage3():
    tmp = stage2()
    file_dat = open('problem_fixed2.bin','rb').read()
    cmd_out = execmd('objdump -S problem_fixed2.bin -M intel | grep read_len -B 3 | grep push').split('\n')
    target_size = eval(cmd_out[4].split()[-1])
    target_opcode = (''.join(re.findall('[0-9a-f]{2} ', cmd_out[6])).replace(' ', '').decode('hex'))
    idx = file_dat.index(target_opcode)

    if len(target_opcode) == 5:
        new_file = file_dat[:idx+1] + p16(target_size) + '\x00\x00' + file_dat[idx+len(target_opcode):]
    else:
        new_file = file_dat[:idx+1] + p8(target_size) + file_dat[idx+len(target_opcode):]

    open('problem_fixed3.bin','wb').write(new_file)
    return base64.b64encode(new_file)

def solver():
    p = log.progress('start...')
    try:
        for _ in range(30):
            p.status('stage 1_{}'.format(_+1))
            s.sendline(stage1())
        log.info('stage1 clear!')
        for _ in range(30):
            p.status('stage 2_{}'.format(_+1))
            s.sendline(stage2())
        log.info('stage2 clear!')
        for _ in range(30):
            p.status('stage 3_{}'.format(_+1))
            s.sendline(stage3())
        log.info('stage3 clear!')
        log.success('get flag!')
        s.interactive()
    except:
        s.interactive()

if __name__ == '__main__' :
    s = remote('bad4.eatpwnnosleep.com', 8888)
    solver()

Result

블로그의 정보

튜기's blogg(st1tch)

St1tch

활동하기