引言:
整理一下之前做过的堆题和考点,备忘一下~
[NISACTF 2022]UAF
菜单,支持创建编辑删除查看四个操作:
create函数里面为第一个堆块分配了8个字节的空间,前四个字节放giao
字符串,后四个字节放echo
函数。
之后的堆块,就正常的分配8个空字节了。
delete函数里面没有将指针清空,存在uaf漏洞。观察edit函数也没有检查,可以利用。
但我们发现,edit
函数和del
函数对堆块下标的检查并不一致,edit
函数不允许我们去编辑第一个分配的堆块(下标为0) 。但delete
函数允许我们去释放第一个堆块。
同时edit
函数内没有做输入字符长度的限制,可以实现堆溢出。
本题解题思路如下:
先申请第一个堆块,然后再将它释放掉。
申请第二个堆块,因为
create
函数中是根据page
数组中堆块地址是否为空去叠下标的,delete
函数没有清空地址,所以这里面申请的堆块下标为1,地址就是第一个堆块的地址。绕过
edit
检查,利用第二个堆块修改第一个堆块,更改echo
函数为NICO
函数(后门函数),并在前四个字节填充sh\x00\x00
调用
show(1)
, 实现getshell
show函数中利用第一个堆块的后四字节作为函数地址,将堆块的地址作为参数传给该函数。我们将原本echo的输出函数换成了system函数,再将堆块的内容覆盖为
sh
, 既可以利用system("sh")
来getshell了。
exp如下:
1 | from pwn import * |
[CISCN 2022 华东北]duck
这个题可以打IO
, 可以打environ
, 打IO
的手法还不怎么熟练...先打environ
试一试。
菜单堆题没差,提供创建删除编辑查看四个功能。
add
函数里面直接给出来堆块的大小,并且最多只能创建20个堆块。
edit
函数里面也相应地给出了最多只能编辑0x100
个字节,堆溢出没戏。
漏洞点在del
函数里面,经典的free堆块后没有清空,导致UAF
漏洞。
show
函数就是经典的show,没什么说的。
这个题虽然漏洞点很明显,但是由于是高版本libc
(给的是libc.so.6
, Ubuntu 20.04
以上了,跟我wsl
默认的libc
一样),相比之前有以下几点不同:
glibc2.34
之后删除了库函数中的__malloc_hook
,__free_hook
等钩子函数,所以我们没办法通过覆写hook
来getshell
glibc2.32
之后引入了堆内存对齐检查。申请的堆块的地址一定要是8
字节的整数倍。glibc2.32
之后在使用fastbin
和tachebin
申请堆块时,增加了fd
指针的校验:由于之前版本的
fd
指针可以任意写导致不安全,glibc2.32
之后,在使用fastbin
和tcachebin
两个堆块管理结构申请堆块时,增加了对fd
指针的校验。glibc2.32
下tcache_get
/tcache_put
函数: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
27static __always_inline void
tcache_put (mchunkptr chunk, size_t tc_idx)
{
tcache_entry *e = (tcache_entry *) chunk2mem (chunk);
/* Mark this chunk as "in the tcache" so the test in _int_free will
detect a double free. */
e->key = tcache;
e->next = PROTECT_PTR (&e->next, tcache->entries[tc_idx]);
tcache->entries[tc_idx] = e;
++(tcache->counts[tc_idx]);
}
/* Caller must ensure that we know tc_idx is valid and there's
available chunks to remove. */
static __always_inline void *
tcache_get (size_t tc_idx)
{
tcache_entry *e = tcache->entries[tc_idx];
if (__glibc_unlikely (!aligned_OK (e)))
malloc_printerr ("malloc(): unaligned tcache chunk detected");
tcache->entries[tc_idx] = REVEAL_PTR (e->next);
--(tcache->counts[tc_idx]);
e->key = NULL;
return (void *) e;
}可以看到,相比于
glibc2.31
, 函数在存取堆块的时候使用了两个宏保护指针:1
2
3即 tcache_entry->next中存放的chunk地址为与自身地址进行异或运算后所得到的值, 这就要求我们在利用 tcache_entry 进行任意地址写之前 需要我们提前泄漏出相应 chunk 的地址,即我们需要提前获得堆基址后才能进行任意地址写
由于我们在高版本libc下无法通过写hook
来达到getshell,所以我们可以有两个方法,打IO
或打environ
这里介绍打environ
的方法,我们可以通过environ
来泄露某个栈地址,然后算出它和程序返回地址的栈指针的偏移,进而来修改程序的返回地址,实现堆 + rop
的打法。
首先,常规的tcachebin
操作,先申请九个堆块,然后释放掉前八个,第九个堆块不要释放,防止发生已释放堆块和TOP chunk
的合并。之后利用UAF
漏洞通过第八个堆块泄露main_arana + 0x58
,进而泄露libc
tachebin的一条链子最多装7个chunk,第八个chunk会被甩给unsorted_bin。而当unsorted_bin中只有一个chunk时,他的
fd
和bk
指针相同,均只指向main_arena + 058
,也就是unsorted_bin
环形链表的头的地址。
1 | for i in range(8): |
泄露libc
之后我们着手泄露heap_base
。 在这里打个断点看一下堆的布局。
观察到其实我们释放的第0号堆块,他的fd指针实际上就是heap_base >> 12
。 因为他是第0号堆块,他的old_fd
是0,所以在堆指针异或加密之后,fd就是heap_base >> 12
泄露堆基地址,并得到key = heap_base >> 12
1 | show(0) |
现在我们就可以拿着key
和libc
去泄露environ
啦~
先把后五个堆块从tcache
链表里搞出来,然后拿着environ
的地址和key
把加密后的fd搞出来,修改第1号堆块的fd
,再申请两个堆块,现在第15号堆块就是environ
。 show
一下顺利拿到stack_addr
。
通过动态调试算出来edit
函数的返回地址和这个栈地址的偏移,之后我们在edit
函数返回之后进入rop
链。
1 | for i in range(5): |
再释放两个堆块进入到tcache_bin
,修改第10号堆块的fd指针为edit_ret_addr
的地址(栈指针),再申请回来,现在第17号堆块就在栈上了,编辑栈上的数据,完成一个华丽的getshell。
1 | delete(9) |
这里pop链前面要加三个
p64(0)
是动调之后发现返回地址和pop链还差了24个字节,不知道为什么,之前没搞出来去网上看了下别人的题解,动调了一下才发现。。。
完整exp:
1 | from pwn import * |
[CISCN 2021 初赛]lonelywolf
IDA查看程序,菜单堆题,提供增删改查四个功能。libc-2.27
开启tcache_bin
add函数里面会传一个idx
和一个size
, 然后发现这个idx
是假的,整个程序只能支持一个堆块。
在edit函数里面,有个off_by_null
free函数里面有一个UAF,在这个版本下我们可以利用UAF去实现Double Free
show函数就是一个正常的show。
本题的考点在于Double Free
+ tcache_pthread_struct
利用。大体思路如下:
- 首先利用
Double free
漏洞,重复free同一个tcache
堆块,泄露tcache
地址,进而泄露tcache_pthread_struct
的地址。
tcache
引入的Double free
检查是检查该堆块的bk
指针是不是key
,如果是key就是触发Double free
的报错。所以这里我们需要将该堆块的bk
位归零。
1 | add(0, 0x70) |
- 利用
UAF
漏洞,覆盖fd
指针使其指向tcahch
头,进而控制tcache_pthread_struct
tcache_pthread_struct
大小是0x250(不包含0x10的头部)想下标35是0x250大小堆块的计数器,修改该堆块的计数器,再次释放tcache
头,这个大堆块就会进入到unsorted_bin
中。
1 | edit(0, p64(heap_base + 0x10)) |
- 释放
tcache
头,让其进入到unsorted_bin
中,进而泄露libc
1 |
|
- 修改
tcache
头,申请堆块控制__free_hook
,改为system
1 | free_hook = libc_base + libc.sym["__free_hook"] |
- 再次申请堆块,填充内容
/bin/sh
,释放堆块,getshell。
完整exp如下:
1 | from pwn import * |