格式化字符串漏洞

1. 漏洞原理

在 C 语言中,printf 这一类函数会根据传入的 格式化字符串(format string)解析参数,例如:

1
printf("hello %s\n", name);

但如果程序员直接写成:

1
printf(input);

而没有提供额外参数,用户输入就会直接被当成格式化字符串解析。这时攻击者可以通过特殊的格式符(如 %p%s%n)来读取或写入内存,从而造成严重漏洞。


2. 基本利用方式

(1) %p —— 打印栈上的地址

  • %p 会从栈上依次取出参数并以指针形式打印,不会解引用。
  • 在没有参数的情况下,printf 还是会从栈上取值,因此我们能通过连续输入多个 %p 来泄露栈上的内容。

示例:

1
2
输入: %p  #打印二参的地址               
输入: %7$p #打印第八个参数的地址

(2) %s —— 打印指定地址的字符串

  • %s 会把栈上的值当作 指针,然后尝试打印这个地址对应的字符串(解引用)。

示例:

1
输入: %4$s #打印第五个参数解引用后的值

表示取栈上第 5 个参数的值作为地址,并打印字符串。
如果这个地址正好是 flag 所在位置,就能直接泄露内容。


(3) %n —— 写入内存

  • %n 会把 当前已输出的字符数 写入到对应的参数地址中。
  • 这意味着攻击者可以 往任意地址写入整数,从而修改内存关键位置(如 GOT 表)。

示例:

1
输入: AAAA%n

会将输出的字符数(4)写入栈上取出的地址。

配合偏移控制:

1
输入: %10$n

会将输出字符数写入栈上第 11个参数所指向的地址。

用途:

  • 任意地址写(最关键的利用手段)。
  • 可用来覆盖 GOT 表条目(got表可写时,即partial RELRO),把函数指针劫持到 system

3. 常见利用流程

  1. 信息泄露

    • %p 打印大量内容,找到栈上或 GOT/PIE 地址。
    • %s 配合偏移泄露内存,获取 libc 地址。
  2. 任意写

    • %n 向 GOT 表写入 system 地址,可以用fmtstr_payload(offset, writes, numbwritten, write_size)这个函数改写got表。offset是字符串偏移值,使用pwndbg调试找到输入所在的参数位置,writes是键值对,形如{printf.got:system.plt},键是要改写的地址,值是目标函数的plt调用地址。后面两个参数一般默认。

    • 示例:payload=fmtstr_payload(6,{0x601030:0x40085D})

      0x601030指printf的.got.plt地址,0x40085D指system的.plt地址。

  3. 拿 shell

    • 覆盖 printf 的 GOT 为 system,然后再次调用时输入 /bin/sh

4. 小技巧

  • 指定参数位置
    %7$p → 打印第 8个参数;
    %12$s → 打印第 13个参数指向的字符串。
    这样能精确控制泄露和写入。

  • 控制写入的值
    %n 写入的是“已经打印的字符数”,所以需要通过 %xxx 的方式控制输出长度。例如:

    1
    AAAA%1234c%10$n

    会在输出 AAAA 后打印 1234 个字符,总共 1238 个字符,然后把 1238 写入第 11 个参数的地址。

  • 关于offset和%x$的取值
    exp
    offset=x=6 + 上图最左边那列的编号数


5. 总结

  • %p → 泄露地址。
  • %s → 任意地址解引用。
  • %n → 任意地址写(修改 GOT、函数指针)。

利用链:
信息泄露 → 找 libc → 任意写 → 劫持控制流 → getshell。