SEH除零异常处理及值传递引用传递汇编浅谈

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SEH除零异常处理及值传递引用传递汇编浅谈相关的知识,希望对你有一定的参考价值。

笔记分享
// 过滤函数(发生异常之后通过__except(过滤表达式调用))

DWORD Filters(DWORD Code, PEXCEPTION_POINTERS ExceptionInfo)
{
    /*这只是个测试,捕获到除零异常之后根据情况判断什么类型的异常(根据实际情况来)*/
    switch (Code)
    {
    // 内存访问方面异常
    case EXCEPTION_ACCESS_VIOLATION:
        break;
    // 除零异常
    case STATUS_INTEGER_DIVIDE_BY_ZERO:
    {
        int a = 10;
        // 012C44E1  mov         dword ptr[ebp - 0Ch], 0Ah
        // 改变Ecx的地址 PEXCEPTION_POINTERS结构体记录了异常环境
        ExceptionInfo->ContextRecord->Ecx = (DWORD)&a;
        // 012C44E8  mov         eax, dword ptr[ebp + 0Ch]
        // 012C44EB  mov         ecx, dword ptr[eax + 4]
        // 012C44EE  lea         edx, [ebp - 0Ch]
        // 012C44F1  mov         dword ptr[ecx + 000000ACh], edx
        // 上面理解不透彻 希望懂得老铁们进行讲解 大体就是找到了[ecx + 000000ACh],局部变量10地址替换
    }
    break;
    }
    // 继续执行产异常的指令, 在执行的时候 [ecx] 访问的是局部变量10,所以正常执行
    // 012C44F7  or          eax, 0FFFFFFFFh
    return EXCEPTION_CONTINUE_EXECUTION;
}
// 除法(异常)函数
int* Div(int *a, int  *b)
{
    int c = *a / *b;
    /*
     汇编代码:
        1. 为int c开辟一个空间
            013642F5  mov         dword ptr [ebp-4],eax 
        2. 入栈的是地址 保存的第一个参数的地址[ebp+8] 根据函数调用约定 右->左入栈
            013642F8  mov         eax,dword ptr [ebp+8]     3. 十进制:10  
            013642FB  mov         ecx,dword ptr [ebp+0Ch]   4. 十进制: 0
        5. 会发现除法也好 加法也好总会先保存到一个寄存器中(一般是指令默认操作寄存器)
            013642FE  mov         eax,dword ptr [eax]  
        6. 数据扩展指令,将双字数据扩展为四字类型
            01364300  cdq  
        7. 有符号除法指令 结果保存到eax中
            01364301  idiv        eax,dword ptr [ecx]
        8. 因为是除0 会出现异常处理 原因是因为[ecx]中保存数值为0
    */
    // 这时候b没有变 变得只是寄存器里面的地址把b的地址替换成了自定义的局部变量int a = 10的地址
    // 所以正常运行 返回 10 / 10 = 1
    return &c;
    //  warning C4172 : 返回局部变量或临时变量的地址 EAX寄存器保存
}

// 主函数

int main(int argc, char** argv)
{
    // SEH:Struct Except Handler 结构化异常处理 微软处理异常的一种机制
    __try
    {
        int a = 10;
        int b = 0;      
        int *p = Div(&a, &b);
        /*
        说明:从平衡堆栈的的方式 add esp, 8 函数采用了cdcel C调用约定,入栈顺序右到左,并且可以知道传参进去是两个参数 平衡堆栈大小8个字节(还有一种好玩的叫栈回溯,找调用者函数)
        1. 函数原型:int Div(int a, int b);
        2. 调用: Div(&a, &b);
            1.1 第二个参数地址入栈 0
            01386240  lea         eax,[ebp-30h]  
            01386243  push        eax
            1.2 第二个参数地址入栈 10
            01386244  lea         ecx, [ebp - 24h]
            01386247  push        ecx
            1.3 调用函数Div
            01386248  call        Div(01381483h)
            1.4 调用者平衡堆栈
            0138624D  add         esp, 8
        ==========================================================
        1. 函数原型:int Div(int a, int b);
        2. 调用:Div(a, b)。
        3. 可以发现每个参数入栈的汇编指令都是两条指令,区别在于入栈地址与入栈立即数
            002E617D  mov         eax, dword ptr[ebp - 2Ch]
            002E6180  push        eax
            002E6181  mov         ecx, dword ptr[ebp - 20h]
            002E6184  push        ecx
            002E6185  call        002E14BF
            002E618A  add         esp, 8
        ==========================================================
        1. 函数原型 int Div(int &a, int &b);
        2. 调用:  Div(a, b); 引用在汇编传参的时候回事怎样
            00126180  lea         eax,[ebp-30h]
            00126183  push        eax
            00126184  lea         ecx,[ebp-24h]
            00126187  push        ecx
            00126188  call        001214C4
            0012618D  add         esp,8
        结论:其实引用与指针在函数传参,汇编来看没有区别的,只是语法使用确实有约束
        再看:
            int nCount = 1;
            int &nVar = nCount;
        汇编如下:
            00C76172  mov         dword ptr [ebp-24h],1
            00C76179  lea         eax,[ebp-24h]
            00C7617C  mov         dword ptr [ebp-30h],eax
            引用自己开启的内存中保存的是被引用变量的地址。
        */
        cout << "正常执行 a / b :" << *p << endl;
    }
    // 捕获异常 过滤表达式处理 这里是一个函数
    // 00059135  call        000510D7  
    // 0F963922  call        ecx
    // 0F969263  call        0F963912
    // 76FF34BF  call        ecx  
    // 76FF348E  call        76FF349B
    // 大概经过层次Ret 返回到了Div return 处 因VS没有看到调用的函数
    __except (Filters(GetExceptionCode(), GetExceptionInformation()))
    {
        cout << "异常块" << endl;
    }
    system("pause");
    return 0;
}

关于指针与类型偏移量的一些笔记
C/C++代码:

    char Arrnumber[][2] = { "1", "2", "3", "4", };
    int* p = (int *)Arrnumber;
    p += 1;
    char* p1 = Arrnumber[0];
    p1 += 1;
    short* p2 = (short *)Arrnumber;
    p2 += 1;

对照汇编:

注释:二维数组略显复杂 从数据段ds:中取出内容 2个字节放入ax寄存器(16位),
     后进行ebp操作(可以简单理解放入函数栈开辟的局部变量)
C/C: char Arrnumber[][2] = { "1", "2", "3", "4", };
    00FD2C68  mov         ax,word ptr ds:[00FDDA88h]  
    00FD2C6E  mov         word ptr [ebp-10h],ax  
    00FD2C72  mov         ax,word ptr ds:[00FDDAD0h]  
    00FD2C78  mov         word ptr [ebp-0Eh],ax  
    00FD2C7C  mov         ax,word ptr ds:[00FDDAE4h]  
    00FD2C82  mov         word ptr [ebp-0Ch],ax  
    00FD2C86  mov         ax,word ptr ds:[00FDDB00h]  
    00FD2C8C  mov         word ptr [ebp-0Ah],ax 
C/C: int* p = (int *)Arrnumber;
注释:【ebp-10】是存储数组中第一个元素1内存单元,lea拿到单元地址送入到eax寄存器中
    00FD2C90  lea         eax,[ebp-10h] 
注释:eax保存的是地址,dword ptr则是声明几个字节(双字),元素1的地址传送到【ebp-1Ch】内存单元中,其实也就是[p] 
    00FD2C93  mov         dword ptr [ebp-1Ch],eax 
注释:元素1的地址从单元中给了eax,准备做加法运算
    00FD2C96  mov         eax,dword ptr [ebp-1Ch] 
C/C: p += 1;
注释:add eax, 4   
    00FD2C99  add         eax,4 
注释:eax是add之后的值,在送回p的内存单元中。
    00FD2C9C  mov         dword ptr [ebp-1Ch],eax 
以上完成了 p += 1;的过程,根据数据类型进行的偏移量

;以下内容都是一样   
    00FD2C9F  mov         eax,2  
    00FD2CA4  imul        ecx,eax,0  
    00FD2CA7  lea         edx,[ebp+ecx-10h]  
    00FD2CAB  mov         dword ptr [ebp-28h],edx  
    00FD2CAE  mov         eax,dword ptr [ebp-28h]  
    00FD2CB1  add         eax,1  
    00FD2CB4  mov         dword ptr [ebp-28h],eax  
    00FD2CB7  lea         eax,[ebp-10h]  
    00FD2CBA  mov         dword ptr [ebp-34h],eax  
    00FD2CBD  mov         eax,dword ptr [ebp-34h]  
    00FD2CC0  add         eax,2  
    00FD2CC3  mov         dword ptr [ebp-34h],eax  

相对比一维数组初始化操作汇编指令少很多,直接立即数传送到局部变量
但是对于指针进行加法操作的时候,是一样的。
C/C++源码:
    初始化数组一维:
        char Arrnumber[] = { 1, 2, 3, 4, };
对照汇编
    00192C68  mov         byte ptr [ebp-0Ch],1  
    00192C6C  mov         byte ptr [ebp-0Bh],2  
    00192C70  mov         byte ptr [ebp-0Ah],3  
    00192C74  mov         byte ptr [ebp-9],4  
    00192C78  lea         eax,[ebp-0Ch]  
    00192C7B  mov         dword ptr [ebp-18h],eax  
    00192C7E  mov         eax,dword ptr [ebp-18h]  
    00192C81  add         eax,4  
    00192C84  mov         dword ptr [ebp-18h],eax  
    00192C87  lea         eax,[ebp-0Ch]  
    00192C8A  mov         dword ptr [ebp-24h],eax  
    00192C8D  mov         eax,dword ptr [ebp-24h]  
    00192C90  add         eax,1  
    00192C93  mov         dword ptr [ebp-24h],eax  
    00192C96  lea         eax,[ebp-0Ch]  
    00192C99  mov         dword ptr [ebp-30h],eax  
    00192C9C  mov         eax,dword ptr [ebp-30h]  
    00192C9F  add         eax,2  
    00192CA2  mov         dword ptr [ebp-30h],eax 

引用:《C++反汇编与逆向分析技术揭秘》p34页的指针寻址公式:
p + n 目标地址 = 首地址 + sizeof(指针类型 type) * n;

以上是关于SEH除零异常处理及值传递引用传递汇编浅谈的主要内容,如果未能解决你的问题,请参考以下文章

SEH(structured exception handling)基础篇---终止处理程序

异常

SEH 结构化异常

第24章 SEH结构化异常处理—异常处理及软件异常

第25章 SEH结构化异常处理_未处理异常及向量化异常

使用筛选器和SEH处理异常