2021 强网杯,一如既往的超多pwn题。
notebook
程序分析
一道kernel
题目,并没有典型的一些内核漏洞。但是看到了一些读写锁,以及 copy_from_user
等操作。所以猜测为条件竞争漏洞。这里为了实现一个稳定的UAF
漏洞,选择使用 user-fault
缺页机制来实现。
1 | ssize_t __fastcall mynote_write(file *file, const char *buf, size_t idx, loff_t *pos) |
1 | ssize_t __fastcall mynote_read(file *file, char *buf, size_t idx, loff_t *pos) |
有一个常见的对堆块读写的操作,里面涉及到copy_from_user
和copy_to_user
函数。然后这里没有加读写锁。然后程序还加了一些多余的对堆块的操作,比如堆块删除。
1 | __int64 __fastcall notedel(size_t idx) |
然后还给了一个类似的后门函数,可以输出所有的堆块地址。
1 | __int64 __fastcall notegift(void *buf) |
然后也给了驱动的基址,我们可以直接用 lsmod
读取出来。
利用分析
这里可以当执行 write
函数,去写堆块数据时,在 copy_from_user
函数处触发 user-fault
缺页中断。然后去穿插执行 delete
函数,将该堆块释放掉。然后,再去写数据。那么就构造了一个 UAF
漏洞。
泄漏fd指针
这里需要感谢 xxrw
大佬,指点了我们 4.14
版本之后,内核堆块加了一个保护,会对 fd
指针进行加密。加密的方法是:
fd = now_heap_addr ^ next_free_heap_addr ^ random_num
。
而这里,我们申请一个 chunk
出来之后,其实并不会对这个chunk
的内容进行操作,也就是可以泄漏出 fd
的值。然后再接着申请堆块,最后通过 gift
函数,可以输出申请的堆块地址。
那么,这里最后可以得到当前堆块地址,和紧邻的申请的下一块地址,计算出 random_num
。
然后再通过 UAF
漏洞,修改一个释放的chunk
的 fd
指针为 fd = now_heap_addr ^ (name+0xf0) ^ random_num
。
然后,还需要在 name+0xf0
处 伪造一个 fd = (name+0xf0) ^ (random_num) ^ (next_heap_addr)
这里的操作相当于将 name+0xf0
的地址插入了 now_heap
和 next_heap
这两个空闲堆块之间。
然后,由于分配的时候,会对整个链表进行检查,所以这里必须要在 name+0xf0
处还伪造一个fd
指针。
但是,这里由于分配的随机性,并不能保证我们后续分配的一定马上是 now_heap
和 name+0xf0
,所以这里需要多分配几次,看是否能分配到该堆块。
如果,能够分配到 name+0xf0
,就相当于能够控制紧邻的heap_list
,实现了任意地址读写。
泄漏地址
比赛的时候,泄漏地址卡了我们很久,最后我们比赛的方法是利用了一个bpf
的cve
,遍历 bpf
虚表得到 内核地址。
但是,后面看了其他队伍的exp
,发现这里如果知道当前驱动的基址,那么可以利用驱动中的内核函数的偏移地址进行一个换算得到基址。如下是 copy_from_user
在 驱动中的偏移:
1 | .text:0000000000000167 E8 04 0E 00 00 call _copy_from_user |
这里的 0x040e
是 copy_from_user
函数的偏移,我们进行如下换算:
1 | size_t kernel_base = ((*(uint32_t*)buf1 + modaddr + 0x16C) | 0xFFFFFFFF00000000) - 0x476C30; |
复写mod_probepath
最后即可通过覆写 modprobe_path
来 getshell
EXP
1 |
|
ezCloud
程序分析
1 | for ( index = 0LL; index <= 15 && *(&user_login->is_vip + index + 8); ++index )// add_note |
add
堆块的时候,如果size=0
,那么 就不会对该堆块进行清空。有可能会残留堆块数据。如果刚好残留了一个note
地址。那么可以通过 修改这个残留的 note
地址,去修改下一个 note
里的存储地址。最后将该存储地址指向user_info
结构体。
利用分析
漏洞点在于,当申请size=0
的堆块时,就不会对该堆块进行晴空。如果残留数据刚好残留了一个堆地址,以及在0x10
处残留了 一个大数。那么后续就能够实现一个堆溢出。
为了构造一个在0x0
残留堆地址的chunk
只需要是tcache
中取出即可,但是要通过堆喷大量的 0x20
大小的堆块。这里选择用get
来堆喷。
EXP
1 | from pwn import * |
shellcode
利用分析
就是一个写shellcode
的题目。但是首先有一个检查,输入的 shellcode
必须是可见字符,可以通过 pwnable.tw
的一道题的写法来先实现一个任意shellcode
读取。然后通过改变架构,来执行 open
函数,读取 flag
后,通过 测信道将 flag
输出出来。
EXP
1 | from pwn import * |
pipeline
程序分析
程序漏洞很明显,通过输入一个负数即可实现一个堆溢出。
利用分析
通过堆溢出修改紧邻的pipe
结构体,修改其中的堆块地址,来实现任意地址写。
然后劫持 free_hook
,通过 realloc
去触发 free_hook
。
EXP
1 | from pwn import * |
Baby_diary
程序分析
1 | void __fastcall sub_1528(unsigned int idx, int size) |
漏洞很明显,有一个人造的 off-by-one
漏洞。但是这个 off-by-one
只能修改相邻 chunk
的 size
的最低4位 bit
。所以其实还是只能利用 off-by-one
来实现堆重叠。
利用分析
而高于 2.29
的 off-by-one
漏洞的利用,有两种方法。
一是能够拿到程序基址,利用 chunk+list
里 含有当前堆块的 地址,来绕过 unlink
检查。好像Nu1L
队是利用这个方法来做的,这个泄漏基址时也需要爆破。
二是模版类题目,即利用largebin attack
来攻击。
这里我用的是第二种,走 largebin attack
的方法。但是这里有一个限制条件就是要 保证利用的 largebin
堆块的倒数第三、四个字节地址是 \x2000
。如果要爆破比率较低,基本就是靠欧皇水平了。
EXP
1 | from pwn import * |
Baby_pwn
程序分析
1 | unsigned __int64 __fastcall sub_EB1(_BYTE *chunk) |
会将输入的堆块数据中 \x11
改写为 \x00
。可以利用这个来实现 堆重叠。
利用分析
2.27 的堆重叠,构造比较简单。
然后利用 setcontext+53
的位置来实现 orw
EXP
1 | from pwn import * |
orw
利用分析
add
和 delete
都可以向上溢出。修改 atoi
后,输入更多的 shellcode
,最终执行 read
读入更多的 shellcode
执行 orw
EXP
1 | from pwn import * |
- 本文作者: A1ex
- 本文链接: http://yoursite.com/2021/06/25/2021-QWB题解/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!