原文

照着这篇文章写的,不算翻译吧(毕竟我英文差的**)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/* 
Heap overflow vulnerable program.
*/
#include <stdlib.h>
#include <string.h>

int main( int argc, char * argv[] )
{
char * first, * second;

/*[1]*/ first = malloc( 666 );
/*[2]*/ second = malloc( 12 );
if(argc!=1)
/*[3]*/ strcpy( first, argv[1] );
/*[4]*/ free( first );
/*[5]*/ free( second );
/*[6]*/ return( 0 );
}

先看看程序的 [3] 中做了什么:
argv[1] 复制进堆块 first 中 且没有任何限制,因此当用户输入一个大于 666 字节就会覆盖下一个 chunk

首先分析下 glibc mallocfree 的时候都做了什么吧。

当输入的字节小于 666 字节时,在 [4] 中做了如下操作:

1. 非 mmap 的 chunk,合并 前/后 的块
2. 合并上一个块:
    * 如果当前释放的 chunk 中的 P 位为 0,就将上一个块在 `bins` 中移除( `unlink` 操作),将当前块的大小增加上上一个块的大小 并且 将当前块指向上一个块。
    * 在这个例子中 P 位为 1(第一个块默认的 P 位都为 1,尽管上一个块不存在)。
3. 合并下一个块:
    * 当下一个块为 free 时,将下一个块从 bins 中移除( 也是执行 `unlink`  ),将当前块的大小加上下一个块的大小。(在这里例子中下一个块为 second,所以也不会合并)
    * 有意思的是,获取下一个块是否为 free 是根据下下个块的 P 位来决定的。
4. 然后添加 合并后的块 到  unsorted bin 中。在这里例子中没有发生合并,所以只用将  first 添加到 unsorted bin 就好。

下面说说攻击者在第三行中覆盖 second 这块内存的内容:

  • prev_size = 覆盖成偶数(使 PREV_INUSE 位为 0)
  • size = -4
  • fd = free address -12
  • bk = shellcode address

在攻击者的用向下,[4] 做了如下事:

  1. 合并上下堆块
  2. 合并上一个块:和上面分析的一样,不再重复
  3. 合并下一个块:
    判断下一个块是否为空闲,前面提到过是根据下下个块的 P 位来判断的。那么怎么找到下下个块的地址呢?就是:下一个块的地址+下一个块大小。在这里我们把下一块的大小变成了-4,所以他 下一块的地址(prev_size)+(-4) 又指向了自己

回顾一下 unlink 的过程:

1. 首先传入要 unlink 的堆块,假定变量 P
2. BK = P->bk , FD = P->fd // 将 P 的 bk 和 fd 分别给 BK 和 FD 变量
3. FD->bk = BK, BK->fd = FD(因为用文字太绕了。)
4. //用文字描述就是:当前块的上一个块的下一个块(即:(P->bk)BK->fd) 等于 当前块的下一块。下一块的上一块同理。

来张图比什么都清晰:
unlink过程图 —此图来自 ctf-wiki


回到上面,在 [3] 处覆盖了 second 堆块后:
此时 second 堆应该是这样的:

second

字段 —–
prev_size —– 偶数(最后一位为 P 位,只要是偶数最后一位就为 0,因此偶数即可)
size —– -4 (减去4,指向了自己)
fd —– GOT入口-12 (稍后解释)
bk —– shellcode 地址

回顾一下代码的 [4]:free(first)

执行这句话的时候发生了什么?

  1. 释放掉 first 块
  2. 判断上一个块是否空闲,在这里不是空闲的,所以跳过
  3. 判断下一个块是否空闲,在这里判断的是 second 块,原本应该是 非空闲 的,but 这里的 size 是 -4,所以取下下块的时候又跑到了自己块的 prev_size 处,这里的值是偶数,所以 P 位为0,所以就未占用咯,所以要合并咯,合并就要用到 unlink 咯。

那么在这里 unlink 都做了什么呢?

1
2
3
4
FD = P->fd ( FD = GOT入口-12 )
BK = P->bk ( BK = shellcode地址 )
FD->bk = BK( FD->bk = GOT入口,因为 FD 是 GOT-12,bk 位于 FD 12个字节处,所以 FD->bk 就等于 FD+12 的地址 )
BK->fd = FD(这里是什么不重要了,但是此处的内容也会被破坏,所以要注意)

在这里 GOT 入口表被覆盖成 shellcode 地址了。

这里覆盖 GOT 入口表本身没什么意义,只是一个思路吧,当作迈出去的第一步。

至此, unlink 的利用就算结束了,下篇文章分析一道,

1
2
3
while True:
print "特别特别特别"
print "简单的题目。"