Win32汇编学习笔记09.SEH和反调试

news/2025/1/9 21:15:57/文章来源:https://www.cnblogs.com/weiyuanzhang/p/18662913

Win32汇编学习笔记09.SEH和反调试-C/C++基础-断点社区-专业的老牌游戏安全技术交流社区 - BpSend.net

SEH - structed exception handler 结构化异常处理

跟筛选一样都是用来处理异常的,但不同的是 筛选器是整个进程最终处理异常的函数,但无法做到比较精细的去处理异常(例如处理某个函数的异常), 跟 C++ 的 try { } catch { } 的思路一脉相承, SHE 实现的 就是 函数自己 来处理自己的异常,实现方式就是通过回调函数实现的,把回调函数注册给操作系统,当你函数内部出现异常时,系统就会调用你的回调函数,此时就可以处理了,处理完之后可以继续执行代码或者把异常交给筛选器

因此要使用SHE只需要做2件事,1是自己实现异常回调函数,2是吧异常回调函数注册给系统

把函数注册给系统的方式就是 把函数地址 存到 fs:[0] 就可以了

img

可以看到 偏移为0 的位置 是一个异常链 表, , 记录的是一个结构体指针

因此我们需要构造一个结构体 , 把 函数地址 放到 Handler

img

回调函数声明在 msdn 是没有定义,这是微软没有文档化的函数,但是在微软 C 库的 实现用了,可以直接到里面去搜

img

参数: 第一个 异常记录 (异常信息) 第二个 不用管 第3个环境记录 (寄存器环境) 第4个也可以不用管

第2个和第4个是给嵌套异常和展开异常用的

声明

img

第3个和第四个也是给 嵌套异常 和展开异常用的

.586
.model flat,stdcall
option casemap:noneinclude windows.incinclude user32.incinclude kernel32.incincludelib user32.libincludelib kernel32.lib;构造结构体 异常回调函数结构体
EXCEPTION_REGISTRATION_RECORD strucNext dd 0           ;调用这异常回调函数函数结构体指针Handler dd 0        ;当前异常回调函数地址  
EXCEPTION_REGISTRATION_RECORD ends.datag_szF0 db "F0",0g_szF1  db "F1",0.codeassume fs:nothing   ;对fs的类型进行强转;处理 F1 异常的回调函数 
F1Handler proc uses esi pER:ptr EXCEPTION_RECORD, pFrame:dword, pContext:ptr CONTEXT, pDC:dwordinvoke MessageBox, NULL, offset g_szF1, NULL, MB_OK;ExceptionContinueExecution, - 程序继续执行;ExceptionContinueSearch,    - 此异常我不处理,交给其它处理;异常交给F0 的 异常回调函数处理mov eax, ExceptionContinueSearch     ;返回异常处理方式 不然会直接退出ret
F1Handler endp  ;产生异常函数  F1  
F1 procLOCAL @err:EXCEPTION_REGISTRATION_RECORDLOCAL @dwOldSeh:dword    ;原先的过程函数地址;保存调用者的异常回调函数mov eax, fs:[0]mov @dwOldSeh, eax  ;保存调用者回调函数地址 到 next,不然无法找到调用者异常函数处理的地址mov eax, fs:[0]mov @err.Next, eax   ;注册异常回调mov @err.Handler, offset F1Handlerlea eax, @errmov fs:[0], eax;产生异常xor esi, esidiv esi;卸载SEH   还原过程函数(不然 F0 产生的异常会又回来)mov eax, @dwOldSehmov fs:[0], eaxret
F1 endp  ;处理 F0 异常的回调函数
F0Handler proc pER:ptr EXCEPTION_RECORD, pFrame:dword, pContext:ptr CONTEXT, pDC:dwordinvoke MessageBox, NULL, offset g_szF0, NULL, MB_OK;处理 F1 产生的除0异常   assume esi:ptr EXCEPTION_RECORD   ;类型强转mov esi, pER      ;将 异常信息 pER 给  esi  .if [esi].ExceptionCode == EXCEPTION_INT_DIVIDE_BY_ZERO   ;如果是除0异常;处理,跳过产生异常的代码mov esi, pContextassume esi:ptr CONTEXTadd [esi].regEip, 2    ;除0指令是2个字节   regEip 是返回的地址assume esi:nothingmov eax, ExceptionContinueExecution   ;返回异常处理方式 不然会直接退出ret.endifassume esi:nothingret
F0Handler endp;产生异常函数  F0
F0 procLOCAL @err:EXCEPTION_REGISTRATION_RECORDLOCAL @dwOldSeh:dword;保存调用者的异常回调函数mov eax, fs:[0]mov @dwOldSeh, eax;保存调用者回调函数地址 到 next,不然无法找到调用者异常函数处理的地址mov eax, fs:[0]mov @err.Next, eax;注册异常mov @err.Handler, offset F0Handler ;存入异常回调函数地址lea eax, @errmov fs:[0], eaxinvoke F1;产生异常mov eax, 1211hmov [eax], eax;卸载SEHmov eax, @dwOldSehmov fs:[0], eaxret
F0 endpstart:invoke F0xor eax, eaxinvoke ExitProcess,eax
end start

image.png

异常链 : SEH链 尾结点是系统默认的异常函数处理地址

但是我们一般不会像上面写

因为结构体是2成员, 一个是调用者的异常回调函数信息结构体地址 , 一个是自己的异常回调函数地址,都是 4字节

那么我们只需要在栈上 push 2个 dword(2个地址指针) ,就可以了

.586
.model flat,stdcall
option casemap:noneinclude windows.incinclude user32.incinclude kernel32.incincludelib user32.libincludelib kernel32.lib.datag_szF0 db "F0",0g_szF1  db "F1",0.codeassume fs:nothingF1Handler proc uses esi pER:ptr EXCEPTION_RECORD, pFrame:dword, pContext:ptr CONTEXT, pDC:dwordinvoke MessageBox, NULL, offset g_szF1, NULL, MB_OK;ExceptionContinueExecution, - 程序继续执行;ExceptionContinueSearch, - 此异常我不处理,交给其它处理mov eax, ExceptionContinueSearchret
F1Handler endp  F1 proc;注册SEHpush offset F1Handler             ; handler push 自己异常回调函数的地址push fs:[0] ;next                 ;push 调用者异常处理结构体信息地址mov fs:[0], esp                 ;注册回调函数,移位此时esp 存的就是结构体首地址xor esi, esidiv esi;卸载SEHpop fs:[0]          ;把 next 弹回 fs:[0]         add esp, 4          ;平栈,因为自己的异常回调函数地址不需要弹栈,直接丢弃ret
F1 endp  F0Handler proc pER:ptr EXCEPTION_RECORD, pFrame:dword, pContext:ptr CONTEXT, pDC:dwordinvoke MessageBox, NULL, offset g_szF0, NULL, MB_OKassume esi:ptr EXCEPTION_RECORDmov esi, pER.if [esi].ExceptionCode == EXCEPTION_INT_DIVIDE_BY_ZERO;处理,跳过产生异常的代码mov esi, pContextassume esi:ptr CONTEXTadd [esi].regEip, 2assume esi:nothingmov eax, ExceptionContinueExecutionret.endifassume esi:nothingret
F0Handler endpF0 proc;注册异常; | next      | <--esp; | Fohandler | push offset F0Handler ;handler    ;push 自己异常回调函数的地址push fs:[0] ;next                 ;push 调用者异常处理结构体信息地址mov fs:[0], esp                   ;注册回调函数,移位此时esp 存的就是结构体首地址invoke F1;产生异常mov eax, 1211hmov [eax], eax;卸载SEH   pop fs:[0]          ;把 next 弹回 fs:[0]         add esp, 4          ;平栈,因为自己的异常回调函数地址不需要弹栈,直接丢弃ret
F0 endpstart:invoke F0xor eax, eaxinvoke ExitProcess,eax
end start

反调试

一切阻止调试的方法都被称为反调试

在 OD 或者 x32Dbg 中下断点时,他会插入一行代码,但在调试器中看不出来的 那就是把这一行指令在内存的第一个字节改成了 CC (int 3)

img

当 TF 被置位 为 1 时 ,执行一行代码 就会 抛出异常,抛出异常之后就会恢复为 0,因此可以不断通过 改变 TF 位,来判断每一行代码,判断是否被下断点

.586
.model flat,stdcall
option casemap:noneinclude windows.incinclude user32.incinclude kernel32.incincludelib user32.libincludelib kernel32.lib.datag_szCaption db "友情提示",0g_szText  db "你干嘛调试我?",0g_szText2 db "结束了", 0g_ddEnd dd 0        ;函数结束地址.codeassume fs:nothingFuncTest proc;存储mov g_ddEnd, offset ENDTF  ;保存函数结束位置;设置TF标志位  (将值置为1 就会抛异常)pushfdor dword ptr [esp], 100hpopfdxor eax, eaxxor eax, eaxxor eax, eaxxor eax, eaxxor eax, eaxxor eax, eaxxor eax, eaxxor eax, eaxxor eax, eaxxor eax, eaxxor eax, eaxxor eax, eaxxor eax, eaxxor eax, eaxENDTF:retFuncTest endp;异常回调函数,将 TF 置位
F0Handler proc uses esi edi pER:ptr EXCEPTION_RECORD, pFrame:dword, pContext:ptr CONTEXT, pDC:dwordassume esi:ptr EXCEPTION_RECORDmov esi, pERmov edi, pContextassume edi:ptr CONTEXT;判断mov eax, [edi].regEip.if byte ptr [eax] == 0cch   ;指令的第一个字节是CC 说明被调试;被设置断点了invoke MessageBox, NULL, offset g_szText, offset g_szCaption, MB_OKinvoke ExitProcess, 0    ;退出进程.endif;结束mov eax, g_ddEnd.if [edi].regEip == eax    ;程序结束, TF就不需要置位了mov eax, ExceptionContinueExecutionret.endif;继续设置TF标志位or [edi].regFlag, 100hmov eax, ExceptionContinueExecutionassume edi:nothingassume esi:nothingret
F0Handler endpF0 proc;注册异常; | next      | <--esp; | Fohandler | push offset F0Handler ;handlerpush fs:[0] ;nextmov fs:[0], espinvoke FuncTest;卸载SEHpop fs:[0]add esp, 4ret
F0 endpstart:invoke F0invoke MessageBox, NULL , offset g_szText2, NULL, MB_OKxor eax, eaxinvoke ExitProcess,eax
end start

对于部分调试器,他会接收所有异常,这种处理方式就是 把 主要代码放在异常中实现

异常很多时候都被用作反调试

对抗反调试的方法:

把代码分成多块,每块做加密,执行每块代码之前 先进异常还原,还原之后再进异常变成加密状态,因为不解密前面的代码,无法知道后面的代码去哪

处理方法:代码追踪,把执行的每一行代码记录下来

把代码放到堆里面,在堆里面执行完再回到代码区,这样代码追踪就失效了,因为重启地址就变了

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/866749.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Windows攻防】Windows注册表 IFEO注入

介绍 IFEO 注入攻击是一种基于注册表的攻击技术,几乎可以保证以登录用户甚至管理员/系统用户的身份执行代码。在任何你可以想象的用例中,如果你想要将你的有效载荷绑定到 Windows 平台上二进制文件的“启动”,IFEO(图像文件执行选项)可能是你最好的选择。这是一种开发人员…

[Java] 计算Java对象大小

序在Java应用程序的性能优化场景中,时常需要考虑Java对象的大小,以便评估后,进一步提出优化方案:占用内存的大小。(比如 本地内存) 对象数据在网络传输中占用的网络带宽 对象数据在存储时占用的磁盘空间 ...概述 对象大小如何计算对象大小包括俩部分的内容,对象头和对象…

并行前缀(Parallel Prefix)加法器

并行前缀(Parallel Prefix)加法器 并行前缀加法器的基本介绍 二进制加法器是目前数字计算单元中的重要模块,基础的加法器架构包括行波进位加法器(Ripple Carry Adder),超前进位加法器(Carry Look-Ahead Adder),进位选择加法器(Carry Select Adder)等。加法器的进位传…

科技风?写实风?教你设置多风格三维地图

概述 三维地图通过高度、深度、立体感等表现形式,能够真实还原地形地貌、城市建筑和空间结构。相比二维地图,它能够更清晰地展示复杂的地理数据,帮助用户快速理解空间关系,如地形起伏、建筑高度等。在实际应用中,我们可以将不同风格的三维地图作为项目的主体元素进行展示,…

【模拟电子技术】03-PN与二极管的特性

【模拟电子技术】03-PN与二极管的特性上节中有提到对PN结施加反向电压时,会使得PN结所形成的势垒增加,阻止多子到另一边。在掺杂浓度比较低的时候,外加电场加强,中间的耗尽层会加长,变成了一个粒子加速器,自由电子进去后不断加速。直到某一电场强度时,粒子加速足够大的时…

NocoBase 本周更新汇总:支持大规模数据量的导入和导出

本周更新包括:支持大规模数据量的导入和导出等。汇总一周产品更新日志,最新发布可以前往我们的博客查看。 NocoBase 目前更新包括的版本更新包括三个分支:main ,next和 develop。main :截止目前最稳定的版本,推荐安装此版本。 next:包含即将发布的新功能,经过初步测试的…

MSSQL:DBLINK连接oracle 19

无法为该请求检索数据。(Microsoft.SqlServer.Management.Sdk.Sfc)其他信息:执行Transact-SQL语句或批处理时发生了异常。(Microsoft.SqlServer.ConnectionInfo)在与SQL Server 建立连接时出现与网络相关的特定于实例的错误。未找到或无法访问服务器。请验证实例名称是否正…

五款强大报表软件助力企业提升数据分析效率

本文将为大家介绍五款功能强大的报表软件,包括山海鲸报表、JReport、Power BI、Zoho Analytics 和 SAP Crystal Reports。这些工具各具特色,能够帮助企业快速生成数据报表并进行深度分析。无论是数据可视化、报表定制、自动化生成还是与其他系统的集成,它们都能为企业的决策…

Linq中的设置操作 (C#):Distinct 和 DistinctBy、Except 和 ExceptBy、Intersect 和 IntersectBy、Union 和 UnionBy

LINQ 中的集运算是指根据相同或单独集合中是否存在等效元素来生成结果集的查询运算。 注:这些示例使用 System.Collections.Generic.IEnumerable<T> 数据源。 基于 System.Linq.IQueryProvider 的数据源使用 System.Linq.IQueryable<T> 数据源和表达式树。 表达式…

2025多校冲刺省选模拟赛3

过于困难,直接放弃2025多校冲刺省选模拟赛3\(T1\) A. 等差 \(100pts/100pts\)考虑哈希,每 \(k\) 个作为一组与上一组统一计算。取 \(Base>\) 值域时用高精度来存储并判断的正确性显然。观察到可行的最小的 \(k\) 单调不降,不妨直接枚举答案。暴力实现时间复杂度为 \(O(n…

还不会 Cert Manager 自动签发证书?一文掌握

相信很多小伙伴对于 Cert Manager 不陌生,Cert Manager 是 Kubernetes 上的证书管理工具,基于 ACME 协议与 Lets Encrypt 签发免费证书并为证书自动续期,实现永久免费使用证书。 本文将介绍如何使用 Cert Manager 实现自动签发证书并与 Rainbond 结合使用。 Cert Manager 概…

JAVA-Day 09:While循环语句

While循环 while循环格式 初始化语句; while(条件判断语句){ 循环体语句; 条件控制语句; } 初始化语句只执行一次 判断语句为True,循环继续 判断语句为False,循环结束 例: 世界最高山峰珠穆朗玛峰的高度为8844.43米=8844430毫米,假如有 一张足够大的纸,它的厚度为0.1毫米。…