首页
社区
课程
招聘
[原创]C++异常处理 学习笔记
发表于: 2022-6-16 21:37 10577

[原创]C++异常处理 学习笔记

2022-6-16 21:37
10577

由于打ctf的时候经常看见异常处理的题目,虽然知道流程但是原理还是不是很理解,也不会写,所以准备学一下。记录一下本人的学习过程,如有错误,希望各位大佬能指正。

异常是程序在执行期间产生的问题。C++ 异常是指在程序运行时发生的特殊情况,比如尝试除以零的操作。

触发异常后,如果有对应的异常处理就会跳到异常处理去执行,相当于是编写代码的人针对会发生的错误的解决方案。如果没有异常处理或者异常处理程序未成功处理异常,程序就会退出。具体可以看加密与解密的seh异常处理部分。

TIB(Thread Information Block 线程信息块) 是保存线程基本信息的数据结构,user mode下位于TEB(Thread Enviroment Block线程环境块)的头部,TEB是操作系统为了保存每个线程的私有数据而创建的,所以每个线程都有TEB。

TIB结构如下

image-20220616193019338

x86平台的user mode上,fs:[0]总是指向TEB,可以通过这个来定位。(x64平台上是gs:[0]指向TEB)

TEB偏移量为0的结构,是用来对线程的异常进行处理的结构。结构如下

image-20220616194858424

俩元素,第一个是指向下一个结构的指针,第二个是指向handler,处理异常的函数。这个结构层层相连,构成了我们的SEH。SEH就相当于是一个长链表,链表元素就是一个个_EXCEPTION_REGISTRATION_RECORD结构。当运行发生异常,程序就会去fs:[0]里找到TEB,然后在TEB的偏移量为0的地方找到SEH的链表头,然后一个个遍历,直到找到对应的异常处理函数。(其实感觉书上这里有点问题,不是先找VEH吗,不是很懂)

因为TEB是线程的私有的数据结构,所以每个线程都有自己的SEH链,所以我们说,SEH是基于线程的。

如果一个异常发生了,且没有调试器可以进行干预,则操作系统会将控制权交予用户态的异常处理。但是因为内核态和用户态用的是俩不同的栈,所以用户态无法直接获得异常信息。操作系统此时会将仨数据结构放到用户态的栈里面:EXCEPTION_RECORD,CONTEXT,_EXCEPTION_POINTERS。第三个结构有俩指针,第一个指向EXCEPTION_RECORD,第二个指向CONTEXT。这样用户态就可以获得异常信息了。

由于我们知道SEH本质就是一个链表,所以我们只需要把我们写好的一个_EXCEPTION_REGISTRATION_RECORD结构插入到链表头就行。首先push指向我们handler的地址,然后push fs:[0],此时就成功的创造了一个_EXCEPTION_REGISTRATION_RECORD。最后mov fs:[0],esp,就成功的修改了我们的TEB,相当于插入了一个新的节点。

image-20220616201349427

卸载就是把esp赋值为刚刚存入fs:[0]的(像上图的右下角的那个_EXCEPTION_REGISTRATION_RECORD的next的地址)。然后pop一下保证栈帧平衡,就可以了。

image-20220616204142053

test_seh函数的最开始,先push handler,再push fs:[0]。

看到这个源代码里的try....except块里的代码

image-20220616204701598

下方的except对应这个,ida显示的是__except filter

image-20220616204856744

except里的处理部分的代码呢?在这里

image-20220616204953960

__except($LN8)中的LN8是刚刚的filter对应的label,这里个人感觉就是判断是哪个filter里的处理代码的标志,看label。

验证一下,又加了一点代码

可以看到

image-20220616210755394

except里面的操作都对应的是except filter

image-20220616210902828

image-20220616210955176

而从这个except filter上方的label可以找到对应的处理代码。

所以以后我再遇到这种seh的题,首先定位filter,然后根据filter的label找到对应的处理代码,然后下断进行分析。

在VEH里面放自己的异常处理函数感觉在实战中还是蛮常见的,比如无痕hook那种,硬件断点加VEH,十分好用,一定要好好学。

VEH和SEH最大的几个区别就是

用户产生异常后,内核函数KiDispatchException会修改eip为KiUserExceptionDispatcher,然后就啥也不干,所以主要的异常处理都是在KiUserExceptionDispatcher函数中的。

在这里插入图片描述

首先是进入RtIDispatchException函数,这个函数的作用是找到对应的异常处理函数。如果返回的是true,意味着异常被处理,那就皆大欢喜,直接进入ZwContinue函数,将eip进行一个修正,然后回到出现异常或者新的地方执行。如果没有被处理就跳转然后进行二次分发,和加密与解密上的描述相同

image-20220615224640309

在这里插入图片描述

先从VEH链表中遍历,如果没有就遍历SEH链表。

感觉逻辑也不难,整个流程大概如下:

试着写了一个简单的代码

通过除零异常来抛出异常,从而跳到我们写的异常处理函数进行执行,对input进行操作后set ip后返回。此处idiv ecx指令机器码为2,所以eip + 2。

AddVectoredExceptionHandler是官方提供的添加VEH异常处理函数的一个函数,第一个参数如果是0就是添加到链表尾部,1的话就是头部。第二个参数是我们自个儿写的异常处理函数。

image-20220616211800438

image-20220616211811087

可以看到还是比较明显的,可能还需要整个无痕hook之类的才比较实用,不然拖进ida很容易就被一眼看出来了。

然后没啥不同的地方了,注册后遇到对应的异常就会跳到异常处理函数,要分析的话在异常处理下断即可。

异常处理还是比较神奇,也能用异常处理来实现很多牛逼的操作,这个基础还是得打好。

https://blog.csdn.net/weixin_30917213/article/details/96341398

https://blog.csdn.net/weixin_42052102/article/details/83540134

加密与解密第四版

 
 
 
 
 
 
 
 
 
#include <windows.h>
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
char flag[] = "ek`fzrdg^sdrs|";
 
VOID test_seh()
{
    int* pValue = NULL;
    __try
    {
        printf("into Try.\n");
        *pValue = 0x114514;
    }
    __except (printf("into except"))
    {
        cout << "into handler" << endl;
        for (int i = 0; i < strlen(flag); i++) {
            flag[i] += 1;
        }
    }
}
 
int main(int argc, char* argv[])
{
    test_seh();
    cout << flag;
    return 0;
}
#include <windows.h>
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
char flag[] = "ek`fzrdg^sdrs|";
 
VOID test_seh()
{
    int* pValue = NULL;
    __try
    {
        printf("into Try.\n");
        *pValue = 0x114514;
    }
    __except (printf("into except"))
    {
        cout << "into handler" << endl;
        for (int i = 0; i < strlen(flag); i++) {
            flag[i] += 1;
        }
    }
}
 
int main(int argc, char* argv[])
{
    test_seh();
    cout << flag;
    return 0;
}
 
 
 
 
 
 
 
 
 
#include <windows.h>
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
char flag[] = "dj_eyqcfrcqr{";
 
 
VOID test_seh()
{
    int* pValue = NULL;
    __try
    {
        printf("into __Try.\n");
        __try {
            *pValue = 0x114514;
        }
        __except(printf("into into except!\n")) {
            cout << "into into handler!" << endl;
            for (int i = 0; i < strlen(flag); i++) {
                flag[i] += 1;
            }
        }
        *pValue = 0x114514;
    }
    __except (printf("into __except"))
    {
        cout << "into handler" << endl;
        for (int i = 0; i < strlen(flag); i++) {
            flag[i] += 1;
        }
    }
}
 
int main(int argc, char* argv[])
{
    test_seh();
    cout << flag;
    return 0;
}
#include <windows.h>
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
char flag[] = "dj_eyqcfrcqr{";
 

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

最后于 2022-6-16 22:29 被夏男人编辑 ,原因:
收藏
免费 12
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回
//