7-31-知识点学习
利用setcontext来进行ORW
当遇到堆题禁用execve时我们就需要用ORW的方法来读取flag,那么堆题如何实现栈迁移来进行ORW就成了一个问题,而利用setcontext是解决这个问题的一个方案
什么是setcontext
setcontext是libc中的一个函数,而它的汇编代码中含有大量的 mov <寄存器> 这类汇编语句,如下图(注:setcontext以glibc2.29为分界线,2.29以下和2.29及以上有较大不同):
; 条件跳转:如果无进位(CF=0),跳转到 loc_52000
jnb short loc_52000 ; Jump if Not Below (CF=0)
; 恢复 FPU(浮点单元)环境
mov rcx, [rdi+0E0h] ; 加载 FPU 环境地址到 RCX = [rdi + 0xE0]
fldenv byte ptr [rcx] ; 从 [RCX] 恢复 FPU 状态(浮点寄存器、控制字等)
; 恢复 MXCSR(SSE 控制状态)
ldmxcsr dword ptr [rdi+1C0h] ; 从 [rdi + 0x1C0] 加载 MXCSR(SSE 浮点控制寄存器)
; 恢复通用寄存器和栈指针
mov rsp, [rdi+0A0h] ; 恢复栈指针 RSP = [rdi + 0xA0]
mov rbx, [rdi+80h] ; 恢复 RBX = [rdi + 0x80]
mov rbp, [rdi+78h] ; 恢复 RBP(帧指针)= [rdi + 0x78]
mov r12, [rdi+48h] ; 恢复 R12 = [rdi + 0x48]
mov r13, [rdi+50h] ; 恢复 R13 = [rdi + 0x50]
mov r14, [rdi+58h] ; 恢复 R14 = [rdi + 0x58]
mov r15, [rdi+60h] ; 恢复 R15 = [rdi + 0x60]
; 准备跳转到保存的 RIP(程序计数器)
mov rcx, [rdi+0A8h] ; 加载 RIP 到 RCX = [rdi + 0xA8]
push rcx ; 将 RIP 压栈,后续通过 `ret` 跳转
; 恢复调用约定相关的寄存器
mov rsi, [rdi+70h] ; 恢复 RSI = [rdi + 0x70]
mov rdx, [rdi+88h] ; 恢复 RDX = [rdi + 0x88]
mov rcx, [rdi+98h] ; 恢复 RCX = [rdi + 0x98]
mov r8, [rdi+28h] ; 恢复 R8 = [rdi + 0x28]
mov r9, [rdi+30h] ; 恢复 R9 = [rdi + 0x30]
mov rdi, [rdi+68h] ; 恢复 RDI = [rdi + 0x68](最后恢复,避免覆盖参数)
; 清理并返回(模拟跳转到保存的 RIP)
xor eax, eax ; 清零 EAX(通常表示成功返回)
retn ; 弹出栈顶的 RIP 并跳转
可以看到其中有非常多的控制寄存器的mov指令,它们都利用 rdi+<偏移值> 来获取具体的数值,所以如果我们可以控制 rdi寄存器 随后将程序跳转到 setcontext+53(也就是图中mov rsp,[rdi+RSP_OFFSET]的位置) 这样我们就可以控制包括 rip rsp 这两个重要寄存器
基本思路
控制执行流之后就有两种思路:
是直接控制程序执行流去执行ROP链
是先用 mprotect 函数开辟一段可读可写可执行的空间再跳到上面去执行 shellcode
这里我们主要讲解执行ROP链打ORW的思路:
控制 rsp寄存器 到我们布置好 ROP的地址,然后控制 rip 为 ret地址
如何控制rip并开始ORW?
通过观察上图发现最后一条指令是ret而其中有一个push rcx的操作,因此可以判断ret返回的地方就是rcx中存储的地址
那么此时栈中的情况就是下面的这样、
ORW部分的代码
... ...
ORW部分的代码
rcx中的值 <-- rsp
-----我是分割线-----
ret指令 <-- rip
而setcontext最后是一个ret指令那么就是
ORW部分的代码
... ...
ORW部分的代码 <-- rsp
-----我是分割线-----
rcx中存放的指令 <-- rip
那么为了能够开始我们的ORW,所以我们需要控制rcx为ret指令的地址也就是控制rdi+0xa8h这个地方为ret指令的地址
glibc2.29之前的一般利用思路:
想办法修改free_hook为setcontext+53
申请一个chunk1
chunk1_usr_addr+0xa0=rsp迁移地址;
chunk1_usr_addr+0xa8=ret指令地址;
申请一个chunk2
chunk2中存放ORW的ROP链,同时这也是rsp需要迁移的地方
free(chunk1) 此时的rdi恰好指向chunk_usr_addr,然后可以触发后续一些列的东西
glibc2.29之后的改变
下面来看看glibc2.29之后setcontext做出了哪些改变
; 恢复 MXCSR 寄存器(浮点运算控制状态)
ldmxcsr dword ptr [rdx+1C0h] ; 从内存 [rdx + 0x1C0] 加载 MXCSR 控制字
; 恢复栈指针和通用寄存器
mov rsp, [rdx+0A0h] ; 恢复栈指针 RSP = [rdx + 0xA0]
mov rbx, [rdx+80h] ; 恢复 RBX = [rdx + 0x80]
mov rbp, [rdx+78h] ; 恢复 RBP = [rdx + 0x78](帧指针)
mov r12, [rdx+48h] ; 恢复 R12 = [rdx + 0x48]
mov r13, [rdx+50h] ; 恢复 R13 = [rdx + 0x50]
mov r14, [rdx+58h] ; 恢复 R14 = [rdx + 0x58]
mov r15, [rdx+60h] ; 恢复 R15 = [rdx + 0x60]
; 准备跳转到保存的 RIP(程序计数器)
mov rcx, [rdx+0A8h] ; 加载 RIP 到 RCX = [rdx + 0xA8]
push rcx ; 将 RIP 压栈,后续通过 `ret` 跳转
; 恢复其他调用约定相关的寄存器
mov rsi, [rdx+70h] ; 恢复 RSI = [rdx + 0x70]
mov rdi, [rdx+68h] ; 恢复 RDI = [rdx + 0x68]
mov rcx, [rdx+98h] ; 恢复 RCX = [rdx + 0x98]
mov r8, [rdx+28h] ; 恢复 R8 = [rdx + 0x28]
mov r9, [rdx+30h] ; 恢复 R9 = [rdx + 0x30]
mov rdx, [rdx+88h] ; 恢复 RDX = [rdx + 0x88]
; 清理并返回(模拟跳转到保存的 RIP)
xor eax, eax ; 清零 EAX(可能表示成功返回)
retn ; 弹出栈顶的 RIP 并跳转
调整:从rdi+偏移变成了rdx+偏移
而rdi+偏移变成了rdx+偏移这是一个很大的改变,因为我们打堆题一般利用setcontext的思路是修改free_hook为setcontext+53,然后这个时候rdi恰好指向了 chunk_usr_addr但是如果从 rdi 到了 rdx,那么我们该如何控制 rdx 就成了一个问题
由于在free的时候我们可以控制的只有rdi那么我们就需要一个既可以通过rdi来控制rdx,又可以通过rdi来控制rip的gadget
那么真的会有这样的gadget吗?(有的兄弟,有的)
在glibc2.29中有类似这样的gadget,既可以控制rdx,最后又可以控制rip
mov rdx, [rdi+0x8]; mov rax, [rdi]; mov rdi, rdx; jmp rax;
mov rdx, [rdi+0x8]; mov [rsp], rax; call qword ptr [rdx+0x20];
这里我们就简单讲一下第二个gadget要咋用
一般我们需要构造三个堆块,三个堆块的作用分别如下
将free_hook赋值为上面mov rdx,qword ptr [rdi+8];
chunk1:
prev_size, size
setcontext+53, rdi_addr[一般是chunk2_usr_data-0xa0] (这里需要计算好,rdi+0xa0的位置是rsp_addr也就是chunk3_usr_data,rdi+0xa8得是rip的地址[一般放ret])
chunk2:
orw_addr(chunk3_usr_data), ret_addr
chunk3:
prev_size, size
... ... ... ...
pop_r_ret, XXXX --> orw 的ROP链
然后再glibc2.31的setcontext再次做出了调整 mov rsp, [rdx+0xa0h] 的位置变成了setcontext+61但是这个改变并不算大,但是在glibc2.31中上面我们发现的两个牛逼gadget被制裁了,只剩下一个了
mov rdx, [rdi+0x8]; mov [rsp], rax; call qword ptr [rdx+0x20];

浙公网安备 33010602011771号