首页
论坛
课程
招聘
[原创]第一章:1.4、循环分支的识别技巧
2010-6-1 14:31 13919

[原创]第一章:1.4、循环分支的识别技巧

2010-6-1 14:31
13919
循环几乎是在我们的代码中应用频率最高的一种编程方式,例如拷贝字符串、笨拙的从1+2+3...100的程序等等,数不尽数,本小节就带领各位了解循环分支,并熟悉各种循环互相之间的差异。
    我们都知道c语言的循环主要分为三种,既for、while与do-while,但是当我们以逆向的角度来看待这些循环结构的时候,我们会发现其本质上只有一种,而且即便我们再次细分的话,也仅仅能分离出两种情况。但是笔者仍然会将这一小节分为三部分讲解,以求尽量照顾读者们的接受能力。

1.4.1、do-while循环的识别技巧

    “嘿!为什么要先学这里,不应该是for循环吗?”
    相信很多读者都会产生以上疑问,要的就是这种效果!就让我们带着这个疑问开始这一节的学习,先看源码:

int _tmain(int argc, _TCHAR* argv[])
{
    int nNum = 26;
    printf("Mom! I can sing my ABC!\r\n");

    // 听!小Baby开始唱ABC了……
    do 
    {
        printf("%c ",0x41+(26-nNum) );
        nNum--;
    } while (nNum>0);
    return 0;
}

    现在让我们从反汇编的角度看看小Baby是怎么唱歌的:

004117DE  MOV DWORD PTR SS:[EBP-8], 1A             ; 16进制的0x1A等于10进制的26,不要在这里犯晕
004117E7  PUSH Test_0.00415C18                     ; /format = "Mom! I can sing my ABC!
004117EC  CALL DWORD PTR DS:[<&MSVCR90D.printf>]   ; \printf
004117F2  ADD ESP, 4
004117FC  /MOV EAX, 1A                             ; <--!!!!!
00411801  |SUB EAX, DWORD PTR SS:[EBP-8]
00411804  |ADD EAX, 41                             ; EAX = 41+(1A-[EBP-8]) = 0x41+(26-nNum)
00411809  |PUSH EAX                                ; /<%c>
0041180A  |PUSH Test_0.0041573C                    ; |format = "%c "
0041180F  |CALL DWORD PTR DS:[<&MSVCR90D.printf>]  ; \printf
00411815  |ADD ESP, 8
0041181F  |MOV EAX, DWORD PTR SS:[EBP-8]
00411822  |SUB EAX, 1
00411825  |MOV DWORD PTR SS:[EBP-8], EAX           ; [EBP-8]-- = nNum--
00411828  |CMP DWORD PTR SS:[EBP-8], 0             ; 看[EBP-8]是否仍大于0,是的话则跳转到标记处继续
0041182C  \JG SHORT Test_0.004117FC

    是不是感觉很容易理解?这似乎与我们前面所讲的内容差距不大,那么就让我们在看看Release版的:

00401000  PUSH ESI
00401001  PUSH EDI
00401002  MOV EDI, DWORD PTR DS:[<&MSVCR90.printf>>;  MSVCR90.printf
00401008  PUSH Test_0.004020F4                     ; /format = "Mom! I can sing my ABC!
0040100D  CALL EDI                                 ; \printf
0040100F  ADD ESP, 4
00401012  MOV ESI, 41                              ; 将ESI加0x41后准备
00401017  /PUSH ESI                                ; <--!!!!!
00401018  |PUSH Test_0.00402110                    ;  ASCII "%c "
0040101D  |CALL EDI                                ; 又见此优化
0040101F  |INC ESI                                 ; 直接将ESI加1
00401020  |ADD ESP, 8                              ; 为什么要将平衡堆栈操作放到这里?
          |                                        ; 这与CPU的流水线有关系,我们目前先不深究。
          |                                        ;
00401023  |CMP ESI, 5B                             ; 快看看,直接与0x5B相比较了(Z的ASCII码是0x5A)
00401026  \JL SHORT Test_0.00401017                ; 如果小于此值则继续
00401028  POP EDI
00401029  XOR EAX, EAX
0040102B  POP ESI
0040102C  RETN

    通过以上代码我们不难看出,编译器直接将我们的代码优化为以下模式了:

int _tmain(int argc, _TCHAR* argv[])
{
    int nNum = 0x41;
    printf("Mom! I can sing my ABC!\r\n");

    // 听!小Baby开始唱ABC了……
    do 
    {
        printf("%c ",nNum++ );
    } while (nNum<0x5B);
    return 0;
}

    看看,多么聪明的编译器!直接看透了我们代码的本质!在感叹之余,不要忘记总结反汇编代码的特点,我们现在可以看到的最大的特点就是一个有条件判断的向上跳转,因此可以这样理解“如果我们看到了一个判断分支的跳转是向上的,那么这必然就是一个循环”。

特点总结:
DO_TAG:
  ......
  ......
  CMP XXX,XXX
  JXX DO_TAG

1.4.2、while循环的识别技巧

    先看源码:

int _tmain(int argc, _TCHAR* argv[])
{
    int nNum = 26;
    printf("Mom! I can sing my ABC!\r\n");

    // 听!小Baby开始唱ABC了……
    while(nNum>0)
    {
        printf("%c ",0x41+(26-nNum) );
        nNum--;
    }
    return 0;
}

    再看Debug版反汇编代码:

004117DE  MOV DWORD PTR SS:[EBP-8], 1A
004117E7  PUSH Test_0.00415C18                     ; /format = "Mom! I can sing my ABC!
004117EC  CALL DWORD PTR DS:[<&MSVCR90D.printf>]   ; \printf
004117F2  ADD ESP, 4
004117FC  /CMP DWORD PTR SS:[EBP-8], 0
00411800  |JLE SHORT Test_0.00411830               ; 多了个判断,如果其小于等于0则跳出循环
00411802  |MOV EAX, 1A
00411807  |SUB EAX, DWORD PTR SS:[EBP-8]
0041180A  |ADD EAX, 41
0041180F  |PUSH EAX                                ; /<%c>
00411810  |PUSH Test_0.0041573C                    ; |format = "%c "
00411815  |CALL DWORD PTR DS:[<&MSVCR90D.printf>]  ; \printf
0041181B  |ADD ESP, 8
00411825  |MOV EAX, DWORD PTR SS:[EBP-8]
00411828  |SUB EAX, 1
0041182B  |MOV DWORD PTR SS:[EBP-8], EAX
0041182E  \JMP SHORT Test_0.004117FC

    细心的读者可能发现了,这与do-while循环几乎如出一辙,仅仅在循环头部多了两条用于判断是否跳出循环的指令,那Release版的又会是怎样的呢?

00401000  PUSH ESI
00401001  PUSH EDI
00401002  MOV EDI, DWORD PTR DS:[<&MSVCR90.printf>>;  MSVCR90.printf
00401008  PUSH Test_0.004020F4                     ; /format = "Mom! I can sing my ABC!
0040100D  CALL EDI                                 ; \printf
0040100F  ADD ESP, 4
00401012  MOV ESI, 41
00401017  /PUSH ESI
00401018  |PUSH Test_0.00402110                    ;  ASCII "%c "
0040101D  |CALL EDI
0040101F  |INC ESI
00401020  |ADD ESP, 8
00401023  |CMP ESI, 5B
00401026  \JL SHORT Test_0.00401017
00401028  POP EDI
00401029  XOR EAX, EAX
0040102B  POP ESI
0040102C  RETN

    请不要怀疑我复制错了代码,事实就是这样!do-while与while生成的Release版在这里看就是100%完全相同的。
    编译器很明显的已经探测出了我们的循环判断用的是一个常量,因此就不存在首次执行条件不匹配的情况。既然如此,它为什么还要在循环前面加上那个判断分支来浪费我们的空间与时间呢?
    当然,如果我们将它的判断条件改为一个变量,那么就是另外一番景象了:

00401000  PUSH EBX
00401001  MOV EBX, DWORD PTR DS:[<&MSVCR90.printf>>;  MSVCR90.printf
00401007  PUSH EDI
00401008  PUSH Test_0.004020F4                     ; /format = "Mom! I can sing my ABC!
0040100D  CALL EBX                                 ; \printf
0040100F  MOV EDI, DWORD PTR SS:[ESP+10]           ; 取得参数(其实就是main函数的argc)
00401013  ADD ESP, 4
00401016  TEST EDI, EDI                            ; 测试参数是否为0
00401018  JLE SHORT Test_0.00401034                ; 如果为0则跳出循环
0040101A  PUSH ESI
0040101B  MOV ESI, 5B                              ; 0x5B = 0x41+26 
00401020  SUB ESI, EDI                             ; 用0x5B减去参数
00401022  /PUSH ESI
00401023  |PUSH Test_0.00402110                    ;  ASCII "%c "
00401028  |CALL EBX
0040102A  |DEC EDI                                 ; 参数减1
0040102B  |ADD ESP, 8
0040102E  |INC ESI                                 ; ESI加1
0040102F  |TEST EDI, EDI
00401031  \JG SHORT Test_0.00401022                ; 如果参数大于ESI则结束循环
00401033  POP ESI
00401034  POP EDI
00401035  XOR EAX, EAX
00401037  POP EBX
00401038  RETN

    我们可以看出用变量做判断条件很明显与常量不一样,而关于优化,很显然他只是单纯的将我们的“0x41+(26-argc)”优化成“0x5B-argc”。

特点总结:
WHILE_TAG:
  CMP XXX,XXX
  JXX WHILE_END_TAG
  ......
  ......
  CMP XXX,XXX
  JXX WHILE_TAG
WHILE_END_TAG:

1.4.3、for循环的识别技巧

    for循环与while循环本质上都是一样的,唯一的不同在于for循环在循环体内多了一个步长部分,接下来我们一起看看for循环的样子,先看源码:

int _tmain(int argc, _TCHAR* argv[])
{
    printf("Mom! I can sing my ABC!\r\n");

    // 听!小Baby开始唱ABC了……
    for (int nNum = 26;nNum>0;nNum--)
    {
        printf("%c ",0x41+(26-nNum) );
    }
    return 0;
}

    接下来我们再看看Debug版的反汇编代码:

004117E0  PUSH Test_0.00415C18                     ; /format = "Mom! I can sing my ABC!
004117E5  CALL DWORD PTR DS:[<&MSVCR90D.printf>]   ; \printf
004117EB  ADD ESP, 4
004117F5  MOV DWORD PTR SS:[EBP-8], 1A
004117FC  JMP SHORT Test_0.00411807
004117FE  /MOV EAX, DWORD PTR SS:[EBP-8]           ; / 步长控制部分开始
00411801  |SUB EAX, 1                              ; | 步长为1
00411804  |MOV DWORD PTR SS:[EBP-8], EAX           ; \ 将操作后的结果传回给局部变量,步长操作结束
00411807  |CMP DWORD PTR SS:[EBP-8], 0
0041180B  |JLE SHORT Test_0.00411832               ; 如果此变量小于等于0则结束循环
0041180D  |MOV EAX, 1A
00411812  |SUB EAX, DWORD PTR SS:[EBP-8]
00411815  |ADD EAX, 41                             ; 0x41+(0x1A-[EBP-8])
0041181A  |PUSH EAX                                ; /<%c>
0041181B  |PUSH Test_0.0041573C                    ; |format = "%c "
00411820  |CALL DWORD PTR DS:[<&MSVCR90D.printf>]  ; \printf
00411826  |ADD ESP, 8
00411830  \JMP SHORT Test_0.004117FE               ; 跳到循环头部

    看到这里不知道各位读者们是否发现了什么,记得我当时学到这里时,直觉上认识到了以下两点:
(1、)显然循环语句是do-while先诞生的,而后是while,最后才是for,这从侧面上讲for应该是最“高级”的了。
(2、)从执行效率上看,代码最短且判断最少的就是do-while循环了。

    Release版反汇编:

00401000  PUSH ESI
00401001  PUSH EDI
00401002  MOV EDI, DWORD PTR DS:[<&MSVCR90.printf>>;  MSVCR90.printf
00401008  PUSH Test_0.004020F4                     ; /format = "Mom! I can sing my ABC!
0040100D  CALL EDI                                 ; \printf
0040100F  ADD ESP, 4
00401012  MOV ESI, 41
00401017  /PUSH ESI
00401018  |PUSH Test_0.00402110                    ;  ASCII "%c "
0040101D  |CALL EDI
0040101F  |INC ESI
00401020  |ADD ESP, 8
00401023  |CMP ESI, 5B
00401026  \JL SHORT Test_0.00401017
00401028  POP EDI
00401029  XOR EAX, EAX
0040102B  POP ESI
0040102C  RETN

    又是常量惹的祸,这段代码与do-while、while一模一样,有疑问的读者可以返回上面仔细观察一下,连地址都是一样的。
    现在正好印证了我开篇时讲的一句话“其本质上只有一种”,很明显的,我们的while与for都是以do-while为基础框架的,只不过是在里面加了一些小判断。为了让各位读者更清晰的看到它们之间的异同,我再为各位献上一个变量版的:

00401000  PUSH EBX
00401001  MOV EBX, DWORD PTR DS:[<&MSVCR90.printf>>;  MSVCR90.printf
00401007  PUSH EDI
00401008  PUSH Test_0.004020F4                     ; /format = "Mom! I can sing my ABC!
0040100D  CALL EBX                                 ; \printf
0040100F  MOV EDI, DWORD PTR SS:[ESP+10]
00401013  ADD ESP, 4
00401016  TEST EDI, EDI
00401018  JLE SHORT Test_0.00401034
0040101A  PUSH ESI
0040101B  MOV ESI, 5B
00401020  SUB ESI, EDI
00401022  /PUSH ESI
00401023  |PUSH Test_0.00402110                    ;  ASCII "%c "
00401028  |CALL EBX
0040102A  |DEC EDI
0040102B  |ADD ESP, 8
0040102E  |INC ESI
0040102F  |TEST EDI, EDI
00401031  \JG SHORT Test_0.00401022
00401033  POP ESI
00401034  POP EDI
00401035  XOR EAX, EAX
00401037  POP EBX
00401038  RETN

    再重申一下,笔者并没有搞错,以变量为判断条件的for循环与while循环所生成的代码是完全相同的,连地址都一样……

特点总结:
FOR_START_TAG:
    初始化块
    JMP CMP_TAG
STEP_TAG:
    步长块
CMP_TAG:
    反条件判断
    JXX  FOR_END_TAG
    ……
    ……
JXX STEP_TAG

    到此,我们应该可以做一个总结了,Debug版下三种循环各不相同,Release版下可总结如下:
(1、)当循环采用常量为判断条件时,相同逻辑的三种循环生成的代码完全相同。
(2、)当循环采用变量为判断条件时,相同逻辑的while与for生成的代码完全相同,而do-while则自成一格。

小插曲:循环体的语句外提优化

    我们看看下面这段代码:

int _tmain(int argc, _TCHAR* argv[])
{
        printf("Mom! I can sing my ABC!\r\n");

        // 听!小Baby开始唱ABC了……
        for (int nNum = 24;nNum>0;nNum--)
        {
                argc = (int)argv;
                printf("%c ",0x41+(26-nNum) );
        }
        printf("%p",argc);
        return 0;
}

   通过这段代码我们可以发现一处可以优化的地方,就是“argc = (int)argv”这条语句,很明显

00401000  PUSH ESI
00401001  PUSH EDI
00401002  MOV EDI, DWORD PTR DS:[<&MSVCR90.printf>>;  MSVCR90.printf
00401008  PUSH Test_0.004020F4                     ; /format = "Mom! I can sing my ABC!
0040100D  CALL EDI                                 ; \printf
0040100F  ADD ESP, 4
00401012  MOV ESI, 43
00401017  /PUSH ESI
00401018  |PUSH Test_0.00402110                    ;  ASCII "%c "
0040101D  |CALL EDI
0040101F  |INC ESI
00401020  |ADD ESP, 8
00401023  |CMP ESI, 5B
00401026  \JL SHORT Test_0.00401017
00401028  MOV EAX, DWORD PTR SS:[ESP+10]           ; EAX = (int)argv; <--注意这里
0040102C  PUSH EAX
0040102D  PUSH Test_0.00402114                     ;  ASCII "%p"
00401032  CALL EDI
00401034  ADD ESP, 8
00401037  POP EDI
00401038  XOR EAX, EAX
0040103A  POP ESI
0040103B  RETN

    由上面代码可知,循环体内很明显没有我们“argc = (int)argv;”的代码,再向下看一行才知道,这段代码被提到了外面,这就是编译进行的代码外提优化。
    本小节到此就结束了,笔者在本小节向大家详细的介绍了三种循环结构在逆向时的一些需要注意的特点,当你怎能快速的花柱这些特点之后,剩下的就是反复的练习了。

【返回到目录】:http://bbs.pediy.com/showthread.php?t=113689

安卓应用层抓包通杀脚本发布!《高研班》2021年3月班开始招生!

收藏
点赞0
打赏
分享
最新回复 (26)
雪    币: 230
活跃值: 活跃值 (15)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
lonkil 活跃值 1 2010-6-1 15:42
2
0
学完此节,将楼主的文章关键部分提取出来,方便记忆,呵呵。

几种循环的汇编代码结构:

do while的:

DO_TAG:
  ......
  ......
  CMP XXX,XXX
  JXX DO_TAG

while的 :

WHILE_TAG:
  CMP XXX,XXX
  JXX WHILE_END_TAG
  ......
  ......
  CMP XXX,XXX
  JXX WHILE_TAG
WHILE_END_TAG:

For循环的(根据个人理解总结的,正确与否请自辨) :

  xxxx;初始化变量
  jmp For_TAG2:
For_TAG:
  xxx;变量递增或递加操作
For_TAG2:
  CMP XXX,XXX
  JXX For_END_TAG
  ......
  ......
  CMP XXX,XXX
  JXX For_TAG
For_END_TAG:

从生成的汇编码来看,应该在代码中多使用do...while结构,呵呵。

看了这几期,感觉编译器,真的太牛X了,优化真做到极致了,关键还能在那么短的时间内生成好可执行文件。

楼主,辛苦了,不影响正常工作的情况下,能坚持下来。给点力,嚯嚯。。。。

PS:楼主又忘加返回主目录的链接了。
雪    币: 1834
活跃值: 活跃值 (15)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
yingyue 活跃值 2010-6-1 15:48
3
0
习惯性顶贴。。。。。。。。。。。。。。。。
雪    币: 65
活跃值: 活跃值 (10)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
yasm 活跃值 2010-6-1 16:07
4
0
先支持,在看帖。
雪    币: 360
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
rol 活跃值 2010-6-1 16:12
5
0
善于总结。
习惯性看帖。。。。。。。。。。。。
雪    币: 202
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
zzycqok 活跃值 2010-6-1 17:16
6
0
先顶再看是好习惯。
雪    币: 267
活跃值: 活跃值 (38)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
wswm 活跃值 2010-6-2 08:35
7
0
继续占个前排  留个纪念。。。
雪    币: 789
活跃值: 活跃值 (267)
能力值: ( LV9,RANK:280 )
在线值:
发帖
回帖
粉丝
A1Pass 活跃值 5 2010-6-2 08:36
8
0
希望大家多多讨论交流,如果本文有哪块不易理解,还望各位读者不惜指出,也算是做了件利己利人的好事。
雪    币: 202
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
zzycqok 活跃值 2010-6-2 10:49
9
0
这一段没有源代码:
[QUOTE=AOnePass;817338]请不要怀疑我复制错了代码,事实就是这样!do-while与while生成的Release版在这里看就是100%完全相同的。
    编译器很明显的已经探测出了我们的循环判断用的是一个常量,因此就不存在首次执行条件不匹配的情况。既然如此,它为什么还要在循环前面加上那个判断分支来浪费我们的空间与时间呢?
    当然,如果我们将它的判断条件改为一个变量,那么就是另外一番景象了:

00401000  PUSH EBX
00401001  MOV EBX, DWORD PTR DS:[<&MSVCR90.printf>>;  MSVCR90.printf
00401007  PUSH EDI
00401008  PUSH Test_0.004020F4                     ; /format = "Mom! I can sing my ABC!
0040100D  CALL EBX                                 ; \printf
0040100F  MOV EDI, DWORD PTR SS:[ESP+10]           ; 取得参数(其实就是main函数的argc)
00401013  ADD ESP, 4
00401016  TEST EDI, EDI                            ; 测试参数是否为0
00401018  JLE SHORT Test_0.00401034                ; 如果为0则跳出循环
0040101A  PUSH ESI
0040101B  MOV ESI, 5B                              ; 0x5B = 0x41+26
00401020  SUB ESI, EDI                             ; 用0x5B减去参数
00401022  /PUSH ESI
00401023  |PUSH Test_0.00402110                    ;  ASCII "%c "
00401028  |CALL EBX
0040102A  |DEC EDI                                 ; 参数减1
0040102B  |ADD ESP, 8
0040102E  |INC ESI                                 ; ESI加1
0040102F  |TEST EDI, EDI
00401031  \JG SHORT Test_0.00401022                ; 如果参数大于ESI则结束循环
00401033  POP ESI
00401034  POP EDI
00401035  XOR EAX, EAX
00401037  POP EBX
00401038  RETN
[/QUOTE]

没有源代码时看得有点晕,我把源代码加上
int _tmain(int argc, _TCHAR* argv[])
{
        printf("Mom! I can sing my ABC!\r\n");

        // 听!小Baby开始唱ABC了……
        while(argc>0)
        {
                printf("%c ",0x41+(26-argc) );
                argc--;
        }
        return 0;
}
雪    币: 270
能力值: (RANK:10 )
在线值:
发帖
回帖
粉丝
小天狼星 活跃值 2010-6-2 10:57
10
0
具有深度价值的浮点数识别你还讲不讲?
雪    币: 202
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
zzycqok 活跃值 2010-6-2 11:07
11
0
感谢楼主,后面for的也有一个变量版的。
源码也挺简单的,方便和我一样的菜鸟对照看吧,高手飘过。
int _tmain(int argc, _TCHAR* argv[])
{
    printf("Mom! I can sing my ABC!\r\n");

    // 听!小Baby开始唱ABC了……
    for (;argc>0;argc--)
    {
        printf("%c ",0x41+(26-argc) );
    }
    return 0;
}
雪    币: 202
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
zzycqok 活跃值 2010-6-2 11:11
12
0
谢谢楼主。
搬个板凳等着楼主更新。
雪    币: 130
活跃值: 活跃值 (40)
能力值: ( LV11,RANK:180 )
在线值:
发帖
回帖
粉丝
besterChen 活跃值 4 2010-6-2 13:56
13
0
开O2以后,基本上 全都是do ~~while了~~~
雪    币: 34
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
msjqd 活跃值 2010-6-7 10:52
14
0
学习了 看过你的前面几篇文章 不错
雪    币: 200
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
swivan 活跃值 2010-6-7 11:24
15
0
支持并学习ing,楼主强大
雪    币: 141
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
SunV 活跃值 2010-6-23 00:58
16
0
小插曲:循环体的语句外提优化

    我们看看下面这段代码:

int _tmain(int argc, _TCHAR* argv[])
{
    printf("Mom! I can sing my ABC!\r\n");

    // 听!小Baby开始唱ABC了……
    for (int nNum = 24;nNum>0;nNum--)
    {
        argc = (int)&argc;
        printf("%c ",0x41+(26-nNum) );
    }
    printf("%p",argc);
    return 0;
}

   通过这段代码我们可以发现一处可以优化的地方,就是“argc = (int)&argc”这条语句,很明显

00401000  PUSH ESI
00401001  PUSH EDI
00401002  MOV EDI, DWORD PTR DS:[<&MSVCR90.printf>>;  MSVCR90.printf
00401008  PUSH Test_0.004020F4                     ; /format = "Mom! I can sing my ABC!
0040100D  CALL EDI                                 ; \printf
0040100F  ADD ESP, 4
00401012  MOV ESI, 43
00401017  JMP SHORT Test_0.00401020
00401019  LEA ESP, DWORD PTR SS:[ESP]
00401020  LEA EAX, DWORD PTR SS:[ESP+C]            ; /循环开始
00401024  PUSH ESI                                 ; |      ;两次压栈操作操作,从而上下标识的是一个地址。。。
00401025  PUSH Test_0.00402110                     ; |ASCII "%c "
0040102A  MOV DWORD PTR SS:[ESP+14], EAX           ; |
0040102E  CALL EDI                                 ; |
00401030  INC ESI                                  ; |
00401031  ADD ESP, 8                               ; |
00401034  CMP ESI, 5B                              ; |
00401037  JL SHORT Test_0.00401020                 ; \循环结束
00401039  MOV ECX, DWORD PTR SS:[ESP+C]            ; ECX = (int)&argc; <--注意这里
0040103D  PUSH ECX
0040103E  PUSH Test_0.00402114                     ;  ASCII "%p"
00401043  CALL EDI
00401045  ADD ESP, 8
00401048  POP EDI
00401049  XOR EAX, EAX
0040104B  POP ESI
0040104C  RETN
由上面代码可知,循环体内很明显没有我们“argc = (int)&argc;”的代码,再向下看一行才知道,这段代码被提到了外面,这就是编译进行的代码外提优化。
===============================================
Lz这段分析有点问题吧,我用红色部分标识出来的应该就是循环语句中的:
argc = (int)&argc;
以上纯属个人愚见,还请LZ仔细看下
雪    币: 789
活跃值: 活跃值 (267)
能力值: ( LV9,RANK:280 )
在线值:
发帖
回帖
粉丝
A1Pass 活跃值 5 2010-7-5 11:15
17
0
[QUOTE=SunV;827238]小插曲:循环体的语句外提优化

    我们看看下面这段代码:

int _tmain(int argc, _TCHAR* argv[])
{
    printf("Mom! I can sing my ABC!\r\n");

    // 听!小Baby开始唱ABC了...[/QUOTE]

这位朋友很细心,笔者在这里确实例举了一个错误的例子,现在已经修改。

为了表示感谢,您的名字将出现在本系列教程的导航帖中!
雪    币: 237
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
流浪蚂蚁 活跃值 2010-7-7 22:57
18
0
楼主很好 很强大 基础扎实
雪    币: 237
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
流浪蚂蚁 活跃值 2010-7-7 23:00
19
0
学习后自己动手分析了下,发现2点存在疑问,希望楼主帮忙开解下,谢谢!
疑问1:
//************************************************//
100ED564    A1 78FF1310     mov     eax, dword ptr [1013FF78]   ;eax = ds:[1013FF78]
100ED569    33C5            xor     eax, ebp                          ;eax ^= ebp 这样做能得到什么?                             
100ED56B    50              push    eax                            ;EAX进栈作参数
\\************************************************\\

疑问2:
//*************************************************//                                         
100ED56C    8D45 F4         lea     eax, dword ptr [ebp-C]        ;eax = ebp-C
100ED56F    64:A3 00000000  mov     dword ptr fs:[0], eax        ;fs:[0] = eax = (fs:[0] = ebp-C)
100ED575    894D DC         mov     dword ptr [ebp-24], ecx        ;[ebp-24] = ecx
100ED578    8B45 DC         mov     eax, dword ptr [ebp-24]        ;eax = [ebp-24] = (eax = ecx)
100ED57B    8B08            mov     ecx, dword ptr [eax]        ;ecx = ds:[eax] = (ecx = ds:[ecx])这样做是什么目的?
100ED57D    51              push    ecx                            ;作为参数进栈                 
\\*************************************************//
雪    币: 204
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
kxtom 活跃值 2010-7-8 16:07
20
0
谢谢楼主,辛苦拉。
雪    币: 196
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
BestHacker 活跃值 2010-7-8 16:49
21
0
楼主有没有系统性整理一下的集中贴?
雪    币: 1115
活跃值: 活跃值 (15)
能力值: ( LV12,RANK:230 )
在线值:
发帖
回帖
粉丝
pencil 活跃值 5 2010-7-29 11:17
22
0
看后留名~非常棒的逆向入门教程,希望楼主坚持下去。
雪    币: 789
活跃值: 活跃值 (267)
能力值: ( LV9,RANK:280 )
在线值:
发帖
回帖
粉丝
A1Pass 活跃值 5 2010-8-6 05:28
23
0
第一个感觉应该与加密有关(因为异或之后进栈保存了),第二个感觉像是取vptr操作,你得看看后面有没有类似call [ecx+0xYY]的东东……

信息有限,上面的都是猜测
雪    币: 182
活跃值: 活跃值 (10)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
denglifeng 活跃值 1 2010-8-9 14:37
24
0
好文章,顶一个
雪    币: 39
活跃值: 活跃值 (10)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
jiangming 活跃值 2010-9-18 17:49
25
0
每贴必顶……
游客
登录 | 注册 方可回帖
返回