我们编写的源码经过编译转化为exe可执行文件,而exe则是二进制文件,在分析二进制文件时,为了更好地理解它,我们通过调试器进行反汇编处理,将二进制代码转化为汇编语言指令代码
而代码窗口中的内容又有以下布局:
004011A0 | E8 6715000 | CALL 0040270C | |
---|---|---|---|
进程的虚拟内存地址 | x86 CPU指令 | 反汇编代码 | 此部分空出为注释区,可自己添加 |
EP(EntryPoint):指的是可执行代码的入口点
快捷键 | 描述 |
---|---|
CTRL+F2 | 终止当前进程,重新开始调试 |
F7 | 执行一句操作码,遇到call将会跳转进入函数 |
F8 | 执行一句操作码,遇到call执行函数,但是不会进入函数 |
CTRL+F9 | 一直在函数内部运行,直到retn指令跳出(和F8的区别在于,此指令是执行发起指令时所在函数的return,F8就是一个简单的跳过功能 |
CTRL+G | 移动到指定地址,用来查看代码或内存(运行状态下不可用) |
F4 | 执行到光标位置,直接跳转到要调试的地址 |
; | 为当前位置填写注释 |
: | 为当前位置填写标签 |
F2 | 设置或者取消断点 |
F9 | 一直运行,存在断点则停止在断点处 |
* | 显示当前EIP指针的位置 |
- | 显示上一个光标的位置 |
Enter | 若存在call或者jmp则跟踪并显示相关地址 |
ALT+B | 显示存在的断点列表 |
我们已知目标可执行文件调用了MassageBox的API,则我们只需要执行到目标API中的函数即可,我们通常的思路是,按F7或者F8执行程序,遇到call或者jmp指令就使用F7执行进入函数(遇到非目标函数ctrl+F9)或者enter查看函数内容,非目标函数则使用-回到原来的的位置,按F8继续调试(留意可能的API调用或程序内容)
在ollydbg的右键菜单中使用search for中的all referenced strings其中内容是其在调试之初对程序使用的字符串和API都将被摘录出来成为列表
在程序运行后,我们可以根据运行效果,猜测运行时调用了哪些API,可以直接使用ALL intermodular calls查看调用的所有API,不过在文件受到压缩或保护后,ollydbug就无法列出API列表了,此时我们要进行调试我们需要为加载进内存中的DLL代码库设置断点
压缩器(运行时压缩器):它可以将可执行文件的代码,数据,资源压缩,但是其压缩完成后仍是一个可执行文件
保护器:保护器具有压缩功能,并且还添加了反调试,反模拟,反转储,可有效保护进程
API的实现:API实际上是系统提供的一系列函数,他们位于系统提供的DLL库中,我们的程序进行的一系列操作必须通过系统的API向系统提出请求,系统才会将对应DLL文件加载到应用程序的进程内存
我们可以在ollydbg的view菜单(也可以ALT+M)中找到memory map打开内存映射窗口,看到程序请求的DLL库列表等,为了找到调用的API,我们使用search for中的name选项在其中寻找对应API,双击即可定位到该位置,注意是本可执行程序的name列表,有时候选定了一个地址name列表可能会是对应的DLL库中的name列表
修改程序中的字符串有两种方式:
我们在定位字符串后,找到字符串所在内存地址,然后在数据窗口查找该地址,然后选中要修改的字符串部分,使用ctrl+e进行快速编辑该字符串即可修改,需要注意,如果修改后字符串长度大于原有字符串,可能会导致程序后面的数据被覆盖,此时就有可能发生内存错误(不建议比原有字符串长),并且在修改时一定要注意unicode字符串的结尾是NULL占据两个字节。
如果要保存文件,我们则应该在数据窗口选中修改后的字符串,右键找到edit中的Copy to executable或Copy all modifications to executable,进入新的界面中选中save file,然后根据提示保存即可,在文件命名处要加上exe的可执行文件扩展名
注意:可执行文件在保存时一般会给字符串留出更多空间,所以有时候以更长的字符串覆盖也不会出问题
这种方法实际上是利用了一个特性,应用程序被加载到内存有一个最小内存分配大小,即使程序大小未达到分配内存大小,其他的内存也会被分配,并且以NULL填充
我们可以利用填充为NULL的内存,在其中添加需要的字符串,最后在传递字符串到API处,将PUSH的地址修改成我们修改处的地址起始点即可(选中对应指令后按空格键即可通过Assemble窗口修改)
首先我们自己写一个和作者一摸一样的程序,尝试逆向它:
#include "windows.h"
#include "tchar.h"
int _tmain(int argc, TCHAR* argv[]) {
MessageBox(NULL, L"Hello World",
L"This is a test file",
MB_OK);
return 0;
}
首先我们通过all referenced strings找到我们的对应字符串(可以看到我这里字符串地址是B22100和B22128),然后打上断点过后按F9执行到断点处,然后在数据窗口找到字符串内存:
进行修改(注意keep size选项,保证修改不会超过范围),第一处改为测试通过,第二处改为大写字母:
调试后修改成功:
保存文件:
我们重新调试文件,然后找到字符串,在填充为NULL的内存区域加入字符串内容(如下我们在283020处建立新字符串):
改变字符串载入地址:
进行调试,修改成功:
注意:此修改方法保存的可执行文件无法正常运行,当可执行文件加载进入内存时,通常来讲进程的内存仍然存在,但是对应的文件偏移并不存在,所以无法正常运行
文件偏移是指在计算机中对文件进行访问时,当前位置与文件开头之间的距离。每个文件都可以看作是一系列字节的集合,文件偏移表示从文件开头到当前位置所经过的字节数。
文件偏移常用于读取和写入文件的特定位置。通过设置文件偏移,可以确定下一个要读取或写入的位置,以便在文件中执行精确的操作。
在大多数编程语言和操作系统中,文件偏移以字节为单位进行计量。初始文件偏移通常为0,表示从文件的开头开始。随着读取或写入操作的进行,文件偏移会自动增加,指向下一个要操作的位置。
文件偏移对于处理文件的不同部分非常有用。通过更改文件偏移,可以在文件中前后移动,查找和更新特定位置的数据,或者追加新数据到文件末尾。
需要注意的是,在某些情况下,文件偏移可能以其他单位(如块或记录)进行计量,这取决于使用的文件系统或应用程序的要求。