poison null byte
之前学的时候有点云里雾里,画了一遍图觉得清楚多了(果然美术生就要画图(*σ´∀`)σ)(例题做的真坐牢QAQ)
原理
如果对输入长度检查不严,导致chunk中内容输入多了一个零字节溢出至next chunk的size最低位,改变next chunk的size的大小
主要作用就是改变next chunk的preinuse位触发合并和造成堆块错位
利用
2.26以前
实验代码如下(来自how2heap):
1 |
|
示意图如下:
- 以下步骤是为了控制b的size,为之后c向下合并做准备
- 分割b是为了产生b1伪造一个在unsorted bin中的pre chunk,防止unlink报错。触发c的向下合并,再malloc回来c后我们就能完全控制b2
ps:像是形成一个三明治结构(二三层间得有个缝),然后控制中间的chunk
pps:申请大小要大于fastbin的范围
2.26-2.28
有tcache后注意两点:
- 申请大小要大于tcache的范围
- heap最开始会划分一个0x290的chunk给tcache,因此要先申请一个pad chunk使起始地址0x100对齐
2.28以后
2.29增加了很多检查:
- _int_malloc函数的for(;;)循环中unsorted bin遍历开头增加next chunk的大小合法性检查,在b2 = malloc(0x80)时会报错(这个可以绕过)
1
2
3if (__glibc_unlikely (chunksize_nomask (next) < 2 * SIZE_SZ)
|| __glibc_unlikely (chunksize_nomask (next) > av->system_mem))
malloc_printerr ("malloc(): invalid next size (unsorted)"); - _int_free向下合并时增加pre chunk的size和presize的检查,在触发c的向下合并时会报错因此有了另一种更加复杂的利用方法,实验代码如下(来自how2heap):
1
2if (__glibc_unlikely (chunksize(p) != prevsize))
malloc_printerr ("corrupted size vs. prev_size while consolidating");为了绕开新加的presize的检查,我们就要保证off by null的时候只覆盖preinuse位,即size要保证0x100对齐且不存在chunk堆块的错位。那么下一步我们就需要绕开unlink对于chunk前后连接的的检查1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
int main()
{
setbuf(stdin, NULL);
setbuf(stdout, NULL);
void *tmp = malloc(0x1);
void *heap_base = (void *)((long)tmp & (~0xfff));
size_t size = 0x10000 - ((long)tmp&0xffff) - 0x20;
void *padding= malloc(size);
//布置prev地址对齐0x1000
void *prev = malloc(0x500);
void *victim = malloc(0x4f0);
malloc(0x10);
void *a = malloc(0x4f0);
malloc(0x10);
void *b = malloc(0x510);
malloc(0x10);
free(a);
free(b);
free(prev);
malloc(0x1000);
void *prev2 = malloc(0x500);
assert(prev == prev2);
((long *)prev)[1] = 0x501;
*(long *)(prev + 0x500) = 0x500;
void *b2 = malloc(0x510);
((char*)b2)[0] = '\x10';
((char*)b2)[1] = '\x00'; // b->fd <- fake_chunk
void *a2 = malloc(0x4f0);
free(a2);
free(victim);
void *a3 = malloc(0x4f0);
((char*)a3)[8] = '\x10';
((char*)a3)[9] = '\x00';
void *victim2 = malloc(0x4f0);
((char *)victim2)[-8] = '\x00';
/* VULNERABILITY */
void *merged = malloc(0x100);
memset(merged, 'A', 0x80);
memset(prev2, 'C', 0x80);
assert(strstr(merged, "CCCCCCCCC"));
} - 我们要布置的连接在unsorted bin中的fake chunk就是prev+0x10,那么prev的fd_nextsize和bk_nextsize就是fake chunk的fd和bk。需要fd_nextsize和bk_nextsize有效就得将prev放进large bin
- 以上步骤完成后fake chunk的fd和bk布置完毕,接下来就是通过large bin将chunk地址写入a和b,由于我们之前已经保证了prev的地址对齐0x1000,因此申请回a和b再覆盖两字节就能完成fake chunk的连接
- off by null触发victim的向下合并,这时我们可以通过prev2控制victim2的头部
例题
BalsnCTF 2019-PlainNote(2.29)
高版本的off by null,做的是真坐牢,但收获也不少。没找到2.29的libc所以用的2.31
静态分析
静态分析不难,需要注意的就是add函数中存在off by null漏洞,会在输入最后加一个空字符,在存在漏洞的同时也使泄露地址变得困难
以及本题存在沙箱,需要用orw读取flag
沙箱是白名单形式的,只允许read、write、open和exit的系统调用,程序中所有的输入输出都是通过read和write实现的
输出会用strlen取输出的字节数,而strlen是通过空字符确定长度的,所以不可能通过连带的方式泄露地址
思路
主要流程有四步:
- 泄露libc基址
- 泄露heap地址
- 执行off by null
- 进行orw
执行off by null按照实验代码执行就行,orw用gadget+setcontext模板就行,主要难点在于泄露地址,所以off by null的步骤得进行一定的改进
由于堆的基址会保持页对齐即0x1000对齐,但进行b’\x00\x10’覆盖时会把第四位也覆盖为0,所以只有当我们的prev chunk的地址的第四位为0时才能覆盖成功,也就是说在开启aslr的情况下存在一个1/16的爆破(本地做的时候可以先关闭aslr) - 由于init函数会使heap非常的混乱所以可以先把bin清空
- 首先在最开始堆布局的时候prev上再加两个0x20的chunk,这样off by null之后的堆布局如下所示: 其中prev3用于off by null更改victim的presize和preinuse,prev2用于泄露
- 在off by null结束之后申请一个0x4f0的chunk,这样我们就能通过prev2利用show函数打印fd指针
由于我们需要泄露libc基址和heap两个地址,所以我们要先申请回0x530的chunk,然后释放一个unsorted bin范围的chunk(pad),再释放0x530的chunk,这样prev2的fd指向一个heap地址pad,再申请回pad,prev2的fd就指向unsorted bin - 然后我们可可以从prev2中再切下来一个tcache范围内的chunk,通过打tcache执行orw
- orw时需要注意一点,直接执行open函数时实际调用的syscall是0x101,会被沙箱ban掉 所以要通过rax进行syscall,这里选用的是time函数和timelocal函数中间的sub_D3F40
exp
1 |
|
PlaidCTF 2015-plaiddb(2.19)
低版本的off by null,忘记了有个东西叫二叉树是我的错(^_^),由于是2015年的题所以2.19的libc多少有点离谱了,用的2.23的libc
静态分析
在不需要理会平衡树的具体算法的情况下只需要注意一点,读入key的sub_1040函数中存在off by null漏洞
realloc的大致逻辑是如果堆块能够向上拓展的话就向上拓展,如果不能,就重新分配堆块
思路
由于程序会申请一些0x20和0x40的堆块作为key和chunk,这样会使heap结构变得复杂,所以可以先申请一些备用
这样我们进行利用的就只是用于data的堆块了,由于data的大小是可控的所以这样利用起来更加方便
由于要利用key的off by null,所以可以先释放一个data再占用这个地址的chunk作为key
其余步骤和上一题类似,申请六个堆块,靠近top chunk的chunk用于防合并,首尾两个chunk用于触发unsorted bin向下合并,中间0x70的chunk用于fastbin attack,剩下两个chunk一个用于泄露libc,一个用于off by null
ps:one_gadget都不可用,需要用realloc调整栈内容
exp
1 |
|