• 从逆向的角度去理解C++虚函数表


      很久没有写过文章了,自己一直是做C/C++开发的,我一直认为,作为一个C/C++程序员,如果能够好好学一下汇编和逆向分析,那么对于我们去理解C/C++将会有很大的帮助,因为程序中所有的奥秘都藏在汇编中,很多问题我们从表面上无法看出到底是为什么,只要用逆向工具一分析,很快就能知道其中的所以然来。我们都知道虚函数表是放在类对象的最前面,但是很多人并不知道虚函数表的第一项存放的是什么,下面我用IDA分析一下C++的虚函数调用,从汇编的角度去理解虚函数。此文只适合具有逆向分析基础的读者,如果没有逆向分析基础,请直接绕过。有如下C++代码:

    class Test
    {
    public:
        Test()
        {
            printf("Test::Test
    ");
        }
        virtual ~Test()
        {
            printf("Virtual ~Test()
    ");
        }
        virtual void prointer()=0;
        virtual void pointf()=0;
    };
    class TestA:public Test
    {
    public:
        TestA()
        {
            printf("TestA::TestA
    ");
        }
        virtual ~TestA()
        {
            printf("TestA::TestA
    ");
        }
        virtual void prointer()
        {
            printf("Derive Class TestA::Pointer
    ");
        }
        virtual void pointf()
        {
            printf("Derive Class TestA::Pointf
    ");
        }
    };
    
    
    int _tmain(int argc, _TCHAR* argv[])
    {
        TestA *pTest=new TestA;
        pTest->pointf();
        pTest->prointer();
        delete pTest;
        return 0;
    }

        用IDA打开EXE文件,用流程图模式显示:

    运行到调用new的地方:

    一层一层跟进去,我们会发现,new的内部其实就是调用的malloc进行内存分配,msvcr100d_malloc便是malloc函数:

    往下走,发现如下指令:

    text:00C3149F call    j_??2@YAPAXI@Z_0                ; operator new(uint)
    .text:00C314A4 add     esp, 4
    .text:00C314A7 mov     [ebp+this], eax
    .text:00C314AD mov     [ebp+var_4], 0
    .text:00C314B4 cmp     [ebp+this], 0
    .text:00C314BB jz      short loc_C314D0
    .text:00C314BD mov     ecx, [ebp+this]                 ; this
    .text:00C314C3 call    j_??0TestA@@QAE@XZ              ; TestA::TestA(void)
    .text:00C314C8 mov     [ebp+var_10C], eax              ; new的返回值赋给[epb+var_10C]
    .text:00C314CE jmp     short loc_C314DA                ; eax=this
    .text:00C314D0 ; ---------------------------------------------------------------------------
    .text:00C314D0
    .text:00C314D0 loc_C314D0:                             ; CODE XREF: _wmain+5Bj
    .text:00C314D0 mov     [ebp+var_10C], 0
    .text:00C314DA
    .text:00C314DA loc_C314DA:                             ; CODE XREF: _wmain+6Ej
    .text:00C314DA mov     eax, [ebp+var_10C]              ; eax=this
    .text:00C314E0 mov     [ebp+var_104], eax              ; [ebp+var_104]=this
    .text:00C314E6 mov     [ebp+var_4], 0FFFFFFFFh
    .text:00C314ED mov     ecx, [ebp+var_104]              ; ecx=this
    .text:00C314F3 mov     [ebp+pTest], ecx                ; [ebp+pTest]=this
    .text:00C314F6 mov     eax, [ebp+pTest]                ; eax=this
    .text:00C314F9 mov     edx, [eax]
    .text:00C314FB mov     esi, esp
    .text:00C314FD mov     ecx, [ebp+pTest]
    .text:00C31500 mov     eax, [edx+8]                    ; eax=this+8
    .text:00C31503 call    eax                             ; 调用this+8处的代码

    跟进去,发现如下指令:

    text:00C31820 push    ebp
    .text:00C31821 mov     ebp, esp
    .text:00C31823 sub     esp, 0CCh
    .text:00C31829 push    ebx
    .text:00C3182A push    esi
    .text:00C3182B push    edi
    .text:00C3182C push    ecx
    .text:00C3182D lea     edi, [ebp+var_CC]
    .text:00C31833 mov     ecx, 33h
    .text:00C31838 mov     eax, 0CCCCCCCCh
    .text:00C3183D rep stosd
    .text:00C3183F pop     ecx
    .text:00C31840 mov     [ebp+this], ecx
    .text:00C31843 mov     esi, esp
    .text:00C31845 push    offset aDeriveClassT_0          ; "Derive Class TestA::Pointf
    "
    .text:00C3184A call    ds:__imp__printf
    .text:00C31850 add     esp, 4
    .text:00C31853 cmp     esi, esp
    .text:00C31855 call    j___RTC_CheckEsp
    .text:00C3185A pop     edi
    .text:00C3185B pop     esi
    .text:00C3185C pop     ebx
    .text:00C3185D add     esp, 0CCh
    .text:00C31863 cmp     ebp, esp
    .text:00C31865 call    j___RTC_CheckEsp
    .text:00C3186A mov     esp, ebp
    .text:00C3186C pop     ebp
    .text:00C3186D retn
    .text:00C3186D ?pointf@TestA@@UAEXXZ endp

    这就是pTest->pointf();调用的汇编代码,继续往下走:

    调用了[edx+4]其实也就是this+4处的代码,跟进去,代码如下:

    .text:00C517B0 push    ebp
    .text:00C517B1 mov     ebp, esp
    .text:00C517B3 sub     esp, 0CCh
    .text:00C517B9 push    ebx
    .text:00C517BA push    esi
    .text:00C517BB push    edi
    .text:00C517BC push    ecx
    .text:00C517BD lea     edi, [ebp+var_CC]
    .text:00C517C3 mov     ecx, 33h
    .text:00C517C8 mov     eax, 0CCCCCCCCh
    .text:00C517CD rep stosd
    .text:00C517CF pop     ecx
    .text:00C517D0 mov     [ebp+this], ecx
    .text:00C517D3 mov     esi, esp
    .text:00C517D5 push    offset aDeriveClassTes          ; "Derive Class TestA::Pointer
    "
    .text:00C517DA call    ds:__imp__printf
    .text:00C517E0 add     esp, 4
    .text:00C517E3 cmp     esi, esp
    .text:00C517E5 call    j___RTC_CheckEsp
    .text:00C517EA pop     edi
    .text:00C517EB pop     esi
    .text:00C517EC pop     ebx
    .text:00C517ED add     esp, 0CCh
    .text:00C517F3 cmp     ebp, esp
    .text:00C517F5 call    j___RTC_CheckEsp
    .text:00C517FA mov     esp, ebp
    .text:00C517FC pop     ebp
    .text:00C517FD retn
    .text:00C517FD ?prointer@TestA@@UAEXXZ endp

    以上其实就是调用pTest->prointer();的汇编代码,继续往下执行:

    这里调用的就是this处的指令,跟进去,有如下指令:

    text:011D1890 push    ebp
    .text:011D1891 mov     ebp, esp
    .text:011D1893 sub     esp, 0CCh
    .text:011D1899 push    ebx
    .text:011D189A push    esi
    .text:011D189B push    edi
    .text:011D189C push    ecx
    .text:011D189D lea     edi, [ebp+var_CC]
    .text:011D18A3 mov     ecx, 33h
    .text:011D18A8 mov     eax, 0CCCCCCCCh
    .text:011D18AD rep stosd
    .text:011D18AF pop     ecx
    .text:011D18B0 mov     [ebp+this], ecx
    .text:011D18B3 mov     ecx, [ebp+this]                 ; this
    .text:011D18B6 call    j_??1TestA@@UAE@XZ              ; TestA::~TestA(void)
    .text:011D18BB mov     eax, [ebp+arg_0]
    .text:011D18BE and     eax, 1
    .text:011D18C1 jz      short loc_11D18CF
    .text:011D18C3 mov     eax, [ebp+this]
    .text:011D18C6 push    eax                             ; void *
    .text:011D18C7 call    j_??3@YAXPAX@Z_0                ; operator delete(void *)
    .text:011D18CC add     esp, 4

    此处就是析构函数调用的地方,综上所述,我们可以得出结论,new最终是调用的malloc进行内存分配,在虚函数表中,虚析构函数是放在最前面的。当然这只是最简单的类继承体系,而且有些地方写得不够详细,大家可以自己动手写代码用IDA去进行逆向分析,探索其中的奥秘,以上只是本人的一些理解,如果有不合理的地方欢迎大家指正,希望能和大家共同学习,共同进步。

  • 相关阅读:
    Firefly AIO-3399ProC开发板安装RKNN Toolkit 1.6.0开发环境
    用1kΩ电阻可以组合出多少个阻值?
    电气元件与电气控制的保护(PPT)
    MASM汇编DOS
    可免费解锁PDF的网站
    org.springframework.web.filter.CharacterEncodingFilter cannot be cast to javax.servlet.Filter和java.lang.LinkageError: loader constraint violation: loader (instance of org/apache/jasper/servlet/JasperL
    访问联合类型中某个类型特有的属性或方法
    防止攻击者篡改外部脚本
    对 state 中的数组使用 v-model
    TS 定义一个最简单的字符串数组类型
  • 原文地址:https://www.cnblogs.com/lzmfywz/p/4775997.html
Copyright © 2020-2023  润新知