如何在内存中find代表扫雷的矿井布局的数据结构?
我正在尝试学习逆向工程,使用扫雷作为示例应用程序。 我已经find了一个简单的WinDbg命令这个MSDN文章 ,揭示了所有的地雷,但它是旧的,没有详细解释,真的不是我正在寻找。
我有IDA Pro反汇编程序和WinDbgdebugging器 ,我已经加载winmine.exe到他们两个。 有人可以提供一些实用的技巧来find代表矿区的数据结构的位置吗?
在WinDbg中,我可以设置断点,但是我很难想象在什么时候设置断点以及在哪个内存位置。 同样,当我查看IDA Pro中的静态代码时,我不确定在哪里可以find表示矿区的函数或数据结构。
是否有任何反向工程师在Stackoverflow,可以指出我在正确的方向?
3的第1部分
如果你认真对待逆向工程 – 忘记培训师和作弊引擎。
好的反向工程师应该首先了解OS,核心API函数,程序一般结构(什么是运行循环,窗口结构,事件处理例程),文件格式(PE)。 Petzold的经典“编程Windows”可以帮助(www.amazon.com/exec/obidos/ISBN=157231995X)以及在线MSDN。
首先,你应该考虑雷场初始化程序可以调用的地方。 我想到以下几点:
- 当你启动游戏
- 当你点击高兴的脸
- 当你点击游戏 – >新build或按F2
- 当你改变等级难度
我决定检查F2加速器命令。
要find加速器处理代码,您需要find窗口消息处理过程(WndProc)。 它可以通过CreateWindowEx和RegisterClass调用追查下来。
读书:
- CreateWindowEx http://msdn.microsoft.com/en-us/library/ms632680%28VS.85%29.aspx
- RegisterClass http://msdn.microsoft.com/en-us/library/ms633586%28VS.85%29.aspx
- Petzold的第3章“Windows和消息”
打开IDA,Imports窗口,find“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
按function名称(使用'N'重命名为更好的东西)
现在看看
.text:01001BCF mov edx, [ebp+Msg]
这是消息ID,在F2button的情况下应该包含WM_COMMAND值。 你要find与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
find消息ID值是一个简单的方法。
要了解加速器处理检查:
- 使用键盘加速器
- 资源黑客( http://angusj.com/resourcehacker/ )
对于单个答案来说,这是相当多的文字。 如果你有兴趣,我可以再写几个post。 长故事简短的雷区存储为字节数组[24×36],0x0F表示字节不被使用(打小字段),0x10 – 空字段,0x80 – 我的。
3的第2部分
好的,我们继续用F2button。
按F2键时使用键盘加速键wndProcfunction
…收到WM_COMMAND或WM_SYSCOMMAND消息。 wParam参数的低位字包含加速器的标识符。
好的,我们已经find了处理WM_COMMAND的地方,但是如何确定相应的wParam参数值呢? 这是资源黑客进入的地方。 用二进制喂它,它显示你的一切。 像加速器表为我。
替代文字http://files.getdropbox.com/u/1478671/2009-07-29_161532.jpg
你可以在这里看到,F2button对应于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
那是启动新游戏的function吗? 在最后的部分找出来! 敬请关注。
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'; 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,所以arrays可以容纳27 * 32个单元格(虽然用户界面允许最大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命令的智能使用可以为您提供很酷的雷区转储(自定义大小9×9)。 检查边界!
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 ................................
嗯,看起来我需要另一个post来closures这个话题
看来你正在试图反汇编源代码,但是你需要做的是看看正在运行的程序的内存空间。 hex编辑器HxD有一个function,可以让你这样做。
http://www.freeimagehosting.net/uploads/fcc1991162.png http://www.freeimagehosting.net/uploads/fcc1991162.png
一旦进入内存空间,当你在电路板上乱七八糟的时候,就需要对内存进行快照。 隔离什么改变与什么不改变。 如果您认为自己掌握了hex内存中数据结构的位置,请尝试在内存中对其进行编辑,然后查看该板是否因此而更改。
你想要的过程与构buildvideo游戏的“培训者”不同。 这些通常是基于find像健康和弹药这样的价值观念在生活中改变它们。 你可以find一些关于如何build立游戏教练的好教程。
看看这个代码项目文章,它比你提到的博客文章更深入一点。
http://www.codeproject.com/KB/trace/minememoryreader.aspx
编辑
而这篇文章,虽然不是关于扫雷直接,但给你一个很好的一步一步的指导,通过使用WinDbg内存寻找:
http://www.codingthewheel.com/archives/extracting-hidden-text-with-windbg
编辑2
再次,这不是关于扫雷,但它确实给了我一些思考我的记忆debugging的食物,这里有大量的教程:
http://memoryhacking.com/forums/index.php
另外,下载CheatEngine (由Nick D提到),并通过它自带的教程进行工作。
“在WinDbg中,我可以设置断点,但我很难想象在什么时候设置断点以及在哪个内存位置。同样,当我在IDA Pro中查看静态代码时,我不知道从哪里开始find代表矿井的function或数据结构“。
究竟!
那么,你可以寻找像random()那样的例程,这个例程在矿山build造的时候会被调用。 这本书在我进行反向工程的实验时帮助了我很多。 🙂
一般来说,设置断点的好地方是调用消息框,调用声音,定时器和其他win32 API例程。
顺便说一下,我正在用OllyDbg扫描扫雷艇。
更新: 尼莫提醒我一个伟大的工具,由埃里克“黑暗字节”Heijnen 作弊引擎 。
作弊引擎(CE)是观察和修改其他进程内存空间的好工具。 除了这个基础设施之外,CE还拥有更多的特殊function,例如查看进程的反汇编内存以及将代码注入其他进程。
(该项目的真正价值在于你可以下载源代码–Delphi-看看这些机制是如何实现的 – 我在很多年前就已经这样做了:o)
关于这个话题的相当不错的文章可以在Uninformed上find。 它涵盖了非常详细的逆向扫雷(作为逆向工程Win32应用程序的介绍),并且都是相当好的资源。
这个网站可能会更有帮助:
http://www.subversity.net/reversing/hacking-minesweeper
一般的做法是:
- 以某种方式获得源代码。
- 反汇编,希望剩下的符号可以帮助你。
- 猜猜数据types,并尝试操纵它,并使用内存扫描仪来限制可能性。
回应赏金
那么,在第二次阅读,似乎你想要如何使用像WinDBGdebugging器,而不是如何反向工程的常见问题的指导。 我已经向您展示了网站,告诉您需要search的值,所以问题是,您如何search?
我在这个例子中使用记事本,因为我没有安装扫雷。 但是这个想法是一样的。
你input
s <options> <memory start> <memory end> <pattern>
按“?”,然后按“s”查看帮助。
一旦你find了你想要的记忆模式,你可以按alt + 5来调出记忆查看器来获得更好的显示效果。
WinDBG需要一些习惯,但它和其他任何debugging器一样好。
在debugging器中开始跟踪的一个好的方法是在鼠标上。 所以find主窗口程序(我认为像spyxx这样的工具可以检查窗口属性和事件处理程序地址是其中之一)。 打破它,并find它在哪里处理鼠标事件 – 将有一个开关,如果你可以在汇编中识别它(在Windows.h中查看WM_XXX的鼠标值)。
在那里放置一个断点,并开始join。在你释放鼠标button和屏幕更新之间的某个地方,victum将访问你正在寻找的数据结构。
要有耐心,尽量确定在任何特定时间正在进行的工作,但是不要为了您目前的目标而怀疑自己没有兴趣的代码太深入。 debugging器可能需要几次运行才能确定它。
正常的win32应用程序工作stream程的知识也有帮助
地雷可能会被存储在某种二维数组中。 这意味着它可以是一个指针数组,也可以是一个布尔型的C风格数组。
只要表单接收到鼠标事件,就会引用此数据结构。 索引将使用鼠标坐标计算,可能使用整数除法。 这意味着你应该寻找一个cmp
或者一个类似的指令,其中一个操作数是使用偏移量和x
来计算的,其中x
是一个涉及整数除法的计算结果。 偏移量将成为数据结构开始的指针。
假设有关地雷的信息至less在行内(即它是一个二维数组,或一个数组arrays)在内存中连续地排列是相当合理的。 因此,我会尝试在同一行中打开几个相邻的单元格,使得进程的内存转储变得相同,然后对它们进行比较,并在同一个内存区域中查找任何重复的更改(即在第一步中更改了1个字节,下一个字节在下一步更改为完全相同的值,等等)。
还有一种可能性,即它是一个打包位arrays(每个矿区3位应足以logging所有可能的状态 – closures/打开,我的/不是我的,标记/无标记),所以我也会留意模式也是可重复的,虽然难以发现)。 但是这不是一个方便的结构,我不认为内存使用是扫雷的瓶颈,所以这种事情不太可能会被使用。
虽然不是一个“逆向工程师的工具”,更多的玩具甚至像我这样的白痴都可以使用,但是请查看Cheat Engine 。 这使得跟踪哪些内存部分发生了变化,何时甚至是通过指针跟踪已更改的内存部分(尽pipe您可能不需要这些内容)有所帮助。 包括一个很好的互动教程。