先看程序主逻辑
这里可以看到要求输入六位的序列号,最后三位是
353
,然后前三位的和是5.但是我们并没有发现success的提示。
我们搜索并定位
可以看到success的提示在except里面
这里我们要提到
SEH
(结构化异常处理),这是Windows下的一种异常处理机制。
简单介绍一下SEH。
参考加密与解密
- 功能
SEH实际包含两个主要功能:结束处理(termination handling)和异常处理(exception handling)
每当你建立一个try块,它必须跟随一个finally块或一个except块。
一个try
块之后不能既有finally块又有except块。但可以在try-except块中嵌套try-finally块,反过来 也可以。
__try
,__finally
关键字用来标出结束处理程序两段代码的轮廓
不管保护体(try块) 是如何退出的。不论你在保护体中使用return,还是goto,或者是longjump,结束处理程序 (finally块)都将被调用。
在try使用__leave
关键字会引起跳转到try块的结尾 - TIB结构:在用户模式下,TIB(ThreadInformationBlock)位于TEB的头部。而TEB是操作系统为了保存每个线程的私有数据创建的,每个线程都有自己的TEB。
nt!_TEB
+0x000 NtTib : _NT_TIB
+0x01c EnvironmentPointer : Ptr32 Void
我们看一下TIB的结构
typedef struct _NT_TIB //sizeof 1ch
{
00h struct _EXCEPTION_REGISTRATION *ExceptionList; //SEH链入口
04h PVOID StackBase; //堆栈基址
08h PVOID StackLimit; //堆栈大小
0ch PVOID SubSystemTib;
union {
PVOID FiberData;
10h DWORD Version;
};
14h PVOID ArbitraryUserPointer;
18h struct _NT_TIB *Self; //本NT_TIB结构自身的线性地址
}NT_TIB;
我们看到,ExceptionList在TIB的头部。而在X86下,TEB总是由fs:[0]
指向的。
ExceptionList
是一个链表的结构.
画了一个流程图便于理解
+---------+ +----------------+ +---------------+
| 发生异常 +--->+ TIB +----->+ Next +--+
| | | fs:[0] | +---------------+ | +------------------+
+---------+ +----------------+ | Handler +-------------->+ 异常处理函数 |
+---------------+ | | ... |
| | retn |
+----------+ +------------------+
|
+-------v-------+
| Next +--+
+---------------+ | +------------------+
| Handler +-------------->+ 异常处理函数 |
+---------------+ | | ... |
| | retn |
+----------+ +------------------+
|
+-------v-------+
| FFFFFFh |
+---------------+ +------------------+
| Handler +-------------->+ 异常处理函数 |
+---------------+ | ... |
| retn |
+------------------+
next是下一个链的地址。如果next的值是FFFFFFh
,表示是链表的最后一个节点,该节点的回调函数是系统设置的一个终结处理函数,所有无人值守的异常都会到达这里。
异常处理函数可以是自定义的函数,系统有一个默认的函数,但我们可以自定义一个异常处理函数,让它来处理。
但是得先安装自定义函数才能使用。
我们可以写一个异常处理的例子
//Powered by HAPPY
#include <Windows.h>
#include <iostream>
int exception_memory_access_violation(LPEXCEPTION_POINTERS p_exinfo)
{
if (p_exinfo->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION)
{
return EXCEPTION_EXECUTE_HANDLER; //handle this exception
}
else return EXCEPTION_CONTINUE_SEARCH; //Do not handle this exception
}
int main()
{
char* mem = 0;
std::cout << "Hello World!\n";
__try {
*mem = 0; //throw exception
}
__except (exception_memory_access_violation(GetExceptionInformation())) //handler
{
puts("Memory error in except");
}
}
我们可以将其编译后反汇编研究下except的代码以及是如何安装SEH的。限于篇幅,我们不做深入探究。了解更多可以看一下https://blog.csdn.net/w1012747007/article/details/77131781
异常分析
这里,我们看到filter(这是IDA自动生成的提示)
0x401373
的filter
返回1,即EXCEPTION_EXECUTE_HANDLER
,意思是捕获该异常,也就是说无论发生什么异常,都将被0x401379
的异常处理捕获。这里,我们知道了,如果想要程序输出success,程序中就需要出现一次异常。让
0x401379
的exception执行.我们从try块开头开始分析。
这里的给
ms_exc.registration.TryLevel
赋值是用于处理嵌套的try。trylevel为0表示最外层,具体可以参见此处大致的流程如下
esi的值是输入的序列号转为十六进制的结果。(例如输入的序列号是
110799
,那么esi就是0x110799)重点来看
0x401354
的处理,我们分析一下执行到0x401354
时帧栈+--------+
| eax |
+--------+
| EIP +->0x401353 (call会把下一条汇编的地址压入。)
+--------+
| |
+--------+
执行完
0x401354
,eax就是0x401353
然后div的时候,如果esi的值是0,就会触发异常,也就是我们想要的结果。
我们只需要esi的值原来是
0x401353
,也就是和eax一样。而esi的值的由来前面分析过,所以序列码就是
401353