• 程序的结构有顺序结构、选择结构、循环结构和分支结构。接下来看看分支结构switch。
    在控制台工程ida4中通过项目设置把所有.cpp文件都Exclude出去。再新建一个C++源文件switch.cpp,内容为:

    #include <stdio.h>

    bool isleap(int y)
    {
        return (y%4==0&&y%100!=0||y%400==0);
    }
    void main()
    {
        int year;
        int month;
        printf("input year and month:");
        scanf("%d%d", &year, &month);
        switch(month){
        case 1:
        case 3:
        case 5:
        case 7:
        case 8:
        case 10:
        case 12:
            puts("31 days in the month.");
            break;
        case 4:
        case 6:
        case 9:
        case 11:
            puts("30 days in the month.");
            break;
        case 2:
            printf("%d days in the month.\n", 28+isleap(year));
            break;
        default:
            puts("invalid month!");
        }
        puts("finished");
    }

    在Debug模式下用F7构建成可执行文件ida4.exe,然后用IDA打开ida4.exe。
    为了观察有没有break的区别,我有的case后面有break有的后面没有。
    加载完毕之后,在文本形式下,把变量var_4改名为year,把变量var_8改名为month,把变量var_C改名为temp。再来看代码。

    .text:004010A0 main            proc near               ; CODE XREF: _mainj
    .text:004010A0
    .text:004010A0 var_4C          = byte ptr -4Ch
    .text:004010A0 temp            = dword ptr -0Ch
    .text:004010A0 month           = dword ptr -8
    .text:004010A0 year            = dword ptr -4
    .text:004010A0
    .text:004010A0                 push    ebp
    .text:004010A1                 mov     ebp, esp
    .text:004010A3                 sub     esp, 4Ch
    .text:004010A6                 push    ebx
    .text:004010A7                 push    esi
    .text:004010A8                 push    edi
    .text:004010A9                 lea     edi, [ebp+var_4C]
    .text:004010AC                 mov     ecx, 13h
    .text:004010B1                 mov     eax, 0CCCCCCCCh
    .text:004010B6                 rep stosd
    这一段是函数头。

    .text:004010B8                 push    offset aInputYearAndMo ; "input year and month:"
    .text:004010BD                 call    printf
    .text:004010C2                 add     esp, 4
    .text:004010C5                 lea     eax, [ebp+month]
    .text:004010C8                 push    eax
    .text:004010C9                 lea     ecx, [ebp+year]
    .text:004010CC                 push    ecx
    .text:004010CD                 push    offset aDD      ; "%d%d"
    .text:004010D2                 call    scanf
    .text:004010D7                 add     esp, 0Ch
    这一段让用户输入年和月保存到year和month中。

    .text:004010DA                 mov     edx, [ebp+month]
    .text:004010DD                 mov     [ebp+temp], edx
    .text:004010E0                 mov     eax, [ebp+temp]
    .text:004010E3                 sub     eax, 1
    .text:004010E6                 mov     [ebp+temp], eax
    这一段是switch结构的开始,根据month计算跳转表的下标保存到temp中。

    .text:004010E9                 cmp     [ebp+temp], 0Bh
    .text:004010ED                 ja      short loc_40113B
    这一段用temp跟0Bh(跳转表的最大下标)进行无符号大小比较,超过了就跳转到default。

    .text:004010EF                 mov     ecx, [ebp+temp]
    .text:004010F2                 jmp     ds:off_401166[ecx*4]
    这一段根据temp转到跳转表中相应项指定的地方。off_401166是跳转表的地址。双击它可以到跳转表,跳转表中都是一些代码入口偏移量,如图:

    .text:004010F9
    .text:004010F9 loc_4010F9:                             ; DATA XREF: .text:off_401166o
    .text:004010F9                                         ; .text:0040116Eo ...
    .text:004010F9                 push    offset a31DaysInTheMon ; "31 days in the month."
    .text:004010FE                 call    puts
    .text:00401103                 add     esp, 4
    .text:00401106                 jmp     short loc_401148
    .text:00401108 ; ---------------------------------------------------------------------------
    这一段调用puts输出"31 days in the month.",然后跳转到整个switch结构之后。

    .text:00401108
    .text:00401108 loc_401108:                             ; CODE XREF: main+52j
    .text:00401108                                         ; DATA XREF: .text:00401172o ...
    .text:00401108                 push    offset a30DaysInTheMon ; "30 days in the month."
    .text:0040110D                 call    puts
    .text:00401112                 add     esp, 4
    .text:00401115                 jmp     short loc_401148
    .text:00401117 ; ---------------------------------------------------------------------------
    这一段调用puts输出"30 days in the month.",然后跳转到整个switch结构之后。

    .text:00401117
    .text:00401117 loc_401117:                             ; CODE XREF: main+52j
    .text:00401117                                         ; DATA XREF: .text:0040116Ao
    .text:00401117                 mov     edx, [ebp+year]
    .text:0040111A                 push    edx
    .text:0040111B                 call    j_isleap
    .text:00401120                 add     esp, 4
    这一段调用isleap计算year是否是闰年,结果1或者0保存在al中。

    .text:00401123                 and     eax, 0FFh
    .text:00401128                 add     eax, 1Ch
    .text:0040112B                 push    eax
    .text:0040112C                 push    offset aDDaysInTheMont ; "%d days in the month.\n"
    .text:00401131                 call    printf
    .text:00401136                 add     esp, 8
    .text:00401139                 jmp     short loc_401148
    .text:0040113B ; ---------------------------------------------------------------------------
    这一段根据al中的布尔值加上28得到这个月的天数,调用printf输出"%d days in the month.\n",然后跳转到整个switch结构之后。

    .text:0040113B
    .text:0040113B loc_40113B:                             ; CODE XREF: main+4Dj
    .text:0040113B                 push    offset aInvalidMonth ; "invalid month!"
    .text:00401140                 call    puts
    .text:00401145                 add     esp, 4
    这一段是switch结构中的default部分,调用puts输出"invalid month!"。
    switch结构到这里结束。

    .text:00401148
    .text:00401148 loc_401148:                             ; CODE XREF: main+66j
    .text:00401148                                         ; main+75j ...
    .text:00401148                 push    offset aFinished ; "finished"
    .text:0040114D                 call    puts
    .text:00401152                 add     esp, 4
    .text:00401155                 pop     edi
    .text:00401156                 pop     esi
    .text:00401157                 pop     ebx
    .text:00401158                 add     esp, 4Ch
    .text:0040115B                 cmp     ebp, esp
    .text:0040115D                 call    __chkesp
    .text:00401162                 mov     esp, ebp
    .text:00401164                 pop     ebp
    .text:00401165                 retn
    .text:00401165 main            endp
    这一段调用puts输出"finished",检查堆栈,main函数结束。

    咱们注意看IDA View-A文字形式左侧边的箭头。如图。

    这是有史以来最大的一张图,我用拼接的办法做的,为的是看到箭头的全貌。
    从图里可以看到switch结构的箭头跟前面的都不一样。
    1号箭头是有default的情况的处理箭头,实际上跟if/else一样。如果没有default应该就没有这个箭头。
    2号箭头是一个起点多个终点,这就是跳转表,是switch结构的特征箭头。
    /*************************************************************
    老大的视频教程上的例子,因为case比较少,编译器没有用跳转表,用的是一系列的if/else来实现,所以没有这种特征。
    *************************************************************/
    3号箭头是多个起点一个终点,这是break的特征。

    转到图形形式,可以看到分块的程序汇编代码,用右键菜单Group nodes功能把每个模块换成跟汇编指令对应的C源程序语句,调整条件判断与颜色一致。
    完成后程序的流程图为:

    注意图中我用椭圆标记的部分,那就是根据跳转表跳转的控制。

    用传说中的F5来看看。如图。

     

    虽然得到的代码有些错误(对printf和scanf的调用),但基本上能还原switch结构,不过会把default处理成if/else。

  • C++程序的基本语法已经大体了解了,该进入面向对象的部分了。今天来初识对象。
    在控制台工程ida4中通过项目设置把所有.cpp文件都Exclude出去。再新建一个C++源文件object.cpp,内容为:

    #include <iostream>
    using namespace std;
    #include <cstring>

    class Person{
        char name[20];
        int age;
    public:
        Person(char const* name, int age):age(age){
            strcpy(this->name, name);
        }
        void show(){
            cout << "I am " << name << ", I am born in " << 2009-age << endl;
        }
        void welcome(Person const& other){
            cout << "Hello " << other.name << ", I am " << name << ", nice to see you!" << endl;
        }
    };
    int main()
    {
        Person niu("Voldemort", 25);
        Person chen("ChenZongquan", 36);
        chen.show();
        chen.welcome(niu);
        return 0;
    }

    这次完全按照标准C++的格式来写了,大家可能注意到头文件和main函数已经变了。没关系,咱们专注到对象上。
    在Debug模式下用F7构建成可执行文件ida4.exe,然后用IDA打开ida4.exe。

    加载完毕之后,在文本形式下,把变量var_30改名为chen,把变量var_18改名为niu,把立即数19h和24h分别改成十进制的25和36。再来看main函数的代码,看看是如何创建对象和调用对象成员函数的。

    .text:00401590 main            proc near               ; CODE XREF: _mainj
    .text:00401590
    .text:00401590 var_70          = byte ptr -70h
    .text:00401590 chen            = byte ptr -30h
    .text:00401590 niu             = byte ptr -18h
    .text:00401590
    .text:00401590                 push    ebp
    .text:00401591                 mov     ebp, esp
    .text:00401593                 sub     esp, 70h
    .text:00401596                 push    ebx
    .text:00401597                 push    esi
    .text:00401598                 push    edi
    .text:00401599                 lea     edi, [ebp+var_70]
    .text:0040159C                 mov     ecx, 1Ch
    .text:004015A1                 mov     eax, 0CCCCCCCCh
    .text:004015A6                 rep stosd
    这一段是函数的栈初始化代码,跟以往的一样。

    .text:004015A8                 push    25
    .text:004015AA                 push    offset aVoldemort ; "Voldemort"
    .text:004015AF                 lea     ecx, [ebp+niu]
    .text:004015B2                 call    j_Person__Person
    这一段是对应代码“Person niu("Voldemort", 25)”,创建对象niu,可以看到它在调用构造函数Person::Person。注意除了把两个参数25和"Voldemort"入栈外,它还把变量niu的地址存到了寄存器ecx中。等会儿到构造函数中再看这个ecx的作用,推测是到成员函数中当成this用。

    .text:004015B7                 push    36
    .text:004015B9                 push    offset aChenzongquan ; "ChenZongquan"
    .text:004015BE                 lea     ecx, [ebp+chen]
    .text:004015C1                 call    j_Person__Person
    这一段则是创建另一个对象chen,也是把参数入栈,把地址存到了ecx中。

    .text:004015C6                 lea     ecx, [ebp+chen]
    .text:004015C9                 call    j_Person__show
    这一段是“chen.show()”,调用对象chen的成员函数show,机器指令的操作是把chen的地址存入ecx中后调用的Person::show函数。

    .text:004015CE                 lea     eax, [ebp+niu]
    .text:004015D1                 push    eax
    .text:004015D2                 lea     ecx, [ebp+chen]
    .text:004015D5                 call    j_Person__welcome
    这一段是“chen.welcome(niu)”,先是把参数niu的地址入栈,再把chen的地址保存到ecx中,调用Person::welcome函数。
    /**********************************************************
    可以看出对引用形参传递的实参是地址,实质上是用指针实现的。
    **********************************************************/

    .text:004015DA                 xor     eax, eax
    .text:004015DC                 pop     edi
    .text:004015DD                 pop     esi
    .text:004015DE                 pop     ebx
    .text:004015DF                 add     esp, 70h
    .text:004015E2                 cmp     ebp, esp
    .text:004015E4                 call    __chkesp
    .text:004015E9                 mov     esp, ebp
    .text:004015EB                 pop     ebp
    .text:004015EC                 retn
    .text:004015EC main            endp
    这一段对应“return 0”,跟原来void main的时候比多了一句xor eax, eax,也就是把eax置0作为main函数的返回值,然后检查堆栈,main函数结束。
    /******************************************************
    整个main函数到这里结束了,对象niu和chen应该释放了,但我们并没有看到传说中的默认析构函数的调用。这意味着编译器只是在必须的时候才为类产生默认的析构函数。
    ******************************************************/

    ----------------------------------------
    再看看几个成员函数。
    先来看构造函数。找到那个函数,把变量var_4改成this,变量arg_0改成arg_name,arg_4改名arg_age,把立即数14h改成20,代码如下:

    .text:00401610 Person__Person  proc near               ; CODE XREF: j_Person__Personj
    .text:00401610
    .text:00401610 var_44          = byte ptr -44h
    .text:00401610 this            = dword ptr -4
    .text:00401610 arg_name        = dword ptr  8
    .text:00401610 arg_age         = dword ptr  0Ch
    .text:00401610
    .text:00401610                 push    ebp
    .text:00401611                 mov     ebp, esp
    .text:00401613                 sub     esp, 44h
    .text:00401616                 push    ebx
    .text:00401617                 push    esi
    .text:00401618                 push    edi
    .text:00401619                 push    ecx
    .text:0040161A                 lea     edi, [ebp+var_44]
    .text:0040161D                 mov     ecx, 11h
    .text:00401622                 mov     eax, 0CCCCCCCCh
    .text:00401627                 rep stosd
    这一段是函数的栈初始化代码,略过。

    .text:00401629                 pop     ecx
    .text:0040162A                 mov     [ebp+this], ecx
    这一段把前面放在ecx中的对象地址取出来保存到函数中的局部变量this中。

    .text:0040162D                 mov     eax, [ebp+this]  ;eax=this
    .text:00401630                 mov     ecx, [ebp+arg_age] ;ecx=arg_age
    .text:00401633                 mov     [eax+20], ecx  ;this->age=ecx
    这一段处理初始化列表,[eax+20]表示这个对象的age成员,其中20是age成员的偏移量。把形参age的值传送到成员变量age中。

    .text:00401636                 mov     edx, [ebp+arg_name] ;edx=arg_name
    .text:00401639                 push    edx   
    .text:0040163A                 mov     eax, [ebp+this]  ;eax=this->name,因为成员name的偏移为0,所以没有写。
    .text:0040163D                 push    eax
    .text:0040163E                 call    strcpy
    .text:00401643                 add     esp, 8   ;恢复堆栈
    .text:00401646                 mov     eax, [ebp+this]  ;以this为返回值。
    这一段是“strcpy(this->name, name)”,调用strcpy把形参name的字符串复制到成员name的字符串中。
    /********************************************************
    因为strcpy是声明为__cdecl的,所以这个函数由调用方来恢复堆栈。
    构造函数以this为返回值,有点不好理解。构造函数不是没有返回值吗?
    ********************************************************/

    .text:00401649                 pop     edi
    .text:0040164A                 pop     esi
    .text:0040164B                 pop     ebx
    .text:0040164C                 add     esp, 44h
    .text:0040164F                 cmp     ebp, esp
    .text:00401651                 call    __chkesp
    .text:00401656                 mov     esp, ebp
    .text:00401658                 pop     ebp
    .text:00401659                 retn    8 ;自行恢复堆栈。
    .text:00401659 Person__Person  endp
    这一段是构造函数结束,自行恢复堆栈。

    ----------------------------------------
    再看看成员函数show()。这个函数就一句话:
            cout << "I am " << name << ", I am born in " << 2009-age << endl;
    找到那个函数,把变量var_4改成this,把loc_4010CD改成endl,把立即数7d9h改成2009,代码如下:

    .text:00401670 Person__show    proc near               ; CODE XREF: j_Person__showj
    .text:00401670
    .text:00401670 var_44          = byte ptr -44h
    .text:00401670 this            = dword ptr -4
    .text:00401670
    .text:00401670                 push    ebp
    .text:00401671                 mov     ebp, esp
    .text:00401673                 sub     esp, 44h
    .text:00401676                 push    ebx
    .text:00401677                 push    esi
    .text:00401678                 push    edi
    .text:00401679                 push    ecx
    .text:0040167A                 lea     edi, [ebp+var_44]
    .text:0040167D                 mov     ecx, 11h
    .text:00401682                 mov     eax, 0CCCCCCCCh
    .text:00401687                 rep stosd
    这一段是函数的栈初始化代码,略过。

    .text:00401689                 pop     ecx
    .text:0040168A                 mov     [ebp+this], ecx
    这一段设置this,跟前面的构造函数一样。所有非静态成员函数应该都会有这个。

    .text:0040168D                 push    offset endl ;endl入栈
    .text:00401692                 mov     eax, [ebp+this]
    .text:00401695                 mov     ecx, 2009
    .text:0040169A                 sub     ecx, [eax+14h] ;计算2009-this->age
    .text:0040169D                 push    ecx  ;把计算结果入栈
    .text:0040169E                 push    offset aIAmBornIn;把", I am born in "入栈
    .text:004016A3                 mov     edx, [ebp+this] 
    .text:004016A6                 push    edx  ;把this->name入栈
    .text:004016A7                 push    offset aIAm      ;把"I am "入栈
    .text:004016AC                 push    offset std__cout ;把cout入栈
    .text:004016B1                 call    j_std__operator__;调operator<<输出“I am”,返回值为cout在eax中
    .text:004016B6                 add     esp, 8  ;恢复堆栈
    .text:004016B9                 push    eax  ;把cout入栈
    .text:004016BA                 call    j_std__operator__;调operator<<输出this->name,返回值为cout在eax中
    .text:004016BF                 add     esp, 8  ;恢复堆栈
    .text:004016C2                 push    eax  ;把cout入栈
    .text:004016C3                 call    j_std__operator__;调operator<<输出“, I am born in ”,返回值为cout在eax中
    .text:004016C8                 add     esp, 8  ;恢复堆栈
    .text:004016CB                 mov     ecx, eax  ;把cout入栈
    .text:004016CD                 call    sub_401104 ;调operator<<输出2009-this->age的结果,返回值为cout在eax中
    .text:004016D2                 mov     ecx, eax  ;把cout入栈
    .text:004016D4                 call    j_std__basic_ostream_char_std__char_traits_char_____operator__;调operator<<输出endl
    这一段是执行输出语句,编译器对连续的输出采用了一种特殊的处理办法:把要输出的全部数据按相反的顺序计算、入栈,然后反复调用相应的输出运算符函数来输出,每次输出之后下次输出之前都把上次输出的返回值cout(在eax中)传给输出运算符函数。

    .text:004016D9                 pop     edi
    .text:004016DA                 pop     esi
    .text:004016DB                 pop     ebx
    .text:004016DC                 add     esp, 44h
    .text:004016DF                 cmp     ebp, esp
    .text:004016E1                 call    __chkesp
    .text:004016E6                 mov     esp, ebp
    .text:004016E8                 pop     ebp
    .text:004016E9                 retn
    .text:004016E9 Person__show    endp
    这一段是show函数结束。

    ----------------------------------------
    最后看看成员函数welcome(Person const& other)。这个函数就一句话:
            cout << "Hello " << other.name << ", I am " << name << ", nice to see you!" << endl;
    找到那个函数,把变量var_4改成this,把arg_0改成other,把立即数7d9h改成2009,代码如下:

    .text:00401710 Person__welcome proc near               ; CODE XREF: j_Person__welcomej
    .text:00401710
    .text:00401710 var_44          = byte ptr -44h
    .text:00401710 this            = dword ptr -4
    .text:00401710 other           = dword ptr  8
    .text:00401710
    .text:00401710                 push    ebp
    .text:00401711                 mov     ebp, esp
    .text:00401713                 sub     esp, 44h
    .text:00401716                 push    ebx
    .text:00401717                 push    esi
    .text:00401718                 push    edi
    .text:00401719                 push    ecx
    .text:0040171A                 lea     edi, [ebp+var_44]
    .text:0040171D                 mov     ecx, 11h
    .text:00401722                 mov     eax, 0CCCCCCCCh
    .text:00401727                 rep stosd
    这一段是函数的栈初始化代码,略过。

    .text:00401729                 pop     ecx
    .text:0040172A                 mov     [ebp+this], ecx
    这一段设置this,跟前面的构造函数和show函数一样。

    .text:0040172D                 push    offset endl  ;endl入栈
    .text:00401732                 push    offset aNiceToSeeYou  ;", nice to see you!"入栈
    .text:00401737                 mov     eax, [ebp+this]  ;this->name入栈
    .text:0040173A                 push    eax 
    .text:0040173B                 push    offset aIAm_0     ;", I am "入栈
    .text:00401740                 mov     ecx, [ebp+other]  ;other->name入栈
    .text:00401743                 push    ecx
    .text:00401744                 push    offset aHello     ;"Hello "入栈
    .text:00401749                 push    offset std__cout  ;cout入栈
    .text:0040174E                 call    j_std__operator__ ;调用输出运算符函数输出“Hello ”
    .text:00401753                 add     esp, 8
    .text:00401756                 push    eax   ;cout入栈
    .text:00401757                 call    j_std__operator__ ;调用输出运算符函数输出other->name
    .text:0040175C                 add     esp, 8
    .text:0040175F                 push    eax   ;cout入栈
    .text:00401760                 call    j_std__operator__ ;调用输出运算符函数输出“, I am ”
    .text:00401765                 add     esp, 8
    .text:00401768                 push    eax   ;cout入栈
    .text:00401769                 call    j_std__operator__ ;调用输出运算符函数输出this->name
    .text:0040176E                 add     esp, 8
    .text:00401771                 push    eax   ;cout入栈
    .text:00401772                 call    j_std__operator__ ;调用输出运算符函数输出“, nice to see you!”
    .text:00401777                 add     esp, 8
    .text:0040177A                 mov     ecx, eax   ;cout入栈
    .text:0040177C                 call    j_std__basic_ostream_char_std__char_traits_char_____operator__;输出endl
    这一段调用输出运算符函数来输出一系列数据。

    .text:00401781                 pop     edi
    .text:00401782                 pop     esi
    .text:00401783                 pop     ebx
    .text:00401784                 add     esp, 44h
    .text:00401787                 cmp     ebp, esp
    .text:00401789                 call    __chkesp
    .text:0040178E                 mov     esp, ebp
    .text:00401790                 pop     ebp
    .text:00401791                 retn    4
    .text:00401791 Person__welcome endp
    这一段是welcome函数结束,自行恢复堆栈。

    这一次尝试的体会,基本上所谓的面向对象,也就是在创建对象时会调用构造函数,调用成员函数前把对象的地址保存到寄存器ecx中,到成员函数中再把ecx中的对象地址转存到var_4也就是this中。
    另一个就是发现cout连续输出的处理跟printf输出数据的方式很类似,按从右向左的方向把数据全部入栈之后才开始输出。
    这一次没有处理虚函数的问题,下一次再深入。

  • 续补

    ; 为用rep movsd指令传递结构变量做准备(20h/4=8)
    .text:00401300 lea     esi, [esp+0BCh+s_name_0]
    .text:00401304 lea     edi, [esp+0BCh+r]               ; 准备把Person结构变量s(也就是big的返回值)复制到结构变量r中
    .text:0040130B push    eax                             ; 把字符ch入栈准备输出
    .text:0040130C rep movsd                               ; 复制结构变量s到r
    这一段把刚才保存的s复制到结构变量r中,中间插入了一个push eax是后面的输出函数用的。

    .text:0040130E push    offset cout_addr                ; 把对象cout地地址入栈
    .text:00401313 call    operator_output_char
    .text:00401318 add     esp, 4Ch
    .text:0040131B mov     esi, eax                        ; eax中是operator<<(cout,ch)的返回值,cout的地址
    .text:0040131D mov     ecx, esi                        ; 把cout的地址放到ecx中准备传给this
    .text:0040131F push    0Ah                             ; 换行符入栈准备输出(endl被直接处理成了换行符)
    .text:00401321 call    cout_operator_output_char       ; 调用cout.operator<<输出换行符
    这一段输出字符变量ch,直接用的保存在eax中的数据,没有重新从内存中读取变量ch,这也是编译器的优化。另外输出endl被直接变成了输出换行符'\n',同样是编译器的优化结果。

    .text:00401326 mov     ecx, [esi]                      ; 以下部分为异常处理
    .text:00401328 xor     edi, edi
    .text:0040132A mov     edx, [ecx+4]
    .text:0040132D mov     cl, [edx+esi+4]
    .text:00401331 test    cl, 6
    .text:00401334 lea     eax, [edx+esi]
    .text:00401337 jnz     short loc_40134D
    .text:00401339 mov     eax, [eax+28h]
    .text:0040133C mov     ecx, eax
    .text:0040133E mov     edx, [eax]
    .text:00401340 call    dword ptr [edx+2Ch]
    .text:00401343 cmp     eax, 0FFFFFFFFh
    .text:00401346 jnz     short loc_40134D
    .text:00401348 mov     edi, 4
    .text:0040134D
    .text:0040134D loc_40134D:                             ; CODE XREF: _main+147j
    .text:0040134D                                         ; _main+156j
    .text:0040134D mov     eax, [esi]
    .text:0040134F mov     ecx, [eax+4]
    .text:00401352 add     ecx, esi
    .text:00401354 test    edi, edi
    .text:00401356 jz      short loc_40136E
    .text:00401358 mov     eax, [ecx+4]
    .text:0040135B mov     edx, [ecx+28h]
    .text:0040135E or      eax, edi
    .text:00401360 test    edx, edx
    .text:00401362 jnz     short loc_401366
    .text:00401364 or      al, 4
    .text:00401366
    .text:00401366 loc_401366:                             ; CODE XREF: _main+172j
    .text:00401366 push    0
    .text:00401368 push    eax
    .text:00401369 call    ?clear@ios_base@std@@QAEXH_N@Z  ; std::ios_base::clear(int,bool)
    这一段是输出异常情况的检查和处理,我没有去分析,如果有充分的时间我会重新去分析的。

    .text:0040136E
    .text:0040136E loc_40136E:                             ; CODE XREF: _main+166j
    .text:0040136E push    ebx                             ; 把在.text:004012B5 mov ebx, eax指令用ebx保存的调用mul函数的返回值num入栈
    .text:0040136F mov     ecx, offset cout_addr           ; cout的地址入栈
    .text:00401374 call    operator_output_int             ; 输出整数num,返回值为cout的地址保存在eax中
    .text:00401379 mov     esi, eax
    .text:0040137B push    0Ah                             ; 换行符入栈(代替endl)
    .text:0040137D mov     ecx, esi                        ; 用ecx保存上一个输出函数返回的cout地址准备传递给this
    .text:0040137F call    cout_operator_output_char       ; 输出换行符
    这一段输出num的值,经过编译器优化同样是直接用保存在ebx中的数据而没有重新读取内存中的num,而且endl也直接处理成了'\n'。

    .text:00401384 mov     ecx, [esi]                      ; 以下部分为异常处理
    .text:00401386 xor     edi, edi
    .text:00401388 mov     edx, [ecx+4]
    .text:0040138B mov     cl, [edx+esi+4]
    .text:0040138F test    cl, 6
    .text:00401392 lea     eax, [edx+esi]
    .text:00401395 jnz     short loc_4013AB
    .text:00401397 mov     eax, [eax+28h]
    .text:0040139A mov     ecx, eax
    .text:0040139C mov     edx, [eax]
    .text:0040139E call    dword ptr [edx+2Ch]
    .text:004013A1 cmp     eax, 0FFFFFFFFh
    .text:004013A4 jnz     short loc_4013AB
    .text:004013A6 mov     edi, 4
    .text:004013AB
    .text:004013AB loc_4013AB:                             ; CODE XREF: _main+1A5j
    .text:004013AB                                         ; _main+1B4j
    .text:004013AB mov     eax, [esi]
    .text:004013AD mov     ecx, [eax+4]
    .text:004013B0 add     ecx, esi
    .text:004013B2 test    edi, edi
    .text:004013B4 jz      short loc_4013CC
    .text:004013B6 mov     eax, [ecx+4]
    .text:004013B9 mov     edx, [ecx+28h]
    .text:004013BC or      eax, edi
    .text:004013BE test    edx, edx
    .text:004013C0 jnz     short loc_4013C4
    .text:004013C2 or      al, 4
    .text:004013C4
    .text:004013C4 loc_4013C4:                             ; CODE XREF: _main+1D0j
    .text:004013C4 push    0
    .text:004013C6 push    eax
    .text:004013C7 call    ?clear@ios_base@std@@QAEXH_N@Z  ; std::ios_base::clear(int,bool)
    这一段又是输出异常的检查和处理。

    .text:004013CC
    .text:004013CC loc_4013CC:                             ; CODE XREF: _main+1C4j
    .text:004013CC mov     ecx, dword ptr [esp+78h+res+4]
    .text:004013D0 mov     edx, dword ptr [esp+78h+res]
    .text:004013D4 push    ecx                             ; 把res的高4字节入栈
    .text:004013D5 push    edx                             ; 把res的低4字节入栈
    .text:004013D6 mov     ecx, offset cout_addr           ; cout的地址入栈
    .text:004013DB call    operator_output_double          ; 输出res中的小数
    .text:004013E0 mov     esi, eax
    .text:004013E2 push    0Ah
    .text:004013E4 mov     ecx, esi
    .text:004013E6 call    cout_operator_output_char       ; 输出换行符
    这一段输出double变量res中的值。变量值不在寄存器中,编译器从内存中分两次读取了res的8个字节数据入栈,再把cout的地址入栈,调用operator<<(cout,double),再输出换行符。

    .text:004013EB mov     eax, [esi]                      ; 以下为异常处理
    .text:004013ED xor     edi, edi
    .text:004013EF mov     ecx, [eax+4]
    .text:004013F2 lea     eax, [ecx+esi]
    .text:004013F5 mov     cl, [ecx+esi+4]
    .text:004013F9 test    cl, 6
    .text:004013FC jnz     short loc_401412
    .text:004013FE mov     eax, [eax+28h]
    .text:00401401 mov     ecx, eax
    .text:00401403 mov     edx, [eax]
    .text:00401405 call    dword ptr [edx+2Ch]
    .text:00401408 cmp     eax, 0FFFFFFFFh
    .text:0040140B jnz     short loc_401412
    .text:0040140D mov     edi, 4
    .text:00401412
    .text:00401412 loc_401412:                             ; CODE XREF: _main+20Cj
    .text:00401412                                         ; _main+21Bj
    .text:00401412 mov     eax, [esi]
    .text:00401414 mov     ecx, [eax+4]
    .text:00401417 add     ecx, esi
    .text:00401419 test    edi, edi
    .text:0040141B jz      short loc_401433
    .text:0040141D mov     eax, [ecx+4]
    .text:00401420 mov     edx, [ecx+28h]
    .text:00401423 or      eax, edi
    .text:00401425 test    edx, edx
    .text:00401427 jnz     short loc_40142B
    .text:00401429 or      al, 4
    .text:0040142B
    .text:0040142B loc_40142B:                             ; CODE XREF: _main+237j
    .text:0040142B push    0
    .text:0040142D push    eax
    .text:0040142E call    ?clear@ios_base@std@@QAEXH_N@Z  ; std::ios_base::clear(int,bool)
    这一段又是输出异常的检查和处理。

    .text:00401433
    .text:00401433 loc_401433:                             ; CODE XREF: _main+22Bj
    .text:00401433 lea     ecx, [esp+78h+r]
    .text:00401437 push    ecx                             ; Person结构变量r的地址入栈
    .text:00401438 push    offset cout_addr                ; cout的地址入栈
    .text:0040143D call    operator_output_Person          ; 调用自定义的operator<<(cout,Person)输出r
    .text:00401442 add     esp, 8
    .text:00401445 mov     esi, eax
    .text:00401447 mov     ecx, esi
    .text:00401449 push    0Ah
    .text:0040144B call    cout_operator_output_char       ; 输出换行符
    这一段调用自定义的operator<<函数来输出结构变量r,再输出换行符。由于参数是引用传递,所以没有象调用big函数那样复制结构变量,只传递了结构变量r的地址。

    .text:00401450 mov     edx, [esi]                      ; 以下为异常处理
    .text:00401452 xor     edi, edi
    .text:00401454 mov     eax, [edx+4]
    .text:00401457 add     eax, esi
    .text:00401459 test    byte ptr [eax+4], 6
    .text:0040145D jnz     short loc_401473
    .text:0040145F mov     eax, [eax+28h]
    .text:00401462 mov     ecx, eax
    .text:00401464 mov     edx, [eax]
    .text:00401466 call    dword ptr [edx+2Ch]
    .text:00401469 cmp     eax, 0FFFFFFFFh
    .text:0040146C jnz     short loc_401473
    .text:0040146E mov     edi, 4
    .text:00401473
    .text:00401473 loc_401473:                             ; CODE XREF: _main+26Dj
    .text:00401473                                         ; _main+27Cj
    .text:00401473 mov     eax, [esi]
    .text:00401475 mov     ecx, [eax+4]
    .text:00401478 add     ecx, esi
    .text:0040147A test    edi, edi
    .text:0040147C jz      short loc_401494
    .text:0040147E mov     eax, [ecx+4]
    .text:00401481 mov     edx, [ecx+28h]
    .text:00401484 or      eax, edi
    .text:00401486 test    edx, edx
    .text:00401488 jnz     short loc_40148C
    .text:0040148A or      al, 4
    .text:0040148C
    .text:0040148C loc_40148C:                             ; CODE XREF: _main+298j
    .text:0040148C push    0
    .text:0040148E push    eax
    .text:0040148F call    ?clear@ios_base@std@@QAEXH_N@Z  ; std::ios_base::clear(int,bool)
    这一段又是输出异常的检查和处理。

    .text:00401494
    .text:00401494 loc_401494:                             ; CODE XREF: _main+28Cj
    .text:00401494 pop     edi
    .text:00401495 pop     esi
    .text:00401496 xor     eax, eax                        ; eax清零作为main函数的返回值
    .text:00401498 pop     ebx
    .text:00401499 mov     esp, ebp                        ; 恢复栈寄存器
    .text:0040149B pop     ebp
    .text:0040149C retn
    .text:0040149C _main endp
    main函数到此结束,返回eax中的0。

    关于release模式,总结一下:
    /*************************************************************
    由于不需要调试,没有符号表了,所以在用IDA加载的时候不提示是否加载符号表的问题了。
    调用函数不再是通过一个只有一个jmp指令的函数间接调用了,而是直接调用目标函数了。
    在函数中尽可能地去掉了临时局部变量,不再额外开出0x40个字节的保护性栈空间,不再对局部变量进行默认0xCC初始化。
    对栈空间进行随机访问时不再用ebp作基地址,直接用esp+偏移作基地址。
    指令的顺序可能出于优化的目的被编译器打乱了。
    对变量的访问,如果寄存器中有就直接使用寄存器的,寄存器中没有才重新从内存中读取。
    在输出输出指令之后直接进行了输出输出的异常检查及处理。
    特别有意思的是,在release模式下,cout<<endl直接处理成cout<<'\n'了。
    *************************************************************/

  • 这次咱们进入面向对象:封装、继承和多态。
    新建一个空的控制台工程ida5-1,再新建一个C++源文件object.cpp,内容为:

    #include <iostream>
    using namespace std;
    #include <cstring>

    class Person{
        char name[20];
        int age;
    public:
        Person(char const* name, int age):age(age){
            strcpy(this->name, name);
        }
        virtual void show()const{
            cout << "大家好,我叫" << name << ",今年" << age << endl;
        }
        void welcome(Person const& other)const{
            cout << "很高兴认识你," << other.name << ",我是" << name << endl;
        }
        char const* getName()const{return name;}
        int getAge()const{return age;}
        virtual ~Person(){}
    };
    class Teacher : public Person{
        char course[10];
    public:
        Teacher(char const* name, int age, char const* course):Person(name,age){
            strcpy(this->course, course);
        }
        virtual void show()const{
            cout << "同学们好,我叫" << getName() << ",教大家" << course << endl;
        }
        void welcome(Person const& other)const{
            cout << "欢迎你来我这里学习," << other.getName() << endl;
        }
    };
    class Student : public Person{
        int num;
    public:
        Student(char const* name, int age, int num):Person(name,age),num(num){}
        virtual void show()const{
            cout << "大家好,我是" << num << "号学员" << getName() << endl;
        }
        void welcome(Person const& other)const{
            cout << "你好," << other.getName() << ",欢迎你加入我们班!" << endl;
        }
    };
    int main()
    {
        Person nb("wxf", 22);
        Person* pt[3]={NULL};
        pt[0] = new Teacher("Voldemort", 25, "软件逆向");
        pt[1] = new Student("陈宗权", 36, 5);
        pt[2] = new Student("上善若水", 23, 7);
        int i;
        for(i=0; i<3; i++){
            pt[i]->show();
            pt[i]->welcome(nb);
        }
        for(i=0; i<3; i++)
            delete pt[i];
        return 0;
    }

    这次完全按照标准C++的格式来写的,标准头文件和标准main函数。
    在这个程序中用了:
    一个基类Person,其中有两个数据成员name和age,一个带参构造函数,一个虚成员函数show,一个普通成员函数welcome,两个getter函数,一个虚析构函数。
    一个派生类Teacher,其中有一个数据成员course,一个带参构造函数,一个虚成员函数show,一个普通成员函数welcome。
    一个派生类Student,其中有一个数据成员num,一个带参构造函数,一个虚成员函数show,一个普通成员函数welcome。
    在Debug模式下用F7构建成可执行文件ida5-1.exe,然后用IDA打开ida5-1.exe。

    加载完毕之后,在文本形式下,把变量var_28改名为nb,把变量var_34、var_30、var_2C改名为pt_0、pt_1、pt_2,把变量var_38改名为i,把立即数16h改成十进制的22,把立即数19h改成十进制的25,把立即数24h改成十进制的36,把立即数17h改成十进制的23,把变量aA的数组长度改为9,把变量asc_46F01C的数组长度也改为9。
    再来看main函数的代码,如此之长,超过了以往每一次的代码。不过也在意料之中,因为咱们源程序就比以往每次都长很多。还是分段来看吧。

    .text:00401620 main            proc near               ; CODE XREF: _mainj
    .text:00401620
    这一段是main函数开始。

    .text:00401620 var_AC          = byte ptr -0ACh
    .text:00401620 var_6C          = dword ptr -6Ch
    .text:00401620 var_68          = dword ptr -68h
    .text:00401620 var_64          = dword ptr -64h
    .text:00401620 var_60          = dword ptr -60h
    .text:00401620 var_5C          = dword ptr -5Ch
    .text:00401620 var_58          = dword ptr -58h
    .text:00401620 var_54          = dword ptr -54h
    .text:00401620 var_50          = dword ptr -50h
    .text:00401620 var_4C          = dword ptr -4Ch
    .text:00401620 var_48          = dword ptr -48h
    .text:00401620 var_44          = dword ptr -44h
    .text:00401620 var_40          = dword ptr -40h
    .text:00401620 var_3C          = dword ptr -3Ch
    .text:00401620 i               = dword ptr -38h
    .text:00401620 pt_0            = dword ptr -34h
    .text:00401620 pt_1            = dword ptr -30h
    .text:00401620 pt_2            = dword ptr -2Ch
    .text:00401620 nb              = byte ptr -28h
    .text:00401620 var_C           = dword ptr -0Ch
    .text:00401620 var_4           = dword ptr -4
    .text:00401620
    这一段是变量表。咱们显式定义的变量只有对象nb、指针数组pt[3]和变量i,其它都是为临时变量准备的。

    .text:00401620                 push    ebp
    .text:00401621                 mov     ebp, esp
    这一段是保存ebp。

    .text:00401623                 push    0FFFFFFFFh
    .text:00401625                 push    offset unknown_libname_16 ; Microsoft VisualC 2-8/net runtime
    .text:0040162A                 mov     eax, large fs:0
    .text:00401630                 push    eax
    .text:00401631                 mov     large fs:0, esp
    这一段是在做什么呢?本来不想这么早涉及异常,不过既然遇到了就简单说说吧。首先把本块的异常ID(也就是0FFFFFFFFh)和异常处理函数的地址入栈,依次在ebp-4和ebp-8位置。然后取得原来的异常处理链入口(在fs:0处)并入栈保存在ebp-0Ch位置,最后把当前异常处理结构登记为异常处理链第一个入口。

    .text:00401638                 sub     esp, 0A0h
    .text:0040163E                 push    ebx
    .text:0040163F                 push    esi
    .text:00401640                 push    edi
    .text:00401641                 lea     edi, [ebp+var_AC]
    .text:00401647                 mov     ecx, 28h
    .text:0040164C                 mov     eax, 0CCCCCCCCh
    .text:00401651                 rep stosd
    这一段就是常规的栈初始化代码。注意跟以往不同的是,栈初始化不是从ebp-0A0h到ebp-00h,而是从ebp-0ACh到ebp-0Ch,因为ebp-0Ch到ebp-00h留出来保存异常处理信息了。


    .text:00401653                 push    22
    .text:00401655                 push    offset aWxf     ; "wxf"
    .text:0040165A                 lea     ecx, [ebp+nb]
    .text:0040165D                 call    j_Person__Person
    这一段是创建Person对象nb,传了两个参数"wxf"和22,对应代码为
        Person nb("wxf", 22);

    .text:00401662                 mov     [ebp+var_4], 0
    这一段把新异常ID设置为0。

    .text:00401669                 mov     [ebp+pt_0], 0
    .text:00401670                 xor     eax, eax
    .text:00401672                 mov     [ebp+pt_1], eax
    .text:00401675                 mov     [ebp+pt_2], eax
    这一段定义指针数组pt[3]并初始化成全部为NULL。NULL在经过编译器后就已经是0了。对应代码:
        Person* pt[3]={NULL};

    .text:00401678                 push    28h             ; sizeof(Teacher)
    .text:0040167A                 call    ??2@YAPAXI@Z    ; operator new(uint)
    .text:0040167F                 add     esp, 4
    .text:00401682                 mov     [ebp+var_40], eax
    这一小段是new Teacher并恢复堆栈,把new返回的堆空间地址保存到临时空间var_40中。

    .text:00401685                 mov     byte ptr [ebp+var_4], 1
    这一小段把新异常ID设置为1。

    .text:00401689                 cmp     [ebp+var_40], 0
    .text:0040168D                 jz      short loc_4016A8
    这一小段检查内存空间分配是否成功,如果失败则跳转到4016a8处把临时变量var_60设置为NULL,成功则调用构造函数。

    .text:0040168F                 push    offset aA       ; "软件逆向"
    .text:00401694                 push    25
    .text:00401696                 push    offset aVoldemort ; "Voldemort"
    .text:0040169B                 mov     ecx, [ebp+var_40]
    .text:0040169E                 call    j_Teacher__Teacher
    这一小段调用Teacher类的构造函数,传递对象地址var_40、"Voldemort"、25、"软件逆向"作为参数

    .text:004016A3                 mov     [ebp+var_60], eax
    .text:004016A6                 jmp     short loc_4016AF
    .text:004016A8 ; ---------------------------------------------------------------------------
    .text:004016A8
    这一小段把构造函数的返回值(对象地址this)保存到临时变量var_60中,然后跳过001618。


    .text:004016A8 loc_4016A8:                             ; CODE XREF: main+6Dj
    .text:004016A8                 mov     [ebp+var_60], 0
    .text:004016AF
    这一小段把var_60设置为NULL作为构造函数的返回值,指示不成功。

    .text:004016AF loc_4016AF:                             ; CODE XREF: main+86j
    .text:004016AF                 mov     ecx, [ebp+var_60]
    .text:004016B2                 mov     [ebp+var_3C], ecx
    这一小段把构造函数的返回值(对象地址或者NULL)转存到var_3C中,对象创建并初始化工作完成。

    .text:004016B5                 mov     byte ptr [ebp+var_4], 0
    这一小段把异常ID恢复为0,因为new Teacher("Voldemort", 25, "软件逆向")中可能发生异常的指令已经执行完毕。

    .text:004016B9                 mov     edx, [ebp+var_3C]
    .text:004016BC                 mov     [ebp+pt_0], edx
    这一小段把var_3C中的值转存到pt[0]中。
    前面几个小段是用new创建一个Teacher对象,把对象地址保存到pt[0]中,对应源代码为:
        pt[0] = new Teacher("Voldemort", 25, "软件逆向");
    /********************************************************
    从new返回对象地址到最后保存到pt[0]中,经历了var_40、var_60、var_3C三次中转,不知道是什么原因?
    ********************************************************/


    .text:004016BF                 push    20h             ; sizeof(Student)
    .text:004016C1                 call    ??2@YAPAXI@Z    ; operator new(uint)
    .text:004016C6                 add     esp, 4
    .text:004016C9                 mov     [ebp+var_48], eax
    这一小段是new Student并恢复堆栈,把new返回的堆空间地址保存到临时空间var_48中。

    .text:004016CC                 mov     byte ptr [ebp+var_4], 2
    这一小段把新异常ID设置为2。

    .text:004016D0                 cmp     [ebp+var_48], 0
    .text:004016D4                 jz      short loc_4016EC
    这一小段检查内存空间分配是否成功,如果失败则跳转到4016EC处把临时变量var_64设置为NULL,成功则调用构造函数。

    .text:004016D6                 push    5
    .text:004016D8                 push    36
    .text:004016DA                 push    offset aI       ; "陈宗权"
    .text:004016DF                 mov     ecx, [ebp+var_48]
    .text:004016E2                 call    j_Student__Student
    .text:004016E7                 mov     [ebp+var_64], eax
    .text:004016EA                 jmp     short loc_4016F3
    .text:004016EC ; ---------------------------------------------------------------------------
    .text:004016EC
    这一小段调用Student类的构造函数,传递对象地址var_48、"陈宗权"、36和5作为参数,把返回值也就是对象地址保存到变量var_64中,然后跳过4016EC直接到4016F3。

    .text:004016EC loc_4016EC:                             ; CODE XREF: main+B4j
    .text:004016EC                 mov     [ebp+var_64], 0
    .text:004016F3
    这一小段把var_64设置为NULL作为构造函数的返回值,指示不成功。

    .text:004016F3 loc_4016F3:                             ; CODE XREF: main+CAj
    .text:004016F3                 mov     eax, [ebp+var_64]
    .text:004016F6                 mov     [ebp+var_44], eax
    这一小段把构造函数的返回值(对象地址或者NULL)转存到var_44中,对象创建并初始化工作完成。

    .text:004016F9                 mov     byte ptr [ebp+var_4], 0
    这一小段把异常ID恢复为0,因为new Student("陈宗权", 36, 5)中可能发生异常的指令已经执行完毕。

    .text:004016FD                 mov     ecx, [ebp+var_44]
    .text:00401700                 mov     [ebp+pt_1], ecx
    这一小段把var_44中的值转存到pt[1]中。
    前面几个小段是用new创建一个Student对象,把对象地址保存到pt[1]中,对应源代码为:
        pt[1] = new Student("陈宗权", 36, 5);

    .text:00401703                 push    20h             ; sizeof(Student)
    .text:00401705                 call    ??2@YAPAXI@Z    ; operator new(uint)
    .text:0040170A                 add     esp, 4
    .text:0040170D                 mov     [ebp+var_50], eax
    .text:00401710                 mov     byte ptr [ebp+var_4], 3
    .text:00401714                 cmp     [ebp+var_50], 0
    .text:00401718                 jz      short loc_401730
    .text:0040171A                 push    7
    .text:0040171C                 push    17h
    .text:0040171E                 push    offset asc_46F01C ; "上善若水"
    .text:00401723                 mov     ecx, [ebp+var_50]
    .text:00401726                 call    j_Student__Student
    .text:0040172B                 mov     [ebp+var_68], eax
    .text:0040172E                 jmp     short loc_401737
    .text:00401730 ; ---------------------------------------------------------------------------
    .text:00401730
    .text:00401730 loc_401730:                             ; CODE XREF: main+F8j
    .text:00401730                 mov     [ebp+var_68], 0
    .text:00401737
    .text:00401737 loc_401737:                             ; CODE XREF: main+10Ej
    .text:00401737                 mov     edx, [ebp+var_68]
    .text:0040173A                 mov     [ebp+var_4C], edx
    .text:0040173D                 mov     byte ptr [ebp+var_4], 0
    .text:00401741                 mov     eax, [ebp+var_4C]
    .text:00401744                 mov     [ebp+pt_2], eax
    这一段跟前面一段几乎是一样的,就不再分析了,对应的源代码为:
        pt[2] = new Student("上善若水", 23, 7);

    .text:00401747                 mov     [ebp+i], 0
    .text:0040174E                 jmp     short loc_401759
    .text:00401750 ; ---------------------------------------------------------------------------
    .text:00401750
    这一段是for循环的准备部分i=0,然后就跳转到条件判断部分。

    .text:00401750 loc_401750:                             ; CODE XREF: main+16Aj
    .text:00401750                 mov     ecx, [ebp+i]
    .text:00401753                 add     ecx, 1
    .text:00401756                 mov     [ebp+i], ecx
    .text:00401759
    这一段是for循环的调整部分i++。

    .text:00401759 loc_401759:                             ; CODE XREF: main+12Ej
    .text:00401759                 cmp     [ebp+i], 3
    .text:0040175D                 jge     short loc_40178C
    这一段是for循环的条件判断部分i<3,不满足则跳出循环,到40178C处。

    .text:0040175F                 mov     edx, [ebp+i]            ;edx=i
    .text:00401762                 mov     ecx, [ebp+edx*4+pt_0]   ;ecx=pt[i]
    这一小段取得对象地址保存到ecx中,作为后面调用成员函数时传递给this指针的参数。

    .text:00401766                 mov     eax, [ebp+i]            ;eax=i
    .text:00401769                 mov     edx, [ebp+eax*4+pt_0]   ;edx=pt[i]
    .text:0040176D                 mov     eax, [edx]              ;eax=pt[i]->vfptr
    这一小段取得虚表指针vfptr,它在对象的最前面4个字节。

    .text:0040176F                 mov     esi, esp
    .text:00401771                 call    dword ptr [eax]         ;通过虚函数表调用第一个虚函数
    .text:00401773                 cmp     esi, esp
    .text:00401775                 call    __chkesp
    这一小段保存esp,调用虚表指针指向的第一个成员函数,也就是show函数,然后再检查堆栈。对应源代码为:
            pt[i]->show();

    .text:0040177A                 lea     ecx, [ebp+nb]
    .text:0040177D                 push    ecx
    .text:0040177E                 mov     edx, [ebp+i]
    .text:00401781                 mov     ecx, [ebp+edx*4+pt_0]
    .text:00401785                 call    j_Person__welcome
    这一段调用成员函数welcome,把pt[i]作为this参数,nb作为实参,对应源代码为。
            pt[i]->welcome(nb);

    .text:0040178A                 jmp     short loc_401750
    这里跳转回到for循环的条件判断部分,构成循环。

    .text:0040178C ; ---------------------------------------------------------------------------
    .text:0040178C
    .text:0040178C loc_40178C:                             ; CODE XREF: main+13Dj
    .text:0040178C                 mov     [ebp+i], 0
    .text:00401793                 jmp     short loc_40179E
    .text:00401795 ; ---------------------------------------------------------------------------
    .text:00401795
    .text:00401795 loc_401795:                             ; CODE XREF: main:loc_4017DCj
    .text:00401795                 mov     eax, [ebp+i]
    .text:00401798                 add     eax, 1
    .text:0040179B                 mov     [ebp+i], eax
    .text:0040179E
    .text:0040179E loc_40179E:                             ; CODE XREF: main+173j
    .text:0040179E                 cmp     [ebp+i], 3
    .text:004017A2                 jge     short loc_4017DE
    这一段是第二个for循环的准备、调整和条件判断部分,对应源代码为:
        for(i=0; i<3; i++)

    .text:004017A4                 mov     ecx, [ebp+i]           ;ecx=i
    .text:004017A7                 mov     edx, [ebp+ecx*4+pt_0]  ;edx=pt[i]
    .text:004017AB                 mov     [ebp+var_58], edx      ;var_58=pt[i]
    .text:004017AE                 mov     eax, [ebp+var_58]      ;eax=var_58
    .text:004017B1                 mov     [ebp+var_54], eax      ;var_54=eax
    .text:004017B4                 cmp     [ebp+var_54], 0        ;检查对象地址是否为NULL
    .text:004017B8                 jz      short loc_4017D5       ;如果为空就不执行delete相关操作了,直接跳到4017D5,以NULL作为操作的返回值
    .text:004017BA                 mov     esi, esp               ;保存堆栈当前位置
    .text:004017BC                 push    1                      ;把1入栈
    .text:004017BE                 mov     ecx, [ebp+var_54]      ;取得pt[i]所指的对象的地址
    .text:004017C1                 mov     edx, [ecx]             ;通过对象地址取得虚函数表指针保存到edx中
    .text:004017C3                 mov     ecx, [ebp+var_54]      ;把pt[i]所指的对象的地址作为this
    .text:004017C6                 call    dword ptr [edx+4]      ;调用虚函数表中的偏移量为4的函数,也就是deleting链函数,它会调用析构函数和delete函数
    .text:004017C9                 cmp     esi, esp
    .text:004017CB                 call    __chkesp               ;检查堆栈是否恢复
    .text:004017D0                 mov     [ebp+var_6C], eax      ;把deleting函数的返回值(也就是对象的地址)保存到var_6C中
    .text:004017D3                 jmp     short loc_4017DC       ;跳转到for循环的调整部分
    .text:004017D5 ; ---------------------------------------------------------------------------
    .text:004017D5
    .text:004017D5 loc_4017D5:                             ; CODE XREF: main+198j
    .text:004017D5                 mov     [ebp+var_6C], 0        ;以NULL作为deleting函数的返回值
    .text:004017DC
    .text:004017DC loc_4017DC:                             ; CODE XREF: main+1B3j
    .text:004017DC                 jmp     short loc_401795       ;跳转到for循环的调整部分
    这一段是循环体,通过虚函数表调用对象的deleting成员函数,并把1作为实参,把对象地址作为返回值。对应的源代码为:
            delete pt[i];
    /********************************************************
    deleting成员函数不是析构函数,也不是delete运算符函数,但是它会调用这两个函数。
    以1为实参的作用还有待后面分析。
    ********************************************************/

    .text:004017DE ; ---------------------------------------------------------------------------
    .text:004017DE
    .text:004017DE loc_4017DE:                             ; CODE XREF: main+182j
    .text:004017DE                 mov     [ebp+var_5C], 0          ;设置main函数的返回值为0
    .text:004017E5                 mov     [ebp+var_4], 0FFFFFFFFh  ;把异常id恢复成0FFFFFFFFh也就是-1
    .text:004017EC                 lea     ecx, [ebp+nb]            ;把nb的地址传给this
    .text:004017EF                 call    j_Person___Person        ;为nb对象调用析构函数Person::~Person()
    .text:004017F4                 mov     eax, [ebp+var_5C]        ;把main函数的返回值0保存到eax中
    .text:004017F7                 mov     ecx, [ebp+var_C]         ;取得原来的异常链入口地址
    .text:004017FA                 mov     large fs:0, ecx          ;恢复原来的异常链入口
    这一段恢复最初的异常处理链,设置main函数的返回值为0。

    .text:00401801                 pop     edi
    .text:00401802                 pop     esi
    .text:00401803                 pop     ebx
    .text:00401804                 add     esp, 0ACh
    .text:0040180A                 cmp     ebp, esp
    .text:0040180C                 call    __chkesp
    .text:00401811                 mov     esp, ebp
    .text:00401813                 pop     ebp
    .text:00401814                 retn
    .text:00401814 main            endp
    这一段恢复和检查堆栈,main函数结束。

    ===================================================
    总结一下,因为程序太长了。程序中有几个由编译器自动在用new创建对象的地方加上的if/else控制,检查new的结果是否为空。这里截取其中一个的图,另外几个类似。

    顺便回顾一下for循环的箭头图。

    在整个main函数中,主要涉及的是异常处理、new/delete、构造函数、析构函数、虚函数表。
    1、在这个例子中的异常处理是由系统自动加的,用来处理new/delete过程中可能产生的异常。
    2、new和delete本身涉及的内存分配在这里没有深入,以后再去分析。new涉及到调用构造,在new之后自动就会调用构造。delete涉及到调用析构,但是咱们这里是虚析构,所以通过虚函数表指针vftable找到的scalar_deleting_destructor函数来调用,在那个函数中进行的析构函数和delete函数的调用,这个问题我会在下面继续分析。
    3、构造函数本身的情况在上一次课4-8中已经分析过,但是这一次加入了初始化列表,加入了继承,所以下面会再以新加入的为重点来分析。
    4、析构函数在以前的课程中没有涉及到,也在下面分析。
    5、虚函数表是属于一个类的表,每个有虚函数的类,编译时都会产生一张表,所有属于这个类的对象共享一张表,多态机制就是靠它来实现的。每个对象中有一个隐含的指针成员指向这个虚函数表,所以每个对象都能访问到虚函数表。这个指针成员是如何指向虚函数表的,还有待下面对构造函数进行进一步的分析。虚函数表中是这个类中所有函数指针组成的一个数组,每个对象都能通过指针找到自己那个类的虚函数表,所以调用虚函数时总是能调用到对象自己所属的那个类中定义的虚函数,这就是多态的本质。
    为此,后面继续分析父子类的scalar_deleting_destructor函数、构造函数、析构函数。
    ===================================================


    ★来分析scalar_deleting_destructor函数。三个类就会有三个,父类Person,子类Teacher和Student。两个子类的应该是一样的,咱们看Person类的和Teacher类的就行了。

    先看Person类的。找到Person___scalar_deleting_destructor_函数,把变量名var_4改名为this就行了。
    ---------------------------------------
    .text:00401A90 Person___scalar_deleting_destructor_ proc near
    .text:00401A90                                         ; CODE XREF: j_Person___scalar_deleting_destructor_j
    .text:00401A90                                         ; .text:00401307j
    .text:00401A90
    .text:00401A90 var_44          = byte ptr -44h
    .text:00401A90 this            = dword ptr -4
    .text:00401A90 arg_0           = dword ptr  8
    .text:00401A90
    .text:00401A90                 push    ebp
    .text:00401A91                 mov     ebp, esp
    .text:00401A93                 sub     esp, 44h
    .text:00401A96                 push    ebx
    .text:00401A97                 push    esi
    .text:00401A98                 push    edi
    .text:00401A99                 push    ecx               ;保存传过来的对象地址
    .text:00401A9A                 lea     edi, [ebp+var_44]
    .text:00401A9D                 mov     ecx, 11h
    .text:00401AA2                 mov     eax, 0CCCCCCCCh
    .text:00401AA7                 rep stosd
    这一段是栈的局部变量区初始化。

    .text:00401AA9                 pop     ecx               ;取得传过来的对象地址到ecx中
    .text:00401AAA                 mov     [ebp+this], ecx   ;把ecx保存到this指针中
    .text:00401AAD                 mov     ecx, [ebp+this]   ;又把this保存到ecx中
    .text:00401AB0                 call    j_Person___Person ;调用析构函数
    这一段直接调用Person的析构函数。
    /******************************************
    注意调析构的时候没有再次使用虚函数表。也就是说,所谓的虚析构,并不是析构函数是虚函数,而是这个类的scalar_deleting_destructor_函数是虚函数。
    ******************************************/

    .text:00401AB5                 mov     eax, [ebp+arg_0]  ;取得传递过来的参数到eax中
    .text:00401AB8                 and     eax, 1            ;检测最低位是否为1
    .text:00401ABB                 test    eax, eax
    .text:00401ABD                 jz      short loc_401ACB  ;如果是0就跳过后面delete操作直接到401ACB
    .text:00401ABF                 mov     ecx, [ebp+this]   ;又把this保存到ecx中
    .text:00401AC2                 push    ecx               ;把ecx也就是this入栈。
    .text:00401AC3                 call    operator_delete   ;调用delete运算符函数
    .text:00401AC8                 add     esp, 4            ;恢复堆栈
    这一段检测传递过来的参数是否低位是1,如果是1就调用delete来释放对象的空间。
    /******************************************
    *  对这个1,我这样推测:如果在源程序中是用delete来释放对象,在调用scalar_deleting_destructor_函数之前编译器先push 1,而不是用delete来释放对象的时候就把push 1换成push 0。可以把这个push的值当成一个布尔值,由它决定要不要调用delete运算符函数。不过,当不是用delete来释放对象的时候,对象应该不是用new创建的,系统能完全掌握它的生命期,完全可以直接调用那个类的构造函数,根本不需要用到这个虚的scalar_deleting_destructor_函数。不明白为什么编译器要费劲插入这么个函数来代替原来的虚析构函数。
    *  注意到调用成员函数的时候是直接把对象地址放在寄存器ecx中就call的,但是调用operator_delete的时候却是把对象地址用push入栈后再call的,不知道为什么要这么处理。
    ******************************************/

    .text:00401ACB
    .text:00401ACB loc_401ACB:                             ; CODE XREF: Person___scalar_deleting_destructor_+2Dj
    .text:00401ACB                 mov     eax, [ebp+this]
    .text:00401ACE                 pop     edi
    .text:00401ACF                 pop     esi
    .text:00401AD0                 pop     ebx
    .text:00401AD1                 add     esp, 44h
    .text:00401AD4                 cmp     ebp, esp
    .text:00401AD6                 call    __chkesp
    .text:00401ADB                 mov     esp, ebp
    .text:00401ADD                 pop     ebp
    .text:00401ADE                 retn    4
    .text:00401ADE Person___scalar_deleting_destructor_ endp
    这一段把this作为函数的返回值,恢复并检查堆栈。
    ---------------------------------------

    再来看看Teacher类的。同样是找到Teacher___scalar_deleting_destructor_函数,把变量名var_4改名为this就行了。
    ---------------------------------------
    .text:00401C60 Teacher___scalar_deleting_destructor_ proc near ; CODE XREF: .text:00401195j
    .text:00401C60                                         ; j_Teacher___scalar_deleting_destructor_j
    .text:00401C60
    .text:00401C60 var_44          = byte ptr -44h
    .text:00401C60 this            = dword ptr -4
    .text:00401C60 arg_0           = dword ptr  8
    .text:00401C60
    .text:00401C60                 push    ebp
    .text:00401C61                 mov     ebp, esp
    .text:00401C63                 sub     esp, 44h
    .text:00401C66                 push    ebx
    .text:00401C67                 push    esi
    .text:00401C68                 push    edi
    .text:00401C69                 push    ecx
    .text:00401C6A                 lea     edi, [ebp+var_44]
    .text:00401C6D                 mov     ecx, 11h
    .text:00401C72                 mov     eax, 0CCCCCCCCh
    .text:00401C77                 rep stosd
    .text:00401C79                 pop     ecx
    .text:00401C7A                 mov     [ebp+this], ecx
    .text:00401C7D                 mov     ecx, [ebp+this]
    .text:00401C80                 call    j_Teacher___Teacher
    .text:00401C85                 mov     eax, [ebp+arg_0]
    .text:00401C88                 and     eax, 1
    .text:00401C8B                 test    eax, eax
    .text:00401C8D                 jz      short loc_401C9B
    .text:00401C8F                 mov     ecx, [ebp+this]
    .text:00401C92                 push    ecx
    .text:00401C93                 call    operator_delete
    .text:00401C98                 add     esp, 4
    .text:00401C9B
    .text:00401C9B loc_401C9B:                             ; CODE XREF: Teacher___scalar_deleting_destructor_+2Dj
    .text:00401C9B                 mov     eax, [ebp+this]
    .text:00401C9E                 pop     edi
    .text:00401C9F                 pop     esi
    .text:00401CA0                 pop     ebx
    .text:00401CA1                 add     esp, 44h
    .text:00401CA4                 cmp     ebp, esp
    .text:00401CA6                 call    __chkesp
    .text:00401CAB                 mov     esp, ebp
    .text:00401CAD                 pop     ebp
    .text:00401CAE                 retn    4
    .text:00401CAE Teacher___scalar_deleting_destructor_ endp
    ---------------------------------------
    可以看到Teacher类的这个函数跟Person类的这个函数唯一不同的地方就是调析构函数的时候调用的是Teacher类的析构函数。
    由此咱们可以推测Student类的Student___scalar_deleting_destructor_函数跟这个函数唯一不同的地方也应该是调用析构函数的地方调用的是Student类的析构函数。

    ★来看构造函数。还是三个类就会有三个,父类Person,子类Teacher和Student。两个子类的应该是一样的,咱们看Person类的和Teacher类的就行了。这一次咱们重点关注初始化列表、虚表指针和对父类构造函数的调用。

    先来看Person类的构造函数Person(char const* name, int age)。把变量var_4改名为this,把arg_0改名为name,arg_4改名为age。
    ---------------------------------------
    .text:004018A0 Person__Person  proc near               ; CODE XREF: j_Person__Personj
    .text:004018A0
    .text:004018A0 var_44          = byte ptr -44h
    .text:004018A0 this            = dword ptr -4
    .text:004018A0 name            = dword ptr  8
    .text:004018A0 age             = dword ptr  0Ch
    .text:004018A0
    .text:004018A0                 push    ebp
    .text:004018A1                 mov     ebp, esp
    .text:004018A3                 sub     esp, 44h
    .text:004018A6                 push    ebx
    .text:004018A7                 push    esi
    .text:004018A8                 push    edi
    .text:004018A9                 push    ecx
    .text:004018AA                 lea     edi, [ebp+var_44]
    .text:004018AD                 mov     ecx, 11h
    .text:004018B2                 mov     eax, 0CCCCCCCCh
    .text:004018B7                 rep stosd
    这一段是栈初始化。

    .text:004018B9                 pop     ecx
    .text:004018BA                 mov     [ebp+this], ecx
    .text:004018BD   

  • 其实咱们在前面已经好多次涉及到函数了,所以这次的逆向试验要深入一点,有两个目的:
    一是深入观察值传递时各种函数的参数和返回值的传递情况,
    二是尝试逆向release模式。

    新建一个空的控制台工程ida5-2,再新建一个C++源文件function.cpp,内容为:

    #include <iostream>
    using namespace std;

    struct Person{
        char name[20];
        int age;
        double weight;
    };

    void welcome(char const* msg)
    {
        cout << msg << endl;
    }
    void welcome()
    {
        cout << "欢迎来到牛B论坛" << endl;
    }
    char inputChar()
    {
        welcome("请输入一个字母:");
        char ch;
        cin >> ch;
        return ch;
    }
    char toUpper( char ch )
    {
        if(ch>='a'&&ch<='z')
            ch+=('A'-'a');
        return ch;
    }
    int mul( int x, float y )
    {
        float res = x * y;
        return (int)res;
    }
    double div( double x, int y )
    {
        double res = x / y;
        return res;
    }
    Person big(Person a, Person b)
    {
        if(a.age        return b;
        if(b.age        return a;
        return a.weight<?xml:namespace prefix = b.weight?b />}
    ostream& operator<<(ostream& os, Person const& one)
    {
        os << one.name << ':' << one.age << ',' << one.weight;
        return os;
    }
    int main()
    {
        Person s={"上善若水",23,62.5}, c={"陈宗权",35,54.3}, r;
        char ch = toUpper('v');
        int num = mul(5, 6.7F);
        double res = div(123.4, 5);
        r = big(s,c);
        cout << ch << endl;
        cout << num << endl;
        cout << res << endl;
        cout << r << endl;
        return 0;
    }

    字符类型用于比较的时候会先扩展成整数类型再比较。

    ●int mul( int x, float y )函数
    .text:00401880 ; int __cdecl mul(int x, float y)
    .text:00401880 mul             proc near               ; CODE XREF: j_mulj
    .text:00401880
    .text:00401880 var_44          = byte ptr -44h
    .text:00401880 res             = dword ptr -4
    .text:00401880 x               = dword ptr  8
    .text:00401880 y               = dword ptr  0Ch
    .text:00401880
    .text:00401880                 push    ebp
    .text:00401881                 mov     ebp, esp
    .text:00401883                 sub     esp, 44h
    .text:00401886                 push    ebx
    .text:00401887                 push    esi
    .text:00401888                 push    edi
    .text:00401889                 lea     edi, [ebp+var_44]
    .text:0040188C                 mov     ecx, 11h
    .text:00401891                 mov     eax, 0CCCCCCCCh
    .text:00401896                 rep stosd
    .text:00401898                 fild    [ebp+x]       ;从整数x转换成单精度浮点数放到浮点寄存器中
    .text:0040189B                 fmul    [ebp+y]       ;把浮点寄存器中的浮点数乘以y,结果还在FPU寄存器中
    .text:0040189E                 fst     [ebp+res]     ;把相乘结果传送到变量res中
    .text:004018A1                 call    __ftol        ;把浮点数转换成整数类型,具体如何转换到调试时再看
    .text:004018A6                 pop     edi
    .text:004018A7                 pop     esi
    .text:004018A8                 pop     ebx
    .text:004018A9                 mov     esp, ebp
    .text:004018AB                 pop     ebp
    .text:004018AC                 retn
    .text:004018AC mul             endp

    ●double div( double x, int y )函数
    .text:004018C0 ; double __cdecl div(double x, int y)
    .text:004018C0 div             proc near               ; CODE XREF: j_divj
    .text:004018C0
    .text:004018C0 var_48          = byte ptr -48h
    .text:004018C0 res             = qword ptr -8
    .text:004018C0 x               = qword ptr  8
    .text:004018C0 y               = dword ptr  10h
    .text:004018C0
    .text:004018C0                 push    ebp
    .text:004018C1                 mov     ebp, esp
    .text:004018C3                 sub     esp, 48h
    .text:004018C6                 push    ebx
    .text:004018C7                 push    esi
    .text:004018C8                 push    edi
    .text:004018C9                 lea     edi, [ebp+var_48]
    .text:004018CC                 mov     ecx, 12h
    .text:004018D1                 mov     eax, 0CCCCCCCCh
    .text:004018D6                 rep stosd
    .text:004018D8                 fild    [ebp+y]       ;从整数y转换成单精度浮点数放到浮点寄存器中
    .text:004018DB                 fdivr   [ebp+x]       ;把x的值除以浮点寄存器中的浮点数,结果还在FPU寄存器中
    .text:004018DE                 fst     [ebp+res]     ;把相除结果传送到变量res中,同时也保留在浮点寄存器中作为返回值
    .text:004018E1                 pop     edi
    .text:004018E2                 pop     esi
    .text:004018E3                 pop     ebx
    .text:004018E4                 mov     esp, ebp
    .text:004018E6                 pop     ebp
    .text:004018E7                 retn
    .text:004018E7 div             endp

    ●Person big(Person a, Person b)函数
    .text:00401900 big             proc near               ; CODE XREF: j_bigj
    .text:00401900
    .text:00401900 var_44          = byte ptr -44h
    .text:00401900 bigger_ptr      = dword ptr -4                     ;函数内临时用的一个指针,用来指向a和b中比较大的那个
    .text:00401900 bigger_copy_ptr = dword ptr  8                     ;编译器自动产生的作为参数传过来的指针,用来存放a和b中比较大的那个的拷贝
    .text:00401900 a_name          = byte ptr  0Ch                    ;形参Person结构变量a
    .text:00401900 a_age           = dword ptr  20h
    .text:00401900 a_weight        = qword ptr  24h
    .text:00401900 b_name          = byte ptr  2Ch                    ;形参Person结构变量b
    .text:00401900 b_age           = dword ptr  40h
    .text:00401900 b_weight        = qword ptr  44h
    .text:00401900
    .text:00401900                 push    ebp
    .text:00401901                 mov     ebp, esp
    .text:00401903                 sub     esp, 44h
    .text:00401906                 push    ebx
    .text:00401907                 push    esi
    .text:00401908                 push    edi
    .text:00401909                 lea     edi, [ebp+var_44]
    .text:0040190C                 mov     ecx, 11h
    .text:00401911                 mov     eax, 0CCCCCCCCh
    .text:00401916                 rep stosd
    .text:00401918                 mov     eax, [ebp+a_age]
    .text:0040191B                 cmp     eax, [ebp+b_age]
    .text:0040191E                 jge     short loc_401932             ;如果a.age>=b.age就跳转到401932,再进一步比较
    .text:00401920                 mov     ecx, 8
    .text:00401925                 lea     esi, [ebp+b_name]            ;否则就以b作为结果,把b的数据复制到bigger_copy_ptr指向的地方
    .text:00401928                 mov     edi, [ebp+bigger_copy_ptr]
    .text:0040192B                 rep movsd
    .text:0040192D                 mov     eax, [ebp+bigger_copy_ptr]   ;并且以复制品的地址bigger_copy_ptr作为返回值
    .text:00401930                 jmp     short loc_401977             ;跳转到函数结束处
    .text:00401932 ; ---------------------------------------------------------------------------
    .text:00401932
    .text:00401932 loc_401932:                             ; CODE XREF: big+1Ej
    .text:00401932                 mov     ecx, [ebp+b_age]
    .text:00401935                 cmp     ecx, [ebp+a_age]
    .text:00401938                 jge     short loc_40194C             ;如果b.age>=a.age意味着age相等,就跳转到40194C,比weight
    .text:0040193A                 mov     ecx, 8
    .text:0040193F                 lea     esi, [ebp+a_name]            ;否则就以a作为结果,把b的数据复制到bigger_copy_ptr指向的地方
    .text:00401942                 mov     edi, [ebp+bigger_copy_ptr]
    .text:00401945                 rep movsd
    .text:00401947                 mov     eax, [ebp+bigger_copy_ptr]   ;并且以复制品的地址bigger_copy_ptr作为返回值
    .text:0040194A                 jmp     short loc_401977             ;跳转到函数结束处
    .text:0040194C ; ---------------------------------------------------------------------------
    .text:0040194C
    .text:0040194C loc_40194C:                             ; CODE XREF: big+38j
    .text:0040194C                 fld     [ebp+a_weight]               ;把a.weight装入到浮点寄存器中
    .text:0040194F                 fcomp   [ebp+b_weight]               ;跟b.weight比较,结果在浮点标志寄存器中
    .text:00401952                 fnstsw  ax                           ;把浮点标志寄存器复制到ax寄存器中
    .text:00401954                 test    ah, 1                        ;检查ah中最后一位
    .text:00401957                 jz      short loc_401961             ;为0表示a.weight大,跳转到401961
    .text:00401959                 lea     edx, [ebp+b_name]            ;不为0表示b.weight大,让bigger_ptr指向b
    .text:0040195C                 mov     [ebp+bigger_ptr], edx
    .text:0040195F                 jmp     short loc_401967             ;转到401967进行数据复制
    .text:00401961 ; ---------------------------------------------------------------------------
    .text:00401961
    .text:00401961 loc_401961:                             ; CODE XREF: big+57j
    .text:00401961                 lea     eax, [ebp+a_name]            ;a.weight大就让bigger_ptr指向a
    .text:00401964                 mov     [ebp+bigger_ptr], eax
    .text:00401967
    .text:00401967 loc_401967:                             ; CODE XREF: big+5Fj
    .text:00401967                 mov     ecx, 8                       ;数据复制,把bigger_ptr指向的Person结构变量的数据复制到bigger_copy_ptr指向的地方
    .text:0040196C                 mov     esi, [ebp+bigger_ptr]
    .text:0040196F                 mov     edi, [ebp+bigger_copy_ptr]
    .text:00401972                 rep movsd
    .text:00401974                 mov     eax, [ebp+bigger_copy_ptr]   ;并且以复制品的地址bigger_copy_ptr作为返回值
    .text:00401977
    .text:00401977 loc_401977:                             ; CODE XREF: big+30j
    .text:00401977                                         ; big+4Aj
    .text:00401977                 pop     edi
    .text:00401978                 pop     esi
    .text:00401979                 pop     ebx
    .text:0040197A                 mov     esp, ebp
    .text:0040197C                 pop     ebp
    .text:0040197D                 retn                                 ;函数结束
    .text:0040197D big             endp
    这个函数的形参和返回类型是结构类型。实参是复制过来的,返回值也是复制过来的。有意思的是,返回类型是结构类型的时候,编译器会在调用前为存放返回值的结构变量分配好空间并且隐含把空间地址作为一个参数传递给函数,并且在函数中会把那个结构变量的地址传为机器码一级的返回值保存到eax中。
    函数内部的跳转也不少,看看图形形式的流程图可能更清楚一些。

    ●ostream& operator<<(ostream& os, Person const& one)函数
    想把结构复习一下,在IDA中定义了一个Person结构。
    .text:004019A0 operator__      proc near               ; CODE XREF: j_operator__j
    .text:004019A0
    .text:004019A0 var_40          = byte ptr -40h
    .text:004019A0 cout_ptr        = dword ptr  8                      ;第一个参数cout的引用
    .text:004019A0 one_ptr         = dword ptr  0Ch                    ;第二个参数one的引用
    .text:004019A0
    .text:004019A0                 push    ebp
    .text:004019A1                 mov     ebp, esp
    .text:004019A3                 sub     esp, 40h
    .text:004019A6                 push    ebx
    .text:004019A7                 push    esi
    .text:004019A8                 push    edi
    .text:004019A9                 lea     edi, [ebp+var_40]
    .text:004019AC                 mov     ecx, 10h
    .text:004019B1                 mov     eax, 0CCCCCCCCh
    .text:004019B6                 rep stosd
    .text:004019B8                 mov     eax, [ebp+one_ptr]
    .text:004019BB                 mov     ecx, [eax+Person.weight2]
    .text:004019BE                 push    ecx
    .text:004019BF                 mov     edx, [eax+Person.weight1]
    .text:004019C2                 push    edx                         ;这两个push把one.weight入栈
    .text:004019C3                 push    2Ch                         ;‘,’字符入栈
    .text:004019C5                 mov     eax, [ebp+one_ptr]
    .text:004019C8                 mov     ecx, [eax+Person.age]
    .text:004019CB                 push    ecx                         ;one.age入栈
    .text:004019CC                 push    3Ah                         ;‘:’字符入栈
    .text:004019CE                 mov     edx, [ebp+one_ptr]
    .text:004019D1                 push    edx                         ;one.name入栈
    .text:004019D2                 mov     eax, [ebp+cout_ptr]
    .text:004019D5                 push    eax                         ;cout入栈
    .text:004019D6                 call    j_std__operator__           ;调用operator<<(cout,one.name)
    .text:004019DB                 add     esp, 8
    .text:004019DE                 push    eax
    .text:004019DF                 call    sub_401235                  ;调用operator<<(cout,':')
    .text:004019E4                 add     esp, 8
    .text:004019E7                 mov     ecx, eax
    .text:004019E9                 call    sub_401145                  ;调用cout.operator<<(one.age)
    .text:004019EE                 push    eax
    .text:004019EF                 call    sub_401235                  ;调用operator<<(cout,',')
    .text:004019F4                 add     esp, 8
    .text:004019F7                 mov     ecx, eax
    .text:004019F9                 call    sub_401186                  ;调用cout.operator<<(one.weight)
    .text:004019FE                 mov     eax, [ebp+cout_ptr]         ;以cout为返回值
    .text:00401A01                 pop     edi
    .text:00401A02                 pop     esi
    .text:00401A03                 pop     ebx
    .text:00401A04                 add     esp, 40h
    .text:00401A07                 cmp     ebp, esp
    .text:00401A09                 call    __chkesp
    .text:00401A0E                 mov     esp, ebp
    .text:00401A10                 pop     ebp
    .text:00401A11                 retn
    .text:00401A11 operator__      endp

    几个函数分析完毕了,接下来该进入调试了,观察各个函数调用时栈、参数和返回值的情况。
    由于程序执行的时候实际上是由mainCRTStartup函数来调用的_main过程,_main过程直接通过jmp main实现转到main函数,所以先来看看这个mainCRTStartup函数。
    .text:00425470 public mainCRTStartup
    .text:00425470 mainCRTStartup proc near
    .text:00425470
    .text:00425470 var_1C= dword ptr -1Ch
    .text:00425470 var_18= dword ptr -18h
    .text:00425470 var_4= dword ptr -4
    .text:00425470
    .text:00425470 push    ebp
    .text:00425471 mov     ebp, esp
    .text:00425473 push    0FFFFFFFFh
    .text:00425475 push    offset stru_4720F8
    .text:0042547A push    offset __except_handler3
    .text:0042547F mov     eax, large fs:0
    .text:00425485 push    eax
    .text:00425486 mov     large fs:0, esp
    .text:0042548D add     esp, 0FFFFFFF0h
    .text:00425490 push    ebx
    .text:00425491 push    esi
    .text:00425492 push    edi
    .text:00425493 mov     [ebp+var_18], esp
    .text:00425496 call    ds:__imp__GetVersion@0          ; GetVersion()
    .text:0042549C mov     _osver, eax
    .text:004254A1 mov     eax, _osver
    .text:004254A6 shr     eax, 8
    .text:004254A9 and     eax, 0FFh
    .text:004254AE mov     _winminor, eax
    .text:004254B3 mov     ecx, _osver
    .text:004254B9 and     ecx, 0FFh
    .text:004254BF mov     _winmajor, ecx
    .text:004254C5 mov     edx, _winmajor
    .text:004254CB shl     edx, 8
    .text:004254CE add     edx, _winminor
    .text:004254D4 mov     _winver, edx
    .text:004254DA mov     eax, _osver
    .text:004254DF shr     eax, 10h
    .text:004254E2 and     eax, 0FFFFh
    .text:004254E7 mov     _osver, eax
    .text:004254EC push    0
    .text:004254EE call    _heap_init
    .text:004254F3 add     esp, 4
    .text:004254F6 test    eax, eax
    .text:004254F8 jnz     short loc_425504
    .text:004254FA push    1Ch
    .text:004254FC call    fast_error_exit
    .text:00425501 ; ---------------------------------------------------------------------------
    .text:00425501 add     esp, 4
    .text:00425504
    .text:00425504 loc_425504:                             ; CODE XREF: mainCRTStartup+88j
    .text:00425504 mov     [ebp+var_4], 0
    .text:0042550B call    _ioinit
    .text:00425510 call    ds:__imp__GetCommandLineA@0     ; GetCommandLineA()
    .text:00425516 mov     _acmdln, eax
    .text:0042551B call    __crtGetEnvironmentStringsA
    .text:00425520 mov     _aenvptr, eax
    .text:00425525 call    _setargv
    .text:0042552A call    _setenvp
    .text:0042552F call    _cinit
    .text:00425534 mov     ecx, _environ
    .text:0042553A mov     __initenv, ecx
    .text:00425540 mov     edx, _environ
    .text:00425546 push    edx                             ; envp
    .text:00425547 mov     eax, __argv
    .text:0042554C push    eax                             ; argv
    .text:0042554D mov     ecx, __argc
    .text:00425553 push    ecx                             ; argc
    .text:00425554 call    _main
    .text:00425559 add     esp, 0Ch
    .text:0042555C mov     [ebp+var_1C], eax
    .text:0042555F mov     edx, [ebp+var_1C]
    .text:00425562 push    edx
    .text:00425563 call    exit
    .text:00425563 mainCRTStartup endp
    这个函数中先是异常处理,再是运行环境检查,堆初始化,io初始化,取得命令行参数,取得环境变量,C语言环境初始化,这些都是看不见的准备工作。
    接下来是把命令行参数个数、命令行字符串序列、环境变量字符串序列作为参数调用main函数,main函数才得以执行。然后是用main函数的返回值作为参数调用exit函数终止程序。


    在main函数的栈初始化之后(00401A4E处)按F2键设置断点,按F9键开始执行,在IDA View-ESP窗口中找到EBP,那就是main函数中局部变量的底部,往上的就是main中的局部变量。把它们的类型进行适当调整,如图:

    在图里EBP下面还有一个saved_fp,这是保存的进入main函数时EBP的值。再下面还有一个retaddr,值为425559h这是main函数执行完毕后要返回的地方,也就是mainCRTStartup函数中call _main指令的下一个指令。
    因为这个程序中会涉及到小数运算,所以通过Debugger菜单下的FPU registers把浮点处理器寄存器窗口也显示出来,如图。

    准备就绪之后,按F8继续调试,咱们就发现IDA View-ESP窗口又定位到ESP,不是咱们关心的EBP的地方了。在这个窗口中找个空白地方,点右键,在Synchronize with中可以看到它是设置为与ESP同步的,改成EBP就行了。如图:

    /******************************************************
    不过只要继续调试,所以在这个窗口中改的类型都会丢失,不知道怎么能让它们保留下来。
    ******************************************************/
    继续调试执行到下面语句:
    .text:00401ACB call    sub_401334
    到这里已经完成两个结构变量s和c的初始化。可以在IDA View-ESP窗口中用+Q把c_name和s_name设置成Person结构类型,看到结构变量各个成员中的数据:
    0013FF40 c_name Person <'陈宗权', 23h, 5.43e1>
    0013FF60 s_name Person <'上善若水', 17h, 6.25e1>

    回到IDA View-EIP窗口准备继续调试。
    这时候记录:
    1.下一个指令的地址为.text:00401aD0。
    2.ESP寄存器的值为0013FE84。
    按F7单步调试,程序执行进入到调用的函数sub_401334内部,这个所谓的函数不是真正的函数,只是一个跳转语句,如下:
    .text:00401334 sub_401334 proc near                    ; CODE XREF: main+9Bp
    .text:00401334 jmp     sub_401770
    .text:00401334 sub_401334 endp
    真正的函数是sub_401770,咱们暂时不理会。
    看看ESP,现在变成0013FE80了,也就是有一个数据入栈了。在IDA View-ESP窗口中找到ESP,可以看到新入栈的数是00401aD0,正好是执行call指令之前记录的下一个指令的地址,也就是函数的返回地址。函数调用时总会把调用处下一个指令的地址入栈,这样在函数执行完毕后总是能通过栈中保存的这个地址正确返回到调用这个函数的地方的下一个指令继续执行。

    按F7跟踪到函数内部,执行完
    .text:00401770 push    ebp
    .text:00401771 mov     ebp, esp
    .text:00401773 sub     esp, 40h
    .text:00401776 push    ebx
    .text:00401777 push    esi
    .text:00401778 push    edi
    .text:00401779 lea     edi, [ebp+var_40]
    .text:0040177C mov     ecx, 10h
    .text:00401781 mov     eax, 0CCCCCCCCh
    .text:00401786 rep stosd
    这些是welcome函数保存寄存器和栈初始化部分,还是记录ESP,这里是0013FE30。
    继续F7执行,
    .text:00401788 push    offset loc_4010FF
    .text:0040178D push    offset aNGb                     ; "欢迎来到牛B论坛"
    .text:00401792 push    offset std__cout
    这几句执行完毕之后ESP是0013FE24。
    下面是
    .text:00401797 call    j_std__operator__
    它的下一指令的地址是0040179C。
    继续F7执行跟踪到函数调用内部,ESP又减4了,变成了0013FE20,栈中又多了一个数据0040179C,就是调用这个函数的下一个指令的地址。
    这个函数是输出一个字符串的。用Ctrl+F7执行到从这个函数返回,再观察ESP是0012FE24,恢复到了call指令之前的栈顶,但是,调用函数时传参数占用的栈空间还没有释放,可以看看栈:
    0013FE24 dd    47EE80h ; .data:std__cout
    0013FE28 dd    47101Ch ; .rdata:aNGb
    0013FE2C dd    4010FFh ; .text:loc_4010FF
    0013FE30 ; [BEGIN OF STACK FRAME sub_401770. PRESS KEYPAD "-" TO COLLAPSE]
    接下来用
    .text:0040179C add     esp, 8
    把cout和字符串地址aNGb从栈中清除,只留下loc_4010FF也就是endl还在栈中等待输出,eax寄存器中是返回值0047EE80,正好是cout的地址。
    .text:0040179F mov     ecx, eax
    这个指令把eax中的数据也就是cout的地址保存到ecx中了。在调用对象的成员函数时,往往把对象地址保存到ecx中,到成员函数中直接把ecx中的数据传递给函数内的this指针,这样让this指向用来调用这个成员函数的对象。
    又要调用函数了,
    .text:004017A1 call    j_std__basic_ostream_char_std__char_traits_char_____operator__
    同样先记录ESP:0013FE2C和call指令的下一个指令的地址:004017A6。再继续F7跟踪进这个call指令,依然是ESP减少4,下一个指令的地址004017A6自动入栈了。继续跟踪这个运算符函数,无非是调用了传递过去的那个函数endl。用Ctrl+F7从函数中返回,ESP恢复到了0013FE30,它把参数占用的空间已经释放了,可见它是__stdcall的。

    继续Ctrl+F7返回到main函数。在.text:00401AE1处用F2设置断点。按F9执行,期间需要到控制台窗口输入一个字符,然后到这里停住了,记录ESP:0x0013FE84。继续F7执行:
    .text:00401AE1 push    40D66666h
    .text:00401AE6 push    5
    .text:00401AE8 call    j_mul
    用来调用mul函数的两个参数6.7和5入栈了,咱们从前面的调试知道下一个指令的地址00401AED也入栈了。栈内数据如下:
    0013FE78 ; [BEGIN OF STACK FRAME mul. PRESS KEYPAD "-" TO COLLAPSE]
    0013FE78 dd    401AEDh ; main+BD
    0013FE7C arg_0 dd 5
    0013FE80 arg_4 dd 6.6999998
    0013FE80 ; [END OF STACK FRAME mul. PRESS KEYPAD "-" TO COLLAPSE]
    可以看到6.7F在计算机中的表示是6.6999998,不精确。
    继续执行到下面内容时,开始仔细观察。目前FPU寄存器
    .text:00401898 fild    [ebp+arg_0]
    执行完这一指令后,arg_0中的整数5对应的小数5.0装入到FPU的寄存器ST0中了。一些FPU状态也发生了变化,如图:

    可以跟前面的图ida523对比。
    .text:0040189B fmul    [ebp+arg_4]
    这一指令应该是用arg_4中的6.7与ST0中的5.0相乘。
    /************************************************************
    我一直以为计算结果应该保存在ST0中,但是执行完这一指令后ST0中没有数据了,FPU中各个寄存器的状态也都没有变化,那结果存哪里了呢?按照指令本身的规定是结果保存到ST0中了,但是ST0显示没有数据,一片空白,估计是IDA的问题。
    ************************************************************/
    .text:0040189E fst     [ebp+var_4]
    这一指令是把刚才相乘的结果传送到变量var_4中,结果var_4是变成了3.35e1,也就是33.5。不过ST0中还是没有数据,如图:

    下面又该调用函数_ftol了,这是系统内部的函数,同样来记录ESP:0013FE24,再下一指令地址:004018A6。继续F7调试:
    .text:004018A1 call    __ftol
    进入这个函数依然是下一个指令地址入栈,跟调用别的函数一样。
    .text:004221BC __ftol proc near                        ; CODE XREF: mul+21p
    .text:004221BC
    .text:004221BC var_C= qword ptr -0Ch
    .text:004221BC var_4= word ptr -4
    .text:004221BC var_2= word ptr -2
    .text:004221BC
    .text:004221BC push    ebp
    .text:004221BD mov     ebp, esp
    .text:004221BF add     esp, 0FFFFFFF4h
    这一步为三个局部变量留出空间,一共12个字节。
    .text:004221C2 fstcw   [ebp+var_2]
    .text:004221C6 wait
    这一步把FPU处理器控制字CTRL传送到变量var_2中。
    .text:004221C7 mov     ax, [ebp+var_2]
    .text:004221CB or      ah, 0Ch
    .text:004221CE mov     [ebp+var_4], ax
    把控制字最高两位置1后保存到变量var_4中。
    .text:004221D2 fldcw   [ebp+var_4]
    把var_4中的控制字数据重新装入到FPU控制寄存器中。
    .text:004221D5 fistp   [ebp+var_C]
    把ST0中的数据的整数部分以8字节整数格式保存到变量var_C中并将ST0中数据出浮点栈,结果var_C中是0000000000000021h,也就是33.5的整数部分33。
    .text:004221D8 fldcw   [ebp+var_2]
    恢复最初保存在var_2中的FPU控制字。
    .text:004221DB mov     eax, dword ptr [ebp+var_C]
    .text:004221DE mov     edx, dword ptr [ebp+var_C+4]
    把变量var_C中保存的8个字节的整数数据保存到edx和eax寄存器中作为返回值。
    .text:004221E1 leave
    .text:004221E2 retn
    .text:004221E2 __ftol endp
    到这里__ftol函数结束。
    转换过程跟踪完毕,回到上一级函数mul,继续返回到main函数,执行main函数中的指令:
    .text:00401AED add     esp, 8
    把调用mul函数时传递的两个参数从栈中清除。
    .text:00401AF0 mov     [ebp+num], eax
    把调用mul函数的结果保存到变量num中。结果只保留了低32位,其中高32位在edx中,丢弃了。
    后面是继续调用其它几个函数,跟前面已经进行过的分析大同小异,不再跟踪调试了。

    关于函数调用,总结一下。
    /***************************************************
    调用函数时,一般实参是按反序入栈的,调用成员函数时对象地址则是通过寄存器ecx来传递的。
    执行到调用一个near函数的call指令的时候会自动把call的下一个指令的地址入栈以便函数执行完毕之后能据此返回到call后面的指令继续执行。
    在函数中,首先是push ebp,然后把esp传递给ebp,之后就以ebp为各个局部变量(包括形参变量)的基地址。在函数结束的时候再把ebp回传给esp,然后pop ebp恢复上一级函数中各局部变量的基地址。
    函数的返回值一般保存在eax中(也可能只有ax或者al有效),返回值为小数时一般结果在ST0中,返回结构变量则是在调用时就开辟好存放返回值的空间,把这个空间的地址作为第一个参数传递到函数中,在函数结束时把要返回的数据复制到这个空间中,并且用eax返回这个空间地址。
    ***************************************************/

    下面开始第二个任务,逆向release模式的程序,跟debug模式的程序作个对比。
    同样是这个程序,用release模式构建成ida5-2.exe,然后用IDA加载。
    现在来看看main函数吧,第一次分析release的代码,我差点累死了。

    .text:004011F0 ; int __cdecl main(int argc, const char **argv, const char *envp)
    .text:004011F0 _main proc near                         ; CODE XREF: start+AFp
    .text:004011F0
    .text:004011F0 ch_= dword ptr -6Ch
    .text:004011F0 res= qword ptr -68h
    .text:004011F0 c_name_0= dword ptr -60h
    .text:004011F0 c_name_4= word ptr -5Ch
    .text:004011F0 c_name_6= byte ptr -5Ah
    .text:004011F0 c_name_7= dword ptr -59h
    .text:004011F0 c_name_11= dword ptr -55h
    .text:004011F0 c_name_15= dword ptr -51h
    .text:004011F0 c_name_19= byte ptr -4Dh
    .text:004011F0 c_age= dword ptr -4Ch
    .text:004011F0 c_weight1= dword ptr -48h
    .text:004011F0 c_weight2= dword ptr -44h
    .text:004011F0 s_name_0= dword ptr -40h
    .text:004011F0 s_name_4= dword ptr -3Ch
    .text:004011F0 s_name_8= byte ptr -38h
    .text:004011F0 s_name_9= dword ptr -37h
    .text:004011F0 s_name_13= dword ptr -33h
    .text:004011F0 s_name_17= word ptr -2Fh
    .text:004011F0 s_name_19= byte ptr -2Dh
    .text:004011F0 s_age= dword ptr -2Ch
    .text:004011F0 s_weight1= dword ptr -28h
    .text:004011F0 s_weight2= dword ptr -24h
    .text:004011F0 r= byte ptr -20h
    .text:004011F0 argc= dword ptr  8
    .text:004011F0 argv= dword ptr  0Ch
    .text:004011F0 envp= dword ptr  10h
    上面是变量表,可以看到release模式下不再有多余的变量了。当然,它还是把Person结构的char[20]成员name拆成了多个部分,以便进行初始值的传送操作。

    .text:004011F0
    .text:004011F0 push    ebp
    .text:004011F1 mov     ebp, esp
    .text:004011F3 and     esp, 0FFFFFFF8h   ;让栈esp的位置8字节对齐
    .text:004011F6 sub     esp, 6Ch    ;直接开出局部变量的空间
    这一段开出局部变量的空间,不再象debug模式下那样多开40h字节空间,而且也不再用0xCC填充局部变量的空间。

    .text:004011F9 mov     eax, dword ptr asc_4160D8       ; "上善若水",9字节
    .text:004011FE mov     ecx, dword ptr asc_4160D8+4
    .text:00401204 mov     dl, byte ptr asc_4160D8+8 ;dl中保存初始值字符串"上善若水"的第9个字节
    .text:0040120A mov     [esp+6Ch+s_name_0], eax  ;初始化s.name的第1-4字节
    .text:0040120E mov     [esp+6Ch+s_name_4], ecx  ;初始化s.name的第5-8字节
    .text:00401212 mov     ecx, dword ptr aI               ; "陈宗权",7字节
    .text:00401218 xor     eax, eax    ;eax清零
    .text:0040121A mov     [esp+6Ch+c_name_0], ecx  ;初始化c.name的第1-4字节
    .text:0040121E mov     [esp+6Ch+s_name_9], eax  ;初始化s.name的第10-13字节填充0
    .text:00401222 xor     ecx, ecx    ;ecx清零
    .text:00401224 mov     [esp+6Ch+s_name_13], eax  ;初始化s.name的第14-17字节填充0
    .text:00401228 mov     [esp+6Ch+c_name_7], ecx  ;初始化c.name的第8-11字节填充0
    .text:0040122C mov     [esp+6Ch+s_name_17], ax  ;初始化s.name的第18-19字节填充0
    .text:00401231 mov     [esp+6Ch+c_name_11], ecx  ;初始化c.name的第12-15字节填充0
    .text:00401235 push    ebx
    .text:00401236 mov     [esp+70h+s_name_8], dl  ;初始化s.name的第9字节
    .text:0040123A mov     dx, word ptr aI+4  ;dx中保存初始值字符串"陈宗权"的第5-6个字节
    .text:00401241 mov     [esp+70h+s_name_19], al  ;s.name的第20字节填充0
    .text:00401245 mov     [esp+70h+s_weight1], eax  ;初始化s.weight的低4字节(正好是0,所以用eax),也就是62.5的低4字节
    .text:00401249 mov     al, byte ptr aI+6  ;al中保存初始值字符串"陈宗权"的第7个字节
    .text:0040124E push    esi
    .text:0040124F mov     [esp+74h+c_name_15], ecx  ;初始化c.name的第16-19字节填充0
    .text:00401253 push    edi
    .text:00401254 mov     [esp+78h+s_age], 23  ;初始化s.age为23
    .text:0040125C mov     [esp+78h+s_weight2], 404F4000h ;初始化s.weight高4字节为62.5的高4字节
    .text:00401264 mov     [esp+78h+c_name_4], dx  ;初始化c.name的第5-6字节
    .text:00401269 mov     [esp+78h+c_name_6], al  ;初始化c.name的第7字节
    .text:0040126D mov     [esp+78h+c_name_19], cl  ;初始化c.name的第20字节填充0
    .text:00401271 mov     [esp+78h+c_age], 35  ;初始化c.age为35
    .text:00401279 mov     [esp+78h+c_weight1], 66666666h ;初始化c.weight低4字节为54.3的低4字节
    .text:00401281 mov     [esp+78h+c_weight2], 404B2666h ;初始化c.weight高4字节为54.3的高4字节
    这一段是初始化Person结构变量s和c,可以看出初始化的顺序很乱,而且还夹杂着保存了3个寄存器ebx、esi、edi入栈。编译器出于优化的目的打乱了操作顺序,这样一来可读性就很差了,给逆向带来了很大的困难。

    .text:00401289 call    welcome                         ; 显示欢迎信息
    .text:0040128E call    inputChar                       ; 从键盘输入一个字符,结果保存在eax中
    .text:00401293 push    eax
    .text:00401294 call    toUpper                         ; 把从键盘输入的字符转换成大写字符,结果保存在eax中
    .text:00401299 push    40D66666h                       ; float 6.7
    .text:0040129E push    5                               ; int
    .text:004012A0 mov     byte ptr [esp+84h+ch_], al      ; 把toUpper的结果保存在变量ch中
    .text:004012A4 call    mul
    .text:004012A9 push    5                               ; int
    .text:004012AB push    405ED999h                       ; 123.4 part1
    .text:004012B0 push    9999999Ah                       ; 123.4 part2
    .text:004012B5 mov     ebx, eax                        ; mul的结果由eax转存到ebx
    .text:004012B7 call    div
    .text:004012BC sub     esp, 8
    .text:004012BF mov     ecx, 8                          ; 为用rep movsd指令传递结构变量做准备(20h/4=8)
    .text:004012C4 fstp    [esp+98h+res]                   ; div的结果保存到res中
    这一段是调用welcome函数、inputChar函数、mul函数和div函数,跟debug模式下没有太大的区别。

    .text:004012C8 lea     esi, [esp+98h+c_name_0]         ; 传递Person结构变量c作参数
    .text:004012CC mov     edi, esp
    .text:004012CE rep movsd
    .text:004012D0 sub     esp, 20h
    .text:004012D3 mov     ecx, 8                          ; 为用rep movsd指令传递结构变量做准备(20h/4=8)
    .text:004012D8 lea     esi, [esp+0B8h+s_name_0]        ; 传递Person结构变量s作参数
    .text:004012DC mov     edi, esp
    .text:004012DE lea     edx, [esp+0B8h+s_name_0]        ; 把Person结构变量s的地址保存到edx中
    .text:004012E2 rep movsd
    .text:004012E4 push    edx                             ; 再把Person结构变量s的地址作参数(为什么啊?)
    这里在为调用big正常传递完参数s和c之后,又把s的地址作为参数传递到栈中了,难道返回结构变量的函数都会如此?

    .text:004012E5 call    big
    .text:004012EA mov     ecx, 8                          ; 为用rep movsd指令传递结构变量做准备(20h/4=8)
    .text:004012EF mov     esi, eax                        ; 把返回值的地址传递给esi
    .text:004012F1 mov     eax, [esp+0BCh+ch_]             ; 把ch保存到eax中为后面的输出做准备
    .text:004012F5 lea     edi, [esp+0BCh+s_name_0]        ; 把返回值复制到Person结构变量s中(s中原来的值就被覆盖了啊)
    .text:004012F9 rep movsd
    这一段调用big函数并把返回值复制到结构变量s中覆盖了s中原来的值,因为后面不再用s了,所以这样节省一个变量的空间。

    .text:004012FB mov     ecx, 8 

    续补:http://fishasm.blogbus.com/logs/46334685.html

  • 前面的看过了if,接下来看看if/else的逆向。
    用vc建一个空的控制台工程ida4,或者在原来的工程ida4上也行,再新建一个C++源文件ifelse.cpp,内容为:

    #include <stdio.h>

    void main()
    {
        int a;
        printf("请输入一个整数:");
        scanf("%d", &a);
        if(a&1){//判断最后一个二进制位是否为1
            printf("奇数\n");
        }
        else{
            printf("偶数\n");
        }
        printf("欢迎光临bbs.niubcode.com\n");
    }
    如果是在原工程上写的,记得象第3课用的方法那样把别的.cpp文件从编译中Exclude出去。
    在Debug模式下用F7键Build成可执行文件ida4.exe,准备开始练习带if/else控制结构的逆向。
    用IDA加载完毕后,把变量var_4的名字改成a,把提示字符串不对的地方都按照上一部分讲if的时候的方法改好,最后得到的main函数反汇编代码如下:
    .text:00401010 main            proc near               ; CODE XREF: _mainj
    .text:00401010
    .text:00401010 var_44          = byte ptr -44h
    .text:00401010 a               = dword ptr -4
    .text:00401010
    .text:00401010                 push    ebp
    .text:00401011                 mov     ebp, esp
    .text:00401013                 sub     esp, 44h
    .text:00401016                 push    ebx
    .text:00401017                 push    esi
    .text:00401018                 push    edi
    .text:00401019                 lea     edi, [ebp+var_44]
    .text:0040101C                 mov     ecx, 11h
    .text:00401021                 mov     eax, 0CCCCCCCCh
    .text:00401026                 rep stosd
    .text:00401028                 push    offset aIFI     ; "请输入一个整数:"
    .text:0040102D                 call    printf
    .text:00401032                 add     esp, 4
    .text:00401035                 lea     eax, [ebp+a]
    .text:00401038                 push    eax
    .text:00401039                 push    offset aD       ; "%d"
    .text:0040103E                 call    scanf
    .text:00401043                 add     esp, 8
    .text:00401046                 mov     ecx, [ebp+a]
    .text:00401049                 and     ecx, 1
    .text:0040104C                 test    ecx, ecx
    .text:0040104E                 jz      short loc_40105F
    .text:00401050                 push    offset aC       ; "奇数\n"
    .text:00401055                 call    printf
    .text:0040105A                 add     esp, 4
    .text:0040105D                 jmp     short loc_40106C
    .text:0040105F ; ---------------------------------------------------------------------------
    .text:0040105F
    .text:0040105F loc_40105F:                             ; CODE XREF: main+3Ej
    .text:0040105F                 push    offset asc_425030 ; "偶数\n"
    .text:00401064                 call    printf
    .text:00401069                 add     esp, 4
    .text:0040106C
    .text:0040106C loc_40106C:                             ; CODE XREF: main+4Dj
    .text:0040106C                 push    offset aNTBbs_niubcode ; "欢迎光临bbs.niubcode.com\n"
    .text:00401071                 call    printf
    .text:00401076                 add     esp, 4
    .text:00401079                 pop     edi
    .text:0040107A                 pop     esi
    .text:0040107B                 pop     ebx
    .text:0040107C                 add     esp, 44h
    .text:0040107F                 cmp     ebp, esp
    .text:00401081                 call    __chkesp
    .text:00401086                 mov     esp, ebp
    .text:00401088                 pop     ebp
    .text:00401089                 retn
    .text:00401089 main            endp

    在代码的左侧边我们会发现跟只有if的时候不同的地方:有两个转向箭头,注意其中一条是实线一条是虚线,如图:

    虚线表示有条件跳转,实线表示无条件跳转。

    等IDA完全加载完毕后IDA View-A窗口会自动转到图形浏览形式,当然也可以用空格或者右键菜单的Graph view转到图形浏览形式。
    在图形形式下可以看到程序还是分模块的,而且比只有if的时候多了一块。前面一次说到鼠标在转向线和箭头上的时候会提示跳转的信息,其实鼠标移动到某一个模块上时,你也会发现与这个模块相连的转向线会变色。

    象上一次逆向if那样,用右键菜单Group nodes把各个模块换成对应的更直观的C语言代码或者文字,可以看到更清晰的流程图。如图:

    说一下标志寄存器中的ZF位,如果某一个指令的计算结果为0,ZF位会设置为1;如果某一个指令的计算结果非0,ZF位就设置为0。可以这么看:ZF位表示结果是否为0。
    另外,对应控制条件
    if(a&1)
    的指令
    .text:00401046                 mov     ecx, [ebp+a]
    .text:00401049                 and     ecx, 1
    .text:0040104C                 test    ecx, ecx  ;这个test指令感觉多余
    可以看到编译器做的处理是
    ecx = a
    ecx = ecx & 1
    if( ecx!=0 )
    也就是说,编译器把if(a&1)是解释成if((a&1)!=0)。
    if/else结构跟if结构相差不大,就说到这里吧。

  • 前面的看过了if和if/else,再来看看if/else的级联的逆向。
    在控制台工程ida4中通过项目设置把所有.cpp文件都Exclude出去。再新建一个C++源文件ifelseif.cpp,内容为:

    #include <stdio.h>

    void main()
    {
        int score;
        printf("input a score:");
        scanf("%d", &score);
        if(score<0||score>100)
            puts("invalid score!");
        else if(score>=85)
            puts("excellent!");
        else if(score>=60)
            puts("pass");
        else
            puts("fail");
        puts("bbs.niubcode.com,陈宗权");
    }

    在Debug模式下用F7构建成可执行文件ida4.exe,然后用IDA打开ida4.exe。加载后文字形式的main函数的反汇编代码中把变量var_4改名score,把如下:
    .text:00401010 main            proc near               ; CODE XREF: _mainj
    .text:00401010
    .text:00401010 var_44          = byte ptr -44h
    .text:00401010 score           = dword ptr -4
    .text:00401010
    .text:00401010                 push    ebp
    .text:00401011                 mov     ebp, esp
    .text:00401013                 sub     esp, 44h
    .text:00401016                 push    ebx
    .text:00401017                 push    esi
    .text:00401018                 push    edi
    .text:00401019                 lea     edi, [ebp+var_44]
    .text:0040101C                 mov     ecx, 11h
    .text:00401021                 mov     eax, 0CCCCCCCCh
    .text:00401026                 rep stosd
    .text:00401028                 push    offset aInputAScore ; "input a score:"
    .text:0040102D                 call    printf
    .text:00401032                 add     esp, 4
    .text:00401035                 lea     eax, [ebp+score]
    .text:00401038                 push    eax
    .text:00401039                 push    offset aD       ; "%d"
    .text:0040103E                 call    scanf
    .text:00401043                 add     esp, 8
    .text:00401046                 cmp     [ebp+score], 0
    .text:0040104A                 jl      short loc_401052
    .text:0040104C                 cmp     [ebp+score], 100
    .text:00401050                 jle     short loc_401061
    .text:00401052
    .text:00401052 loc_401052:                             ; CODE XREF: main+3Aj
    .text:00401052                 push    offset aInvalidScore ; "invalid score!"
    .text:00401057                 call    puts
    .text:0040105C                 add     esp, 4
    .text:0040105F                 jmp     short loc_401098
    .text:00401061 ; ---------------------------------------------------------------------------
    .text:00401061
    .text:00401061 loc_401061:                             ; CODE XREF: main+40j
    .text:00401061                 cmp     [ebp+score], 85
    .text:00401065                 jl      short loc_401076
    .text:00401067                 push    offset aExcellent ; "excellent!"
    .text:0040106C                 call    puts
    .text:00401071                 add     esp, 4
    .text:00401074                 jmp     short loc_401098
    .text:00401076 ; ---------------------------------------------------------------------------
    .text:00401076
    .text:00401076 loc_401076:                             ; CODE XREF: main+55j
    .text:00401076                 cmp     [ebp+score], 60h
    .text:0040107A                 jl      short loc_40108B
    .text:0040107C                 push    offset aPass    ; "pass"
    .text:00401081                 call    puts
    .text:00401086                 add     esp, 4
    .text:00401089                 jmp     short loc_401098
    .text:0040108B ; ---------------------------------------------------------------------------
    .text:0040108B
    .text:0040108B loc_40108B:                             ; CODE XREF: main+6Aj
    .text:0040108B                 push    offset aFail    ; "fail"
    .text:00401090                 call    puts
    .text:00401095                 add     esp, 4
    .text:00401098
    .text:00401098 loc_401098:                             ; CODE XREF: main+4Fj
    .text:00401098                                         ; main+64j ...
    .text:00401098                 push    offset aBbs_niubcode_c ; "bbs.niubcode.com,陈宗权"
    .text:0040109D                 call    puts
    .text:004010A2                 add     esp, 4
    .text:004010A5                 pop     edi
    .text:004010A6                 pop     esi
    .text:004010A7                 pop     ebx
    .text:004010A8                 add     esp, 44h
    .text:004010AB                 cmp     ebp, esp
    .text:004010AD                 call    __chkesp
    .text:004010B2                 mov     esp, ebp
    .text:004010B4                 pop     ebp
    .text:004010B5                 retn
    .text:004010B5 main            endp
    注意到这次代码左侧边有了更多的箭头,虚线的对应条件转移指令,实线的对应无条件转移指令。

    /****************************************************
    对应到源程序中,我个人推测,
    虚线与虚线交叉意味着复合条件(这里是score<0||score>100),
    虚线与实线交叉意味着if_else结构。
    请各位看到的兄弟给个意见。
    ****************************************************/
    转到图形形式,可以看到分块的程序汇编代码,如图:

    注意跳转线的颜色,条件转移中真和假的跳转线的颜色不同,无条件转移的颜色跟条件转移的不同。
    用右键菜单Group nodes功能把每个模块换成跟汇编指令对应的C源程序语句,写if语句的时候注意编译器对条件的调整,让if语句控制的跳转方向跟跳转线的颜色一致。
    替换过程中可能会发现图形显示格局会变化,不必关心,等全部替换完毕后在所有模块之外找个空白位置点右键选Layout graph重新布局就是了。如果还不满意,可以自行随意拖动调整模块和线条的位置。我这里调整后的结构图是:

    这就是流程图了。

    从这一部分的代码中可以看到,
    1、源程序中的if条件可能会在编译的时候被换成相反的条件;
    2、复合条件会分别变成分离条件和跳转,不过IDA似乎能识别这种连续的cmp和jxx指令为复合条件;
    3、可以看到||运算是短路的,如果第一个为真就不比第二个条件了。

  • 前面尝试了if的各种用法,对控制结构应该有些了解了。接下来可以向for循环的逆向进军了。
    在控制台工程ida4中通过项目设置把所有.cpp文件都Exclude出去。再新建一个C++源文件for.cpp,内容为:

    #include <stdio.h>
    #include <ctype.h>

    void main()
    {
        char url[]="bbs.niubcode.com";
        int asc[sizeof(url)]={0};
        char upper[sizeof(url)]="";
        int i;
        for(i=0; i    {
            asc[i] = url[i];
            upper[i] = islower(url[i])?url[i]+('A'-'a'):url[i];
        }
        for(i=0; i    {
            printf("%d ", asc[i]);
        }
        printf("\n%s\n", upper);
    }

    在Debug模式下用F7构建成可执行文件ida4.exe,然后用IDA打开ida4.exe。
    这一次代码比较长,加载后文字形式的main函数的反汇编代码也比较长,咱们就一段一段的来看吧:

    .text:0040D830 main            proc near               ; CODE XREF: _mainj
    这是main函数的开始。

    .text:0040D830
    .text:0040D830 var_B4          = byte ptr -0B4h
    .text:0040D830 var_74          = dword ptr -74h
    .text:0040D830 var_70          = dword ptr -70h
    .text:0040D830 var_6C          = byte ptr -6Ch
    .text:0040D830 var_6B          = dword ptr -6Bh
    .text:0040D830 var_67          = dword ptr -67h
    .text:0040D830 var_63          = dword ptr -63h
    .text:0040D830 var_5F          = dword ptr -5Fh
    .text:0040D830 var_58          = dword ptr -58h
    .text:0040D830 var_54          = byte ptr -54h
    .text:0040D830 var_14          = dword ptr -14h
    .text:0040D830 var_10          = dword ptr -10h
    .text:0040D830 var_C           = dword ptr -0Ch
    .text:0040D830 var_8           = dword ptr -8
    .text:0040D830 var_4           = byte ptr -4
    这一段是变量名,这显然比实际的变量名多很多。
    第一个var_B4是保留的0x40个字节的空间,不用理会。
    var_70是变量i,占4个字节。
    var_6C是字符数组upper,占17(0x11)个字节,到var_5F为止。
    var_58是整型数组asc,占68(0x44)个字节。
    var_14是字符数组url,占17(0x11)个字节,到var_4为止。
    /********************************************************
    问题是,var_74是干什么的呢?
    ********************************************************/

    .text:0040D830
    .text:0040D830                 push    ebp
    .text:0040D831                 mov     ebp, esp
    .text:0040D833                 sub     esp, 0B4h
    .text:0040D839                 push    ebx
    .text:0040D83A                 push    esi
    .text:0040D83B                 push    edi
    .text:0040D83C                 lea     edi, [ebp+var_B4]
    .text:0040D842                 mov     ecx, 2Dh
    .text:0040D847                 mov     eax, 0CCCCCCCCh
    .text:0040D84C                 rep stosd
    这一段是把局部变量的空间全部用0xCC填充,没有其它作用。

    .text:0040D84E                 mov     eax, dword ptr ds:aBbs_niubcode_c ; "bbs.niubcode.com"
    .text:0040D853                 mov     [ebp+var_14], eax
    .text:0040D856                 mov     ecx, dword ptr ds:aBbs_niubcode_c+4
    .text:0040D85C                 mov     [ebp+var_10], ecx
    .text:0040D85F                 mov     edx, dword ptr ds:aBbs_niubcode_c+8
    .text:0040D865                 mov     [ebp+var_C], edx
    .text:0040D868                 mov     eax, dword ptr ds:aBbs_niubcode_c+0Ch
    .text:0040D86D                 mov     [ebp+var_8], eax
    .text:0040D870                 mov     cl, byte ptr ds:aBbs_niubcode_c+10h
    .text:0040D876                 mov     [ebp+var_4], cl
    这一段是给字符数组url初始化。对应语句
    char url[]="bbs.niubcode.com";
    可以看到:
    1、数组的初始化实际上也是在每次运行时用操作指令放置数据的,而不是象前面的字符串常量那样直接用db放置的。
    2、用字符串常量初始化字符数组实际上是一方面在.rdata段产生了一个字符串常量,一方面在栈中产生了一个字符数组,把.rdata段中的字符串常量复制了过来。
    3、VC数组的数据放置不是一个字节一个字节地放置,而是尽量一次4个字节,只有不够4个字节的才一个字节一个字节处理,也因此多出来了几个虚假变量名(var_10~var_4)。
    4、有点疑问:为什么它用的是一系列mov指令,不用stosb或者stosd呢?

    .text:0040D879                 mov     [ebp+var_58], 0
    .text:0040D880                 mov     ecx, 10h
    .text:0040D885                 xor     eax, eax
    .text:0040D887                 lea     edi, [ebp+var_54]
    .text:0040D88A                 rep stosd
    这一段是给整数数组asc初始化。对应语句
    int asc[sizeof(url)]={0};
    指定初始值用mov指令设置,初始值是立即数。初始值不够的元素(10h个)是用0(xor eax, eax)来初始化的。

    .text:0040D88C                 mov     dl, ds:byte_422C60
    .text:0040D892                 mov     [ebp+var_6C], dl
    .text:0040D895                 xor     eax, eax
    .text:0040D897                 mov     [ebp+var_6B], eax
    .text:0040D89A                 mov     [ebp+var_67], eax
    .text:0040D89D                 mov     [ebp+var_63], eax
    .text:0040D8A0                 mov     [ebp+var_5F], eax
    这一段是给字符数组upper初始化。对应语句
    char upper[sizeof(url)]="";
    初始化方式跟前面url的不同,因为指定了数组大小,用了一个空字符串(只有一个字符\0),所以是第一个元素用了指定值\0(ds:byte_422C60)来初始化,别的元素用的0来初始化。
    /********************************************
    注意这里的4字节数据操作地址居然没有4字节对齐。
    ********************************************/

    .text:0040D8A3                 mov     [ebp+var_70], 0
    .text:0040D8AA                 jmp     short loc_40D8B5
    .text:0040D8AC ; ---------------------------------------------------------------------------
    这一段是for循环的准备部分,给变量i赋初值0,然后跳转到条件判断部分。对应语句
    for(i=0;
    这部分只执行一次,放在了前面,IDA拿一条长长的横线把它跟后面反复执行的部分隔开了。

    .text:0040D8AC
    .text:0040D8AC loc_40D8AC:                             ; CODE XREF: main+D4j
    .text:0040D8AC                 mov     ecx, [ebp+var_70]
    .text:0040D8AF                 add     ecx, 1
    .text:0040D8B2                 mov     [ebp+var_70], ecx
    这一段是for循环的调整部分,对应语句
    i++
    把变量i的值增加1。
    /***********************************
    增加1为什么不用inc指令呢?
    ***********************************/

    .text:0040D8B5
    .text:0040D8B5 loc_40D8B5:                             ; CODE XREF: main+7Aj
    .text:0040D8B5                 cmp     [ebp+var_70], 11h
    .text:0040D8B9                 jnb     short loc_40D906
    这一段是for循环的条件判断,对应语句
    i比较循环变量i是否小于17,如果不小于17(也就是达到17了)就跳出循环。

    .text:0040D8BB                 mov     edx, [ebp+var_70]  ;edx=i
    .text:0040D8BE                 movsx   eax, byte ptr [ebp+edx+var_14] ;eax=(int)url[i]
    .text:0040D8C3                 mov     ecx, [ebp+var_70]  ;ecx=i
    .text:0040D8C6                 mov     [ebp+ecx*4+var_58], eax  ;asc[i]=eax
    这一段是把字符数组url中下标为i的元素扩展成整数赋值给整数数组asc中下标为i的元素,对应语句
    asc[i] = url[i];
    注意其中ecx*4是因为一个int变量占4个字节。
    奇怪的是,VC不但没有优化对变量i(也就是var_70)的访问,而且还分别用了edx和ecx两个寄存器。智能的编译器应该能优化只从内存读取一次i。

    .text:0040D8CA                 mov     edx, [ebp+var_70]
    .text:0040D8CD                 movsx   eax, byte ptr [ebp+edx+var_14]
    .text:0040D8D2                 push    eax
    .text:0040D8D3                 call    islower
    .text:0040D8D8                 add     esp, 4
    这一段是调用islower来判断url[i]是不是一个小写字母,对应代码
    islower(url[i])
    结果在eax中。

    .text:0040D8DB                 test    eax, eax    ;看eax的值
    .text:0040D8DD                 jz      short loc_40D8EF   ;是0就跳转到横线下面
    .text:0040D8DF                 mov     ecx, [ebp+var_70]  ;ecx=i
    .text:0040D8E2                 movsx   edx, byte ptr [ebp+ecx+var_14] ;edx=url[i]
    .text:0040D8E7                 sub     edx, 20h    ;edx-=0x20,变成对应的大写字母
    .text:0040D8EA                 mov     [ebp+var_74], edx  ;temp=edx
    .text:0040D8ED                 jmp     short loc_40D8FA   ;跳转到下一段
    .text:0040D8EF ; ---------------------------------------------------------------------------
    .text:0040D8EF
    .text:0040D8EF loc_40D8EF:                             ; CODE XREF: main+ADj
    .text:0040D8EF                 mov     eax, [ebp+var_70]  ;eax=i
    .text:0040D8F2                 movsx   ecx, byte ptr [ebp+eax+var_14] ;
    .text:0040D8F7                 mov     [ebp+var_74], ecx
    这一段是计算三目运算符的,对应代码
    islower(url[i])?url[i]+('A'-'a'):url[i]
    如果islower的结果(在eax中)为0表明不是小写字母,跳转到计算冒号后面的部分;如果非0表明是小写字母,继续计算冒号之前的部分转换成对应的大写字母。
    可以看到VC把+('A'-'a')做了预先计算,变成了-20。
    /*************************************************
    到这里知道var_74的作用了:用来保存三目运算符的临时计算结果
    *************************************************/

    .text:0040D8FA
    .text:0040D8FA loc_40D8FA:                             ; CODE XREF: main+BDj
    .text:0040D8FA                 mov     edx, [ebp+var_70] ;edx=i
    .text:0040D8FD                 mov     al, byte ptr [ebp+var_74];al=(char)temp
    .text:0040D900                 mov     [ebp+edx+var_6C], al ;upper[i]=al
    这一段把上一段保存的临时计算结果转存到upper[i]中,对应代码
    upper[i]=?:
    从上面可以看到字符类型进行算术运算的时候会转成int类型,计算完毕之后再转回字符类型。

    .text:0040D904                 jmp     short loc_40D8AC
    .text:0040D906 ; ---------------------------------------------------------------------------
    这一段是直接跳转到for循环的调整部分,第一个for循环构成一个闭环。

    .text:0040D906
    .text:0040D906 loc_40D906:                             ; CODE XREF: main+89j
    .text:0040D906                 mov     [ebp+var_70], 0
    .text:0040D90D                 jmp     short loc_40D918
    .text:0040D90F ; ---------------------------------------------------------------------------
    这一段是第二个for循环的初始化部分,对应语句
    for(i=0;
    跟第一个循环的一样,跳转到这个for循环的条件判断部分。

    .text:0040D90F
    .text:0040D90F loc_40D90F:                             ; CODE XREF: main+103j
    .text:0040D90F                 mov     ecx, [ebp+var_70]
    .text:0040D912                 add     ecx, 1
    .text:0040D915                 mov     [ebp+var_70], ecx
    这一段是调整部分,跟前一个调整部分一样。
    /************************************************************
    我故意把源程序写成的
    ++i
    可是从汇编代码来看,在for循环的调整部分用i++与++i并无区别。
    ************************************************************/

    .text:0040D918
    .text:0040D918 loc_40D918:                             ; CODE XREF: main+DDj
    .text:0040D918                 cmp     [ebp+var_70], 10h
    .text:0040D91C                 jnb     short loc_40D935
    这一段是for循环的条件判断,跟上一个for循环的一样,不过我故意把循环次数减1了,对应语句
    i比较循环变量i是否小于16,如果不小于16(也就是达到16了)就跳出循环。

    .text:0040D91E                 mov     edx, [ebp+var_70] ;edx=i
    .text:0040D921                 mov     eax, [ebp+edx*4+var_58] ;eax=asc[i]
    .text:0040D925                 push    eax
    .text:0040D926                 push    offset aD       ; "%d "
    .text:0040D92B                 call    printf
    .text:0040D930                 add     esp, 8
    这一段是调用printf输出一个元素asc[i],对应语句为
    printf("%d ", asc[i]);

    .text:0040D933                 jmp     short loc_40D90F
    .text:0040D935 ; ---------------------------------------------------------------------------
    这一段是直接跳转到for循环的调整部分,第二个for循环也构成一个闭环。

    .text:0040D935
    .text:0040D935 loc_40D935:                             ; CODE XREF: main+ECj
    .text:0040D935                 lea     ecx, [ebp+var_6C]
    .text:0040D938                 push    ecx
    .text:0040D939                 push    offset aS_0     ; "\n%s\n"
    .text:0040D93E                 call    printf
    .text:0040D943                 add     esp, 8
    这一段是输出变成大写字母后的字符串upper。

    .text:0040D946                 pop     edi
    .text:0040D947                 pop     esi
    .text:0040D948                 pop     ebx
    .text:0040D949                 add     esp, 0B4h
    .text:0040D94F                 cmp     ebp, esp
    .text:0040D951                 call    __chkesp
    .text:0040D956                 mov     esp, ebp
    .text:0040D958                 pop     ebp
    .text:0040D959                 retn
    这一段是恢复寄存器和栈检查,在几乎每个main函数中都一样。

    .text:0040D959 main            endp
    main函数至此结束。

    先来改名。在IDA View-A文本形式下双击var_B4,进入Stack frame,
    用右键菜单Array...把var_B4改成长度为64的db数组,
    把var_74改成dd变量__temp,
    把var_70改成dd变量i,
    把var_6C改成17个元素的db数组upper,
    把var_58改成17个元素的dd数组asc,
    把var_14改成17个元素的db数组url。如图:

    回到IDA View-A窗口,代码可读性好些了。
    .text:0040D830 main            proc near               ; CODE XREF: _mainj
    .text:0040D830
    .text:0040D830 var_B4          = byte ptr -0B4h
    .text:0040D830 __temp          = dword ptr -74h
    .text:0040D830 i               = dword ptr -70h
    .text:0040D830 upper           = byte ptr -6Ch
    .text:0040D830 asc             = dword ptr -58h
    .text:0040D830 url             = byte ptr -14h
    .text:0040D830
    .text:0040D830                 push    ebp
    .text:0040D831                 mov     ebp, esp
    .text:0040D833                 sub     esp, 0B4h
    .text:0040D839                 push    ebx
    .text:0040D83A                 push    esi
    .text:0040D83B                 push    edi
    .text:0040D83C                 lea     edi, [ebp+var_B4]
    .text:0040D842                 mov     ecx, 2Dh
    .text:0040D847                 mov     eax, 0CCCCCCCCh
    .text:0040D84C                 rep stosd
    .text:0040D84E                 mov     eax, dword ptr ds:aBbs_niubcode_c ; "bbs.niubcode.com"
    .text:0040D853                 mov     dword ptr [ebp+url], eax
    .text:0040D856                 mov     ecx, dword ptr ds:aBbs_niubcode_c+4
    .text:0040D85C                 mov     dword ptr [ebp+url+4], ecx
    .text:0040D85F                 mov     edx, dword ptr ds:aBbs_niubcode_c+8
    .text:0040D865                 mov     dword ptr [ebp+url+8], edx
    .text:0040D868                 mov     eax, dword ptr ds:aBbs_niubcode_c+0Ch
    .text:0040D86D                 mov     dword ptr [ebp+url+0Ch], eax
    .text:0040D870                 mov     cl, byte ptr ds:aBbs_niubcode_c+10h
    .text:0040D876                 mov     [ebp+url+10h], cl
    .text:0040D879                 mov     [ebp+asc], 0
    .text:0040D880                 mov     ecx, 10h
    .text:0040D885                 xor     eax, eax
    .text:0040D887                 lea     edi, [ebp+asc+4]
    .text:0040D88A                 rep stosd
    .text:0040D88C                 mov     dl, ds:byte_422C60
    .text:0040D892                 mov     [ebp+upper], dl
    .text:0040D895                 xor     eax, eax
    .text:0040D897                 mov     dword ptr [ebp+upper+1], eax
    .text:0040D89A                 mov     dword ptr [ebp+upper+5], eax
    .text:0040D89D                 mov     dword ptr [ebp+upper+9], eax
    .text:0040D8A0                 mov     dword ptr [ebp+upper+0Dh], eax
    .text:0040D8A3                 mov     [ebp+i], 0
    .text:0040D8AA                 jmp     short loc_40D8B5
    .text:0040D8AC ; ---------------------------------------------------------------------------
    .text:0040D8AC
    .text:0040D8AC loc_40D8AC:                             ; CODE XREF: main+D4j
    .text:0040D8AC                 mov     ecx, [ebp+i]
    .text:0040D8AF                 add     ecx, 1
    .text:0040D8B2                 mov     [ebp+i], ecx
    .text:0040D8B5
    .text:0040D8B5 loc_40D8B5:                             ; CODE XREF: main+7Aj
    .text:0040D8B5                 cmp     [ebp+i], 11h
    .text:0040D8B9                 jnb     short loc_40D906
    .text:0040D8BB                 mov     edx, [ebp+i]
    .text:0040D8BE                 movsx   eax, [ebp+edx+url]
    .text:0040D8C3                 mov     ecx, [ebp+i]
    .text:0040D8C6                 mov     [ebp+ecx*4+asc], eax
    .text:0040D8CA                 mov     edx, [ebp+i]
    .text:0040D8CD                 movsx   eax, [ebp+edx+url]
    .text:0040D8D2                 push    eax
    .text:0040D8D3                 call    islower
    .text:0040D8D8                 add     esp, 4
    .text:0040D8DB                 test    eax, eax
    .text:0040D8DD                 jz      short loc_40D8EF
    .text:0040D8DF                 mov     ecx, [ebp+i]
    .text:0040D8E2                 movsx   edx, [ebp+ecx+url]
    .text:0040D8E7                 sub     edx, 20h
    .text:0040D8EA                 mov     [ebp+__temp], edx
    .text:0040D8ED                 jmp     short loc_40D8FA
    .text:0040D8EF ; ---------------------------------------------------------------------------
    .text:0040D8EF
    .text:0040D8EF loc_40D8EF:                             ; CODE XREF: main+ADj
    .text:0040D8EF                 mov     eax, [ebp+i]
    .text:0040D8F2                 movsx   ecx, [ebp+eax+url]
    .text:0040D8F7                 mov     [ebp+__temp], ecx
    .text:0040D8FA
    .text:0040D8FA loc_40D8FA:                             ; CODE XREF: main+BDj
    .text:0040D8FA                 mov     edx, [ebp+i]
    .text:0040D8FD                 mov     al, byte ptr [ebp+__temp]
    .text:0040D900                 mov     [ebp+edx+upper], al
    .text:0040D904                 jmp     short loc_40D8AC
    .text:0040D906 ; ---------------------------------------------------------------------------
    .text:0040D906
    .text:0040D906 loc_40D906:                             ; CODE XREF: main+89j
    .text:0040D906                 mov     [ebp+i], 0
    .text:0040D90D                 jmp     short loc_40D918
    .text:0040D90F ; ---------------------------------------------------------------------------
    .text:0040D90F
    .text:0040D90F loc_40D90F:                             ; CODE XREF: main+103j
    .text:0040D90F                 mov     ecx, [ebp+i]
    .text:0040D912                 add     ecx, 1
    .text:0040D915                 mov     [ebp+i], ecx
    .text:0040D918
    .text:0040D918 loc_40D918:                             ; CODE XREF: main+DDj
    .text:0040D918                 cmp     [ebp+i], 10h
    .text:0040D91C                 jnb     short loc_40D935
    .text:0040D91E                 mov     edx, [ebp+i]
    .text:0040D921                 mov     eax, [ebp+edx*4+asc]
    .text:0040D925                 push    eax
    .text:0040D926                 push    offset aD       ; "%d "
    .text:0040D92B                 call    printf
    .text:0040D930                 add     esp, 8
    .text:0040D933                 jmp     short loc_40D90F
    .text:0040D935 ; ---------------------------------------------------------------------------
    .text:0040D935
    .text:0040D935 loc_40D935:                             ; CODE XREF: main+ECj
    .text:0040D935                 lea     ecx, [ebp+upper]
    .text:0040D938                 push    ecx
    .text:0040D939                 push    offset aS_0     ; "\n%s\n"
    .text:0040D93E                 call    printf
    .text:0040D943                 add     esp, 8
    .text:0040D946                 pop     edi
    .text:0040D947                 pop     esi
    .text:0040D948                 pop     ebx
    .text:0040D949                 add     esp, 0B4h
    .text:0040D94F                 cmp     ebp, esp
    .text:0040D951                 call    __chkesp
    .text:0040D956                 mov     esp, ebp
    .text:0040D958                 pop     ebp
    .text:0040D959                 retn
    .text:0040D959 main            endp

    这次咱们看到的箭头与以往的不同,如图:

    一个实线箭头与一个虚线箭头相邻不相交,一个反向的粗实线箭头与这两个箭头都交叉,这应该是for循环的典型结构。
    for循环中如果嵌套了别的控制结构,箭头会复杂一些,无非也就是多了一些箭头,典型结构依然存在,如图。

    转到图形形式,可以看到分块的程序汇编代码,用右键菜单Group nodes功能把每个模块换成跟汇编指令对应的C源程序语句,调整判断条件与颜色一致。
    完成后程序的流程图为:

    可以看到三目运算符?:的流程图跟ifelse的一样。还有for循环中的循环条件的流程图也跟ifelse一样。
        在看图形形式的过程中,咱们会发现程序稍微复杂一点,IDA产生的图就比较乱了,而且往往在用Group nodes时会莫名其妙地变换模块的位置,这应该算是IDA的一个bug吧。另外就是虽然可以调整线条和模块的位置,但是不能调整起点和终点在模块上的位置,这是一个不小的缺陷。