前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >用单步异常检测OllyDbg的巧妙方法

用单步异常检测OllyDbg的巧妙方法

作者头像
战神伽罗
发布2019-07-24 16:18:45
1.3K0
发布2019-07-24 16:18:45
举报
文章被收录于专栏:Eureka的技术时光轴

SEH大概算得上是WINDOWS下公开的秘密了,什么?您还不知道?没关系,下面我来简单地介绍一下。SEH即结构化异常处理(Structured Exception Handling),简单地说就是当程序出现错误时,系统把当前的一些信息压入堆栈,然后转入我们设置好的异常处理程序中执行,在异常处理程序中我们可以终止程序或者修复异常后继续执行。异常处理处理分两种,顶层异常处理和线程异常处理,下面我们要用到的是线程异常处理。具体做法是,每个线程的FS:[0]处都是一个指向包含异常处理程序的结构的指针,这个结构又可以指向下一个结构,从而形成一个异常处理程序链。当发生异常时,系统就沿着这条链执行下去,直到异常被处理为止。我们可以使FS:[0]指向我们自己写的异常处理程序,从而自己处理异常。这里只是关于异常处理的简单介绍,具体内容请参考看雪学院的《加密与解密》及相关的windows编程书籍。 我们都知道用调试器(下面的介绍都以当前流行的调试器OllyDbg为例)可以设置断点,那么当设置断点时调试器究竟是怎样工作的呢?这要分几种情况了,一种是代码断点,即Cracker在某行代码上下断点,这时调试器自动把这行代码的首字节改为CC(即INT3中断,这个修改在OD中不会显示)这样每当程序运行到这里都会产生中断,而调试器可以接管这个中断,从而实现对程序的控制;另一种是内存断点,即当程序对某处内存有操作(读或写)时产生中断,这是直接利用CPU的调试寄存器DRx来完成的;还有一种不太像中断的“中断”,即单步中断,也就是说当你在调试器中选择“步过”某条指令时,程序自动在下一条语句停下来,这其实也属于一种中断,而且可以说是最常用的一种形式了,当我们需要对某段语句详细分析,想找出程序的执行流程和注册算法时必须要进行这一步。是80386以上的INTEL CPU中EFLAGS寄存器,其中的TF标志位表示单步中断。当TF为1时,CPU执行完一条指令后会产生单步异常,进入异常处理程序后TF自动置0。调试器通过处理这个单步异常实现对程序的中断控制。持续地把TF置1,程序就可以每执行一句中断一次,从而实现调试器的单步跟踪功能。 讲到这里,不知聪明的您看出什么问题没有:如果我们的程序本身就含有对单步异常的处理程序会怎么样呢?呵呵,据笔者的实验是,OD会不理睬我们程序自己的单步异常处理程序而自顾自地把异常处理接管了。这其实就给了我们一种很巧妙的方法,我们可以自己把TF置1,然后把注册算法中十分关键的运算放在我们程序自己的单步异常处理程序中。这样当程序在正常条件下执行时,一旦产生单步异常就会转到我们自己写好的异常处理中继续进行而不会受到影响,如果程序被调试,而Cracker选择了按F8步过这段程序,那么这时产生的单步异常会被调试器忽略,这样那些关键的代码就得不到执行,从而产生令人十分迷惑的结果。 好了,说了这么多,下面看一个实际的例子:(MASM32 8.2下编译通过)

.386 .model flat,stdcall option casemap:none

include windows.inc include kernel32.inc include user32.inc includelib kernel32.lib includelib user32.lib

MY_DLG equ 1000 IDC_INPUT equ 1001 IDC_OUTPUT equ 1002 ID_OK equ 1003

.data

szTit db 'SEH CrackMe',0 szGood db 'Good Job!!',0 szBad db 'You Failed, Try again!!',0

.data? num dd ? pSuc dd ? .code

singlestepHandler proc c pExcept,pFrame,pContext,pDispath pushad assume esi:ptr EXCEPTION_RECORD,edi:ptr CONTEXT mov esi,[pExcept] mov edi,[pContext] cmp [esi].ExceptionCode,STATUS_SINGLE_STEP jnz @continue ;检查是否为单步异常 inc [edi].regEax ;EAX = EAX + 1 mov ebx,[edi].regEip cmp byte ptr [ebx],90h ;检查下一个字节是否90(NOP) jz @finish ;如果遇到NOP则结束跟踪 or [edi].regFlag,100h ;否则继续对TF位置1

@finish: popad mov eax,ExceptionContinueExecution jmp @exit @continue: popad mov eax,ExceptionContinueSearch @exit: ret

singlestepHandler endp

DlgProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM

.if uMsg == WM_CLOSE invoke EndDialog,hWnd,0 .elseif uMsg == WM_COMMAND mov eax,wParam .if eax == ID_OK

xor eax,eax ;EAX清零 pushfd ;把EFLAGS压栈 or dword ptr [esp],100h ;因为TF在EFLAGS的第9位 popfd ;EFLAGS出栈,用这种方法把TF置1 inc eax ;从这里开始“单步跟踪” inc eax inc eax dec eax ;对EAX进行一些操作,如果没有异常EAX=2 nop ;用NOP标志跟踪结束 mov [num],eax invoke GetDlgItemInt,hWnd,IDC_INPUT,addr pSuc,TRUE .if [num] == eax ;与输入进行比较 invoke SetDlgItemText,hWnd,IDC_OUTPUT,addr szGood .else invoke SetDlgItemText,hWnd,IDC_OUTPUT,addr szBad .endif .endif .else mov eax,FALSE ret .endif mov eax,TRUE ret

DlgProc endp

start:

assume fs:nothing push offset singlestepHandler push fs:[0] mov fs:[0],esp ;安装我们自己的异常处理程序 invoke GetModuleHandle,NULL invoke DialogBoxParam,eax,MY_DLG,NULL,addr DlgProc,NULL

pop fs:[0] add esp,4 ;卸载异常处理程序

invoke ExitProcess,0

end start

该程序如果在正常情况下运行,正确的注册码应该是6,见下图: 而如果用OD调试,却会发现正确的注册码是2,如下图所示。怎么样?如果是一个菜鸟Cracker,准会晕头转向了吧? 为什么会出现这种令人迷惑的结果呢?下面我们来仔细分析一下。在对话框消息处理过程中,首先用OR运算然后POPFD来设置TF位,这样在正常情况下,执行完一条指令后就进入我们写的异常处理程序,在异常处理程序使EAX加1,然后继续置TF位为1,这样每执行一句都会中断,直到执行到NOP这一句后不再继续“单步跟踪”,在消息处理过程中EAX加3减1,结果应为2,但因为共执行了4条指令,每次在异常处理程序中EAX都会加1,因此正常情况下结果应为6。如果用OD等调试,因为不会执行异常处理程序,结果就为2。这只是一个最简单的例子,如果我们把十分复杂的算法判断都写进单步异常处理程 序中,是不是就会让Cracker很郁闷呢? 以上只是一个简单的例子,我在这里只是提供一种思路,具体怎样把注册算法与异常处理程序巧妙结合起来,以起到最佳的保护的效果,就要靠聪明的读者您自己了。^_^如果您有什么意见建议欢迎与

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档