XP扫雷分析
分析布雷过程
将xp的winmine.exe拖到ollydbg2中,F9运行,出现窗口后Alt+W选择扫雷的主窗口,由于是顶层窗口,因此Parent一栏为Topmost,右键选择000B047C,选择Conditional Breakpoint(条件断点),输入[DWORD ESP+8] == WM_LBUTTONUP。选中一块雷区点击,ollydbg便停在入口处(由于程序内部释放焦点,因此我们由winmine切入到ollydbg窗口时)
01001BC9 /. 55 PUSH EBP
01001BCA |. 8BEC MOV EBP,ESP
01001BCC |. 83EC 40 SUB ESP,40
01001BCF |. 8B55 0C MOV EDX,DWORD PTR SS:[ARG.2]
01001BD2 |. 8B4D 14 MOV ECX,DWORD PTR SS:[ARG.4]
01001BD5 |. 53 PUSH EBX
01001BD6 |. 56 PUSH ESI
01001BD7 |. 33DB XOR EBX,EBX
01001BD9 |. 57 PUSH EDI
01001BDA |. BE 00020000 MOV ESI,200
01001BDF |. 43 INC EBX
01001BE0 |. 33FF XOR EDI,EDI
01001BE2 |. 3BD6 CMP EDX,ESI
01001BE4 |. 0F87 75030000 JA 01001F5F
01001F5F |> \8D82 FFFDFFFF LEA EAX,[EDX-201] ; Switch (messages 201..212, 7 exits)
01001F65 |. 83F8 11 CMP EAX,11
01001F68 |. 0F87 3B020000 JA 010021A9
01001F6E |. 0FB680 DE2100 MOVZX EAX,BYTE PTR DS:[EAX+10021DE]
01001F75 |. FF2485 C22100 JMP DWORD PTR DS:[EAX*4+10021C2]
跳几次,就到WM_LBUTTONUP的位置了
01001FDF |> \33FF XOR EDI,EDI ; Cases 202 (WM_LBUTTONUP), 205 (WM_RBUTTONUP), 208 (WM_MBUTTONUP) of switch winmine.1001F5F
01001FE1 |. 393D 40510001 CMP DWORD PTR DS:[1005140],EDI
01001FE7 |. 0F84 BC010000 JE 010021A9
01001FED |> /893D 40510001 MOV DWORD PTR DS:[1005140],EDI
01001FF3 |. |FF15 D8100001 CALL DWORD PTR DS:[<&USER32.ReleaseCaptu ; [USER32.ReleaseCapture
01001FF9 |. |841D 00500001 TEST BYTE PTR DS:[1005000],BL
01001FFF |. |0F84 B6000000 JZ 010020BB
01002005 |. |E8 D7170000 CALL 010037E1 ; [winmine.010037E1
0100200A |. |E9 9A010000 JMP 010021A9
0100200F |> |393D 48510001 CMP DWORD PTR DS:[1005148],EDI ; Case 204 (WM_RBUTTONDOWN) of switch winmine.1001F5F
由于下面已经是WM_RBUTTONDOWN的判断了,因此CALL 010021A9是处理WM_LBUTTONUP的函数,我们F7跟进
010037E1 /$ A1 18510001 MOV EAX,DWORD PTR DS:[1005118] ; winmine.010037E1(guessed void)
010037E6 |. 85C0 TEST EAX,EAX
010037E8 |. 0F8E C8000000 JLE 010038B6
010037EE |. 8B0D 1C510001 MOV ECX,DWORD PTR DS:[100511C]
010037F4 |. 85C9 TEST ECX,ECX
010037F6 |. 0F8E BA000000 JLE 010038B6
010037FC |. 3B05 34530001 CMP EAX,DWORD PTR DS:[1005334]
01003802 |. 0F8F AE000000 JG 010038B6
01003808 |. 3B0D 38530001 CMP ECX,DWORD PTR DS:[1005338]
0100380E |. 0F8F A2000000 JG 010038B6
01003814 |. 53 PUSH EBX
01003815 |. 33DB XOR EBX,EBX
01003817 |. 43 INC EBX
01003818 |. 833D A4570001 CMP DWORD PTR DS:[10057A4],0 ; 次数?
0100381F |. 75 4A JNE SHORT 0100386B
01003821 |. 833D 9C570001 CMP DWORD PTR DS:[100579C],0 ; 时间?
01003828 |. 75 41 JNE SHORT 0100386B
0100382A |. 53 PUSH EBX ; /Arg1 => 1
0100382B |. E8 BD000000 CALL 010038ED ; \winmine.010038ED
01003830 |. FF05 9C570001 INC DWORD PTR DS:[100579C]
01003836 |. E8 7AF0FFFF CALL 010028B5 ; [winmine.010028B5
0100383B |. 6A 00 PUSH 0 ; /TimerFunc = 00000000
0100383D |. 68 E8030000 PUSH 3E8 ; |Timeout = 1000. ms
01003842 |. 53 PUSH EBX ; |TimerID
01003843 |. FF35 245B0001 PUSH DWORD PTR DS:[1005B24] ; |hWnd = 000B047C, class = 扫雷, text = 扫雷
01003849 |. 891D 64510001 MOV DWORD PTR DS:[1005164],EBX ; |
0100384F |. FF15 B4100001 CALL DWORD PTR DS:[<&USER32.SetTimer>] ; \USER32.SetTimer
01003855 |. 85C0 TEST EAX,EAX
01003857 |. 75 07 JNZ SHORT 01003860
01003859 |. 6A 04 PUSH 4 ; /Arg1 = 4
0100385B |. E8 F0000000 CALL 01003950 ; \winmine.01003950
01003860 |> A1 18510001 MOV EAX,DWORD PTR DS:[1005118]
01003865 |. 8B0D 1C510001 MOV ECX,DWORD PTR DS:[100511C]
0100386B |> 841D 00500001 TEST BYTE PTR DS:[1005000],BL
01003871 |. 5B POP EBX
01003872 |. 75 10 JNZ SHORT 01003884
01003874 |. 6A FE PUSH -2
01003876 |. 59 POP ECX
01003877 |. 8BC1 MOV EAX,ECX
01003879 |. 890D 1C510001 MOV DWORD PTR DS:[100511C],ECX
0100387F |. A3 18510001 MOV DWORD PTR DS:[1005118],EAX
01003884 |> 833D 44510001 CMP DWORD PTR DS:[1005144],0
0100388B |. 74 09 JE SHORT 01003896
0100388D |. 51 PUSH ECX ; /Arg2
0100388E |. 50 PUSH EAX ; |Arg1
0100388F |. E8 23FDFFFF CALL 010035B7 ; \winmine.010035B7
01003894 |. EB 20 JMP SHORT 010038B6
01003896 |> 8BD1 MOV EDX,ECX
01003898 |. C1E2 05 SHL EDX,5
0100389B |. 8A9402 405300 MOV DL,BYTE PTR DS:[EAX+EDX+1005340]
010038A2 |. F6C2 40 TEST DL,40
010038A5 |. 75 0F JNZ SHORT 010038B6
010038A7 |. 80E2 1F AND DL,1F
010038AA |. 80FA 0E CMP DL,0E
010038AD |. 74 07 JE SHORT 010038B6
010038AF |. 51 PUSH ECX ; /Arg2
010038B0 |. 50 PUSH EAX ; |Arg1
010038B1 |. E8 5CFCFFFF CALL 01003512 ; \winmine.01003512
010038B6 |> FF35 60510001 PUSH DWORD PTR DS:[1005160] ; /Arg1 = 0
010038BC |. E8 52F0FFFF CALL 01002913 ; \winmine.01002913
010038C1 \. C3 RETN
由于雷区数据不应该随函数的退出而消失,可以判断数据存放在静态或者全局变量区,所以着重关注DS:[100??]这种地方,多试几次可知10057A4记录了用户挖雷次数,而100579C处记录着已用时间,真正的布雷函数显然在那几个call中。你在这些汇编代码中发现了SetTimer这个函数,你在游戏时可能已经发现,在用户挖第一个雷时才会计数,因此call 010038ED和call 010028B5处一定有猫腻,第一个点进去以后是这样:
010038ED /$ 833D B8560001 CMP DWORD PTR DS:[10056B8],3 ; winmine.010038ED(guessed Arg1)
010038F4 |. 75 47 JNE SHORT 0100393D
010038F6 |. 8B4424 04 MOV EAX,DWORD PTR SS:[ARG.1]
010038FA |. 48 DEC EAX ; Switch (cases 1..3, 4 exits)
010038FB |. 74 2A JZ SHORT 01003927
010038FD |. 48 DEC EAX
010038FE |. 74 15 JZ SHORT 01003915
01003900 |. 48 DEC EAX
01003901 |. 75 3A JNZ SHORT 0100393D
01003903 |. 68 05000400 PUSH 40005 ; Case 3 of switch winmine.10038FA
01003908 |. FF35 305B0001 PUSH DWORD PTR DS:[1005B30]
0100390E |. 68 B2010000 PUSH 1B2
01003913 |. EB 22 JMP SHORT 01003937
01003915 |> 68 05000400 PUSH 40005 ; Case 2 of switch winmine.10038FA
0100391A |. FF35 305B0001 PUSH DWORD PTR DS:[1005B30]
01003920 |. 68 B1010000 PUSH 1B1
01003925 |. EB 10 JMP SHORT 01003937
01003927 |> 68 05000400 PUSH 40005 ; Case 1 of switch winmine.10038FA
0100392C |. FF35 305B0001 PUSH DWORD PTR DS:[1005B30]
01003932 |. 68 B0010000 PUSH 1B0
01003937 |> FF15 68110001 CALL DWORD PTR DS:[<&WINMM.PlaySoundW>] ; \WINMM.PlaySoundW
0100393D \> C2 0400 RETN 4 ; Default case of switch winmine.10038FA
可以看出这个函数就是在你点击了雷以后发出声音的,没什么价值,但是此时你注意到下面的一个函数用到了rand,对啊,这个随机数在初始化雷阵的时候肯定用得到啊,先下个断点再说。开始新游戏,然后扫雷就断在rand函数处:
01003940 /$ FF15 B0110001 CALL DWORD PTR DS:[<&msvcrt.rand>] ; [MSVCRT.rand
01003946 |. 99 CDQ
01003947 |. F77C24 04 IDIV DWORD PTR SS:[ARG.1]
0100394B |. 8BC2 MOV EAX,EDX
0100394D \. C2 0400 RETN 4
这个函数显然是用来取rand()%N的,运行到winmine代码中,会发现一个循环,很重要
010036C7 |> /FF35 34530001 /PUSH DWORD PTR DS:[1005334] ; /Arg1 = 9
010036CD |. |E8 6E020000 |CALL 01003940 ; \winmine.01003940
010036D2 |. |FF35 38530001 |PUSH DWORD PTR DS:[1005338] ; /Arg1 = 9
010036D8 |. |8BF0 |MOV ESI,EAX ; |
010036DA |. |46 |INC ESI ; |
010036DB |. |E8 60020000 |CALL 01003940 ; \winmine.01003940
010036E0 |. |40 |INC EAX
010036E1 |. |8BC8 |MOV ECX,EAX
010036E3 |. |C1E1 05 |SHL ECX,5
010036E6 |. |F68431 405300 |TEST BYTE PTR DS:[ESI+ECX+1005340],80
010036EE |.^\75 D7 |JNZ SHORT 010036C7
010036F0 |. |C1E0 05 |SHL EAX,5
010036F3 |. |8D8430 405300 |LEA EAX,[ESI+EAX+1005340]
010036FA |. |8008 80 |OR BYTE PTR DS:[EAX],80
010036FD |. |FF0D 30530001 |DEC DWORD PTR DS:[1005330]
01003703 |.^\75 C2 \JNZ SHORT 010036C7
这段代码经过逆向分析可以得到伪代码:
- [1005338]处存放棋盘大小 SIZE
- [1005330]处存放余下要摆放的雷数 MineToSet
- [1005340]存放棋盘数据 BYTE DATA[32][32] 我们初级模式9*9只用到[1][1]-[9][9]的数据
while(MineToSet)
{
int line=rand%SIZE+1,column=rand%SIZE+1;
if((DATA[line][column]&0x80) == 0)
{//如果不是雷
DATA[line][column] |= 0x80;//设置为雷
MineToSet--;
}
}
我们从ollydbg的内存窗口取得1005340的数据,分析以后可以得到雷区数组,可以得知8F处的为雷,0F处的不是雷
问题思考:
- 为什么是 ESP+8? 这个问题式函数栈帧的结构问题,对函数下断点是断点总是断在入口处,而一般情况下(本例如此)进入该函数之前往往是(该函数原型为stdcall func(A,B,C,…,Z)):
push Z
push Y
.......
push B
push A
call func
??next??
call是将下个指令地址压栈,因此进了func以后内存栈帧结构为这样
低地址
??next?? <---ESP
A <---ESP+4
B <---ESP+8
...
Y
Z
高地址
windows的窗口回调过程函数定义为:
LRESULT CALLBACK WindowProc( HWND hwnd, // handle to window
UINT uMsg, // message identifier
WPARAM wParam, // first message parameter
LPARAM lParam // second message parameter
);
因此我们想截获uMsg == WM_LBUTTONUP消息,则判断[ESP+8]处即可
- 为什么选择WM_LBUTTONUP?
这个你可以动手试试就知道了,你在雷区按下(设该点为A)鼠标不放,移动到别处(设该点为B),若B也在雷区,则B处鼠标释放后程序视为挖雷 而如果在外边则什么效果也没有。相反如果你在雷区之外A处按下鼠标不放,拖到雷区之内B处释放,则B处雷挖开。可见是否挖雷与LBUTTONDOWN的位置无关,而与LBUTTONUP位置有关
- 为什么初始布置的雷区和实际的不一样?如上图,左上角的雷本是没有的,而[3][4]处本来应该有雷,为什么没有?
其实这2处在用户第一次点击雷区时被winmine偷偷替换掉了,也就是说用户第一次就点中了雷!但是这是不对的,第一次就中雷就没意思了。所以winmine釜底抽薪,吧这个雷换到别的地方去了。xp版winmine变态的地方在于,点人脸的时候会布雷,而点第一次的时候你永远不会踩中雷,因为他在第一次的时候偷偷把他换掉。如果你第一次就点中了布好的雷上,他就把这个位置换到别的地方,让他不是雷,如果你没点到雷上自然啥都不做,所以第一次永远点不到雷上。。。
[1][1]-[9][9]之外的,都是0x10 [1][1]-[9][9]初始化为0x0F,然后每次你按人脸的时候,布9科雷 布雷就是发现arr[j]&0x80 == 0的时候进行arr[j] |= 0x80 高位置1,点了第一次,他会判断你是否中了雷,中了,就把雷放到别的地方去,反正总是让你第一次不中雷,中了,他自然再去获取随机数,找到一个没有雷的位置,然后设置为雷,然后把你踩的地方设为非雷
XP扫雷内挂分析
先介绍一下xp扫雷内挂用法:
- 1.打开扫雷界面
- 2.输入X Y Z Z Y
- 3.按一下右下角的shift键
这时,鼠标放在雷区活动,你会看到屏幕左上角有个小光点在一闪一闪。(很小很小的,不容易看。最好桌面是深色的,要不看不清),小光点出现,说明鼠标停在的格子不是雷,没有小光点,就是雷区!
static int index=0;
static WCHAR str[]=L"XYZZY";
static int xCur=-1,yCur=-1;
static xBoxMax,yBoxMax;//雷区大小,初级为9*9,存储在注册表中
static BYTE data[32][32];//雷区数据
switch(uMsg)
{
case WM_KEYDOWN:
{
switch(wParam)
{
case VK_SHIFT:
if(index >= 5)
index ^= 0x14;//如果前5字符正确,后面只要index>=5即可
return DefWindowProc();
break;
case VK_F4:
case VK_F5:
case VK_F6:
...无关代码
break;
default:
if(index < 5)
{
if(str[index] == wParam)
index++;
else
index=0;
}
}
break;
case WM_MOUSEMOVE:
{
......
if(index)
{
if((index == 5 && wParam & MK_CONTROL) || index > 5)
{
int x=LOWORD(lParam);
int y=HIWORD(lParam);
xCur=x/16;//这个16显然可以推测为一个雷格子的大小了。。。
yCur=(y-39)/16;//39同样可以推测为窗口顶部到雷区上边缘长度
if(xCur>0 && yCur>0 && xCur<xBoxMax && yCur<yBoxMax)
{
HDC hdc=GetDC(NULL);
//注意下面data[yCur][xCur]是对的,yCur代表行,然而在绘图中却是竖直方向
if(data[yCur][xCur]&0x80)//如果是雷
SetPixel(hdc,0,0,RGB(0,0,0_));//显示为黑
else
SetPixel(hdc,0,0,RGB(255,255,255));
ReleaseDC(NULL,hdc);
}
}
}
}
break;
}
return DefWindowProc();
}
思考
- 按照内挂用法,应该是按下5字母后按下shift,那为什么代码中是
index == 5 && wParam & MK_CONTROL
而不是index == 5 && wParam & MK_SHIFT
呢? - 另外除了xyzzy组合,你还可以输入什么序列来激活内挂?
Win7扫雷分析
我系统Win8.1,直接把Win7 MineSweeper那个文件夹弄过来,,是无法运行扫雷的。由于是系统组件,ida载入可以得到符号,先研究为何无法运行!从用户入口WinMain开始跟:
int __stdcall WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
DWORD v4; // ecx@1
int result; // eax@2
HMODULE v6; // eax@3
void *v7; // eax@6
void *v8; // esi@7
WCHAR Buffer; // [sp+4h] [bp-24h]@3
char v10; // [sp+6h] [bp-22h]@3
__int16 v11; // [sp+22h] [bp-6h]@3
HeapSetInformation(0, HeapEnableTerminationOnCorruption, 0, 0);
if ( IsGamePlayable(v4, L"Shell-InBoxGames-Minesweeper-EnableGame") )
{
g_hInstance = hInstance;
Buffer = 0;
memset(&v10, 0, 0x1Cu);
v11 = 0;
v6 = GetModuleHandleW(0);
if ( LoadStringW(v6, 0xFBCu, &Buffer, 16) && !lstrcmpW(&Buffer, L"0") )
g_Flowerbed = 1;
v7 = operator new(4u);
if ( v7 )
{
*(_DWORD *)v7 = EngineHandler::`vftable';
v8 = v7;
}
else
{
v8 = 0;
}
RegisterLogNameResolver((const unsigned __int16 *(__stdcall *)(unsigned int))MsLog::MsLogResolver);
if ( InitializeEngine((struct IEngineInterface *)v8, 0) )
{
if ( v8 )
IEngineInterface::`scalar deleting destructor'(v8, 1);
result = 0;
}
else
{
if ( v8 )
IEngineInterface::`scalar deleting destructor'(v8, 1);
result = 1;
}
}
else
{
result = 1;
}
return result;
}
发现IsGamePlayable处返回失败,在此需要将返回值改为TRUE,接着调试,发现InitializeEngine执行后直接进程结束,因此跟入该函数:
bool __stdcall InitializeEngine(struct IEngineInterface *a1, struct IControllerInterface *a2)
{
DialogHelper *v2; // eax@1
DWORD v3; // ST20_4@1
int v4; // eax@1
int v5; // ecx@1
__int16 v6; // dx@2
rsize_t v7; // ebx@3
wchar_t *v8; // eax@3
const wchar_t *v10; // eax@5
HANDLE v11; // ebx@5
wchar_t *v12; // eax@8
const WCHAR *v13; // esi@8
HWND v14; // ebx@8
const WCHAR *v15; // eax@12
LPWSTR *v16; // eax@12
unsigned int v17; // ecx@12
const WCHAR *v18; // eax@16
const unsigned __int16 *v19; // eax@18
unsigned __int16 *v20; // eax@19
void *v21; // eax@28
wchar_t *v22; // eax@31
DWORD v23; // eax@31
int i; // ecx@31
WCHAR v25; // dx@32
void *v26; // eax@38
XmlManager *v27; // eax@39
ResourceBase *v28; // ecx@41
ResourceWMA *v29; // ecx@41
DllFileMgr *v30; // eax@45
wchar_t *v31; // eax@47
bool v32; // al@47
wchar_t *v33; // esi@48
int (**v34)(void); // eax@48
int v35; // eax@48
DWORD v36; // eax@54
void *v37; // eax@57
ResourceManager *v38; // eax@58
void *v39; // eax@64
RenderManager *v40; // eax@65
void *v41; // eax@69
Audio *v42; // eax@70
CommonControllerThread *v43; // eax@75
void *v44; // eax@77
ResourceWMA *v45; // eax@78
XmlNode *v46; // eax@82
unsigned __int16 *v47; // edi@84
struct IEngineInterface *v48; // edi@90
DWORD v49; // ST20_4@96
HMODULE v50; // eax@96
bool nSize; // [sp+14h] [bp-6ACh]@0
const unsigned __int16 *v52; // [sp+18h] [bp-6A8h]@0
void (__stdcall *v53)(); // [sp+18h] [bp-6A8h]@55
char v54; // [sp+24h] [bp-69Ch]@48
struct tagMSG Msg; // [sp+30h] [bp-690h]@98
int v56; // [sp+4Ch] [bp-674h]@44
int v57; // [sp+50h] [bp-670h]@44
int v58; // [sp+54h] [bp-66Ch]@44
int v59; // [sp+58h] [bp-668h]@44
void (__stdcall *v60)(unsigned __int32 *); // [sp+5Ch] [bp-664h]@44
void (__stdcall *v61)(unsigned __int32); // [sp+60h] [bp-660h]@104
int v62; // [sp+64h] [bp-65Ch]@44
int v63; // [sp+68h] [bp-658h]@44
HWND v64; // [sp+6Ch] [bp-654h]@62
int v65; // [sp+70h] [bp-650h]@62
int v66; // [sp+74h] [bp-64Ch]@62
int v67; // [sp+78h] [bp-648h]@62
int v68; // [sp+7Ch] [bp-644h]@62
RAWINPUTDEVICE pRawInputDevices; // [sp+80h] [bp-640h]@47
int pNumArgs; // [sp+8Ch] [bp-634h]@12
struct IEngineInterface *v71; // [sp+90h] [bp-630h]@1
XmlNode *v72; // [sp+94h] [bp-62Ch]@1
unsigned int v73; // [sp+98h] [bp-628h]@12
void *pv; // [sp+9Ch] [bp-624h]@12
wchar_t *Dst; // [sp+A0h] [bp-620h]@3
bool v76; // [sp+A7h] [bp-619h]@16
OLECHAR psz; // [sp+A8h] [bp-618h]@17
WCHAR Filename[518]; // [sp+2B0h] [bp-410h]@31
int v79; // [sp+6BCh] [bp-4h]@16
v71 = a1;
v72 = a2;
v2 = (DialogHelper *)GetModuleHandleW(0);
DialogHelper::Init(v2, 0, 0, nSize);
v4 = (*(int (__thiscall **)(struct IEngineInterface *, DWORD))(*(_DWORD *)a1 + 16))(a1, v3);//EngineHandler::GetProjectName
v5 = v4 + 2;
do
{
v6 = *(_WORD *)v4;
v4 += 2;
}
while ( v6 );
v7 = ((v4 - v5) >> 1) + 24;
v8 = (wchar_t *)operator new[](2 * v7);
Dst = v8;
if ( v8 )
{
wcscpy_s(v8, v7, L"Local\\Oberon_");
v10 = (const wchar_t *)(*(int (__thiscall **)(struct IEngineInterface *))(*(_DWORD *)a1 + 16))(a1);//EngineHandler::GetProjectName
wcscat_s(Dst, v7, v10);
wcscat_s(Dst, v7, L"_Singleton");
v11 = CreateMutexW(0, 1, Dst);
operator delete(Dst);
if ( v11 )
{
if ( GetLastError() != 183 )
{
v15 = GetCommandLineW();
pNumArgs = 0;
v16 = CommandLineToArgvW(v15, &pNumArgs);
v17 = 1;
pv = v16;
v73 = 1;
if ( pNumArgs > 1 )
{
do
{
if ( !wcscmp(*((const unsigned __int16 **)pv + v17), L"-mce") )
g_bMediaCenter = 1;
v17 = v73++ + 1;
}
while ( (signed int)v73 < pNumArgs );
}
v18 = GetCommandLineW();
RegisterApplicationRestart(v18, 0);
CoInitialize(0);
v76 = 0;
Dst = 0;
v79 = 0;
if ( CoCreateInstance(
&_GUID_9a5ea990_3034_4d6f_9128_01f3c61022bc,
0,
1u,
&_GUID_e7b2fb72_d728_49b3_a5f2_18ebf5f1349e,
(LPVOID *)&Dst) >= 0
&& SHGetFolderPathEx(&FOLDERID_ProgramFiles, 0, 0, &psz, 260) >= 0 )
{
v19 = (const unsigned __int16 *)(*(int (__thiscall **)(struct IEngineInterface *))(*(_DWORD *)a1 + 84))(a1);//EngineHandler::GetGdfPath
if ( StringCchCatW(&psz, 0x104u, v19) >= 0 )
{
v20 = SysAllocString(&psz);
g_bstrGDFPath = v20;
if ( v20 )
{
if ( SysStringLen(v20)
&& (*(int (__stdcall **)(wchar_t *, unsigned __int16 *, void **))(*(_DWORD *)Dst + 24))(//CGameExplorer::VerifyAccess
Dst,
g_bstrGDFPath,
&pv) >= 0
&& pv )
v76 = 1;
}
}
}
v79 = -1;
if ( Dst )
(*(void (__stdcall **)(wchar_t *))(*(_DWORD *)Dst + 8))(Dst);
if ( !v76 )
CleanupEngine(5);
v21 = operator new(4u);
if ( v21 )
{
*(_DWORD *)v21 = 0;
g_pSecondTimerCallback = v21;
}
else
{
g_pSecondTimerCallback = 0;
}
_set_new_handler(HandleNewFail);
_set_new_mode(1);
g_pInterface = (void *)a1;
v22 = (wchar_t *)(*(int (__thiscall **)(struct IEngineInterface *))(*(_DWORD *)a1 + 16))(a1);
g_wszProjectName = LocalizeMessage(v22);
v23 = GetModuleFileNameW(0, Filename, 0x200u);
for ( i = v23 - 1; ; --i )
{
if ( i > v23 )
goto LABEL_38;
v25 = Filename[i];
if ( v25 == 47 || v25 == 92 )
break;
}
Filename[i] = 0;
LABEL_38:
SetCurrentDirectoryW(Filename);
XmlNode::SetNodeName((XmlNode *)&ErrorXml, L"ErrorLog");
v26 = operator new(0x10u);
pv = v26;
v79 = 1;
if ( v26 )
v27 = (XmlManager *)XmlManager::XmlManager((XmlManager *)v26);
else
v27 = 0;
v79 = -1;
g_pXmlManager = v27;
CheckAllocation((const void *)v27);
ResourceBase::SetNameForDPI(v28, (bool)g_wszProjectName);
Log(0x10u, 1129, "Engine.cpp", L"Initializing Virtual FS");
if ( ResourceWMA::LoadAsNeeded(v29) )
{
Log(0x10u, 1138, "Engine.cpp", L"Project name localized to: %s", g_wszProjectName);
OutputDebugStringW(Filename);
Log(0x10u, 1142, "Engine.cpp", L"Using working directory: %s", Filename);
Log(0x10u, 1146, "Engine.cpp", L"Initializing GDI+");
v56 = 1;
v58 = 1;
v57 = 0;
v59 = 0;
GdiplusStartup(&g_GdiPlusToken, &v56, &v60);
v63 = 0;
v62 = 0;
LoadWindowPrefs(&v63, &v62);
pv = operator new(0x1440u);
v79 = 2;
if ( pv )
v30 = (DllFileMgr *)DllFileMgr::DllFileMgr(0);
else
v30 = 0;
v79 = -1;
::pv = v30;
v31 = (wchar_t *)(**(int (***)(void))v71)();
Str::Str(v31);
v79 = 3;
v32 = DllFileMgr::Open(::pv, (const struct Str *)&pRawInputDevices, g_bDebugEnabled, L"input");
v79 = -1;
v76 = v32 == 0;
Str::~Str((Str *)&pRawInputDevices);
if ( v76 )
{
D3DXTex::TF_Row::TF_Row((D3DXTex::TF_Row *)&pRawInputDevices);
v79 = 4;
v33 = *(wchar_t **)(Str::Str(0x37Cu) + 8);
v34 = *(int (***)(void))v71;
LOBYTE(v79) = 5;
v35 = (*v34)();
Str::Format((Str *)&pRawInputDevices, v33, v35);
LOBYTE(v79) = 4;
Str::~Str((Str *)&v54);
DialogHelper::ShowMessageBox(
0x385u,
0,
1u,
0xFFFEu,
0,
(const unsigned __int16 *)pRawInputDevices.hwndTarget,
(const unsigned __int16 *)1,
(bool)v52);
}
else if ( CreateGameWindow() )
{
if ( g_bMediaCenter )
{
Log(0x10u, 1211, "Engine.cpp", L"Initializing MCE Dialog");
DialogHelper::InitMCE(0, v52);
if ( g_bMediaCenter )
{
Log(0x10u, 1220, "Engine.cpp", L"Registering for raw input, for remote control");
pRawInputDevices.usUsagePage = 12;
pRawInputDevices.usUsage = 1;
pRawInputDevices.hwndTarget = g_hWnd;
pRawInputDevices.dwFlags = 0;
if ( !RegisterRawInputDevices(&pRawInputDevices, 1u, 0xCu) )
{
v36 = GetLastError();
Log(0x10u, 1231, "Engine.cpp", L"Register failed, winerror %d", v36);
}
}
}
Log(0x10u, 1237, "Engine.cpp", L"Adding system events");
Event::RegisterEventType(2, Event_MouseEnter::Create);
Event::RegisterEventType(4, Event_MouseDown::Create);
Event::RegisterEventType(5, Event_MouseDoubleClick::Create);
Event::RegisterEventType(3, Event_MouseLeave::Create);
Event::RegisterEventType(6, Event_MouseRelease::Create);
Event::RegisterEventType(7, Event_MouseReleaseOut::Create);
Event::RegisterEventType(8, Event_MouseStatusBarClick::Create);
Event::RegisterEventType(9, Event_MouseOuterDown::Create);
Event::RegisterEventType(10, Event_MouseOuterRelease::Create);
Event::RegisterEventType(11, Event_MouseGlobalRelease::Create);
Event::RegisterEventType(1, Event_ButtonClick::Create);
Event::RegisterEventType(13, Event_AnimationComplete::Create);
Event::RegisterEventType(12, Event_AnimationFrame::Create);
Event::RegisterEventType(14, Event_AnimationInt::Create);
Event::RegisterEventType(15, Event_Timer::Create);
Event::RegisterEventType(16, Event_FocusCheck::Create);
Event::RegisterEventType(17, Event_FocusGot::Create);
Event::RegisterEventType(18, Event_FocusLost::Create);
Event::RegisterEventType(19, Event_FocusSelect::Create);
Event::RegisterEventType(20, Event_FocusUnSelect::Create);
Event::RegisterEventType(21, Event_FocusGetNodeAtDir::Create);
Event::RegisterEventType(22, Event_FocusGetNextTabNode::Create);
Event::RegisterEventType(23, Event_InitComplete::Create);
Event::RegisterEventType(24, Event_DoDefaultAction::Create);
Event::RegisterEventType(25, Event_DragCheck::Create);
Event::RegisterEventType(26, Event_DragStart::Create);
Event::RegisterEventType(27, Event_DragMove::Create);
Event::RegisterEventType(28, Event_DragEnd::Create);
Event::RegisterEventType(29, Event_KeyDown::Create);
Event::RegisterEventType(30, Event_KeyUp::Create);
Event::RegisterEventType(31, Event_AccessGetParent::Create);
Event::RegisterEventType(32, Event_AccessGetChildCount::Create);
Event::RegisterEventType(33, Event_AccessGetChild::Create);
Event::RegisterEventType(34, Event_TipClose::Create);
Event::RegisterEventType(37, Event_DpiChange::Create);
Event::RegisterEventType(35, Event_ControllerKey::Create);
Log(0x10u, 1280, "Engine.cpp", L"Registering Node Types");
NodeButton::Register();
NodeLabel::Register();
NodeBase::Register();
NodeSprite::Register();
NodeEmitter::Register();
NodeShot::Register();
NodeNumber::Register();
Log(0x10u, 1291, "Engine.cpp", L"Initializing Timekeeping");
if ( Timekeeping::InitializeTimekeeping((Timekeeping *)v52) )
{
DialogHelper::SetDialogShutdownCallback((DialogHelper *)Engine_ResetTimer, v53);
Log(0x10u, 1304, "Engine.cpp", L"Initializing ResourceManager");
v37 = operator new(0x54u);
pv = v37;
v79 = 6;
if ( v37 )
v38 = (ResourceManager *)ResourceManager::ResourceManager(v37);
else
v38 = 0;
v79 = -1;
g_pResourceManager = v38;
CheckAllocation((const void *)v38);
if ( ResourceManager::Initialize(g_pResourceManager) )
{
v65 = 0;
v66 = 0;
v68 = 0;
v64 = g_hRenderWindow;
LOWORD(v68) = 0;
v67 = 32;
(*(void (__stdcall **)(int *, int *))(*(_DWORD *)g_pInterface + 12))(&v65, &v66);
if ( g_bDoubleDPI )
{
v65 *= 2;
v66 *= 2;
}
Log(0x10u, 1331, "Engine.cpp", L"Initializing RenderManager");
v39 = operator new(0x90u);
pv = v39;
v79 = 7;
if ( v39 )
v40 = (RenderManager *)RenderManager::RenderManager(v39);
else
v40 = 0;
v79 = -1;
g_pRenderManager = v40;
CheckAllocation((const void *)v40);
if ( RenderManager::Initialize(g_pRenderManager, (const struct RenderInitializeOptions *)&v64) )
{
Log(0x10u, 1342, "Engine.cpp", L"Initializing Audio");
v41 = operator new(0x28u);
pv = v41;
v79 = 8;
if ( v41 )
v42 = (Audio *)Audio::Audio(v41);
else
v42 = 0;
v79 = -1;
g_pAudio = v42;
CheckAllocation((const void *)v42);
if ( Audio::Initialize(g_pAudio) )
{
Log(0x10u, 1354, "Engine.cpp", L"Initializing XNA Common Controller");
pv = operator new(0x80u);
v79 = 9;
if ( pv )
v43 = (CommonControllerThread *)CommonControllerThread::CommonControllerThread(
(int)g_hWnd,
g_bMediaCenter,
(int)v72);
else
v43 = 0;
v79 = -1;
g_pCommonController = v43;
Thread::Begin(v43);
Log(0x10u, 1361, "Engine.cpp", L"Initializing Timer");
v44 = operator new(0x34u);
if ( v44 )
v45 = (ResourceWMA *)Timer::Timer(v44);
else
v45 = 0;
g_pTimer = v45;
CheckAllocation((const void *)v45);
if ( ResourceWMA::LoadAsNeeded(g_pTimer) )
{
Log(0x10u, 1372, "Engine.cpp", L"Loading Fonts");
v46 = XmlManager::GetXml(g_pXmlManager, L"xml\\Fonts.xml");
if ( v46 )
{
pv = XmlNode::XPathElementSearch(v46, L"/Font", &v73);
Log(0x10u, 1383, "Engine.cpp", L"%d Fonts Found", v73);
for ( Dst = 0; (unsigned int)Dst < v73; Dst = (wchar_t *)((char *)Dst + 1) )
{
v47 = XmlNode::GetNodeValue(*((XmlNode **)pv + (_DWORD)Dst));
Log(0x10u, 1388, "Engine.cpp", L"Loading Font: %s", v47);
if ( AddFontResourceW(v47) )
{
Array<NodeBase *>::Add(v47);
}
else
{
Log(0x10u, 1412, "Engine.cpp", L"Couldn't add font: %s", v47);
operator delete(v47);
}
}
operator delete(pv);
}
else
{
Log(0x10u, 1426, "Engine.cpp", L"No Font Xml Found");
}
Log(0x10u, 1431, "Engine.cpp", L"Registering for session notification");
WTSRegisterSessionNotification(g_hWnd, 0);
v72 = XmlManager::GetXml(g_pXmlManager, L"engine.xml");
g_bInInitializer = 1;
Log(0x10u, 1439, "Engine.cpp", L"Engine Initialization Complete: Initializing Game Code.");
v48 = v71;
if ( !(unsigned __int8)(*(int (__thiscall **)(struct IEngineInterface *))(*(_DWORD *)v71 + 48))(v71) )
{
Log(0x10u, 1442, "Engine.cpp", L"Game code initialization failed.");
CleanupEngine(0);
}
g_bInInitializer = 0;
if ( v72 )
g_bFocusPause = XmlNode::GetXmlInt(v72, L"/PauseOnLostFocus", -1) > 0;
Engine_LoadingComplete();
g_Accelerator = 0;
if ( (*(int (__thiscall **)(struct IEngineInterface *))(*(_DWORD *)v48 + 40))(v48) )
{
v49 = (*(int (__thiscall **)(struct IEngineInterface *))(*(_DWORD *)v48 + 40))(v48);
v50 = GetModuleHandleW(0);
g_Accelerator = LoadAcceleratorsW(v50, (LPCWSTR)v49);
}
g_bInitializing = 0;
v60(&g_GdiPlusToken);
while ( 1 )
{
while ( !PeekMessageW(&Msg, 0, 0, 0, 0) )
RunEngine();
if ( !GetMessageW(&Msg, 0, 0, 0) )
break;
if ( !g_Accelerator || !TranslateAcceleratorW(g_hWnd, g_Accelerator, &Msg) )
{
TranslateMessage(&Msg);
DispatchMessageW(&Msg);
}
}
v61(g_GdiPlusToken);
(*(void (__thiscall **)(struct IEngineInterface *))(*(_DWORD *)v48 + 72))(v48);
CleanupEngine(0);
}
Log(0x10u, 1365, "Engine.cpp", L"Failed to initialize Timer");
}
else
{
Log(0x10u, 1346, "Engine.cpp", L"Failed to initialize Audio");
}
}
else
{
Log(0x10u, 1335, "Engine.cpp", L"Failed to initialize RenderManager");
}
}
else
{
Log(0x10u, 1308, "Engine.cpp", L"Failed to initialize ResourceManager");
}
}
else
{
Log(0x10u, 1294, "Engine.cpp", L"Failed to initialize timekeeping");
}
}
else
{
Log(0x10u, 1198, "Engine.cpp", L"Window Creation Failed");
}
}
else
{
Log(0x10u, 1132, "Engine.cpp", L"InitOberVFS() Failed");
}
CleanupEngine(0);
}
v12 = (wchar_t *)(*(int (__thiscall **)(struct IEngineInterface *))(*(_DWORD *)a1 + 16))(a1);
v13 = LocalizeMessage(v12);
v14 = FindWindowW(v13, 0);
LocalFree((HLOCAL)v13);
if ( v14 )
{
if ( IsIconic(v14) )
ShowWindow(v14, 10);
BringWindowToTop(v14);
SetForegroundWindow(v14);
}
}
ExitProcess(0);
}
return 0;
}
可以发现,初始化了一个com组件,GUID=e7b2fb72_d728_49b3_a5f2_18ebf5f1349e(感兴趣可以在注册表CLSID中查,是个CGameExplorer类),可以看到最后一个参数获得了一个com对象,调用序列:
EngineHandler::GetProjectName return "MineSweeper.dll"
CoCreateInstance(CGameExplorer)
EngineHandler::GetGdfPath return "\\Microsoft Games\\Minesweeper\\Minesweeper.exe"
CGameExplorer::VerifyAccess 这里会返回空,也是检测什么东西的,需要改返回值为0
改了这2处,就可以运行了!改法有很多,不说了。再来看结构,扫雷用C++写的,用到了XNA,可以找到棋牌类:
void *__thiscall Board::Board(void *this, int a2, int a3, int a4, int a5, int a6, int a7, int a8, char a9)
{
void *v9; // esi@1
unsigned int v10; // eax@1
int v12; // [sp-14h] [bp-30h]@7
int v13; // [sp-10h] [bp-2Ch]@7
unsigned int v14; // [sp-Ch] [bp-28h]@3
signed int v15; // [sp-8h] [bp-24h]@1
void *v16; // [sp+Ch] [bp-10h]@1
int v17; // [sp+18h] [bp-4h]@1
v9 = this;
v16 = this;
v15 = 16;
*(_DWORD *)this = &Board::`vftable';
Array<UITile *>::Array<UITile *>(v15);
*((float *)v9 + 7) = 0.0;
*((_DWORD *)v9 + 8) = a2;
*((_DWORD *)v9 + 1) = a5;
*((_DWORD *)v9 + 2) = a4;
*((_DWORD *)v9 + 3) = a3;
*((_DWORD *)v9 + 11) = a6;
*((_DWORD *)v9 + 9) = a7;
v17 = 0;
*((_DWORD *)v9 + 4) = 0;
*((_DWORD *)v9 + 5) = 0;
*((_DWORD *)v9 + 6) = 0;
*((_DWORD *)v9 + 16) = 0;
*((_DWORD *)v9 + 17) = 0;
*((_DWORD *)v9 + 10) = a8;
Board::initTiles((Board *)v9);
v10 = *((_DWORD *)v9 + 9);
if ( v10 != -1 && *((_DWORD *)v9 + 10) != -1 )
{
v15 = *((_DWORD *)v9 + 10);
v14 = v10;
if ( a9 )
{
Board::placeMines((Board *)v9, v14, v15);
++*((_DWORD *)v9 + 6);
}
else
{
Board::AttemptReveal((Board *)v9, v14, v15);
}
}
if ( *((_DWORD *)v9 + 1) > *((_DWORD *)v9 + 3) * *((_DWORD *)v9 + 2) - 9 )
{
v15 = 1;
Str::Str(L"Too many mines for tile count");
StrErr(v12, v13, v14, v15);
*((_DWORD *)v9 + 1) = *((_DWORD *)v9 + 3) * *((_DWORD *)v9 + 2) - 9;
}
return v9;
}
继续向上看,来到Game构造函数:
void *__thiscall Game::Game(void *this)
{
void *v1; // esi@1
int v2; // edi@1
int v3; // eax@1
int v4; // ST08_4@1
signed int v5; // eax@1
int v6; // ST08_4@1
v1 = this;
*(_DWORD *)this = &Game::`vftable';
Array<UITile *>::Array<UITile *>(16);
Array<UITile *>::Array<UITile *>(16);
Array<UITile *>::Array<UITile *>(16);
Array<UITile *>::Array<UITile *>(16);
Array<UITile *>::Array<UITile *>(16);
Array<UITile *>::Array<UITile *>(16);
v2 = (int)((char *)v1 + 152);
*(_DWORD *)v2 = 0;
*(_DWORD *)(v2 + 8) = 0;
*(_DWORD *)(v2 + 12) = 0;
GameStats::GameStats((char *)v1 + 168);
Game::G = (Game *)v1;
*((_DWORD *)v1 + 8) = -1;
*((_DWORD *)v1 + 9) = -1;
*((_DWORD *)v1 + 4) = 0;
*((_DWORD *)v1 + 3) = 0;
*((_DWORD *)v1 + 2) = 0;
*((_BYTE *)v1 + 218) = 0;
*((_DWORD *)v1 + 10) = 2;
*((_DWORD *)v1 + 12) = 0;
*((_DWORD *)v1 + 13) = 0;
*((_BYTE *)v1 + 197) = 0;
*((_BYTE *)v1 + 195) = 1;
*((_BYTE *)v1 + 193) = 0;
*((_BYTE *)v1 + 194) = 0;
*((_BYTE *)v1 + 196) = 0;
*((_BYTE *)v1 + 192) = 0;
*((_DWORD *)v1 + 47) = 0;
*((_BYTE *)v1 + 24) = 1;
*((_BYTE *)v1 + 25) = 1;
*((_BYTE *)v1 + 26) = 1;
*((_BYTE *)v1 + 27) = 1;
*((_BYTE *)v1 + 28) = 1;
*((_BYTE *)v1 + 29) = 1;
*((_BYTE *)v1 + 30) = 0;
*((_DWORD *)v1 + 50) = 1;
v3 = EDifficultyToWidth(1);
v4 = *((_DWORD *)v1 + 50);
*((_DWORD *)v1 + 51) = v3;
v5 = EDifficultyToHeight(v4);
v6 = *((_DWORD *)v1 + 50);
*((_DWORD *)v1 + 52) = v5;
*((_DWORD *)v1 + 53) = EDifficultyToMineCount(v6);
Game::RandomizeSeedOnTime((Game *)v1);
CSQMTimeRecorder::SetDataId((CSQMTimeRecorder *)((char *)v1 + 152), 0x17C7u);
Game::Reset((Game *)v1, 0, 0, 0);
Game::RequestSetState(v1, 0);
*((_BYTE *)v1 + 216) = 0;
*((_BYTE *)v1 + 217) = 0;
return v1;
}
EDifficultyToWidth和EDifficultyToHeight,分析可知是将默认难度级别转换为雷区阵列的高和宽,同理EDifficultyToMineCount是雷数。那么Game类结构:
+200 Difficulty 1 2 3
+204 Width 9 16 16
+208 Height 9 16 30
+212 MineCount 10 40 99
再往上层看调用者:
struct Game *__stdcall Game::SafeGetSingleton()
{
void *v0; // ecx@2
Game *v1; // eax@3
int v3; // [sp-10h] [bp-2Ch]@6
int v4; // [sp-Ch] [bp-28h]@6
int v5; // [sp-8h] [bp-24h]@6
void *v6; // [sp+Ch] [bp-10h]@2
int v7; // [sp+18h] [bp-4h]@2
if ( !Game::G )
{
v0 = operator new(0xE0u);
v6 = v0;
v7 = 0;
if ( v0 )
v1 = (Game *)Game::Game(v0);
else
v1 = 0;
v7 = -1;
Game::G = v1;
if ( !v1 || (v6 = &v3, Str::Str(dword_10868FC), !(unsigned __int8)Game::initLogic(Game::G, v3, v4, v5)) )
Game::PrintFatalErrorAndQuit(103);
}
return Game::G;
}
可见,Game是个单例类,构造的类指针存在了全局变量Game::G中,因此我们只要读取内存PE获取该结构相对.data段偏移,即可获取全部所需数据(或者调用SafeGetSingleton,只不过要定位.text段基址)。再来看 Game::Reset
void __thiscall Game::Reset(Game *this, bool a2, bool a3, bool a4)
{
Game *v4; // esi@1
int v5; // eax@2
int v6; // edi@5
double v7; // st7@6
int v8; // edi@7
int v9; // eax@8
int v10; // ecx@9
int v11; // eax@9
int v12; // ecx@14
int v13; // edi@15
void *v14; // eax@15
void *v15; // eax@16
bool v16; // di@18
void *v17; // ecx@19
int v18; // eax@22
void *v19; // eax@22
int v20; // [sp+18h] [bp-28h]@15
void *v21; // [sp+18h] [bp-28h]@21
int v22; // [sp+1Ch] [bp-24h]@15
int v23; // [sp+20h] [bp-20h]@7
int v24; // [sp+24h] [bp-1Ch]@9
int v25; // [sp+28h] [bp-18h]@9
int v26; // [sp+2Ch] [bp-14h]@13
char v27; // [sp+33h] [bp-Dh]@1
v4 = this;
Game::SetTimerEnabled(this, 0);
v27 = 0;
if ( !a3 )
{
v5 = *((_DWORD *)v4 + 4);
if ( v5 )
{
if ( *(_DWORD *)(v5 + 24) > 0 && *((_DWORD *)Game::G + 10) == 1 )
{
v6 = *(_DWORD *)(v5 + 32);
if ( v6 != 4 )
{
v7 = *(float *)(v5 + 28);
floorf(*(float *)(v5 + 28));
GameStats::AddNewScore((char *)v4 + 168, v6, (signed int)v7, 0);
Game::Save(Game::G, 0, 0);
}
}
}
}
v8 = *((_DWORD *)v4 + 50);
*((float *)v4 + 11) = 1.0;
v23 = v8;
if ( a3 && (v9 = *((_DWORD *)v4 + 4)) != 0 )
{
v23 = *(_DWORD *)(v9 + 32);
v24 = *(_DWORD *)(v9 + 12);
v10 = *(_DWORD *)(v9 + 8);
v11 = *(_DWORD *)(v9 + 4);
v25 = v10;
}
else if ( v8 == 4 )
{
v24 = *((_DWORD *)v4 + 51);
v25 = *((_DWORD *)v4 + 52);
v11 = *((_DWORD *)v4 + 53);
}
else
{
v24 = EDifficultyToWidth(v8);
v25 = EDifficultyToHeight(v8);
v11 = EDifficultyToMineCount(v8);
}
v26 = v11;
if ( a3 && (v12 = *((_DWORD *)v4 + 4)) != 0 )
{
v13 = *(_DWORD *)(v12 + 36);
v22 = *(_DWORD *)(v12 + 40);
v20 = *(_DWORD *)(v12 + 44);
Board::`scalar deleting destructor((void *)v12, 1);
*((_DWORD *)v4 + 4) = 0;
v14 = operator new(0x48u);
if ( v14 )
v15 = Board::Board(v14, v23, v24, v25, v26, v20, v13, v22, 1);
else
v15 = 0;
*((_DWORD *)v4 + 4) = v15;
*((_DWORD *)v15 + 6) = 0;
Game::RandomizeSeedOnTime(v4);
v27 = 1;
v16 = 1;
}
else
{
v17 = (void *)*((_DWORD *)v4 + 4);
v16 = 1;
if ( v17 )
{
Board::`scalar deleting destructor(v17, 1);
*((_DWORD *)v4 + 4) = 0;
}
v21 = operator new(0x48u);
if ( v21 )
{
v18 = _time(0);
v19 = Board::Board(v21, v23, v24, v25, v26, v18, -1, -1, 0);
}
else
{
v19 = 0;
}
*((_DWORD *)v4 + 4) = v19;
}
Game::SaveGameExplorerStatistics(v4);
*((_BYTE *)v4 + 217) = 0;
if ( !*((_BYTE *)v4 + 193) || !Game::RandomizeArt(v4, v16) )
{
if ( !a2 )
goto LABEL_33;
if ( *((_BYTE *)v4 + 27) )
UIBoardCanvas::SetAllTilesTopAlpha(*((UIBoardCanvas **)v4 + 3), 0);
Game::ResetCanvas(v4);
}
if ( a2 )
{
Game::RequestSetState(v4, 1);
UIBoardCanvas::Refresh(*((UIBoardCanvas **)v4 + 3), 1);
Game::DoNewBoardAnimation(v4);
}
LABEL_33:
if ( v27 )
UIBoardCanvas::ShowTipMessage(*((UIBoardCanvas **)v4 + 3), L"Restart");
if ( a4 )
{
UserInterface::ProcessMouseMove(g_pUserInterface, v16);
Engine_ResetTimer();
}
if ( a3 )
*(_DWORD *)(*((_DWORD *)v4 + 4) + 24) = 1;
*((_BYTE *)v4 + 197) = 0;
}
分析可知:
Game:sizeof=224
+8 DWORD
+12 UIBoardCanvas *
+16 Board*
+24 bool
+25 bool
+26 bool
+27 bool
+28 bool
+29 bool
+30 bool
+32 int
+36 int
+40 int state
+48 DWORD
+52 DWORD
+56 Array<UITile *>*
+72 Array<UITile *>*
+88 Array<UITile *>*
+104 Array<UITile *>*
+120 Array<UITile *>*
+136 Array<UITile *>*
+152 CSQMTimeRecorder*
+188 DWORD
+192 bool
+193 bool
+194 bool
+195 bool
+196 bool
+197 bool
+200 Difficulty
+204 Width
+208 Height
+212 MineCount
+216 bool
+217 bool
+218 bool IsTimerEnabled
Board:sizeof=72
+4 MineCount
+8 Height
+12 Width
+24 是否挖过雷?
+28 float
+32 Difficulty
+36 HitX
+40 HitY
+44
+68 Array<Array<BYTE>>* MineArray
Array<UITile *>:sizeof=16
+0 MineCount
+4
+8
+12 DWORD[] MineIndexArray//存储雷位置索引
UITile:
+36 state
void __thiscall Board::placeMines(Board *this, int a2, int a3)
{//布置雷区
Board *v3; // esi@1
int v4; // eax@1
int i; // edi@4
signed int v6; // ecx@5
int v7; // eax@9
int v8; // edi@10
unsigned int v9; // ebx@13
unsigned int v10; // ecx@15
signed int v11; // ebx@16
int v12; // eax@16
int v13; // edx@16
unsigned int Seed; // [sp+Ch] [bp-8h]@1
int pv; // [sp+10h] [bp-4h]@2
v3 = this;
Seed = GetRandomSeed();
SetRandomSeed(*((_DWORD *)v3 + 11));
v4 = (int)operator new(0x10u);
if ( v4 )
pv = Array<UITile *>::Array<UITile *>(v4, 16);
else
pv = 0;
for ( i = 0; i < *((_DWORD *)v3 + 3) * *((_DWORD *)v3 + 2); ++i )
{
v6 = *((_DWORD *)v3 + 3);
if ( (i % v6 - a2) * (2 * (i % v6 - a2 >= 0) - 1) > 1 || (i / v6 - a3) * (2 * (i / v6 - a3 >= 0) - 1) > 1 )//取第一次点击位置以外的雷区位置,存到数组array1里,这样设计就不会导致第一次就点到雷
Array<NodeBase *>::Add(pv, i);
}
v7 = (int)operator new(0x10u);
if ( v7 )
v8 = Array<UITile *>::Array<UITile *>(v7, *((_DWORD *)v3 + 1));
else
v8 = 0;
while ( *(_DWORD *)v8 != *((_DWORD *)v3 + 1) && *(_DWORD *)pv )
{//若雷数不够且array1没被用完就一直布雷
v9 = GetRandom(0, *(_DWORD *)pv - 1);//rand(0,array1.size()-1)
Array<NodeBase *>::Add(v8, *(_DWORD *)(*(_DWORD *)(pv + 12) + 4 * v9));//在array2中增加该位置
Array<NodeBase *>::Remove(pv, v9);//从array1中去除该位置
}
v10 = 0;
if ( *(_DWORD *)v8 )
{
do
{
v11 = *((_DWORD *)v3 + 3);
v12 = *(_DWORD *)(*(_DWORD *)(v8 + 12) + 4 * v10) / v11;
v13 = *(_DWORD *)(*(_DWORD *)(v8 + 12) + 4 * v10++) % v11;//分别还原横纵坐标
*(_BYTE *)(v12 + *(_DWORD *)(*(_DWORD *)(*(_DWORD *)(*((_DWORD *)v3 + 17) + 12) + 4 * v13) + 12)) = 1;//可以推测出将布雷坐标数组存到Array<Array<BYTE>>二维数组里
}
while ( v10 < *(_DWORD *)v8 );
}
Array<int>::`scalar deleting destructor'((void *)v8, 1);
if ( pv )
Array<int>::`scalar deleting destructor'((void *)pv, 1);
SetRandomSeed(Seed);
}
向上层找:
int __thiscall Board::AttemptReveal(Board *this, unsigned int a2, unsigned int a3)
{
Board *v3; // esi@1
int v4; // eax@1
int v5; // eax@6
unsigned int *v7; // [sp+0h] [bp-10h]@0
int v8; // [sp+Ch] [bp-4h]@1
v3 = this;
v4 = *(_DWORD *)(*(_DWORD *)(*(_DWORD *)(*(_DWORD *)(*((_DWORD *)this + 16) + 12) + 4 * a2) + 12) + 4 * a3);
v8 = 0;
if ( v4 == 9 || v4 == 11 )
{
if ( *((_DWORD *)this + 6) )
{
if ( *(_BYTE *)(a3 + *(_DWORD *)(*(_DWORD *)(*(_DWORD *)(*((_DWORD *)this + 17) + 12) + 4 * a2) + 12)) )
{
v8 = 0;
LABEL_11:
++*((_DWORD *)v3 + 6);
goto LABEL_12;
}
v5 = Board::revealAt(this, a2, a3, 0, a2, a3, 0);
}
else
{
Board::placeMines(this, a2, a3);//如果是首次挖雷,则布雷
v5 = Board::revealAt(v3, a2, a3, 0, a2, a3, 0);
*((_DWORD *)v3 + 9) = a2;
*((_DWORD *)v3 + 10) = a3;//存储点击位置
}
v8 = v5;
goto LABEL_11;
}
if ( *((_BYTE *)Game::G + 24) )
GameAudio::PlaySoundProto(0, 0, 0, v7);
LABEL_12:
if ( *((_DWORD *)v3 + 12) <= 0u )
UIBoardCanvas::Refresh(*((UIBoardCanvas **)Game::G + 3), 1);
return v8;
}
至此,想得到的结果已经都得到,根据以上分析可以得到代码:
template<typename T>
struct Array
{
DWORD MineCount;
DWORD unused1;
DWORD unused2;
T* data;
};
#include <TlHelp32.h>
void CWin7MineSweeperHackerDlg::OnBnClickedOk()
{
// TODO: 在此添加控件通知处理程序代码
UpdateData(TRUE);
m_output="";
PROCESSENTRY32 pe;
pe.dwSize=sizeof(PROCESSENTRY32);
HANDLE hProcess=NULL;
HANDLE hSnapshot=CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);
if(hSnapshot == INVALID_HANDLE_VALUE || !Process32First(hSnapshot,&pe))
{
AfxMessageBox("ERROR!");
return;
}
do
{
if(StrStr(pe.szExeFile,"MineSweeper.exe"))
{
hProcess=OpenProcess(PROCESS_ALL_ACCESS,FALSE,pe.th32ProcessID);
break;
}
}
while(Process32Next(hSnapshot,&pe));
CloseHandle(hSnapshot);
if(hProcess == NULL)
{
AfxMessageBox("找不到进程!");
return;
}
MODULEENTRY32 me;
me.dwSize=sizeof(MODULEENTRY32);
LPVOID peBase=NULL;
DWORD peSize;
hSnapshot=CreateToolhelp32Snapshot(TH32CS_SNAPMODULE,pe.th32ProcessID);
if(hSnapshot == NULL || !Module32First(hSnapshot,&me))
{
AfxMessageBox("ERROR");
return;
}
do
{
if(StrStrI(me.szModule,"mine"))
{
peBase=(LPVOID)me.modBaseAddr;
peSize=me.modBaseSize;
break;
}
} while (Module32Next(hSnapshot,&me));
CloseHandle(hSnapshot);
if(peBase == NULL)
{
AfxMessageBox("ERROR");
return;
}
BYTE* buffer=new BYTE[peSize];
DWORD readnum;
ReadProcessMemory(hProcess,peBase,buffer,peSize,&readnum);
IMAGE_DOS_HEADER* dosheader=(IMAGE_DOS_HEADER*)buffer;
IMAGE_NT_HEADERS* ntheader=(IMAGE_NT_HEADERS*)(buffer+dosheader->e_lfanew);
IMAGE_SECTION_HEADER* cur=(IMAGE_SECTION_HEADER*)(ntheader+1);
for(int i=0;i<ntheader->FileHeader.NumberOfSections;i++)
{
if(StrStrI((CHAR*)cur->Name,".data"))
{
break;
}
cur++;
}
struct Board;
struct Game
{
DWORD unused[4];
struct Board* board;
};
struct Board
{
DWORD unused1;
DWORD MineCount;
DWORD Height;
DWORD Width;
DWORD unused2[4];
DWORD Difficulty;
DWORD HitX;
DWORD HitY;
DWORD unused3[6];
Array< Array<BYTE> >* MineArray;
};
BYTE* GameInThatProc = (BYTE*)*(Game**)(buffer + cur->VirtualAddress + 0x88B4);
//换算成本地地址
Game G;
if (GameInThatProc == NULL)
{
AfxMessageBox("数据有误");
goto end1;
}
ReadProcessMemory(hProcess, GameInThatProc, &G, sizeof(G), &readnum);
//换算成本地地址
Board B;
if (G.board == NULL)
{
AfxMessageBox("数据有误");
return;
}
ReadProcessMemory(hProcess, G.board, &B, sizeof(B), &readnum);
Array< Array<BYTE> > MA;
if (B.MineArray == NULL)
{
AfxMessageBox("数据有误");
goto end1;
}
ReadProcessMemory(hProcess, B.MineArray, &MA, sizeof(MA), &readnum);
typedef Array<BYTE>* pMAsub;//先取指针数组
pMAsub* data1 = new pMAsub[B.Width];
if (MA.data == NULL)
{
AfxMessageBox("数据有误");
goto end1;
}
ReadProcessMemory(hProcess, MA.data, data1, sizeof(Array<BYTE>*) * B.Width, &readnum);
for(int i=0;i<B.Width;i++)
{
Array<BYTE> data2;//对指针数组每个指针找到对象内存
ReadProcessMemory(hProcess, data1[i], &data2, sizeof(data2), &readnum);
if (data2.data == NULL)
{
AfxMessageBox("数据有误");
goto end1;
}
BYTE* data3 = new BYTE[B.Height];
ReadProcessMemory(hProcess, data2.data, data3, sizeof(BYTE) * B.Height, &readnum);
for(int j=0;j<B.Height;j++)
{
if(data3[j] == 1)//雷
{
m_output += "雷";
}
else
{
m_output += "空";
}
}
delete[]data3;
m_output += "\r\n";
}
delete[]data1;
end1:
delete []buffer;
CloseHandle(hProcess);
UpdateData(FALSE);
}
新开一局扫雷,可以发现程序获取结果:
空空空空空空空空空
空空空空空空空空空
空空空空空空空空空
空空空空空空空空空
空空空空空空空空空
空空空空空空空空空
空空空空空空空空空
空空空空空空空空空
空空空空空空空空空
可见,没点击前,是不布雷的,和之前分析的一样。点击以后:
空雷空空空空空空空
空雷空空空空空空空
空空空空空空空空空
空空雷空雷空空空空
空空雷空空空空空空
空空空空空空空空空
雷雷空空空空空空空
空空雷空空空空空空
雷空空空雷空空空空
特别注意,,由于程序中使用的数据结构原因,得到的结果,和扫雷屏幕上显示,关于y=x对称!!!