2021D3CTF,题目质量十分好,难度中上。包含一个签到的qemu-pwn,一个kernel-pwn和一个 c++ pwn。以及现在不太熟悉的 php 和 windows。
liproll
程序分析
1 | __int64 __fastcall liproll_unlocked_ioctl(__int64 a1, unsigned int a2, unsigned int *a3) |
驱动主要有四种功能,和一个主要结构体 spell
,该结构体如下:
1 | typedef struct spell_struct{ |
create_spell
是创建一个 spell
结构体并为其分配内存,将其地址保存在 list
中 ;choose_a_spell
是从 list
中选择 一个 spell
结构体;cast_spell
是一个漏洞功能,下面详细分析:
1 | unsigned __int64 __fastcall cast_a_spell(__int64 *a1) |
其主要功能是 修改 spell
结构体里的内容,首先是根据用户 传入的 一个 spell
结构体 获取其中的 buf
和size
,然后将 用户 Buf
中的内容 copy
到 内核栈上 tobuf
中,这里存在溢出。内核栈 tobuf
长度为 0x100,而用户传入的 size
大于 0x100,所以存在栈溢出。而这里的栈溢出,会修改 global_buf1
和 v7
,也即会修改 全局变量 global_buffer
和 size
。而我们后续内核的 read\write\open
等函数都是通过 global_buffer
来指定 地址的,也就是 我们一旦能修改 global_buffer
就能实现 任意地址读写。
利用分析
kernel
的题 其实一旦能实现任意地址读写之后,能采用的方法挺多的,常见的有 ret2user
或 ROP
或者直接爆破修改 cred
结构体。但是这道题,上述方法都会比较困难。因为该题采用了细粒度的 kaslr,FG-KASLR 开启后会导致 vmlinux 和相应的内核模块以函数为单位分段,然后在原先地址随机化的基础 上打乱函数加载顺序,⽆法通过静态分析确定函数在 base_addr 基础上的偏移。
所以这道题,xmzyshypnc
学长 直接采用了 覆盖 modprobe_path
来提权,这种方法其实在真实提权漏洞中也是用的更广泛的,具体原理分析可以参考我之前的 kernel 博客。
- 泄露地址
首先我们需要将 vmlinux 的地址泄露出来。这里直接选择第一个 spell
结构体,然后通过 read
将其 buf读取出来,其 Buf
中就含有 vmlinux
地址。我们可以据此计算出 基址。
- 计算
modprobe_path
地址
当没有开启 kaslr
时,我们可以直接使用如下命令找到 modprobe_path
的地址:
1 | cat /proc/kallsyms | grep modprobe_path |
但是这道题modprobe_path
在/proc/kallsyms
里没有符号,我们可以通过引用找到它参考,先找到__request_module
函数,在gdb里查看函数汇编即可找到modprobe_path
。这里可以看到 modprobe_path
的地址是 0xffffffffbb848460
。
1 | / |
- 提权
知道 modprobe_path
的地址后,就可以通过上面任意地址写 漏洞,修改 modprobe_path
的地址为一个 我们在 tmp 目录下创建的一个 sh 文件。然后 打开一个非法文件,使得程序报错,最后去执行 modprobe_path
指向的sh
文件。在该 sh
文件中 我们可以将 flag 拷贝到 tmp
目录下,就可以读取flag了。
当然这种方法对于这道题是非预期解,出题者的想法是去绕过 FG-KALSR。但是,我环境目前出问题了,所以就没法做这种方法了:)
EXP
1 |
|
狡兔三窟
程序分析
主要有6个功能,Edit
为存储数据,有两次机会,存储机制为 vector
,输入数据超过当前存储空间时,会申请比当前堆块大一倍的堆块继续存储,并删掉旧堆块;也有一次 clear
机会,可以将输入指针指向起始处。Save
会申请一个与当前存储数据堆块一致的堆块,并释旧堆块。backup
会备份当前存储数据的堆块地址。delete
为删掉第一个存储输入的堆块。show
可以输出第一个存储堆块的起始地址的值。encourage
功能中有一个后门,会调用 第一个存储数据堆块 指向的 地址 的值。
程序漏洞存在于当 delete
第一个存储堆块后,back_up
中的仍然保存了被删掉的堆块地址,也就是存在 UAF
漏洞。
利用分析
程序的利用思路很明显,就是要去调用后门。我们需要将 第一个存储堆块的的起始地址指向 一个 含有 one_gadget
的地址。
那么,我们可以利用 UAF
漏洞,将第一个存储堆块释放掉后,再第二次输入时将其申请回来。那么就可以修改 其起始地址,并写入 one_gadget
地址。
EXP
1 | from pwn import * |
hackphp
程序分析
在 zif_hack_php
函数中,当申请的堆块大小 0<=size<256或者size>512时,程序会将申请的 堆块 buf
使用 _efree
释放掉。但是却没有将 buf
堆块指针清空为0。所以这里存在一个 UAF
漏洞。
1 | void __fastcall zif_hackphp_create(zend_execute_data *execute_data, zval *return_value) |
利用分析
我们的初步思路,就是利用 UAF
漏洞来修改 _efree@Got
为system
,然后在堆块里写入 /readflag
,释放该堆块,即可执行 system('/readflag')
。
PHP
的堆机制与 ptmalloc
并不相似,PHP
的内存分配是一次性向系统申请开辟的,PHP
自身有个内存管理池,每次申请内存都会先在管理池中寻找合适的内存块,找不到才向系统申请内存。并且释放后的内存不交回给系统,而是放在内存管理池中继续使用。其管理机制,与 Kernel
中的slab/slub
分配器类似,分配的堆结构并没有堆头,而是与 内存桶对齐。
PHP
的空闲堆块,有一个 fd
指针指向下一个相同大小的堆块。并且,这里对该指针并没有做过多检查,我们可以理解为 2.27
下的 tcache
,可以直接使用 tcache poisoning
攻击,将fd
指向任意地址,实现分配堆块到任意地址。
此外,还得讲一下后面会用到的 str_repeat
对象,该对象也会向 php
申请堆内存。当创建一个 str_repeat("a",0x30)
对象时,系统会返回 0x50
的堆块空间,并且返回堆块偏移 0x18
地址处 存储的是 字符串的大小,后面才是 字符内容。如下所示:
1 | 0x7ffff7fca178 <buf>: 0x00007ffff5871280 0x0000000000000000 |
EXP
1 |
|
- 本文作者: A1ex
- 本文链接: http://yoursite.com/2021/03/06/2021-antCTF/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!