1.测试代码
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int init_func(){
setvbuf(stdin, 0, 2, 0);
setvbuf(stdout, 0,2 ,0);
setvbuf(stderr, 0, 2, 0);
return 0;
}
int dofunc(){
char buf2[0x8];
char buf[0x8] = {};
int *p;
buf[0] = 0x61;
buf[1] = 0x62;
buf[2] = 0x63;
buf[3] = 0x64;
buf[4] = 0x65;
buf[5] = 0x66;
buf[6] = 0x67;
buf[7] = 0x68;
strcpy(buf2, "deadbeef");
printf("buf_str is %s\n", buf);
printf("buf_addr_p is %p\n", buf);
printf("buf_addr_x is %x\n", buf);
printf("buf[0]_d is %d\n", buf[0]);
printf("buf[0]_10d is %10d\n", buf[0]);
printf("buf[0]_c is %c\n", buf[0]);
printf("buf[0]_10c is %10c\n", buf[0]);
printf("buf_str is %s\n", p);
printf("buf_addr is %p\n", p);
printf("buf_addr_p is %p,next %p,next %p,next %p,next %p,next %p,next %p,next %p,next %p\n", buf);
return 0;
}
int main(){
init_func();
dofunc();
return 0;
}
2.格式化字符串
%[parameter][flags][field width][.precision][length]type
每一种 pattern 的含义请具体参考维基百科的格式化字符串 。以下几个 pattern 中的对应选择需要重点关注
parameter
- n$,获取格式化字符串中的指定参数
flag
- field width
输出的最小宽度 - precision
输出的最大长度 - length,输出的长度
hh,输出一个字节
h,输出一个双字节 - type
type | 符号 |
---|---|
d/i | 有符号整数 |
u | 无符号整数 |
x/X | 16 进制 unsigned int 。x 使用小写字母;X 使用大写字母。如果指定了精度,则输出的数字不足时在左侧补 0。默认精度为 1。精度为 0 且值为 0,则输出为空。 |
o | 8 进制 unsigned int 。如果指定了精度,则输出的数字不足时在左侧补 0。默认精度为 1。精度为 0 且值为 0,则输出为空。 |
s | 如果没有用 l 标志,输出 null 结尾字符串直到精度规定的上限;如果没有指定精度,则输出所有字节。如果用了 l 标志,则对应函数参数指向 wchar_t 型的数组,输出时把每个宽字符转化为多字节字符,相当于调用 wcrtomb 函数。 |
c | 如果没有用 l 标志,把 int 参数转为 unsigned char 型输出;如果用了 l 标志,把 wint_t 参数转为包含两个元素的 wchart_t 数组,其中第一个元素包含要输出的字符,第二个元素为 null 宽字符。 |
p, | void * 型,输出对应变量的值。printf(“%p”,a) 用地址的格式打印变量 a 的值,printf(“%p”, &a) 打印变量 a 所在的地址。 |
n | 不输出字符,但是把已经成功输出的字符个数写入对应的整型指针参数所指的变量。 |
% | '%'字面值,不接受任何 flags, width。 |
3.代码演示结果
┌──(kali㉿kali)-[~/Desktop/git/ctf-pwn/fmt_test1]
└─$ ./fmt_test1
buf_str is abcdefghdeadbeef
buf_addr_p is 0x7ffebf91a7c8
buf_addr_x is bf91a7c8
buf[0]_d is 97
buf[0]_10d is 97
buf[0]_c is a
buf[0]_10c is a
buf_str is ���==/
buf_addr is 0x56039f67e100
buf_addr_p is 0x7ffebf91a7c8,next (nil),next (nil),next (nil),next 0x7ffebf91852c,next 0x56039f67e060,next 0x6867666564636261,next 0x6665656264616564,next 0x56039f67e100
3.修改源代码
printf("buf_str is %s\n", buf);
printf("buf_addr_p is %p\n", buf);
printf("buf_addr_x is %x\n", buf);
printf("buf[0]_d is %d\n", buf[0]);
printf("buf[0]_10d is %10d\n", buf[0]);
printf("buf[0]_c is %c\n", buf[0]);
printf("buf[0]_10c is %10c\n", buf[0]);
printf("buf_str is %s\n", p);
printf("buf_addr is %p\n", p);
printf("buf_addr_p is %p,next %10$p,next %p,next %p,next %p,next %p,next %p,next %p,next %p,next %p,next %p\n", buf);
4.测试结果
┌──(kali㉿kali)-[~/Desktop/git/ctf-pwn/fmt_test1]
└─$ ./fmt_test1
buf_str is abcdefghdeadbeef
buf_addr_p is 0x7ffef8963a08
buf_addr_x is f8963a08
buf[0]_d is 97
buf[0]_10d is 97
buf[0]_c is a
buf[0]_10c is a
buf_str is ���==/
buf_addr is 0x557e18117100
buf_addr_p is 0x7ffef8963a08,next 0x7ffef8963a30,next (nil),next (nil),next (nil),next 0x7ffef896176c,next 0x557e18117060,next 0x6867666564636261,next 0x6665656264616564,next 0x557e18117100,next 0x7ffef8963a30
可以看到%10p的作用是直接打印第十个%p位置上的指针内容
5.gdb调试
格式化字符串调用参数在x64中的顺序:
rsi -> rdi -> rdx -> rcx -> r8 -> r9 -> stack
通过格式化字符串可以无限泄露栈上的数据
* 回复评论:如有不对欢迎批评
编译器是谁写的我不知道,但是RSI是不是第一个被print了
* 确实有问题,我重新调了一下,很久没搞二进制了
按照结果来看
RSI => RDX => RCX => R8 => R9 => RSP(这里开始就是stack了)