如何在内存中找到代表Minesweeper矿井布局的数据结构?


94

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

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

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

在Stackoverflow上有任何反向工程师可以向我指出正确的方向吗?


27
对于学生来说,这是一个好主意。有点像以扫雷为猫的解剖实验室。
ojblass

3
对于可能感到困惑的国际读者而言,扫雷器是Windows Vista附带的快乐版本的寻花游戏的美国版。microsoft.blognewschannel.com/index.php/archives/2006/09/28/…–
Kip

16
快乐的寻花游戏?O_o政治上的正确性已经走得太远了。
尤金(Eugene)'2009年

10
好吧,至少在瑞典语版本的Vista中,扫雷器版本是默认设置。我想他们在矿井实际上倾向于把孩子炸成碎片的地方默认使用幸福花的版本。
JesperE

1
所以...只是单击一些随机方块以查看它们是否是地雷,对此没有帮助,是吗?
Smandoli

Answers:


125

第1部分,共3部分


如果您热衷于逆向工程-那就不用培训师和作弊引擎了。

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

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

  • 当您启动游戏时
  • 当您单击笑脸时
  • 当您单击游戏->新建或按F 2时
  • 当您更改级别难度时

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

要查找加速器处理代码,您将找到窗口消息处理过程(WndProc)。可以通过CreateWindowEx和RegisterClass调用进行跟踪。

阅读:

打开IDA,“导入”窗口,找到“ CreateWindow *”,跳至该窗口并使用“将外部参照跳转到操作数(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_并按Enter。您现在应该拥有

.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参数值呢?这就是Resource hacker发挥作用的地方。用二进制文件喂它,它显示了一切。对我来说像加速器表。

替代文字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”;断点“ 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  ................................

嗯,看来我还需要另一篇帖子以结束话题


1
@斯坦尼斯拉夫,斯坦尼斯拉夫的好答案。如果您可以详细说明,请这样做。这些长而翔实的答案是最好的。也许还有更多关于您如何确定雷区数据结构的信息?
KingNestor

@Stanislav,我接受了您的回答,因为250名代表的赏金活动即将结束。恭喜你!
KingNestor

1
@Stanislav,我已将您的多部分答案编辑为一个答案。您尚未达到单个答案的大小限制,我认为通常最好选择一个答案而不是发布多个答案。随意编辑您的原始答案(此答案),并在您认为合适的情况下添加。
mmcdole 2009年

2
此外,史诗般的答案斯坦尼斯拉夫。非常感谢您的辛苦!
mmcdole

15

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

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

进入内存空间后,就可以在与电路板打交道时为内存拍摄快照。隔离更改与不更改。如果您认为自己的数据结构位于十六进制内存中,可以尝试在内存中对其进行编辑,然后查看电路板是否随之更改。

您想要的过程与为视频游戏构建“培训师”无异。这些通常是基于发现诸如健康和弹药之类的价值观在记忆中所处的位置,并即时对其进行更改。您也许可以找到一些有关如何构建游戏训练师的很好的教程。


2
好了,您可以~~~~通过静态反汇编找到内存的位置。您可以按照组装说明进行操作,查找诸如rand()函数之类的东西以生成矿场,然后从那里进行跟踪,以查看该场在内存中的存储位置(以及存储方式)。
mmcdole 2009年

两种方法都具有挑战性。过去,我曾尝试反汇编应用程序,但发现它非常痛苦。您如何精确发现rand()函数?
James McMahon

谢谢您的回答。
KingNestor

11

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

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

编辑

而且,本文虽然不直接涉及minesweeper,但它为您提供了有关使用WinDbg遍历内存的良好分步指南:

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

编辑2

再说一次,这与扫雷无关,但它确实为我进行内存调试提供了一些思路,这里有很多教程:

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

另外,下载CheatEngine(由Nick D.提及)并完成其随附的教程。


9

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

究竟!

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

通常,设置断点的好地方是对消息框的调用,对声音的调用,计时器和其他win32 API例程。

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

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

Cheat Engine(CE)是监视和修改其他进程内存空间的好工具。除了该基本功能外,CE还具有更多特殊功能,例如查看进程的反汇编内存以及将代码注入其他进程。

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


5

关于这个主题的一篇很好的文章可以在Uninformed中找到。它涵盖了非常详尽的逆向Minesweeper(作为逆向工程Win32应用程序的介绍),并且围绕着相当不错的资源。


4

该网站可能会更有用:

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

进行此操作的一般方法是:

  1. 以某种方式获取源代码。
  2. 分解并希望剩余的符号可以为您提供帮助。
  3. 猜测数据类型并尝试对其进行操作,并使用内存扫描仪来限制可能性。

回应赏金

好吧,在第二读中,似乎您想要的是有关如何使用WinDBG之类的调试器的指南,而不是有关如何进行逆向工程的常见问题的指南。我已经向您展示了一个网站,该网站告诉您需要搜索的值,所以问题是,如何搜索?

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

替代文字

您输入

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

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

找到所需的内存模式后,可以按alt + 5调出内存查看器以显示漂亮的图像。

替代文字

WinDBG习惯了一些习惯,但它与其他调试器一样好。


1
由于以无源方式发送Minesweeper,“以某种方式获取源代码”是一个愚蠢的说法。用源进行反向工程不是反向工程,而是源代码分析。
mrduclaw

@mrduclaw有些应用程序可以将汇编反编译为源语言。没有术语称为“源代码分析”。
未知

1
@Unknown有些应用程序试图从给定的已编译二进制文件中以源语言重构程序。但是您无法从编译的二进制文件中获得带有作者注释和引用的“源代码”。当然,其中一些“反编译器”比其他“反编译器”做得更好,但是它们并没有为您提供作者编写的代码(编译器优化的代码通常与程序员的代码有很大不同)。而且您从未进行过质量保证测试吗?PREfast和Sparse等工具有什么作用?静态源代码分析。
mrduclaw

PREfast和Sparse中的静态源代码分析与手动读取反编译的代码以进行破解完全不同。我认为没有人会混淆这两个不同的想法。
未知

@Unknown我更进一步,并同意您不应将反向工程反汇编与查看源代码(无论是反编译的,如果您正在执行源代码分析的源代码)相混淆。这就是我的意思。因此,请不要混淆两者。:)
mrduclaw

0

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

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

请耐心等待,尝试确定在任何给定时间正在执行的操作,但不要打扰深入研究可能对当前目标不感兴趣的代码。在调试器中可能需要运行几次才能确定下来。

普通的win32应用程序工作流程的知识也有帮助。


0

地雷可能会以某种二维阵列的形式存储。这意味着它既可以是指针数组,也可以是单个C风格的布尔数组。

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


0

假设关于地雷的信息至少在行中连续布置在内存中是合理的,即它是2D数组或数组数组。因此,我将尝试在同一行中打开几个相邻的单元格,对进程进行内存转储,然后对其进行比较,以查找同一内存区域中的所有重复更改(即,第一步更改了1个字节,下一个更改了下一步将字节更改为完全相同的值,依此类推)。

还有一种可能是打包的位数组(每个地雷3位应该足以记录所有可能的状态-闭合/打开,地雷/无地雷,已标记/未标记),所以我也会注意这一点(模式也可以重复,尽管很难发现)。但这不是一个方便的结构,我不认为内存使用是Minesweeper的瓶颈,因此不太可能使用这种东西。


0

尽管不是严格意义上的“逆向工程工具”,甚至像我这样的白痴也可以使用更多玩具,但请查看Cheat Engine。它使跟踪内存中的哪些部分发生了更改,何时变得更容易,甚至提供了通过指针跟踪更改后的内存部分的规定(尽管您可能不需要这样做)。包括一个不错的交互式教程。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.