找回密码
 立即注册

QQ登录

只需一步,快速开始

1401软件论坛总版规【新人必看】如何正确发布收费帖子加入vip系统学习仅需498元免费获得1401软件安全论坛vip开通本站VIP会员请联系QQ 564853771
论坛纪念优盘Beyond EXE超强加密器V1.61401论坛破解工具包 2017-9-12[破解班vip]入门篇[破解班vip]基础篇
[破解班vip]脱壳篇[破解班vip]实战篇[破解班vip]网络验证篇[破解班vip]零基础HOOK教程[破解班vip]零基础易语言入门
[破解班vip]零基础c语言入门[破解班vip]零基础Delphi编程入门[破解班vip]寒假培训课程共7课[破解班vip]破解提高篇[破解班vip]易语言培训课程
[破解班vip]核心技术培训篇[破解班vip]pe格式与pe操作[破解班vip]2022全新封包-山寨-爆破全系列[原创课程]c++单文档框架课程[逆向班vip]汇编语句与反汇编基础
[逆向班vip]全自动扫雷辅助[逆向班vip]手机模拟按键[逆向班vip]植物大战僵尸辅助广告位招租...付费破解软件 +Q 564853771
查看: 3460|回复: 0

【转】《基于VC平台下C++反汇编与逆向分析研究——No.1》

[复制链接]
  • TA的每日心情
    郁闷
    昨天 08:48
  • 签到天数: 2752 天

    [LV.Master]伴坛终老

    963

    主题

    3310

    回帖

    2万

    积分

    管理员

    UID
    1
    元宝
    63618
    威望
    4309
    贡献
    607
    信誉值
    0
    精华
    34
    在线时间
    3589 小时
    注册时间
    2014-1-5
    最后登录
    2024-4-18
    违规
    -2
    积分
    22600

    最佳新人活跃会员推广达人宣传达人灌水之王突出贡献吾爱富翁论坛元老在线王已有小成管理团队

    QQ
    发表于 2015-11-13 21:25:08 | 显示全部楼层 |阅读模式
    Ȧ
    ————————————————————————————————————————————————
    分析环境:WIN7sp1
    所用工具:VC++6.0/OllyDBG/IDA
    适用人群:有一定计算机基础,熟悉C/C++编程,熟悉X86系列汇编/了解OD/IDA等调试工具使用,对逆向安全有极大兴趣者!
    ————————————————————————————————————————————————

    开篇前言:

           1.何为反汇编?简单来说就是通过读取可执行文件的二进制代码,将其还原为汇编代码的过程。

       
           2.何为逆向分析? 再简单来说就是在只有可执行文件汇编代码的情况下,利用逆向思维来还原程序本身的意图和行为等。

           3.两者的用处?主要用于软件破解、WG分析、病毒分析、漏洞分析、软件逆向、软件汉化、底层技术等领域。

    (注:逆向工程分析不仅仅是一门学科,还是一门艺术,逆向的艺术!想战胜它往往需要极大费精力、时间、毅力等。这其中还涉及密码学、社会工程学、数学算法,以及软件工程、网络通讯、操作系统底层及硬件等,但付出总是和回报成正比...)

    ————————————————————————————————————————————————
    正文部分:
    本帖隐藏的内容首先了解下VC的两种编译方式:Debug和Relese 也就是我们常说的调试版和发布版,那具体这两种编译方式有什么区别呢?往下面看:
           Debug 通常称为调试版本,它包含调试信息,并且不作任何优化,便于程序员调试程序。Release 称为发布版本,它往往是进行了各种优化,使得程序在代码大小和运行速度上都是最优的,以便用户很好地使用。
           上面的解释可能太生硬,那我们就先做个例子来对比下!程序就选择我们最熟悉的Hello World,下面我们就看下两个世界的Hello World...
       
    源码:
    ————————————————————————————————————————————————

    • #include<stdio.h>
    • void main()
    • {
    •         printf("Hello World");
    • }

    [color=rgb(81, 112, 58) !important]复制代码



    ————————————————————————————————————————————————
    我们使用两种不同的编译方式编译生产后载入到OD查看,先看Debug版本的:载入OD后,停留在00401120处,也就是OEP,先看反汇编代码,注意着色地方
    ————————————————————————————————————————————————

    00401120 >/$  55            push ebp                                 ;  程序OEP(Initial CPU selection)
    00401121  |.  8BEC          mov ebp,esp
    00401123  |.  6A FF         push -0x1
    00401125  |.  68 38214200   push Test.00422138
    0040112A  |.  68 D0414000   push Test._except_handler3               ;  SE 处理程序安装
    0040112F  |.  64:A1 0000000>mov eax,dword ptr fs:[0]
    00401135  |.  50            push eax
    00401136  |.  64:8925 00000>mov dword ptr fs:[0],esp
    0040113D  |.  83C4 F0       add esp,-0x10
    00401140  |.  53            push ebx
    00401141  |.  56            push esi
    00401142  |.  57            push edi
    00401143  |.  8965 E8       mov [local.6],esp
    00401146  |.  FF15 4CA14200 call dword ptr ds:[<&KERNEL32.GetVersion>;  kernel32.GetVersion
    0040114C  |.  A3 707C4200   mov dword ptr ds:[_osver],eax
    00401151  |.  A1 707C4200   mov eax,dword ptr ds:[_osver]
    00401156  |.  C1E8 08       shr eax,0x8
    00401159  |.  25 FF000000   and eax,0xFF
    0040115E  |.  A3 7C7C4200   mov dword ptr ds:[_winminor],eax
    00401163  |.  8B0D 707C4200 mov ecx,dword ptr ds:[_osver]
    00401169  |.  81E1 FF000000 and ecx,0xFF
    0040116F  |.  890D 787C4200 mov dword ptr ds:[_winmajor],ecx
    00401175  |.  8B15 787C4200 mov edx,dword ptr ds:[_winmajor]
    0040117B  |.  C1E2 08       shl edx,0x8
    0040117E  |.  0315 7C7C4200 add edx,dword ptr ds:[_winminor]
    00401184  |.  8915 747C4200 mov dword ptr ds:[_winver],edx
    0040118A  |.  A1 707C4200   mov eax,dword ptr ds:[_osver]
    0040118F  |.  C1E8 10       shr eax,0x10
    00401192  |.  25 FFFF0000   and eax,0xFFFF
    00401197  |.  A3 707C4200   mov dword ptr ds:[_osver],eax
    0040119C  |.  6A 00         push 0x0
    0040119E  |.  E8 BD2D0000   call Test._heap_init
    004011A3  |.  83C4 04       add esp,0x4
    004011A6  |.  85C0          test eax,eax
    004011A8  |.  75 0A         jnz short Test.004011B4
    004011AA  |.  6A 1C         push 0x1C
    004011AC  |.  E8 CF000000   call Test.fast_error_exit
    004011B1  |.  83C4 04       add esp,0x4
    004011B4  |>  C745 FC 00000>mov [local.1],0x0
    004011BB  |.  E8 A0270000   call Test._ioinit
    004011C0  |.  FF15 48A14200 call dword ptr ds:[<&KERNEL32.GetCommand>; [GetCommandLineA
    004011C6  |.  A3 04964200   mov dword ptr ds:[_acmdln],eax
    004011CB  |.  E8 70250000   call Test.__crtGetEnvironmentStringsA
    004011D0  |.  A3 487C4200   mov dword ptr ds:[_aenvptr],eax
    004011D5  |.  E8 56200000   call Test._setargv
    004011DA  |.  E8 011F0000   call Test._setenvp
    004011DF  |.  E8 1C1B0000   call Test._cinit
    004011E4  |.  8B0D 8C7C4200 mov ecx,dword ptr ds:[_environ]
    004011EA  |.  890D 907C4200 mov dword ptr ds:[__initenv],ecx
    004011F0  |.  8B15 8C7C4200 mov edx,dword ptr ds:[_environ]
    004011F6  |.  52            push edx                    ;第一个参数
    004011F7  |.  A1 847C4200   mov eax,dword ptr ds:[__argv]
    004011FC  |.  50            push eax                    ;第二个参数
    004011FD  |.  8B0D 807C4200 mov ecx,dword ptr ds:[__argc]
    00401203  |.  51            push ecx                     ;第三个参数
    00401204  |.  E8 FCFDFFFF   call Test.00401005           ;main函数入口点
    00401209  |.  83C4 0C       add esp,0xC                              ;  (Initial CPU selection)
    0040120C  |.  8945 E4       mov [local.7],eax
    0040120F  |.  8B55 E4       mov edx,[local.7]
    00401212  |.  52            push edx                                 ; /status
    00401213  |.  E8 281B0000   call Test.exit                           ; \exit
    ————————————————————————————————————————————————

    初学者,看到这些是不是很晕?不要紧我们细细品味:

            1.这是VC++6.0典型的入口标志,大家最好熟记,感兴趣的同学可以对比下其他语言编译器的入口特征,例如:VB,E语言,Win32Asm等,熟悉入口特征在以后的逆向脱壳中,对于寻找程序oep也很有帮助!
            2.我们分析的重点在main函数,上面的汇编代码主要是VC++6.0初始化启动的过程,我们大概了解下即可,看下我标绿的几个函数,即启动函数,这里借用官方语言了解下:
            1.GetVersion函数:获取当前运行平台的版本号。
            2._heap_init函数:用于初始化堆空间。在函数实现中使用HeapCreate申请堆空间
            3.GetCommandLineA函数:获取命令行参数信息的首地址
            4._crtGetEnvironmentStringA函数:获取环境变量信息的首地址
            5._setargv函数:此函数根据GetCommandLineA获取命令行参数信息的首地址并进行参数分析
            6._setenvp函数:此函数根据_crtGetEnvironmentStringA函数获取环境变量信息的首地址进行分析。
            7._cinit函数:用于全局变量数据和浮点数寄存器的初始化。

            以上都是编译器帮我们自动完成,有兴趣的同学so一下,越过初始化启动过程来到main函数,即 00401204 处,很多人问,你怎么知道这里是main函数?一般在完成初始化启动操作以后,下面有3个连续push操作的call就应该是main函数,理由很简单,main函数有三个参数
            下面开始OD调试,首先从OEP处一步一步F8跟下去,不要F7(除非你想了解启动函数具体过程),否则你将循环在无尽的call与retn中...我们F8一直执行到main函数处,即00401204处:
    ————————————————————————————————————————————————
    004011F6  |.  52            push edx                    ;第一个参数
    004011F7  |.  A1 847C4200   mov eax,dword ptr ds:[__argv]
    004011FC  |.  50            push eax                    ;第二个参数
    004011FD  |.  8B0D 807C4200 mov ecx,dword ptr ds:[__argc]
    00401203  |.  51            push ecx                     ;第三个参数
    00401204  |.  E8 FCFDFFFF   call Test.00401005           ;main函数入口点
    ————————————————————————————————————————————————
            上面的三个连续push就是mian函数的三个参数,这里不多解释,这时我们注意2个寄存器的值,首先EIP指向00401204,esp值为0012FF50,OK,这时可以F7了,进去以后,如下:
    ————————————————————————————————————————————————
    00401005  /$ /E9 06000000   jmp Test.main   
    0040100A  |  |CC            int3
    0040100B  |  |CC            int3
    0040100C  |  |CC            int3
    0040100D  |  |CC            int3
    0040100E  |  |CC            int3
    0040100F  |  |CC            int3
    00401010 >|> \55            push ebp
    00401011  |.  8BEC          mov ebp,esp
    00401013  |.  83EC 40       sub esp,40
    00401016  |.  53            push ebx
    00401017  |.  56            push esi
    00401018  |.  57            push edi
    00401019  |.  8D7D C0       lea edi,[local.16]
    0040101C  |.  B9 10000000   mov ecx,10
    00401021  |.  B8 CCCCCCCC   mov eax,CCCCCCCC
    00401026  |.  F3:AB         rep stos dword ptr es:[edi]
    00401028  |.  68 1C204200   push 42201C               
    00401032  |.  83C4 04       add esp,4
    00401035  |.  5F            pop edi                                 
    00401036  |.  5E            pop esi                                 
    00401037  |.  5B            pop ebx                                 
    00401038  |.  83C4 40       add esp,40
    0040103B  |.  3BEC          cmp ebp,esp
    0040103D  |.  E8 9E000000   call 004010E0
    00401042  |.  8BE5          mov esp,ebp
    00401044  |.  5D            pop ebp                                 
    00401045  \.  C3            retn————————————————————————————————————————————————
    F7跟进来以后,直接停留到:00401005  /$ /E9 06000000   jmp Test.main  
            好奇的人肯定会问,为什么不直接指向00401010呢,多一处无条件跳转为何?这里应该是考虑到冗余的问题,毕竟这里是debug版本,当然利用这个空隙可做很多很多事情...至于为何用int 3来填充,是 因为需要保护这段空间,int 3即中断
    开始详细分析,先来到第一句汇编指令00401005处,此处是一个无条件跳转:jmp     _main  此句什么意思呢?其实这句等价于 jmp  00401010 因为此处的_main只是作为一个地址标识符,他指向的地址就是00401010处,相当于我们使用寄存器跳转,如:
    mov eax,00401010
    jmp eax

    反汇编注释版:
    ————————————————————————————————————————————————
    00401005  /$ /E9 06000000   jmp Test.main            ;这里无条件跳转到00401010处
    0040100A  |  |CC            int3
    0040100B  |  |CC            int3
    0040100C  |  |CC            int3   
    0040100D  |  |CC            int3
    0040100E  |  |CC            int3        
    0040100F  |  |CC            int3                                         ;以上空间用int3填充                              
    00401010 >|> \55            push ebp                             ;  保存ebp
    00401011  |.  8BEC          mov ebp,esp                       ;  ebp指向栈顶
    00401013  |.  83EC 40       sub esp,0x40                     ;  开辟局部变量空间
    00401016  |.  53            push ebx                                ;  ebx入栈保存
    00401017  |.  56            push esi                                 ;  esi入栈保存
    00401018  |.  57            push edi                                 ;  edi入栈保存,以上几句用于保护现场
    00401019  |.  8D7D C0       lea edi,[local.16]                       ;  设置局部变量初始化起始地址
    0040101C  |.  B9 10000000   mov ecx,0x10                         ;  循环次数
    00401021  |.  B8 CCCCCCCC   mov eax,0xCCCCCCCC      ;  将4个int 3指令,即4个CC放入eax中
    00401026  |.  F3:AB         rep stos dword ptr es:[edi]              ;  循环拷贝填充局部空间,以上4句用来CC初始化
    00401028  |.  68 1C204200   push Test.0042201C                 ; /Hello World
    0040102D  |.  E8 2E000000   call Test.printf                         ; \调用printf函数
    00401032  |.  83C4 04       add esp,0x4                              ;  调用完printf,恢复esp
    00401035  |.  5F            pop edi                                  ;  还原edi
    00401036  |.  5E            pop esi                                  ;  还原esi
    00401037  |.  5B            pop ebx                                  ;  还原ebx
    00401038  |.  83C4 40       add esp,0x40                            ;  恢复局部变量所用空间
    0040103B  |.  3BEC          cmp ebp,esp                              ;  检测栈平衡
    0040103D  |.  E8 9E000000   call Test._chkesp                ;  调试信息,F7跟进去看看
    00401042  |.  8BE5          mov esp,ebp                              ;  恢复esp
    00401044  |.  5D            pop ebp                                  ;  恢复ebp
    00401045  \.  C3            retn                                     ;  返回主程序,等同ret | add esp,4
    ————————————————————————————————————————————————
    文字叙述:
    push ebp                                   push入栈指令 ,ebp帧指针,意思就是把ebp入栈保存,(此时esp-4)
    mov ebp,esp                              mov传送指令,esp为栈指针,就是把esp保存到ebp中,因为下面要用到esp
    sub     esp, 40h                         申请局部变量空间而用,40h的大小根据调用子程序中变量多少而不同
    连续三句push  也就是我们常说的保护现场,因为子程序中有可能要用到这三个寄存器,需要先保存起来  
    lea edi,[local.16]                        lea是地址传送指令,此句的意思就是把ebp的地址加上40h放到edi中
    mov     ecx, 10h                         ecx一般用于循环次数,这里就是设置循环次数
    mov     eax, 0CCCCCCCCh       eax赋值为8个C,也就是初始化CC操作,CC汇编指令等于int 3中断指令
    rep stosd                                    循环拷贝字符串操作,把整个区域设置为CC
    push Test.0042201C                 把字符串地址指针做为参数入栈
    call    _printf                               调用printf函数
    add     esp, 4                              调用完毕,恢复栈操作
    连续三个pop 出栈,和之前的3个push相对应,回复相关寄存器
    add     esp, 40h                         对应之前的sub esp,40h   恢复esp的值
    cmp ebp,esp                           判断现在的esp值是否与之前保存在ebp中的值吻合
    call    __chkesp                          栈出错信息调试,下面根据查看
    mov     esp, ebp                         把ebp的值还给esp,还原esp
    mov     esp, ebp                         函数调用结束,恢复ebp的值
    retn                                            返回到主函数

    这里有一个call    __chkesp 函数来显示调试信息, 跟进去看下:
    ————————————————————————————————————————————————
    004010E0 >/$ /75 01         jnz short Test.004010E3  
    004010E2  |. |C3            retn
    004010E3  |> \55            push ebp
    004010E4  |.  8BEC          mov ebp,esp
    004010E6  |.  83EC 00       sub esp,0x0
    004010E9  |.  50            push eax
    004010EA  |.  52            push edx
    004010EB  |.  53            push ebx
    004010EC  |.  56            push esi
    004010ED  |.  57            push edi
    004010EE  |.  68 5C204200   push Test.0042205C                       ;  The value of ESP was not properly saved across a function call.  This is usually a result of calling a function declared with one calling convention with a function pointer declared with a different calling convention.
    004010F3  |.  68 58204200   push Test.00422058
    004010F8  |.  6A 2A         push 0x2A
    004010FA  |.  68 48204200   push Test.00422048                       ;  i386\chkesp.c
    004010FF  |.  6A 01         push 0x1
    00401101  |.  E8 5A150000   call Test._CrtDbgReport
    00401106  |.  83C4 14       add esp,0x14
    00401109  |.  83F8 01       cmp eax,0x1
    0040110C  |.  75 01         jnz short Test.0040110F
    0040110E  |.  CC            int3
    0040110F  |>  5F            pop edi
    00401110  |.  5E            pop esi
    00401111  |.  5B            pop ebx
    00401112  |.  5A            pop edx
    00401113  |.  58            pop eax
    00401114  |.  8BE5          mov esp,ebp
    00401116  |.  5D            pop ebp
    00401117  \.  C3            retn
    ————————————————————————————————————————————————
            首先是一个jnz跳转,此跳转根据zp标志位来决定,也就是之前的CMP比较命令,如果esp不等于ebp就发生跳转,弹出调试信息窗口,如果相等就直接retn返回到main函数。想知道程序是如何弹出调试信息窗口的同学,可以再跟一下,都是一些系统内部函数,也好理解,好了,Debug版本解析到此为止!
    ————————————————————————————————————————————————

    分析过debug版本,再来看看Release 版本,你会感觉到震惊...直接Release 编译生成后载入OD,来到main入口处:
    ————————————————————————————————————————————————
    004010E3  |.  50            push eax
    004010E4  |.  FF35 20994000 push dword ptr ds:[0x409920]        
    004010EA  |.  FF35 1C994000 push dword ptr ds:[0x40991C]
    004010F0  |.  E8 0BFFFFFF   call Test.00401000        ;main函数入口
    ————————————————————————————————————————————————
    可以看出,main函数的三个参数被直接简化为三个直接的push,F7跟进去看看:
    ————————————————————————————————————————————————
    00401000  /$  68 30704000   push Test.00407030                       ;  Hello World
    00401005  |.  E8 06000000   call Test.00401010
    0040100A  |.  59            pop ecx
    0040100B  \.  C3            retn
    ————————————————————————————————————————————————
           怎么样,是不是感觉很精简,push入栈printf函数的一个参数,直接使用call调用printf,这里的pop ecx 等价于 add esp,4,再回头看下两种版本的对比:
            Debug 通常称为调试版本,它包含调试信息,并且不作任何优化,便于程序员调试程序。Release 称为发布版本,它往往是进行了各种优化,使得程序在代码大小和运行速度上都是最优的,以便用户很好地使用。
           一切尽在不言中,慢慢品味...

    《本节完,待续...》



    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|Archiver|手机版|小黑屋|1401软件安全 ( ICP备16034480号 )

    GMT+8, 2024-4-19 00:32 , Processed in 2.248243 second(s), 26 queries , Gzip On.

    Powered by Discuz! X3.5

    Copyright © 2001-2020, Tencent Cloud.

    快速回复 返回顶部 返回列表