这是一份迟来的 wp,最近一直在忙考试什么的,只能抽时间来复现一下题目。首先要吐槽一下 pcb 的线下比赛,虽然是第一次举办,但是平台也要测试好再上,否则会出现比赛一开始平台就崩了的尴尬情况 ORZ。
不过线上赛的题目质量还不错,学到了一些东西。
pwn
1. OverInt
pwn 的签到题目,漏洞可以说是摆在脸上了(题目名字也是提示),一处任意地址写:
不过在之前有两步验证需要通过,第一个是类似 hash 的函数,要求返回值是 35,第二个是求和函数,要求输入四个数,并且它们的和要等于 0x20633372,另外,0x20633372 和最开始输入的数之和要小于等于 4。
存在一个漏洞,通过整数溢出来绕过验证。
第一个验证可以爆破出来,然后构造一个整数溢出,让最后的和的二进制最高位变成一,由于是 int 型变量,那么这个数就会被视为负数从而绕过验证。
直接上脚本
1 | from pwn import * |
没有太多需要解释的,唯一一点就是需要注意任意地址写每次只能写一个字节,而且我们需要利用这个漏洞两次,第一次用来泄露地址,第二次触发漏洞。
2. code
和上一道题类似,需要先过一个 hash 验证,然后就是一个标准的栈溢出,通过爆破 hash 验证,爆破脚本如下
1 | solver = "wabcdefghijklmnopqrstuvxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" |
最后解出来密码是 wyBTs
接着就可以通过经典的栈溢出来拿 shell 了,需要注意一点,程序中有 seccomp 的保护,直接使用 one_gadget 不行,需要用 system 函数。
1 | from pwn import * |
3. random (FSOP 攻击)
本题的考点是之前学过的 IO_FILE 的利用,不过之前的例子(HCTF 2018 the_end)是直接修改了 vtable 中的函数指针,但是本题需要我们先将 vtable 迁移到可控的内存,然后伪造函数指针,达到利用效果。
先用 IDA 看一下程序的代码
重点在函数 sub_D20 ,注册了三个虚函数
也就是说,相当于构建了一个菜单,输入 1 打开 /dev/urandom 文件
输入 2 会进入到主要逻辑
输入 3 关闭设备文件
主要漏洞出在第三个函数,我们需要了解一个知识点,那就是 fopen 和 fclose,当调用了 fopen 之后,内核会打开一个磁盘上的文件,然后使用文件指针(就是 FILE* 类型)来标记打开的文件,并且将这个结构体存储在堆上,当调用 fclose 时,内核的关键操作是 free 之前创建在堆上的堆块,清除已经打开的文件指针,但是,本题将 fopen 所创建的堆块地址给了一个全局变量 stream,而在 fclose 之后没有清除这个指针,造成 UAF,我们可以尝试释放 stream 之后修改堆块的内容来劫持 vtable。
本题所有保护都开启了,首先要做的是泄露地址,仔细观察,在 compare 函数中有一个格式化字符串漏洞,但是由于使用了 printf_chk,所以并不能拿来修改地址,只能泄露一些地址。
一个知识点是,当 scanf 输入较多,会在堆上申请空间并存储输入(当然栈上也有)这就导致了泄露出的地址位置比较奇怪,需要多次测试才能找到合适的地址。
我们需要找到三个必要的地址,libc基地址、程序的地址以及堆的地址。
泄露出地址之后,开始构建漏洞利用环境,将 stream close,接着调用 compare,当 scanf 的时候,就会将刚刚 free 的 FILE 结构体给分配回来,并且可以写入任意的值,在这里我们开始伪造 FILE 结构体,并且将 vtable 迁移到堆上可控内存中,伪造好 vtable 函数指针,当执行 fread 时会出错,程序跑到 _IO_flush_all_lockp,然后调用伪造的函数(one_gadget),即可拿到 shell。
关于 IO_FILE 的一些分析在前面的文章中说过了,所以直接给出脚本
PS: 借用了 Lilac 战队师傅们的脚本,师傅们把 IO_FILE 的一些结构体封装在了一个类里面,用起来很方便。
IO_FILE 类
1 | from pwn import * |
EXP
1 | from pwn import * |
4. treasure
本题有点意思,第一个函数的代码:
1 | void *settreasure() |
调用 mmap 在一个随机的地址分配了空间,然后把一段 shellcode 拷贝到新分配的空间中,随后清除原来的 shellcode。
第二个函数:
1 | __int64 treasure() |
这是程序的主要逻辑,简单的说就是一次可以执行不大于 9 个字节的任意代码,要求在这种情况下拿到 shell。
保护只开了 NX。
这道题在比赛的时候想了很多做法,但是都失败了,现在回想原因是掉进了出题人的坑里面,总是先要找办法命中出题人的 shellcode,但实际上,我们可以利用这 9 个字节来做一个系统调用,read 自己的 shellcode。
首先,要通过动态调试来看看在跳转到 9 字节 shellcode 时,环境是怎样的
寄存器方面,有用的地址在 RDX 中,里面存储着 shellcode 的地址,且位于可读可写可执行段。
栈上没有什么有用的地址,所以,我们就利用 RDX 构造 read 系统调用。
首先在网上找一下 64 位 linux 的系统调用表,发现 0 号 syscall 就是 read
根据参数表,可以写出以下汇编,来满足 syscall 的要求
1 | push rdx |
这几句汇编转换成机器码之后的长度是 8 个字节,满足要求,实现的功能是向 RWX 段写入自己的 shellcode。
接下来就是构造真正的 shellcode 了,如果直接把 shellcode 写上去的话,会发现程序直接崩溃退出,原因是修改了正在执行中的代码,rip 指向了机器码中的一个非法位置,从而抛出异常。
所以,我们需要给 shellcode 添加一点前缀,加上一些 \x90,这样,rip 就会命中一堆 nop ,从而正确的执行到 shellcode(又叫做雪橇攻击)。
脚本
1 | from pwn import * |
- 本文作者: Catalpa
- 本文链接: https://wzt.ac.cn/2019/01/20/pcb2018-online/
-
版权声明:
本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。