格式化字符串(37)
首先啥是格式化字符串
这里借用维基百科中的内容:
格式化字符串(英语:format string)是一些[程序设计语言]的输入/输出[库]中能将[字符串]参数转换为另一种形式输出的[函数]。例如C、C++等程序设计语言的printf类函数,其中的转换说明(conversion specification)用于把随后对应的0个或多个函数参数转换为相应的格式输出;格式化字符串中转换说明以外的其它字符原样输出。[1]
关闭地址随机化
可以先查看一下
1 | cat /proc/sys/kernel/randomize_va_space |
然后再root用户下:
1 | echo 0 > /proc/sys/kernel/randomize_va_space |
这样就关闭的地址空间随机化
首先来看看printf中的%
%[标志][输出最小宽度][.精度][长度]类型
1.输出最小宽度:用十进制整数来表示输出的最小位数。
- 若实际位数多于定义的宽度,则按实际位数输出
- 若实际位数少于定义的宽度则补空格或者0
2.类型
%c:输出字符,配上%n可用于向指定地址写数据。
- 这个配上%n就比牛了
%d:输出十进制整数,配上%n可用于向指定地址写数据。
%x:输出16进制数据,如%i$x表示要泄漏偏移i处4字节长的16进制数据,%i$lx表示要泄漏偏移i处8字节长的16进制数据,32bit和64bit环境下一样。
%p:输出16进制数据,与%x基本一样,只是附加了前缀0x,在32bit下输出4字节,在64bit下输出8字节,可通过输出字节的长度来判断目标环境是32bit还是64bit。
%s:输出的内容是字符串,即将偏移处指针指向的字符串输出,如%i$s表示输出偏移i处地址所指向的字符串,在32bit和64bit环境下一样,可用于读取GOT表等信息。
%n:将%n之前printf已经打印的字符个数赋值给偏移处指针所指向的地址位置,如%100×10$n表示将0x64写入偏移10处保存的指针所指向的地址(4字节),而%$hn表示写入的地址空间为2字节,%$hhn表示写入的地址空间为1字节,%$lln表示写入的地址空间为8字节,在32bit和64bit环境下一样。有时,直接写4字节会导致程序崩溃或等候时间过长,可以通过%$hn或%$hhn来适时调整。
%n:是通过格式化字符串漏洞改变程序流程的关键方式,而其他格式化字符串参数可用于读取信息或配合%n写数据。
来看看printf函数的使用
1 | char str[100]; |
错误的用法
1 | char str[100]; |
在第二种的时候,我们就可以对printf进行控制
看一个正常的程序
1 |
|
修改一下
1 |
|
用gcc对它进行编译
这里我踩了一个坑
需要用 -g 不然它会有 without debugging 的警告
1 | gcc test.c -g -o test |
可以看到前面的test 和 123 是正常的输出,
但是后面那个f7fcf6a0是啥呢?
我们用gdb来动态调试一下
首先是设置断点在printf 这里
然后程序就停止在了这里,接着用n【next】
从format 的 读取方向就可以看到 %x 这个读取了下一个内存单元的地址
所以,只要能控制这个format 就可以一直读取内存中的数据
1 |
|
我们可以去直接读取str[]的内容
输入:AAAA%08x%08x%08x%08x%08x%08x
gdb 调试一下
我们可以用%s来获取指针指向的内存数据
比如说:我们来构造尝试获取0x41414141的数据
输入:
x41x41x41x41%08x%08x%08x%08x%08x%s
用%n格式来写入数据
它的作用是把前面打印的长度写入某个内存地址
1 |
|
我们发现可以够着格式化字符串去访问栈内的数据,并且利用%n向内存中写入东西。
但是%n的作用是返回打印字符串的长度写到内存中,当我们向写入一个地址的时候,应该如何操作呢?
肯定就是要用自定义打印字符串宽度啦
比如:我们想把0x8048000 这个地址写入数据,我们就要做的就是把10进制的134512640作为格式符来控制宽度就可以了
1 |
|
这就就能把num 的值 改成相应的地址。