为何注重逆向工程?

                         ==www.cciss.cn.==
                             
               How to regard converse engineering?

|=---------------=[ 为何注重逆向工程 ]=----------------------------=|
|=-----------------------------------------------------------------=|
|=---------------=[ 7all<7all_at_cciss.cn> ]=------------------=|
|=-----------------------------------------------------------------=|
|=---------------=[ 版权所有:www.cciss.cn ]=-----------------------=|

--[ 逆向工程的定义
  通过google的搜索,也没有找到合适的对于逆向工程的定义,从其起源来看,逆向工程的确
是存在了有些时间了。搜索到的很多信息大多数是针对工程术语的,有些他们的使用术语比较
使人头大:)。而这里我们想说的逆向工程是计算机领域的逆向工程,从实际意义来说,就是把
高级语言写的程序,在看完其反编译的汇编代码后,根据汇编代码再反向写回到高级语言的程
序表达方式。例如:我们拿C、C++等写的程序,编译完后是汇编代码/机器码,我们在不知道
源代码的情况下,只能看到汇编代码,于是只能从汇编代码去读程序的那个模块使用了那种功
能,这样我们就可以根据我们阅读汇编代码的情况,对程序进行逆向,然后把程序再用C、C++
的形式进行重写。
  当然,上面说的这个办法比较过于繁琐,为了逆向的简单性,我们可以采取在知道了汇编代
码对应的C程序功能后,直接使用C内嵌汇编的方式实现我们对源程序的逆向过程。
  简单来说,逆向工程可以具体如下的一个流程:
  A: 高级语言程序(C、C++、...)-->编译-->生成可执行程序(机器码/汇编语言)
  B: 可执行程序-->反编译(IDA、WIN32DASM等反编译软件)-->汇编代码
  C: 汇编代码-->分析、了解汇编代码程序流程-->进行逆向或者实现某个功能
  D: 逆向工程完毕

--[ 如何学习逆向工程?
  首先需要的就是要对汇编语言有一些了解,因为在我们对程序反编译完后,我们看到的只是汇编
代码,所以对汇编语言必须要有一定的了解,甚至是精通。
  其次,熟悉高级语言的编程,例如C、C++,只有这样才能更好的在我们的逆向过程中去发现更多的
乐趣。
  最后,自己多写程序多测试,只有这样才能积累更扎实的基础。顺便说下,我们下面的文档都是针
对C->ASM的一个逆向的过程,当然这个系列的文档不会涵盖很广的方面,但是基本的还是会有的:)

--[ 为何注重逆向工程?
  逆向工程在软件破解、漏洞挖掘等技术中占据了非常重要的位置。这些技术都是和逆向工程紧密结
合的技术,没有了所谓的逆向工程的过程,很难理解一些技术怎么去做:)

--[ 简单演示
编程语言: C
调试工具: VC6

  A: 源代码演示,下面的C代码很简单,myfun函数实现参数x + 参数y的计算,然后把相加的数值给z,
最后函数返回z.
     /*
        Author : 7all
        Data   : 2006-08-09
        Content: 代码演示简单的逆向工程示例
    */
    #include <stdio.h>
   
    int main()
    {
        int i;
        i = myfun(3, 4);
        printf("i = %d/n/n", i);
        exit(0);
    }
    //function
    int myfun(int x, int y)
    {
        int z;
        z = x + y;
        return z;
    }
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  B: 如何逆向?(这里的部分具体办法,请参照C->ASM部分的lesson1,在这个里面详细的讲述了下.)
  B-1: 调试程序,我得到了下面的反编译汇编代码:
    //调用myfun函数的地方
      11:       i = myfun(3, 4);
        00401028   push        4
        0040102A   push        3
        0040102C   call        @ILT+5(_myfun) (0040100a)
        //myfun函数
        16:   int myfun(int x, int y)
        17:   {
        0040B760   push        ebp
        0040B761   mov         ebp,esp
        0040B763   sub         esp,44h
        0040B766   push        ebx
        0040B767   push        esi
        0040B768   push        edi
        0040B769   lea         edi,[ebp-44h]
        0040B76C   mov         ecx,11h
        0040B771   mov         eax,0CCCCCCCCh
        0040B776   rep stos    dword ptr [edi]
        18:       int z;
        19:       z = x + y;
        0040B778   mov         eax,dword ptr [ebp+8]
        0040B77B   add         eax,dword ptr [ebp+0Ch] //这里在操作的时候是赋值给了eax寄存器
        0040B77E   mov         dword ptr [ebp-4],eax
        20:       return z;
        0040B781   mov         eax,dword ptr [ebp-4]
        21:   }
        0040B784   pop         edi
        0040B785   pop         esi
        0040B786   pop         ebx
        0040B787   mov         esp,ebp
        0040B789   pop         ebp
        0040B78A   ret

      从上面汇编代码看,我们看到了myfun函数的汇编代码过程,以及我们的C程序中调用myfun函数的地方.
    下面我们把这个C程序拿汇编反编译下.
  B-2: 重构汇编代码,实现与C代码相同的功能.
    首先,我们把i = myfun(3, 4);这里的C格式的函数调用转变为汇编格式,代码示例如下:
    pusha;
        push 0x04;
        push 0x03;
        call myfun;
        mov  i,eax;
        popa;
        其次,我们把myfun函数转换为汇编格式,代码示例如下:
        int myfun(int x, int y)
     {
        __asm{
            xor  eax,eax;
            xor  ebx,ebx;
            mov  eax,x;
            mov  ebx,y;
            add  eax,ebx;
        }
     }
     最后,我们把上面书写的C代码转变为我们逆向后的汇编代码,实现的功能与C格式的完全相同:)示例如下:
     /*
    Author : 7all
    Data   : 2006-08-09
    Content: 代码演示简单的逆向工程示例
    */
    #include <stdio.h>
    int myfun(int x, int y);
    int main()
    {
        static int i;
        __asm{
            pusha;
            push 0x04;
            push 0x03;
            call myfun;
            mov  i,eax; //这里把eax的值给i变量.eax的值也正是myfun函数计算后的返回值
            popa;
        }
        printf("i = %d/n/n", i);
        exit(0);
    }
    int myfun(int x, int y)
    {
        __asm{
            xor  eax,eax;
            xor  ebx,ebx;
            mov  eax,x;
            mov  ebx,y;
            add  eax,ebx;
        }
    }
    B-3: 分析
      我们看到B-2步骤最终生成了一段汇编格式的程序,但是这段代码实现的功能与我们的C代码实现的功
    能完全相同.因为上面的C代码是我们事先写好,然后调试反编译汇编代码出来的,所以在这里的逆向难免
    会受到原来C代码的影响.
      Now,我们假设我们只能得到下面的汇编代码,然后我们怎么去分析并重构C程序实现的功能呢?我想有些
    同志看到上面的逆向过程后难免会感觉头大.这个...没有关系,我们在这节分析下为什么我们会在以上步
    骤去这样做那样做,最终才实现了逆向的过程的.OK,Let's go:)
      记住,下面的步骤全部是假设我们看不到C源代码的前提下进行的静态汇编代码分析(没有跟踪调试).
    首先,我们先看下myfun函数反编译后的汇编代码,代码如下:
        0040B760   push        ebp------|
        0040B761   mov         ebp,esp--|->创建堆栈框架,这里就不用看了.
        0040B763   sub         esp,44h--|
        0040B766   push        ebx------|
        0040B767   push        esi------|->大家可能注意到了,这里push进来
        0040B768   push        edi------|  的最后都pop出去了,维持堆栈平衡的DD.
        0040B769   lea         edi,[ebp-44h]---|
        0040B76C   mov         ecx,11h---------|->通过我们的静态观察,发现
        0040B771   mov         eax,0CCCCCCCCh--|一直到这里的汇编代码...
        0040B776   rep stos    dword ptr [edi]-|全TMD是罗嗦的代码.
        0040B778   mov         eax,dword ptr [ebp+8]----|->这四行才是程序真正要实现的功能:)
        0040B77B   add         eax,dword ptr [ebp+0Ch]--|->前面的那些代码,我们静态观察罗嗦代码占的比例很
        0040B77E   mov         dword ptr [ebp-4],eax----|大:)当然,实际情况可能不同,但是静态分析汇编代
        0040B781   mov         eax,dword ptr [ebp-4]----|码的关键就是先排除一些乱七八糟的代码:)
        0040B784   pop         edi------|
        0040B785   pop         esi------|
        0040B786   pop         ebx------|->释放堆栈框架
        0040B787   mov         esp,ebp--|
        0040B789   pop         ebp------|
        0040B78A   ret
      通过我们上面对myfun函数汇编代码的静态分析,我把注意力集中在了中间的那四行,这四行进行了某些操作:)
      0040B778   mov         eax,dword ptr [ebp+8]---->eax赋值
        0040B77B   add         eax,dword ptr [ebp+0Ch]-->eax=eax+[ebp+0xc]
        0040B77E   mov         dword ptr [ebp-4],eax---->[ebp-4]=eax--|->这里也为废话
        0040B781   mov         eax,dword ptr [ebp-4]---->eax=[ebp-4]--|
        大家看后两行代码,显然这里的汇编代码是没有优化过的汇编代码.很简单,本身eax进行了add(加法)操作
        后,就已经是一个数值了.可是,偏偏又加了这么两步,至少生成的可执行程序又增加了那么几字节的大小,
        千万别小看这么几个字节,如果你的程序是数万行的代码,而生成的可执行程序(即汇编代码),在汇编代码
        中每个地方都加这么几个字节,那可能就是几K.其实几K吧,也没有什么了不起的,但是如果是木马 Or 病毒
        Or 后门程序的话,那么我相信几K的代码还是蛮严重的.
        所以,这里顺便提醒大家,在最终编译程序的时候,别忘记使用编译选项来尽量的优化代码:)
  其次,我们来分析下myfun函数调用的汇编代码,汇编代码如下:
    push 4 //压4入ESP
        push 3 //压3入ESP
        call myfun (0040100a) //调用函数
        看到这里,我们不得不突然间想到上面我们对myfun函数的静态分析中,提取出来的感觉比较关键的汇编代码.
        myfunc函数提取的关键汇编代码:
        0040B778   mov  eax,dword ptr [ebp+8]---->eax赋值
        0040B77B   add  eax,dword ptr [ebp+0Ch]-->eax=eax+[ebp+0xc]
        0040B77E   mov  dword ptr [ebp-4],eax---->[ebp-4]=eax
        0040B781   mov  eax,dword ptr [ebp-4]---->eax=[ebp-4]
        上面在调用myfun函数时,采用了压入ESP的办法.而这里是
        mov  eax,dword ptr [ebp+8] //哈哈,ebp+8 = esp + 4 = 4.
        同理,[ebp+0Ch] = esp + 8 = 3.
        add  eax,dword ptr [ebp+0Ch]  //这里也就是进行了一个加法运算,最后得到的结果肯定是7
        然后eax最后的结果也为7:)
        可能有人问,为什么ebp+8 = esp + 4 ? 这个问题留给以后的文档里面吧:)我们慢慢来.
  第三,我们根据上面提取的myfun函数的关键汇编代码来还原myfun函数.
    我们看调用myfun函数时,是push了两个值进入myfun函数,那么我们就可以判断myfun函数有两个参数.
    然后,myfun函数有一个加法运算后的返回值给了eax寄存器.这样看来,我们基本知道了myfun函数的
    基本信息了.
    1: myfun函数有两个参数
    2: myfun函数有返回值
    3: myfun函数进行了int类型的数值操作
    ok,根据上面的基本信息,我们先来重构下myfun函数:)
    int myfun(int a, int b)//记住,C的参数在汇编的压入是后进的格式,即先压入b,再压入a
    {
      xor  eax,eax
      xor  ebx,ebx //其实也可以不使用xor汇编指令,但是我想要大家养成一个习惯,以后写shellcode会方便:)
      mov  eax,a
      mov  ebx,b
      add  eax,ebx
    }
  最后,我们根据前面的静态分析过程,再次的重构下整个的代码:)
    push 4
    push 3
    call myfun
   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      int myfun(int a, int b)//记住,C的参数在汇编的压入是后进的格式,即先压入b,再压入a
    {
      xor  eax,eax
      xor  ebx,ebx //其实也可以不使用xor汇编指令,但是我想要大家养成一个习惯,以后写shellcode会方便:)
      mov  eax,a
      mov  ebx,b
      add  eax,ebx
    }
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    根据上面的函数调用和函数逆向,我们感觉这样的静态分析结果,逆向出的代码是可以实现C程序代码功能的.
    感觉是永远会欺骗人的东西,要想感觉成为现实,还是需要测试结果的.
    下面还有一个问题,我们的C代码在调用printf函数时,使用了int i这个int类型变量,而这个i正是我们获取
    myfun函数返回值的变量,其实到这里已经没有什么难题了.我们把前面的调用myfun函数的汇编代码稍微调
    整一下下就可以了:)代码如下:
    push 4
    push 3
    call myfun
    mov  i,eax //因为eax寄存器在函数myfun内部是存在加法运算和的寄存器,这样i就是最后的计算结果了:)
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    整理代码如下:(其实和上面我们的那个代码是相同的:))
    //函数调用及保存函数返回值
    __asm{
      push 4
        push 3
        call myfun
        mov  i,eax
    }
    //myfun函数的重构
    int myfun(int a, int b)
    {
      __asm{
          xor  eax,eax
          xor  ebx,ebx
          mov  eax,a
          mov  ebx,b
          add  eax,ebx //这里eax保存了myfun函数计算后的返回值.
        }
    }
    马上把这个代码copy到程序中做下测试,看看功能是否和我刚开始写的那个C代码实现的功能完全一致?
       
--[总结
  本来想把这个章节写的内容多些,但是写来写去也不会太多了.因为意思就这么些,大家顺便的一看,就
知道学习逆向工程的重要性了:)
  对于文章中提到的一些调试的具体办法,请参考lesson1文档,在lesson1文档内我做了图片格式的注释.
 
--[ 参考资料
  Intel汇编指令格式 www.intel.com
  C程序设计
  <<How To Study C & ASM code(1)>>:  http://bbs.cciss.cn/viewtopic.php?id=5 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值