【学习笔记】《逆向工程核心原理》01-逆向分析Hello World程序
文章目录
- 1. 编译Hello world
- 2. 调试 hello world程序
- 2.1. 加载调试程序
- 2.2. 入口点
- 2.3. 跟踪401636函数
- 2.4. 设置“大本营”的四种方法
- 2.4.1. goto命令
- 2.4.2. 设置断点
- 2.4.3. 注释
- 2.4.4. 标签
- 2.5. 快速查找指定代码的四种方法
- 2.5.1. 代码执行法
- 2.5.2. 字符串检索法
- 2.5.3. API检索法 -在调用代码中设置断点
- 2.5.4. API检索法-在API代码中设置断点
- 3. 使用“打补丁”方式修改“Hello world”字符串
- 3.1. 打补丁
- 3.2. 修改字符串的两种方法
- 3.2.1. 直接修改字符串缓冲区
- 3.2.2. 在其他内存区域生成新字符串并传递给消息函数
思维导图

1. 编译Hello world
使用VS进行编译
#include "windows.h"
#include "tchar.h"
int _tmain(int argc, TCHAR *argv[])
{
MessageBox(NULL,
L"Hello World!",
L"www.reversecore.com",
MB_OK);
return 0;
}
2. 调试 hello world程序
2.1. 加载调试程序
代码窗口
默认用于显示反汇编代码,还用于显示各种注释、标签,分析代码时显示循环、跳转位置等信息
寄存器窗口
实时显示CPU寄存器的值,可用于修改特定的寄存器
数据窗口
以Hex/ASCII/Unicode值的形式显示进程的内存地址,也可在此修改内存地址
栈窗口
实时显示ESP寄存器指向的进程栈内存,并允许修改
2.2. 入口点
运行程序后的暂停地点就是程序执行的起始地址40126c
他是一段EP EntryPoint,入口点
代码
其中最引人注意的就是 call
与 jmp
两个命令
title: EP(EntryPoint,入口点)
EP是Windows可执行文件(EXE、DLL、SYS等)的代码入口点,是执行应用程 序时最先执行的代码的起始位置,它依赖于CPU。
如果没有显示出符号地址 就修改一下调试选项即可
2.3. 跟踪401636函数
我们当前的目标是在 main
函数中找出调用 MessageBox
函数的代码
这里我一直f8直到跳出提示框
此时调用了 401000
函数,这应该就是main
函数
这里下断点。
重新运行 然后f7进入内部发现了 MessageBox
函数
2.4. 设置“大本营”的四种方法
2.4.1. goto命令
我想直接到 main()
函数。那么就使用ctrl+g 输入地址 4011DF
即可跳转到指定地址
2.4.2. 设置断点
调试代码时,还可以设置BP(BreakPoint,断点)(快捷键F2)让调试流转到“大本营”
这里变成了红色就是 打好了断点
2.4.3. 注释
使用 ;
进行注释
然后在 查找->用户的注释
里面可以看到我们的注释。双击即可跳转到对应的位置
红字显示部分即是光标所处位置。注释位置与光标位置重合时,将仅以红字方式显示(所以刚开始的时候需要把光标暂时移动到其他位置)。双击相应注释,光标将自动定位到相应位置。
2.4.4. 标签
使用 :
符号可以添加标签,添加标签后会更加直观
同样,我们也可以在 查找->用户定义的标签
里面 进行查看
2.5. 快速查找指定代码的四种方法
2.5.1. 代码执行法
我们需要查找的是 main()
函数中调用 MessageBox()
函数的代码。在调试器中调试HelloWorld.exe(StepOver(F8))时,main
函数的MessageBox
函数在某个时刻就会被调用执行,
弹出消息对话框,显示“HelloWorld!”这条信息
这时候就能够定位到 main
函数的EP位置了
此方法只适合被调试的代码量程序不大、且程序功能明确的情况
如果被调试的代码量很大且比较复杂时,就不适合用此方法了
地址40100E处有一条调用 MessageBoxW()API
的语句 如图所示。
地址401002与401007处分别有一条PUSH语句,它们把消息对话框的标题与显示字符串(Title=“htp://www.reversecore.com
”,Text=“HelloWorld!
”)保存到栈(Stack)中,并作为参数传递给 MessageBoxW()
函数。
Win32应用程序中,API函数的参数是通过栈传递的。VC++中默认字符串是使用Unicode码表示的,并且,处理字符串的API函数也全部变更为Unicode系列函数。
2.5.2. 字符串检索法
OD在初次载入待调试的程序时,会先进行预分析。 此过程会查看进程内存,程序中引用的字符串和调用的API都会被摘录出来,整理到另外一个列表中。这样的列表对调试时很有用的。
查找->所有文本参考字符串
这里就能看到 helloword
双击即可跳转到 main函数
内部
可以看到这里时把 410C14
处地址存储的数据压入栈中
我们可以在数据窗口 处使用 go to
功能到对应的地址查看对应的内存地址数据
这里显示了hello world字符串。 他是以unicode编码表示的
VC++中,static字符串会被默认保存为Unicode码形式,static字符串是指在程序内部被硬编码(HardCoding)的字符串。
这里两处代码区域地址是不同的
- 410XXX是数据段
- 401XXX是代码段
2.5.3. API检索法 -在调用代码中设置断点
右键->查找->所有模块间的调用
Windows编程中,若想向显示器显示内容,则需要使用Win32API向OS请求显示输出。换言之,应用程序向显示器画面输出内容时,需要在程序内部调用Win32API。
认真观察一个程序的功能后,我们能够大致推测出它在运行时调用的Win32API,若能进一步查找到调用的Win32API,
则会为程序调试带来极大便利。以HelloWorld.exe为例,它在运行时会弹出一个消息窗口,由此我们可以推断出该程序调用了 user32.MessageBoxW()API
我们可以看到调用了 MessageBoxW()
的代码。该函数位于40100E处
它是 user32.MessageBoxW()
的API
双击即可定位到对应的位置
对于程序中调用的API,OllyDbg如何准确摘录出它们的名称呢?首先,它不是通过查看源代码来摘取的,若要了解其中的原理,需要理解PE文件格式的IAT(ImportAddress Table,导入地址表)结构
2.5.4. API检索法-在API代码中设置断点
OllyDbg并不能为所有可执行文件都列出API函数调用列表。使用压缩器/保护器工具对可执行文件进行压缩或保护之后,文件结构就会改变,此时OllyDbg就无法列出API调用列表了(甚至连调试都会变得十分困难)
**●压缩器(Runtime'Packer,运行时压缩器)**
压缩器是一个实用压缩工具,能够压缩可执行文件的代码、数据、资源等,与普通压缩不同,它压缩后的文件本身就是一个可执行文件。
**●保护器**
保护器不仅具有压缩功能,还添加了反调试、反模拟、反转储等功能,能够有效保护进程。若想仔细分析保护器,分析者需要具有高级逆向知识。
这种情况下,DLL代码库被加载到进程内存后,我们可以直接向DLL代码库添加断点。API
是操作系统对用户应用程序提供的一系列函数,它们实现于C:Windows\systems32文件夹中的*.dll
文件(如kernel32.dll、user32.dll、gdi32.dll、advapi32.dll、ws2_32.dll等)内部。
简言之,我们编写的应用程序执行某种操作时(如各种I/O操作),必须使用OS提供的API向OS提出请求,然后与被调用API对应的系统DLL文件就会被加载到应用程序的进程内存。
Alt+M
打开内存映射窗口
在其中我们可以查看到一部分 helloworld.exe
进程内存。
也可以发现 USER32
库被加载到了内存
右键->查找->所有模块中的名字 可以列出被加载的DLL文件中提供的所有API
单击名字进行排序,然后键盘输入 MessageBoxW
后可以定位到对应的函数
USER32
模块中有一个Export类型的MessageBoxW
函数(不同系统环境下函数地址不同)。
双击 MessageBoxW
函数后就会显示其代码,它实现于 USER32.dll
库中
可以发现 MessageBoxW
函数的地址空间与 hello world.exe
使用的地址空间完全不同
我们在函数起始地址上设置断点。然后重新执行程序
若 HelloWorld.exe
应用程序中调用了 MessageBoxW
API,则调试时程序运行到该处就会暂停
结果确实是在这里暂停了。也说明了程序调用了这个API
在寄存器的窗口出查看ESP可以发现此时的值为19FF18 ,它是进程栈的地址。在右下角的栈窗口中可以看到更详细的信息。
可以发现ESP值的19FF18处对应了一个返回地址401014
HelloWorld.exe
的 main
函数调用完 MessageBoxW
函数后,程序执行流将返回到该地址处。
按Ctrl+F9快捷键使程序运行到 MessageBoxW
函数的RETN命令处
然后按F7键也可以返回到 401014
地址处 地址 401014
的上方就是地址 40100E
,它正是调用 MessageBoxW
函数的地方
3. 使用“打补丁”方式修改“Hello world”字符串
3.1. 打补丁
利用“打补丁”技术不仅可以修复已有程序中的Bug,还可以向程序中添加新功能。“打补丁”的对象可以是文件、内存,还可以是程序的代码、数据等。
本示例中,我们将使用“打补丁”技术把HelloWorld.exe程序消息窗口显示的“HelloWorld!”字符串更改为其他字符串。
重新调试并将调试流运行到 main
函数的起始地址处(401000),并下断点
3.2. 修改字符串的两种方法
下面介绍2种简单的修改字符串的方法。
①直接修改字符串缓冲区(buffer)。
②在其他内存区域生成新字符串并传递给消息函数。
以上2种方法各有优缺点,下面分别了解一下。
3.2.1. 直接修改字符串缓冲区
MessageBoxW
函数的字符串参数 “HelloWorld!”
保存在地址410C14
处的一段缓冲区中,只
要修改这段内容,就可以修改 MessageBoxW
函数显示出的字符串。
在Dump窗口中按Ctrl+G快捷键执行Goto命令,在弹出窗口中输入 410C14
进入字符串缓冲区。然后使用鼠标选中 410C14
地址处的字符串,按Ctrl+E快捷键打开编辑窗口
这里可以看到 占用了24个字节 采用了Unicode编码。一个字符占用2个字节,ASCII则只占用一个字节。我们修改一下数据
这里修改后的字符串如果比原来的字符串长。可能会覆盖掉一些原本字符串后存在的一些有意义的数据。这会导致数据损坏。是十分危险的。
然后继续执行。会发现已经被修改了
以上就是直接更改字符串缓冲区来修改的方法。
这种方法的优点是使用起来十分简单,但缺点是它对新字符串的长度有限制,新字符串的长度不应比原字符串长。
可执行文件保存字符串时一般会给字符串多留出一些空间,
所以,如果你的运气足够好,使用更长的字符串覆盖原字符串时,即使原字符串后面的部分空间被侵占,程序仍然能正常运行。
但是我们不建议大家这样做,随着这些不安定因素逐渐增多,整个系统的稳定性最终会遭到破坏。
请记住,我们是解决问题的人,而不是制造麻烦的
保存更到可执行文件
选择修改后的数据 然后复制到可执行文件
在弹出窗户右键保存程序即可
3.2.2. 在其他内存区域生成新字符串并传递给消息函数
因为修改字符串缓冲区存在长度问题。
这里有了另外一种办法
我们先进入 main
函数内部
401007
地址处有一条 PUSH 0x410c14
命令,它把410c14
地址处的“HelloWorld!”
字符串以参数形式传递给MessageBoxW
函数。
向 MessageBoxW
函数传递字符串参数时,传递的是字符串所在区域的首地址。如果改变了字符串地址,消息框就会显示变更后的字符串。
在内存的某个区域新建一个长字符串,并把新字符串的首地址传递给MessageBoxWO函数,可以认为传递的是完全不同的字符串地址。
我们跳转到 410c14
然后往下翻。可以可看到一块空的区域
这些内存区域由null填充结束。这就是程序中未使用的NULL填充区域
应用程序被加载到内存时有一个最小的内存分配大小,一般为1000。即使程序运行时只占用100内存,它被加载到内存时仍然会分到1000左右的内存,这些内存一部分被程序占用,其余部分为空余区域,全部被填充为NULL。
我们将此处作为字符串缓冲区传递给 MessageBoxw
函数。使用 ctrl+e
写入数据
然后把新缓冲区的地址 411B04
传递给 messageboxw
函数
选中对应的汇编代码。然后点击空格修改即可
修改后的效果
运行试试
若把修改后的代码重新保存为程序文件,可以发现程序无法正常运行,这是由
411B04这一地址引起的。可执行文件被加载到内存并以进程形式运行时,文件并非原
封不动地被载入内存,而是要遵循一定规则进行。
这一过程中,通常进程的内存是存在的,但是相应的文件偏移(offset)并不存在。上面示例中,与内存411B04对应的文件偏移就不存在,所以修改后的程序无法正常运行