首页
社区
课程
招聘
[原创]从逆向工程的角度来看C++ (一)
发表于: 2009-5-1 12:06 16298

[原创]从逆向工程的角度来看C++ (一)

2009-5-1 12:06
16298

前话:

大家好, 这是我为了巩固对C++的逆向而进行的一系列学习笔记。
大家都清楚,现在好多软件都是C++编写的,而C++相对于C来说还是有不少新特性的。
虽说部分特性仅仅是为编译器服务的,但是:还是有部分的特性会最终反映到软件的机器码中的。
所以我们对C++语言编译后的PE进行逆向分析,不仅可以帮助我们更好地理解及分析C++编写的软件,而且对于我们对C++这门语言的更深层次理解更是起到了相当彻底的作用。

C++相对于C的新特性,主要目的是:为了使程序员更加高效快捷安全的编码及开发软件。
对于这些新特性,我个人将其分为两部分:一部分是专门为编译器服务的,它不会影响最终的Native Code的结构;而另一部分严格来说也是为编译器服务,不过最重要的是它会影响最终的Native Code的结构。

打个比方:
C++中全局函数的重载:这个东西完全就是为编译器服务的,在最终的反汇编代码中,你根本看不出来它使用了C++的重载这一面向对象的特性,因为它实际上就是两个不同的函数;
再比如类对象: 当你在反汇编代码中看到某些call在调用前都会有个ecx作为参数传进去(这里IDE假定为VC,因为Borland不是用ecx而是通过堆栈来传递this指针的),而在call内部经常有对ecx的类似“mov edx,ecx  ;  mov  R/M,dword ptr[edx+4*N] ”时, 我想如果你对C++的逆向分析的比较透彻,你会马上发现这是再传递类对象的this指针,并对类成员取值等等。 另外虚函数在反汇编代码中的表现形式也是很有特点的(以后会详细介绍的)。

上面啰嗦了这么多,无非是要说明这样一个道理: 就像C语言的枚举在反汇编的代码中消失的无影无踪,而结构体成员存取值在反汇编中却表现的这么明显。 说白了一个式子:asm-->C-->C++ .

(我发现我好啰嗦啊,各位别拍砖啊

实验平台及工具: XP SP3, VC SP6(默认Debug,Release修改为最小大小优化), OD 等。

P.S  整个系列的文章对于高手来说肯定是没有任何技术含量的,发到这里只是为了可以帮助新手更快的入门。
     另外限于我的水平,整个系列中肯定会有认识不到位的地方或者认识错误的地方,欢迎各位拍砖。

------------------------------------------------------------------------------------------------
进入本次正题:

                                 (一) [  C++ 之 数据类型  ]

来CPP源码:

#include <stdio.h>
#pragma pack(1)
struct F
{
        char a;
        int  b;
        void fuck();
};
#pragma pack()
struct B
{
        int a;
        int b;
        int c;
        void  add(int,int);
        void f(int);
};

void B::add(int a,int b)
{
        c = a + b ;
}
void B::f(int x)
{
        a += x;
}

void F::fuck()
{
        this->a = 'J' ;
        this->b++;
}

enum EE
{
        red  =3,
        blue ,
        green,
        yellow,
};

int main(int argc, char* argv[])
{

        B sb; F sf; EE ee;
        sb.a = 3;sb.b = 4;sb.c = 5;
        sf.a = 'X' ; sf.b = 0x9999;
        sf.b = green;                               

        __asm int 3                        //跟failwest学的, 呵呵, 感觉不错.
        printf("%d   ",sizeof(sb));
        printf("%d   ",sizeof(sf));
        printf("%d   ",sizeof(ee));
        sb.add(sb.a,sb.b);
        sb.f(4);
        sf.fuck();

        return 0;
}

结构体B ,F 两种抽象数据类型, 默认的话, 其结构体大小为数据成员个数乘以4 .
(如果定义了对齐粒度的话就另当别论).函数不会算到大小里面去(后面的虚函数
除外), 对于一般的结构体, 都是当成一个内存块来处理的. 对于B, F这种抽象数据
类型, 一般都是将ecx保存其指针(以后到了类就是this了).

下面看反汇编调试过程吧.
00401030    55              push ebp
00401031    8BEC            mov ebp,esp
00401033    83EC 14         sub esp,14
00401036    C745 EC 0300000>mov dword ptr ss:[ebp-14],3              ; 从这5行可以看出,在栈里面是按局部变量从小到大排的
0040103D    C745 F0 0400000>mov dword ptr ss:[ebp-10],4
00401044    C745 F4 0500000>mov dword ptr ss:[ebp-C],5
0040104B    C645 F8 58      mov byte ptr ss:[ebp-8],58               ; 这里从8开始一个char
0040104F    C745 F9 9999000>mov dword ptr ss:[ebp-7],9999            ; 从7 开始, 保持和上面的紧挨着.
00401056    90              nop
00401057    6A 0C           push 0C                                  ; 默认自然对齐为4字节, 总共12字节
00401059    68 30704000     push lesson1.00407030                    ; ASCII "%d   "
0040105E    E8 3D000000     call lesson1.004010A0                    ; 这里是printf, 不知为啥不自己显示出来
00401063    6A 05           push 5                                   ; 这里重设了对齐粒度,于是为5
00401065    68 30704000     push lesson1.00407030                    ; ASCII "%d   "
0040106A    E8 31000000     call lesson1.004010A0                    ; printf
0040106F    8B45 F0         mov eax,dword ptr ss:[ebp-10]            ; sb.b
00401072    8B4D EC         mov ecx,dword ptr ss:[ebp-14]            ; sb.a
00401075    83C4 10         add esp,10                               ; 两次的放一起平衡esp.
00401078    50              push eax
00401079    51              push ecx
0040107A    8D4D EC         lea ecx,dword ptr ss:[ebp-14]            ; sb的地址.  相当于寄存器传参.
0040107D    E8 7EFFFFFF     call lesson1.00401000                    ; sb.add(sb.a,sb.b);
00401082    6A 04           push 4
00401084    8D4D EC         lea ecx,dword ptr ss:[ebp-14]            ; sb的地址.
00401087    E8 84FFFFFF     call lesson1.00401010                    ; sb.f(4);
0040108C    8D4D F8         lea ecx,dword ptr ss:[ebp-8]             ; sf的地址
0040108F    E8 8CFFFFFF     call lesson1.00401020                    ; sf.fuck
00401094    33C0            xor eax,eax
00401096    8BE5            mov esp,ebp
00401098    5D              pop ebp
00401099    C3              retn

//
00401000    8B4424 08       mov eax,dword ptr ss:[esp+8]             ; 这里没压ebp, 所以为参数2 : sb.b
00401004    8B5424 04       mov edx,dword ptr ss:[esp+4]             ; 参数一: sb.a
00401008    03D0            add edx,eax
0040100A    8951 08         mov dword ptr ds:[ecx+8],edx             ; 这里ecx就定位了那个sb结构体了.
0040100D    C2 0800         retn 8

枚举类型就很简单了, 跟BOOL一样了, sizeof就是4了, 然后使用中就直接被编译器换成了
数值了,比如 EE ee = red;  直接就是mov dword ptr [ebp – index * 4] , 3 ; 另外还有联合体,像枚举联合体这些东西在反汇编代码中根本都看不到影子的,这些并不花哨的“花哨”东西(别绕住了啊,哈哈)在反汇编中消失的无影无踪了。 所以我就不做多介绍了。

因为这是系列之(一),就不介绍太多内容,在余下的分节我们慢慢来了解。 那么今次就到这里吧。


[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 7
支持
分享
最新回复 (13)
雪    币: 442
活跃值: (43)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
楼主下回用code把你的代码框起来
2009-5-1 12:08
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
C++确实很高效!不过个人感觉C#更好用一些
2009-5-1 16:17
0
雪    币: 1450
活跃值: (35)
能力值: (RANK:680 )
在线值:
发帖
回帖
粉丝
4
LS的是搞.NET的吧,呵呵。
你要知道好用的东西并不一定就是最好的东西。

我两年前搞的也是C#,做ASP.NET开发,做完几个项目后就没沾.NET了。
个人认为目前C#还无法和C++比较。虽然托管代码势头现在越来越猛,但在某些方面还是必须得
使用本机代码。
2009-5-1 21:24
0
雪    币: 315
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
哦~~跟帖学习~嚎~
2009-5-2 08:47
0
雪    币: 457
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
感谢基础知识的讲解
2009-5-2 09:29
0
雪    币: 239
活跃值: (160)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
把局部类变成全局类,再看看代码,加深理解

#include <stdio.h>
#pragma pack(1)
struct F
{
  char a;
  int  b;
  void fuck();
};
#pragma pack()
struct B
{
  int a;
  int b;
  int c;
  void  add(int,int);
  void f(int);
};

void B::add(int a,int b)
{
  c = a + b ;
}
void B::f(int x)
{
  a += x;
}

void F::fuck()
{
  this->a = 'J' ;
  this->b++;
}

enum EE
{
  red  =3,
  blue ,
  green,
  yellow,
};

B sb; F sf; EE ee;                //把局部类变成全局类,再看看代码,加深理解

int main(int argc, char* argv[])
{
  sb.a = 3;sb.b = 4;sb.c = 5;
  sf.a = 'X' ; sf.b = 0x9999;
  sf.b = green;        

  __asm int 3      //跟failwest学的, 呵呵, 感觉不错.
  printf("%d   ",sizeof(sb));
  printf("%d   ",sizeof(sf));
  printf("%d   ",sizeof(ee));
  sb.add(sb.a,sb.b);
  sb.f(4);
  sf.fuck();

  return 0;
}

00401030    C705 E0984000 0>mov     dword ptr [4098E0], 3        //全局类的成员赋值
0040103A    C705 E4984000 0>mov     dword ptr [4098E4], 4
00401044    C705 E8984000 0>mov     dword ptr [4098E8], 5
0040104E    C605 F0984000 5>mov     byte ptr [4098F0], 58
00401055    C705 F1984000 0>mov     dword ptr [4098F1], 5
0040105F    CC              int3
00401060    6A 0C           push    0C
00401062    68 30704000     push    00407030                         ; ASCII "%d   "
00401067    E8 54000000     call    004010C0
0040106C    6A 05           push    5
0040106E    68 30704000     push    00407030                         ; ASCII "%d   "
00401073    E8 48000000     call    004010C0
00401078    6A 04           push    4
0040107A    68 30704000     push    00407030                         ; ASCII "%d   "
0040107F    E8 3C000000     call    004010C0
00401084    A1 E4984000     mov     eax, [4098E4]                //全局类取值
00401089    8B0D E0984000   mov     ecx, [4098E0]
0040108F    83C4 18         add     esp, 18
00401092    50              push    eax
00401093    51              push    ecx                              ; test.004070B8
00401094    B9 E0984000     mov     ecx, 004098E0                //全局类地址
00401099    E8 62FFFFFF     call    00401000
0040109E    6A 04           push    4
004010A0    B9 E0984000     mov     ecx, 004098E0
004010A5    E8 66FFFFFF     call    00401010
004010AA    B9 F0984000     mov     ecx, 004098F0
004010AF    E8 6CFFFFFF     call    00401020
004010B4    33C0            xor     eax, eax
004010B6    C3              retn
2009-5-2 22:45
0
雪    币: 200
活跃值: (122)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
C++也好,C#也好,都有各自的优点和缺点,尺有所长,寸有所短,关键要看用于什么
感谢楼主提供这么好的基础教学
2009-5-2 23:09
0
雪    币: 1450
活跃值: (35)
能力值: (RANK:680 )
在线值:
发帖
回帖
粉丝
9
非常感谢dssz的指点。
说实话这个系列能得到优秀真的是有点让我受宠若惊,它差优秀还是有距离的,我想版主更多的是对我的鼓励。
说是教学,不如说是交流,因为在这方面我的经验也很少。
就像刚才dssz指出的,我就完全没考虑到。

谢谢大家的支持以及对文中的错误及不足的指正 。
2009-5-2 23:49
0
雪    币: 9
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
跟着楼主逆向分析C++
2009-5-3 04:13
0
雪    币: 201
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
很适合像我等新手学习! LZ太过谦虚了!
2009-5-3 10:26
0
雪    币: 153
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
当你在反汇编代码中看到某些call在调用前都会有个ecx作为参数传进去(这里IDE假定为VC,因为Borland不是用ecx而是通过堆栈来传递this指针的)???

这是因为调用方式不同。Borland用的是fastcall。你在VC中用void WINAPI B::add(int a,int b)看看。
2009-5-3 14:38
0
雪    币: 1657
活跃值: (291)
能力值: ( LV9,RANK:610 )
在线值:
发帖
回帖
粉丝
13
LZ这样的方法只适用debug,release不是这样的的,所有的变量是需要的时候才初始化,不需要的根本就不编译进去,这个取决主MAIN代码

还有一看这样的就确定ECX是this指针
00401082    6A 04           push 4
00401084    8D4D EC         lea ecx,dword ptr ss:[ebp-14]            ; sb的地址.
00401087    E8 84FFFFFF     call lesson1.00401010                    ; sb.f(4);

至于何时用mov 何时用lea,LZ自己探索好了,我不说了~~^_^

00401036    C745 EC 0300000>mov dword ptr ss:[ebp-14],3              ; 从这5行可以看出,在栈里面是按局部变量从小到大排的
0040103D    C745 F0 0400000>mov dword ptr ss:[ebp-10],4
00401044    C745 F4 0500000>mov dword ptr ss:[ebp-C],5
0040104B    C645 F8 58      mov byte ptr ss:[ebp-8],58               ; 这里从8开始一个char

LZ这个排序不一定的,有的时候会变一下的~!在debug中多半都会这样,但在release中不一定,debug也不一定的~!

LZ的贴不错,希望继续,我已经学习完了~~!
LZ的一个地方是错的asm->c->c++,这个是不对的~~!
asm->c++
asm->delphi
asm->c
这个是根据原代码来确定的,不一定非要先C.

2009-5-3 15:13
0
雪    币: 237
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
14
不错的文章学习一下。
2009-5-3 15:21
0
游客
登录 | 注册 方可回帖
返回
//