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];
posted @ 2025-07-31 19:27  shanlinchuanze  阅读(18)  评论(0)    收藏  举报