格式化字符串:
格式化字符串函数可以接受可变数量的参数,并将第一个字符串作为格式化字符串,然后去解析之后的参数。常见的格式化字符串函数有:
- scanf
- printf,输出到stdout
- fprintf,输出到指定的FILE流
- vprintf,根据参数列表格式化输出到 stdout
- vfpirntf,根据参数列表格式化输出到指定 FILE 流
- sprintf,输出到字符串
- snprintf,输出指定字节数到字符串
- vsprintf,根据参数列表格式化输出到字符串
- vsnprintf,根据参数列表格式化输出指定字节到字符串
- setproctitle,设置argv
- syslog,输出日志
常利用的格式化字符串有:
- %x,以十六进制输出内存内容
- %p,以带前缀0x的十六进制输入内存内容
- %s,泄露指定地址空间的内容
- %n,向指定地址空间写入以输出的字节数
- %hn,向指定地址空间写入两个字节的数据,写入的数值是已输出的字节数
- %hhn,向指定地址空间写入一个字节的数据,写入的数值是已输出的字节数
下面逐个讲解。先看一个例程:
#include <stdio.h> int main() { char s[100]; int a=1, b=0x22222222, c=-1; scanf("%s", s); printf("%04x.%04x.%08x.%s ", a, b, c, s); printf(s); return 0; }
运行后的结果如下:
countfatcode@ubuntu:~/Code $ ./formatstring yuan 0001.22222222.ffffffff.yuan
接下来调试观察是如何泄露内存的:
gdb-peda$ stack 20 0000| 0xffffd3b0 --> 0x80485d3 ("%04x.%04x.%08x.%s ") 0004| 0xffffd3b4 --> 0x1 0008| 0xffffd3b8 ("""""377377377377350323377377350323377377 30331377367377265", <incomplete sequence 360>) 0012| 0xffffd3bc --> 0xffffffff 0016| 0xffffd3c0 --> 0xffffd3e8 ("yuan") 0020| 0xffffd3c4 --> 0xffffd3e8 ("yuan") 0024| 0xffffd3c8 --> 0xf7ffd918 --> 0x0 0028| 0xffffd3cc --> 0xf0b5ff 0032| 0xffffd3d0 --> 0xffffd40e --> 0xffff0000 --> 0x0 0036| 0xffffd3d4 --> 0x1 0040| 0xffffd3d8 --> 0xc2 0044| 0xffffd3dc --> 0x1 0048| 0xffffd3e0 ("""""377377377377yuan") 0052| 0xffffd3e4 --> 0xffffffff 0056| 0xffffd3e8 ("yuan") 0060| 0xffffd3ec --> 0x0 0064| 0xffffd3f0 --> 0xf7ffd000 --> 0x23f40 0068| 0xffffd3f4 --> 0xf7ffd918 --> 0x0 0072| 0xffffd3f8 --> 0xffffd410 --> 0xffffffff 0076| 0xffffd3fc --> 0x804827d ("__libc_start_main") gdb-peda$ ni 0001.22222222.ffffffff.yuan
观察上图栈结构可知程序把printf函数的第一个参数的首地址压入栈中,然后根据格式化字符串依次解析栈中除栈顶中的内容。