c++ 32位异常还原

发布时间 2023-09-13 21:03:14作者: 乘舟凉

本文中的例子下载地址

https://wwmf.lanzout.com/ij4zq18au9yd
密码:2vts

确定try的位置

首先确定try的位置

image

上面明显是一个SEH结构,在c++异常中,state固定在var_4的位置上,这里state初始化位-1,我们将var_4改名为state

image

上图为ida的反编译图,当state赋值为0时,为try的开始,state赋值为-1时表示try块的结束

还原case1 - 4

下面我们分析每一个抛出的异常

image

_CxxThrowException的第一个参数为抛出异常的地址,那么这里的异常应该时一个常量3,我们可以跳转到第二个参数ThrowInfo

image

ThrowInfo的数据结构

image

上面是ThrowInfo的数据结构

可以看到这个异常时一个简单类型异常,并且为int

case 1 - 4 都是差不多的,它们可还原为下面的代码

		case 1:
    		throw 3;
   		case 2:
     		throw 3.0f;
    	case 3:
     		throw '3';
    	case 4:
     		throw 3.0;

还原case 5 - 6

image

image

下面看case 5,v7应该保存了这个异常对象的地址,sub_401310是该对象的构造函数,我们先分析这个类的类名,

image

在CatchTableTypeArray中,前面的类是抛出的类(这里是CDiv0Excepction),后面的类是该类的基类(这里为CExceptionBase),所以这里抛出的类为CDiv0Excepction

现在代码可还原为

case 5:
	throw CDiv0Excepction();

然后我们再分析CDiv0Excepction的构造函数

image

经过我们前面的分析,知道CDiv0Excepction有个基类CExceptionBase,所以sub_401360应为CExceptionBase的构造函数,现在将sub_401360重命名为CExceptionBase,看下图

image

这方面可异常关系不大,所以我简略过,下面查看CDiv0Excepction和CExceptionBase的虚表,最终可还原为

class CExceptionBase{
    public:
		virtual char * toString() = 0;
}

class CDiv0Excepction : public CExceptionBase{
    public:
    	CDiv0Excepction(){
        	printf("CExcepctionDiv0()\r\n");    
        }
    	virtual char * toString(){
            return "div zero excepction";
        }
}

case 6 和 case5是差不多的 ,最终可还原为


class CAccessExcepction : public CExceptionBase{
    public:
    	CAccessExcepction(){
        	printf("CAccessExcepction()\r\n");   
        }
    	virtual char * toString(){
            return "access excepction";
        }
}


case 6:
	throw CAccessExcepction();

还原case 7

下面看case 7

image

image

image

看上面我们可以看到case 7抛出的是一个指针CAccessExcepction *,(所有的对象指针都继承void *),所以下面可还原为

case 7:
	throw new CAccessExcepction();

最终try块可还原为

class CExceptionBase{
    public:
		virtual char * toString() = 0;
}

class CDiv0Excepction : public CExceptionBase{
    public:
    	CDiv0Excepction(){
        	printf("CExcepctionDiv0()\r\n");    
        }
    	virtual char * toString(){
            return "div zero excepction";
        }
}
class CAccessExcepction : public CExceptionBase{
    public:
    	CAccessExcepction(){
        	printf("CAccessExcepction()\r\n");   
        }
    	virtual char * toString(){
            return "access excepction";
        }
}

int sub_401000(int n){
    
    try{
        switch(n){
            case 1:
                throw 3;
            case 2:
                throw 3.0f;
            case 3:
                throw '3';
            case 4:
                throw 3.0;
        	case 5:
				throw CDiv0Excepction();
        	case 6:
				throw CAccessExcepction();
        	case 7:
				throw new CAccessExcepction();
    }
    catch...
}

还原前面4个catch

下面还原catch

image

我们可以看到再函数开头有明显的SEH增加节点的行为转到SEH_401000

image

看到这里是FuncInfo的地址,下面转到FuncInfo

image

FuncInfo的结构

image

上面为FuncInfo的结构

上面只有一个TryBlockMapEntry,代表这里只有一个try块,catch的数量为TryBlockMapEntry的第四个成员dwCatchCount,这里有7个catch块,在ida中msRttiDscr表示为HandlerType

msRttiDscr 有几个成员需要关注,pType表明了catch的类型,CatchProc表示catch后执行的函数,dispCatchObjOffset表示catch后异常对象在栈中的偏移(以EBP为基准线)

前面四个可还原为

    catch(int a){
    	printf("catch int %d\r\n",a);
    }
     catch(float a){
    	printf("catch float %f\r\n",a);
    }
     catch(char a){
    	printf("catch char %c\r\n",a);
    }
    catch(double a){
    	printf("catch double %f\r\n",a);
    }

还原剩下的catch异常

现在还原剩下的catch异常

image

大家看上面圈出的catch异常,可能会认为这个异常类型为CExceptionBase,但是注意看msRttiDscr 的第一个成员nFlag的值为8,这代表这是一个引用类型的异常,所以这个catch异常可还原为

 catch(CExceptionBase& a){
    	printf("catch error %s\r\n",a.toString());
    }

第6个catch异常可还原为

 catch(CAccessExcepction* a){
    	printf("catch error %s\r\n",a->toString());
    }

最后一个异常的类型为0,这个的意思是任何异常都匹配,这个catch异常可还原为

 catch(...){
    	printf("catch ...\r\n");
    }

还原程序的结尾

最后我们这个程序的结尾

我们找最后一个catch异常的catch函数(其实随便一个就可以),

image

image

我们可以看到在catch函数中,最后会返回一个地址,这个地址就是catch函数执行完,要跳转的那个地址,我们跟过去

image

看到上面将state设置为-1,代表try ... catch结束了,再接着跳转到loc_40122E

image

这就是这个函数的结尾,可还原为

printf("Test end!\r\n")

整个函数的还原

最后整理一下整个函数的还原情况

#include <iostream>

class CExceptionBase{
    public:
        virtual char * toString() = 0;
};

class CDiv0Excepction : public CExceptionBase{
    public:
        CDiv0Excepction(){
            printf("CExcepctionDiv0()\r\n");    
        }
        virtual char * toString(){
            return "div zero excepction";
        }
};
class CAccessExcepction : public CExceptionBase{
    public:
        CAccessExcepction(){
            printf("CAccessExcepction()\r\n");   
        }
        virtual char * toString(){
            return "access excepction";
        }
};

int sub_401000(int n){
    
    try{
        switch(n){
            case 1:
                throw 3;
            case 2:
                throw 3.0f;
            case 3:
                throw '3';
            case 4:
                throw 3.0;
            case 5:
                throw CDiv0Excepction();
            case 6:
                throw CAccessExcepction();
            case 7:
                throw new CAccessExcepction();
            }
    }
    catch(int a){
    printf("catch int %d\r\n",a);
    }
    catch(float a){
    printf("catch float %f\r\n",a);
    }
    catch(char a){
    printf("catch char %c\r\n",a);
    }
    catch(double a){
    printf("catch double %f\r\n",a);
    }
     catch(CExceptionBase& a){
        printf("catch error %s\r\n",a.toString());
    }
     catch(CAccessExcepction* a){
        printf("catch error %s\r\n",a->toString());
    }
     catch(...){
        printf("catch ...\r\n");
    }
    printf("Test end!\r\n");
}

int main(){

    sub_401000(1);
}