前言:
SEH(“Structured Exception Handling”),即结构化异常处理·是(windows)操作系统提供给程序设计者的强有力的处理程序错误或异常的武器。它不依托于设计语言,只依托于操作系统,这些并不是编译程序本身所固有的,本质上只不过是对windows内在提供的结构化异常处理的包装。
参看资料:
1.逆向工程核心原理
2.https://www.52pojie.cn/thread-16609-1-1.html
3.https://bbs.pediy.com/thread-27224.html
4.https://bbs.pediy.com/thread-12449.htm
5.https://bbs.pediy.com/thread-189193.htm
第一部分:windows系统的几种常见的异常
- 异常:
- 1)内存非法访问异常(Exception_Access_Violation)
- 1.1) MOV DS:[0],123 —0号区域未被分配
- 1.2) MOV DS:[40100],123 —.test节区起始地址只有读取权限
- 1.3)MOV DS:[80000],123 —内核区域
- 1.4)说明:0号地址虽然属于用户区域但是,该地址没有被分配。
- 2)断点异常(Exception_BreakPoint)
- 2.1)软件断点 —int 3
- 2.2)硬件断点 —对某一个地址下断点
- 3)CPU无法解析异常(Exception_Illegal_Instruction)
- 3.1)某一个指令集不在cpu解析的指令之中
- 4)除0异常
- 5)单步异常
- 1)内存非法访问异常(Exception_Access_Violation)
第二部分:SEH介绍
1.SEH链:SEH是以链状形式存在,如果异常没有被第一个异常处理函数处理,那么就会传递给下一个异常处理函数。
*next指针指向下一个节点,handle指向一个异常处理函数。
2.异常处理函数:异常处理函数是一个回调函数,接收4个参数,返回值是一个枚举类型。
- 四个参数:
- 1)Exception_Recode *pRecode:该参数是一个指向_Exception_Recode结构体的指针。该结构体包含关于异常的类型和地址等重要信息。
- 2)Exception_Register_Recode *pFrame:不重要
- 3)CONTEXT *pContext:对于IA-32 来说有效,保存CPU处理异常前的状态。
- 4)PVOID *pViod:保留应该为1
- 1)Exception_Recode *pRecode:该参数是一个指向_Exception_Recode结构体的指针。该结构体包含关于异常的类型和地址等重要信息。
- 1个返回值:处理成功返回0,否则返回1
3.SEH的入口地址:在TEB.NtTib.ExceptionList,对应的段寄存器地址是FS:[0].
第三部分:SEH反调试基础:
1.首先利用异常处理例程来进行反跟踪
很多CM都是首先安装好一个异常处理例程,然后故意制造一个异常,然后程序抛出异常.ollydebug下用shift + F7 或者 shift + F8进ntdll.KiUserExceptionDispatcher,单步跟踪后最后系统调用用户模块中的异常处理例程. 很多CM都是在异常处理例程编写一个算法来重新将EIP定位到一个会造成异常的指令地址,重复这个过程几次,这样给调试者一种很难跟踪的假象,这种一般只要用 shift + F9 (OllyDebug:该组合键是将异常交给用户程序的异常处理例程来处理),如果我们想弄清楚异常处理例程到底在做什么,我们可以在异常处理例程下个断点来查看其实现过程。
2.未处理异常用于反跟踪的原理:
根据MSDN的描述,UnhandledExceptionFilter在没有debugger attach的时候才会被调用。所以,SetUnhandledExceptionFilter函数还有一个妙用,就是让某些敏感代码避开debugger的追踪。比如你想把一些代码保护起来,避免调试器的追踪,可以采用的方法:把代码放到SetUnhandledExceptionFilter设定的函数里面。通过人为触发一个unhandled exception来执行。由于设定的UnhandledExceptionFilter函数只有在调试器没有加载的时候才会被系统调用,这里巧妙地使用了系统的这个功能来保护代码.
第四部分:绕过SEH反调试:
对于第一种,只需要在IsPresnetDebug()返回时修改判断即可。或者直接修改FS:[18]中的debuged成员为“0”。
对于第二种,因为UnhandledExceptionFilter是否调用取决于系统内核的判断。用户态的调试器要想改变这个行为,要破费一番脑筋了。
2018年1月25日夜于汉阴
第五部分:利用SEH保护验证码
1. 首先,我们看到这个一个MSAM汇编编译而成的程序,如图所示,程序对输入的用户名长度做出验证,正确长度是4-30个字节,
2. 如果正确,则跳入SEH安装程序,输入注册码。输入完毕后,给出一个int 3软件异常,跳入SEH处理函数。
3. 这个SEH处理函数,是一个验证注册码正确的的函数,关于验证过程,本文不做介绍。
4. 快速到达SEH处理函数的方法,如图所是,栈顶是下一个处理函数的指针,下面红圈所是个这次处理函数的指针。
5.备注,测试用例:
https://pan.baidu.com/s/1htLY6ag
第六部分:利用SetUnhandledExceptionFilter和UnhandledExceptionFileter函数实现反调试
1. 异常时,系统处理异常的顺序
- 发生异常时系统的处理顺序(by Jeremy Gordon, Hume):
- 1.系统首先判断异常是否应发送给目标程序的异常处理例程,如果决定应该发送,并且目标程序正在被调试,则系统 挂起程序并向调试器发送EXCEPTION_DEBUG_EVENT消息.
- 2.如果你的程序没有被调试或者调试器未能处理异常,系统就会继续查找你是否安装了线程相关的异常处理例程,如果 你安装了线程相关的异常处理例程,系统就把异常发送给你的程序seh处理例程,交由其处理.
- 3.每个线程相关的异常处理例程可以处理或者不处理这个异常,如果他不处理并且安装了多个线程相关的异常处理例程, 可交由链起来的其他例程处理.
- 4.如果这些例程均选择不处理异常,如果程序处于被调试状态,操作系统仍会再次挂起程序通知debugger.
- 5.如果程序未处于被调试状态或者debugger没有能够处理,并且你调用SetUnhandledExceptionFilter安装了最后异 常处理例程的话,系统转向对它的调用.
- 6.如果你没有安装最后异常处理例程或者他没有处理这个异常,系统会调用默认的系统处理程序,通常显示一个对话框, 你可以选择关闭或者最后将其附加到调试器上的调试按钮.如果没有调试器能被附加于其上或者调试器也处理不了,系统 就调用ExitProcess终结程序.
- 7.不过在终结之前,系统仍然对发生异常的线程异常处理句柄来一次展开,这是线程异常处理例程最后清理的机会.
- 8.UnhandledExceptionFileter是系统最好一个异常 处理器(就是程序崩溃时候,出现的windows的提示框)。
2. 本例样本网络上找不到了,截取前辈的数据。
跳到:
跳到77C013BF后就可以发现开始调用UnhandledExceptionFilter了!!!
3. 总结:在第四部分中我说了,对于第二种方法,属于内核级调试,不容易绕过,但是这个例子给了我们一个思路:由于反调试代码UnhandledExceptionFilter函数内部,我们可以跟进函数内部,把里面的判断给误导了,就可以顺利通过反调试。
2018年1月26日夜于汉阴