kgdiwss 发表于 2007-11-12 21:13:43

逆向分析入门之for循环

回想一下,从毕业后到现在,学编程有三年了,这三年来学了很多的东西,网络安全的知识面算是有了,可是却发现没有一样擅长的,于是乎打算找一样专攻,想来想去还是觉得逆向分析比较好,吸星大法嘛,哈哈.
    这篇文章我个人感觉是非常简单的逆向分析的文章,所以高手就不要见笑啊.当然它还是需要一定的基础的,比如什么叫入栈,什么叫出栈,什么叫栈顶,如果这些都不清楚的话,估计还是会看不懂.之所以抽时间写下来,一是为了记录一下自已的成长过程,二是为了把学习结果总结一下和大家分享.
    OK,废话不多说了,文章开始.

; ========================================================================
; 作者:∮明天去要饭
; Blog: http://hi.baidu.com/kgdiwss
; 论坛: http://bbs.80dnst.com
; ========================================================================

源代码:
#include <stdio.h>

int main(int argc, char *argv[])
{
    for(int i = 0; i < 10; i++)
    {
       printf("i = %d\n", i);
   }
   return 0;
}

用VC6.0的cl.exe编译,命令如下:

cl.exe for1.cpp

就可以得到for1.exe了(见附件)
然后用 IDA5.0.0.879 反汇编后的代码:

.text:00401000 ; int __cdecl main(int argc,const char **argv,const char *envp)
                         ; 从 00401000 这一行代码可以看出来, c的缺省入栈方式是 __cdecl ,即调用者调整堆栈 .
                         ; char *argv[] 变成了 char **argv ,说明 argv 是一个指针的指针
                         ; 反汇编后还多了一个参数: const char *envp
               
.text:00401000 _main         proc near               ; CODE XREF: start+AF↓p
.text:00401000
.text:00401000 var_4         = dword ptr -4      ; 这个变量偏移是-4,即在基地址上方.在基址上方的比较可能是局部变量
                                                                           ; 这里把内存用高址和低址来表示,内存地址从下往上依次减小
                                                                           ; 上方表示比基地址小,下方表示比基址大,暂时可以这么理解.
                                                                           ; 它的长度是4,至于这个变量用来干什么暂时不太清楚,跳过去
                                                      
.text:00401000 argc            = dword ptr8          ; 参数:argc,偏移为8,表示在基址的下方,下面两个变量也是在基址下方
.text:00401000 argv            = dword ptr0Ch   ; 参数:argv,偏移为0ch(即十进制的12),所以argv的长度是12-8=4,这和指针的长度是4字节相符.
.text:00401000 envp            = dword ptr10h   ; 比原代码多出来的变量,偏移为10h(即16),所以它的长度是16-12=4.
.text:00401000 ; ---------------------------------------------------------------------------
                         ; 以下代码保存main函数运行前的环境
                         ; 基地址入栈,函数运行完要pop出来以恢复原来的运行环境.
                         ; ---------------------------------------------------------------------------
.text:00401000               push    ebp                  ; 基地址入栈,main函数运行完后会pop出来的
.text:00401001               mov   ebp, esp         ; ebp指向基址入栈后的顶栈,此时ebp是main函数地址的开始
.text:00401003               push    ecx                   ; 计数器入栈
.text:00401004               mov   , 0; 初始化变量 var_4 为0,即位置处置0
.text:0040100B               jmp   short loc_401016
.text:0040100B
.text:0040100D ; ---------------------------------------------------------------------------
.text:0040100D
.text:0040100D loc_40100D:                                 ; CODE XREF: _main+2D↓j
.text:0040100D               mov   eax, ; 处的值赋给eax
.text:00401010               add   eax, 1                   ; eax值加1
.text:00401013               mov   , eax; 将加1后的结果赋回给
.text:00401013
.text:00401016 ; ---------------------------------------------------------------------------
.text:00401016 loc_401016:                                    ; CODE XREF: _main+B↑j
.text:00401016               cmp   , 0Ah ; 判断处的值 与 0A(即10) 的大小
.text:0040101A               jge   short loc_40102F; 大于或等于10时跳到loc_40102F处,即退出程序
.text:0040101A
.text:0040101C               mov   ecx, ; 如果小于10则将值赋给计数器ecx
.text:0040101F               push    ecx                      ; 值入栈
.text:00401020               push    offset s_ID         ; 字符串 "i = %d\n" 入栈
.text:00401025               call    sub_401035         ; 对比一下源码 sub_401035 应该是printf的地址
                                                                              ; 分析到这里我们可以确定, var_4 就是源代码中的变量i
.text:00401025
.text:0040102A               add   esp, 8                   ; 调用了 printf 函数后,要调整一下堆栈以恢复当前函数的堆栈
                                                                              ; 从这里可以看出来__cdecl方式是调用者调整堆栈
                                                                              ; 如果是__stdcall方式的话,这句话就会在printf函数的堆栈中完成
.text:0040102D               jmp   short loc_40100D ; 跳到 loc_40100D 继续下一轮的比较
.text:0040102D
.text:0040102F ; ---------------------------------------------------------------------------
                         ; 以下代码恢复main函数运行前的堆栈
.text:0040102F ; ---------------------------------------------------------------------------
.text:0040102F loc_40102F:                                    ; CODE XREF: _main+1A↑j
.text:0040102F               xor   eax, eax
.text:00401031               mov   esp, ebp
.text:00401033               pop   ebp
.text:00401034               retn
.text:00401034
.text:00401034 _main         endp

    怎么样,是不是很简单啊,分析的过程倒是很简单,不过要写成文章倒是花了我不少时间.
    如果你发现有什么错误请告诉我,非常感谢。
    如果有什么疑问,也欢迎到『80王朝』论坛来提,我会抽时间回复的。

    提示:
    不知道为什么我排好版后的文章发到这来版面就不整齐了,而且颜色也没了,如果看不太清楚可以到这里看(有颜色标记应该容易看懂点):
    http://b0d.5d6d.com/viewthread.php?tid=157&page=1&extra=page%3D1

[ 本帖最后由 kgdiwss 于 2007-11-21 10:40 编辑 ]

sufeiyu 发表于 2007-11-13 12:07:23

我是个新手 看完以后有3个问题 希望高手解答
1 )“如果是__stdcall方式的话,这句话就会在printf函数的堆栈中完成” 这句话怎么理解 计算机的堆栈不是只有一个 从上到下分配的吗 可以另外分出一个堆栈吗
2 ) 程序push了很多东西进去 却只pop出一个ebp 要是根据后进先出原理 pop给ebp的应该是最后push的 offset s_ID这怎么理解阿
3)就是最后的这句.text:0040102F               xor   eax, eax有什么用啊
都是些菜鸟问题 主要是刚接触 不太懂 希望高手不要笑话

kgdiwss 发表于 2007-11-13 15:39:17

1、堆栈只有一个,调用什么函数,堆栈中当前值就是什么函数的,调用了printf,堆栈中当前值都是printf中的,你不恢复的话,main函数怎么继续啊。
2、mov   esp, ebp光是这句话,就把栈顶位置都改了。所以不能死记pop出来的是最后一次push进去的,要看栈顶在哪。你不信可以用OllyDbg跟一下看看。
3、xor   eax, eax这句话不知道解释成清零合不合适。

athlete 发表于 2007-11-13 16:46:03

呵呵虽然看不懂 我还是新手 但我能做的事就是帮你顶

sufeiyu 发表于 2007-11-13 17:46:13

mov   ebp, esp         ; ebp指向基址入栈后的顶栈,此时ebp是main函数地址的开始
mov   esp, ebp 理解了非常感谢

hackercc 发表于 2007-12-1 19:44:09

楼主厉害啊 虽然我不是很精通但是看你的解答很专业啊向你学习
页: [1]
查看完整版本: 逆向分析入门之for循环