原理
在64位程序中,函数的前6个参数通过寄存器传递。
这就需要我们通过gadgets操作寄存器,但是我们很难找到每个寄存器对应的gadgets。
这时,我们可以利用x64下的__libc_csu_init中的gadgets。
这个函数是用来对libc进行初始化操作的,而一般程序都会调用libc。
在Linux函数调用中:
前6个参数**从左向右依次放入rdi,rsi,rdx,rcx,r8,r9。
超出6个的参数从右向左放入栈中。
例子
这里使用ctf wiki上的一个例题(点我下载)。
checksec查看保护
查看保护发现开启了NX保护,是64位程序。
IDA分析
发现有一个read函数,存在栈溢出漏洞。
没有其它函数信息,也没有system和/bin/sh。
这就需要我们自己构造system和/bin/sh:
寻找gadgets
同时寻找6个寄存器的gadgets比较困难,这个程序使用了libc。
我们可以去__libc_csu_init函数寻找gadgets。
从0x040061A——0x0400622,我们可以控制寄存器rbx、rbp、r12、r13、r14、r15的数据。
从0x0400600——0x0400609我们可以:
- 将r13的值赋给rdx
- 将r14的值赋值给rsi
- 将r15d赋值给edi(只能控制rdi寄存器的低32位,本程序rdi的高32位为0)
这三个寄存器是参数传递的前三个寄存器。
我们能够控制r13、r14、r15,也就能间接控制rdx、rsi、edi。
除去这些寄存器,rbx、rbp和r12也可以根据需要被我们合理利用。
除此之外,发现0x40060D——0x400614部分,进行条件跳转:
如果 rbx + 1 != rbp 就跳转,不执行下面的代码。
我们可以控制rbp和rbx的关系,让它不跳转,可以设置rbx = 0, rbp = 1。
剩下的rbx和r12可以用来控制call命令。
构造思路
csu函数的执行流程:
填充垃圾字符
- 跳转到csu_end_addr处执行
将栈上的数据压入对应的寄存器(rbx、rbp、r12、r13、r14、r15)
- ret指令跳转到csu_start_addr处执行
将数据写入rdx、rsi、edi(前三个参数的寄存器)
- 调用地址为r12 + rbx * 8的函数
填充垃圾字符(平衡堆栈)
- ret指令返回last。
三次构造payload进行注入:
0.获取bss基地址、main函数地址、read和write函数的got表地址
1.第一次构造:
利用gadgets泄露write的真实地址。
2.第二次构造:
向bss段写入**/bin/sh**字符串。
3.第三个构造:
执行**execve(‘/bin/sh’)**。
编写脚本
1 | from pwn import * |