格式化字符串基础
基础知识
常见格式化字符串函数
| 函数 | 基本介绍 |
|---|---|
| printf | 输出到stdout |
| fprintf | 输出到指定FILE流 |
| vprintf | 根据参数列表格式化输出到stdout |
| vfprintf | 根据参数列表格式化输出到FILE流 |
| sprintf | 输出到字符串 |
| snprintf | 输出指定字节数到字符串 |
| vsprintf | 根据参数列表格式化输出到字符串 |
| vsnprintf | 根据参数列表格式化输出指定字节到字符串 |
常用格式化字符串形式
1 | %[parameter][flags][field width][.precision][length]type |
parameter:n$,获取格式化字符串中的指定第 n 个参数flags:在width设置后指定可以用来作为填充的内容之类的内容field width:输出的最小宽度precision:输出的最大长度- length,输出的长度
hh,输出一个字节h,输出一个双字节
- type
d/i,有符号整数u,无符号整数x/X,16进制o,8进制s,所有字节c,char类型单个字符p,void *型,输出对应变量的值。printf("%p",a)用地址的格式打印变量a的值,printf("%p", &a)打印变量a所在的地址。- n,不输出字符,但是把已经成功输出的字符个数写入对应的整型指针参数所指的变量。
hhn写一字节hn写两字节n写四字节ln32 位写四字节,64 位写八字节lln写八字节
原理验证
示例程序:
1 |
|
32位
编译命令:
1 | gcc test.c -g -m32 -o test |
输出结果:
1 | aaaa.0xf7ffc988.0xffffcf2a.0x56555595.0xffffcf2a.0xf7ffc984.0x61616161.0x2e70252e |
栈结构:
1 | 00:0000│ esp 0xffffcee0 —▸ 0xffffcef8 ◂— 'aaaa.%p.%p.%p.%p.%p.%p.%p' |
自上而下依次是参数0~6,参数0为格式化字符串地址,而格式化字符串前4字节又作为参数6(由于栈结构不同,需要视情况而定)。因此如果将格式化字符串合适的位置设置为目标地址就可以对该地址的数据进行操作。
64位
编译命令
1 | gcc test.c -g -m64 -o test |
输出结果:
1 | aaaa.0x7fffffffde78.0x70.0x555555554770.0x7ffff7dced80.0x7ffff7dced80.0x2e70252e61616161.0x70252e70252e7025 |
寄存器:
1 | RAX 0x0 |
栈结构:
1 | 00:0000│ rdi rsp 0x7fffffffdd20 ◂— 'aaaa.%p.%p.%p.%p.%p.%p.%p' |
由于 64 位程序先使用 rdi、rsi、rdx、rcx、r8、r9 寄存器作为函数参数的前六个参数,多余的参数会依次压在栈上,因此前 6 个输出的为寄存器中的值(aaaa 看做是格式化字符串参数),格式化字符串前 8 个字节作为参数 6 。
快速手算offset:
64位下,调试到printf函数,stack查看要泄露的内容距离栈顶的偏移:

此时canary据栈顶0x21,所以是0x21+1+5(寄存器) = 39
(解释:+1是因为stack命令从0开始计数,+5是因为rid之后的五个寄存器要用来传参,rdi不数进来(%1$p打印的是rsi的值可以证明(%1$p=%p)))
或者直接用工具:

- Title: 格式化字符串基础
- Author: Chiu
- Created at : 2024-07-31 13:44:54
- Updated at : 2024-07-31 13:46:19
- Link: https://github.com/Idealist17/github.io/2024/07/31/格式化字符串基础/
- License: This work is licensed under CC BY-NC-SA 4.0.
Comments