2017 SCTF Bad
by St1tchProb
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