首页 文章

如何在内存中找到代表扫雷的矿山布局的数据结构?

提问于
浏览
90

我'm trying to learn about reverse engineering, using Minesweeper as a sample application. I'在一个简单的WinDbg命令中发现了这个MSDN article,它显示了所有的地雷,但是它已经很老了,没有详细解释,真的没有't what I'我正在寻找 .

我有IDA Pro disassemblerWinDbg debugger,我已经将winmine.exe加载到它们中 . 在找到代表矿区的数据结构的位置方面,有人能为这些程序中的任何一个提供一些实用技巧吗?

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

Stackoverflow上是否有任何反向工程师可以指向正确的方向?

10 回答

  • 0

    第1部分,共3部分

    如果你认真对待逆向工程 - 忘记训练师和作弊引擎 .

    优秀的逆向工程师应该首先了解操作系统,核心API函数,程序通用结构(什么是运行循环,窗口结构,事件处理例程),文件格式(PE) . Petzold的经典作品“Programming Windows”可以提供帮助(www.amazon.com/exec/obidos/ISBN=157231995X)以及在线MSDN .

    首先,您应该考虑可以调用雷区初始化例程的位置 . 我想到了以下几点:

    • 启动游戏时

    • 当你点击快乐的脸

    • 单击游戏 - >新建或按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中按setting conditional breakpoint并在游戏中按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按钮 .

    Using Keyboard Accelerators按F2按钮时按下wndProc功能

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

    好的,我们已经找到了WM_COMMAND的处理位置,但是如何确定相应的wParam参数值?这是Resource hacker发挥作用的地方 . 用二进制文件提供它,它会显示所有内容 . 像加速器表对我来说 .

    alt text 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  ................................
    

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

  • 9

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

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

    一旦进入内存空间,就可以在使用电路板时拍摄内存快照 . 隔离哪些更改与哪些更改 . 如果您认为数据结构位于十六进制内存中的位置,请尝试在内存中进行编辑,并查看该板是否因此而更改 .

    您想要的过程与为视频游戏构建“培训师”并没有什么不同 . 这些通常基于找到像 Health 和弹药这样的值存在于记忆中并在运行中更改它们的位置 . 您可以找到一些关于如何构建游戏培训师的好教程 .

  • 0

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

    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.提及)并完成它附带的教程 .

  • 0

    “在WinDbg中我可以设置断点,但是我很难想象在什么时候设置断点和什么内存位置 . 同样,当我在IDA Pro中查看静态代码时,我不知道在哪里可以找到开始找到代表矿区的功能或数据结构 . “

    究竟!

    那么,您可以查找在构建mines表期间将调用的例如random()的例程 . 当我在进行逆向工程实验时,这个book给了我很多帮助 . :)

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

    顺便说一下,我现在用OllyDbg扫描扫雷 .

    Update: nemo提醒我一个伟大的工具,Cheat Engine由Eric "Dark Byte" Heijnen .

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

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

  • 5

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

  • 11

    这个网站可能会更有帮助:

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

    这样做的一般方法是:

    • 不知何故得到源代码 .

    • 反汇编并希望剩下的符号可以帮到你 .

    • 猜测数据类型并尝试操作它并使用内存扫描程序来限制可能性 .

    In response to Bounty

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

    我在这个例子中使用记事本,因为我没有安装Minesweeper . 但是想法是一样的 .

    alt text

    你输入

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

    按“?”然后按“s”查看帮助 .

    一旦找到所需的内存模式,就可以按alt 5调出内存查看器以获得良好的显示效果 .

    alt text

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

  • 14

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

    在那里放置一个断点并开始踩踏 . 在释放鼠标按钮和屏幕更新之间的某个时间点,victum将访问您正在寻找的数据结构 .

    要有耐心,尝试确定在任何给定时间正在做什么,但不要太费力地看待你怀疑对你目前的目标无趣的代码 . 在调试器中可能需要多次运行才能确定它 .

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

  • 121

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

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

  • 0

    假设关于地雷的信息在存储器中至少对于行(即,它是2D阵列或阵列阵列)连续布局是相当合理的 . 因此,我会尝试打开同一行中的几个相邻单元格,按照我的方式对进程进行内存转储,然后对它们进行差异化并查找同一内存区域中的任何重复更改(即第一步更改1个字节,下一步字节在下一步变为完全相同的值,等等 .

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

  • 4

    虽然不是严格意义上的"reverse engineer's tool",而且更像玩具甚至像我这样的白痴都可以使用,请查看Cheat Engine . 它可以很容易地跟踪内存的哪些部分已经改变,何时,甚至有通过指针跟踪已更改的内存部分的规定(尽管你可能不需要) . 包括一个很好的互动教程 .

相关问题