这篇文章详细的描述了堆,并且会教你如何编写基于堆溢出漏洞的利用。
让客户满意是我们工作的目标,不断超越客户的期望值来自于我们对这个行业的热爱。我们立志把好的技术通过有效、简单的方式提供给客户,将通过不懈努力成为客户在信息化领域值得信任、有价值的长期合作伙伴,公司提供的服务项目有:申请域名、虚拟空间、营销软件、网站建设、龙川网站维护、网站推广。
运行下面的程序:
#include string.h
#include stdlib.h
#include stdio.h
int main(int argc, char *argv[])
{
char *buf1 = malloc(128);
char *buf2 = malloc(256);
read(fileno(stdin), buf1, 200);
free(buf2);
free(buf1);
}
在第 10 行中,存在一个明显的可利用的溢出漏洞。那么首先我们就来了解下系统是怎样管理堆的 。
0×01 基本堆和堆块的布局
每个程序分配的内存(这里指的是 malloc 函数)在内部被一个叫做 ” 堆块 ” 的所替代。一个堆块是由元数据和程序返回的内存组成的(实际上内存是 malloc 的返回值)。所有的这些堆块都是保存在堆上,这块内存区域在申请新的内存时会不断的扩大。同样,当一定数量的内存释放时,堆可以收缩。在 glibc 源码中定义的堆块如下:
struct malloc_chunk {
INTERNAL_SIZE_T prev_size; /* Size of previous chunk (if free). */
INTERNAL_SIZE_T size; /* Size in bytes, including overhead. */
struct malloc_chunk* fd; /* double links -- used only if free. */
struct malloc_chunk* bk;
/* Only used for large blocks: pointer to next larger size. */
struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */
struct malloc_chunk* bk_nextsize;
};
假设内存中没有堆块释放,新分配的内存区域紧随之前申请的堆块后。因此如果一个程序依次调用malloc(256),malloc(512),以及malloc(1024),内存布局如下:
Meta-data of chunk created by malloc(256)
The 256 bytes of memory return by malloc
—————————————–
Meta-data of chunk created by malloc(512)
The 512 bytes of memory return by malloc
—————————————–
Meta-data of chunk created by malloc(1024)
The 1024 bytes of memory return by malloc
—————————————–
Meta-data of the top chunk
在堆块之间的”—”是虚拟的边界,实际当中他们是彼此相邻的。你可能会问,为何我要在布局当中包含一个”顶块”元数据。顶级块表示堆中可利用的内存,而且是唯一的可以大小可以生长的堆块。当申请新的内存时,顶块分成两个部分:第一个部分变成所申请的堆块,第二个部分变为新的顶块(因此顶块大小可以收缩)。如果顶块不能够满足申请的内存区域大小,程序就会要求操作系统扩大顶块大侠(让堆继续生长)。
栈溢出保护及整数保护
说点什么
之前觉得学习原理很没有用,不如实践去执行
原理很多、很杂,很没用
真正去做时发现,原理是能做、不能做
从根本上指导实践,减少100%的试错时间
知行合一...
Canary原理
Canary是在栈的尾部插入值,函数返回时检测canary是否改变,判断是否溢出
利用gcc编译:
gcc ... -fstack-protector
如果Canary检查到,会调用__stack_chk_failed函数
绕过:
绕过方法1
至少利用两次栈溢出,才可以使用绕过方法1
V3后代表开启canary,所以v3是canary字节
第一次打印canary,第二次利用
buf距离ebp,10C个字节,v3距离ebp,C个字节,所以buf为100个字节
gdb:
dias main
disas vuln
这几个指令代表是有canary,
gdb可以创建随机字符
检测到栈溢出
利用gdb调试
可以看到,canary的末尾值为00
可以看到,ebp-0xC即为canary值,
0xffc66a68就是ebp的值,
0x08048695就是返回地址,所以canary后要有3个字节才是返回地址
sendline函数会添加回车再发送,所以是0x101个字符
收到的4个字符就是canary,减去回车 0xa就是canary。
注意:回车会导致多输入一个字符
Canary会随机变化,但是fork的函数中canary可以进行爆破
绕过方法2
调用多次的canary值和父进程完全一样,因此可以爆破
先填充0x100个字节,接上cannary值,canary4个字节,所以最后1字节是0x00,前三个字节进行爆破,如果接收到被篡改那么输出信息
绕过方法3
修改该函数的got表即可
最多溢出到canary,不能覆盖到ebp与ret地址,所以要利用格式化字符串漏洞,将getshell函数转换到chk_fail函数的got表,使得栈溢出触发getshell函数。
格式化字符串三点:改写的地址,写入的值,格式化字符串是第几个参数
chk_fail函数之前没调用过,所以是plt的第二条指令
get_shel高字节与chk_fail相同,因此只需要改低字节
%%%dc实现输出addr-4个字符, addr-4是因为stack_chk_fail先输出,是4个字节
最后将前面参数对应的数字,写入到第7个参数(buf对应第几个)对应的地址
绕过方法4
v4:buf大小是0x100
IO_gets就是gets,不限长度输入,以0字符截断,将flag地址进行覆盖
注意:本地的flag内容与远程的flag不相同!
找到chk_fail函数的argv[0]与flag地址的偏移,才能覆盖
64位因此参数传递是通过寄存器,rdi,rsi,rdx,rcx,r8,r9
ELF文件如果较小,会有多重映射
因此用find命令寻找字符串与地址
找到初始地址,0x400d20,也就是要修改的地址
0x218是地址偏移,当执行stack_chk_fail时,就会把new_flag_addr的值输出出来
绕过方法5
canary初始值就是TLS结构中stack_guard,
修改stack_guard就可以绕过canary
输入远大于 char s 数组,可以溢出
gdb中如果没有 符号表(striped),调试不方便 ,但是可以结合IDA与gdb进行调试
栈离TLS距离较近,则可以溢出
所以填充(TLScanary地址 - buf起始地址)的无用值
注意溢出时也会有canary,这个canary也要修改成一样的
这道题没有现成getshell,因此需要第一遍获取system地址,第二遍执行/bin/sh
利用read函数读入system函数,产生栈偏移到data段直接执行system函数,再构造read链,
read函数3个参数,因此填充
pop_rdi_ret能够传递参数
leave指令将rbp移动到rsp,因此栈会偏移
常见攻击方法与攻击过程的简单描述
系统攻击是指某人非法使用或破坏某一信息系统中的资源,以及非授权使系统丧失部分或全部服务功能的行为。
通常可以把攻击活动大致分为远程攻击和内部攻击两种。随着互联网络的进步,其中的远程攻击技术得到很大发展,威胁也越来越大,而其中涉及的系统漏洞以及相关的知识也较多,因此有重要的研究价值。
寻找客户端漏洞
目标:客户端的漏洞?
客户端易受攻击:IE,Outlook,Firefox,MSN,Yahoo etc.
黑客利益的驱使:Botnet,Visa,CD-Key,DDOS etc.
发现漏洞较容易(More 0day?):较容易发现,新的领域
为什么挖掘图像格式中的漏洞?
Windows,Linux等操作系统支持多种图像格式:Bmp, GIF, JPG, ANI, PNG etc.文件格式众多,代码复杂易找到漏洞
Windows中很多图像格式解析的实现方式与开源代码及其相似,经常发现同一bug;(Why?)
黑客们并没有在每种格式中发现漏洞,没有足够的“eyes”关注
从安全人员的角度:
格式众多,算法复杂容易出现漏洞
影响范围极广跨应用跨平台,例如:
Windows 平台上任何解析jpg 的应用,office,outlook,IE...GDIPLUS.dll
Windows 内核实现对Ani 的支持,通过ie 不需要用户互动操作。谁会怀疑网页上的指针文件?
PNG Msn, libpng 很多开源软件
隐蔽性严重威胁用户安全
从黑客的角度:
如果利用图像格式触发的漏洞,会降低了受害者的警觉性,易利用社会工程学。蠕虫传播可能利用一些非常容易让人警惕的文件格式,但我们讨论的是图片格式jgp, png, ani...不容易让人引起怀疑
多种攻击媒介,利于黑客攻击:通过网页,邮件可以穿越防火墙的保护,IDS不易检查,需要对各种格式,协议进行解析才能检查出漏洞攻击。
图像的基本格式
流格式由很多段构成,段里面又由标记,参数(漏洞点),数据段构成
还可能有段里面再嵌套段(漏洞点)
Gif,Ani可能包含很多帧,刷新率,帧的索引(漏洞点)
可能会有标记图形模式的bit-map,可能会有逻辑上的错误png
JPG格式中的漏洞
先来一个实际的例子:
GDIPlus.DLL漏洞MS04-028 Nick DeBaggis
影响巨大,攻击很多
漏洞产生原因:
JPEG格式中的注释段(COM)由0xFFFE开始(标记)+2字节得注释段字节数(参数) +注释(数据)构成。因为字节数这个参数值包含了本身所占的2字节,所以GDIPLUS.dll在解析jpg格式文件中的注释段时会把这个值减去2,如果这个值设置成0,1就会产生整数溢出。
JPG格式中的漏洞
是不是觉得很相似?
2000 Solar Designer 发现了Netscape 浏览器的JPEG 解析漏洞,与Nick DeBaggis 发现的漏洞原理是相同的。
另一个相似的例子
Stefan Esser发现的XBOX Dashboard local vulnerability,该漏洞存在于XBOX Dashboard对.wav格式和.xtf格式文件的解析上,虽然说不是图形格式但漏洞原理却相同。
细节:同样存在一个size参数这次是它本身的大小是4字节,所以当size值为0-3时就会发生整数溢出。
疑问:为什么有些人从偶blog转文章就不写出处呢 也算是我翻来搜去搞来的文章呀bY FIRef0x
疑问:为什么会一再出现同类型的漏洞?
是否程序员们从概念上忽略了某些问题?
为什么都是整数溢出漏洞?
此类漏洞的本质是什么?
是否还有这种漏洞?
问题的本质
这些文件格式是由很多“段”构成的数据流,而每个段由:标记,参数,数据等结构构成,在程序解析这些文件格式的时候会依据“标记”来确认段,并读劝参数” 进行一定的运算,再依据这些参数来处理随后紧跟的“数据”。以上提到的几个漏洞的产生原因就是在对参数进行运算的时候相信了文件输入的参数没有进行确认而导致的。
思维扩展
不要相信用户的输入,同样不要相信文件的输入;
包括标记,错误的标记也会导致问题
包括参数,详细检查输入参数
包括数据,数据里面可能还嵌套着另一个“段”
思维扩展的结果
Venustech AD-Lab:Windows LoadImage API Integer Buffer overflow
影响极为广泛: bmp,cur,ico,ani格式的文件都受影响。
描述:
WINDOWS的USER32库的LoadImage系统API 存在着整数溢出触发的缓冲区溢出漏洞,这个API允许加载一个bmp,cur,ico,ani格式的图标来进行显示,并根据图片格式里说明的大小加4来进行数据的拷贝,如果将图片格式里说明的大小设置为0xfffffffc-0xffffffff,则将触发整数溢出导致堆缓冲区被覆盖。攻击者可以构造恶意的bmp,cur,ico,ani格式的文件,嵌入到HTML页面,邮件中,发送给被攻击者,成功利用该漏洞则可以获得系统的权限。
LoadImage API 整数溢出漏洞分析
代码:
.text:77D56178 mov eax, [ebx+8] //Direct read our size here:P
.text:77D5617B mov [ebp+dwResSize], eax
.text:77D5617E jnz short loc_77D56184
.text:77D56180 add [ebp+dwResSize], 4 //add 4 int overflow...
.text:77D56184
.text:77D56184 loc_77D56184: ; CODE XREF: sub_77D5608F+EF_j
.text:77D56184 push [ebp+dwResSize] //allocate a wrong size
.text:77D56187 push 0
.text:77D56189 push dword_77D5F1A0
.text:77D5618F call ds:RtlAllocateHeap
总结:转换思路后找到这个加4的漏洞,同样的类型,信任“文件”输入。
思维扩展的结果
EEYE 2004:Windows ANI File Parsing Buffer Overflow
堆栈漏洞极易利用,攻击方法隐蔽。
原理:
相信“ 文件” 输入参数,没做检查直接用作memcpy 的参数。
PNG漏洞,不同的模式
逻辑问题1:
EEYE PNG (Portable Network Graphics) Deflate Heap Corruption Vulnerability
原因:对 Length 码 #286 and #287没有做正确的处理,导致解压程序认为长度是0
do { *dest = *src; ++dest; ++src; } while (--len);
逻辑问题2:libPNG 1.2.5堆栈溢出
代码:
if (!(png_ptr-mode PNG_HAVE_PLTE)) {
/* Should be an error, but we can cope with it */
g_warning(png_ptr, Missing PLTE before tRNS); }
else if (length (png_uint_32)png_ptr-num_palette) {
g_warning(png_ptr, Incorrect tRNS chunk length);
g_crc_finish(png_ptr, length);
return;
}
分析:代码编写的逻辑错误,错误的使用了else if.
相似漏洞:MSN png 漏洞,Media player png 漏洞
逻辑问题的总结
非常容易出现在复杂的文件格式处理中
容易出现在压缩,解压代码中:需要处理很多长度,大小相关的参数。
这种漏洞不一定是缓冲区溢出,也可能是越界访问等等
想象漏洞
发现漏洞有时候是一种想象的过程
例子1:
Venustech ADLab:Microsoft Windows Kernel ANI File Parsing Crash Vulnerability
介绍:ANI是WINDOWS 支持的动画光标格式,在ANI是由多个普通的光标文件组成一个动画,其中ANI文件的头处会标记是几个图标frame,WINDOWS 的内核在显示光标的时候并未对该值进行检查,如果将这个数字设置为0,会导致受影响的WINDOWS系统计算出错误的光标的地址并加以访问,触发了内核的蓝屏崩溃。不仅仅是应用使用ANI文件时会触发,只要在EXPLORER下打开ANI文件存在的目录就会触发。攻击者也可以发送光标的文件,引诱用户访问含有恶意光标显示的页面,以及发送嵌入光标的HTML邮件,导致被攻击者系统蓝屏崩溃。
原理:在计算frame地址的时候失败。
例子2:
Venustech ADLab:Microsoft Windows Kernel ANI File Parsing DOS Vulnerability
介绍:ANI是WINDOWS 2000支持的动画光标格式,在ANI是由多个普通的光标件组成一个动画,其中ANI文件的头处会标记每FRAME切换的频率,该值越小切换的速度越快,WINDOWS 的内核在切换光标FRAME的时候并未对该值进行检查,如果将这个数字设置为0,受影响的WINDOWS的内核会陷入内核的死锁,不再响应任何用户界面的操作。该漏洞触发必须要在使用ANI文件的应用中才能触发,攻击者引诱用户访问含有恶意光标显示的页面,以及发送嵌入光标的HTML邮件,导致被攻击者系统内核死琐。
原因:没有考虑刷新频率是0的情况。
总结
下溢:Size参数小于自身所占大小
上溢:Size加上一个正整数值产生上溢
直接作为参数输入memcpy类函数
非法参数导致地址访问越界
多种逻辑上的错误
充分发挥想象:刷新率?
总结
安全提示:
文件格式是攻击者的另一种输入渠道,同样不要信任从文件读取的数据
解析文件格式时应该对参数进行充分的检查
同样需要想象力,需要考虑到每种可能的情况
作为软件开发商,确保代码的完整性要求不断提高警惕性和纪律性。如今的软件状况和近年来的货币压力很相似,但不同的是:有了货币,各国政府可以不断创造新技术以提高货币防伪性,而有了软件,犯罪份子则能够不断创造新技术来攻击我们的代码。 所有这些攻击直接针对源代码,整数溢出是其中最具威胁性的漏洞。这些漏洞潜伏在用户软件中,而在传统测试包中很难触发这些漏洞,这些漏洞往往是黑客寻找渗透点的首要目标。 一般来讲,当恶意攻击者将未经审核的加减称出运算(程序员通常看不出运算操作具有威胁性)通过特殊输入推向极限时,就会出现整数溢出的现象。如果攻击者成功执行整数溢出,结果可能让整个系统崩溃或者导致拒绝服务攻击。由于消除整数溢出漏洞的成本较高,程序员必须想办法找出这些漏洞并在释放代码之前消除漏洞。 在代码中避免这些漏洞的防御解决方案包括:在算数运算进行前对每个用户可以更改的运算值进行严格检查。然而,对于大多数应用程序而言,这将是个艰巨的任务,因为用户提供的运算值可以在多个函数调用边界至程序点传播,而在这些地方算数运算的算数值的源码都是不清楚的。 基于静态分析的工具可以用来检查程序中的整数溢出漏洞。并且,根据问题的本质性,静态分析工具应该能在真正程序路径内准确追踪数值。近日,SAT分析表明执行该分析能够检测整数溢出漏洞,并且程序员需要确保代码受到保护。精确到比特的数据分析和使用SAT限制控制流,再加上SAT解决器,可以确保准确报告潜在漏洞的同时保持很小的报错率。 定义整数溢出以及黑客如何利用整数溢出发动攻击 整数溢出是一种软件行为,由于算术运算的计算结果太大而无法在系统位宽度范围内存储导致的。现在大多数机器都是32位或者64位的,这就限制了存储算数运算的输出数只能保存在32位或者64位中。 那么,当算术运算的结果太大而无法存储系统位宽范围内,数值结果就会被截断,导致产生不可预计的结果。尽管如此,这种满溢的数值可以用在很多关键操作中,如数组索引、内存分配或者内存逆向引用。 这种整数溢出不仅会导致软件内部崩溃,而且能够让攻击者通过攻击整数溢出安全漏洞访问或者破坏系统的重要内存。下面的代码显示的是两个未签名32位数值的加法计算,如果总和大于UINT_MAX(2^32 – 1或者0xFFFFFFFF)就可能出现溢出。 在上面的例子中,如果a和b都等于2^31 + 1,产生的结果x(即2^32 + 2)就会超过32位,从而使x缩短为32位,该x溢出值将作为标准输出,所得出的结果将于程序员a和b相加的结果不相符。不过,由于这种溢出是良性的,因此并没有对这个程序造成严重影响。但情况并非总是如此,让我们看看下面的例子: 在上面的例子中,x仍然包含a+b的溢出值,如果a和b都是2^31 + 1,那么x就会是2。如果之后溢出值x作为mlloc的内存分配大小参数,将只分配x字节(x值并不等于a加b字节)。这样就与程序与分配a+b字节(即2^32 + 2 )的预想不相符,并且系统将只分配x字节(即2)。 在第七行的access值为p[a](这个例子中为p[2^31 + 1]),能够访问未分配甚至特权内存位置。恶意攻击者可能会控制a和b的值(从用户读取)以攻击整数溢出漏洞从而读取甚至破坏特权内存位置。 另外,在上面的例子中,如果恶意攻击者确定了一个2字节内存分配的地址(称为L),并随后决定代表关键安全特权的内存是L的40字节偏移值(offset),用户可以分别将a和b的值设置为40和2^32 – 38,得出的结果x溢出,并包含数值2,这使2字节分配位置L在第六行上,而第七行的p[a] 从L处重写40字节的内存位置偏移值。 这种对任意内存位置的重写很容易激发整数溢出漏洞,特别是对于那些通常运行超级管理特权的重要安全应用程序,因为重要安全内存位置位于英语程序的地址空间内。对于实际应用的软件中的整数溢出漏洞,攻击者可以用任意代码的地址覆盖地址代码,从而使软件执行任意代码。
溢出漏洞是一种计算机程序的可更正性缺陷。
溢出漏洞的全名:缓冲区溢出漏洞
因为它是在程序执行的时候在缓冲区执行的错误代码,所以叫缓冲区溢出漏洞。
它一般是由于编成人员的疏忽造成的。
具体的讲,溢出漏洞是由于程序中的某个或某些输入函数(使用者输入参数)对所接收数据的边界验证不严密而造成。
根据程序执行中堆栈调用原理,程序对超出边界的部分如果没有经过验证自动去掉,那么超出边界的部分就会覆盖后面的存放程序指针的数据,当执行完上面的代码,程序会自动调用指针所指向地址的命令。
根据这个原理,恶意使用者就可以构造出溢出程序。