格式化字符串
这个题目是ctfwiki上的
c代码如下:
1 |
|
(突然发现腾讯文档居然还支持代码好强啊
我们编译一下:
1 | gcc -m32 -fno-stack-protector -no-pie -o leakMemory leakMemory.c -g |
把保护措施都关掉了
1 | % checksec leakMemory |
先看一下几个payload
1 | pwndbg> b printf |
在printf处下断点
此时栈上的布局如下
1 | 00:0000│ esp 0xffffcf9c —▸ 0x80484ea (main+100) ◂— add esp, 0x20 |
continue一下:
1 | pwndbg> c |
输出了信息的同时, 命中第二个断点
此时栈上的布局如下:
1 | 00:0000│ esp 0xffffcfac —▸ 0x80484f9 (main+115) ◂— add esp, 0x10 |
此时printf函数会把格式化字符串之后的栈上的信息当作参数打印出来:
contiue一下
1 | pwndbg> c |
之前栈上的信息显示的不全,栈的内存如下:
1 | pwndbg> x/20x 0xffffcfb0 |
0xffffcfb0 是格式化字符串的地址, 我们看到此时printf函数将0xffffcfc0 0xf7fcf410 0x0804849d
都打印出来了, 也就是格式化字符串之后的三个位置的信息
我们通过这种方式泄露栈的信息,但是也可以直接去取得栈中被视为第n+1个参数的值
至于为什么是第n+1, 这是因为格式化字符串是第一个参数
比如 通过 %3\$x(这个的原理是啥?为什么要加\$符号) 我们可以泄露栈上被视为第4个参数的值
栈布局如下:(左边的一列是栈地址,也就是内存的地址,箭头代表了这个内存单元存储的数据,如果是指针还会进一步指示)
1 | 00:0000│ esp 0xffffcfac —▸ 0x80484f9 (main+115) ◂— add esp, 0x10 |
同样看不清, 还是直接打印内存信息吧(x命令的用法,这里显示的是内存地址从0xffffcfb0开始的,因为内存是按照字节编址的,所以一行正好是16个字节内存地址就是加10)
1 | pwndbg> x/20x 0xffffcfb0 |
猜猜这时候打印的信息是啥?
答案是栈上被视为第四个参数的信息: 0x0804849d
同样的我们还可以通过%s来得到字符串的信息
栈布局如下:
1 | 00:0000│ esp 0xffffcfac —▸ 0x80484f9 (main+115) ◂— add esp, 0x10 |
还是看不清,直接看内存吧(md 垃圾pwndbg)
1 | pwndbg> x/20x 0xffffcfb0 |
这个时候会直接将 0xffffcfc0 对应的字符串打印出来
结果自然就是 %s了
如果我们输入%2$s, 这个时候就很有趣了, 按照道理程序会将 0xf7fcf410 对应地址的当作字符串打印出来, 可是如果这个地址无效呢?
我自己尝试的结果是直接退出了,什么都没有打印出来emm
这时候如果我们指定一个合法的地址, 比如got表中某个函数的地址这就很神奇了
exp如下:
1 | from pwn import * |
我们运行这个exp
在pwndbg中下断点
运行到第二个printf的时候
1 | ───────────────────────────────────[ STACK ]──────────────────────────────────── |
另一边
1 | [+] Waiting for debugger: Done |
continue
1 | [DEBUG] Received 0x8 bytes: |
这个时候我们就得到了scanf函数的地址了
ok 还有几个地方没弄明白之后再写
确定可控制格式化字符串位置的方法
既然程序是有漏洞的,我们就必须知道可被控制的格式化字符串的位置,这时候大致有以下几种姿势
- 构造类似
[tag]%p%p%p%p%p%p...
这样的参数 - Pwngdb中有一个叫做
fmarg
可以用来获取指定地址到底是第几个参数
不是很理解第一种方法原理,但是第二种方法比较好用
不过我们可以来看一个例子:
IDA中的C代码:
1 | int __cdecl main(int argc, const char **argv, const char **envp) |
显然的格式化字符串漏洞
我们需要在printf
函数处下断点
然后随便输入一些数字:
1 | [-------------------------------------code-------------------------------------] |
这时候可以看到flag
了,注意我们是在本地调试,调试的时候当然可以看到flag,一般的pwn题都是要远程连接的
如果我们要泄露flag
的值,就需要构造%n$s
这样的传进去。所以获取参数的位置很关键
1 | gdb-peda$ fmtarg 0x7fffffffde28 |
通过Pwngdb
就可以查看参数的位置了
这时候运行
1 | abc@ubuntu ~/Desktop/pwnEaxmple/zifuchuan |
就得到了flag
(这里值得注意的就是,64位系统和32位系统传参是不一样的)