格式化字符串

一.程序崩溃 

利用格式化字符串漏洞使得程序崩溃是最为简单的利用方式,只需要输入若干个 %s 。原理:栈上并不是每个值都对应了合法的地址,总是会有某个地址可以使得程序崩溃。这一利用,虽然攻击者本身似乎并不能控制程序,但是这样却可以造成程序不可用。

输出第n个参数就是%(n-1)$[格式化控制符]

使用格式化字符串漏洞任意写虽然我们可以利用格式化字符串漏洞达到任意地址读,但是我们并不能直接通过读取来利用漏洞getshell,我们需要任意地址写。因此我们在本节要介绍格式化字符串的另一个特性——使用printf进行写入。

printf有一个特殊的格式化控制符%n,和其他控制输出格式和内容的格式化字符不同的是,这个格式化字符会将已输出的字符数写入到对应参数的内存中。

因此我们可以验证任意地址读写,接下来就可以可以构造exp拿shell。

若程序中有system函数,可以直接选择劫持一个函数的got表项为system的plt表项,从而执行system(“/bin/sh”)。

例:printf函数可以单参数调用,可以劫持printf为system,然后再次通过read读取”/bin/sh”,此时printf(“/bin/sh”)将会变成system(“/bin/sh”)。

二.泄露内存

泄露栈内存1.获取某个变量的值2.获取某个变量对应地址的内存

泄露任意地址内存1.利用 GOT 表得到 libc 函数地址,进而获取 libc,进而获取其它 libc 函数地址2.盲打,dump 整个程序,获取有用信息。




wiki例题:泄露栈内存,按照Wiki上的步骤进行调示

源代码

生成64位的文件,在终端试运行,输入%08x.%08x.%08x

输出了类似地址的东西

gdb调试一下:

栈中第一个变量为返回地址,第二个变量为格式化字符串的地址,第三个变量为 a 的值,第四个变量为 b 的值,第五个变量为 c 的值,第六个变量为我们输入的格式化字符串对应的地址。继续运行程序可以看出,程序确实输出了每一个变量对应的数值,并且断在了下一个 printf 处
此时,由于格式化字符串为 %x%x%x,所以,程序 会将栈上的 0xffffcd04 及其之后的数值分别作为第一,第二,第三个参数按照 int 型进行解析,分别输出。继续运行,我们可以得到如下结果去,确实和想象中的一样。

注意:因为栈上的数据会因为每次分配的内存页不同而有所不同,所以,我们看到的数据与wiki上的可能有不同,这是因为栈是不对内存页做初始化的。

直接获取栈中被视为第 n+1 个参数的值:


获取栈变量对应字符串 :

为什么我输不出%s,按道理 在第二次执行 printf 函数的时候,确实是将 0xffffcd04 处的变量视为字符串变量,输出了其数值所对应的地址处的字符串。

当然,并不是所有的都会正常运行,如果对应的变量不能够被解析为字符串地址,那么,程序就会直接崩溃。

小总结:

利用 %x 来获取对应栈的内存,但建议使用 %p,可以不用考虑位数的区别。

利用 %s 来获取变量所对应地址的内容,只不过有零截断。

利用 %order$x 来获取指定参数的值,利用 %order$s 来获取指定参数对应地址的内容。

泄露任意地址内存:用途:泄露某一个 libc 函数的 got 表内容,从而得到其地址,进而获取 libc 版本以及其他函数的地址

由 0x41414141 处所在的位置可以看出我们的格式化字符串的起始地址正好是输出函数的第 5 个参数,但是是格式化字符串的第 4 个参数。测试一下
我的结果与Wiki上有点不一样,不知道这算不算崩溃了
格式化字符串所对应的变量值 0x73243425 并不能够被改程序访问,所以程序就自然崩溃了。  

获取 scanf@got 的地址:

按照Wiki上的步骤没有成功,最后发现原因是因为在生成第一个地址后,我们在printf处下断点,下完断点后应该直接continue,而不应该run,因为程序已经在运行了

覆盖内存:

%n,不输出字符,但是把已经成功输出的字符个数写入对应的整型指针参数所指的变量。

...[overwrite addr]....%[overwrite offset]$n

其中... 表示我们的填充内容,overwrite addr 表示我们所要覆盖的地址,overwrite offset 地址表示我们所要覆盖的地址存储的位置为输出函数的格式化字符串的第几个参数。所以一般来说,也是如下步骤确定覆盖地址确定相对偏移进行覆盖


源代码
输出了c的地址
我们可以发现在 0xffffd03c 处存储着变量 c 的数值。继而,我们再确定格式化字符串'%d%d'的地址 0xffffcfd8 相对于 printf 函数的格式化字符串参数 0xffffcfc0 的偏移为 0x18,即格式化字符串相当于 printf 函数的第 7 个参数,相当于格式化字符串的第 6 个参数。

 a、b 是已初始化的全局变量,因此不在堆栈中

覆盖小数字:

此时对应的存储的格式化字符串已经占据了 6 个字符的位置,如果我们再添加两个字符 aa,那么其实 aa%k 就是第 6 个参数,$nxx 其实就是第 7 个参数,后面我们如果跟上我们要覆盖的地址,那就是第 8 个参数,所以如果我们这里设置 k 为 8,其实就可以覆盖了。

说明没有必要必须把地址放在最前面,放在那里都可以,只要我们可以找到其对应的偏移即可。

覆盖大数字 :

可以选择直接一次性输出大数字个字节来进行覆盖,但是这样基本也不会成功,因为太长了。而且即使成功,一次性等待的时间也太长。所有的变量在内存中都是以字节进行存储的。在 x86 和 x64 的体系结构中,变量的存储格式为以小端存储,即最低有效位存储在低地址。

hh 对于整数类型,printf期待一个从char提升的int尺寸的整型参数。

h  对于整数类型,printf期待一个从short提升的int尺寸的整型参数。

可以利用 %hhn 向某个地址写入单字节,利用 %hn 向某个地址写入双字节。

看看Wiki吧。还待更新。


fmtstr_payload(offset,{key,value})函数,这个函数的作用是用来生成格式化字符串漏洞写内存的。payload.fmtstr_payload的第一个参数为offset偏移,第二个参数是一个字典,意义是往key的地址,写入value的值.也就是往got_puts地址写入system_addr的值,这样调用puts函数就变成了调用system函数。

https://blog.csdn.net/qq_42192672/article/details/83376235

7f通常为64位的真实地址,f7是32位的

 \xff:这是一个转义字符,\x表示后面的数是十六进制,ff是十六进制数。\xhh:这是说明转义字符的格式,以\x开头,后面接两个十六进制数,h在这里应该是hex(十六进制)的意思。  

permutations和combinations都是得到一个迭代器。

combinations方法重点在组合,permutations方法重在排列。

append()函数:以可写方式打开文件。如果文件不存在,将会自动创建。使用该函数,将会覆盖文件中的所有内容。

join()函数

语法:  'sep'.join(seq)

参数说明

sep:分隔符。可以为空

seq:要连接的元素序列、字符串、元组、字典

上面的语法即:以sep作为分隔符,将seq所有的元素合并成一个新的字符串

返回值:返回一个以分隔符sep连接各个元素后生成的字符串

你可能感兴趣的:(格式化字符串)