我正在尝试使用Minesweeper作为示例应用程序来学习逆向工程。我在一个简单的WinDbg命令上发现了这个MSDN article,它显示了所有的地雷,但它已经很老了,没有详细解释,也不是我想要的。
我有IDA Pro disassembler和WinDbg debugger,我已经将winmine.exe加载到它们中。在找到代表矿区的数据结构的位置方面,有人能为这些程序提供一些实用的技巧吗?
在WinDbg中,我可以设置断点,但是我很难想象在什么时候设置断点和内存位置。同样,当我在IDA Pro中查看静态代码时,我不知道在哪里开始找到代表矿区的函数或数据结构。
Stackoverflow上是否有任何反向工程师可以指向正确的方向?
答案 0 :(得分:121)
如果你认真对待逆向工程 - 忘记训练师和作弊引擎。
优秀的逆向工程师应首先了解操作系统,核心API函数,程序通用结构(运行循环,窗口结构,事件处理例程),文件格式(PE)。 Petzold的经典作品“Programming Windows”可以提供帮助(www.amazon.com/exec/obidos/ISBN=157231995X)以及在线MSDN。
首先,您应该考虑可以调用雷区初始化例程的位置。我想到了以下几点:
我决定查看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
点击输入功能名称(使用'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 - 我的。
好的,让我们继续使用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
这是启动新游戏的功能吗?在最后一部分找到它!请继续关注。
让我们看一下该函数的第一部分
.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 :(得分:14)
您似乎正在尝试反汇编源代码,但您需要做的是查看正在运行的程序的内存空间。十六进制编辑器HxD有一个功能,可以让你这么做。
一旦进入内存空间,就可以在使用电路板时拍摄内存快照。隔离哪些更改与哪些更改。如果您认为数据结构位于十六进制存储器中的位置,请尝试在内存中进行编辑,并查看该板是否因此而发生变化。
您想要的过程与为视频游戏构建“培训师”没有什么不同。这些通常基于找到像健康和弹药这样的值存在于记忆中并在运行中更改它们的位置。您可以找到一些关于如何构建游戏培训师的好教程。
答案 2 :(得分:11)
查看此代码项目文章,它比您提到的博客文章更深入。
http://www.codeproject.com/KB/trace/minememoryreader.aspx
这篇文章,虽然不是直接关于扫雷,但是为你提供了一个使用WinDbg在内存中搜寻的一步一步的指南:
http://www.codingthewheel.com/archives/extracting-hidden-text-with-windbg
同样,这不是关于扫雷,但它确实给了我一些思考我的记忆调试,这里有很多教程:
http://memoryhacking.com/forums/index.php
另外,下载CheatEngine(由Nick D.提及)并完成随附的教程。
答案 3 :(得分:9)
“在WinDbg中,我可以设置断点,但是 我很难想象 什么指向设置一个断点和 什么记忆位置。同样,何时 我在IDA Pro中查看静态代码,我是 不知道哪里开始找 功能或数据结构 代表矿区。“
完全正确!
你可以在构建地雷表期间寻找像random()这样的例程。在我进行逆向工程实验时,book给了我很多帮助。 :)
一般来说,设置断点的好地方是调用消息框,调用播放声音,定时器和其他win32 API例程。
顺便说一句,我现在用OllyDbg扫描扫雷。
更新: nemo让我想起了一个伟大的工具,Cheat Engine来自Eric“Dark Byte”Heijnen。
作弊引擎(CE)是观察和修改其他进程内存空间的绝佳工具。除了基本设施之外,CE还具有更多特殊功能,例如查看过程的反汇编内存以及将代码注入其他过程。
(该项目的真实值是您可以下载源代码-Delphi-并查看这些机制是如何实现的 - 我多年前就这样做了:o)
答案 4 :(得分:5)
关于这个主题的一篇很好的文章可以在Uninformed找到。它涵盖了逆转扫雷(作为逆向工程Win32应用程序的介绍)非常精细,并且都是一个非常好的资源。
答案 5 :(得分:4)
这个网站可能会更有帮助:
http://www.subversity.net/reversing/hacking-minesweeper
这样做的一般方法是:
回应Bounty
好吧,在第二次阅读时,似乎你想要一个关于如何使用像WinDBG这样的调试器的指南,而不是通常的逆向工程问题。我已经向您展示了告诉您需要搜索的值的网站,所以问题是,您如何搜索它?
我在此示例中使用记事本,因为我没有安装Minesweeper。但这个想法是一样的。
您输入
s <options> <memory start> <memory end> <pattern>
按“?”然后按“s”查看帮助。
找到所需的内存模式后,可以按alt + 5调出内存查看器,以获得良好的显示效果。
WinDBG需要一些时间来习惯,但它和其他任何调试器一样好。
答案 6 :(得分:0)
在调试器中开始跟踪的一个好处是鼠标向上。所以找到主窗口程序(我认为像spyxx这样的工具可以检查windows属性,事件处理程序地址就是其中之一)。闯入它并找到它处理鼠标事件的位置 - 如果你能在汇编程序中识别它,就会有一个开关(在windows.h中查看WM_XXX的值是否为鼠标)。
在那里放一个断点并开始踩到。在释放鼠标按钮和更新屏幕之间的某个时间点,victum将访问你正在寻找的数据结构。
要有耐心,尝试确定在任何给定时间内正在做什么,但不要太费力地查看您怀疑对您当前目标无趣的代码。在调试器中可能需要多次运行才能确定它。
了解正常的win32应用程序工作流程也有帮助。
答案 7 :(得分:0)
地雷可能会存储在某种二维阵列中。这意味着它是一个指针数组或一个C样式的布尔数组。
每当表单收到鼠标向上事件时,都会引用此数据结构。索引将使用鼠标坐标计算,可能使用整数除法。这意味着您应该查找cmp
或类似的指令,其中一个操作数使用偏移量计算x
,其中x
是涉及整数的计算结果师。然后,偏移量将成为指向数据结构开头的指针。
答案 8 :(得分:0)
假设有关地雷的信息在内存中至少对于行(即它是2D数组或数组数组)连续布局是相当合理的。因此,我会尝试打开同一行中的几个相邻单元格,按照我的方式对进程进行内存转储,然后对它们进行差异化并查找同一内存区域中的任何重复更改(即第一步更改1个字节,下一步在下一步中,字节更改为完全相同的值,等等。
也有可能它是一个打包位数组(每个3位应该足以记录所有可能的状态 - 关闭/打开,我的/没有我,标记/非标记),所以我要留意那也是(模式也可以重复,但更难发现)。但它不是一个方便的结构,我不认为内存使用是扫雷的瓶颈,所以不太可能使用这种东西。
答案 9 :(得分:0)
虽然不是严格意义上的“逆向工程师工具”,而且更像是一个像我这样的白痴玩具,但请查看Cheat Engine。它可以很容易地跟踪内存的哪些部分已经改变,何时,甚至有通过指针跟踪已更改的内存部分的规定(尽管你可能不需要)。包含了一个很好的交互式教程。