One interesting talk I’ve attended on OHM 2013 was titled “Returning Signals for fun and profit”. This talk was given by Erik Bosman. The talk refers to a new way exploiting binaries using the Linux signal’s stack frame.
This post could be summarised with the following words:
Return oriented programming has been proven to be a very effective way of circumventing data execution prevention features like ASLR present in modern operating systems, but the attacker needs to know the binary structure of the target executable in order to extract the so-called borrowed chunks of code. Here a generic binary exploitation technique will be covered which in some cases requires no knowledge about the running program.
Contexts and switches
A clever way to alter the control flow of the program by using context control will be described. A program execution context comprises the CPU’s registers, the program counter plus other operating system specific data at any point in time. A context switch is the process of storing and restoring the state of a process so that execution can be resumed from the same point at a later time. There are two types of context switches: software and hardware.
In this post I will cover the software context switch used in the process of a task returning from a software signal on 64bits. The Linux implementation of signals is fully POSIX-compliant, and the data used to recover the task from a previus state (the interrupted context: registers, program counter, signal mask, etc.) is be saved in a ucontext_t structure on the stack for the thread, along with the trampoline return address. Signal handlers installed with the SA_SIGINFO flag are able to examine this ucontext_t structure. The definition of the structure ucontext can be seen in the include/uapi/asm-generic/ucontext.h header file:
#ifndef __ASM_GENERIC_UCONTEXT_H #define __ASM_GENERIC_UCONTEXT_H struct ucontext { unsigned long uc_flags; struct ucontext *uc_link; stack_t uc_stack; struct sigcontext uc_mcontext; sigset_t uc_sigmask; /* mask last for extensibility */ }; #endif /* __ASM_GENERIC_UCONTEXT_H */
The structure sigcontext, which holds the saved state of the task (registers, etc) can be examined in the arch/x86/include/asm/sigcontext.h header file:
struct sigcontext { unsigned long r8; unsigned long r9; unsigned long r10; unsigned long r11; unsigned long r12; unsigned long r13; unsigned long r14; unsigned long r15; unsigned long di; unsigned long si; unsigned long bp; unsigned long bx; unsigned long dx; unsigned long ax; unsigned long cx; unsigned long sp; unsigned long ip; unsigned long flags; unsigned short cs; unsigned short gs; unsigned short fs; unsigned short __pad0; unsigned long err; unsigned long trapno; unsigned long oldmask; unsigned long cr2; /* * fpstate is really (struct _fpstate *) or (struct _xstate *) * depending on the FP_XSTATE_MAGIC1 encoded in the SW reserved * bytes of (struct _fpstate) and FP_XSTATE_MAGIC2 present at the end * of extended memory layout. See comments at the definition of * (struct _fpx_sw_bytes) */ void __user *fpstate; /* zero when no FPU/extended context */ unsigned long reserved1[8]; };
The struct _fpstate __user *fpstate is defined in the arch/x86/include/uapi/asm/sigcontext.h header file:
struct _fpstate { /* Regular FPU environment */ unsigned long cw; unsigned long sw; unsigned long tag; unsigned long ipoff; unsigned long cssel; unsigned long dataoff; unsigned long datasel; struct _fpreg _st[8]; unsigned short status; unsigned short magic; /* 0xffff = regular FPU data only */ /* FXSR FPU environment */ unsigned long _fxsr_env[6]; /* FXSR FPU env is ignored */ unsigned long mxcsr; unsigned long reserved; struct _fpxreg _fxsr_st[8]; /* FXSR FPU reg data is ignored */ struct _xmmreg _xmm[8]; unsigned long padding1[44]; union { unsigned long padding2[12]; struct _fpx_sw_bytes sw_reserved; /* represents the extended * state info */ }; };
Finally, the signal information structure struct siginfo is available in include/uapi/asm-generic/siginfo.h:
From include/uapi/asm-generic/siginfo.h:
typedef struct siginfo { int si_signo; int si_errno; int si_code; union { int _pad[SI_PAD_SIZE]; /* kill() */ struct { __kernel_pid_t _pid; /* sender's pid */ __ARCH_SI_UID_T _uid; /* sender's uid */ } _kill; /* POSIX.1b timers */ struct { __kernel_timer_t _tid; /* timer id */ int _overrun; /* overrun count */ char _pad[sizeof( __ARCH_SI_UID_T) - sizeof(int)]; sigval_t _sigval; /* same as below */ int _sys_private; /* not to be passed to user */ } _timer; /* POSIX.1b signals */ struct { __kernel_pid_t _pid; /* sender's pid */ __ARCH_SI_UID_T _uid; /* sender's uid */ sigval_t _sigval; } _rt; /* SIGCHLD */ struct { __kernel_pid_t _pid; /* which child */ __ARCH_SI_UID_T _uid; /* sender's uid */ int _status; /* exit code */ __ARCH_SI_CLOCK_T _utime; __ARCH_SI_CLOCK_T _stime; } _sigchld; /* SIGILL, SIGFPE, SIGSEGV, SIGBUS */ struct { void __user *_addr; /* faulting insn/memory ref. */ #ifdef __ARCH_SI_TRAPNO int _trapno; /* TRAP # which caused the signal */ #endif short _addr_lsb; /* LSB of the reported address */ } _sigfault; /* SIGPOLL */ struct { __ARCH_SI_BAND_T _band; /* POLL_IN, POLL_OUT, POLL_MSG */ int _fd; } _sigpoll; /* SIGSYS */ struct { void __user *_call_addr; /* calling user insn */ int _syscall; /* triggering system call number */ unsigned int _arch; /* AUDIT_ARCH_* of syscall */ } _sigsys; } _sifields; } __ARCH_SI_ATTRIBUTES siginfo_t;
As a summary, the the following kernel source files play a part in this technique:
arch/x86/include/uapi/asm/sigcontext32.h (32bits) struct _fpstate_ia32 struct sigcontext_ia32 arch/x86/include/uapi/asm/sigcontext.h struct _fpstate #ifndef __KERNEL__ struct sigcontext (contains void __user *fpstate; ) #endif /* !__KERNEL__ */ arch/x86/include/asm/sigcontext.h #include <uapi/asm/sigcontext.h> struct sigcontext ( contains void __user *fpstate; )</pre>
The good thing about storing this data on the stack is that the kernel does not need to remember the signals it delivered, but the bad point is that It can be forged.
When switching context, the kernel will execute setup_rt_frame, where all user-space registers are saved and the kernel stack frame return address is modified to point to the handler of the installed signal handler. A small sequence of code jumper is put on the user stack which will return us to kernel space once the signal handler has finished.
From arch/x86/kernel/signal.c:
static int setup_rt_frame(int sig, struct k_sigaction *ka, siginfo_t *info, struct pt_regs *regs) { int usig = signr_convert(sig); sigset_t *set = sigmask_to_save(); compat_sigset_t *cset = (compat_sigset_t *) set; /* Set up the stack frame */ if (is_ia32_frame()) { if (ka->sa.sa_flags & SA_SIGINFO) return ia32_setup_rt_frame(usig, ka, info, cset, regs); else return ia32_setup_frame(usig, ka, cset, regs); } else if (is_x32_frame()) { return x32_setup_rt_frame(usig, ka, info, cset, regs); } else { return __setup_rt_frame(sig, ka, info, set, regs); } }
From arch/x86/kernel/signal.c:
static int __setup_rt_frame(int sig, struct k_sigaction *ka, siginfo_t *info, sigset_t *set, struct pt_regs *regs) { struct rt_sigframe __user *frame; void __user *fp = NULL; int err = 0; frame = get_sigframe(ka, regs, sizeof(struct rt_sigframe), &fp); if (!access_ok(VERIFY_WRITE, frame, sizeof(*frame))) return -EFAULT; if (ka->sa.sa_flags & SA_SIGINFO) { if (copy_siginfo_to_user(&frame->info, info)) return -EFAULT; } put_user_try { /* Create the ucontext. */ if (cpu_has_xsave) put_user_ex(UC_FP_XSTATE, &frame->uc.uc_flags); else put_user_ex(0, &frame->uc.uc_flags); put_user_ex(0, &frame->uc.uc_link); err |= __save_altstack(&frame->uc.uc_stack, regs->sp); /* Set up to return from userspace. If provided, use a stub already in userspace. */ /* x86-64 should always use SA_RESTORER. */ if (ka->sa.sa_flags & SA_RESTORER) { put_user_ex(ka->sa.sa_restorer, &frame->pretcode); } else { /* could use a vstub here */ err |= -EFAULT; } } put_user_catch(err); err |= setup_sigcontext(&frame->uc.uc_mcontext, fp, regs, set->sig[0]); err |= __copy_to_user(&frame->uc.uc_sigmask, set, sizeof(*set)); if (err) return -EFAULT; /* Set up registers for signal handler */ regs->di = sig; /* In case the signal handler was declared without prototypes */ regs->ax = 0; /* This also works for non SA_SIGINFO handlers because they expect the next argument after the signal number on the stack. */ regs->si = (unsigned long)&frame->info; regs->dx = (unsigned long)&frame->uc; regs->ip = (unsigned long) ka->sa.sa_handler; regs->sp = (unsigned long)frame; /* Set up the CS register to run signal handlers in 64-bit mode, even if the handler happens to be interrupting 32-bit code. */ regs->cs = __USER_CS; return 0; }
The above code restores the exact user-register contents into the kernel stack frame (including the return address and flags register) and executes a normal return from syscall, bringing the execution flow back to the original code that jumped to the signal handler.
The stack frame image that Linux configures for signal returning assembles the following diagram:
[-------------] [ STACK ] [-------------] [ FPSTATE ] [-------------] [ UCONTEXT ] [-------------] [ SIGINFO+arg ] [-------------]
A detailed view on 64bits should assemble the following diagram:
[------------------------------------------] 0x00 [ rt_sigreturn() | uc_flags ] 0x10 [ &uc | uc_stack.ss_sp ] 0x20 [ uc_stack.ss_flags | uc_stack.ss_size ] 0x30 [ r8 | r9 ] 0x40 [ r10 | r11 ] 0x50 [ r12 | r13 ] 0x60 [ r14 | r15 ] 0x70 [ rdi | rsi ] 0x80 [ rbp | rbx ] 0x90 [ rdx | rax ] 0xA0 [ rcx | rsp ] 0xB0 [ rip | eflags ] 0xC0 [ cs / gs / fs | err ] 0xD0 [ trapno | oldmask (unused) ] 0xE0 [ cr2 (segfault addr) | &fpstate ] 0xF0 [ __reserved | sigmask ] [------------------------------------------]
Exploitation
By forging the contents of the struct sigcontext, which holds the saved registers values, setting the $rax register to the desired syscall number, setting the systemcall args properly and finally setting the program counter $rip pointing to an >syscall; ret gadget, we can effectively modify the execution flow of the program and execute the desired systemcall. Also, several SigRet frames can be chained by using the value of the saved $rsp register of the returning frame and the gadget syscall;ret.
- rax => syscall_number
- rdi => arg1
- rsi => arg2
- rdx => arg3
- r10 => arg4
- r8 => arg5
- r9 => arg6
- rip => syscall;ret
- rsp => next_frame
- cs=0×33 / gs=0×0 / fs=0×0
- &fpstate = NULL
The forged stack structure should assemble the following:
[------------------------------------------] 0x00 [ rt_sigreturn() | uc_flags ]* 0x10 [ &uc | uc_stack.ss_sp ] 0x20 [ uc_stack.ss_flags | uc_stack.ss_size ] 0x30 [ arg5 | arg6 ]* 0x40 [ arg4 | r11 ]* 0x50 [ r12 | r13 ] 0x60 [ r14 | r15 ] 0x70 [ arg1 | arg2 ]* 0x80 [ rbp | rbx ] 0x90 [ arg3 | syscall_number ]* 0xA0 [ rcx | next_frame ]* 0xB0 [ syscall;ret | eflags ]* 0xC0 [ cs=0x33/gs=0/fs=0 | err ]* 0xD0 [ trapno | oldmask (unused) ] 0xE0 [ cr2 (segfault addr) | &fpstate ]* 0xF0 [ __reserved | sigmask ] [------------------------------------------]
As a summary, for SigReturn oriented programming, the following requirements are needed:
- Controllable stack
- Knowing the address of a system call gadget
- A known writable address
- Known file descriptor
- Control over the RAX register
More information:
- man(2) getcontext
- man(2) setcontext
- Returning signals for fun and profit
- Context switch
- The Linux Signal Handling Model
- The GNU C Library – Signal Handling
- Wikipedia – Context switch
- Wikipedia – Getcontext
- Wikipedia – Setcontext