94

我正在尝试使用 Minesweeper 作为示例应用程序来学习逆向工程。我在一个简单的 WinDbg 命令上找到了这篇MSDN 文章,该命令显示了所有地雷,但它很旧,没有详细解释,也不是我想要的。

我有IDA Pro 反汇编器和WinDbg 调试器,并且我已经将 winmine.exe 加载到它们中。有人可以为这些程序中的任何一个提供一些实用技巧,以查找代表雷场的数据结构的位置吗?

在WinDbg中我可以设置断点,但是我很难想象在什么点设置断点以及在什么内存位置。同样,当我在 IDA Pro 中查看静态代码时,我什至不确定从哪里开始找到代表雷区的函数或数据结构。

Stackoverflow 上是否有任何逆向工程师可以为我指明正确的方向?

4

10 回答 10

125

第 1 部分,共 3 部分


如果你对逆向工程很认真——忘掉培训师和作弊引擎吧。

好的逆向工程师首先应该了解操作系统、核心API函数、程序的一般结构(什么是运行循环、windows结构、事件处理例程)、文件格式(PE)。Petzold 的经典著作《Programming Windows》可以提供帮助(www.amazon.com/exec/obidos/ISBN=157231995X)以及在线 MSDN。

首先,您应该考虑在哪里可以调用雷区初始化例程。我想到了以下几点:

  • 当你启动游戏时
  • 当你点击开心的脸
  • 当你点击 Game->New 或按 F2
  • 当你改变关卡难度时

我决定查看 F2 加速器命令。

要查找加速器处理代码,您需要查找窗口消息处理过程 (WndProc)。它可以通过 CreateWindowEx 和 RegisterClass 调用来追踪。

读书:

打开 IDA,Imports 窗口,找到“CreateWindow*”,跳转到它并使用“Jump xref to operand (X)”命令查看它的调用位置。应该只有一个电话。

现在在上面查找RegisterClass 函数和它的参数WndClass.lpfnWndProc。在我的例子中,我已经命名了函数 mainWndProc。

.text:0100225D                 mov     [ebp+WndClass.lpfnWndProc], offset mainWndProc
.text:01002264                 mov     [ebp+WndClass.cbClsExtra], edi
.text:01002267                 mov     [ebp+WndClass.cbWndExtra], edi
.text:0100226A                 mov     [ebp+WndClass.hInstance], ecx
.text:0100226D                 mov     [ebp+WndClass.hIcon], eax

.text:01002292                 call    ds:RegisterClassW

在函数名称上按 Enter(使用“N”将其重命名为更好的名称)

现在看看

.text:01001BCF                 mov     edx, [ebp+Msg]

这是消息 id,在按下 F2 按钮的情况下应该包含 WM_COMMAND 值。您将找到它与 111h 相比的位置。这可以通过在 IDA 中跟踪 edx 或通过在 WinDbg 中设置条件断点并在游戏中按 F2 来完成。

无论哪种方式都会导致类似

.text:01001D5B                 sub     eax, 111h
.text:01001D60                 jz      short loc_1001DBC

右键单击 111h 并使用“符号常量”->“使用标准符号常量”,输入 WM_ 并回车。你现在应该有

.text:01001D5B                 sub     eax, WM_COMMAND
.text:01001D60                 jz      short loc_1001DBC

这是找出消息 id 值的简单方法。

要了解加速器处理,请查看:

对于一个单一的答案,这是相当多的文字。如果你有兴趣,我可以再写几篇文章。长话短说雷区存储为字节数组 [24x36],0x0F 表示未使用字节(播放较小的字段),0x10 - 空字段,0x80 - 我的。

第 2 部分,共 3 部分


好的,让我们继续使用 F2 按钮。

根据按下 F2 按钮时使用键盘加速器wndProc 函数

... 接收 WM_COMMAND 或 WM_SYSCOMMAND 消息。wParam 参数的低位字包含加速器的标识符。

好了,我们已经找到WM_COMMAND在哪里处理了,但是如何确定对应的wParam参数值呢?这就是资源黑客发挥作用的地方。用二进制文件喂它,它会向你展示一切。就像我的加速器表一样。

替代文字 http://files.getdropbox.com/u/1478671/2009-07-29_161532.jpg

您可以在这里看到,F2 按钮对应于 wParam 中的 510。

现在让我们回到处理 WM_COMMAND 的代码。它将 wParam 与不同的常数进行比较。

.text:01001DBC HandleWM_COMMAND:                       ; CODE XREF: mainWndProc+197j
.text:01001DBC                 movzx   eax, word ptr [ebp+wParam]
.text:01001DC0                 mov     ecx, 210h
.text:01001DC5                 cmp     eax, ecx
.text:01001DC7                 jg      loc_1001EDC
.text:01001DC7
.text:01001DCD                 jz      loc_1001ED2
.text:01001DCD
.text:01001DD3                 cmp     eax, 1FEh
.text:01001DD8                 jz      loc_1001EC8

使用上下文菜单或“H”键盘快捷键显示十进制值,您可以看到我们的跳转

.text:01001DBC HandleWM_COMMAND:                       ; CODE XREF: mainWndProc+197j
.text:01001DBC                 movzx   eax, word ptr [ebp+wParam]
.text:01001DC0                 mov     ecx, 528
.text:01001DC5                 cmp     eax, ecx
.text:01001DC7                 jg      loc_1001EDC
.text:01001DC7
.text:01001DCD                 jz      loc_1001ED2
.text:01001DCD
.text:01001DD3                 cmp     eax, 510
.text:01001DD8                 jz      loc_1001EC8 ; here is our jump

它导致代码块调用一些 proc 并退出 wndProc。

.text:01001EC8 loc_1001EC8:                            ; CODE XREF: mainWndProc+20Fj
.text:01001EC8                 call    sub_100367A     ; startNewGame ?
.text:01001EC8
.text:01001ECD                 jmp     callDefAndExit  ; default

那是启动新游戏的功能吗?在最后一部分中找出答案!敬请关注。

第 3 部分,共 3 部分

让我们看一下该函数的第一部分

.text:0100367A sub_100367A     proc near               ; CODE XREF: sub_100140C+CAp
.text:0100367A                                         ; sub_1001B49+33j ...
.text:0100367A                 mov     eax, dword_10056AC
.text:0100367F                 mov     ecx, uValue
.text:01003685                 push    ebx
.text:01003686                 push    esi
.text:01003687                 push    edi
.text:01003688                 xor     edi, edi
.text:0100368A                 cmp     eax, dword_1005334
.text:01003690                 mov     dword_1005164, edi
.text:01003696                 jnz     short loc_10036A4
.text:01003696
.text:01003698                 cmp     ecx, dword_1005338
.text:0100369E                 jnz     short loc_10036A4

有两个值(dword_10056AC,uValue)读入寄存器 eax 和 ecx,并与另外两个值(dword_1005164,dword_1005338)进行比较。

使用 WinDBG ('bp 01003696'; on break 'p eax; p ecx') 查看实际值——它们对我来说似乎是雷区尺寸。使用自定义雷区大小显示第一对是新维度和第二个当前维度。让我们设置新名称。

.text:0100367A startNewGame    proc near               ; CODE XREF: handleButtonPress+CAp
.text:0100367A                                         ; sub_1001B49+33j ...
.text:0100367A                 mov     eax, newMineFieldWidth
.text:0100367F                 mov     ecx, newMineFieldHeight
.text:01003685                 push    ebx
.text:01003686                 push    esi
.text:01003687                 push    edi
.text:01003688                 xor     edi, edi
.text:0100368A                 cmp     eax, currentMineFieldWidth
.text:01003690                 mov     dword_1005164, edi
.text:01003696                 jnz     short loc_10036A4
.text:01003696
.text:01003698                 cmp     ecx, currentMineFieldHeight
.text:0100369E                 jnz     short loc_10036A4

稍后新值覆盖当前值并调用子程序

.text:010036A7                 mov     currentMineFieldWidth, eax
.text:010036AC                 mov     currentMineFieldHeight, ecx
.text:010036B2                 call    sub_1002ED5

当我看到它

.text:01002ED5 sub_1002ED5     proc near               ; CODE XREF: sub_1002B14:loc_1002B1Ep
.text:01002ED5                                         ; sub_100367A+38p
.text:01002ED5                 mov     eax, 360h
.text:01002ED5
.text:01002EDA
.text:01002EDA loc_1002EDA:                            ; CODE XREF: sub_1002ED5+Dj
.text:01002EDA                 dec     eax
.text:01002EDB                 mov     byte ptr dword_1005340[eax], 0Fh
.text:01002EE2                 jnz     short loc_1002EDA

我完全确定我找到了雷区阵列。使用 0xF 初始化 360h 字节长度数组 (dword_1005340 ) 的周期原因。

为什么 360h = 864?下面有一些提示,该行占用 32 个字节,并且 864 可以除以 32,因此数组可以容纳 27*32 个单元格(尽管 UI 允许最大 24*30 字段,但数组周围有一个字节填充用于边框)。

以下代码生成雷区顶部和底部边界(0x10 字节)。我希望你能在混乱中看到循环迭代;)我不得不用纸和笔

.text:01002EE4                 mov     ecx, currentMineFieldWidth
.text:01002EEA                 mov     edx, currentMineFieldHeight
.text:01002EF0                 lea     eax, [ecx+2]
.text:01002EF3                 test    eax, eax
.text:01002EF5                 push    esi
.text:01002EF6                 jz      short loc_1002F11    ; 
.text:01002EF6
.text:01002EF8                 mov     esi, edx
.text:01002EFA                 shl     esi, 5
.text:01002EFD                 lea     esi, dword_1005360[esi]
.text:01002EFD
.text:01002F03 draws top and bottom borders
.text:01002F03 
.text:01002F03 loc_1002F03:                            ; CODE XREF: sub_1002ED5+3Aj
.text:01002F03                 dec     eax
.text:01002F04                 mov     byte ptr MineField?[eax], 10h ; top border
.text:01002F0B                 mov     byte ptr [esi+eax], 10h       ; bottom border
.text:01002F0F                 jnz     short loc_1002F03
.text:01002F0F
.text:01002F11
.text:01002F11 loc_1002F11:                            ; CODE XREF: sub_1002ED5+21j
.text:01002F11                 lea     esi, [edx+2]
.text:01002F14                 test    esi, esi
.text:01002F16                 jz      short loc_1002F39

其余子程序绘制左右边界

.text:01002F18                 mov     eax, esi
.text:01002F1A                 shl     eax, 5
.text:01002F1D                 lea     edx, MineField?[eax]
.text:01002F23                 lea     eax, (MineField?+1)[eax+ecx]
.text:01002F23
.text:01002F2A
.text:01002F2A loc_1002F2A:                            ; CODE XREF: sub_1002ED5+62j
.text:01002F2A                 sub     edx, 20h
.text:01002F2D                 sub     eax, 20h
.text:01002F30                 dec     esi
.text:01002F31                 mov     byte ptr [edx], 10h
.text:01002F34                 mov     byte ptr [eax], 10h
.text:01002F37                 jnz     short loc_1002F2A
.text:01002F37
.text:01002F39
.text:01002F39 loc_1002F39:                            ; CODE XREF: sub_1002ED5+41j
.text:01002F39                 pop     esi
.text:01002F3A                 retn

巧妙地使用 WinDBG 命令可以为您提供酷炫的雷区转储(自定义大小 9x9)。检查边界!

0:000> db /c 20 01005340 L360
01005340  10 10 10 10 10 10 10 10-10 10 10 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f  ................................
01005360  10 0f 0f 0f 0f 0f 0f 0f-0f 0f 10 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f  ................................
01005380  10 0f 0f 0f 0f 0f 0f 0f-0f 0f 10 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f  ................................
010053a0  10 0f 0f 0f 0f 0f 0f 0f-0f 0f 10 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f  ................................
010053c0  10 0f 0f 0f 0f 0f 0f 0f-0f 0f 10 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f  ................................
010053e0  10 0f 0f 0f 0f 0f 0f 0f-0f 0f 10 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f  ................................
01005400  10 0f 0f 0f 0f 0f 0f 0f-0f 0f 10 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f  ................................
01005420  10 0f 0f 0f 0f 0f 0f 0f-0f 0f 10 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f  ................................
01005440  10 0f 0f 0f 0f 0f 0f 0f-0f 0f 10 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f  ................................
01005460  10 0f 0f 0f 0f 0f 0f 0f-0f 0f 10 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f  ................................
01005480  10 10 10 10 10 10 10 10-10 10 10 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f  ................................
010054a0  0f 0f 0f 0f 0f 0f 0f 0f-0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f  ................................
010054c0  0f 0f 0f 0f 0f 0f 0f 0f-0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f  ................................
010054e0  0f 0f 0f 0f 0f 0f 0f 0f-0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f  ................................

嗯,看来我需要另一个帖子才能结束这个话题

于 2009-07-24T12:39:34.777 回答
15

看起来您正在尝试反汇编源代码,但您需要做的是查看正在运行的程序的内存空间。十六进制编辑器HxD有一个功能可以让您做到这一点。

http://www.freeimagehosting.net/uploads/fcc1991162.png

进入内存空间后,只需在乱搞板子的同时拍摄内存快照即可。区分哪些变化与哪些不变。当您认为您已经掌握了数据结构在十六进制内存中的位置时,请尝试在内存中对其进行编辑,并查看电路板是否因此而改变。

您想要的过程与为视频游戏构建“培训师”没有什么不同。这些通常基于找到诸如健康和弹药之类的值在内存中的位置并动态更改它们。您也许可以找到一些关于如何构建游戏培训师的好教程。

于 2009-07-21T16:27:47.390 回答
12

查看此代码项目文章,它比您提到的博客文章更深入一些。

http://www.codeproject.com/KB/trace/minememoryreader.aspx

编辑

这篇文章虽然不是直接关于扫雷,但为您提供了使用 WinDbg 搜索内存的良好分步指南:

http://www.codingthewheel.com/archives/extracting-hidden-text-with-windbg

编辑 2

同样,这与扫雷无关,但它确实让我对我的内存调试有了一些思考,这里有很多教程:

http://memoryhacking.com/forums/index.php

另外,下载CheatEngine(由 Nick D. 提到)并完成它附带的教程。

于 2009-07-07T11:02:04.780 回答
9

“在 WinDbg 中我可以设置断点,但我很难想象在什么点设置断点以及在什么内存位置。同样,当我在 IDA Pro 中查看静态代码时,我什至不知道从哪里开始找到代表雷区的函数或数据结构。”

确切地!

好吧,您可以寻找诸如 random() 之类的例程,这些例程将在构建 mines 表期间被调用。这本书在我尝试逆向工程时帮助了我很多。:)

一般来说,设置断点的好地方是调用消息框、调用播放声音、计时器和其他 win32 API 例程。

顺便说一句,我现在正在用OllyDbg扫描扫雷。

更新: nemo提醒了我一个很棒的工具, Eric "Dark Byte" Heijnen 的作弊引擎

作弊引擎 (CE) 是查看和修改其他进程内存空间的绝佳工具。除了基本功能之外,CE 还具有更多特殊功能,例如查看进程的反汇编内存并将代码注入其他进程。

(该项目的真正价值在于您可以下载源代码-Delphi-并查看这些机制是如何实现的-我多年前就这样做了:o)

于 2009-05-31T06:09:09.580 回答
5

可以在Uninformed找到一篇关于这个主题的非常好的文章。它非常详细地介绍了逆向扫雷(作为对逆向工程 Win32 应用程序的介绍),并且是一个非常棒的资源。

于 2009-07-22T02:51:43.960 回答
4

这个网站可能更有帮助:

http://www.subversity.net/reversing/hacking-minesweeper

这样做的一般方法是:

  1. 以某种方式获取源代码。
  2. 拆解,希望剩下的符号能帮到你。
  3. 猜测数据类型并尝试操作它并使用内存扫描器来限制可能性。

回应赏金

好吧,在第二次阅读时,您似乎想要一份关于如何使用 WinDBG 等调试器的指南,而不是如何逆向工程的常见问题。我已经向您展示了告诉您需要搜索的值的网站,所以问题是,您如何搜索它?

我在此示例中使用记事本,因为我没有安装扫雷。但想法是一样的。

替代文字

你输入

s <options> <memory start> <memory end> <pattern>

按“?”,然后按“s”查看帮助。

找到所需的内存模式后,您可以按 alt+5 调出内存查看器以获得良好的显示效果。

替代文字

WinDBG 需要一些时间来适应,但它与任何其他调试器一样好。

于 2009-05-31T04:32:44.653 回答
0

在调试器中开始跟踪的一个好点是鼠标悬停。所以找到主窗口过程(我认为像 spyxx 这样的工具可以检查窗口属性,事件处理程序地址就是其中之一)。闯入它并找到它处理鼠标事件的位置 - 如果您可以在汇编程序中识别它,将会有一个开关(查看 windows.h 中鼠标向上的 WM_XXX 值)。

在此处放置一个断点并开始介入。在您释放鼠标按钮和屏幕更新之间的某个时间点,受害者将访问您正在寻找的数据结构。

要有耐心,试着确定在任何给定时间正在做什么,但不要费心去深入研究你怀疑对你当前目标不感兴趣的代码。可能需要在调试器中运行几次才能确定它。

了解正常的 win32 应用程序工作流程也有帮助。

于 2009-07-21T04:51:14.933 回答
0

地雷可能会存储在某种二维数组中。这意味着它要么是一个指针数组,要么是一个 C 风格的布尔数组。

每当表单接收到鼠标向上事件时,都会引用此数据结构。索引将使用鼠标坐标计算,可能使用整数除法。这意味着您可能应该寻找一条cmp或类似的指令,其中一个操作数是使用偏移量计算的x,其中x是涉及整数除法的计算结果。然后偏移量将是指向数据结构开头的指针。

于 2009-07-21T23:57:21.147 回答
0

假设关于地雷的信息至少在内存中连续排列(即,它是一个二维数组或数组数组)是相当合理的。因此,我会尝试在同一行中打开几个相邻的单元格,在进行过程中进行内存转储,然后对它们进行比较并查找同一内存区域中的任何重复更改(即第一步更改了 1 个字节,下一步字节在下一步更改为完全相同的值等)。

也有可能它是一个打包的位数组(每个地雷 3 位应该足以记录所有可能的状态 - 关闭/打开、地雷/非地雷、标记/非标记),所以我也会注意这一点(这些模式也将是可重复的,但更难发现)。但它不是一个方便处理的结构,而且我不认为内存使用是 Minesweeper 的瓶颈,所以不太可能使用这种东西。

于 2009-07-22T02:46:23.517 回答
0

虽然严格来说不是“逆向工程师的工具”,更像是一个玩具,即使是像我这样的白痴也可以使用,请查看Cheat Engine。它可以很容易地跟踪内存的哪些部分已更改,何时更改,甚至可以通过指针跟踪更改的内存部分(尽管您可能不需要)。包括一个很好的交互式教程。

于 2009-07-22T21:32:05.670 回答