格式化字符串基础

Chiu Lv4

基础知识

常见格式化字符串函数

函数 基本介绍
printf 输出到stdout
fprintf 输出到指定FILE流
vprintf 根据参数列表格式化输出到stdout
vfprintf 根据参数列表格式化输出到FILE流
sprintf 输出到字符串
snprintf 输出指定字节数到字符串
vsprintf 根据参数列表格式化输出到字符串
vsnprintf 根据参数列表格式化输出指定字节到字符串

常用格式化字符串形式

1
2
%[parameter][flags][field width][.precision][length]type
例如 %2$08hx %.8lf
  • parametern$ ,获取格式化字符串中的指定第 n 个参数
  • flags:在 width 设置后指定可以用来作为填充的内容之类的内容
  • field width:输出的最小宽度
  • precision:输出的最大长度
  • length,输出的长度
    • hh,输出一个字节
    • h,输出一个双字节
  • type
    • d/i,有符号整数
    • u,无符号整数
    • x/X,16进制
    • o,8进制
    • s,所有字节
    • c,char类型单个字符
    • pvoid * 型,输出对应变量的值。printf("%p",a) 用地址的格式打印变量 a 的值,printf("%p", &a) 打印变量 a 所在的地址。
    • n,不输出字符,但是把已经成功输出的字符个数写入对应的整型指针参数所指的变量。
      • hhn 写一字节
      • hn 写两字节
      • n 写四字节
      • ln 32 位写四字节,64 位写八字节
      • lln 写八字节

原理验证

示例程序:

1
2
3
4
5
6
7
#include<stdio.h>

int main() {
char s[100] = "aaaa.%p.%p.%p.%p.%p.%p.%p";
printf(s);
return 0;
}

32位

编译命令:

1
gcc test.c -g -m32 -o test

输出结果:

1
aaaa.0xf7ffc988.0xffffcf2a.0x56555595.0xffffcf2a.0xf7ffc984.0x61616161.0x2e70252e

栈结构:

1
2
3
4
5
6
7
00:0000│ esp  0xffffcee0 —▸ 0xffffcef8 ◂— 'aaaa.%p.%p.%p.%p.%p.%p.%p'
01:00040xffffcee4 —▸ 0xf7ffc988 (_rtld_global_ro+136) ◂— 0x8e
02:00080xffffcee8 —▸ 0xffffcf2a ◂— 0x0
03:000c│ 0xffffceec —▸ 0x56555595 (main+24) ◂— add ebx, 0x1a3f
04:00100xffffcef0 —▸ 0xffffcf2a ◂— 0x0
05:00140xffffcef4 —▸ 0xf7ffc984 (_rtld_global_ro+132) ◂— 0x6
06:0018│ eax 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
RAX  0x0
RBX 0x0
RCX 0x555555554770 (__libc_csu_init) ◂— push r15
RDX 0x70
RDI 0x7fffffffdd20 ◂— 'aaaa.%p.%p.%p.%p.%p.%p.%p'
RSI 0x7fffffffde78 —▸ 0x7fffffffe21b
R8 0x7ffff7dced80 (initial) ◂— 0x0
R9 0x7ffff7dced80 (initial) ◂— 0x0
R10 0x0
R11 0x0
R12 0x5555555545a0 (_start) ◂— xor ebp, ebp
R13 0x7fffffffde70 ◂— 0x1
R14 0x0
R15 0x0
RBP 0x7fffffffdd90 —▸ 0x555555554770 (__libc_csu_init) ◂— push r15
RSP 0x7fffffffdd20 ◂— 'aaaa.%p.%p.%p.%p.%p.%p.%p'
RIP 0x555555554747 (main+157) ◂— call 0x555555554580

栈结构:

1
2
3
4
5
00:0000│ rdi rsp  0x7fffffffdd20 ◂— 'aaaa.%p.%p.%p.%p.%p.%p.%p'
01:00080x7fffffffdd28 ◂— '%p.%p.%p.%p.%p.%p'
02:00100x7fffffffdd30 ◂— '.%p.%p.%p'
03:00180x7fffffffdd38 ◂— 0x70 /* 'p' */
04:00200x7fffffffdd40 ◂— 0x0

由于 64 位程序先使用 rdirsirdxrcxr8r9 寄存器作为函数参数的前六个参数,多余的参数会依次压在栈上,因此前 6 个输出的为寄存器中的值(aaaa 看做是格式化字符串参数),格式化字符串前 8 个字节作为参数 6 。

快速手算offset:

64位下,调试到printf函数,stack查看要泄露的内容距离栈顶的偏移:

image-20240225170345531

此时canary据栈顶0x21,所以是0x21+1+5(寄存器) = 39

(解释:+1是因为stack命令从0开始计数,+5是因为rid之后的五个寄存器要用来传参,rdi不数进来(%1$p打印的是rsi的值可以证明(%1$p=%p)))

或者直接用工具:

image-20240225170850380

  • 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