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

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: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

image.png

异常链 : 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)

img

当 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

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

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

对抗反调试的方法:

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

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

详细数据库MySQL查询语句

查询语句 &#xff08;SELECT [ALL|DISTINCT] <目标列表达式> [,<目标列表达式>] FROM <表名或视图名> [,<表名或视图名>]|(<SELECT 语句>) [AS] <别名> [WHERE <条件表达式>] [GROUP BY <列名1> [HAVING <条件表达式…

解决anaconda prompt找不到的情况

由于打开某个文件夹导致系统卡死了&#xff0c;鼠标使用不了&#xff0c;只能使用快捷键ctrlaltdelete打开&#xff0c;点任务管理器也没什么用&#xff0c;就点了注销选项。 注销&#xff1a;清空缓存空间和注册表信息&#xff0c;向系统发出清除现在登陆的用户的请求。 导致…

计算机网络 (31)运输层协议概念

一、概述 从通信和信息处理的角度看&#xff0c;运输层向它上面的应用层提供通信服务&#xff0c;它属于面向通信部分的最高层&#xff0c;同时也是用户功能中的最低层。运输层的一个核心功能是提供从源端主机到目的端主机的可靠的、与实际使用的网络无关的信息传输。它向高层用…

【C++经典例题】求1+2+3+...+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句

&#x1f493; 博客主页&#xff1a;倔强的石头的CSDN主页 &#x1f4dd;Gitee主页&#xff1a;倔强的石头的gitee主页 ⏩ 文章专栏&#xff1a; 期待您的关注 题目描述&#xff1a; 原题链接&#xff1a; 求123...n_牛客题霸_牛客网 (nowcoder.com) 解题思路&#xff1a; …

开关不一定是开关灯用 - 命令模式(Command Pattern)

命令模式&#xff08;Command Pattern&#xff09; 命令模式&#xff08;Command Pattern&#xff09;命令设计模式命令设计模式结构图命令设计模式涉及的角色 talk is cheap&#xff0c; show you my code总结 命令模式&#xff08;Command Pattern&#xff09; 命令模式&…

【深度学习量化交易13】继续优化改造基于miniQMT的量化交易软件,增加补充数据功能,优化免费下载数据模块体验!

我是Mr.看海&#xff0c;我在尝试用信号处理的知识积累和思考方式做量化交易&#xff0c;应用深度学习和AI实现股票自动交易&#xff0c;目的是实现财务自由~ 目前我正在开发基于miniQMT的量化交易系统——看海量化交易系统。 MiniQMT是一种轻量级的量化交易解决方案&#xff0…

Vue进阶(贰幺贰)npm run build多环境编译

文章目录 一、前言二、实施三、总结&#xff1a;需要打包区分不同环境四、拓展阅读 一、前言 项目开发阶段&#xff0c;会涉及打包部署到多个环境应用场景&#xff0c;在不同环境中&#xff0c;需要进行项目层面的区分&#xff0c;做不同的操作&#xff0c;可以利用打包的--mo…

回归预测 | MATLAB实GRU多输入单输出回归预测

回归预测 | MATLAB实GRU多输入单输出回归预测 目录 回归预测 | MATLAB实GRU多输入单输出回归预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 回归预测 | MATLAB实GRU多输入单输出回归预测。使用GRU作为RNN的一种变体来处理时间序列数据。GRU相比传统的RNN有较好的记…

ARM交叉编译Boost库

Boost下载&#xff1a;点击跳转 编译过程&#xff1a; 生成project-config.jam ./bootstrap.sh --with-librariesfilesystem,thread --with-toolsetgcc 2. 修改project-config.jam&#xff08;位于第12行附近&#xff09; if ! gcc in [ feature.values <toolset> ] …

【.NET】Kafka消息队列介绍,使用Confluent.Kafka集成Kafka消息队列

一、Kafka介绍 kafka是一种高吞吐量、分布式、可扩展的消息中间件系统&#xff0c;最初由LinkedIn公司开发。随着不断的发展&#xff0c;在最新的版本中它定义为分布式的流处理平台&#xff0c;现在在大数据应用中也是十分广泛。 它可以处理大量的实时数据流&#xff0c;被广…

Jenkins内修改allure报告名称

背景&#xff1a; 最近使用Jenkins搭建自动化测试环境时&#xff0c;使用Jenkins的allure插件生成的报告&#xff0c;一直显示默认ALLURE REPORT&#xff0c;想自定义成与项目关联的名称&#xff0c;如图所示&#xff0c;很明显自定义名称显得高大上些&#xff0c;之前…

Elasticsearch学习(1) : 简介、索引库操作、文档操作、RestAPI、RestClient操作

目录 1.elasticsearch简介1.1.了解es1.2.倒排索引正向索引和倒排索引 1.3.es的一些概念:文档和字段&#xff1b;索引和映射&#xff1b;Mysql与ES1.4.安装es、kibana部署单点es部署kibanaIK分词器安装IK分词器与测试扩展与停用词词典总结 部署es集群 2.索引库操作2.1.mapping映…

【Linux】Linux常见指令(上)

个人主页~ 初识Linux 一、Linux基本命令1、ls指令2、pwd命令3、cd指令4、touch指令5、mkdir指令6、rmdir指令7、rm指令8、man指令9、cp指令10、mv命令 Linux是一个开源的、稳定的、安全的、灵活的操作系统&#xff0c;Linux下的操作都是通过指令来实现的 一、Linux基本命令 先…

【Java项目】基于SpringBoot的【校园交友系统】

【Java项目】基于SpringBoot的【校园交友系统】 技术简介&#xff1a;系统软件架构选择B/S模式、SpringBoot框架、java技术和MySQL数据库等&#xff0c;总体功能模块运用自顶向下的分层思想。 系统简介&#xff1a;系统主要包括管理员和用户。 (a) 管理员的功能主要有首页、个人…

点击底部的 tabBar 属于 wx.switchTab 跳转方式,目标页面的 onLoad 不会触发(除非是第一次加载)

文章目录 1. tabBar 的跳转方式2. tabBar 跳转的特点3. 你的配置分析4. 生命周期触发情况5. 总结 很多人不明白什么是第一次加载&#xff0c;两种情况讨论&#xff0c;第一种情况假设我是开发者&#xff0c;第一次加载就是指点击微信开发者工具上边的编译按钮&#xff0c;每点击…

什么是Kafka?有什么主要用途?

大家好&#xff0c;我是锋哥。今天分享关于【什么是Kafka&#xff1f;有什么主要用途&#xff1f;】面试题。希望对大家有帮助&#xff1b; 什么是Kafka&#xff1f;有什么主要用途&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 Kafka 是一个分布式流…

基于QT和C++的实时日期和时间显示

一、显示在右下角 1、timer.cpp #include "timer.h" #include "ui_timer.h" #include <QStatusBar> #include <QDateTime> #include <QMenuBar> Timer::Timer(QWidget *parent) :QMainWindow(parent),ui(new Ui::Timer) {ui->setup…

单片机-定时器中断

1、相关知识 振荡周期1/12us; //振荡周期又称 S周期或时钟周期&#xff08;晶振周期或外加振荡周期&#xff09;。 状态周期1/6us; 机器周期1us; 指令周期1~4us; ①51单片机有两组定时器/计数器&#xff0c;因为既可以定时&#xff0c;又可以计数&#xff0c;故称之为定时器…

Java 如何传参xml调用接口获取数据

传参和返参的效果图如下&#xff1a; 传参&#xff1a; 返参&#xff1a; 代码实现&#xff1a; 1、最外层类 /*** 外层DATA类*/ XmlRootElement(name "DATA") public class PointsXmlData {private int rltFlag;private int failType;private String failMemo;p…

【C】编译与链接

在本文章里面&#xff0c;我们讲会讲解C语言程序是如何从我们写的代码一步步变成计算机可以执行的二进制指令&#xff0c;并最终执行的。C语言程序运行主要包括两大步骤 -- 编译和链接&#xff0c;接下来我们就来一一讲解。 目录 1 翻译环境和运行环境 2 翻译环境 1&#…