How to study C && ASM Code(6)

来源:互联网 发布:淘宝几单才有2颗心 编辑:程序博客网 时间:2024/04/27 05:20

Download:
http://www.cisrg.cn/doc/lesson6.rar
                    ==www.cisrg.cn==
                            
                    How to study C && ASM Code(6)

|=---------------=[ 逆向函数 ]=------------------------------------=|
|=-----------------------------------------------------------------=|
|=---------------=[ 7all<7all7_at_163.com> ]=----------------------=|
|=-----------------------------------------------------------------=|
|=---------------=[ 版权所有:www.cisrg.cn ]=-----------------------=|

--[ Let's go go...
  第五节《字符输入/输出的逆向》不会再出现在这个世界上了,因为就在他还
在他妈的肚子里面准备出世的时候,被我无情的扼杀了,因为我觉得他生活在
这个世界上没有任何的意义。
  函数就是一组根据某种规则封装起来的程序片断(功能模块),一般C的代码
都是通过函数来组合起来的,当然你也可以不使用函数,那样的话如果你写一段
2000行的C代码的话,一定是一件很有意思的事情。
  函数的逆向在该节我们直采取两种做法,即根据汇编代码逆向为C代码,然后根
据C代码逆向到汇编代码实现。在纯粹的汇编下面,一般使用过程来实现C下面类似
函数的东东,但是因为这里我们没有准备使用汇编编译器编译完整的汇编代码,因
此我们还是会以C的风格来实现被我们封装在C代码里的嵌入式汇编。

--[ C到汇编的函数实现
  废话少说,Coding...
[code]
#include <stdio.h>
#include <stdlib.h>

int func(int x, int y);

int main()
{
 int i;
 i = func(2, 3);
 printf("i = %d/n", i);
 exit(0);
}

int func(int x, int y)
{
 x = x + y;
 return x;
}
[/code]
  好像虽然口里喊着Coding,但仅仅写了这么点代码,心里着实有些不舒服,但没有
办法,懒人或许就是懒人,永远都是如此的懒......
  紧接着,逆向该代码,观察下其结构是如何实现的。
  函数调用处的汇编代码:
  [code]
  9:        i = func(2, 3);
00401038   push        3  //压入参数2
0040103A   push        2  //压入参数1,至于为什么这么个压栈,这个得问问C他爸了。
0040103C   call        @ILT+5(_func) (0040100a)
  [/code]
  由上面我们看到,对于C代码的逆向后的汇编代码,其压栈操作是先压入最后一个参数,
然后以此类推,直到压入第一个参数。最后call一下该函数,call与jmp指令虽然都可以
实现直接跳转,但是其用法却完全不同,在call时会自动产生堆栈平衡,然后把压入的
参数传递到函数内部进行处理。而jmp指令则是直接的跳转。这里就简单的介绍下,详细
的可参考一些汇编资料。
  func函数的汇编代码:
  [code]
  14:   int func(int x, int y)
15:   {
00401080   push        ebp
00401081   mov         ebp,esp  //建立堆栈框架
00401083   sub         esp,40h
00401086   push        ebx
00401087   push        esi
00401088   push        edi
00401089   lea         edi,[ebp-40h]
0040108C   mov         ecx,10h
00401091   mov         eax,0CCCCCCCCh
00401096   rep stos    dword ptr [edi]  //以上代码为维持堆栈平衡的自生成代码,
                                        //前面的文章有较详细的介绍。
16:       x = x + y;
/*
  这里的ebp+8正好为0x02,即前面调用func函数是的第一个参数;
  ebp=c正好为0x03,即前面调用func函数的第二个参数;
  为什么直接的push 3;push 2会存储在ebp+8和ebp+c处呢?
  这里我们需要回到前面进行分析了:)
  在EIP执行到0040103C   call        @ILT+5(_func) (0040100a)这条
  指令,且还没有执行进入func函数内部时,此时由于我们采取了
  push 3;
  push 2;
  这样的压栈操作,因此此时ESP的数据应该为,(ESP->0x0012FF28)
  02 00 00 00
  03 00 00 00
  我们按F11,会进入func函数内部,只要一进入func函数内部,程序会自动把
  执行完func函数后的下一条指令压入ESP内,为什么要把
  0040103C   call        @ILT+5(_func) (0040100a)的下条语句保存到ESP内呢?
  Intel考虑的很周全,这样的话在函数调用ret返回时,就可以取出这条指令交给
  EIP,这样EIP就可以自动执行了。如果此时进入函数内部的地址被覆盖或者发生
  错误,在函数调用ret指令返回时,就会发生错误,这也是缓冲区溢出的利用办法:)
  我们观察下此时ESP的值:(ESP->0x0012FF24)
  41 10 40 00
  02 00 00 00
  03 00 00 00
  如果你有疑问的话,你可以去查下00401041这个地址,该地址正好在call func的
  下条指令:)这里这条指令为:
  00401041   add         esp,8
  备注:如果你编译后,地址可能会改变,不会是00401041,但是你记住在进入函数内
  时观察下压入ESP的值就可以知道你自己的地址是什么了。
  此时,esp+4=0x02,esp+8=0x03。
  OK,如果你明白了这里,就继续看是如何实现把ESP的值给EBP了,也就是如何实现把
  push进来的参数以ebp+8、ebp+c的形式来使用。如果你以上内容看不明白,那么可以
  到FAQ版块去提问,如果提问还是不会,那么你可以给我mail,如果还是不会,请自行
  jmp 黄河。
  Let's go...
  前面我说过,在刚进入函数内部时的push ebp...等的操作是为了维持堆栈平衡的,
  虽然的确如此,但这里我们需要使用下他们了。我们看下函数刚进来时候的汇编代码:
  00401080   push        ebp
 00401081   mov         ebp,esp
 00401083   sub         esp,40h
 首先,在0x00401080地址处push ebp,这里既然push了ebp,我们假设此时ebp值为
 0x0012FF80,那么当执行到00401081 mov ebp,esp这条语句时,此时ESP的数据应该
 为:
 80 FF 12 00
 41 10 40 00
 02 00 00 00
 03 00 00 00
 其次,在0x00401081地址处mov ebp,esp,原则上来说此时ebp与esp的值是相同的,也即
 此时EBP的数据分布为:
 80 FF 12 00
 41 10 40 00
 02 00 00 00
 03 00 00 00
 由于在0x00401083至0x00401098之间的这块代码,没有再改变EBP的值,因此此时EBP-8处
 正好为0x02,而EBP-C处的值正好为0x03。
 OK,到这里该明白为啥下面的语句是 mov eax, ebp+8了吧?再不明白自己去自贡吧(女生
 除外,即使你想也没有这个可能了....)
*/
00401098   mov         eax,dword ptr [ebp+8] 
0040109B   add         eax,dword ptr [ebp+0Ch] //看章节4可知道这里的用法:)
0040109E   mov         dword ptr [ebp+8],eax
17:       return x;
004010A1   mov         eax,dword ptr [ebp+8] //对计算的结果准备ret
18:   }
004010A4   pop         edi
004010A5   pop         esi
004010A6   pop         ebx
/*
  问题一:
  此处为什么要使用这个语句?
  答对者可获取请我吃饭的权力...突然感觉自己很无耻的说。
  不过理解了这里还是对理解函数内部有帮助的,不会的可以提问下。
*/
004010A7   mov         esp,ebp
004010A9   pop         ebp
004010AA   ret
  [/code]
  通过上面的解释,我们知道在该函数内部的有用代码为:
  [code]
00401098   mov         eax,dword ptr [ebp+8] 
0040109B   add         eax,dword ptr [ebp+0Ch] //看章节4可知道这里的用法:)
0040109E   mov         dword ptr [ebp+8],eax
17:       return x;
004010A1   mov         eax,dword ptr [ebp+8] //对计算的结果存储
  [/code]
  于是,我很窃喜的写下了下面的代码来实现该函数:
  [code]
  //完整代码可下载所附带文档
  int func (int x, int y)
  {
   __asm{
    mov eax, x;
    add eax, y;
    mov x, eax;
    }
    return x;
  }
  [/code]
 
--[ 汇编到C的逆向
  汇编到C的逆向牵扯到对汇编指令的熟悉程度,当然这种方法应该从简单的逆向开始,
可以先自己写简单的汇编代码,然后再实现稍微复杂的,以此类推,直到成为“高手”
为止。
  下面举例一个牛人写的汇编函数,最近还在拜读他的英文大作:)
[code]
; itoaproc(source, dest)
; convert integer (source) to string of 6 characters at given destination address
itoaproc    PROC   NEAR32
            push   ebp                  ; save base pointer
            mov    ebp, esp             ; establish stack frame
            push   eax                  ; Save registers
            push   ebx                  ;   used by
            push   ecx                  ;   procedure
            push   edx
            push   edi
            pushf                      ; save flags

            mov    ax, [ebp+12]        ; first parameter (source integer)
            mov    edi, [ebp+8]        ; second parameter (dest offset)
ifSpecial:  cmp    ax,8000h            ; special case -32,768?
            jne    EndIfSpecial        ; if not, then normal case
            mov    BYTE PTR [edi],'-'  ; manually put in ASCII codes
            mov    BYTE PTR [edi+1],'3'  ;   for -32,768
            mov    BYTE PTR [edi+2],'2'
            mov    BYTE PTR [edi+3],'7'
            mov    BYTE PTR [edi+4],'6'
            mov    BYTE PTR [edi+5],'8'
            jmp    ExitIToA            ; done with special case
EndIfSpecial:

            mov    dx, ax              ; save source number

            mov    al,' '              ; put blanks in
            mov    ecx,5               ;   first five
            cld                        ;   bytes of
            rep stosb                  ;   destination field   

            mov    ax, dx              ; copy source number
            mov    cl,' '              ; default sign (blank for +)
IfNeg:      cmp    ax,0                ; check sign of number
            jge    EndIfNeg            ; skip if not negative
            mov    cl,'-'              ; sign for negative number
            neg    ax                  ; number in AX now >= 0
EndIfNeg:

            mov    bx,10               ; divisor

WhileMore:  mov    dx,0                ; extend number to doubleword
            div    bx                  ; divide by 10
            add    dl,30h              ; convert remainder to character
            mov    [edi],dl            ; put character in string
            dec    edi                 ; move forward to next position
            cmp    ax,0                ; check quotient
            jnz    WhileMore           ; continue if quotient not zero

            mov    [edi],cl            ; insert blank or "-" for sign

ExitIToA:   popf                       ; restore flags and registers
            pop    edi
            pop    edx
            pop    ecx
            pop    ebx
            pop    eax
            pop    ebp
            ret    6                   ;exit, discarding parameters
itoaproc    ENDP
[/code]
  本人懒惰,于是想了这么一个课题:
  问题二:把上面的汇编代码逆向为C代码。
  谁能做出来,我请谁吃包子...
--] 总结
  玩笑归玩笑,在函数调用这块是需要对其流程掌握清晰的,这样在具体的跟踪
调试时,才可以对数据的流向有清晰的认识。
 

原创粉丝点击