12   1  /  2  页   跳转

病毒分析学习笔记之二

病毒分析学习笔记之二

本次开始就需要大家用一些调试工具积极跟着我一起调试程序了,用邓爷爷的一句名言:实践才是检验真理的唯一标准。希望大家不要因为代码表明看起来很繁琐而畏惧,也不要光看不操作,让我们一起把反汇编这种看起来晦涩难懂的东西学透学精!

本次课程用到的工具:OD http://www.pediy.com/tools/Debuggers/ollydbg/OllyICE.rar
IDA(可用可不用)http://bbs.pediy.com/showthread.php?t=55801

需要用到的程序见附件

Push ebp
Mov ebx,esp
Sub esp,XXX
看过一些汇编代码的人一定对这个有些印象,那么这是做了什么操作呢?里面有什么奥秘呢?
本次需要了解的两个指令sub(相当于减)sub eax,xxx 就是将eax减去 xxx
与之对应的是add ,相加。


首先我们先引入一个非常简单的小程序:
#include <stdio.h>
#include <string.h>

void func1(int input1, int input2)
{

int j;


char c;


short k;



j = 0;


c = 'a';


k = 1;



printf("sum=%d\n", input1+input2);



return;

}

int main()
{

char output[8] = "abcdef";


int i, j;



i=2;


j=3;


func1(i,j);



printf("%s\r\n", output);



return 0;

}

编译好了之后,用IDA打开该程序 定位到main函数
.text:00401090                push    ebp
.text:00401091                mov    ebp, esp
.text:00401093                sub    esp, 50h
.text:00401096                push    ebx
.text:00401097                push    esi
.text:00401098                push    edi
.text:00401099                lea    edi, [ebp+var_50]
.text:0040109C                mov    ecx, 14h
.text:004010A1                mov    eax, 0CCCCCCCCh
.text:004010A6                rep stosd
.text:004010A8                mov    eax, ds:dword_420030
.text:004010AD                mov    [ebp+var_8], eax
.text:004010B0                mov    cx, ds:word_420034
.text:004010B7                mov    [ebp+var_4], cx
.text:004010BB                mov    dl, ds:byte_420036
.text:004010C1                mov    [ebp+var_2], dl
.text:004010C4                xor    eax, eax
.text:004010C6                mov    [ebp+var_1], al
.text:004010C9                mov    [ebp+var_C], 2
.text:004010D0                mov    [ebp+var_10], 3
.text:004010D7                mov    ecx, [ebp+var_10]
.text:004010DA                push    ecx
.text:004010DB                mov    edx, [ebp+var_C]
.text:004010DE                push    edx
.text:004010DF                call    sub_401005 -------- 这里调用了func_1函数
.text:004010E4                add    esp, 8
.text:004010E7                lea    eax, [ebp+var_8]
.text:004010EA                push    eax
.text:004010EB                push    offset aS      ; "%s\r\n"
.text:004010F0                call    _printf
.text:004010F5                add    esp, 8
.text:004010F8                xor    eax, eax
.text:004010FA                pop    edi
.text:004010FB                pop    esi
.text:004010FC                pop    ebx
.text:004010FD                add    esp, 50h
.text:00401100                cmp    ebp, esp
.text:00401102                call    __chkesp
.text:00401107                mov    esp, ebp
.text:00401109                pop    ebp
.text:0040110A                retn
.text:0040110A _main_0        endp


Func_1函数
.text:00401020
.text:00401020                push    ebp
.text:00401021                mov    ebp, esp
.text:00401023                sub    esp, 4Ch
.text:00401026                push    ebx
.text:00401027                push    esi
.text:00401028                push    edi
.text:00401029                lea    edi, [ebp+var_4C]
.text:0040102C                mov    ecx, 13h
.text:00401031                mov    eax, 0CCCCCCCCh
.text:00401036                rep stosd
.text:00401038                mov    [ebp+var_4], 0
.text:0040103F                mov    [ebp+var_8], 61h
.text:00401043                mov    [ebp+var_C], 1
.text:00401049                mov    eax, [ebp+arg_0]
.text:0040104C                add    eax, [ebp+arg_4]
.text:0040104F                push    eax
.text:00401050                push    offset Format  ; "sum=%d\n"
.text:00401055                call    _printf
.text:0040105A                add    esp, 8
.text:0040105D                pop    edi
.text:0040105E                pop    esi
.text:0040105F                pop    ebx
.text:00401060                add    esp, 4Ch
.text:00401063                cmp    ebp, esp
.text:00401065                call    __chkesp
.text:0040106A                mov    esp, ebp
.text:0040106C                pop    ebp
.text:0040106D                retn
.text:0040106D sub_401020      endp



本次我们不必关心这些代码的含义,我们目前只关心的是函数对栈是如何操作的。所以不要被这些东西吓到,我们要用的只是很少的一部分


用户系统信息:Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; InfoPath.2; .NET CLR 2.0.50727; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET CLR 1.1.4322)

附件附件:

您所在的用户组无法下载或查看附件

本帖被评分 1 次
最后编辑newcenturymoon 最后编辑于 2009-05-24 19:17:29
分享到:
gototop
 

回复: 病毒分析学习笔记之二

栈是如何建造的?
    栈是一种经典的数据结构,它遵循“先进后出”的操作特性。
那么在实现栈时如何保证这种操作特性呢?我们来看一下栈的定义及相关概念。

    栈的逻辑结构与线性表相同,但栈有其特殊的操作规则,所以说栈是运算受限的线性表。我们可以定义栈是限制在表的一端进行插入和删除的线性表,允许插入/删除的一端称为栈顶,另一端称为栈底。对于插入操作称为PUSH(入栈);删除操作称为POP(出栈)。
很明显,我们要对栈进行操作,需要且仅需要记录栈顶的位置。在Intel 80x86系统中,完成这一功能的就是寄存器ESP(Extended Stack Pointer)。所有的PUSH/POP操作都通过它来完成,因而寄存器ESP是经常变化。

    聪明的读者这时会问:如何访问非栈顶的数据呢?Intel 80x86系统中解决的办法是设置另外一个寄存器EBP(Extended Base Pointer),我们用它来保存一个稳定不变的基准地址(基址),然后通过“基址+正负偏移量”的方式实现非栈顶的数据访问。也可以把EBP看作是保存栈底的位置,那么寄存EBP和ESP一起就组成一个栈。

    寄存器是CPU的记忆细胞,非常重要。我们的分析也是依靠寄存器来进行的。更多的寄存器知识可以参考计算机组成原理和汇编语言的相关教程。下面说明几个后文我们需要特别关注的Intel 80x86系统的32位寄存器。
    指令地址寄存器EIP:永远保存下一步将要执行的指令的地址。

    基址寄存器EBP:保存当前函数层的栈内存基地址,要依靠它来完成栈内存的变量寻址。
    栈顶指针寄存器ESP:保存栈顶地址(指针),系统依靠它来完成PUSH、POP操作。栈顶指针所指向的位置是存储有效数据的。故PUSH操作是先往低址移动栈顶指针,再存储数据;而POP操作是先读取数据,再往高址移动栈顶指针。

gototop
 

回复: 病毒分析学习笔记之二

函数调用时候(call)发生了什么?


我们用OD载入这个程序,通过IDA的主函数地址直接找到主函数
在主函数中直接看地址0x004010DF位置的call指令,

如何直接在OD中找到这个函数位置呢?
按下Ctrl+G组合键  此时 在”输入要跟随的表达式”的框中 输入004010DF
OD接下来就会跳转到这个位置,然后在这条指令上按下F4 程序就会跑到这里

下面我们来研究在函数调用(Call指令)的时候栈中发生了什么。
如图是还未进入0x00401005时候的情况, OD右下方显示的栈,且最上方指向的永远是栈顶也就是esp所在位置。现在我们看栈顶地址是0x0012FF1C

 附件: 您所在的用户组无法下载或查看附件


最后编辑newcenturymoon 最后编辑于 2009-05-24 19:10:31
gototop
 

回复: 病毒分析学习笔记之二

好,按F7跟进这个函数。注意看栈,大家看到了么与图1对比,可看出寄存ESP往低址偏移了4字节,现在地址是0012FF18,

 附件: 您所在的用户组无法下载或查看附件



这说明其中执行了一个PUSH操作。再看函数调用时候刚刚被压入的这个数据,也就是目前esp中所存储的数据0x004010E4,再反过头来看我们上面的代码:

.text:004010DF
call
sub_401005
 -------- 这里调用了func_1函数
.text:004010E4
add
esp, 8



在执行完0x00401005处的函数后,程序会返回到主函数中继续运行,这个调用后面的代码指令地址是0x004010e4,而上述实验中证明了在执行call指令的同时,它的返回地址被压入了栈,同时当前EIP位置(下一步将要执行的指令的地址)也跳转到了0x00401005处。

所以call指令相当于执行了一个push指令,一个jmp指令,push是在栈中压入了该函数执行后需要返回的地址,jmp则跳到要执行的函数地址中去,也就是call指令后面的地址。
gototop
 

回复: 病毒分析学习笔记之二

函数入口做了什么?


    按F8继续从刚才的地方往下走,这里有个疑问,0x401005处是一个jmp指令,为什么不直接是func_1函数呢?
这是因为我们现在使用的实验例子是DEBUG版的,在DEBUG版本中,VC汇编程序会产生一个函数跳转指令表,该表的每个表项存放一个函数的跳转指令。程序中的函数调用就是利用这个表来实现跳转到相应函数的入口地址。在DEBUG版本中增加函数跳转指令表,其目的是加快编译速度,当某函数的地址发生变化时,只需要修改相应表项即可,而不需要修改该函数的每一处引用。注意:在RELEASE版本中,不会生成这个表,也就是说call指令执行后0x00401005处的代码就是func_1函数而不是一个jmp 指令。
好了知道了这个,我们再往下走一步F8就到了真正的func_1函数了。

 附件: 您所在的用户组无法下载或查看附件
最后编辑newcenturymoon 最后编辑于 2009-05-24 19:06:06
gototop
 

回复: 病毒分析学习笔记之二

我们结合示例一句一句看,
push ebp


这句是把当前栈的栈底地址压入栈保存起来。(也就是把main()函数层的栈内存基地址(由寄存器ebp指示)入栈保存起来。)

这步走下去之后可以看到栈中被压入了0012FF80这个数据。同时一个push操作 栈顶也提升了

push ebp的作用就是在栈中保存上一个函数栈帧的栈底。
 附件: 您所在的用户组无法下载或查看附件

gototop
 

回复: 病毒分析学习笔记之二

接着mov ebp,esp
这句是把当前的栈顶地址(由寄存器esp指示)作为func1()函数层的栈内存基地址,即更新寄存器ebp为寄存器esp。也就是把现在的栈顶当作了栈底。
这步走下去之后可以看到ebp和当前栈顶esp值相等。

 附件: 您所在的用户组无法下载或查看附件
gototop
 

回复: 病毒分析学习笔记之二

接着sub esp,4c


然后,我们猛然看到,栈顶指针esp向低地址偏移76(0x4C)字节。这里相当于为func1()函数层分配了栈内存。
当前esp变为了0x0012FEC8


还记得刚才的ebp么,0x0012FF14,现在的esp是0x0012FEC8 那么这两段之间的数据就是系统为func_1开辟的栈空间,用于存储func_1函数需要用的数据。
 附件: 您所在的用户组无法下载或查看附件
gototop
 

回复: 病毒分析学习笔记之二

我们不妨再往下看
00401060
add

esp, 4Ch

从这句可以看到esp又加了4csub做了相反的动作,这就是在函数执行完毕需要返回的时候,系统把这个函数开辟的栈空间收回。所以做了相反的动作。


现在我们总结下函数调用过程中,栈中发生的一些故事吧:
假设有某个程序有main函数,func_A函数,func_B函数,在main函数中调用了func_A函数,在func_A函数中调用了func_B函数。
如图:


 附件: 您所在的用户组无法下载或查看附件

如图所示,在函数调用的过程中,伴随的系统栈中的操作如下。


★在 main 函数调用func_A 的时候,首先在自己的栈帧中压入函数返回地址,然后为


func_A 创建新栈帧并压入系统栈。


func_A 调用func_B 的时候,同样先在自己的栈帧中压入函数返回地址,然后为


func_B 创建新栈帧并压入系统栈。


func_B 返回时,func_B 的栈帧被弹出系统栈,func_A 栈帧中的返回地址被“露”


在栈顶,此时处理器按照这个返回地址重新跳到func_A 代码区中执行。

总结起来就是,当某个函数被调用时,系统栈会为这个函数开辟一个新栈帧,并把它压入系统栈中。(也就是函数开始的push ebpsub esp,xxx的过程)当函数返回时,系统栈会弹出该函数所对应的栈帧。


最后编辑newcenturymoon 最后编辑于 2009-05-24 16:45:28
gototop
 

回复: 病毒分析学习笔记之二

我们再用一个图表明ebp,esp和栈之间的关系

 附件: 您所在的用户组无法下载或查看附件


函数栈帧:ESP EBP 之间的内存空间为当前栈帧,EBP 标识了当前栈帧的底部,ESP标识了当前栈帧的顶部。


在函数栈帧中,一般包含以下几类重要信息。


1)局部变量:为函数局部变量开辟的内存空间。


2)栈帧状态值:保存前栈帧的顶部和底部(实际上只保存前栈帧的底部,前栈帧的顶


部可以通过堆栈平衡计算得到),用于在本帧被弹出后恢复出上一个栈帧。(也就是那句push ebp的作用)


3)函数返回地址:保存当前函数调用前的“断点”信息,也就是函数调用前的指令位


置,以便在函数返回时能够恢复到函数被调用前的代码区中继续执行指令。

本次主要讲了 在调用函数时候栈中的变化,通过此次大家应该对esp,ebp有了更深的了解,下次我们将继续深入研究和栈有关的知识。
本帖被评分 2 次
gototop
 
12   1  /  2  页   跳转
页面顶部
Powered by Discuz!NT