具体的异常要回去学中断这些,我打算到时候再细致研究,故而这里只是粗浅地讨论C++的异常处理机制.(其实没太看懂原理和应用的关系,以后还要深入研究)
首先我们要探究一下seh异常处理机制,从与其相关的数据结构讲起.
-
TIB结构
TIB (Thread Infoimation Block, 线程信息块)是保存线程基本信息的数据结构。在用户模式下, 它位于TEB(Thread Environment Block,线程环境块)的头部,而TEB是操作系统为了保存每个线程的私有数据创建的,每个线程都有自己的TEB.在 Windows 2000 DDK 中,TIB的定义如下:typedef struct _NT_TIB { struct _EXCEPTION_REGISTRATION_RECORD *ExceptionList;//指向异常处理链装 PVOID StackBase;//当而线程所使用的栈的栈底 PVOID StackLimit;//当前线程所征用的栈的栈PVOID SubSystemTib; union { PVOID FiberData; ULONG Version;};PVOID ArbitraryUserPointer; struct _NT_TIB *Self; //指向 TIB结构自身 } NT_TIB;
从Windows2000到10都是如此.其中要关注_EXCEPTION_REGISTRATION_RECORD的指针ExceptionList,它位于TIB的偏移0,也是TEB的0.在x86的用户模式下,FS段处理器指向当前线程的TEB数据,也就是TEB总是由fs:[0]指向的,而x64则是gs:[0].而当线程运行在内核模式下时,Windows将 FS段选择器指向内核中的KPCRB结构(Processor Control Region Block,处理器控制块), 该结构的头部同样是上述的NT_TIB结构。
-
_EXCEPTION_REGISTRATION_RECORD 结构
该结构是个单链表,用于描述线程异常处理过程的地址.typedef struct _EXCEPTION_REGISTRATION_RECORD { struct _EXCEPTION_REGISTRATION_RECORD *Next; //指向下一个结构的指针 PEXCEPTION_ROUTINE Handler; //当前异常处理回调函数的地址 } EXCEPTION REGISTRATION RECORD;
这样就能达到回滚的效果了.
由于我们知道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一下保证栈帧平衡,就可以了。
接下来是具体处理try-except块的相关流程,直接贴文[原创]C++异常处理 学习笔记-软件逆向-看雪-安全社区|安全招聘|kanxue.com
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找到对应的处理代码,然后下断进行分析。
以下是我的补充,这里的expect语句是Windows下的扩展,详见try-except 语句 | Microsoft Learn
本文主要介绍C++编程语言中的异常处理(try-catch-throw)的相关内知识,同时通过示例代码介绍异常处理的使用方法。
1 概述
C++编程语言中的异常是指在程序运行时发生的特殊情况,例如除数为“0”的情况。异常机制提供了一种转移程序控制权的方式。
C++编程语言中的异常处理涉及到三个关键字:try、catch、throw。这三个关键字的介绍如下:
throw:当问题出现时,通过使用throw关键字让程序抛出一个异常;
try:关键字try块中的代码被称为保护代码,该段代码可能会抛出异常。try关键字后通常跟着一个或多个catch块;
catch:在想要处理异常的地方,使用catch关键字捕获异常,并进行相关的异常处理。
2 用法
2.1 使用throw抛出异常
可以使用throw关键字在代码中抛出异常。throw关键字操作的对象类型即为抛出异常的类型。
示例代码内容如下:
// 定义除法函数,当分母为0时,此函数会抛出异常信息
double Division(double x, double y)
{
if (0 == int(y))
{
throw "divisor y is zero.";
}
return (x / y);
}
上述代码的函数Division中throw操作的对象是“const char *”类型,所以其抛出的异常类型即为“const char *”类型。如果想要捕获到该异常,则需要将catch语句对应的异常类型设置为“const char *”。
2.2 使用try-catch语句处理异常
示例代码内容如下:
try
{
// 保护代码,可能抛出异常
// throw 抛出异常
}
catch (ExceptionName e1)
{
// 能捕获到 ExceptionName 类型的异常
// 具体的异常处理操作
}
catch (ExceptionName e2)
{
// 能捕获到 ExceptionName 类型的异常
// 具体的异常处理操作
}
catch (ExceptionName eN)
{
// 能捕获到 ExceptionName 类型的异常
// 具体的异常处理操作
}
如果try块中的代码在不同情况下会抛出不同类型的异常,则可使用多个catch语句,捕获并处理这些不同类型的异常。catch只能捕获到其规定类型的异常。
上面的示例代码会捕获到类型为ExceptionName的异常。如果想让catch能够捕获try块抛出的任何类型的异常,则需要在异常类型声明括号内使用省略号“...”,示例代码内容如下:
try
{
// 保护代码,可能抛出任何类型的异常
// throw 抛出异常
}
catch (...)
{
// 能捕获到任何类型的异常
// 具体的异常处理操作
}
3 示例代码
下面展示一个完整的异常处理示例代码。示例代码(exception_demo1.cpp)的内容如下:
include
include
using namespace std;
// 定义除法函数,当分母为0时,此函数会抛出异常信息
double Division(double x, double y)
{
if (0 == int(y))
{
throw "divisor y is zero.";
}
return (x / y);
}
int main()
{
double a = 5.0;
double b = 0.0;
double c = 0;
// try块内为被保护的代码,如果块内代码抛出异常,则会被相关的catch捕获到
try
{
c = Division(a, b);
cout << "c is: " << c << endl;
}
catch (const char * exception)
{
cout << "exception info is: " << exception << endl;
}
return 0;
}
上述代码的Division函数抛出了一个类型为“const char”的异常,因此,为了捕获到该异常,catch的异常类型必须也是“const char”。
编译并执行上述代码,结果如下:
4 异常规格说明
抛出异常的函数后接的throw(),称为异常规格说明,表示该函数可以抛出哪种类型的异常,具体的异常类型在throw()的括号中声明。
具体包括以下几种情况:
如果函数不抛出任何类型的异常,则throw()的括号中无内容。示例如下:
double Division(double x, double y) throw ()
如果函数可以抛出类型为“A”、“B”的异常,则写法为“throw(A, B)”。示例如下:
double Division(double x, double y) throw (const char, int)
上述示例表示Division函数可以抛出“const char”和“int”类型的异常;
如果函数可以抛出任何类型的异常,则不需要后接异常规格说明。示例如下:
double Division(double x, double y)
上述示例表示Division函数可以抛出任何类型的异常。
注意:如果函数抛出异常的类型,与异常规格说明不一致,则会导致程序执行出错。例如,在上述示例代码中,将Division函数的异常规格说明设置为“int”类型,则程序执行时会报如下错误:
[root@node1 /opt/liitdar/mydemos/simples]# ./exception_demo1
terminate called after throwing an instance of 'char const*'
Aborted
[root@node1 /opt/liitdar/mydemos/simples]#
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/liitdar/article/details/86129430
C++ 异常处理 | 菜鸟教程
C++ 异常处理
异常是程序在执行期间产生的问题。C++ 异常是指在程序运行时发生的特殊情况,比如尝试除以零的操作。
异常提供了一种转移程序控制权的方式。C++ 异常处理涉及到三个关键字:try、catch、throw。
- throw: 当问题出现时,程序会抛出一个异常。这是通过使用 throw 关键字来完成的。
- catch: 在您想要处理问题的地方,通过异常处理程序捕获异常。catch 关键字用于捕获异常。
- try: try 块中的代码标识将被激活的特定异常。它后面通常跟着一个或多个 catch 块。
如果有一个块抛出一个异常,捕获异常的方法会使用 try 和 catch 关键字。try 块中放置可能抛出异常的代码,try 块中的代码被称为保护代码。使用 try/catch 语句的语法如下所示:
try { // 保护代码 }catch( ExceptionName e1 ) { // catch 块 }catch( ExceptionName e2 ) { // catch 块 }catch( ExceptionName eN ) { // catch 块 }
如果 try 块在不同的情境下会抛出不同的异常,这个时候可以尝试罗列多个 catch 语句,用于捕获不同类型的异常。
抛出异常
您可以使用 throw 语句在代码块中的任何地方抛出异常。throw 语句的操作数可以是任意的表达式,表达式的结果的类型决定了抛出的异常的类型。
以下是尝试除以零时抛出异常的实例:
double division(int a, int b) { if( b == 0 ) { throw "Division by zero condition!"; } return (a/b); }
捕获异常
catch 块跟在 try 块后面,用于捕获异常。您可以指定想要捕捉的异常类型,这是由 catch 关键字后的括号内的异常声明决定的。
try { // 保护代码 }catch( ExceptionName e ) { // 处理 ExceptionName 异常的代码 }
上面的代码会捕获一个类型为 ExceptionName 的异常。如果您想让 catch 块能够处理 try 块抛出的任何类型的异常,则必须在异常声明的括号内使用省略号 ...,如下所示:
try { // 保护代码 }catch(...) { // 能处理任何异常的代码 }
下面是一个实例,抛出一个除以零的异常,并在 catch 块中捕获该异常。
实例
#include
由于我们抛出了一个类型为 const char* 的异常,因此,当捕获该异常时,我们必须在 catch 块中使用 const char*。当上面的代码被编译和执行时,它会产生下列结果:
Division by zero condition!
C++ 标准的异常
C++ 提供了一系列标准的异常,定义在
下表是对上面层次结构中出现的每个异常的说明:
定义新的异常
您可以通过继承和重载 exception 类来定义新的异常。下面的实例演示了如何使用 std::exception 类来实现自己的异常:
实例
#include
这将产生以下结果:
MyException caught
C++ Exception
在这里,what() 是异常类提供的一个公共方法,它已被所有子异常类重载。这将返回异常产生的原因。
注意:执行 throw 语句类似与执行 return 返回语句,它会终止函数的执行,但是 throw 并不是把控制权交给调用函数,而是沿函数调用序列进行回退,直到找到包含 try 块的函数并能成功匹配异常。
啥意思呢?考虑如下代码:
#include <iostream>using std::cin;
using std::cout;
using std::endl;int add(int a, int b)
{if (!(a + b))throw "Note: a is allowed to equal to -b";cout << "add end\n";return a + b;
}int func(int a, int b)
{int c;try {c = add(a, b);} catch (char ch) {cout << "func catch\n";}cout << "func end\n";return a * c;
}int main()
{int a, b, c;cin >> a >> b;try {c = func(a, b);cout << c << endl;} catch (const char* s) {cout << s << endl;}cout << "END\n";return 0;
}
这里调用关系为:main -> func -> add,输入 a = 1, b = -1,最后会在 add 函数中触发异常,这时 throw 会抛出异常,这时会沿调用路径回溯寻找 try - catch 捕获异常,所以这里触发异常后,控制权就直接回到了 main 函数中,所以 cout << "func end\n"; 不会执行:输出结果如下
xiaozaya@pwn:~/rubbish/cpp$ ./a.out
1 -1
Note: a is allowed to equal to -b
END
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/qq_61670993/article/details/135251333
由于我只做过hgame-mini的C嘎嘎,故只能拿这题来说说经验.其实主要就是在伪代码找expection,然后去汇编代码,看try和catch,看不懂的可以看ida的注释.