前提介绍

  • off-by系列不单单只有off-by-oneoff-by-null还有Off-by-TwoOff-by-ManyOff-by-HalfOff-by-Block,但是off-by-oneoff-by-null是最为常见的,off-by-one是溢出一个字节,该字节可以是任意的,而off-by-null也是溢出一个字节,但是它溢出的字节只能是\x00,所以off-by-null其实是off-by-one的一种特殊情况,比off-by-one的利用条件更苛刻。
  • 这里的off-by-one等并不是只用于打堆,其他方面的pwn也可以利用该漏洞,但是打堆时这类漏洞会可以花式利用。可以进行堆布局(堆风水)和进行堆叠。例如,off-by-one,指非预期溢出1个字节,这样就可以修改堆块的标志位和Size的大小。再如off-by-null,非预期的溢出\x00也可以修改堆块的标志位(常用于堆叠)。这种都被称之为堆布局(堆风水)

img

堆叠

  • 堆叠其实就是unlink这一机制,具体的机制我就讲了,已经放在unlink那边讲过了

  • 堆叠(overlap)一般指的就是将两个内存大小比较小的堆块,合并成一个大的堆块。将ptmalloc这一管理机制进行漏洞的理由,对堆进行一定的布局。堆叠有两种叠法,一种是向前合并,另一种是向后合并。

    • 向前合并的逻辑比较多,可以比较好的利用。直接修改下一个堆块的prev_size即可。
    • 而后向合并的逻辑,并不是很好利用,也没有模版化的利用,一般要用perfect fit来绕过。

off-by利用方式

  • off-by有俩种利用方式:

    • 第一种是利用是对unlink机制进行攻击(主要是off-by-null的利用)
    • 第二种是通过堆溢出,然后修改堆中SIZEP标志位,然后导致前面的堆块的use_size能覆盖到后面一个堆块的位置,造成堆叠,我们将通过一些实验来展示堆叠的利用。(主要是off-by-one的利用即)
  • 本篇文章主要是介绍第二种的off-by利用方式(因为第一种的unlink攻击已经在unlink中介绍过了)

堆知识回顾

p标志位

  • p=0 时,表示前一个 chunk 为空闲,prev_size 才有效
  • p=1 时,表示前一个 chunk 正在使用,prev_size 无效 p 主要用于内存块的合并操作;ptmalloc 分配的第一个块总是将 p 设置为 1,以防止程序引用到不存在的区域
  • 放入fast_bin链表和放入tcache_bin链表里面的堆块,与它相邻的高地址堆块的p位也设置为1

image-20241023171912056

chunk空间复用

  • chunk 的大小要 align 到 8bytes。当一个 chunk 处于使用状态时,它的下一个 chunk 的 prev_size 域肯定是无效的。这个域可以被当前 chunk 使用

image-20241023172200758

示例代码

  • 这些实验只是堆叠的方法,具体的利用方法还是要根据题目来利用,而且堆叠只是pwn中的一种技巧,仅仅只用堆叠是没有什么机会getshell的,还需要和其他利用方法结合才能

实验一

  • 在ubuntu16.04glibc2.23中编译如下c程序,并详细描述堆叠的过程。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include<stdio.h>
#include<malloc.h>
#include<stdlib.h>
int main()
{ unsigned char *p1;
long long int *p2,*p3,*p4;
p1 = (char*)malloc(0x30);
p2 = malloc(0x30);
p3 = malloc(0x30);
p4 = malloc(0x30);
p1[0x38] = 0x81;
free(p1);
free(p2);
return 0;
}
// gcc -o lab_1 lab_.c -fno-stack-protector -z execstack -z norelro -fno-builtin

分析1.1

  • 使用gdb进行动态调试,使用ni命令将程序运行到第4个malloc之后的位置

image-20241023174530089

  • 使用heap -v命令查看堆块,发现有5个堆块,且第二个堆块的size位大小为0x41

image-20241023174704443

  • 再使用ni命令,将程序运行到第一个free之前

image-20241023174827602

  • 使用heap -v再次查看堆块,发现此时只有4个堆块,而且第二个堆块的size位已经变成了0x81

image-20241023174857522

  • 使用ni命令将该程序运行到第二个free之后

image-20241023175255535

  • 使用heap -v命令查看堆块,发现被释放的俩个堆块都被放入了fast_bin

image-20241023175322047

  • 使用fastbins命令查看fastbin链表,我们会看到,fastbin中管理俩个不同大小的堆块,一个是0x40大小的(原来申请的),另一个是0x80大小(利用off-by-one堆叠而成的)

image-20241023175415245

分析1.2

  • 按照分析1.1动态调试给出的地址和值,画出堆叠的过程。刚开始堆块的结构如下。

image-20241023180259022

  • 之后我对chunk0进行写入操作,然后造成off-by-one溢出,修改了chunk1的size

image-20241023180503300

  • 所以发生了堆叠,堆叠后的chunk图如下

image-20241023180643786

总结1

  • 我们在Chunk A处触发Off-by-one漏洞,将Chunk Bsize域篡改为Chunk B + Chunk C的大小,然后释放Chunk B,再次取回,我们此时就可以对Chunk C的内容进行任意读写了。

  • 申请三个chunkchunAchunkBchunkC,对chunkA写入造成off-by-one漏洞,修改chunkBsize位,从而造成堆叠

  • 篡改Chunk Bsize域时,需要保证将Chunk C完全包含,否则将无法通过以下所述的验证。

1
2
3
4
5
6
7
// /glibc/glibc-2.23/source/malloc/malloc.c#L3985
/* Or whether the block is actually not marked used. */
if (__glibc_unlikely (!prev_inuse(nextchunk)))
{
errstr = "double free or corruption (!prev)";
goto errout;
}
  • 注意:篡改Chunk Bsize域时,仍要保持prev_issue位为1,以免触发堆块合并。

实验二

  • 在ubuntu16.04glibc2.23中编译如下c程序,并详细查看第二种堆叠的方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include<stdio.h>
#include<malloc.h>
#include<stdlib.h>
int main()
{ unsigned char *p1;
long long int *p2,*p3,*p4,*p5;
p1 = (char*)malloc(0x30);
p2 = malloc(0x30);
p3 = malloc(0x30);
p4 = malloc(0x30);
p1[0x38] = 0x81;
free(p2);
p5 = malloc(0x70);
return 0;
}
// gcc -o lab_2 lab_2.c -fno-stack-protector -z execstack -z norelro -fno-builtin

分析2.1

  • 使用gdb进行动态调试,使用ni命令将程序运行到第4个malloc之后,free之前

image-20241023192042022

  • 使用heap -v命令查看,发现有5个堆块

image-20241023192222389

  • 然后使用ni命令将程序运行到free之后

image-20241023192241259

  • 再使用heap -v查看堆块,发现第二个堆块被释放了

image-20241023192304678

  • 再使用ni命令将程序运行到最后一个malloc之前

image-20241023192421980

  • 再使用heap -v命令查看,发现发生了堆叠

image-20241023192451770

实验三

  • 在ubuntu16.04glibc2.23中编译如下c程序,并详细查看第三种堆叠的方式,该堆叠方式是利用off-by-null漏洞
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include<stdio.h>
#include<malloc.h>
#include<stdlib.h>
int main(void)
{
unsigned char * p1;
long long int* p2,*p3,*p4,*p5;
p2 = malloc(0x90);
p1 = malloc(0x90);
p3 = malloc(0xF0);
p4 = malloc(0x20);
free(p2);
p1[0x90] = 0x40;
p1[0x91] = 0x01;
p1[0x98] = 0x00;
free(p3);
p5 = malloc(0x230);
return 0;
}

分析3.1

例题

off_by_level_1