当前位置: 首页 > article >正文

【学习笔记】《逆向工程核心原理》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. 在其他内存区域生成新字符串并传递给消息函数

思维导图
Pasted image 20250311005435

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. 加载调试程序

Pasted image 20250310130759

代码窗口
默认用于显示反汇编代码,还用于显示各种注释、标签,分析代码时显示循环、跳转位置等信息
寄存器窗口
实时显示CPU寄存器的值,可用于修改特定的寄存器
数据窗口
以Hex/ASCII/Unicode值的形式显示进程的内存地址,也可在此修改内存地址
栈窗口
实时显示ESP寄存器指向的进程栈内存,并允许修改

2.2. 入口点

运行程序后的暂停地点就是程序执行的起始地址40126c
他是一段EP EntryPoint,入口点 代码
其中最引人注意的就是 calljmp 两个命令
Pasted image 20250310132000

title: EP(EntryPoint,入口点)
 EP是Windows可执行文件(EXE、DLL、SYS等)的代码入口点,是执行应用程 序时最先执行的代码的起始位置,它依赖于CPU。

如果没有显示出符号地址 就修改一下调试选项即可
Pasted image 20250310134346

2.3. 跟踪401636函数

我们当前的目标是在 main 函数中找出调用 MessageBox 函数的代码
这里我一直f8直到跳出提示框
此时调用了 401000 函数,这应该就是main函数
Pasted image 20250310141808
这里下断点。
重新运行 然后f7进入内部发现了 MessageBox 函数
Pasted image 20250310141905

2.4. 设置“大本营”的四种方法

2.4.1. goto命令

我想直接到 main() 函数。那么就使用ctrl+g 输入地址 4011DF 即可跳转到指定地址
Pasted image 20250310172853

2.4.2. 设置断点

调试代码时,还可以设置BP(BreakPoint,断点)(快捷键F2)让调试流转到“大本营”
Pasted image 20250310173005
这里变成了红色就是 打好了断点

2.4.3. 注释

使用 ; 进行注释
Pasted image 20250310173129
然后在 查找->用户的注释 里面可以看到我们的注释。双击即可跳转到对应的位置
Pasted image 20250310173510

红字显示部分即是光标所处位置。注释位置与光标位置重合时,将仅以红字方式显示(所以刚开始的时候需要把光标暂时移动到其他位置)。双击相应注释,光标将自动定位到相应位置。

2.4.4. 标签

使用 : 符号可以添加标签,添加标签后会更加直观
同样,我们也可以在 查找->用户定义的标签里面 进行查看
Pasted image 20250310173921

2.5. 快速查找指定代码的四种方法

2.5.1. 代码执行法

我们需要查找的是 main() 函数中调用 MessageBox() 函数的代码。在调试器中调试HelloWorld.exe(StepOver(F8))时,main函数的MessageBox函数在某个时刻就会被调用执行,
弹出消息对话框,显示“HelloWorld!”这条信息
这时候就能够定位到 main 函数的EP位置了
Pasted image 20250310175222

此方法只适合被调试的代码量程序不大、且程序功能明确的情况
如果被调试的代码量很大且比较复杂时,就不适合用此方法了

Pasted image 20250310175450

地址40100E处有一条调用 MessageBoxW()API 的语句 如图所示。
地址401002401007处分别有一条PUSH语句,它们把消息对话框的标题与显示字符串(Title=“htp://www.reversecore.com”,Text=“HelloWorld!”)保存到栈(Stack)中,并作为参数传递给 MessageBoxW() 函数。

Win32应用程序中,API函数的参数是通过栈传递的。VC++中默认字符串是使用Unicode码表示的,并且,处理字符串的API函数也全部变更为Unicode系列函数。

2.5.2. 字符串检索法

OD在初次载入待调试的程序时,会先进行预分析。 此过程会查看进程内存,程序中引用的字符串和调用的API都会被摘录出来,整理到另外一个列表中。这样的列表对调试时很有用的。
查找->所有文本参考字符串
Pasted image 20250310180123
这里就能看到 helloword 双击即可跳转到 main函数 内部
Pasted image 20250310180212
可以看到这里时把 410C14 处地址存储的数据压入栈中
我们可以在数据窗口 处使用 go to 功能到对应的地址查看对应的内存地址数据
Pasted image 20250310180750
这里显示了hello world字符串。 他是以unicode编码表示的

VC++中,static字符串会被默认保存为Unicode码形式,static字符串是指在程序内部被硬编码(HardCoding)的字符串。

这里两处代码区域地址是不同的
Pasted image 20250310181413

  • 410XXX是数据段
  • 401XXX是代码段
2.5.3. API检索法 -在调用代码中设置断点

右键->查找->所有模块间的调用
Windows编程中,若想向显示器显示内容,则需要使用Win32API向OS请求显示输出。换言之,应用程序向显示器画面输出内容时,需要在程序内部调用Win32API。
认真观察一个程序的功能后,我们能够大致推测出它在运行时调用的Win32API,若能进一步查找到调用的Win32API,
则会为程序调试带来极大便利。以HelloWorld.exe为例,它在运行时会弹出一个消息窗口,由此我们可以推断出该程序调用了 user32.MessageBoxW()API
Pasted image 20250310181814
我们可以看到调用了 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 进程内存。
Pasted image 20250310233254
也可以发现 USER32 库被加载到了内存
Pasted image 20250310233338

右键->查找->所有模块中的名字 可以列出被加载的DLL文件中提供的所有API
单击名字进行排序,然后键盘输入 MessageBoxW 后可以定位到对应的函数
Pasted image 20250310234025
USER32模块中有一个Export类型的MessageBoxW函数(不同系统环境下函数地址不同)。
双击 MessageBoxW 函数后就会显示其代码,它实现于 USER32.dll 库中
Pasted image 20250310234157
可以发现 MessageBoxW 函数的地址空间与 hello world.exe 使用的地址空间完全不同
我们在函数起始地址上设置断点。然后重新执行程序

HelloWorld.exe 应用程序中调用了 MessageBoxW API,则调试时程序运行到该处就会暂停
结果确实是在这里暂停了。也说明了程序调用了这个API
Pasted image 20250310234556

在寄存器的窗口出查看ESP可以发现此时的值为19FF18 ,它是进程栈的地址。在右下角的栈窗口中可以看到更详细的信息。
Pasted image 20250310234901
可以发现ESP值的19FF18处对应了一个返回地址401014
HelloWorld.exemain 函数调用完 MessageBoxW 函数后,程序执行流将返回到该地址处。
Pasted image 20250310235301
按Ctrl+F9快捷键使程序运行到 MessageBoxW 函数的RETN命令处
然后按F7键也可以返回到 401014 地址处 地址 401014 的上方就是地址 40100E,它正是调用 MessageBoxW 函数的地方
Pasted image 20250311000129

3. 使用“打补丁”方式修改“Hello world”字符串

3.1. 打补丁

利用“打补丁”技术不仅可以修复已有程序中的Bug,还可以向程序中添加新功能。“打补丁”的对象可以是文件、内存,还可以是程序的代码、数据等。
本示例中,我们将使用“打补丁”技术把HelloWorld.exe程序消息窗口显示的“HelloWorld!”字符串更改为其他字符串。

重新调试并将调试流运行到 main 函数的起始地址处(401000),并下断点
Pasted image 20250311000641

3.2. 修改字符串的两种方法

下面介绍2种简单的修改字符串的方法。
①直接修改字符串缓冲区(buffer)。
②在其他内存区域生成新字符串并传递给消息函数。
以上2种方法各有优缺点,下面分别了解一下。

3.2.1. 直接修改字符串缓冲区

MessageBoxW 函数的字符串参数 “HelloWorld!” 保存在地址410C14处的一段缓冲区中,只
要修改这段内容,就可以修改 MessageBoxW 函数显示出的字符串。
在Dump窗口中按Ctrl+G快捷键执行Goto命令,在弹出窗口中输入 410C14 进入字符串缓冲区。然后使用鼠标选中 410C14 地址处的字符串,按Ctrl+E快捷键打开编辑窗口
Pasted image 20250311001049
这里可以看到 占用了24个字节 采用了Unicode编码。一个字符占用2个字节,ASCII则只占用一个字节。我们修改一下数据
Pasted image 20250311001446

这里修改后的字符串如果比原来的字符串长。可能会覆盖掉一些原本字符串后存在的一些有意义的数据。这会导致数据损坏。是十分危险的。

然后继续执行。会发现已经被修改了
Pasted image 20250311001659

以上就是直接更改字符串缓冲区来修改的方法。
这种方法的优点是使用起来十分简单,但缺点是它对新字符串的长度有限制,新字符串的长度不应比原字符串长。

可执行文件保存字符串时一般会给字符串多留出一些空间,
所以,如果你的运气足够好,使用更长的字符串覆盖原字符串时,即使原字符串后面的部分空间被侵占,程序仍然能正常运行。
但是我们不建议大家这样做,随着这些不安定因素逐渐增多,整个系统的稳定性最终会遭到破坏。
请记住,我们是解决问题的人,而不是制造麻烦的

保存更到可执行文件
选择修改后的数据 然后复制到可执行文件
Pasted image 20250311002418
在弹出窗户右键保存程序即可
Pasted image 20250311002515

3.2.2. 在其他内存区域生成新字符串并传递给消息函数

因为修改字符串缓冲区存在长度问题。
这里有了另外一种办法

我们先进入 main 函数内部
Pasted image 20250311002755
401007 地址处有一条 PUSH 0x410c14 命令,它把410c14地址处的“HelloWorld!”字符串以参数形式传递给MessageBoxW函数。
MessageBoxW 函数传递字符串参数时,传递的是字符串所在区域的首地址。如果改变了字符串地址,消息框就会显示变更后的字符串。
在内存的某个区域新建一个长字符串,并把新字符串的首地址传递给MessageBoxWO函数,可以认为传递的是完全不同的字符串地址。

我们跳转到 410c14 然后往下翻。可以可看到一块空的区域
Pasted image 20250311003411
这些内存区域由null填充结束。这就是程序中未使用的NULL填充区域

应用程序被加载到内存时有一个最小的内存分配大小,一般为1000。即使程序运行时只占用100内存,它被加载到内存时仍然会分到1000左右的内存,这些内存一部分被程序占用,其余部分为空余区域,全部被填充为NULL。

我们将此处作为字符串缓冲区传递给 MessageBoxw 函数。使用 ctrl+e 写入数据
Pasted image 20250311003800
然后把新缓冲区的地址 411B04 传递给 messageboxw 函数
选中对应的汇编代码。然后点击空格修改即可
Pasted image 20250311003947
修改后的效果
Pasted image 20250311004029
运行试试
Pasted image 20250311004045

若把修改后的代码重新保存为程序文件,可以发现程序无法正常运行,这是由
411B04这一地址引起的。可执行文件被加载到内存并以进程形式运行时,文件并非原
封不动地被载入内存,而是要遵循一定规则进行
这一过程中,通常进程的内存是存在的,但是相应的文件偏移(offset)并不存在。上面示例中,与内存411B04对应的文件偏移就不存在,所以修改后的程序无法正常运行


http://www.kler.cn/a/581341.html

相关文章:

  • 重构及封装
  • 2025-3-11 leetcode刷题情况(贪心算法--区间问题)
  • 计算机视觉算法实战——昆虫识别检测(主页有源码)
  • CSS小玩意儿:目录
  • 树莓派4B使用Ubuntu20.04连接不上热点
  • conda创建Python虚拟环境的原理
  • 基于 Vue 的Deepseek流式加载对话Demo
  • 基于python下载ERA5小时尺度和月尺度的数据
  • 【反无人机数据集】【目标检测】基于深度学习和距离分析的无人机检测图像处理技术应用
  • 基于MATLAB的冰块变化仿真
  • XTDrone调试报错问题集锦
  • 动态规划详解(二):从暴力递归到动态规划的完整优化之路
  • NLP常见任务专题介绍(2)-多项选择任务(MultipleChoice)训练与推理模板
  • SpringBoot开发——整合SpringReport开源报表工具
  • Git的命令学习——适用小白版
  • Android实现Socket通信
  • Chrome 扩展开发 API实战:Bookmarks(二)
  • Python高级之操作Mysql
  • 华为OD机试 - 平均像素值-贪心算法(Java 2024 E卷 100分)
  • 【区块链+ 医疗健康】基于区块链和AI 技术的儿童近视防控大数据平台 | FISCO BCOS 应用案例