这有点惊奇,不过这个列表非常的小。通常,对于一个商业产品来说,需要数百或数千函数。不过因为我们的程序太简单了,它需要的不是很多。你想想我们的程序干了什么,看起来好像是那么多的函数只完成了如此简单的功能!欢迎来到Windows。该窗口首先显示了DLL的名字,紧跟着的是函数的名字。比如,User32.LoadIconA是在DLL User32中,函数名字是LoadIconA。该函数通常用来载入窗口左上角的图标。
下一步,我们搜索下程序中的所有字符串。右键点击反汇编窗口,选择“SearchFor”-> “All Referenced Text Strings”:
该窗口显示了我们程序中所有能找到的字符串。因为程序非常简单,所以这里只有一点。大多数的程序如果没有加壳或混淆的话,都有多得多的字符串(有时能达到十万)。这种情况下,你有可能一个也看不到!加壳工具这样做的原因是逆向工程师(至少新人是这样)严重依赖字符串来查找重要的函数。而删除了字符串后就会难的多。想象一下,如果你搜索字符串然后看到了“Congratulations! You entered the correct serial(恭喜!你输入了正确的序列号)”会怎么样?嗯,这对于逆向来说是巨大的帮助(我们会一次又一次的看到这个)。另外,双击其中的字符串,你会来到反汇编窗口中使用该字符串的指令那。这是一个很好的特性,你能够正确的跳转到使用字符串的代码。
二、运行程序
如果你看Olly的左上角的话,会看到一个黄色背景的小区块,里面写着“暂停(Pause)”。意思是程序已经暂停了(本例中是在开始的时候),等着你进行其他操作。所以,咱们开始干一票吧!按一下F9(或者从“Debug”菜单中选择“Run”)。一会儿后,我们的程序会弹出一个对话框(它有可能显示在Olly的后面,所以最小化Olly以确保能看见窗口)。
当一个寄存器变红的时候,这就意味着最后执行的指令修改了该寄存器。本例中,ESP寄存器(用来存放指向栈顶的地址)增加了1,因为我们向栈中压了一个新值。EIP寄存器增加了2,其中存放了将要运行的指令的地址。因为我们已经不在地址401000了,而是在401002。因为上一个运行的指令是两个字节长。我们现在暂停在下一个指令处。这个指令是在401002,这正是当前EIP的值。
Olly现在暂停的指令是一个CALL。CALL指令意味着我们要临时暂停在我们当前所在的函数中,然后去运行另一个函数。这类似于高级语言中的方法调用,举个例子:
int main()
{
int x = 1;
call doSomething();
x = x + 1;
}
这段代码中,我们首先让x等于1,然后呢我们要在逻辑上暂停这行代码,转而去调用doSomething()。当doSomething()执行完毕后,我们会返回我们原来的逻辑,然后将x加1。
当然,在汇编语言里也是一样。我们首先往栈中压了一个0,现在呢我们又想调用一个函数,例子中调用了Kernel32 dll中的GetModuleHandleA():
你会看到在一块的四个PUSH语句。这回当你四次按下F8的时候,注意观察堆栈区,会看到栈的增长(确实是向下增长,还记不记得那个盘子的例子?)。我觉得我们开始理解什么是压栈了......
你可能会问我们为什么要将这些乱七八糟的数字往栈里压。本例中这四个数字是作为参数传递给函数的(那个函数是在地址401021处)。我们将前面的那个高级语言程序做一点点修改就会比较清楚了:
int main()
{
int x = 1;
int y = 0;
call doSomething( x, y );
x = x + 1;
}
这里我们声明了两个变量x和y,并且将它们传递给了doSomething()函数。doSomething函数将会(可能)对这些变量做些什么,然后将控制权还给调用该函数的程序。通过堆栈是将变量传递给函数的主要方法之一:每个变量被压进堆栈,然后调用函数。然后在函数中,这些变量被访问到。通常PUSH指令的逆操作是POP。
堆栈并不是做这件事的唯一方法,它只是最常用的。这些变量也可以被放到寄存器中,然后在被调用的函数内部访问寄存器。不过本例中,我们程序的编译器选择将变量放到堆栈中。在你学了汇编语言后,这些东西都会变得清晰(你正在学习汇编语言,不是吗?)。后面我们还会复习几次的。
现在,如果我们再按一次F8,你会注意到Olly的工具栏中会显示“Runing”,我们程序的对话框就会显示。这是因为我们单步步过了那个CALL,说明那个CALL中存在程序的大部分。这个调用的代码是等待用户进行一些操作的循环,所以我们永远也不会将控制权交给CALL的下一行。那么,让我们修复它......。点击回到我们的程序,点那个关闭按钮来结束应用。Olly会立即暂停在那个CALL的下一行:
3、内存断点(Memery Breakpoints)
有时候你想查找程序内存中的字符串或在常量,但是你又不知道程序在内存的什么地方。你可以用内存断点来告诉Olly只要任何一条指令读或写一个内存地址(或许多内存地址),然后暂停就行,在任何地方都无所谓。有三种方法设置内存断点。
对于一条指令,右键点击该行,然后选择Breakpoint->Memory, On Access or Memory, On Write。
要在内存数据区设置断点,在数据窗口中选中一个或多个字节,然后右键选择和上面一样的操作。
你也可以对整个内存区域设置断点。打开内存映射窗口(“Me”图标或View->Memery),右键相关内存区域,在弹出菜单中选择“ Set Break On Access for either Access or Write”。
五、内存数据面板的使用
你可以用数据面板检查被调试进程内存空间中的内容。如果反汇编窗口的指令、寄存器或堆栈中的任何一项包含了对内存位置的引用,你可以在该引用上右键然后选择“Follow in Dump”,随即数据面板就会向你显示该地址引用的内容。你也可以在数据面板的任何地方右键单击选择“GoTo”,然后输入要查看的地址。咱们现在试试。
确保FirstProgram已经载入并且停在了入口处。现在,按下F8八次,来到了401021地址指令处,该处指令是CALL FirstPro.40102c。如果你注意看这行的话,会注意到这个CALL会向下跳转到40102c处,在当前行下面的第三行的地方。按下F7我们单步步入那个跳转,然后我们就来到了40102c。记住这是一个CALL指令,所以我们最后还是会回到401021的(至少是该条指令后面的那条)。
现在,我们看看断下的那行。相关指令是MOV [LOCAL.3], FirstPro.00403009。我确定你知道(因为你已经学了汇编语言:P)这条指令是将地址00403009中的内容移动到堆栈中(这里Olly是用LOCAL.3表示的)。你可以在注释列看到Olly已经发现了该地址处的内容是字符串“MyMenu”。好,下面让我们看看。在指令上右键,选择“Follow in Dump(数据窗口跟随)”。注意这里有好几选项:
就像你看到的,数据窗口显示的内存是从403009开始的。正是Olly按指令从中载入字符串的地址。在右边你可以看到字符串“MyMenu”。左边你可以看到每个字符的十六进制数据。你可能注意到了在“MyMenu”后面有些其他的字符串。这些字符串会在程序的其他部分被用到。
六、最后,来点有意思的!
此次教程的最后,让我们做些有意思的事情。让我们修改二进制数据来显示我们自己的信息!我们将字符串“Dialog As Main”改成我们自己的,然后看看效果。
首先,在数据窗口的ASCII列,点那个“Dialog As Main”中的“D”:
注意,左边的第一个十六进制数据也高亮了。这个数字对应字母“D”。如果你查查ASCII码表的话,就会发现字母“D”的十六进制数正是0x44。现在,选中整个“Dialog As Main”字符串:
在选中的内容上右键,选择“Binary” -> “Edit”。我们就可以修改我们程序在内存的内容:
然后就会弹出一个如下的窗口:
第一个文本框以ASCII码的形式显示字符串。第二个文本框是以UNICODE形式(我们的程序用不着,所以空着),最后那个文本框是相关字符串的原始数据。好,咱们改一下。点一下字符串的第一个字母(“D”),然后输入任何你想要将“Dialog As Main”覆盖掉的内容。要注意的是你输入的长度,别多了。否则你就会覆盖掉程序需要的其他字符串,或者更糟糕是覆盖掉了程序需要的代码!!!这里呢,我输入的是“Program R4ndom”: