看雪2019CTF第二场签到题SEH异常处理–MysteriousLetter2

发布于 2019-06-19  124 次阅读


先看程序主逻辑。

这里可以看到要求输入六位的序列号,最后三位是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,表示是链表的最后一个节点,该节点的回调函数是系统设置的一个终结处理函数,所有无人值守的异常都会到达这里。
异常处理函数可以是自定义的函数,系统有一个默认的函数,但我们可以自定义一个异常处理函数,让它来处理。
但是得先安装自定义函数才能使用。
我们可以写一个异常处理的例子

//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自动生成的提示)

0x401373filter返回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