[出处]
http://www.woodmann.com/fravia/Zai_MineSweeper.htm
http://codebreakers-journal.com//include/getdoc.php?id=76&article=37&mode=pdf
[译者]aalloverred
[废话]其实文章很简单,有详尽的代码.但我想照葫芦画一下瓢,也会很有收获的:)
[开始]
第一部分:译文
===========================================================
/*aal:前面有摘要和工具介绍,我略过了:) */
目录
1.查找在哪里加入新菜单项 2
2.查找程序在哪里检查是否有某项菜单被按下 3
3.查找程序在哪里检查是否收到WM_MOUSEMOVE消息 6
4.如何修改扫雷的进程 6
4.1 DLL函数 9
4.2 changeItemStatus 9
4.3 revealCells 11
5. 后序:结论 15
6.致敬和感谢 15
7.参考: 15
1.查找在哪里加入新菜单项
我们应该能够从普通模式转换到作弊模式(反之亦然),所以最好的办法就是使用一个新菜单项.我要在'Game(游戏)'菜单里'New(开局)'后面加入新菜单.
我们可以考虑使用资源编辑器,但又考虑到我们得在内存中修改文件,所以我们就得换种方式思考了.如何才能在已存在的菜单中加入一个新菜单呢?我们可以使用InsertMenuItem函数.这个函数将会放在程序修改所需
的所有代码之中.稍后再看怎么使用这个函数,现在只需要知道的就是InsertMenuItem会在程序的初始化部分
被调用,具体就是紧跟在菜单载入后(因为它要用到菜单的句柄...).现在我们应该找找在哪里插入跳往新代码
段的跳转.程序使用了LoadMenu载入菜单:
:010014B9 push 1F4h <------- lpMenuName
:010014BE push hInstance <-- hInstance
:010014C4 call ds:LoadMenuW
:010014CA push 1F5h <------- lpTableName
:010014CF mov hMenu, eax
:010014D4 push hInstance <-- hInstance
:010014DA call ds:LoadAcceleratorsW
:010014E0 mov [ebp+hAccTable], eax
:010014E3 call sub_10023DB
:010014E8 mov eax, dword_10052A4
:010014ED mov ecx, yBottom
菜单已被载入后我们就可以插入新菜单了,我要直接在10014E8处插入跳转跳往我们调用InsertMenuItem的代
码处.我特意选了这个地址就是因为它的字节数和新的跳转指令的字节数正好相等.
2.查找程序在哪里检查是否有某项菜单被按下
这项工作很有用,因为我们需要添加和管理新的菜单项,即特殊模式选项.如果你选中了'特殊模式',由喜就会
告诉你雷在哪里,否则的话,游戏和原来是一模一样的.
有很多方法解决第二个要点.
- 可以使用Spy++,这个小程序为你提供系统中当前运行的进程的很多信息.
先运行扫雷,再运行Spy++,就可以看到正在运行的进程列表,找到扫雷的进程然后双击,会出现一个对话框告
诉你游戏的一些信息,其中一个就是窗口消息处理过程(的地址).
- 另一种定位窗口消息处理过程的方法就是使用Ida.
这和+Spath在他的教程中讲述的方法很相像.将文件载入到ida然后按组合键ctrl-p;出现一个对话框.这个对话
框包含了程序使用的所有的函数,而我们感兴趣的是WinMain函数,用来定义窗口类的函数.这个函数很有用
因为在这里你可以了解窗口消息处理过程在哪里.这个程序使用RegisterClassW函数注册窗口类:
ATOM RegisterClass(
CONST WNDCLASS *lpWndClass // 类数据结构的地址
);
还有:
typedef struct _WNDCLASS {
UINT style;
WNDPROC lpfnWndProc;
int cbClsExtra;
int cbWndExtra;
HANDLE hInstance;
HICON hIcon;
HCURSOR hCursor;
HBRUSH hbrBackground;
LPCTSTR lpszMenuName;
LPCTSTR lpszClassName;
} WNDCLASS;
这个结构包含了窗体的属性;我们感兴趣的是lpfnWndProc,因为它指向了窗口消息处理过程,看看Ida给出的
反汇编代码,找到我们要找的地址:
:01001472 mov [ebp+WndClass.style], edi <----------------------- Style参数
:01001475 mov [ebp+WndClass.lpfnWndProc], offset sub_100180A <-- lpfnWndProc参数,这里地址是100180A
:0100147C mov [ebp+WndClass.cbClsExtra], edi <------------------ cbClsExtra参数
:0100147F mov [ebp+WndClass.cbWndExtra], edi <------------------ cbWndExtra参数
:01001482 mov [ebp+WndClass.hInstance], ecx <------------------- hInstance 参数
:01001485 mov [ebp+WndClass.hIcon], eax <----------------------- hIcon参数
:01001488 call ds:LoadCursorW
:0100148E push ebx <-- ; int
:0100148F mov [ebp+WndClass.hCursor], eax <--------------------- hCursor参数
:01001492 call ds:GetStockObject
:01001498 mov [ebp+WndClass.hbrBackground], eax <--------------- hbrBackground参数
:0100149B lea eax, [ebp+WndClass]
:0100149E mov esi, offset AppName
:010014A3 push eax ; lpWndClass
:010014A4 mov [ebp+WndClass.lpszMenuName], edi <---------------- lpszMenuName参数
:010014A7 mov [ebp+WndClass.lpszClassName], esi <--------------- lpszClassName参数
:010014AA call ds:RegisterClassW <------------------------------ RegisterClassW
好,现在我们知道窗口消息处理函数在哪里了,是该用到调试器Ollydbg的时候了.
载入程序并跳转到窗口消息处理过程.我们需要设一个断点:考虑到我们要找的是这个过程中验证是否有一
个菜单被点击的那一部分,我们需要当且仅当菜单被点击的时候才中断.当一个菜单被点击的时候,一个特定
的消息WM_COMMAND就送到了这个过程.这个特定的消息将菜单的标识句柄(你点击的那个菜单的)
包含在了参数wParam的低位字中.
来看看窗口消息处理函数的这一部分:
:0100180A PUSH EBP
:0100180B MOV EBP,ESP
:0100180D SUB ESP,40
:01001810 MOV ECX,DWORD PTR SS:[EBP+C] <-- ecx就是消息(这里也就是WM_COMMAND)
:01001813 PUSH EBX
如果我们要捕捉菜单项的点击事件,需要在1001810处设置条件断点.我使用的条件是:
[EBP+0Ch]==111H && [EBP+10H]==209H
其中'[EBP+0Ch]==111H'检验是否WM_COMMAND消息,而'[EBP+10H]==209H'检验是否是'Beginner(初级)'菜单
被单击了(你可以使用你喜欢的菜单项 :-)).运行程序,然后单击你选定的那一项,Ollydbg断下.单步运行不久就
肯定会发现程序检验哪一项菜单被按下的代码:
:010018B7 MOV EAX,111
:010018BC CMP ECX,EAX <-------------------- 收到的是WM_COMMAND消息么?
:010018BE JA winmine.01001B3F
:010018C4 JE winmine.010019AD <------------ 如果收到的是WM_COMMAND消息就跳转
...
:010019AD MOV ECX,DWORD PTR SS:[EBP+10] <-- ecx是参数wParam
:010019B0 MOVZX EAX,CX <------------------- eax菜单项标识
:010019B3 CMP EAX,20B <-------------------- 是"Expert(高级)"被单击了?
:010019B8 JG SHORT winmine.01001A30
:010019BA CMP EAX,209 <-------------------- 是"Beginner(初级)"被单击了?
:010019BF JGE SHORT winmine.010019EE
这就是我们要找的那段代码,因为我们必须检验新加入的菜单项是否被单击了,我们要把10018C4处的跳转改
成一个新跳转.稍后我们再看代码是什么样子的.
3.查找程序在哪里检查是否收到WM_MOUSEMOVE消息
这是必须的,因为我们要从这里跳转到显示一个可能的雷.为了找到程序检验WM_MOUSEMOVE消息的地方
,我们改变条件断点处的条件,新条件是:[EBP+0Ch]==200H
:01001B3F MOV EAX,DWORD PTR SS:[EBP+C] <-- 收到消息
:01001B42 MOV ESI,200 <------------------- 200h = WM_MOUSEMOVE
:01001B47 PUSH 1 :01001B49 XOR EDI,EDI
:01001B4B CMP EAX,ESI <------------------- 收到的是WM_MOUSEMOVE消息?
:01001B4D POP EBX
:01001B4E JA SHORT winmine.01001BB4
:01001B50 JE winmine.01001DD5 <----------- 是, 收到的是WM_MOUSEMOVE消息
单步向下,你会发现这种类型的消息对程序并不是那么有用,所以我们改变1001B50处的跳转.
4.如何修改扫雷的进程
首先我们得找一个插入新代码的地方;文件内有很多空隙,你可以选择其中一个.我将把代码放到起始位置为
10049A5h的地方.这些新代码是干什么用的呢?它添加了一项新菜单然后不过是简单的从我们的DLL中调用
了一个函数而已.InsertMenuItem不在程序的输入表中,所以我们还得使用LoadLibrary/GetProcAddress函数组合.
使用这两个函数非常简单.
LoadLibrary:它是首先要用的函数,它将一个DLL模块映射到调用进程的地址空间中.该函数返回一个句柄供
GetProcAddress使用来找到某个DLL函数的地址.
HINSTANCE LoadLibrary(
LPCTSTR lpLibFileName //标识可执行模块名称的字符串的地址
);
GetProcAddress:返回一个输出DLL函数的地址
FARPROC GetProcAddress(
HMODULE hModule, //DLL模块的句柄 (LoadLibrary的返回值)
LPCSTR lpProcName //函数名字
符串的地址
);
现在已经知道了要如何调用InsertMenuItem函数,下面就让我们看看这个函数是什么样子的:
BOOL WINAPI InsertMenuItem(
HMENU hMenu, //新菜单将要插入到的菜单的句柄
UINT uItem, //新菜单要查入到其前面的那项菜单的标识或位置
BOOL fByPosition, //如果为FALSE, uItem就是菜单标识.否则,它就是个菜单位置
LPMENUITEMINFO lpmii //指向MENUITEMINFO结构,它包含了新菜单项的一些信息);
还有这个:
typedef struct tagMENUITEMINFO {
UINT cbSize; // 结构大小,字节数
UINT fMask; //要返回或要设置的成员
UINT fType; //菜单项类型
UINT fState; // 菜单项状态
UINT wID; //由程序定义的一个16位值用来标识菜单项
HMENU hSubMenu; // 下拉菜单或者与菜单项相连的子菜单的句柄
HBITMAP hbmpChecked; // 菜单项被选中时在其旁边显示的位图的句柄
HBITMAP hbmpUnchecked; // 菜单项未被选中时在其旁边显示的位图的句柄
DWORD dwItemData; // 程序定义的与菜单项相连的一个值
LPTSTR dwTypeData; // 菜单项的内容
UINT cch; // 菜单项文本的长度
} MENUITEMINFO, FAR *LPMENUITEMINFO;
尽管参数很多,但这个函数使用起来非常简单.下面就是要向扫雷程序中加入的代码:
==============================================
:010049A5 ; 我选择的空隙的初始地址
:010049A5 dword_10049A5 dd 0 ; 如果选中specialMode=08h,否则specialMode=00h
:010049A6 aZaiwinmine_dll db 'zaiWinmine.dll',0 ; 新DLL的名称
:010049B5 aUser32_dll db 'User32.dll',0 ; InsertMenuItem在USer32.dll 中
;两个输出函数的名字:
:010049C0 aChangeitemstat db 'changeItemStatus',0 ; 改变菜单的状态
:010049D1 aRevealcell db 'revealCell',0 ; 将某个方格下的雷显示出来
:010049DC dword_10049DC dd 0 ;存储changeItemStatus函数的地址
:010049E0 dword_10049E0 dd 0 ; 存储revealCell函数的地址
:010049E4 aInsertmenuitem db 'InsertMenuItemA',0
:010049F4 aSpecialMode db 'Special Mode',0 ;要放到新菜单中的文本
; MENUITEMINFO结构:
:01004A01 dd 2Ch ; 结构的大小: 11 dwords(双字) = 2Ch 字节
:01004A05 dd 42h ; MIIM_ID + MIIM_STRING
:01004A09 dd 0 ; MFT_STRING, 使用文本字符串显示新菜单
:01004A0D dd 0 ; 默认状态
:01004A11 dd 212h ; 新菜单Id
:01004A15 dd 0 ; 设为NULL因为菜单没有下拉菜单或者子菜单
:01004A19 dd 0 ; NULL, 默认的选中标记
:01004A1D dd 0 ; NULL
:01004A21 dd 0 ; NULL
:01004A25 dd 10049F4h ; 只向新菜单项文本的指针,以空(null,0)结尾的字符串
:01004A29 dd 4 ; 字符串长度
:01004A2D ;从初始化处(10014E8)跳转而来:添加一个新菜单并保存changeItemStatus和revealCell的地址
:01004A2D push offset loc_10014ED ; 要返回的地址
:01004A32 pusha
:01004A33 push offset aUser32_dll ; "User32.dll"
:01004A38 call ds:LoadLibraryA ; 映射User32.dll
:01004A3E push offset aInsertmenuitem ; "InsertMenuItemA"
:01004A43 push eax ;dll模块的句柄
:01004A44 call ds:GetProcAddress ; 获取函数的指针
:01004A4A mov ebx, offset hMenu
:01004A4F push offset dword_1004A01 ; 指向MENUITEMINFO结构的指针
:01004A54 push 0 ; FALSE
:01004A56 push 0 ; 菜单项的位置
:01004A58 push dword ptr [ebx] ; 菜单句柄
:01004A5A call eax ; 调用InsertMenuItem
:01004A5C push offset aZaiwinmine_dll ; "zaiWinmine.dll", dll 的名字
:01004A61 call ds:LoadLibraryA
:01004A67 mov esi, eax
:01004A69 push offset aChangeitemstat ; "changeItemStatus"
:01004A6E push esi
:01004A6F call ds:GetProcAddress
:01004A75 mov ds:dword_10049DC, eax ; 存储了changeItemStatus 的地址
:01004A7A push offset aRevealcell ; "revealCell"
:01004A7F push esi
:01004A80 call ds:GetProcAddress
:01004A86 mov ds:dword_10049E0, eax ; 存储了revealCell 的地址
:01004A8B popa
:01004A8C mov eax, dword_10052A4
:01004A91 retn ; 跳到原始代码处
:01004A92 ; 来自于 'specialMode'菜单单击消息(10018C4): 调用我们的changeItemStatus
:01004A92 push offset loc_10019AD ; 返回地址(从哪里来回哪里去?! )
:01004A97 pusha
:01004A98 call ds:dword_10049DC ; 调用 changeItemStatus
:01004A9E jmp short loc_1004AB6
:01004AA0 ;来自于WM_MOUSEMOVE事件(1001B50): 调用 revealCell
:01004AA0 push offset loc_1001DD5 ; 返回地址(从哪里来回哪里去?! )
:01004AA5 cmp ds:byte_10049A5, 0 ; 是特殊模式么?
:01004AAC jnz short loc_1004AAF ; 是,跳下去 !!
:01004AAE retn ; specialMode未被选中,不用显示雷,跳回原始代码
:01004AAF pusha
:01004AB0 call ds:dword_10049E0 ; 调用 revealCell
:01004AB6 popa
:01004AB7 retn ; 跳到原始代码处
现在,我们要修改10014E8, 10018C4和1001B50处的代码了,因为它们必须跳往新代码
:010014E8 jmp loc_1004A2D
:010018C4 jz loc_1004A92
:01001B50 jz loc_1004AA0
4.1 DLL函数
现在我们已经看完了需要项程序添加的所有的修改代码;从现在开始,我们来看看如何编写这两个DLL函数:
- changeItemStatus,这个函数将要选中/不选中菜单项来开启或者关闭特殊模式.
- revealCell,这个函数揭开方格下的一个雷.
4.2 changeItemStatus
要选中或不选中菜单项,使用CheckMenuItem函数:
DWORD CheckMenuItem(
HMENU hmenu, // 菜单句柄
UINT uIDCheckItem, // 要选中或不选中的菜单
UINT uCheck //菜单标志(flags)
);
很简单的一个函数,它需要:
1.菜单句柄.我们没有这个句柄,但是这个函数在输入表中,并且原来就有些菜单项就可以被选中/不选中,所
以最简单的获得这个句柄的方法就是在这个函数处下个断点,然后点击这样一个菜单,比如说'sound(声音)'菜
单.非常好,Olly断了下来,并且给我们指示出了存储这个句柄的地方.
2.菜单的ID.就是我们赋给新菜单的值.
3.我们对两个值感兴趣:MF_CHECKED (08h):给选中状态设置选中标志属性.MF_UNCHECKED (00h):给未选中
状态设置选中标志属性.
我是用asm写这个DLL的,但是你要用其他语言写也没人拦着你.下面就是这个程序:
; 改变新菜单的状态
changeItemStatus proc
.IF word ptr [ebp+10h] == 212h ; 212h我使用的新菜单的ID
mov ebx, 10049A2h ; 字节[10049A2]存储了菜单状态
xor byte ptr [ebx], 08h
movzx eax, byte ptr [ebx]
mov ebx, 10052BCh ;菜单句柄的存储地址
invoke CheckMenuItem, dword ptr [ebx], 212h, eax
.ENDIF
ret
changeItemStatus endp
唯一需要解释的就是10049A2处的值:如果未选中就是00h,如果选中就是08h.
为什么不是00h和01h?因为我同时将这个地址用来存储菜单状态和菜单标志.
4.3. revealCell
编写这个函数我们必须搞明白"雷"都存储在了那里.我们需要找到在单击方格时中断的方法.继续使用我们
以前所用的工作,我将改变1001810处的条件断点的条件;思路就是当鼠标左键被按下(然后被释放)时中断.但
这个左键释放时会发出WM_LBUTTONUP (202h)消息,所以新条件就是:[ebp+0Ch]==202h.
设置好这个新断点然后点击某个方格,Olly断了下来.单步向下直到Olly告诉你你找到那个地方了为止.
01001C30 XOR EDI,EDI ; Cases 202 (WM_LBUTTONUP) <-- 多亏了 Olly :-D
01001C32 CMP DWORD PTR DS:[1005160],EDI
01001C38 JE winmine.01001D4C
01001C3E MOV DWORD PTR DS:[1005160],EDI
01001C44 CALL DWORD PTR DS:[<&USER32.ReleaseCaptu> ; ReleaseCapture
01001C4A TEST BYTE PTR DS:[1005010],BL 01001C50 JE SHORT winmine.01001C5C
01001C52 CALL winmine.0100373E ; hmmm...
01001C57 JMP winmine.01001D4C ; jump at the end of the procedure
这就是管理WM_LBUTTONUP命令的代码.可以看到一个ReleaseCapture的调用,过程最后是一个跳转,和一个
可疑的调用. 没准儿这个调用就告诉我们雷在哪里呢!跟进这个调用瞟一眼.
010037F2 MOV EDX,ECX ; ecx存储了被单击的方格的行号, eax 存储了列号
010037F4 SHL EDX,5
010037F7 MOV DL,BYTE PTR DS:[EDX+EAX+1005700] ; dl = 被单击的方格中存储的值
正如你看到的,第一行代码从1005721开始存储并且以一个以含有0x10字符结尾.试它几次就会获得表示雷的值是0x8F.
现在我们已经对程序的方方面面都已经了解清楚了,下面要做的就是显示这些该死的雷.我会想你解释如何
在鼠标移过一个方格时显示它们;这些雷直到游戏结束都会一直显现.我们可以在DLL中编写另一个函数,我把它叫做revealCell.
这个函数要干什么呢?考虑到每次鼠标移动都会调用这个函数(我们假设已选中了specialMode(开启了特殊模式)),我们有了下面的方案:
1.鼠标在网格内部么?
否:退出这个函数
是:跳到2
2.当前这个方格下有雷么?
否:退出这个函数
是:显示这个雷
第一点是必需的:因为雷不可能藏在网格外面.而第二点是最后的检验.第一个雷(左上角)从坐标0x0C,0x37处
开始,每个方格都是0x10,0x10大小.有了这些信息我们就能实现第一点了:用一个简单的'if'结构,再加上鼠标按
下处的点的坐标就行.那如何知道鼠标按下时的坐标呢?WM_MOUSEMOVE会告诉你,实际上:
WM_MOUSEMOVE
fwKeys = wParam; // 按键标志
xPos = LOWORD(lParam); //鼠标的水平位置
yPos = HIWORD(lParam); // 鼠标的垂直位置
你所要用到的都在这里了.
第二点,首先我们必须检验某方格是否藏了雷;这不是问题,因为我们已经知道了程序将网格存在了哪里,我
们这就要轻易的实现它了,可是...我们怎么显示某个方格下面就是个雷呢?答案就在图形设备接口(Graphics
Device Interface ,GDI)库里面;在现在这种特定情况下,我们用GDI显示一个位图.在我这个新版本的游戏中,我
使用了一个背景是红色的雷的图片来显示(用资源编辑器打开程序,你会在"Bitmap-410-1033"下面找到我所说
的图片).你可以用从程序中取出来的你喜欢的图片或者你自己制作的图片.为了显示这个图片我用了这些函
数:GetDC, BitBlt, ReleaseDC.
GetDC:获得一个指定窗口的客户区域显示设备上下文(Device Context,DC)的句柄.
HDC GetDC(
HWND hWnd // 窗口句柄
);
这就是我们首先需要调用的函数,它有一个参数.我们如何知道窗口的句柄呢?简单,这三个函数也被扫雷
用到了,所以最好的理解如何使用它们的方法就是在这些函数处中断,然后看看它们是如何被使用的.这一次
,我们在GetDC处设置断点(bpx),然后运行程序.Ollydbg肯定会断下,而我们要做得就仅仅是将包含了句柄的地
址记下来...
BitBlt:它将一个源DC上的图片拷到一个目的DC.
BOOL BitBlt(
HDC hdcDest, // 目的DC的句柄,GetDC的返回值
int nXDest, // 目标矩形区域左上角的x坐标
int nYDest, // 目标矩形区域左上角的y坐标
int nWidth, // 目标矩形区域的宽度
int nHeight, // 目标矩形区域的高度
HDC hdcSrc, // 源设备上下文的句柄
int nXSrc, // 源矩形区域左上角的x坐标
int nYSrc, // 源矩形区域左上角的y坐标
DWORD dwRop // 栅操作码
);
简单.和以前一样,最好的理解如何使用这个函数的方法就是在Olldbg中中断它.如果你想显示另一幅图片就
得改变第6个参数:hdcSrc.
ReleaseDC: ReleaseDC函数释放一个DC
int ReleaseDC(
HWND hWnd, // 窗口句柄
HDC hDC // 设备上下文句柄
);
好了,是时间放出revealCell过程的源码了:
.data
oldRow db "0100499D"
oldColumn db "010049A1"
; 显示方格下面的雷
revealCell proc
pushad
;鼠标在网格区域内么 ?
.IF word ptr [ebp+14h] <0Ch || word ptr [ebp+16h] < 37h
jmp @exitRevealCell
.endif
xor eax, eax
mov ax, word ptr [ebp+14h] ; The x-coord of the mouse
sub eax, 0Ch
shr eax, 4
add eax, 1 ; 鼠标在网格区域内按下时的列号
xor ebx, ebx
mov bx, word ptr [ebp+16h] ; 鼠标Y坐标
sub ebx, 37h
shr ebx, 4
add ebx, 1; 鼠标在网格区域内按下时的行号
mov edi, 100499Dh
.IF ax == word ptr [edi] && bx == word ptr [edi+4] ;和以前的方格是同一个, 没必要再次显示相同的图片
jmp @exitRevealCell
.ENDIF
mov ecx, ebx
mov edx, ecx
shl edx, 5
mov ebx, 1005700h
add ebx, eax
add ebx,edx ;仅当网格下有雷时才显示
.IF byte ptr [ebx] == 8Fh
mov edi, 100499Dh
mov dword ptr [edi], eax ; 存储列号
mov dword ptr [edi+4], ecx ; 存储行号
push eax
push ecx
mov esi, 10052A8h ; 指向窗口句柄
push [esi] ; 窗口句柄
call GetDC
mov esi, eax
pop ecx
pop eax
shl ecx, 4
add ecx, 27h
shl eax, 4
sub eax, 4
mov ebx, 1005AE0h
push 0CC0020h ; SRCCOPY标志
push 0
push 0
push [ebx+48] ; 源 DC, 要现实的图片
push 10h ; 矩形高度
push 10h ; 矩形高度
push ecx ; y 坐标
push eax ; x 坐标
push esi ; 目标DC 的句柄
call BitBlt
mov ebx, 10052A8h
push esi ; DC句柄
push dword ptr [ebx] ;窗口句柄
call ReleaseDC
.ELSE
mov dword ptr [edi], 0
mov dword ptr [edi+4], 0
.ENDIF
@exitRevealCell:
popad
ret
revealCell endp
5. 后序:结论
教程就在这里结束了,剩下的要做的就是写一个装载器装载程序并且将新功能加入到内存中.我不会讲述如
何编写一个简单的载入器,你可以在附件里找到所有的源代码.
玩这个新版的扫雷是我发现了一个奇怪的现象;进入特殊模式,然后第一下就点在雷上.什么事儿也没有,雷
消失了;我们的工作哪里出错了么?呃,不是的.那雷跑哪里去了呢?(提示:这是这个程序的一个特别的特性..:-))
6.致敬和感谢
非常感谢RCE论坛中的所有的朋友和所有的UIC成员.和往常一样,有任何的提示,建议,错误报告 和/或其他你想说的,随时给我写信...
7.参考:
http://www.woodmann.net/fravia/menusspa.htm
http://www.woodmann.net/fravia/TracePlus_MenuPatch.html
http://www.woodmann.net/fravia/kayaker_RegmonPlus.htm
http://www.codeguru.com/mfc/comments/50642.shtml
========================================================
第二部分:分析
其实只是简单的照葫芦画瓢而已(虽然简单我却也花了一天多!!! 汗!!! )我想还要编写dll和loader,那样的话扫雷程序不就由
一个文件变成了三个文件,可我还是觉得用一个文件好 :) 所以我想能不能将这些都做到程序内呢?毫无经验的我操刀就干.
随便找了个空当就添加自己的代码.可是忙活了一天也没成功,笨啊!!!最后发觉程序中的Cave有些不够!!晕,要真是那样我不白费了?
后来发现PEiD的Cave Finder功能(右击段名),在它指示的cave处又忙了半天,添加代码如下...
db "User32.dll",0
db "InsertMenuItem",0
db "Special Mode",0
dd 2ch ; MENUITEMINFO结构
dd 42h
dd 0
dd 0
dd 212h
dd 0
dd 0
dd 0
dd 0
dd ? ;(1004A76) 只向新菜单项文本的指针,以空(null,0)结尾的字符串
dd 4
;------------------------------------------------
;010022D0出的代码要改为:"jmp Insert"(our begins)/* jmp 1004AB0 */
;------------------------------------------------
Insert:
pushad ;<------------我的代码始于这里!!!
push ? ;"User32.dll" (1004A5C)
call LoadLibraryA
push ? ; "InsertMenuItemA"(1004A67)
push eax
call GetProcAddress
mov ebx,? ;menu偏移量(01005A94)
push ? ;MENUITEMINFO指针(1004A83)
push 0
push 0
push dword ptr [ebx]
call eax
popad
mov eax,dword ptr ds:[1005B88] ;<----被覆盖的代码
jmp 010022D5 ;<-----跳回
;-------------------------------------------------------
;1001D60: jmp chck ;1004AE3
;---------------------------------------------
Chck:;1004AE3
jne Back1 ;(1004B13)
cmp word ptr [ebp+10h],212h ;<---------------改变菜单状态!!!
jz 01001DBC ;<----跳出去!!!("jnz back2" 也可以?!)
mov ebx,10051a1 ;51a1存储状态的地方(用cave finder找到的)!!!
xor byte ptr [ebx], 08h
movzx eax, byte ptr [ebx]
mov ebx, 01005A94h ;1005a94--menu句柄!!!
push dword ptr ds:[ebx]
push 212
push eax
call USER32.CheckMenuItem
Back2:
jmp 01001DBC
Back1:
dec eax
je short 01001D76
dec eax
jmp 1001D66 ;跳回(6字节)
;--------------------------------------------------
;1001BEA code:je revealCell
;--------------------------------------------------
revealCell:;1004B20
pushad
cmp word ptr [ebp+14h], 0Ch
jb exit1 ;1004BD3; else1=1004BC1
cmp word ptr [ebp+16h] ,37h
jb exit1
xor eax, eax
mov ax, word ptr [ebp+14h] ; x
sub eax, 0Ch
shr eax, 4
add eax, 1 ;
xor ebx, ebx
mov bx, word ptr [ebp+16h] ; y
sub ebx, 37h
shr ebx, 4
add ebx, 1 ;
mov edi, 1005145h ;前一次的方格
cmp ax,word ptr [edi]
jnz continue1
cmp bx,word ptr [edi+4]
jz exit1
continue1:
mov ecx, ebx
mov edx, ecx
shl edx, 5
mov ebx, 1005700h
add ebx, eax
add ebx,edx
cmp byte ptr [ebx],8Fh
jnz else1
mov edi, 1005145h
mov dword ptr [edi], eax ; 存储列
mov dword ptr [edi+4], ecx ; 存储行
push eax
push ecx
mov esi, 1005B24h ; 窗口句柄指针
push [esi] ; 窗口句柄
call GetDC
mov esi, eax
pop ecx
pop eax
shl ecx, 4
add ecx, 27h
shl eax, 4
sub eax, 4
mov ebx, 1005A20h ;<图片dc!!!
push 0CC0020h ; SRCCOPY
push 0
push 0
push [ebx+30]
push 10h
push 10h
push ecx
push eax
push esi
call BitBlt
mov ebx, 1005B24h
push esi
push dword ptr [ebx]
call ReleaseDC
else1:
mov dword ptr [edi], 0
mov dword ptr [edi+4], 0
exit1:;1004BCE
popad
jmp 01002085 ;跳回!!!(1001BEF?!)
=========================================
亏了cave大小还够,不然我可就比窦娥还冤了.因此我想能不能再加入代码前实现计算好自己要加入的代码的长度呢?不会!!
另外我还想过照瓢画瓢,我想先把作者做好的winmine从内存中dump出来,在找这些可能会简单些,可是也不会!
水平太差,望大侠们笑的时候捂着点嘴:)更希望有时间能够多多批评指点!!!
然后我还对作者所说的特别的特性(第一次就点雷雷就不见了)看了看,好像是这里:
0100351F |. C1E1 05 shl ecx,5
01003522 |. 8D9431 40530001 lea edx,dword ptr ds:[ecx+esi+1005340]
01003529 |. F602 80 test byte ptr ds:[edx],80
0100352C |. 57 push edi
0100352D |. 74 66 je short winmineC.01003595 ; 不是雷就跳
0100352F |. 833D A4570001 00 cmp dword ptr ds:[10057A4],0
01003536 |. 75 50 jnz short winmineC.01003588 ; 第一个是雷?--不跳
01003538 |. 8B2D 38530001 mov ebp,dword ptr ds:[1005338]
0100353E |. 33C0 xor eax,eax
01003540 |. 40 inc eax
01003541 |. 3BE8 cmp ebp,eax
01003543 |. 7E 6B jle short winmineC.010035B0
01003545 |. 8B1D 34530001 mov ebx,dword ptr ds:[1005334]
0100354B |. BF 60530001 mov edi,winmineC.01005360
01003550 |> 33C9 /xor ecx,ecx
01003552 |. 41 |inc ecx
01003553 |. 3BD9 |cmp ebx,ecx
01003555 |. 7E 0B |jle short winmineC.01003562
01003557 |> F6040F 80 |/test byte ptr ds:[edi+ecx],80
0100355B |. 74 0F ||je short winmineC.0100356C
0100355D |. 41 ||inc ecx
0100355E |. 3BCB ||cmp ecx,ebx
01003560 |.^ 7C F5 |\jl short winmineC.01003557
01003562 |> 40 |inc eax
01003563 |. 83C7 20 |add edi,20
01003566 |. 3BC5 |cmp eax,ebp
01003568 |.^ 7C E6 \jl short winmineC.01003550
0100356A |. EB 44 jmp short winmineC.010035B0
0100356C |> FF7424 18 push dword ptr ss:[esp+18] ; /Arg2
01003570 |. C1E0 05 shl eax,5 ; |
01003573 |. 8D8408 40530001 lea eax,dword ptr ds:[eax+ecx+1005340] ; |
0100357A |. C602 0F mov byte ptr ds:[edx],0F ; |
0100357D |. 8008 80 or byte ptr ds:[eax],80 ; |
01003580 |. 56 push esi ; |Arg1
01003581 |. E8 FEFAFFFF call winmineC.01003084 ; \winmineC.01003084<---重新布那一颗雷?!
01003586 |. EB 28 jmp short winmineC.010035B0
因此我们再命苦,也不会一下子就碰着雷了?有其他原因?知道的话,别忘了指导指导!
最后又放上了自己改好的程序再供大家取笑 !!!
附件:winmine.rar
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)