how2heap漏洞和解析

First Post:

Last Update:

Word Count:
4k

Read Time:
16 min

Page View: loading...

First fit

原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
演示glibc 的分配机制
glibc 使用首次适应算法选择空闲的堆块
如果有一个空闲堆块且足够大,那么 malloc 将选择它
如果存在 use-after-free 的情况那可以利用这一特性
首先申请两个比较大的 chunk
第一个 a = malloc(0x512) 在: 0x1682010
第二个 b = malloc(0x256) 在: 0x1682530
我们可以继续分配它
现在我们把 "AAAAAAAA" 这个字符串写到 a 那里
第一次申请的 0x1682010 指向 AAAAAAAA
接下来 free 掉第一个...
接下来只要我们申请一块小于 0x512 的 chunk,那就会分配到原本 a 那里: 0x1682010
第三次 c = malloc(0x500) 在: 0x1682010
我们这次往里写一串 "CCCCCCCC" 到刚申请的 c
第三次申请的 c 0x1682010 指向 CCCCCCCC
第一次申请的 a 0x1682010 指向 CCCCCCCC
可以看到,虽然我们刚刚看的是 a 的,但它的内容却是 "CCCCCCCC"

操作

  • 存在uaf,首先释放一个堆块p1,里面有内容
  • 再申请一个相同大小的堆块p2
  • 这两个堆块实际上指向同一个内存区域

结果

这两个堆块实际上指向同一个内存区域

UAF

原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
申请0x20大小的内存p1 的地址: 0x1f11010
p1[1]赋值为Printf函数,然后打印出"Hello CTFshow"
Hello CTFshow

freep1
因为并没有置为null,所以p1[1]仍然是Printf函数,仍然可以输出打印了"Hello CTFshow again"
Hello CTFshow again
接下来再去malloc一个p2,会把释放掉的p1给分配出来,可以看到他俩是同一地址的
p2 的地址: 0x1f11010
p1 的地址: 0x1f11010
然后把p2[1]给改成demoflag也就是system函数

Then get the flag && enjoy it !

操作

  • free掉chunk1
  • 再申请一个相同大小的chunk2,修改内容
  • 再使用chunk1,会输出修改内容

结果

chunk1和chunk2是同一个chunk

Double Free

原理

1
2
3
4
5
6
7
8
9
10
11
12
13
演示 fastbin 的 double free
首先申请 3 个 chunk
第一个 malloc(8): 0x188f010
第二个 malloc(8): 0x188f030
第三个 malloc(8): 0x188f050
free 掉第一个
当我们再次 free 0x188f010 的时候, 程序将会崩溃因为 0x188f010free 链表的第一个位置上
我们先 free 0x188f030.
现在我们就可以再次 free 0x188f010, 因为他现在不在 free 链表的第一个位置上
现在空闲链表是这样的 [ 0x188f010, 0x188f030, 0x188f010 ]. 如果我们 malloc 三次, 我们会得到两次 0x188f010
第一次 malloc(8): 0x188f010
第二次 malloc(8): 0x188f030
第三次 malloc(8): 0x188f010

操作

  • 申请3个chunk,p1,p2,p3
  • free p1,再freep2,形成 p2 -> p1
  • 再free p1 形成 p1 -> p2 -> p1
  • 连续申请三次chunk

结果

得到两个相同地址的chunk

Fastbin_dup_into_stack – Double free

原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
通过欺骗 malloc 使得返回一个指向受控位置的指针(本例为栈上)
通过 malloc 申请到 0x7ffec12adb40.
先申请3 个 chunk
chunk a: 0x209a010
chunk b: 0x209a030
chunk c: 0x209a050
free 掉 chunk a
如果还对 0x209a010 进行 free, 程序会崩溃。因为 0x209a010 现在是 fastbin 的第一个
先对 b 0x209a030 进行 free
接下来就可以对 0x209a010 再次进行 free 了, 现在已经不是它在 fastbin 的第一个了
现在 fastbin 的链表是 [ 0x209a010, 0x209a030, 0x209a010 ] 接下来通过修改 0x209a010 上的内容来进行攻击.
第一次 malloc(8): 0x209a010
第二次 malloc(8): 0x209a030
现在 fastbin 表中只剩 [ 0x209a010 ] 了
接下来往 0x209a010 栈上写一个假的 size,这样 malloc 会误以为那里有一个空闲的 chunk,从而申请到栈上去
现在覆盖 0x209a010 前面的 8 字节,修改 fd 指针指向 stack_var 前面 0x20 的位置
第三次 malloc(8): 0x209a010, 把栈地址放到 fastbin 链表中
这一次 malloc(8) 就申请到了栈上去: 0x7ffec12adb40

操作

  • 申请3个chunk,p1,p2,p3
  • free p1,再freep2,形成 p2 -> p1
  • 再free p1 形成 p1 -> p2 -> p1
  • 修改p1的fd指向任意地址,栈上都可
  • 连续申请三次chunk
  • 第四次申请chunk会申请到目标地址

结果

任意地址读写

Fastbin_dup_consolidate

原理

1
2
3
4
5
6
申请两个 fastbin 范围内的 chunk: p1=0xbba010 p2=0xbba030
先 free p1
去申请 largebin 大小的 chunk,触发 malloc_consolidate(): p3=0xbba050
因为 malloc_consolidate(), p1 会被放到 unsorted bin 中
这时候 p1 不在 fastbin 链表的头部了,所以可以再次 free p1 造成 double free
现在 fastbin 和 unsortedbin 中都放着 p1 的指针,所以我们可以 malloc 两次都到 p1: 0xbba010 0xbba010

操作

- 1.free 一个fastbin大小的chunk 1
- 2.申请一个largin bin 大小的chunk,此时因为 malloc_consolidate(), chunk1 会被放到 unsorted bin 中
- 再次free chunk1

结果

​ 现在 fastbin 和 unsortedbin 中都放着 p1 的指针,所以我们可以 malloc 两次都到 p1: 0xbba010 0xbba010,任意地址读写

原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
当你在已知位置有指向某个区域的指针时,可以调用 unlink
最常见的情况是易受攻击的缓冲区,可能会溢出并具有全局指针
本练习的重点是使用 free 破坏全局 chunk0_ptr 来实现任意内存写入

全局变量 chunk0_ptr 在 0x6020d0, 指向 0x161e010
我们想要破坏的 chunk 在 0x161e0a0
在 chunk0 那里伪造一个 chunk
我们设置 fake chunk 的 'next_free_chunk' (也就是 fd) 指向 &chunk0_ptr 使得 P->fd->bk = P.
我们设置 fake chunk 的 'previous_free_chunk' (也就是 bk) 指向 &chunk0_ptr 使得 P->bk->fd = P.
通过上面的设置可以绕过检查: (P->fd->bk != P || P->bk->fd != P) == False
Fake chunk 的 fd: 0x6020b8
Fake chunk 的 bk: 0x6020c0

现在假设 chunk0 中存在一个溢出漏洞,可以更改 chunk1 的数据
通过修改 chunk1 中 prev_size 的大小使得 chunk1 在 free 的时候误以为 前面的 free chunk 是从我们伪造的 free chunk 开始的
如果正常的 free chunk0 的话 chunk1 的 prev_size 应该是 0x90 但现在被改成了 0x80
接下来通过把 chunk1 的 prev_inuse 改成 0 来把伪造的堆块标记为空闲的堆块

现在释放掉 chunk1,会触发 unlink,合并两个 free chunk
此时,我们可以用 chunk0_ptr 覆盖自身以指向任意位置
chunk0_ptr 现在指向我们想要的位置,我们用它来覆盖我们的 victim string。
之前的值是: Hello!~
新的值是: BBBBAAAA

操作

​ fd = goal - 0x18

​ bk = goal - 0x10

结果

任意地址写

house_of_spirit

原理

1
2
3
4
5
6
7
8
9
10
11
12
这个例子演示了 house of spirit 攻击
我们将构造一个 fake chunk 然后释放掉它,这样再次申请的时候就会申请到它
覆盖一个指向 fastbin 的指针
这块区域 (长度为: 80) 包含两个 chunk. 第一个在 0x7fff5f0e7268 第二个在 0x7fff5f0e72a8.
构造 fake chunk 的 size,要比 chunk 大 0x10(因为 chunk 头),同时还要保证属于 fastbin,对于 fastbin 来说 prev_inuse 不会改变,但是其他两个位需要注意都要位 0
next chunk 的大小也要注意,要大于 0x10 小于 av->system_mem(128kb)
现在,我们拿伪造的那个 fake chunk 的地址进行 free, 0x7fff5f0e7270.
free!
现在 malloc 的时候将会把 0x7fff5f0e7270 给返回回来
malloc(0x30): 0x7fff5f0e7270
Finish!

操作

构造fake fastbin chunk,free掉这个chunk,再次申请可以拿回这个chunk

前提有一个可控的指针

结果

任意地址写,前提有可控指针

Posion_null_byte

原理

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
当存在 off by null 的时候可以使用该技术
申请 0x100 的 chunk a
a 在: 0x1eb2010
因为我们想要溢出 chunk a,所以需要知道他的实际大小: 0x108
b: 0x1eb2120
c: 0x1eb2330
另外再申请了一个 chunk c:0x1eb2440,防止 free 的时候与 top chunk 发生合并的情况
会检查 chunk size 与 next chunk 的 prev_size 是否相等,所以要在后面一个 0x200 来绕过检查
b 的 size: 0x211
假设我们写 chunk a 的时候多写了一个 0x00 在 b 的 size 的 p 位上
b 现在的 size: 0x200
c 的 prev_size 是 0x210
但他根据 chunk b 的 size 找的时候会找到 b+0x1f0 那里,我们将会成功绕过 chunk 的检测 chunksize(P) == 0x200 == 0x200 == prev_size (next_chunk(P))
申请一个 0x100 大小的 b1: 0x1eb2120
现在我们 malloc 了 b1 他将会放在 b 的位置,这时候 c 的 prev_size 依然是: 0x210
但是我们之前写 0x200 那个地方已经改成了: f0
接下来 malloc 'b2', 作为 'victim' chunk.
b2 申请在: 0x1eb2230
现在 b2 填充的内容是:
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
现在对 b1 和 c 进行 free 因为 c 的 prev_size 是 0x210,所以会把他俩给合并,但是这时候里面还包含 b2 呐.
这时候我们申请一个 0x300 大小的 chunk 就可以覆盖着 b2 了
d 申请到了: 0x1eb2120,我们填充一下 d 为 "D"
现在 b2 的内容就是:
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD

操作

  • 申请0x100大小的chunk a,0x200大小的chunk b,chunk c 防合并
  • free b
  • 通过off-by-one覆写chunk b的size从0x211->0x200
  • chunk b中b+0x1f0的位置放prev_size = 0x200 我们将会成功绕过 chunk 的检测 chunksize(P) == 0x200 == 0x200 == prev_size (next_chunk(P))
  • 申请一个0x100的b1,b1会放到b的位置,c的prev_size仍然是0x210,但是我们之前写 0x200 那个地方已经改成了: f0
  • 申请b2,作为 ‘victim’ chunk
  • free b1 和 c,由于c的prev_size是0x210,会合并b1和c,此时b2仍在

结果

在一个大的free堆块中存在一个未被free的堆块

House_of_lore

原理

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
定义了两个数组stack_buffer_1 在 0x7ffc7a946070
stack_buffer_2 在 0x7ffc7a946050
申请第一块属于 fastbin 的 chunk 在 0x211c010
在栈上伪造一块 fake chunk
设置 fd 指针指向 victim chunk,来绕过 small bin 的检查,这样的话就能把堆栈地址放在到 small bin 的列表上
设置 stack_buffer_1 的 bk 指针指向 stack_buffer_2,设置 stack_buffer_2 的 fd 指针指向 stack_buffer_1 来绕过最后一个 malloc 中 small bin corrupted, 返回指向栈上假块的指针另外再分配一块,避免与 top chunk 合并 0x211c080
Free victim chunk 0x211c010, 他会被插入到 fastbin 中

此时 victim chunk 的 fd、bk 为零
victim->fd: (nil)
victim->bk: (nil)

这时候去申请一个 chunk,触发 fastbin 的合并使得 victim 进去 unsortedbin 中处理,最终被整理到 small bin 0x211c010
现在 victim chunk 的 fd 和 bk 更新为 unsorted bin 的地址
victim->fd: 0x7f6610ee7bd8
victim->bk: 0x7f6610ee7bd8

现在模拟一个可以覆盖 victim 的 bk 指针的漏洞,让他的 bk 指针指向栈上
然后申请跟第一个 chunk 大小一样的 chunk
他应该会返回 victim chunk 并且它的 bk 为修改掉的 victim 的 bk
最后 malloc 一次会返回 victim->bk 指向的那里
p4 = malloc(100)

在最后一个 malloc 之后,stack_buffer_2 的 fd 指针已更改 0x7f6610ee7bd8

p4 在栈上 0x7ffc7a946080

操作

  • 在栈上定义了两个数组 stack1,stack2
  • 申请了一块 fastbin chunk,在栈上伪造一块 fake chunk,设置stack1 fd -> victim chunk,绕过small bin检查
  • 设置 stack_buffer_1 的 bk 指针指向 stack_buffer_2 & 设置 stack_buffer_2 的 fd 指针指向 stack_buffer_1
  • 再分配个chunk,避免和top chunk 合并
  • free victim chunk,会被放入fastbin中同时fd、bk为0
  • 再申请一个large bin chunk触发xx使得victim chunk 进入 unsortedbin
  • fd 和 bk 被更新为main_arena_88
  • 存在一个漏洞可以使得victim的bk -> stack 1
  • 申请一个大小相同的chunk取出victim chunk,并且它的bk为修改掉的victim的bk
  • 再次malloc一次会返回 victim -> bk 指向的那里,也就是stack1,stack2 fd 指针也更改main_arena_88

结果

任意地址malloc

Overlapping_chunks

原理

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
这是一个简单的堆块重叠问题,首先申请 3 个 chunk
这三个 chunk 分别申请到了:
p1:0x2088010
p2:0x2088110
p3:0x2088210
给他们分别填充"1""2""3"

free 掉 p2
p2 被放到 unsorted bin 中
现在假设有一个堆溢出漏洞,可以覆盖 p2
为了保证堆块稳定性,我们至少需要让 prev_inuse 为 1,确保 p1 不会被认为是空闲的堆块
我们将 p2 的大小设置为 385, 这样的话我们就能用 376 大小的空间

现在让我们分配另一个块,其大小等于块p2注入大小的数据大小
malloc 将会把前面 free 的 p2 分配给我们(p2 的 size 已经被改掉了)

p4 分配在 0x2088110 到 0x2088288 这一区域
p3 从 0x2088210 到 0x2088288
p4 应该与 p3 重叠,在这种情况下 p4 包括所有 p3
这时候通过编辑 p4 就可以修改 p3 的内容,修改 p3 也可以修改 p4 的内容

接下来验证一下,现在 p3 与 p4:
p4 = 22222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222
p3 = 3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333�

如果我们使用 memset(p4, '4', 376), 将会:
p4 = 44444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444�
p3 = 4444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444�

操作

  • 申请三个堆块大小为0xf8,0xf8,0x78
  • free p2,p2被放到 unsorted bin 中
  • 假设存在一个堆溢出漏洞,可以覆盖p2

结果

堆块重叠

Overlapping_chunks_2

原理

操作

结果

Mmap_overlapping_chunks

原理

操作

结果

Unsorted_bin_attack

原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
unsorted bin attack 实现了把一个超级大的数(unsorted bin 的地址)写到一个地方
实际上这种攻击方法常常用来修改 global_max_fast 来为进一步的 fastbin attack 做准备

我们准备把这个地方 0x7ffe5b09ba18 的值 0 更改为一个很大的数

一开始先申请一个比较正常的 chunk: 0x14fe010
再分配一个避免与 top chunk 合并

当我们释放掉第一个 chunk 之后他会被放到 unsorted bin 中,同时它的 bk 指针为 0x7efe12f93b78
现在假设有个漏洞,可以让我们修改 free 了的 chunk 的 bk 指针
我们把目标地址(想要改为超大值的那个地方)减去 0x10 写到 bk 指针:0x7ffe5b09ba08

再去 malloc 的时候可以发现那里的值已经改变为 unsorted bin 的地址
0x7ffe5b09ba18: 0x7efe12f93b78

操作

  • 申请一个chunk p1 (0x410),再申请一个chunk p2避免与top chunk 合并
  • free p1,p1会被放入 unsorted bin 中,同时fd 和 bk指针为main_arena_88
  • 假设有个漏洞,可以修改p1的bk指针
  • 修改 bk -> (goal - 0x10)
  • 再malloc相同大小的chunk p,goal已经为unsorted bin 的地址

结果

修改任意位置为 一个很大的数

Large_bin_attack

原理

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
跟 unsorted bin attack 实现的功能差不多,都是把一个地址的值改为一个很大的数

先来看一下目标:
stack_var1 (0x7fff83c2e410): 0
stack_var2 (0x7fff83c2e418): 0

分配第一个 large chunk: 0x6ac000
再分配一个 fastbin 大小的 chunk,来避免 free 的时候下一个 large chunk 与第一个合并了

申请第二个 large chunk 在: 0x6ac360
同样在分配一个 fastbin 大小的 chunk 防止合并掉

最后申请第三个 large chunk 在: 0x6ac7a0
申请一个 fastbin 大小的防止 free 的时候第三个 large chunk 与 top chunk 合并

free 掉第一个和第二个 chunk,他们会被放在 unsorted bin 中 [ 0x6ac360 <--> 0x6ac000 ]

现在去申请一个比他俩小的,然后会把第一个分割出来,第二个则被整理到 largebin 中,第一个剩下的会放回到 unsortedbin 中 [ 0x6ac0a0 ]

free 掉第三个,他会被放到 unsorted bin 中: [ 0x6ac7a0 <--> 0x6ac0a0 ]

假设有个漏洞,可以覆盖掉第二个 chunk 的 "size" 以及 "bk""bk_nextsize" 指针
减少释放的第二个 chunk 的大小强制 malloc 把将要释放的第三个 large chunk 插入到 largebin 列表的头部(largebin 会按照大小排序)。覆盖掉栈变量。覆盖 bk 为 stack_var1-0x10,bk_nextsize 为 stack_var2-0x20

再次 malloc,会把释放的第三个 chunk 插入到 largebin 中,同时我们的目标已经改写了:
stack_var1 (0x7fff83c2e410): 0x6ac7a0
stack_var2 (0x7fff83c2e418): 0x6ac7a0

操作

  • 分配第一个large bin chunk,再申请一个fast bin chunk 隔绝,避免和下一个large bin chunk合并
  • 分配第二个large bin chunk,再申请一个fast bin chunk 隔绝,避免和下一个large bin chunk合并
  • 分配第三个large bin chunk,再申请一个fast bin chunk 隔绝,避免和下一个large bin chunk合并
  • free chunk1 和 chunk2 均被放入unsorted bin 中
  • 现在去申请一个比他俩小的,然后会把第一个分割出来,第二个则被整理到 largebin 中,第一个剩下的会放回到 unsortedbin 中
  • free chunk3 放到unsorted bin 中
  • 存在漏洞,可以覆盖掉第二个chunk 的size bk bk_nextsize
  • 减少释放的第二个 chunk 的大小强制 malloc 把将要释放的第三个 large chunk 插入到 largebin 列表的头部(largebin 会按照大小排序)。覆盖掉栈变量。覆盖 bk 为 stack_var1-0x1
  • 再次 malloc,会把释放的第三个 chunk 插入到 largebin 中,同时我们的目标已经改写了

结果

​ 栈上地址被覆盖