首页
社区
课程
招聘
[旧帖] [原创]C++逆向分析入门 0.00雪花
发表于: 2013-12-20 10:46 2042

[旧帖] [原创]C++逆向分析入门 0.00雪花

2013-12-20 10:46
2042
唉,看雪发帖有一点不好,就是复制过来颜色都没了,本来自己写的注释是红色的,希望能够比较醒目,不过这样一来全变成黑的了。。。没办法,第一次发帖自然要认真一些,手动把所有原来的注释都改成红色的了,希望这样能够看得更加清楚一些。
    我是菜鸟哈,各位有什么意见尽可和我说,欢迎拍砖吐槽!
    这是《黑客免杀攻防》中关于逆向C++的唯一一篇总结,前面的基础知识由于去年已经学习过了钱林松老师的《C++反汇编与逆向分析技术》并做了笔记所以就只是看了一下,发现内容大同小异,不过看过一遍之后也温习了一下C++逆向的基础知识,对于下面的这个菱形继承的实验还是有帮助的。并且之前在学习钱老师的书的时候并没有给继承以及虚函数机制写过总结,这篇就当作是C++逆向系列的补充吧。
        为了分析清楚C++的继承以及虚函数机制,这里使用了程序的Debug版本。直接使用IDA加载分析得到如下代码:
.text:0042D670                 push    ebp
.text:0042D671                 mov     ebp, esp
.text:0042D673                 push    0FFFFFFFFh
…………………………                                 …………………………..
;以上代码均为保存寄存器以及栈初始化等操作的代码,可不必理会
.text:0042D69C                 mov     eax, dword_4950C8
.text:0042D6A1                 xor     eax, ebp
.text:0042D6A3                 push    eax
.text:0042D6A4                 lea     eax, [ebp+var_C]
.text:0042D6A7                 mov     large fs:0, eax
.text:0042D6AD                 lea     ecx, [ebp+class1];取地址指令
.text:0042D6B0                 call    sub_42BA28        ;结合上面的取地址指令可以知道这里应该是调用第一个类的构造函数,因此使用IDA的改名功能改成class1
.text:0042D6B5                 mov     [ebp+var_4], 0
.text:0042D6BC                 lea     ecx, [ebp+class2]
.text:0042D6BF                 call    sub_42B320        ;同理,这是class2
.text:0042D6C4                 mov     byte ptr [ebp+var_4], 1;这类指令将对象数目存至栈中,只有debug版本才有,无需理会
.text:0042D6C8                 lea     ecx, [ebp+class3]
.text:0042D6CB                 call    sub_42BB5E        ;这是class3
.text:0042D6D0                 mov     byte ptr [ebp+var_4], 2
.text:0042D6D4                 lea     ecx, [ebp+class4]
.text:0042D6DA                 call    sub_42B64F        ;这是class4
.text:0042D6DF                 mov     byte ptr [ebp+var_4], 3
.text:0042D6E3                 lea     eax, [ebp+class1]
.text:0042D6E6                 mov     [ebp+pobj1], eax
;上面这几句代码是取出class1的地址然后赋值给另外一个变量,这个变量应该是一个class1类或是其基类的指针,改名为pobj1
.text:0042D6EC                 mov     esi, esp
.text:0042D6EE                 push    0;这是参数
.text:0042D6F0                 mov     eax, [ebp+pobj1]
.text:0042D6F6                 mov     edx, [eax]
.text:0042D6F8                 mov     ecx, [ebp+pobj1]
.text:0042D6FE                 mov     eax, [edx]
;上面蓝色三条指令就是取出类的虚表中的函数的固定步骤,下一句便是调用取出的函数
;而红色的则是将this指针放到ecx中,更加确定了这个函数是类中的函数

.text:0042D700                 call    eax
.text:0042D702                 cmp     esi, esp
.text:0042D704                 call    sub_42BD70
;书上没有上面这两条语句,经过OD调试,发现这是VS2010在dubug版本中加入的栈检查函数,可不必理会
.text:0042D709                 lea     eax, [ebp+class2]
.text:0042D70C                 mov     [ebp+pobj1], eax
.text:0042D712                 mov     esi, esp
.text:0042D714                 push    1
.text:0042D716                 mov     eax, [ebp+pobj1]
.text:0042D71C                 mov     edx, [eax]
.text:0042D71E                 mov     ecx, [ebp+pobj1]
.text:0042D724                 mov     eax, [edx]
.text:0042D726                 call    eax
.text:0042D728                 cmp     esi, esp
.text:0042D72A                 call    sub_42BD70
.text:0042D72F                 lea     eax, [ebp+class3]
.text:0042D732                 mov     [ebp+pobj1], eax
.text:0042D738                 mov     esi, esp
.text:0042D73A                 push    2
.text:0042D73C                 mov     eax, [ebp+pobj1]
.text:0042D742                 mov     edx, [eax]
.text:0042D744                 mov     ecx, [ebp+pobj1]
.text:0042D74A                 mov     eax, [edx]
.text:0042D74C                 call    eax
;直到这里都是同一类型的函数调用,说明class1、class2、class3虚表中至少有一个函数,并且都是使用的同一个指针,说明这三个对象要不就是同一个类,要不就是基类相同
.text:0042D74E                 cmp     esi, esp
.text:0042D750                 call    sub_42BD70
.text:0042D755                 push    0Ah
.text:0042D757                 lea     ecx, [ebp+class4]
.text:0042D75D                 call    sub_42B087
;这里调用class4的函数,不过这里没有使用pobj1指针来调用,说明是通过对象调用的
.text:0042D762                 push    1Ch
.text:0042D764                 call    sub_42C086
;这个函数有一个0x1C作为参数,但是不知道是干嘛用的,先继续看
.text:0042D769                 add     esp, 4
.text:0042D76C                 mov     [ebp+var_1F0], eax
.text:0042D772                 mov     byte ptr [ebp+var_4], 4
.text:0042D776                 cmp     [ebp+var_1F0], 0
.text:0042D77D                 jz      short loc_42D792
;将函数的返回值和0比较,若为0则调到函数结尾,这是需要注意的
.text:0042D77F                 mov     ecx, [ebp+var_1F0]
.text:0042D785                 call    sub_42B320
;看到这里是不是感觉有点熟悉了?看到ecx?并且将前一个函数的返回值传入给ecx,有经验的同学应该知道这里就应该是构造函数了,再看上面class2的构造函数,发现是同一个,说明这正是新创建一个class2对象,但是和上面又有点不一样,按照理论上说ecx应该传入this指针,这里的ecx是前一个函数的返回值,这有什么联系呢?很显然这应该是在堆中分配的类,到这里前面的诸多疑惑都迎刃而解了
.text:0042D78A                 mov     [ebp+var_204], eax
.text:0042D790                 jmp     short loc_42D79C
.text:0042D792 ; ---------------------------------------------------------------------------
.text:0042D792
.text:0042D792 loc_42D792:                             ; CODE XREF: sub_42D670+10Dj
.text:0042D792                 mov     [ebp+var_204], 0
.text:0042D79C
.text:0042D79C loc_42D79C:                             ; CODE XREF: sub_42D670+120j
.text:0042D79C                 mov     eax, [ebp+var_204]
.text:0042D7A2                 mov     [ebp+var_1FC], eax
.text:0042D7A8                 mov     byte ptr [ebp+var_4], 3
.text:0042D7AC                 mov     ecx, [ebp+var_1FC]
.text:0042D7B2                 mov     [ebp+pclass2], ecx
;最终将新建的对象地址赋值给栈中的一个值,这里将其命名为pclass2,下面是一样的过程,也是在堆中申请内存然后创建一个class3对象
.text:0042D7B8                 push    1Ch
.text:0042D7BA                 call    sub_42C086
.text:0042D7BF                 add     esp, 4
.text:0042D7C2                 mov     [ebp+var_1D8], eax
.text:0042D7C8                 mov     byte ptr [ebp+var_4], 5
.text:0042D7CC                 cmp     [ebp+var_1D8], 0
.text:0042D7D3                 jz      short loc_42D7E8
.text:0042D7D5                 mov     ecx, [ebp+var_1D8]
.text:0042D7DB                 call    sub_42BB5E
.text:0042D7E0                 mov     [ebp+var_204], eax
.text:0042D7E6                 jmp     short loc_42D7F2
.text:0042D7E8 ; ---------------------------------------------------------------------------
.text:0042D7E8
.text:0042D7E8 loc_42D7E8:                             ; CODE XREF: sub_42D670+163j
.text:0042D7E8                 mov     [ebp+var_204], 0
.text:0042D7F2
.text:0042D7F2 loc_42D7F2:                             ; CODE XREF: sub_42D670+176j
.text:0042D7F2                 mov     eax, [ebp+var_204]
.text:0042D7F8                 mov     [ebp+var_1E4], eax
.text:0042D7FE                 mov     byte ptr [ebp+var_4], 3
.text:0042D802                 mov     ecx, [ebp+var_1E4]
.text:0042D808                 mov     [ebp+pclass3], ecx
;将其命名为pclass3
.text:0042D80E                 mov     eax, [ebp+pclass2]
.text:0042D814                 mov     edx, [eax]
.text:0042D816                 mov     esi, esp
.text:0042D818                 mov     ecx, [ebp+pclass2]
.text:0042D81E                 mov     eax, [edx+4]
.text:0042D821                 call    eax
;上几条代码显而易见又是调用虚表中的函数,只不过多了一次取址,因为这是指针存储着堆中对象的地址,所以需要比平常多一次取址。同时可以看到这里调用的虚表中的函数和上面的不一样,说明class2至少有两个虚表函数
.text:0042D823                 cmp     esi, esp
.text:0042D825                 call    sub_42BD70
.text:0042D82A                 mov     eax, [ebp+pclass3]
.text:0042D830                 mov     edx, [eax]
.text:0042D832                 mov     esi, esp
.text:0042D834                 mov     ecx, [ebp+pclass3]
.text:0042D83A                 mov     eax, [edx+4]
.text:0042D83D                 call    eax
;同理class3也至少有两个虚表函数
.text:0042D83F                 cmp     esi, esp
.text:0042D841                 call    sub_42BD70
.text:0042D846                 mov     eax, [ebp+pclass2]
.text:0042D84C                 mov     edx, [eax]
.text:0042D84E                 mov     esi, esp
.text:0042D850                 mov     ecx, [ebp+pclass2]
.text:0042D856                 mov     eax, [edx+8]
.text:0042D859                 call    eax
;哈,发现这里还有一个class2虚表中的函数
.text:0042D85B                 cmp     esi, esp
.text:0042D85D                 call    sub_42BD70
.text:0042D862                 mov     eax, [ebp+pclass3]
.text:0042D868                 mov     edx, [eax]
.text:0042D86A                 mov     esi, esp
.text:0042D86C                 mov     ecx, [ebp+pclass3]
.text:0042D872                 mov     eax, [edx+8]
.text:0042D875                 call    eax
;不出意料,class3也有第三个虚表函数
.text:0042D877                 cmp     esi, esp
.text:0042D879                 call    sub_42BD70
.text:0042D87E                 lea     ecx, [ebp+class4]
.text:0042D884                 call    sub_42C1E4
;调用class4中的一个函数
.text:0042D889                 lea     ecx, [ebp+class4]
.text:0042D88F                 call    sub_42B82A
;调用class4中的另一个函数,至此发现class4至少有3个函数
.text:0042D894                 mov     eax, [ebp+pclass2]
.text:0042D89A                 mov     [ebp+var_1C0], eax
.text:0042D8A0                 mov     ecx, [ebp+var_1C0]
.text:0042D8A6                 mov     [ebp+var_1CC], ecx
.text:0042D8AC                 cmp     [ebp+var_1CC], 0
.text:0042D8B3                 jz      short loc_42D8CA
;以上的代码是判断pclass2中的地址,也就是堆中的class2是否为空,这一步到底是要干嘛呢?让我们接下去看
.text:0042D8B5                 push    1
.text:0042D8B7                 mov     ecx, [ebp+var_1CC]
.text:0042D8BD                 call    sub_42B122
;按照上面的格式,这个函数也应该是class2种的一个函数,但是调用这个函数之前却要判断this指针是否为空,这就让我们不禁联想到了这是一个析构函数
.text:0042D8C2                 mov     [ebp+var_204], eax
.text:0042D8C8                 jmp     short loc_42D8D4
.text:0042D8CA ; ---------------------------------------------------------------------------
.text:0042D8CA
.text:0042D8CA loc_42D8CA:                             ; CODE XREF: sub_42D670+243j
.text:0042D8CA                 mov     [ebp+var_204], 0
.text:0042D8D4
.text:0042D8D4 loc_42D8D4:                             ; CODE XREF: sub_42D670+258j
.text:0042D8D4                 mov     eax, [ebp+pclass3]
.text:0042D8DA                 mov     [ebp+var_1A8], eax
.text:0042D8E0                 mov     ecx, [ebp+var_1A8]
.text:0042D8E6                 mov     [ebp+var_1B4], ecx
.text:0042D8EC                 cmp     [ebp+var_1B4], 0
.text:0042D8F3                 jz      short loc_42D90A
.text:0042D8F5                 push    1
.text:0042D8F7                 mov     ecx, [ebp+var_1B4]
.text:0042D8FD                 call    sub_42B122
;这里也是一样的,只不过对象换成了pclass3。之前堆中的对象需要new来申请,而这里则需要使用delete来释放,猜想这里就应该是类的析构函数
.text:0042D902                 mov     [ebp+var_204], eax
.text:0042D908                 jmp     short loc_42D914
.text:0042D90A ; ---------------------------------------------------------------------------
.text:0042D90A
.text:0042D90A loc_42D90A:                             ; CODE XREF: sub_42D670+283j
.text:0042D90A                 mov     [ebp+var_204], 0
.text:0042D914
.text:0042D914 loc_42D914:                             ; CODE XREF: sub_42D670+298j
.text:0042D914                 mov     [ebp+var_19C], 0
.text:0042D91E                 mov     byte ptr [ebp+var_4], 2
;接下来快到函数的末尾了,差不多应该是最开始栈中构造的函数析构的时候了,只不过析构函数的调用顺序和构造函数相反罢了
.text:0042D922                 lea     ecx, [ebp+class4]
.text:0042D928                 call    sub_42B546
.text:0042D92D                 mov     byte ptr [ebp+var_4], 1
.text:0042D931                 lea     ecx, [ebp+class3]
.text:0042D934                 call    sub_42B875
.text:0042D939                 mov     byte ptr [ebp+var_4], 0
.text:0042D93D                 lea     ecx, [ebp+class2]
.text:0042D940                 call    sub_42B92E
.text:0042D945                 mov     [ebp+var_4], 0FFFFFFFFh
.text:0042D94C                 lea     ecx, [ebp+class1]
.text:0042D94F                 call    sub_42B771
.text:0042D954                 mov     eax, [ebp+var_19C]
;下面便是恢复寄存器的值等一些还原现场的操作了
……………………..                                        …………………………………..
.text:0042D988                 pop     ebp
.text:0042D989                 retn
.text:0042D989 sub_42D670      endp

        从上面这样看下来,无非是多个类调用虚函数等的一些操作,没什么好说的,但是进入构造函数和析构函数之后谜底就会揭晓,下面来看比较典型的class2的构造函数和析构函数:

.text:0042DC00 sub_42DC00      proc near               ; CODE XREF: sub_42B320j
.text:0042DC00
.text:0042DC00 var_CC          = byte ptr -0CCh
.text:0042DC00 var_8           = dword ptr -8
;初始化的代码忽略,直接进入正题,这里ecx便是之前传进来的class2的this指针:
.text:0042DC20                 mov     [ebp+var_8], ecx
.text:0042DC23                 mov     ecx, [ebp+var_8]
.text:0042DC26                 call    sub_42B3E3
;这里有一个奇怪的地方,使用this指针调用另外一个函数?并且也很想一个构造函数,这让我们不禁联想起了,class2是继承于另外一个类的,并且通过比较函数地址,发现这个构造函数是另外一个类的构造函数,但是这个构造函数里面却又调用了class1的构造函数,说明class2是间接继承了class1的。这里就很明确的告诉我们class2是class1的子类。
;知道了这个后面的就很容易了,你可以跟进继续看class1的构造函数,也可以直接往下看

.text:0042DC2B                 mov     eax, [ebp+var_8]
.text:0042DC2E                 mov     dword ptr [eax], offset off_483CF8
;上面一条语句便是将class2的虚表地址放入class2对象中,而下面的便是初始化表达式以及用户自定义的初始化代码了
.text:0042DC34                 mov     eax, [ebp+var_8]
.text:0042DC37                 mov     dword ptr [eax+14h], 11111111h
.text:0042DC3E                 mov     eax, 2222h
.text:0042DC43                 mov     ecx, [ebp+var_8]
.text:0042DC46                 mov     [ecx+18h], ax
.text:0042DC4A                 push    offset aCboyConstructo ; "CBoy() Constructor...\r\n"
.text:0042DC4F                 call    sub_42C144
;函数结尾代码省略
.text:0042DC6D                 retn
.text:0042DC6D sub_42DC00      endp

        既然知道了class2的构造函数并且class2是继承于class1的,那class3也就很容易了,这里就不说了。再来看class2的析构函数:

.text:0042DD20 sub_42DD20      proc near               ; CODE XREF: sub_42B92Ej
.text:0042DD20
.text:0042DD20 var_CC          = byte ptr -0CCh
.text:0042DD20 var_8           = dword ptr -8
;函数开头代码省略
.text:0042DD40                 mov     [ebp+var_8], ecx
.text:0042DD43                 mov     eax, [ebp+var_8]
.text:0042DD46                 mov     dword ptr [eax], offset off_483CF8
;上面的代码又是将虚表指针复制到对象中,这步叫做虚表恢复,就是在调用虚构函数之前不管你虚表是对是错,先把你赋值为正确的值就可以了
.text:0042DD4C                 push    offset aCboyDestructor ; "CBoy() Destructor...\r\n"
.text:0042DD51                 call    sub_42C144
;printf函数
.text:0042DD56                 add     esp, 4
.text:0042DD59                 mov     ecx, [ebp+var_8]
.text:0042DD5C                 call    sub_42B54B
;前面的构造函数讲的很清楚了,这里应该是调用父类的析构函数了
;函数结尾代码省略
.text:0042DD74 sub_42DD20      endp

        Class3的析构函数自然也是差不多的,说明class2和class3是在同一个类层次上的,按照上面的方法可以发现class4的是继承于class2和class3的,并且拥有两张虚表,如果用OD动态调试会更加清楚,这里就不详细说明了。
        至此C++关于类、继承以及虚表机制的反汇编代码差不多都已经比较熟悉了,至于后面如何灵活运用,那又是另外一回事了。我自认为对于C++反汇编理解的还可以,但是遇上实践中的一些代码经常会无从下手,这应该和经验不误关联吧。
        写完一看时间都过12点了,赶紧洗洗睡吧,但愿今晚做梦不要又梦到逆向!!!让我睡个好觉吧!

[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

收藏
免费 0
支持
分享
最新回复 (5)
雪    币: 270
活跃值: (405)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
沙发,支持一下楼主。
2013-12-20 14:13
0
雪    币: 41
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
很厉害啊
2013-12-20 16:19
0
雪    币: 16468
活跃值: (2493)
能力值: ( LV9,RANK:147 )
在线值:
发帖
回帖
粉丝
4
算好的了,我经常碰到一些代码的时候,都是懒得看的.
2013-12-20 16:29
0
雪    币: 1
活跃值: (11)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
本人还只刚入门,希望以后多交流哈
2013-12-20 19:24
0
雪    币: 1
活跃值: (11)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
恩,我就是之前看一些代码都是自己的理解和代码混在一起,对于新手来说很不容易理解,也不清晰,所以就弄了一下,在CSDN上发博客就可以直接会有颜色,还是希望看雪能够改进一下
2013-12-20 19:26
0
游客
登录 | 注册 方可回帖
返回
//