首页
论坛
课程
招聘
[调试逆向] [系统底层] [原创]基于windows异常机制相关程序分析及一些知识的补充
2021-4-30 01:13 2567

[调试逆向] [系统底层] [原创]基于windows异常机制相关程序分析及一些知识的补充

2021-4-30 01:13
2567

本次分析的重点是,用实例来展示异常的机制的原理以及IDA的一些用法,借用此crack来演示。刚好,这个程序使用的技术和windows异常机制有关,也许程序的作者也是想表达出这样的意思。在这里对windows异常机制做一些补充,在我前面的文章中提到了CPU异常以及用户异常,这里补充一下VEH→SEH→UEH以及VCH,网络上有这类的资料有很多,在这就不在细讲,只做简单的总结。

1.异常机制的补充

VEH向量化异常处理器、SEH结构化异常处理器,UEH(UEF)顶层异常处理器。
VEH:保存在全局的一个链表中,所有的线程共享一个VEH链表,VEH活动于用户层。
SEH:保存在栈空间上是一个局部的链表,每个线程都有自己的SEH,线程之间的SEH互相不影响(既活动于内核层又活动于用户层)。
UEH:保存在一个全局变量上,由于全局变量是所有线程能够共享的,所以所有的线程共享一个UEH,它是属于处理异常的最后一道关卡,如果没记错的话,它存在于两个地方,程序的开始位置和最后各存在一个,但是一般不做异常处理(也可以做异常处理),更多的被用于内存转储并上传服务器。最重要的是当程序处于被调试状态时,不会调用UEH。所以可以用于反调试。
VCH:位置是跟随SEH的,就存储在SEH的旁边的一个链表中,可以设置多个,线程共享。可以看成是SEH的一部分,因为存在于SEH后面。也不会做异常处理,前面异常处理成功了,才会调用此VCH。
KiUserExceptionDispatcher 函数
1) 调用RtlDispatchException 查找并执行异常处理函数
2) 如果RtlDispatchException返回真,调用ZwContinue再次进入0环重新把寄存器的值赋值给Trap_Frame,但线程再次返回3环时,会从修正后的位置开始行。
3) 如果RtlDispatchException返回假,调用ZwRaiseException进行第二轮异常分发
图为做小实验而找到的KiUserExceptionDispatcher:

RtlDispatchException函数:
1) 查找VEH链表(全局链表),如果有则调用
2) 查找SEH链表(局部链表,在堆栈中),如果有则调用
3) 最后一道防线UEH(UEF),住在SEH隔壁
简单理清一下UEH流程(程序用到UEH机制):
只有程序被调试时,才会存在未处理异常
UnhandledExceptionFilter的执行流程:
1) 通过NtQueryInformationProcess查询当前进程是否正在被调试,如果是,返回EXCEPTION_CONTINUE_SEARCH,此时会进入第二轮分发
2) 如果没有被调试:
查询是否通过SetUnhandledExceptionFilter注册处理函数如果有就调用
如果没有通过SetUnhandledExceptionFilter注册处理函数弹出窗口让用户选择终止程序还是启动即时调试器
如果用户没有启用即时调试器,那么该函数返回EXCEPTION_EXECUTE_HANDLE
用一张图表示执行顺序:

2.分析程序

程序使用的是UEH的机制,安装一个顶层异常处理函数(SetUnhandledExceptionFilter),来处理异常,使用基于UEH机制的反调试技术。
发现无壳,并且是用汇编编辑器编写的。

使用IDA pro打开发现基本都是使用的库函数。除了DialogFunc回调函数的功能还不知道,那么接下来分析主要功能,就在这个回调函数里面。

点击DialogFunc就得到这么一个流程图

y设置变量类型,修改之前

修改之后一目了然,并且在0040102B的地址处,可以发现正是消息回调函数,然后和10h进行比较,那么就可以确定10h其实是个枚举值,可以看到还有两个也是这枚举值,一并修改了,当然了如果想改回去也可以使用u 取消目标(局部变量\全局变量\函数)的定义。

m选择枚举值

可以确定这是一个窗口消息,那么就可以搜索WM_就能搜索出来了。

如果不确定是否是不是一个窗口消息,那么可以通过此链接搜索对应的16进制,一般常用的消息都能搜到。
https://www.cnblogs.com/lihuali/p/7566989.html

可以看到返回的消息不是关闭程序的宏之后,就会执行右侧的指令,那么在40103E处和WM_COMMAND值相等时,就会执行左侧的代码,发现又在处和0BBBh比较,目前还不知道,wParam参数表示什么,我们使用OD查看一下

 

发现是一个按钮事件,那么LOWORD(wParam)就表示是一个子窗口ID,那么在上面截图中,在40105E位置处发现压入了一个0BB8的值,通过连系下图就可知,是一个编辑框事件。
那么GetDlgItemTextA获取文本框的值,会返回一个编辑框中字符的个数和5比较。

可以看到的是如果个数不相等那么就弹出窗口并返回


那输入fubar看看能不能显示成功呢,发现还是错误。

但逻辑是这样的没错。如果ESI和EDI比较成功会显示一些信息。不会弹出messagebox

根据地址用OD动态调试看看401066,F8单步走,EIP没错,得到了输入的字符串在40332a地址处,继续F8,401068这里应该会产生一个异常,当然如果仅仅只是想暴力破解它,那么就可以把在此处把401068地址的数据直接NOP掉,这里就不做演示了,因为我们的目的并不在此。

OD默认设置是忽略异常的,可以通过取消设置也会断下,也可以使用x64debug,我们使用x64debug查看。在左下角处,发现触发了异常

通过下列表中可以发现,产生的异常叫非法访问异常。

那么我们查看一下内存布局,发现当前是处于代码段,权限只是执行和读取,并不能写入。在这里啰嗦一句,OD中查看权限是不准确的,可能会显示读写执行,所以就换成x64debug,当然也可以使用内核调试器windebug来调试。

继续F8执行,执行到77D8702D处时,x64debug会触发两次内存异常一直反复横跳,根本无法调试。

那么我们设置一下点选项-异常-点Unknown exceptions,点不暂停(do not break),继续执行程序发现已经崩溃了,那么就可以确定了,UEH异常回调函数根本就没处理异常信息。这里你可能会说,OD调试为什么就不会崩溃,因为OD调试器有反反调试的插件,已经HOOK了关键的函数了,所以可以在OD里面去调试,这里就不再演示x64debug了(可能以后有所补充,毕竟要写内核级的驱动插件,目前还不会)。

前面跳转这的时候,会发现是这样的。

是的,前面做的小实验,就是为了这张图才存在的。

发现一模一样,那么就可以确定,进入了R3环的KiUserExceptionDispatcher函数。
第一个调用的就是RtlDispatchException这个函数。分析到这,动态调试基本就已经没啥可看的了,具体的执行流程可参照前面所补充的一些知识。前面有所提到这样的一张图,程序一开始就初始化安装了顶层异常函数。那么我们可以点击401103地址处进入这个回调函数中进行静态的分析。

发现一大串1字节的字符

C:光标所在地址处的内容解析成代码
P:创建为函数
Y:修改变量类型
可以发现再做一些运算。

可以在Stuctures结构选项中insert插入顶层异常指针所用的结构体,TopLevelExceptionFilter结构体的原型

通过FN+INSERT点击Add standard structure再CTRL+F进行搜索添加

插入了需要需要用到两个结构体

ExceptionInfo原型:

T:解析结构体偏移,在地址403353处

通过计算eax=A0,edx=A0

通过对403367处T进行context解析,发现edi的值被修改了,指向40304E处的值magic

可以看到在40336D地址处eax的值发生了变化,eax=40332A,又把40332A赋值给了ebx+esi,在这里不清楚到底是哪一个寄存器,那么我们就可以通过windebug附加此程序,直接找到context结构的A0位置处。


看到了A0位置处是esi

那么可以确定的是在403372处,context.esi被修改了。eax做了加法运算,eax=40107F,并且把eax赋值给了context.eip,最后eax进行异或运算eax=0,在自减1,最后得出eax=-1,那么作为一个返回值返回宏为EXCEPTION_CONTINUE_EXECUTION,回到异常块eip处再执行一次。由于context.eip被修改那么将会从指定eip开始执行。

那么跟踪修改后的esi=40332A和edi=40304E可知


g 跳转到40107F处

那么就可以确定edi是答案,esi是用户输入的。
测试成功:


第五届安全开发者峰会(SDC 2021)议题征集正式开启!

最后于 2021-4-30 15:54 被APT_华生编辑 ,原因:
上传的附件:
收藏
点赞1
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回