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

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

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

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

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

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

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

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

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

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

声明

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

.586
.model flat,stdcall
option casemap:none include windows.inc
include user32.inc
include kernel32.inc includelib user32.lib
includelib kernel32.lib ;构造结构体 异常回调函数结构体
EXCEPTION_REGISTRATION_RECORD struc
Next dd 0 ;调用这异常回调函数函数结构体指针
Handler dd 0 ;当前异常回调函数地址
EXCEPTION_REGISTRATION_RECORD ends .data
g_szF0 db "F0",0
g_szF1 db "F1",0 .code
assume fs:nothing ;对fs的类型进行强转 ;处理 F1 异常的回调函数
F1Handler proc uses esi pER:ptr EXCEPTION_RECORD, pFrame:dword, pContext:ptr CONTEXT, pDC:dword invoke MessageBox, NULL, offset g_szF1, NULL, MB_OK ;ExceptionContinueExecution, - 程序继续执行
;ExceptionContinueSearch, - 此异常我不处理,交给其它处理 ;异常交给F0 的 异常回调函数处理
mov eax, ExceptionContinueSearch ;返回异常处理方式 不然会直接退出
ret
F1Handler endp ;产生异常函数 F1
F1 proc
LOCAL @err:EXCEPTION_REGISTRATION_RECORD
LOCAL @dwOldSeh:dword ;原先的过程函数地址 ;保存调用者的异常回调函数
mov eax, fs:[0]
mov @dwOldSeh, eax ;保存调用者回调函数地址 到 next,不然无法找到调用者异常函数处理的地址
mov eax, fs:[0]
mov @err.Next, eax ;注册异常回调
mov @err.Handler, offset F1Handler
lea eax, @err
mov fs:[0], eax ;产生异常
xor esi, esi
div esi ;卸载SEH 还原过程函数(不然 F0 产生的异常会又回来)
mov eax, @dwOldSeh
mov fs:[0], eax ret
F1 endp ;处理 F0 异常的回调函数
F0Handler proc pER:ptr EXCEPTION_RECORD, pFrame:dword, pContext:ptr CONTEXT, pDC:dword invoke 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, pContext
assume esi:ptr CONTEXT
add [esi].regEip, 2 ;除0指令是2个字节 regEip 是返回的地址
assume esi:nothing mov eax, ExceptionContinueExecution ;返回异常处理方式 不然会直接退出
ret
.endif
assume esi:nothing ret
F0Handler endp ;产生异常函数 F0
F0 proc
LOCAL @err:EXCEPTION_REGISTRATION_RECORD
LOCAL @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, @err
mov fs:[0], eax invoke F1 ;产生异常
mov eax, 1211h
mov [eax], eax ;卸载SEH
mov eax, @dwOldSeh
mov fs:[0], eax
ret
F0 endp start:
invoke F0
xor eax, eax
invoke ExitProcess,eax
end start

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

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

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

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

.586
.model flat,stdcall
option casemap:none include windows.inc
include user32.inc
include kernel32.inc includelib user32.lib
includelib kernel32.lib .data
g_szF0 db "F0",0
g_szF1 db "F1",0 .code
assume fs:nothing F1Handler proc uses esi pER:ptr EXCEPTION_RECORD, pFrame:dword, pContext:ptr CONTEXT, pDC:dword invoke MessageBox, NULL, offset g_szF1, NULL, MB_OK ;ExceptionContinueExecution, - 程序继续执行
;ExceptionContinueSearch, - 此异常我不处理,交给其它处理
mov eax, ExceptionContinueSearch
ret
F1Handler endp F1 proc
;注册SEH
push offset F1Handler ; handler push 自己异常回调函数的地址
push fs:[0] ;next ;push 调用者异常处理结构体信息地址
mov fs:[0], esp
;注册回调函数,移位此时esp 存的就是结构体首地址 xor esi, esi
div esi ;卸载SEH
pop fs:[0] ;把 next 弹回 fs:[0]
add esp, 4 ;平栈,因为自己的异常回调函数地址不需要弹栈,直接丢弃 ret
F1 endp F0Handler proc pER:ptr EXCEPTION_RECORD, pFrame:dword, pContext:ptr CONTEXT, pDC:dword
invoke MessageBox, NULL, offset g_szF0, NULL, MB_OK assume esi:ptr EXCEPTION_RECORD
mov esi, pER .if [esi].ExceptionCode == EXCEPTION_INT_DIVIDE_BY_ZERO ;处理,跳过产生异常的代码
mov esi, pContext
assume esi:ptr CONTEXT
add [esi].regEip, 2
assume esi:nothing mov eax, ExceptionContinueExecution
ret
.endif
assume esi:nothing ret
F0Handler endp F0 proc
;注册异常
; | next | <--esp
; | Fohandler | push offset F0Handler ;handler ;push 自己异常回调函数的地址
push fs:[0] ;next ;push 调用者异常处理结构体信息地址
mov fs:[0], esp ;注册回调函数,移位此时esp 存的就是结构体首地址 invoke F1 ;产生异常
mov eax, 1211h
mov [eax], eax ;卸载SEH
pop fs:[0] ;把 next 弹回 fs:[0]
add esp, 4 ;平栈,因为自己的异常回调函数地址不需要弹栈,直接丢弃 ret
F0 endp start:
invoke F0
xor eax, eax
invoke ExitProcess,eax
end start

反调试

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

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

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

.586
.model flat,stdcall
option casemap:none include windows.inc
include user32.inc
include kernel32.inc includelib user32.lib
includelib kernel32.lib .data
g_szCaption db "友情提示",0
g_szText db "你干嘛调试我?",0
g_szText2 db "结束了", 0
g_ddEnd dd 0 ;函数结束地址 .code
assume fs:nothing FuncTest proc
;存储
mov g_ddEnd, offset ENDTF ;保存函数结束位置 ;设置TF标志位 (将值置为1 就会抛异常)
pushfd
or dword ptr [esp], 100h
popfd xor eax, eax
xor eax, eax
xor eax, eax
xor eax, eax
xor eax, eax
xor eax, eax
xor eax, eax
xor eax, eax
xor eax, eax
xor eax, eax
xor eax, eax
xor eax, eax
xor eax, eax
xor eax, eax ENDTF:
ret FuncTest endp ;异常回调函数,将 TF 置位
F0Handler proc uses esi edi pER:ptr EXCEPTION_RECORD, pFrame:dword, pContext:ptr CONTEXT, pDC:dword assume esi:ptr EXCEPTION_RECORD
mov esi, pER mov edi, pContext
assume edi:ptr CONTEXT ;判断
mov eax, [edi].regEip
.if byte ptr [eax] == 0cch ;指令的第一个字节是CC 说明被调试
;被设置断点了
invoke MessageBox, NULL, offset g_szText, offset g_szCaption, MB_OK
invoke ExitProcess, 0 ;退出进程
.endif ;结束
mov eax, g_ddEnd
.if [edi].regEip == eax ;程序结束, TF就不需要置位了
mov eax, ExceptionContinueExecution
ret
.endif ;继续设置TF标志位
or [edi].regFlag, 100h
mov eax, ExceptionContinueExecution assume edi:nothing
assume esi:nothing ret
F0Handler endp F0 proc
;注册异常
; | next | <--esp
; | Fohandler |
push offset F0Handler ;handler
push fs:[0] ;next
mov fs:[0], esp invoke FuncTest ;卸载SEH
pop fs:[0]
add esp, 4 ret
F0 endp start:
invoke F0 invoke MessageBox, NULL , offset g_szText2, NULL, MB_OK xor eax, eax
invoke ExitProcess,eax
end start

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

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

对抗反调试的方法:

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

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

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

Win32汇编学习笔记09.SEH和反调试的更多相关文章

  1. 汇编学习笔记(11)int指令和端口

    格式 int指令也是一种内中断指令,int指令的格式为int n,n是中断类型码.也就是说,使用int指令可以调用任意的中断例程,例如我们可以显示的调用0号中断例程,还记得在汇编学习笔记(10)中我们 ...

  2. Win32汇编学习(4):绘制文本

    这次,我们将学习如何在窗口的客户区"绘制"字符串.我们还将学习关于"设备环境"的概念. 理论: "绘制"字符串 Windows 中的文本是一 ...

  3. 机器学习实战(Machine Learning in Action)学习笔记————09.利用PCA简化数据

    机器学习实战(Machine Learning in Action)学习笔记————09.利用PCA简化数据 关键字:PCA.主成分分析.降维作者:米仓山下时间:2018-11-15机器学习实战(Ma ...

  4. C++ GUI Qt4学习笔记09

    C++ GUI Qt4学习笔记09   qtc++ 本章介绍Qt中的拖放 拖放是一个应用程序内或者多个应用程序之间传递信息的一种直观的现代操作方式.除了剪贴板提供支持外,通常它还提供数据移动和复制的功 ...

  5. thinkphp学习笔记3—项目编译和调试模式

    原文:thinkphp学习笔记3-项目编译和调试模式 1.项目编译 在章节2.4项目编译中作者讲到使用thinkphp的项目在第一次运行的时候会吧核心需要加载的文件去掉空白和注释合并到一个文件中编译并 ...

  6. 汇编学习笔记(3)[bx]和loop

    本文是<汇编语言>一书的学习笔记,对应书中的4-6章. 汇编程序的执行 要想将源代码变为可执行的程序需经过编译.连接两个步骤,WIN7操作系统下需要MASM程序来进行编译连接工作.将MAS ...

  7. Win32汇编学习(5):绘制文本2

    这次我们将学习有关文本的诸多属性如字体和颜色等. 理论: Windows 的颜色系统是用RGB值来表示的,R 代表红色,G 代表绿色,B 代表蓝色.如果您想指定一种颜色就必须给该颜色赋相关的 RGB ...

  8. CSS学习笔记09 简单理解BFC

    引子 在讲BFC之前,先来看看一个例子 <!DOCTYPE html> <html lang="en"> <head> <meta cha ...

  9. Win32汇编学习(1):基本概念

    背景知识 Windows 把每一个 Win32 应用程序放到分开的虚拟地址空间中去运行,也就是说每一个应用程序都拥有其相互独立的 4GB 地址空间,当然这倒不是说它们都拥有 4GB 的物理地址空间,而 ...

  10. 罗云彬win32汇编教程笔记 子函数的声明, 定义与调用

    在主程序中用call指令来调用子程序. Win32汇编中的子程序也采用堆栈来传递参数,这样就可以用invoke伪指令来进行调用和语法检查工作. 一. 子程序的定义 子程序的定义方式如下所示. 子程序名 ...

随机推荐

  1. Week09_day05(Java API操作Hbase)

    package com.wyh.HbaseAPI; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbas ...

  2. C#中的StreamWriter和"谁创建谁释放"原则

    C# 类库中的 StreamWriter 类在释放时会同时关闭其所依赖的基础流对象,这是为了确保所有缓冲数据都被写入基础流中,并且在不再需要 StreamWriter 对象时,基础流对象也能够被及时释 ...

  3. 第二届獬豸杯wp

    第二届獬豸杯wp 容器密码:}2N|n_yxdt!G/Ru}|_zdn$@?6@CD8E 计算机和手机部分已经在第二届 獬豸杯-复现 - 萧瑟迪亲传大弟子 - 博客园这里发过了,服务器部分自己又写了一 ...

  4. $GOPATH/go.mod exists but should not

    开启模块支持后,并不能与GOPATH共存,所以把项目从GOPATH中移出即可

  5. 表访问方法:PostgreSQL 中数据更新的处理方式

    作者:Cary 前言 本文将详细探讨 PostgreSQL 如何处理更新操作.在 PostgreSQL 中,成功的更新可以被视为"插入一条新记录",同时"标记旧记录为不可 ...

  6. 使用 bc4 解决 git 合并冲突问题

    博客地址:https://www.cnblogs.com/zylyehuo/ STEP1:安装 beyond compare 安装地址: https://www.scootersoftware.com ...

  7. pve节点频繁宕机问题排查

    1.时间: 我是大概20220521日上午11:03分收到这个事情开始跟进: 再这之前一直是其他同事在处理,由于最近比较忙,没有安排的事情基本也都没有深入跟进,只是知道个大概. 2.问题现象: ​ q ...

  8. RabbitMQ 消息实现过程+事务+消息确认

    服务端(生产者) 1.引用 rabbitmq 包 2.建立连接工厂 connectionfactory 3.创建 频道 createchannel 4.在频道中 绑定消息队列 5.发布basicpub ...

  9. ASP.NET 日志路径

    默认路径 protected void Button_StreamWrite_Click(object sender, EventArgs e) {     StreamWriter sw = new ...

  10. leetcode每日一题:数组美丽值求和

    引言 ​ 今天的每日一题原题是2278. 字母在字符串中的百分比,直接模拟,逐个匹配,统计letter在原始字符串s中出现的次数,然后再计算所占百分比即可.更换成前几天遇到的更有意思的一题来写这个每日 ...