-
-
[原创]C++异常处理 学习笔记
-
发表于: 2022-6-16 21:37 10577
-
由于打ctf的时候经常看见异常处理的题目,虽然知道流程但是原理还是不是很理解,也不会写,所以准备学一下。记录一下本人的学习过程,如有错误,希望各位大佬能指正。
异常是程序在执行期间产生的问题。C++ 异常是指在程序运行时发生的特殊情况,比如尝试除以零的操作。
触发异常后,如果有对应的异常处理就会跳到异常处理去执行,相当于是编写代码的人针对会发生的错误的解决方案。如果没有异常处理或者异常处理程序未成功处理异常,程序就会退出。具体可以看加密与解密的seh异常处理部分。
TIB(Thread Information Block 线程信息块) 是保存线程基本信息的数据结构,user mode下位于TEB(Thread Enviroment Block线程环境块)的头部,TEB是操作系统为了保存每个线程的私有数据而创建的,所以每个线程都有TEB。
TIB结构如下
x86平台的user mode上,fs:[0]总是指向TEB,可以通过这个来定位。(x64平台上是gs:[0]指向TEB)
TEB偏移量为0的结构,是用来对线程的异常进行处理的结构。结构如下
俩元素,第一个是指向下一个结构的指针,第二个是指向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,相当于插入了一个新的节点。
卸载就是把esp赋值为刚刚存入fs:[0]的(像上图的右下角的那个_EXCEPTION_REGISTRATION_RECORD的next的地址)。然后pop一下保证栈帧平衡,就可以了。
test_seh函数的最开始,先push handler,再push fs:[0]。
看到这个源代码里的try....except块里的代码
下方的except对应这个,ida显示的是__except filter
except里的处理部分的代码呢?在这里
__except($LN8)中的LN8是刚刚的filter对应的label,这里个人感觉就是判断是哪个filter里的处理代码的标志,看label。
验证一下,又加了一点代码
可以看到
except里面的操作都对应的是except filter
而从这个except filter上方的label可以找到对应的处理代码。
所以以后我再遇到这种seh的题,首先定位filter,然后根据filter的label找到对应的处理代码,然后下断进行分析。
在VEH里面放自己的异常处理函数感觉在实战中还是蛮常见的,比如无痕hook那种,硬件断点加VEH,十分好用,一定要好好学。
VEH和SEH最大的几个区别就是
用户产生异常后,内核函数KiDispatchException会修改eip为KiUserExceptionDispatcher,然后就啥也不干,所以主要的异常处理都是在KiUserExceptionDispatcher函数中的。
首先是进入RtIDispatchException函数,这个函数的作用是找到对应的异常处理函数。如果返回的是true,意味着异常被处理,那就皆大欢喜,直接进入ZwContinue函数,将eip进行一个修正,然后回到出现异常或者新的地方执行。如果没有被处理就跳转然后进行二次分发,和加密与解密上的描述相同
先从VEH链表中遍历,如果没有就遍历SEH链表。
感觉逻辑也不难,整个流程大概如下:
试着写了一个简单的代码
通过除零异常来抛出异常,从而跳到我们写的异常处理函数进行执行,对input进行操作后set ip后返回。此处idiv ecx指令机器码为2,所以eip + 2。
AddVectoredExceptionHandler是官方提供的添加VEH异常处理函数的一个函数,第一个参数如果是0就是添加到链表尾部,1的话就是头部。第二个参数是我们自个儿写的异常处理函数。
可以看到还是比较明显的,可能还需要整个无痕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第五空间RE-5_Universal-题解 7671
- [原创]C++异常处理 学习笔记 10578
- [原创]KCTF2022 第二题pizzatqqql队题解 14804