ret2csu
仅供个人学习参考
原理
漏洞来源于_libc_csu_init 这个文件,它原本是用于对程序进行初始化,但是它的一些代码片段可以被我们利用
如图
下面我们将0x4007d0 - 0x4007e4的片段称为gadget2,0x4007e6 - 0x4007f4的片段称为gadget1
利用gadget1,我们可以实现:对rbx,rbp,r12,r13,r14,r15传参
利用gadget2,我们可以实现:将r13的值赋给rdx,将r14的值赋给rsi,将r15d的值赋给edi
即我们可以对rbx,rbp,r12,rdx,rsi,edi传参
注意这段汇编
1 | cmp rbx, rbp |
它的作用是比较rbx和rbp的值,如果二者不相等,就跳转到loc_4007D0这个位置,我们一般不希望它跳转,所以我们一般令rbp=1,rbx=0(因为上一句汇编是add rbx, 1 ,即使rbx的值加1)
补充:r15d和rdi都是r15的低32位部分,区别在于,r15d用于32位模式(指处理器模式而不是操作系统架构!!),rdi用于64位模式,并且RDI可以访问更大的内存空间
1 | call ds:(__frame_dummy_init_array_entry - 600E10h)[r12+rbx*8] |
ds: 是段寄存器前缀,用于指定数据段寄存器。它表示要访问的数据存储在数据段中。这句汇编代码的意思是call __frame_dummy_init_array_entry - 600E10h这个地址的函数,该地址通过r12+rbx*8计算得出,并且数据存储在数据段中。
利用
VNCTF2022公开赛clear_got
可以看到存在栈溢出,无binsh字段,无system函数,开启了nx保护
注意这个函数
1 | memset(&qword_601008, 0, 0x38uLL); |
它将&qword_601008往上0x38大小的数据覆写为0,即下面这段数据
发现这段数据是got表的内容,即函数执行到这里,got表的内容就被清空了
于是我们考虑利用系统调用
1 | execve("/bin/sh",NULL,NULL) |
64位下,其调用号为0x3b
回顾一下:x86_64:syscall调用号存放于rax syscall参数(传参顺序):rdi rsi rdx r10 r8 r9
首先我们要能控制rax,其次往.bss段写入’/bin/sh’,最后分别控制rdi和rsi
如何控制rax?
就本题而言,没有出现’pop rax ret’字段,所以我们要另辟蹊径
*调用 read 函数*
mov rax, 0 ; 系统调用号为 0 表示 read
mov rdi, fd ; 文件描述符
mov rsi, buf ; 缓冲区地址
mov rdx, buf_len ; 读取的最大字节数
syscall
*检查返回值*
cmp rax, 0 ; 如果 rax 大于 0,则表示成功读取了字节数
jl error ; 如果 rax 小于 0,则表示发生了错误
; 在这里可以处理读取的数据
*正常退出*
mov eax, 60 ; 系统调用号为 60 表示 exit
xor edi, edi ; 退出码为 0
syscall
write同理
可以看出在x86_64架构下,read和write函数的返回值会存放在rax寄存器,所以我们可以利用这两个函数来控制rax
康康gadget
一堆r12 13啥啥的,所以我们得利用_libc_csu_init 来构造rop链
在x86_64架构下,main函数的返回值通常存放在寄存器 rax 中,而main函数正常执行最后有return 0,故rax里面已经存放着0,并且read函数的系统调用号刚好是0,所以我们可以直接syscall read
exp如下:
1 | from pwn import * |