您的位置:首页 > 其它

How to study C && ASM Code(6)

2007-04-09 21:39 429 查看
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...
#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;
}

好像虽然口里喊着Coding,但仅仅写了这么点代码,心里着实有些不舒服,但没有
办法,懒人或许就是懒人,永远都是如此的懒......
紧接着,逆向该代码,观察下其结构是如何实现的。
函数调用处的汇编代码:
9:        i = func(2, 3);
00401038   push        3  //压入参数2
0040103A   push        2  //压入参数1,至于为什么这么个压栈,这个得问问C他爸了。
0040103C   call        @ILT+5(_func) (0040100a)

由上面我们看到,对于C代码的逆向后的汇编代码,其压栈操作是先压入最后一个参数,
然后以此类推,直到压入第一个参数。最后call一下该函数,call与jmp指令虽然都可以
实现直接跳转,但是其用法却完全不同,在call时会自动产生堆栈平衡,然后把压入的
参数传递到函数内部进行处理。而jmp指令则是直接的跳转。这里就简单的介绍下,详细
的可参考一些汇编资料。
func函数的汇编代码:
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

通过上面的解释,我们知道在该函数内部的有用代码为:
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] //对计算的结果存储

于是,我很窃喜的写下了下面的代码来实现该函数:
//完整代码可下载所附带文档
int func (int x, int y)
{
__asm{
mov eax, x;
add eax, y;
mov x, eax;
}
return x;
}


--[ 汇编到C的逆向
汇编到C的逆向牵扯到对汇编指令的熟悉程度,当然这种方法应该从简单的逆向开始,
可以先自己写简单的汇编代码,然后再实现稍微复杂的,以此类推,直到成为“高手”
为止。
下面举例一个牛人写的汇编函数,最近还在拜读他的英文大作:)
; 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

本人懒惰,于是想了这么一个课题:
问题二:把上面的汇编代码逆向为C代码。
谁能做出来,我请谁吃包子...
--] 总结
玩笑归玩笑,在函数调用这块是需要对其流程掌握清晰的,这样在具体的跟踪
调试时,才可以对数据的流向有清晰的认识。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: