栈溢出
0x00 前言
在各类软件安全漏洞中,栈溢出漏洞无疑是最常见,危害最大的漏洞类型之一,在各种操作系统和应用软件中广泛存在。第一个利用栈溢出漏洞发动攻击的病毒是“Morris蠕虫”,它是在互联网中传播的第一个蠕虫病毒,受到当时各大媒体的强烈关注。该蠕虫病毒由康奈尔大学学生Robert Tappan Morris编写,1988年11月2日从MIT传播到互联网上。
直到1996年,Aleph1在Phrack第49期上公布了关于栈溢出漏洞的文章Smashing the stack for fun and profit。里面详细描述了Linux系统中栈的结构,通过示例代码进行反汇编分析,论述了栈溢出漏洞成因和利用技巧。
随后互联网涌现出了大量栈溢出的文章,其中以Dildog在Phrack杂志上写的The Tao of windows buffer overflow最为出名。他在文中详细介绍了Windows平台上的栈溢出利用技术,提出了“jmp esp/call esp”覆盖返回地址的经典方法。
1999年,Dark Spyrit在Phrack第54期上的文章Win32 buffer overflow Location,Exploitation and Prevention中提出了成熟的Windows缓冲区溢出利用技术及防御措施,重点提出利用系统DLL中的指令控制程序执行流程的方法。随后David Litchfield在BlackHat大会上做了题目为defeating the stack based buffer overflow prevention mechanism of microsoft windows 2003 server的主题演讲,里面提出了利用覆盖SEH结构来绕过Cookie检测,扩展了栈溢出的利用思路。
随后涌现出了各种栈溢出利用技巧,pop pop ret,ret2lib和ROP技术等EXP技术。
0x01 原理
本站在0day安全相关内容中有较详细介绍,不做重复介绍。
0x00 前言
此篇文章首次更新于2023年8月8日。于鄙人读完了0day以及做完漏洞战争栈溢出之后。
0x01 总结
第一版本暂时随意写写,后续更新。
栈溢出的调试手段,基本是通过栈回溯找到漏洞函数,首先触发崩溃,然后在返回地址处下内存写断点,这样通常就会断在mov或rep movs等字符复制的指令处,然后通过栈回溯或者函数交叉引用查找到漏洞函数地址(有时候是通过寄存器+偏移来进行调用这时候IDA交叉引用看不出来)。
对于ActiveX控件的调试,一般直接通过OLEAUT32模块中的DispCallFunc函数对首个call ecx指令下断跟进,此时进入的就是PoC中调用的控件函数。如果PoC中包含多个空间函数,就得逐个跟进call ecx去判断对应的是哪个控件函数。将PoC分别在漏洞版本和修复版本的程序上运行,再通过比对运行指令的差异性,从中找到漏洞成因及修复方法(这里有意思,补丁对比是静态的,有时候其实很难看清楚,但是如果对比运行指令,然后写个脚本导出,再对比,更容易看到怎么修复,如何修复的了,这个应该可以通过调试器插件实现)。
0x02 问题
-
call的地址动态的时候能否在动态调试器的交叉引用找到调用地址。
-
OD调试的时候,为什么有时候捕捉不到崩溃,其次有时候部分显示框没有内容,这个如果看一下调试器实现源码可能会很好的理解。
0x03 问题答案
-
-
发现是插件问题
堆溢出
堆溢出介绍
0x00 前言
以前,很多Exploit公布站点上关于堆溢出的Exploit明显要比栈溢出少,一方面是由于堆漏洞比栈漏洞更难发现,另一方面是因为其利用难度较栈溢出大,而且受漏洞场景影响较大,很多以往的堆溢出利用方式只能在WindowsXP下运行,并且还受SP版本影响,特别是XPSP2之后对堆结构做了较大改动,这也大大地增加了黑客及安全研究人员在利用堆溢出漏洞上投入的成本。
1999年,w00w00安全小组的Matt Conver编写了w00w00on Heap Overflows一文,中文版译名为《Heap/BSS溢出机理分析》,该文是最早将堆溢出原理公布的佳作。当时,Mat只有十几岁,无疑是安全界的传奇少年,其在堆溢出方面具有相当深入的研究。
2002年,在BlackHat大会上,Halvar Flake发表题为Third Generation Exploitation的演讲,主要揭露Windows2000平台上的堆数据结构和算法,并提出堆溢出利用方法。
2003年9月,DaveAitel针对MS03-026:RPC DCOM堆溢出漏洞写的Exploiting the MSRPCHeapOverflow详细描述了针对实际堆溢出漏洞的Exploit技巧-ArbitraryDWORDReset(在《0day安全:软件漏洞分析技术》一书中称为“DWORDSHOOT”)。著名的“冲击波病毒”正是利用此漏洞发动攻击的,关于“冲击波”的源码在网上可下载到。
2004年,David Litchfield在BlackHat大会上发表的演讲Windows Heap Overflows较为全面地讲述了Windows2000/XP 平台下的堆溢出利用技术,覆盖VEH、UEF、PEB和TEB等方法关于这些利用方法在《0day安全:软件漏洞分析技术》的第8章“高级内存攻击技术”中有详细讲解。2004年,Matt Conver在CanSecWest黑客大会上发表的演讲Windows Heap Exploiation全面地揭露了从Windows 2K SPO到Windows XP SP2平台上的堆溢出利用技术,包括如何突破Wimdows XPSP2上的堆保护机制。
2007年,Nicolas Waisman发表Underslanding and bypassing Windows Heap Protection,主要讲解WindowsXPSP2及WindowsVista的堆保护机制及绕过方法同时提供多款Immunity Debugger插件用于调试堆漏洞也正是因为Immunity Debugger提供了许多漏洞分析辅助插件,所以很多漏洞分析者都习惯使用ImmDbg调试漏洞,尤其是国外。
2008年,Ben Hawkes在Ruxcon大会上演讲的Atacking the Vista Heap首次公开了Windows Vista平台下的堆内部结构,以及绕过Vista堆保护的方法。
2009年,John McDonald和Chris Valasek在BlackHat USA大会上发表题为Practical Windows
XP/2003Heap Exploitation的演讲详细地介绍了Windows XP SP3和Server 2003的堆结构及核心算法
同时较为全面地总结出各种堆溢出利用技术,该文的中译版已由冰雪风谷翻译并发在看雪论坛上。
2010年在Pw20wn2010黑客大赛上Peter vreugdenhil利用内存泄露和覆盖虚表指针的方法攻下Windows7的IE8浏览器,此处利用的是CVE-2011-0027:MDACADO记录堆溢出漏洞。首先,利用heap spray将Shellcode传递到可预测的地址,再利用堆溢出覆盖虚表指针劫持EIP,同时借助内存泄露定位对象所属DLL(msado15di1)的基址来绕过ASLR又使用ROP技术绕过DEP保护从而实现Exploit的稳定利用。在随后的几年里,很多信息泄露漏洞被拿来绕过ASLR,而其也被很多大厂商当作独立的漏洞来处理,比如Google、微软等。
现在,随着栈溢出的减少,堆溢出的漏洞反而越来越多了,针对此类型的漏洞利用技术也变多了,利用技术已经逐步趋于成熟。很多主流软件的栈溢出漏洞已经很少见了,比如IE浏览器、Adobe软件,反而是堆上的漏洞成为主流。
0x01 原理
测试代码:
#include <windows.h>
#include <stdio.h>
int main ( )
{
HANDLE hHeap;
char *heap;
char str[] = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
hHeap = HeapCreate(HEAP_GENERATE_EXCEPTIONS, 0x1000, 0xffff);
getchar(); // 用于暂停程序,便于调试器加载
heap = HeapAlloc(hHeap, 0, 0x10);
printf("heap addr:0x%08x\n",heap);
strcpy(heap,str); // 导致堆溢出
HeapFree(hHeap, 0, heap); // 触发崩溃
HeapDestroy(hHeap);
return 0;
}
运行程序,windbg加载,g命令运行,到了崩溃处,kb栈回溯,找到strcpy(heap,str);对应的指令处。
随后查看分配的heap地址,本实验中为00280590,16字节大小,再往前8字节是HEAP_ENTRY。
!heap -p -a <address>
!heap -p 命令显示各种形式的页堆信息。 在使用 !heap -p 之前,必须为目标进程启用页面堆。 这是通过全局标志 (gflags.exe) 实用工具完成的。 为此,请启动实用工具,在“ 图像文件名 ”文本框中填写目标应用程序的名称,选择“ 图像文件选项 ”和“ 启用页面堆”,然后选择“ 应用”。 或者,可以通过键入 gflags /ixxx.exe+hpa 从命令提示符窗口启动全局标志实用工具,其中 xxx.exe 是目标应用程序的名称。
-a 选项使得显示指定堆的所有信息。
! heap列出所有堆。
00280000堆信息如下
00280588的size为0x28字节,请求的大小是0x10字节。(这里由于是windbg重新运行程序导致成了调试堆,然后分配堆size过大导致了无法覆盖到下一个堆头,填充0x30字节可以,不过我选择重新来一遍,但是图就不重新截了)
多复制了16字节,正好覆盖了下一个堆块的堆头和指向空闲链表的前后指针。
这里书中说heapfree函数调用时会让该堆块与下一个空闲堆块合并,但是其地址有错,笔者实验中是分配堆块与003805a0这个地址的空闲堆块合并。然后修改合并堆块的前后向指针,此时引用0x41414141造成崩溃。此溢出被称为堆溢出,覆盖高地址空间的heap结构,如果是反方向,溢出被称为堆下溢(低地址)。
windows xp sp2可以使用DWROD SHOOT进行任意空间的任意写,但是Windows 7有一些防御机制,所以不行。
0x02 堆调试技巧
2.1 堆尾检查
!gflag +htc +hpc开启堆尾和堆参数检查。
执行发现提示00250588的堆块改变了002505A0的值,该地址的值偏移为0x18,但是请求大小只有0x10,算上堆头也只有0x18,其申请空间对于堆头起始地址的覆盖偏移为0x00~0x17(0x18字节)。但是溢出了。
2.2 页堆
堆尾检查主要是堆被破坏后的场景,不利于定位导致漏洞的代码。为此微软应用page heap页堆的概念,开启该机制之后,堆管理器会在堆块中增加不可访问的栅栏页,当堆覆盖到栅栏页时就立即触发异常。
gflag.exe /i app.exe +hpa开启。
随后打开程序,windbg附加,g运行。
!gflag查看是否开启了页堆机制。随后停在了ecx=4,正好有0x10字节未复制,此时堆环境未被破坏,随后kb栈回溯,造成漏洞的函数是401000,uf查看函数。
0x00 前言
此篇文章首次更新于2023年9月3日。于鄙人读完了0day以及做完漏洞战争堆溢出之后。
0x01 总结
第一版本暂时随意写写,后续更新。
堆溢出的调试手段,基本是通过开启页堆的方式定位漏洞产生点(如果开启堆尾检查需要在程序运行之前),首先触发崩溃,然后通过Windbg提供的堆调试支持,找到对应的漏洞代码。
0x02 问题
-
Windows具体的堆管理机制
-
双重释放漏洞
堆溢出介绍
0x00 前言
双重释放漏洞(Double Free)漏洞最早曝光于2002年3月11日,由Zlib官方公告Zlib压缩库存在双重释放漏洞,其CVE编号为CVE-2002-0059。
2003年2月,在Bugtrag漏洞公告邮件列表上,国外安全研究人员Igor Dobrovitski公布了一份关于Linux CVS双重释放漏洞(CVE-2003=0015)的利用代码,通过控制堆块的前后向指针,实现写任意地址,然后Shellcode覆盖特定的函数指针,从而实现代码执行。2003年7月,绿盟科技在其月刊上发布文章《GLIBC环境下double-free堆操作漏洞利用原理及相关漏洞分析》,对堆分配管理机制及双重释放的利用技巧进行分析。
2007年,在BlackHat USA上,来自IOActive安全公司的Justin Ferguson在大会上分享了Understanding the heap by break it议题,针对双重释放漏洞漏洞提出新的漏洞利用思路。
0x01 原理
双重释放漏洞漏洞主要是对同一块内存进行二次重复释放导致的,利用漏洞可以执行任意代码。
漏洞代码:
#include <stdio.h>
#include "windows.h"
int main (int argc, char *argv[])
{
void *p1,*p2,*p3;
p1 = malloc(100);
printf("Alloc p1:%p\n",p1);
p2 = malloc(100);
printf("Alloc p2:%p\n",p2);
p3 = malloc(100);
printf("Alloc p3:%p\n",p3);
printf("Free p2\n");
free(p2);
printf("Double Free p2\n");
free(p2);
printf("Free p1\n");
free(p1);
printf("Free p3\n");
free(p3);
return 0;
}
这里先多次释放p2不会有问题,但是如果先释放p1,p3,然后双重释放p2就会有问题。
这是由于先释放p1,p3,第一次释放p2的时候会导致堆块合并,改变原有堆头信息及前后指针,之后再对其中的地址进行引用就会造成访问异常。正是因为程序引用到了已释放的内存,所以说双重释放漏洞是UAF漏洞的子集,如果程序不存在堆块合并操作,双重释放后可能不会马上崩溃,但会在遗留隐患。
双重释放漏洞总结
TODO
UAF漏洞
UAF 漏洞介绍
0x01 前言
UAF(Use After Free,释放后重用)漏洞,顾名思义就是使用到已被释放的内存,最终导致内存崩溃或任意代码执行的漏洞。UAF漏洞在浏览器中最为常见的,比如IE,Chrome,Firefox等。
2005年12月,第一个UAF漏洞CVE-2005-4360被发现。
2007年的BlackHat USA黑客大会上,来自Watchfire安全团队的安全研究员分享了主题为Dangling Pointer:Smashing The Pointer For Fun And Profit,讲述了悬挂指针(Dangling Pointer)的原理及危害,结合漏洞实例,完整地讲述UAF的漏洞原理及利用技巧。同年堆漏洞利用的经典文章Heap Feng Shui in JavaScript,提出了Heap Spray经典的漏洞利用技巧。
0x02 原理
漏洞代码:
#include <stdio.h>
#include <stdlib.h>
#include <cstring>
#define size 32
int main(int argc, char **argv) {
char *buf1;
char *buf2;
buf1 = (char *) malloc(size);
printf("buf1:0x%p\n", buf1);
free(buf1);
// 分配 buf2 去“占坑”buf1 的内存位置
buf2 = (char *) malloc(size);
printf("buf2:0x%p\n\n", buf2);
// 对buf2进行内存清零
memset(buf2, 0, size);
printf("buf2:%d\n", *buf2);
// 重引用已释放的buf1指针,但却导致buf2值被篡改
printf("==== Use After Free ===\n");
strncpy(buf1, "hack", 5);
printf("buf2:%s\n\n", buf2);
free(buf2);
getchar();
return 0;
}
显然已free的内存空间的指针buf1处的值修改影响了buf2。(为什么free的时候不自动将指针设置为null呢?)
如果原有的漏洞程序引用到悬挂指针处指向的数据用于执行指令或作为索引地址去执行,就可能导致任意代码执行。
在浏览器的UAF漏洞中,通常是某个C++对象被释放后重用。
内存如下:
调用虚函数如下,如果覆盖了虚表指针,那么调用虚函数的时候就会到shellcode。
UAF漏洞总结
TODO
整数溢出漏洞
整数溢出漏洞介绍
0x00 前言
整数溢出的利用,与堆栈溢出的方法类似,首先构造相应的数值造成整数溢出,然后根据溢出内存是在堆上还是栈上采取不同的利用方法。
0x01 原理
1.1 整数
整数分为有符号和无符号两种类型,有符号数以最高位作为其符号位,即正整数最高位为1,负整数最高位为0,而无符号无此类情况,取值范围为非负数。以32位Windows系统为例:short int(2字节),int(4字节),unsigned int(书的电子版本是2字节,但是笔者觉得是4字节)
VC6.0(x86)中定义的整数变量取值范围:
类型 | 占用字节数 | 取值范围 |
---|---|---|
int | 4 | -2147483648~2147483647 |
short int | 2 | -32768~32767 |
long int | 4 | -2147483648~2147483647 |
unsigned int | 4 | 0~4294967295 |
unsigned short int | 4 | 0~65535 |
unsigned long int | 4 | 0~4294967295 |
1.2 基于栈的整数溢出
1.2.1 代码
#include "stdio.h"
#include "string.h"
int main(int argc, char *argv){
int i;
char buf[8]; // 栈缓冲区
unsigned short int size; // 无符号短整数取值范围:0 ~ 65535
char overflow[65550];
memset(overflow,65,sizeof(overflow)); // 填充为“A”字符
printf("请输入数值:\n");
scanf("%d",&i);
size = i;
printf("size:%d\n",size); // 输出系统识别出来的size数值
printf("i:%d\n",i); // 输出系统识别出来的i数据
if (size > 8)
return -1;
memcpy(buf,overflow,i); // 栈溢出
return 0;
}
代码问题如下,将int类型的i赋予给了unsigned short int类型的size,size得到的值是i % 65535,检测的值是size,但是复制的大小却是i,所以如果,i % 65535 < 8,那么无论i多大都会进行memcpy。
1.3 基于堆的整数溢出
1.3.1 代码
#include "stdio.h"
#include "windows.h"
int main(int argc, char * argv)
{
int* heap;
unsigned short int size;
char *pheap1, *pheap2;
HANDLE hHeap;
printf("输入size数值:\n");
scanf("%d",&size);
hHeap = HeapCreate(HEAP_GENERATE_EXCEPTIONS, 0x100, 0xfff);
if (size <= 0x50)
{
size -= 5;
printf("size:%d\n",size);
pheap1 = HeapAlloc(hHeap, 0, size);
pheap2 = HeapAlloc(hHeap, 0, 0x50);
}
HeapFree(hHeap, 0, pheap1);
HeapFree(hHeap, 0, pheap2);
return 0;
}
代码问题如下,size为4的时候满足小于0x50,随后-5,由于是unsigned short int类型,所以不会是-1,而是65535,但是堆的最大大小为4095字节,所以分配不了造成了溢出。
栈溢出总结
0x00 前言
此篇文章首次更新于2023年10月5日。于鄙人读完了0day以及做完漏洞战争整数溢出之后。
0x01 总结
第一版本暂时随意写写,后续更新。
整数溢出的调试手段,基本是通过栈回溯找到漏洞函数,首先触发崩溃,然后查看是否该处就是漏洞函数,如果是,那么追踪变量,如果不是,需要去找疑似的变量,甚者可以根据PoC反推可能出现的位置,对于一些需要累加的循环,可以通过条件断点打印每一次循环的关键变量数值便于追踪漏洞点。
0x02 问题
- 源码级的动态调试对于不在内存中的变量如何查看,是否只有通过看汇编看寄存器得到结果,但是这样太容易丢失变量值了。
- 整数溢出是否能自动化,汇编级的自动化是否会比源码级的更准确,比如无符号指令涉及的变量最后用到了有符号指令上。
格式化字符串漏洞
格式化字符串漏洞介绍
0x00 前言
格式化字符串(Format String)漏洞最早是在1999年被Tymm Twillman发现的,他在著名的BugTrag邮件列表中公布了一篇关于proftpd软件漏洞的文章Exploit for proftpd 1.2.0pre6,但当时并没有被圈内人士重视。直到2000年,tf8在BugTrag上公布了一份利用wu-ftpd格式化字符串漏洞实现任意代码执行的漏洞,才使得格式化字符串这类漏洞被广为人知。
0x01 原理
格式化字符串漏洞产生的主要源于对用户输入的内容未进行过滤,这些输入数据都是作为参数传递给某些执行格式化操作的函数,如printf,fprintf,vprintf,sprintf等。恶意用户可以使用%s和%x等格式符,从堆栈或其他内存位置输出数据,也可以使用%n向任意地址写入任意数据,配合printf函数和其它类似功能的函数就可以像任意地址写入被格式化的字节数,可能导致任意代码执行,或者从漏洞程序读取敏感信息,比如密码等。
漏洞代码:
#include <stdio.h>
#include <string.h>
int main (int argc, char *argv[])
{
char buff[1024]; // 设置栈空间
strncpy(buff,argv[1],sizeof(buff)-1);
printf(buff); //触发漏洞
return 0;
}
strncpy执行的时候
buff=0x12fb4c。
然后由于printf的基本形式是printf(“格式化控制字符”,参数列表)
所以这里会当栈上有两个参数。从栈顶依次是,buff地址,strncpy的dst(也是buff地址),argv[1],maxlen
然后信息就泄露了。(为啥strncpy的参数没有被回收啊,奇怪。。。)
此外还可以用%n写入数据。
格式化字符串漏洞总结
通过%n格式化控制符实现任意数据(ECX 输出字符控制)写任意地址(EAX 变量控制),一般用Shellcode地址覆盖返回地址或者SEH异常地址。
应该还要加东西
数组越界漏洞
数组越界漏洞与溢出的关系
数组越界访问包含读写类型,溢出属于数据写入
通常数组越界访问是由于数组下标数值超出数组元素个数导致的,比如定义数组buf[5],但程序却通过buf[8]来访问数据,此时即为数组越界。数组的读写操作有时是同时并存。
部分溢出漏洞的本质就是数组越界
导致溢出的原因有很多种,有些正式由于对数组下标范围未作有效限制,导致允许越界访问数组并对其写入数据。如果数组在栈上就是栈溢出,在堆上就是堆溢出。
数组越界(访问)犹如倒错水杯,溢出犹如倒水时从被子里漫出来。
TODO 其实是我觉得原理太简单了暂时不想写。。。
文章评论