格式化字符串漏洞原理
printf应该是我们学习c语言起使用的最频繁的函数了
其语法我们熟悉的不能再熟悉了—>printf (“格式化字符串”,参量… )
我们可以写一段代码:
|
当然我们也可以这样写:
|
第二种写法虽然没有格式化字符但是仍然可以输出chen这个字符串
那我们再看看第三种写法
|
这次我们没有给printf函数参数,只是仅仅给他格式化字符,猜一下,这次能成功吗,如果成功了,会输出什么?
输出了像地址的16进制?
我们明明没有给他用以输出的参数,那么这串数据是从哪里来的?
我们用图来表示一下printf输出的时候栈结构是什么样子
ps:关于这图,格式化字符串不一定要放在栈顶才能实现任意地址写入,注意别被误导了,下面会提一嘴
如果我们只传入了格式化字符串而没有传入参数
那么格式化字符串仍然会遵循着原先的逻辑,向高地址处逐个字长的输出当前栈的内容/指针(输出的方式根据其格式化字符的不同而不同)
这是因为printf函数并不知道参数个数,它的内部有个指针,用来索检格式化字符串。对于特定类型%,就去取相应参数的值,直到索检到格式化字符串结束
pwn题中的格式化字符串通常有两种出法
第一种,使用格式化字符串泄露栈上的内容(canary或者是随机数不一定),由于wp分类中的HUBU2022.fmt已经是这方面的例题了,这里不做额外的讲解,感兴趣的可以去wp分区中自行查看
第二种,也是难度较前者稍高,不好理解的一种
任意内存的读取及任意内存写入
我们首先得了解一个不常用的格式化字符串**%n**
他的作用是将在其之前打印出来的字节数赋值给指定的变量
比如: AAAA%n 就会赋值4给变量
如果我们后面跟上要修改的变量地址,就可以做到任意地址的写入
没懂?没关系来看一道例题
开了canary保护,大概率是格式化字符串
看看ida
int __cdecl main(int a1) |
这里的/dev/urandom是什么?
我们只需要了解他是linux系统中的随机伪设备,他的作用就是提供永不为空的随机字节流
浅看一下他生成的字节流长什么样子
说回这道题,看逻辑应该是要我们输入这个随机数,如果一样就调用system(/bin/sh)
而存放这个随机数的就是dword_804C044的地址
那我们这时候应该有两种想法,一种是泄露这个随机数,输入他
还有一种是通过修改这个随机数的值来判定成功
第一种办法本人是没法做出来,感兴趣的可以试一试
那么讲一下第二种办法,由上文的学习我们已经知道
要想用格式化字符串漏洞泄露栈上的内容,需要我们知道目标地址和格式化字符串存放的地址的偏移
用gdb看一下偏移
现在main函数处设置一个断点(这里由于main函数被删符号表了,所以b main的话gdb查找不到函数的,删符号表的体现就是在ida中main函数不是粗体字,ida只是凭借逻辑识别他为main函数)
接着运行并且跳转到输入字符串这边,我们先输入8个A看一下栈分布的情况
这里还是先解释一下x/20wx $esp这个命令是什么意思
其作用是用gdb查看内存 格式: x /n u f
n是要显示的内存单元个数
f表示显示方式, 可取如下值
u表示一个地址单元的长度
这里的x/20wx 的意思就是说查看20个4字节长度的内存单元 并且按16进制的格式显示
至于为什么是以esp为初始地址显示
是因为格式化字符串%n进行任意地址改写是在ESP所指向的地址处所指向的地址处写入数据(可以理解为栈顶)
所以我们需要知道当我们最终构造的payload中需要改写的地址内容距离esp的偏移是多少
而且也正是因为这一点,决定了我们可以不用一定要把格式化字符串放在栈顶
这里可以看到代表着AAAAAAAA的两个0x4141414141距离esp的偏移是10和11(如果我们只输入4个A只会占用偏移10这个字长,因为这是32位程序,等下编写exp的不要被搞晕了)
所以此时我们要如何构造我们的payload?
此时我们将格式化字符放在payload的最后
from pwn import * |
由于此时程序是32位,%n前面传入的p32(addr)则为一个字长,四个字节,所以此时addr处的随机数就被我们修改为4
我们接着再输入4,就成功破解了随机数
学会了?觉得很简单?再来看一个比较绕的exp
from pwn import * |
这次我们把addr放在后面传输了,可以看到和上文的区别是前面多了7个A而且这次随机数被我们修改成7了
看不懂没关系,接下来详解
我们再次明确一下概念,这里提到的偏移指的是距离esp的字长数
那么我们要实现改写的是addr这个地址的随机数对吧
此时我们先传入的是字符串“AAAAAAA%13$n”他的字节数是多少?
很明显是12个字节,也就是三个字长
我们之前通过gdb已经明白了,我们写入栈中的第一个字长是位于10偏移处,也就是AAAA
那么接下里的AAA%就会被写入11偏移
13$n就会被写入12偏移
而此时的addr就会被存放在13偏移处,所以此时我们的n就要从10更改为13
似乎有点能理解了是吧?
实际上pwntools中有一个函数,他可以自动帮我们生成这样的payload,而我们要做到的只是给予他基本的参数
fmtstr_payload(offset, {addr: data})
offset就是我们需要更改内容的地址距离esp的偏移
addr就是我们需要改写内容的地址
data就是我们需要改写的数据
来看一下接下来的exp可以怎么写
from pwn import* |
可以看到,我们就这样轻易的将addr处的随机数更改为了1
是不是比之前的两种payload构造办法简单许多?