绕过__chkesp堆栈检查

2023-11-08

    前面很多注入相关的文章中都提到为了保证注入后原始程序能恢复正常的执行流,需要在编译器中关闭堆栈检查。为了解决问题,这是个好手段,但是不得不说这是回避问题,不是根本上解决问题。本文旨在解决这个问题。

   vs用 __chkesp来实现堆栈检查。__chkesp顾名思义,检查esp的值,检查失败就抱错。什么时候esp会出错?情况很多,如果排除缓冲区溢出的可能,那还有堆栈失衡的情况。比如不正常的退出函数方式。我经常遇到的不正常退出函数引发_chkesp失败一般都发生在修改堆栈上保存的返回地址,使之指向某个裸函数,然后从裸函数执行退出(或者跳转)之后。

    要绕过__chkesp检查,首先要知道怎样的函数调用方式会引起堆栈检查。调用Windows提供的API后编译都会安排一段__chkesp,另外在"直接调用地址"返回后,也会被插入这段代码。那么什么是"直接调用地址"?来看一段代码:

typedef int (*funcAddAddress)(int,int);

int add(int a,int b)
{
	return a+b;
}

int _tmain(int argc, _TCHAR* argv[])
{
	funcAddAddress funcAddAddressPtr = (funcAddAddress)add; 
	add(1,1); //(1)
	printf("======\n");
	(*funcAddAddressPtr)(1,1); //(2)
	return 0;
}
    对同一个函数进行调用,(1)是普通调用方式,不会产生__chkesp,而(2)是我所谓的"直接调用地址"方式,函数返回后会被插入__chkesp。下面用反汇编代码验证一下这个说法:
	funcAddAddress funcAddAddressPtr = (funcAddAddress)add;
013513EE  mov         dword ptr [funcAddAddressPtr],offset add (1351091h)  
	add(1,1);
013513F5  push        1  
013513F7  push        1  
013513F9  call        add (1351091h)  
013513FE  add         esp,8  
	printf("======\n");
01351401  mov         esi,esp  
01351403  push        offset string "======\n" (135573Ch)  
01351408  call        dword ptr [__imp__printf (13582B0h)]  
0135140E  add         esp,4  
01351411  cmp         esi,esp  
01351413  call        @ILT+310(__RTC_CheckEsp) (135113Bh)  
	(*funcAddAddressPtr)(1,1);
01351418  mov         esi,esp  
0135141A  push        1  
0135141C  push        1  
0135141E  call        dword ptr [funcAddAddressPtr]  
01351421  add         esp,8  
01351424  cmp         esi,esp  
01351426  call        @ILT+310(__RTC_CheckEsp) (135113Bh)  
	return 0;
0135142B  xor         eax,eax  
此处补发一个相关的pdf: 产生__chkesp的函数调用方式 

    既然知道了编译器会在什么时候插入__chkesp代码,接下来进入本文的正题,__chkesp检查失败和绕过__chkesp堆栈检查。

    先看下__chkesp检查失败的情况(反面教材):

#include "stdafx.h"
unsigned int retAddress;
void Test();
void NormalFunc()
{
    //data[1]: ebp的值; data[4] :函数返回地址
    unsigned int data[1] = {0x0};
    unsigned int* ptr = data;
    ptr+=3;
    //保存返回地址
    retAddress = *ptr;
    *ptr = (unsigned int)Test;
    return;
}

void Test()
{
    //跳回到main函数体中!
    __asm
    {
        lea eax,[ebp+0x04];
        mov eax,[eax]
        mov retAddress,eax;
        push retAddress;
        ret
    }
}

typedef void (*DirectCallFunc)();

int main()
{
    DirectCallFunc dirCallFunc = NormalFunc;
    (*dirCallFunc)();
    getchar();
    return 0;
}

函数调用前ESI/ESP的值:


函数调用后ESI/ESP的值:


很明显,因为esi!=esp所以引起__chkesp检查失败。接着看看引起失败的原因:

函数调用前,编译器保存了当前栈指针:

	(*dirCallFunc)();
00411435  mov         esi,esp  
00411437  call        dword ptr [dirCallFunc]  
0041143A  cmp         esi,esp  
0041143C  call        @ILT+300(__RTC_CheckEsp) (411131h)  
	getchar();
并在函数中通过PROLOG/EPILOG生成/恢复函数帧框架:

void Test()
{
00411A90  push        ebp  
00411A91  mov         ebp,esp  
00411A93  sub         esp,0C0h  
00411A99  push        ebx  
00411A9A  push        esi  
00411A9B  push        edi  
00411A9C  lea         edi,[ebp-0C0h]  
00411AA2  mov         ecx,30h  
00411AA7  mov         eax,0CCCCCCCCh  
00411AAC  rep stos    dword ptr es:[edi]

}
00411ABF  pop         edi  
00411AC0  pop         esi  
00411AC1  pop         ebx  
00411AC2  add         esp,0C0h  
00411AC8  cmp         ebp,esp  
00411ACA  call        @ILT+300(__RTC_CheckEsp) (411131h)  
00411ACF  mov         esp,ebp  
00411AD1  pop         ebp  
00411AD2  ret
原本main调用NormalFunc,NormalFunc有编译器生成函数调用框架并在执行完毕后顺利的返回到main函数中,此时堆栈平衡通过__chkesp检查。但是由于NormalFunc的返回地址被修改成Test中,节外生枝的跳转到Test中执行。如果Test函数正常终止,那么函数调用前后将还是堆栈平衡的,进一步说就是函数调用完成后esp==函数调用前esi的值==函数调用前esp的值,因此程序可以毫无悬念的通过了__chkesp检查。然而,这里的Test并不是这样的普通青年,他只正常的走过了PROLOG代码开辟新堆栈,此时esp已发生了变动,然后他2B的通过push/ret的方式,从函数中间越过EPILOG恢复堆栈的代码返回到main函数中(也只能通过这种方式返回到main函数中)。因此函数调用完成后esp!=函数调用前esi的值==函数调用前esp的值,在__chkesp关卡上被截住了~

    现在找到失败的原因了,那找出相应的解决方法也不难:原本Test由编译器自动生成函数栈,大不了取消编译器做这个步骤就行了。这样虽然进入了Test却不额外生成函数帧,Test函数就像main函数的一部分似得,另外由于push/ret是一段esp自平衡的操作,因此堆栈还是维持NormalFunc结束时的样子。

__declspec(naked) void Test()
{
	//跳回到main函数体中!
	__asm
	{
		push retAddress;
                ret
	}
}
    一个什么都没干的裸函数(想干啥自己补全吧),程序仍然从函数中间退出,不过至少能通过__chkesp检查。

    裸函数是简单粗暴的解决方式,但是裸函数内部不能定义局部变量,完全不好用。这就得提出新的改变方案:1.函数得正常开辟调用堆栈,可以正常使用变量;2.函数不经过编译器生成的EPILOG的洗礼,从函数中间返回main函数;3讲了这么多,最重要的,必须能通过_chkesp的检查,否则,并没有什么卵用~

    仔细想想,函数不过是越过EPLLOG代码,然后返回main函数所以才出错的么?大不了在返回前参考vs的代码自己来做EPILOG,来抵消进入Test时PROLOG的影响不就行了?

void Test()
{
	__asm
	{
		pop         edi
		pop         esi
		pop         ebx
		mov         esp,ebp
		pop         ebp
	}
	//跳回到main函数体中! 
        __asm
        {
                push retAddress;
                ret
        }
}
来看下程序调用前后的结果:

附注,也可以把retAddress的地址修改为__chkesp后面的地址~

参考文档:越过__chkesp堆栈检查

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

绕过__chkesp堆栈检查 的相关文章

随机推荐

  • 在有NVIDIA显卡的机器上安装Ubuntu 18.04 LTS的一些建议

    在装有NVIDIA显卡的机器上安装Ubuntu 18 04 LTS的一些建议 如果安装途中出现问题 导致不能正常进入系统 请看以下步骤 一 编辑Grub Ubuntu的引导程序 二 root下更改 三 重启进入系统 下载驱动 如果安装途中出
  • Raven2渗透

    1 实训目的 通过此次实验来学习对Raven2的渗透 Raven 2是中级boot2root VM 有四个要捕获的标志 在多次破坏之后 Raven Security采取了额外的措施来加固其Web服务器 以防止黑客 找到四个flag 学习一些
  • /etc/login.defs配置文件详解

    etc login defs 文件是用来定义创建用户时需要的一些用户配置信息 如创建用户时 是否需要家目录 UID和GID的范围 用户及密码的有效期限 家目录的权限 密码加密方式等等
  • SpringBoot开发使用篇(2)—数据层解决方案

    目录 一 数据层解决方案 1 1 SQL 1 1 1 数据源配置 Hikari 1 1 2 持久化技术 JdbcTemplate 1 1 3 H2数据库 1 2 NoSQL 1 2 1 Redis 1 2 2 Mongodb 1 2 3 E
  • 链式法则

    2个事件同时发生的概率 P a b P a b P b 其中 P a b 表示 a和b事件同时发生的概率 P a b 是一个条件概率 表示在b事件发生的条件下 a发生的概率 3个事件的概率链式调用 P a b c P a b c P b c
  • 三、Linux网络编程:Socket编程-网络模型

    3 Socket编程 网络模型 3 1 OSI七层模型 图解 每层的功能 模型 功能 物理层 比特流传输 数据链路层 网络控制 链路纠错 网络层 寻址 路由 传输层 建立主机端到端的连接 会话层 建立 维护和管理会话 表示层 格式转化 加密
  • 解决sqlplus /as sysdba登陆oracle无效

    安装完oracle 然后执行完下面的自动配置脚本后 没有任何地方设置过密码 etc init d oracledb ORCLCDB 19c configure 在这个命令执行完成后 会提醒查看完整日志的地方 Look at the log
  • C语言100例 第一天习题练习

    C语言中基本的输入与输出 例题1 输入两个正整数a和b 输出a b的值 其中a b 10000 include
  • Centos7 开机卡死在桌面

    问题 Centos7 开机死卡成了这样 一动不动 如下图 原因 一般来说是一些开机自启的东西使得Centos卡死 有可能是在 etc rc d rc local文件里加入的脚本 也有可能 etc fstab文件里面自动挂载的硬盘 解决方法
  • 【自然语言处理】情感分析(三):基于 Word2Vec 的 LSTM 实现

    情感分析 三 基于 Word2Vec 的 LSTM 实现 本文是 情感分析 系列的第 3 3 3 篇 前两篇分别是 自然语言处理 情感分析 一 基于 NLTK 的 Naive Bayes 实现 自然语言处理 情感分析 二 基于 scikit
  • jmeter调试错误大全

    一 前言 在使用jmeter做接口测试的过程中大家是不是经常会遇到很多问题 但是无从下手 不知道从哪里开始找起 对于初学者而言这是一个非常头痛的事情 这里结合笔者的经验 总结出以下方法 二 通过查看运行日志调试问题 写好脚本后 可以先试着运
  • 【保姆级】Python最新版3.11.1开发环境搭建,看这一篇就够了(适用于Python3.11.2安装)

    工欲善其事必先利其器 在使用Python开发程序之前 在计算机上搭建Python开发环境是必不可少的环节 目前Python最新稳定版本是3 11 1 且支持到2027年 如下图所示 本文手把手带你从0 到1搭建Python最新版3 11 1
  • 如何在Mac上远程控制另一台Mac

    1 先请在苹果 Mac 电脑上的 系统偏好设置 窗口中打开 共享 功能 2 接着在共享窗口中的左侧点击启用 屏幕共享 选项 3 当屏幕共享功能打开以后 请点击 电脑设置 按钮 4 随后请勾选二个选项 VNC 显示程序可以使用密码控制屏幕 并
  • 异步赠书:9月重磅新书升级,本本经典

    本期活动已结束 新活动地址 http blog csdn net epubit17 article details 78210459 获奖读者名单 如下 领取赠书步骤 1 加入异步社区活动QQ群439467328 2 在下方地址中填写收件信
  • java.lang.NoSuchMethodError: javax.servlet.http.HttpServletRequest.isAsyncStarted()Z 的解决

    jetty 9 嵌入式开发时 启动正常 但是页面一浏览就报错如下 java lang NoSuchMethodError javax servlet http HttpServletRequest isAsyncStarted Z 原因 j
  • 用i18n 实现vue2+element UI的国际化多语言切换详细步骤及代码

    一 i18n的安装 这个地方要注意自己的vue版本和i1n8的匹配程度 如果是vue2点几 记得安装i18n的 8版本 不然会自动安装的最新版本 后面会报错哦 查询了下资料 好像最新版本是适配的vue3 npm install vue i1
  • angular请求的防抖(debounce)

    在开发项目过程中 我们会遇到这样的场景 当用户在搜索框中输入名字时 当用户输入完毕后 自动发送搜索请求 实时响应 而不是多按一个按钮或者回车键 如果按照常规思路 我们会绑定input的keyup事件 每次击键后 执行相对应的请求函数 但是
  • MyBatis 3 提示 Column ‘******‘ specified twice

    造成错误的原因是 Mapper xml 配置文件 insert 语句写入重复字段 错误配置文件展示
  • 如何进行本地分支管理

    文章目录 如何进行本地分支管理 Git进行分支管理 显示分支一览表 创建分支 转到新创建的分支 创建分支并转到新创建的分支 分支合并 删除分支 冲突合并 Tortoise进行分支管理 显示分支 创建分支 切换分支 分支合并 冲突合并 VS2
  • 绕过__chkesp堆栈检查

    前面很多注入相关的文章中都提到为了保证注入后原始程序能恢复正常的执行流 需要在编译器中关闭堆栈检查 为了解决问题 这是个好手段 但是不得不说这是回避问题 不是根本上解决问题 本文旨在解决这个问题 vs用 chkesp来实现堆栈检查 chke