格式化字符串漏洞基础利用
格式化字符串漏洞基础利用
阅读 ctf-wiki 后总结
泄露内存
利用格式化字符串漏洞,我们还可以获取我们所想要输出的内容。一般会有如下几种操作
- 泄露栈内存
- 获取某个变量的值
- 获取某个变量对应地址的内存
- 泄露任意地址内存
- 利用 GOT 表得到 libc 函数地址,进而获取 libc,进而获取其它 libc 函数地址
- 盲打,dump 整个程序,获取有用信息。
简单的泄露栈内存
例如,给定如下程序
1 |
|
32 位程序使用的是栈传参,64 位系统前 7 个参数是用寄存器传参。32 位程序可以直接利用格式化字符串泄露出存在栈上的参数。(64 位要对应调整)
编译 32 位程序:
1 | gcc -m32 -fno-stack-protector -no-pie -o leakmemory leakmemory.c |
输入输出如下:
1 | >>%p.%p.%p |
栈情况:
1 | ────[ stack ]──── |
泄露任意地址内存
上面已经实现依次获取栈中的每个参数,通过像下面这样构造,直接获取指定为位置的参数:
1 | 第n个参数 |
只要知道目标数据在栈上的偏移 n ,就能够获取。
小总结
会用来泄露什么
理论上任何栈上数据都能被泄露出来,目前遇到过的有以下这些:
Canary
泄露出 Canary 的值,从而绕过 Canary 保护。
text 段地址
泄露出 text 段的真实地址,从而绕过 PIE 对于 text 段的保护,为 ROP 实现提供基础。
libc 函数地址
泄露 libc 函数地址,获取 libc base addr 。这里也可以用来是绕过 PIE 保护,但泄露 libc 地址意义不止于此。
某些变量
有些题目会有 if 判断输入值等是否与预先设定的值相等,以此增加难度。
关键字选择
- 利用 %x 来获取对应栈的内存,但建议使用 %p,可以不用考虑位数的区别。
- 利用 %s 来获取变量所对应地址的内容,只不过有零截断。
- 利用 %order$x 来获取指定参数的值,利用 %order$s 来获取指定参数对应地址的内容。
覆盖内存
覆盖内存使用的 %n
和 %c
配合实现。
c
简单点来说就是产生几个 null 字符。
n
不输出字符,但将成功输出的字符个数写入对应的整型指针参数所指的变量。
写入的时候也有多种方式:
- n:int
- hn:short int 写入双字节
- hhn:char int 写入单字节
给出如下的程序来介绍相应的部分(32位):
1 | /* example/overflow/overflow.c */ |
覆盖任意地址
覆盖小数字
这里以将 a 覆盖为 2 为例。需要将覆盖的目标地址后置,因为机器字长为 4 (64 位是 8)。
构造字符串如下:
1 | aa%k$nxx[addr] |
aa
两个可见字符,所以最后会向目标地址写入 2 。k
目标地址的偏移位置。xx
让字符串对其机器字长,这里是 4 。[addr]
覆盖的目标地址。
怎么对齐
对齐方法在 32 64 程序中,覆盖大数字、小数字中都通用,以上面这个为例。python 使用 len 计算长度后,用机器字长取余,余数就是对齐长度。
1 | # 32位机器字长:4 |
第一个可控字符偏移是 6 ,aa%k$nxx
长度为 8 (不会算就 python len),所以 k 偏移应该是 8 。
构造覆盖小数字利用代码:
1 | def fora(): |
对应的结果如下
1 | >>>python exploit.py |
覆盖大数字
覆盖基本结构和上面差不多,区别是通常是覆盖大数字会分次覆盖,避免一下数据太大而不成功,所以会用到标志 hhn
或 hn
。
还是使用上面例题,写入的目标地址为 0x0804A028 。使用单字节写入(hhn),写入值为 0x12345678
。变量是小端序存储,也在内存中是这样的:\x78\x56\x34\x12
,简单点就是从右向左覆盖。
1 | 0x0804A028 \x78 |
为了与覆盖小数字统一,避免计算地址占用字长,将地址放置在字符串末尾,得出以下框架:
1 | # 格式化字符串 |
x
控制输出多少个 null 字符。y
写入地址的偏移量。
手工计算 c 生成字符数
写入顺序为:0x78、0x56、0x34、0x12
1 | 需要写入0x78,已经存储0x0字符 |
得到结果:payload="%120c%y$hhn%222c%y$hhn%222c%y$hhn%222c%y$hhn"
,长度是 44 ,预估地址偏移是两位数字,再进行一下修改,计算对齐长度为 0 ,最后 payload 为:
1 | payload="%120c%18$hhn%222c%19$hhn%222c%20$hhn%222c%21$hhn" |
覆盖栈内存
确定覆盖地址
覆盖那里内容都好,覆盖地址肯定要明确的,覆盖栈上变量也是需要的。变量地址一般会存放在栈上,我们就需要找到栈存放这个变量地址的偏移。
确定相对偏移
调试在 printf 打断点:
1 | ────[ stack ]──── |
在 0xffffcd14 处存储着变量 c 的地址。偏移量为 6 。
进行覆盖
这样,第 6 个参数处的值就是存储变量 c 的地址,我们便可以利用 %n 的特征来修改 c 的值。payload 如下
1 | [addr of c]%012d%6$n |
addr of c 的长度为 4,故而我们得再输入 12 个字符才可以达到 16 个字符,以便于来修改 c 的值为 16。
具体脚本如下
1 | def forc(): |
结果如下
1 | ➜ overwrite git:(master) ✗ python exploit.py |