mirror of
https://github.com/nganhkhoa/CTF-All-In-One.git
synced 2024-12-24 19:21:15 +07:00
fix
This commit is contained in:
parent
18fd112f8f
commit
39dff718ef
@ -18,9 +18,32 @@ $ git checkout --track -b local_glibc-2.23 origin/release/2.23/master
|
|||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## malloc
|
## malloc.c
|
||||||
下面我们先分析 glibc 2.23 版本的源码,它是 Ubuntu16.04 的默认版本,在 pwn 中也最常见。然后,我们再探讨新版本的 glibc 中所加入的漏洞缓解机制。
|
下面我们先分析 glibc 2.23 版本的源码,它是 Ubuntu16.04 的默认版本,在 pwn 中也最常见。然后,我们再探讨新版本的 glibc 中所加入的漏洞缓解机制。
|
||||||
|
|
||||||
|
## 相关结构
|
||||||
|
#### 堆块结构
|
||||||
|
- Allocated Chunk
|
||||||
|
- Free Chunk
|
||||||
|
- Top Chunk
|
||||||
|
|
||||||
|
#### Bins 结构
|
||||||
|
- Fast Bins
|
||||||
|
- Small Bins
|
||||||
|
- Large Bins
|
||||||
|
- Unsorted Bins
|
||||||
|
|
||||||
|
#### Arena 结构
|
||||||
|
|
||||||
|
## 分配函数
|
||||||
|
`_int_malloc()`
|
||||||
|
|
||||||
|
## 释放函数
|
||||||
|
`_int_free()`
|
||||||
|
|
||||||
|
## 重分配函数
|
||||||
|
`_int_realloc()`
|
||||||
|
|
||||||
|
|
||||||
## 参考资料
|
## 参考资料
|
||||||
- [The GNU C Library (glibc)](https://www.gnu.org/software/libc/)
|
- [The GNU C Library (glibc)](https://www.gnu.org/software/libc/)
|
||||||
|
@ -15,6 +15,8 @@
|
|||||||
|
|
||||||
更详细的我们已经在章节 1.5.8 中介绍了,章节 1.5.7 中也有相关内容,请回顾一下。
|
更详细的我们已经在章节 1.5.8 中介绍了,章节 1.5.7 中也有相关内容,请回顾一下。
|
||||||
|
|
||||||
|
对堆利用来说,不用于栈上的溢出能够直接覆盖函数的返回地址从而控制 EIP,只能通过间接手段来劫持程序控制流。
|
||||||
|
|
||||||
|
|
||||||
## how2heap
|
## how2heap
|
||||||
how2heap 是由 shellphish 团队制作的堆利用教程,介绍了多种堆利用技术,这篇文章我们就通过这个教程来学习。推荐使用 Ubuntu 16.04 64位系统环境,glibc 版本如下:
|
how2heap 是由 shellphish 团队制作的堆利用教程,介绍了多种堆利用技术,这篇文章我们就通过这个教程来学习。推荐使用 Ubuntu 16.04 64位系统环境,glibc 版本如下:
|
||||||
@ -208,6 +210,7 @@ libc-2.23 中对 double-free 的检查过程如下:
|
|||||||
goto errout;
|
goto errout;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
它在检查 fast bin 的 double-free 时只是检查了第一个块。所以其实是存在缺陷的。
|
||||||
|
|
||||||
三个 malloc 之后:
|
三个 malloc 之后:
|
||||||
```
|
```
|
||||||
@ -251,7 +254,7 @@ gef➤ heap bins fast
|
|||||||
[ Fastbins for arena 0x7ffff7dd1b20 ]
|
[ Fastbins for arena 0x7ffff7dd1b20 ]
|
||||||
Fastbins[idx=0, size=0x10] ← Chunk(addr=0x602030, size=0x20, flags=PREV_INUSE) ← Chunk(addr=0x602010, size=0x20, flags=PREV_INUSE)
|
Fastbins[idx=0, size=0x10] ← Chunk(addr=0x602030, size=0x20, flags=PREV_INUSE) ← Chunk(addr=0x602010, size=0x20, flags=PREV_INUSE)
|
||||||
```
|
```
|
||||||
第三个 free 之后,chunk a 再次被添加到 fastbins 中:
|
此时由于 chunk a 处于 bin 中第 2 块的位置,不会被 double-free 的检查机制检查出来。所以第三个 free 之后,chunk a 再次被添加到 fastbins 中:
|
||||||
```
|
```
|
||||||
gef➤ x/15gx 0x602010-0x10
|
gef➤ x/15gx 0x602010-0x10
|
||||||
0x602000: 0x0000000000000000 0x0000000000000021 <-- chunk a [be freed again]
|
0x602000: 0x0000000000000000 0x0000000000000021 <-- chunk a [be freed again]
|
||||||
@ -266,6 +269,8 @@ gef➤ heap bins fast
|
|||||||
[ Fastbins for arena 0x7ffff7dd1b20 ]
|
[ Fastbins for arena 0x7ffff7dd1b20 ]
|
||||||
Fastbins[idx=0, size=0x10] ← Chunk(addr=0x602010, size=0x20, flags=PREV_INUSE) ← Chunk(addr=0x602030, size=0x20, flags=PREV_INUSE) ← Chunk(addr=0x602010, size=0x20, flags=PREV_INUSE) → [loop detected]
|
Fastbins[idx=0, size=0x10] ← Chunk(addr=0x602010, size=0x20, flags=PREV_INUSE) ← Chunk(addr=0x602030, size=0x20, flags=PREV_INUSE) ← Chunk(addr=0x602010, size=0x20, flags=PREV_INUSE) → [loop detected]
|
||||||
```
|
```
|
||||||
|
此时 chunk a 和 chunk b 似乎形成了一个环。
|
||||||
|
|
||||||
再三个 malloc 之后:
|
再三个 malloc 之后:
|
||||||
```
|
```
|
||||||
gef➤ x/15gx 0x602010-0x10
|
gef➤ x/15gx 0x602010-0x10
|
||||||
@ -398,7 +403,31 @@ gef➤ heap bins fast
|
|||||||
[ Fastbins for arena 0x7ffff7dd1b20 ]
|
[ Fastbins for arena 0x7ffff7dd1b20 ]
|
||||||
Fastbins[idx=0, size=0x10] ← Chunk(addr=0x602010, size=0x20, flags=PREV_INUSE) ← Chunk(addr=0x602030, size=0x20, flags=PREV_INUSE) ← Chunk(addr=0x602010, size=0x20, flags=PREV_INUSE) → [loop detected]
|
Fastbins[idx=0, size=0x10] ← Chunk(addr=0x602010, size=0x20, flags=PREV_INUSE) ← Chunk(addr=0x602030, size=0x20, flags=PREV_INUSE) ← Chunk(addr=0x602010, size=0x20, flags=PREV_INUSE) → [loop detected]
|
||||||
```
|
```
|
||||||
这一次 malloc 之后,我们不再填充无意义的 "DDDDDDDD",而是填充一个地址,即栈地址减去 0x8,从而在栈上伪造出一个 free 的 chunk(当然也可以是其他的地址):
|
这一次 malloc 之后,我们不再填充无意义的 "DDDDDDDD",而是填充一个地址,即栈地址减去 0x8,从而在栈上伪造出一个 free 的 chunk(当然也可以是其他的地址)。这也是为什么 `stack_var` 被我们设置为 `0x21`(或`0x20`都可以),其实是为了在栈地址减去 0x8 的时候作为 fake chunk 的 size 字段。
|
||||||
|
|
||||||
|
glibc 在执行分配操作时,若块的大小符合 fast bin,则会在对应的 bin 中寻找合适的块,此时 glibc 将根据候选块的 size 字段计算出 fastbin 索引,然后与对应 bin 在 fastbin 中的索引进行比较,如果二者不匹配,则说明块的 size 字段遭到破坏。所以需要 fake chunk 的 size 字段被设置为正确的值。
|
||||||
|
```c
|
||||||
|
/* offset 2 to use otherwise unindexable first 2 bins */
|
||||||
|
#define fastbin_index(sz) \
|
||||||
|
((((unsigned int) (sz)) >> (SIZE_SZ == 8 ? 4 : 3)) - 2)
|
||||||
|
|
||||||
|
if ((unsigned long) (nb) <= (unsigned long) (get_max_fast ()))
|
||||||
|
{
|
||||||
|
idx = fastbin_index (nb);
|
||||||
|
[...]
|
||||||
|
|
||||||
|
if (victim != 0)
|
||||||
|
{
|
||||||
|
if (__builtin_expect (fastbin_index (chunksize (victim)) != idx, 0))
|
||||||
|
{
|
||||||
|
errstr = "malloc(): memory corruption (fast)";
|
||||||
|
[...]
|
||||||
|
}
|
||||||
|
[...]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
简单地说就是 fake chunk 的 size 与 double-free 的 chunk 的 size 相同即可。
|
||||||
```
|
```
|
||||||
gef➤ x/15gx 0x602010-0x10
|
gef➤ x/15gx 0x602010-0x10
|
||||||
0x602000: 0x0000000000000000 0x0000000000000021 <-- chunk d
|
0x602000: 0x0000000000000000 0x0000000000000021 <-- chunk d
|
||||||
@ -543,6 +572,7 @@ Ubuntu16.04 使用 libc-2.23,其中 unlink 实现的代码如下,其中有
|
|||||||
} \
|
} \
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
在解链操作之前,针对堆块 P 自身的 fd 和 bk 检查了链表的完整性,即判断堆块 P 的前一块 fd 的指针是否指向 P,以及后一块 bk 的指针是否指向 P。
|
||||||
|
|
||||||
malloc\_size 设置为 0x80,可以分配 small chunk,然后定义 header_size 为 2。申请两块空间,全局指针 `chunk0_ptr` 指向 chunk0,局部指针 `chunk1_ptr` 指向 chunk1:
|
malloc\_size 设置为 0x80,可以分配 small chunk,然后定义 header_size 为 2。申请两块空间,全局指针 `chunk0_ptr` 指向 chunk0,局部指针 `chunk1_ptr` 指向 chunk1:
|
||||||
```
|
```
|
||||||
@ -601,11 +631,11 @@ gef➤ x/40gx 0x602010-0x10
|
|||||||
0x602120: 0x0000000000000000 0x0000000000020ee1 <-- top chunk
|
0x602120: 0x0000000000000000 0x0000000000020ee1 <-- top chunk
|
||||||
0x602130: 0x0000000000000000 0x0000000000000000
|
0x602130: 0x0000000000000000 0x0000000000000000
|
||||||
gef➤ x/5gx 0x601058
|
gef➤ x/5gx 0x601058
|
||||||
0x601058: 0x0000000000000000 0x00007ffff7dd2540 <-- fake chunk
|
0x601058: 0x0000000000000000 0x00007ffff7dd2540 <-- fake chunk FD
|
||||||
0x601068: 0x0000000000000000 0x0000000000602010 <-- bk pointer
|
0x601068: 0x0000000000000000 0x0000000000602010 <-- bk pointer
|
||||||
0x601078: 0x0000000000000000
|
0x601078: 0x0000000000000000
|
||||||
gef➤ x/5gx 0x601060
|
gef➤ x/5gx 0x601060
|
||||||
0x601060: 0x00007ffff7dd2540 0x0000000000000000 <-- fake chunk
|
0x601060: 0x00007ffff7dd2540 0x0000000000000000 <-- fake chunk BK
|
||||||
0x601070: 0x0000000000602010 0x0000000000000000 <-- fd pointer
|
0x601070: 0x0000000000602010 0x0000000000000000 <-- fd pointer
|
||||||
0x601080: 0x0000000000000000
|
0x601080: 0x0000000000000000
|
||||||
```
|
```
|
||||||
@ -618,7 +648,21 @@ BK = P->bk;
|
|||||||
FD->bk = BK
|
FD->bk = BK
|
||||||
BK->fd = FD
|
BK->fd = FD
|
||||||
```
|
```
|
||||||
再说简单一点,由于这时候 P->fd->bk 和 P->bk->fd 都指向 P,所以最后的结果为:
|
根据 fd 和 bk 指针在 malloc_chunk 结构体中的位置,这段代码等价于:
|
||||||
|
```
|
||||||
|
FD = P->fd = &P - 24
|
||||||
|
BK = P->bk = &P - 16
|
||||||
|
FD->bk = *(&P - 24 + 24) = P
|
||||||
|
FD->fd = *(&P - 16 + 16) = P
|
||||||
|
```
|
||||||
|
这样就通过了 unlink 的检查,最终效果为:
|
||||||
|
```
|
||||||
|
FD->bk = P = BK = &P - 16
|
||||||
|
BK->fd = P = FD = &P - 24
|
||||||
|
```
|
||||||
|
原本指向堆上 fake chunk 的指针 P 指向了自身地址减 24 的位置,这就意味着如果程序功能允许堆 P 进行写入,就能改写 P 指针自身的地址,从而造成任意内存写入。若允许堆 P 进行读取,则会造成信息泄漏。
|
||||||
|
|
||||||
|
在这个例子中,由于 P->fd->bk 和 P->bk->fd 都指向 P,所以最后的结果为:
|
||||||
```
|
```
|
||||||
chunk0_ptr = P = P->fd
|
chunk0_ptr = P = P->fd
|
||||||
```
|
```
|
||||||
@ -646,11 +690,11 @@ gef➤ x/40gx 0x602010-0x10
|
|||||||
0x602120: 0x0000000000000000 0x0000000000020ee1 <-- top chunk
|
0x602120: 0x0000000000000000 0x0000000000020ee1 <-- top chunk
|
||||||
0x602130: 0x0000000000000000 0x0000000000000000
|
0x602130: 0x0000000000000000 0x0000000000000000
|
||||||
gef➤ x/5gx 0x601058
|
gef➤ x/5gx 0x601058
|
||||||
0x601058: 0x0000000000000000 0x00007ffff7dd2540 <-- fake chunk
|
0x601058: 0x0000000000000000 0x00007ffff7dd2540 <-- fake chunk FD
|
||||||
0x601068: 0x0000000000000000 0x0000000000601058 <-- bk pointer
|
0x601068: 0x0000000000000000 0x0000000000601058 <-- bk pointer
|
||||||
0x601078: 0x0000000000000000
|
0x601078: 0x0000000000000000
|
||||||
gef➤ x/5gx 0x601060
|
gef➤ x/5gx 0x601060
|
||||||
0x601060: 0x00007ffff7dd2540 0x0000000000000000 <-- fake chunk
|
0x601060: 0x00007ffff7dd2540 0x0000000000000000 <-- fake chunk BK
|
||||||
0x601070: 0x0000000000601058 0x0000000000000000 <-- fd pointer
|
0x601070: 0x0000000000601058 0x0000000000000000 <-- fd pointer
|
||||||
0x601080: 0x0000000000000000
|
0x601080: 0x0000000000000000
|
||||||
gef➤ x/gx chunk0_ptr
|
gef➤ x/gx chunk0_ptr
|
||||||
@ -844,7 +888,7 @@ Freeing the overwritten pointer.
|
|||||||
Now the next malloc will return the region of our fake chunk at 0x7ffc782dae00, which will be 0x7ffc782dae10!
|
Now the next malloc will return the region of our fake chunk at 0x7ffc782dae00, which will be 0x7ffc782dae10!
|
||||||
malloc(0x10): 0x7ffc782dae10
|
malloc(0x10): 0x7ffc782dae10
|
||||||
```
|
```
|
||||||
house-of-spirit 是一种 fastbins 攻击方法,通过构造 fake chunk,然后将其 free 掉,就可以在下一次 malloc 时返回 fake chunk 的地址,即任意我们可控的区域。house-of-spirit 是一种可以同时控制栈和堆溢出的方法。利用的第一步不是去控制一个 chunk,而是控制传给 free 函数的指针,将其指向一个被 fake chunk。所以 fake chunk 的伪造是关键。
|
house-of-spirit 是一种 fastbins 攻击方法,通过构造 fake chunk,然后将其 free 掉,就可以在下一次 malloc 时返回 fake chunk 的地址,即任意我们可控的区域。house-of-spirit 是一种通过堆的 fast bin 机制来辅助栈溢出的方法,一般的栈溢出漏洞的利用都希望能够覆盖函数的返回地址以控制 EIP 来劫持控制流,但如果栈溢出的长度无法覆盖返回地址,同时却可以覆盖栈上的一个即将被 free 的堆指针,此时可以将这个指针改写为栈上的地址并在相应位置构造一个 fast bin 块的元数据,接着在 free 操作时,这个栈上的堆块被放到 fast bin 中,下一次 malloc 对应的大小时,由于 fast bin 的先进后出机制,这个栈上的堆块被返回给用户,再次写入时就科恩给你造成返回地址的改写。所以利用的第一步不是去控制一个 chunk,而是控制传给 free 函数的指针,将其指向一个 fake chunk。所以 fake chunk 的伪造是关键。
|
||||||
|
|
||||||
首先 malloc(1) 用于初始化内存环境,然后在 fake chunk 区域伪造出两个 chunk。另外正如上面所说的,需要一个传递给 free 函数的可以被修改的指针,无论是通过栈溢出还是其它什么方式:
|
首先 malloc(1) 用于初始化内存环境,然后在 fake chunk 区域伪造出两个 chunk。另外正如上面所说的,需要一个传递给 free 函数的可以被修改的指针,无论是通过栈溢出还是其它什么方式:
|
||||||
```
|
```
|
||||||
@ -982,6 +1026,8 @@ gef➤ x/gx &b
|
|||||||
```
|
```
|
||||||
所以 house-of-spirit 的主要目的是,当我们伪造的 fake chunk 内部存在不可控区域时,运用这一技术可以将这片区域变成可控的。上面为了方便观察,在 fake chunk 里填充一些字母,但在现实中这些位置很可能是不可控的,而 house-of-spirit 也正是以此为目的而出现的。
|
所以 house-of-spirit 的主要目的是,当我们伪造的 fake chunk 内部存在不可控区域时,运用这一技术可以将这片区域变成可控的。上面为了方便观察,在 fake chunk 里填充一些字母,但在现实中这些位置很可能是不可控的,而 house-of-spirit 也正是以此为目的而出现的。
|
||||||
|
|
||||||
|
该技术的缺点也是需要对栈地址进行泄漏,否则无法正确覆盖需要释放的堆指针,且在构造数据时,需要满足对齐的要求等。
|
||||||
|
|
||||||
加上内存检测参数重新编译,可以看到问题所在,即尝试 free 一块不是由 malloc 分配的 chunk:
|
加上内存检测参数重新编译,可以看到问题所在,即尝试 free 一块不是由 malloc 分配的 chunk:
|
||||||
```
|
```
|
||||||
$ gcc -fsanitize=address -g house_of_spirit.c
|
$ gcc -fsanitize=address -g house_of_spirit.c
|
||||||
@ -1003,3 +1049,4 @@ Freeing the overwritten pointer.
|
|||||||
## 参考资料
|
## 参考资料
|
||||||
- [how2heap](https://github.com/shellphish/how2heap)
|
- [how2heap](https://github.com/shellphish/how2heap)
|
||||||
- [Heap Exploitation](https://heap-exploitation.dhavalkapil.com/)
|
- [Heap Exploitation](https://heap-exploitation.dhavalkapil.com/)
|
||||||
|
- <<Glibc堆利用的若干方法>>
|
||||||
|
@ -106,7 +106,82 @@ Finally, we allocate 'd', overlapping 'b2': 0xabb030
|
|||||||
b2 content:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
b2 content:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||||
New b2 content:BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
New b2 content:BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||||
```
|
```
|
||||||
该技术适用的场景需要某个 malloc 的内存区域存在一个单字节溢出漏洞。
|
该技术适用的场景需要某个 malloc 的内存区域存在一个单字节溢出漏洞。通过溢出下一个 chunk 的 size 字段,攻击者能够在堆中创造出重叠的内存块,从而达到改写其他数据的目的。再结合其他的利用方式,同样能够获得程序的控制权。
|
||||||
|
|
||||||
|
对于单字节溢出的利用有下面几种:
|
||||||
|
- 扩展被释放块:当溢出块的下一块为被释放块且处于 unsorted bin 中,则通过溢出一个字节来将其大小扩大,下次取得次块时就意味着其后的块将被覆盖而造成进一步的溢出
|
||||||
|
|
||||||
|
```
|
||||||
|
0x100 0x100 0x80
|
||||||
|
|-------|-------|-------|
|
||||||
|
| A | B | C | 初始状态
|
||||||
|
|-------|-------|-------|
|
||||||
|
| A | B | C | 释放 B
|
||||||
|
|-------|-------|-------|
|
||||||
|
| A | B | C | 溢出 B 的 size 为 0x180
|
||||||
|
|-------|-------|-------|
|
||||||
|
| A | B | C | malloc(0x180-8)
|
||||||
|
|-------|-------|-------| C 块被覆盖
|
||||||
|
|<--实际得到的块->|
|
||||||
|
```
|
||||||
|
|
||||||
|
- 扩展已分配块:当溢出块的下一块为使用中的块,则需要合理控制溢出的字节,使其被释放时的合并操作能够顺利进行,例如直接加上下一块的大小使其完全被覆盖。下一次分配对应大小时,即可取得已经被扩大的块,并造成进一步溢出
|
||||||
|
|
||||||
|
```
|
||||||
|
0x100 0x100 0x80
|
||||||
|
|-------|-------|-------|
|
||||||
|
| A | B | C | 初始状态
|
||||||
|
|-------|-------|-------|
|
||||||
|
| A | B | C | 溢出 B 的 size 为 0x180
|
||||||
|
|-------|-------|-------|
|
||||||
|
| A | B | C | 释放 B
|
||||||
|
|-------|-------|-------|
|
||||||
|
| A | B | C | malloc(0x180-8)
|
||||||
|
|-------|-------|-------| C 块被覆盖
|
||||||
|
|<--实际得到的块->|
|
||||||
|
```
|
||||||
|
|
||||||
|
- 收缩被释放块:此情况针对溢出的字节只能为 0 的时候,也就是本节所说的 poison-null-byte,此时将下一个被释放的块大小缩小,如此一来在之后分裂此块时将无法正确更新后一块的 prev_size 字段,导致释放时出现重叠的堆块
|
||||||
|
|
||||||
|
```
|
||||||
|
0x100 0x210 0x80
|
||||||
|
|-------|---------------|-------|
|
||||||
|
| A | B | C | 初始状态
|
||||||
|
|-------|---------------|-------|
|
||||||
|
| A | B | C | 释放 B
|
||||||
|
|-------|---------------|-------|
|
||||||
|
| A | B | C | 溢出 B 的 size 为 0x200
|
||||||
|
|-------|---------------|-------| 之后的 malloc 操作没有更新 C 的 prev_size
|
||||||
|
0x100 0x80
|
||||||
|
|-------|------|-----|--|-------|
|
||||||
|
| A | B1 | B2 | | C | malloc(0x180-8), malloc(0x80-8)
|
||||||
|
|-------|------|-----|--|-------|
|
||||||
|
| A | B1 | B2 | | C | 释放 B1
|
||||||
|
|-------|------|-----|--|-------|
|
||||||
|
| A | B1 | B2 | | C | 释放 C,C 将与 B1 合并
|
||||||
|
|-------|------|-----|--|-------|
|
||||||
|
| A | B1 | B2 | | C | malloc(0x180-8)
|
||||||
|
|-------|------|-----|--|-------| B2 将被覆盖
|
||||||
|
|<实际得到的块>|
|
||||||
|
```
|
||||||
|
|
||||||
|
- house of einherjar:也是溢出字节只能为 0 的情况,当它是更新溢出块下一块的 prev_size 字段,使其在被释放时能够找到之前一个合法的被释放块并与其合并,造成堆块重叠
|
||||||
|
|
||||||
|
```
|
||||||
|
0x100 0x100 0x101
|
||||||
|
|-------|-------|-------|
|
||||||
|
| A | B | C | 初始状态
|
||||||
|
|-------|-------|-------|
|
||||||
|
| A | B | C | 释放 A
|
||||||
|
|-------|-------|-------|
|
||||||
|
| A | B | C | 溢出 B,覆盖 C 块的 size 为 0x200,并使其 prev_size 为 0x200
|
||||||
|
|-------|-------|-------|
|
||||||
|
| A | B | C | 释放 C
|
||||||
|
|-------|-------|-------|
|
||||||
|
| A | B | C | C 将与 A 合并
|
||||||
|
|-------|-------|-------| B 块被重叠
|
||||||
|
|<-----实际得到的块------>|
|
||||||
|
```
|
||||||
|
|
||||||
首先分配三个 chunk,第一个 chunk 类型无所谓,但后两个不能是 fast chunk,因为 fast chunk 在释放后不会被合并。这里 chunk a 用于制造单字节溢出,去覆盖 chunk b 的第一个字节,chunk c 的作用是帮助伪造 fake chunk。
|
首先分配三个 chunk,第一个 chunk 类型无所谓,但后两个不能是 fast chunk,因为 fast chunk 在释放后不会被合并。这里 chunk a 用于制造单字节溢出,去覆盖 chunk b 的第一个字节,chunk c 的作用是帮助伪造 fake chunk。
|
||||||
|
|
||||||
@ -470,6 +545,7 @@ molloc 中对于 small bin 链表的检查是这样的:
|
|||||||
|
|
||||||
[...]
|
[...]
|
||||||
```
|
```
|
||||||
|
即检查 bin 中第二块的 bk 指针是否指向第一块,来发现对 small bins 的破坏。为了绕过这个检查,所以才需要同时伪造 bin 中的前 2 个 chunk。
|
||||||
|
|
||||||
接下来释放掉 victim chunk,它会被放到 unsoted bin 中,且 fd/bk 均指向 unsorted bin 的头部:
|
接下来释放掉 victim chunk,它会被放到 unsoted bin 中,且 fd/bk 均指向 unsorted bin 的头部:
|
||||||
```
|
```
|
||||||
|
@ -73,7 +73,7 @@ Now, the next chunk we overwrite will point at our target buffer, so we can over
|
|||||||
old string: This is a string that we want to overwrite.
|
old string: This is a string that we want to overwrite.
|
||||||
new string: YEAH!!!
|
new string: YEAH!!!
|
||||||
```
|
```
|
||||||
house_of\_force 是一种通过改写 top chunk 来欺骗 malloc 返回任意地址的技术。我们知道在空闲内存的最高处,必然存在一块空闲的 chunk,即 top chunk,当 bins 和 fast bins 都不能满足分配需要的时候,malloc 会从 top chunk 中分出一块内存给用户。所以 top chunk 的大小会随着分配和回收不停地变化。这种攻击假设有一个溢出漏洞,可以改写 top chunk 的头部,然后将其改为一个非常大的值,以确保所有的 malloc 将使用 top chunk 分配,而不会调用 mmap。
|
house_of\_force 是一种通过改写 top chunk 的 size 字段来欺骗 malloc 返回任意地址的技术。我们知道在空闲内存的最高处,必然存在一块空闲的 chunk,即 top chunk,当 bins 和 fast bins 都不能满足分配需要的时候,malloc 会从 top chunk 中分出一块内存给用户。所以 top chunk 的大小会随着分配和回收不停地变化。这种攻击假设有一个溢出漏洞,可以改写 top chunk 的头部,然后将其改为一个非常大的值,以确保所有的 malloc 将使用 top chunk 分配,而不会调用 mmap。这时如果攻击者 malloc 一个很大的数目(负有符号整数),top chunk 的位置加上这个大数,造成整数溢出,结果是 top chunk 能够被转移到堆之前的内存地址(如程序的 .bss 段、.data 段、GOT 表等),下次再执行 malloc 时,攻击者就能够控制转移之后地址处的内存。
|
||||||
|
|
||||||
首先随意分配一个 chunk,此时内存里存在两个 chunk,即 chunk 1 和 top chunk:
|
首先随意分配一个 chunk,此时内存里存在两个 chunk,即 chunk 1 和 top chunk:
|
||||||
```
|
```
|
||||||
@ -121,6 +121,7 @@ gef➤ x/12gx 0x602010+0xfffffffffffff050
|
|||||||
0x6010a0 <bss_var+32>: 0x6972777265766f20 0x00000000002e6574
|
0x6010a0 <bss_var+32>: 0x6972777265766f20 0x00000000002e6574
|
||||||
0x6010b0: 0x0000000000000000 0x0000000000000f69 <-- top chunk
|
0x6010b0: 0x0000000000000000 0x0000000000000f69 <-- top chunk
|
||||||
```
|
```
|
||||||
|
该技术的缺点是会受到 ASLR 的影响,因为如果攻击者需要修改指定位置的内存,他首先需要知道当前 top chunk 的位置以构造合适的 malloc 大小来转移 top chunk。而 ASLR 将使堆内存地址随机,所以该技术还需同时配合使用信息泄漏以达成攻击。
|
||||||
|
|
||||||
#### unsorted_bin_attack
|
#### unsorted_bin_attack
|
||||||
```c
|
```c
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
- [技术简介](#技术简介)
|
- [技术简介](#技术简介)
|
||||||
- [编译参数](#编译参数)
|
- [编译参数](#编译参数)
|
||||||
- [保护机制检测](#保护机制检测)
|
- [保护机制检测](#保护机制检测)
|
||||||
|
- [地址空间布局随机化](#地址空间布局随机化)
|
||||||
|
|
||||||
|
|
||||||
## 技术简介
|
## 技术简介
|
||||||
@ -142,10 +143,14 @@ Partial RELRO Canary found NX enabled PIE enabled No RPATH No RU
|
|||||||
开启优化 `-O2` 后,编译没有检测出任何问题,checksec 后 FORTIFY 为 No。当配合 `-D_FORTIFY_SOURCE=2`(也可以 `=1`)使用时,提示存在溢出问题,checksec 后 FORTIFY 为 Yes。
|
开启优化 `-O2` 后,编译没有检测出任何问题,checksec 后 FORTIFY 为 No。当配合 `-D_FORTIFY_SOURCE=2`(也可以 `=1`)使用时,提示存在溢出问题,checksec 后 FORTIFY 为 Yes。
|
||||||
|
|
||||||
#### NX
|
#### NX
|
||||||
No-eXecute,表示不可执行,其原理是将数据所在的内存页标识为不可执行,如果程序产生溢出转入执行 shellcode 时,CPU 会抛出异常。其绕过方法是 ret2libc。
|
No-eXecute,表示不可执行,其原理是将数据所在的内存页标识为不可执行,如果程序产生溢出转入执行 shellcode 时,CPU 会抛出异常。
|
||||||
|
|
||||||
|
在 Linux 中,当装载器将程序装载进内存空间后,将程序的 .text 段标记为可执行,而其余的数据段(.data、.bss 等)以及栈、堆均为不可执行。因此,传统利用方式中通过修改 GOT 来执行 shellcode 的方式不再可行。
|
||||||
|
|
||||||
|
但这种保护并不能阻止攻击者通过代码重用来进行攻击(ret2libc)。
|
||||||
|
|
||||||
#### PIE
|
#### PIE
|
||||||
PIE(Position Independent Executable)需要配合 ASLR 来使用,以达到可执行文件的加载时地址随机化。简单来说,PIE 是编译时随机化,由编译器完成;ASLR 是加载时随机化,由操作系统完成。开启 PIE 时,编译生成的是动态库文件(Shared object)文件,而关闭 PIE 后生成可执行文件(Executable)。
|
PIE(Position Independent Executable)需要配合 ASLR 来使用,以达到可执行文件的加载时地址随机化。简单来说,PIE 是编译时随机化,由编译器完成;ASLR 是加载时随机化,由操作系统完成。ASLR 将程序运行时的堆栈以及共享库的加载地址随机化,而 PIE 在编译时将程序编译为位置无关、、即程序运行时各个段加载的虚拟地址在装载时确定。开启 PIE 时,编译生成的是动态库文件(Shared object)文件,而关闭 PIE 后生成可执行文件(Executable)。
|
||||||
|
|
||||||
我们通过实际例子来探索一下 PIE 和 ASLR:
|
我们通过实际例子来探索一下 PIE 和 ASLR:
|
||||||
```c
|
```c
|
||||||
@ -298,7 +303,7 @@ RELRO(ReLocation Read-Only)设置符号重定向表为只读或在程序启
|
|||||||
|
|
||||||
RELOR 有两种形式:
|
RELOR 有两种形式:
|
||||||
- Partial RELRO:一些段(包括 `.dynamic`)在初始化后将会被标记为只读。
|
- Partial RELRO:一些段(包括 `.dynamic`)在初始化后将会被标记为只读。
|
||||||
- Full RELRO:除了Partial RELRO,延迟绑定将被禁止,所有的导入符号将在开始时被解析,`.got.plt` 段会被完全初始化为目标函数的最终地址,并被标记为只读。
|
- Full RELRO:除了Partial RELRO,延迟绑定将被禁止,所有的导入符号将在开始时被解析,`.got.plt` 段会被完全初始化为目标函数的最终地址,并被标记为只读。另外 `link_map` 和 `_dl_runtime_resolve` 的地址也不会被装入。
|
||||||
|
|
||||||
|
|
||||||
## 编译参数
|
## 编译参数
|
||||||
@ -345,3 +350,9 @@ NX : ENABLED
|
|||||||
PIE : disabled
|
PIE : disabled
|
||||||
RELRO : Partial
|
RELRO : Partial
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## 地址空间布局随机化
|
||||||
|
最后再说一下地址空间布局随机化(ASLR),该技术虽然不是由 GCC 编译时提供的,但对 PIE 还是有影响。该技术旨在将程序的内存布局随机化,使得攻击者不能轻易地得到数据区的地址来构造 payload。由于程序的堆栈分配与共享库的装载都是在运行时进行,系统在程序每次执行时,随机地分配程序堆栈的地址以及共享库装载的地址。使得攻击者无法预测自己写入的数据区的虚拟地址。
|
||||||
|
|
||||||
|
针对该保护机制的攻击,往往是通过信息泄漏来实现。由于同一模块中的所有代码和数据的相对偏移是固定的,攻击者只要泄漏出某个模块中的任一代码指针或数据指针,即可通过计算得到此模块中任意代码或数据的地址。
|
||||||
|
Loading…
Reference in New Issue
Block a user