小白的第一次逆向之扫雷分析与辅助制作
思路:
1、想要实现的功能
鼠标悬停,自动提示是否有雷
一键自动扫雷
2、如何实现这些功能
Q:首先要了解如果想要实现想要的功能,需要进行什么操作?
A:实现鼠标移动 ,自动识别有没有雷
需要hook掉回调函数,响应WM_MOUSEMOVE消息
Q:怎么判断鼠标所在的位置是不是有雷?是不是需要把屏幕坐标转换成扫雷区域的数组坐标?
A:找到雷的分辨类型;找到雷的数量;找到扫雷区域的大小
Q:如何实现一键扫雷?
A:遍历扫雷的数组,对所有不是雷的区域;发WM_LBUTTONDOWN的消息,这样应该实现了扫雷的功能。同时,是不是还需要对数组坐标进行向屏幕坐标的转换
具体实现步骤
有了思路之后,进行具体的实现
找数据
找界面的区域大小和雷的数量的数据应该是最好找的
查看是否有重定位(如果有就关掉),上CE
首先先找高度的基址
通过几次尝试之后,发现
那么这两地址应该就是高度的基址了,具体为什么有2个先不管,放到窗口中先放一放
然后找宽度的基址
同样的经过几次尝试之后,发现
这两个地址就该就是跟宽度相关的地址
找雷数的基址
步骤一样
但是这里发现了3条数据,上面都是2条,有点不符合规律,所有就测试一下,到底这3个地址是不是全部都跟地雷数量有关(暂且按下不表,具体中od去看到底哪个才是)
根据地址的连续性,稍微整理了一下
找初始化地图数据的地址
找到了地雷和区域大小的基址,那么只要找到更改这个对区域进行更改的函数,猜想应该可以找到布雷的方法和各种雷等数据的判断类型
关键是如何找到这个函数,首先要想明白的一件事是,游戏对初始化的时候,肯定是会对数据进行调用的,那么是不是可以通过游戏初始化的时候,访问或者更改这里面的代码从而找到调用的地址,试一下把
通过故意还原游戏,从而找到了一访问某个数据的地址,或得这个地址的 数据为
01003712
这个时候,上od去看一下,是不是跟我们猜想的一样
01002ED5 /$ B8 60030000 MOV EAX,0x360 ; eax = 0x360 ,不知道要干嘛,接着看
01002EDA |> 48 /DEC EAX ; eax -=1
01002EDB |. C680 40530001>|MOV BYTE PTR DS:[EAX+<雷区界面基址>],0xF ; 通过流程分析可以知道,0xf是空地的类型值,说明在初始化空地
01002EE2 |.^ 75 F6 \JNZ SHORT winmine.01002EDA ; 说明数组应该是0x360 这么大
01002EE4 |. 8B0D 34530001 MOV ECX,DWORD PTR DS:[<宽度0>] ; ecx =宽度的数值,x轴
01002EEA |. 8B15 38530001 MOV EDX,DWORD PTR DS:[<高度0>] ; edx= 高度的数值,y轴
01002EF0 |. 8D41 02 LEA EAX,DWORD PTR DS:[ECX+0x2] ; 额,没看懂,把宽度+2 保存到eax中,eax == 宽度+2
01002EF3 |. 85C0 TEST EAX,EAX ; 判断eax是不是为空
01002EF5 |. 56 PUSH ESI ; 根据函数最后有pop esi,应该是保存环境
01002EF6 |. 74 19 JE SHORT winmine.01002F11 ; 如果eax=0,跳转
01002EF8 |. 8BF2 MOV ESI,EDX ; esi =高度的数值,y轴
01002EFA |. C1E6 05 SHL ESI,0x5 ; esi = 代表y轴的高度*32
01002EFD |. 8DB6 60530001 LEA ESI,DWORD PTR DS:[ESI+0x1005360] ; esi = [数组基址+0x20 +esi(高度*32)],应该是这个高度下的数组的最下一行
01002F03 |> 48 /DEC EAX ; eax = 宽度+2,说明左右的边界各占1,eax-=1
01002F04 |. C680 40530001>|MOV BYTE PTR DS:[EAX+<雷区界面基址>],0x10 ; 10应该就是边界的类型了,这个应该是填充数组第一行的边界,从右往左
01002F0B |. C60406 10 |MOV BYTE PTR DS:[ESI+EAX],0x10 ; 这个高度下区域最下面的边界,也是从右往左填充的
01002F0F |.^ 75 F2 \JNZ SHORT winmine.01002F03 ; eax!=0 循环填充边界,也就是说填充的时候是 数组基址+0x20+高度*32+具体的列数
01002F11 |> 8D72 02 LEA ESI,DWORD PTR DS:[EDX+0x2] ; 同样的,esi 保存的是高度的数值+2,从而说明上下边界高度是1,而且不占游戏界面的区域
01002F14 |. 85F6 TEST ESI,ESI ; 判断esi 是不是为空
01002F16 |. 74 21 JE SHORT winmine.01002F39 ; 为空直接跳出了
01002F18 |. 8BC6 MOV EAX,ESI ; eax =高度的值+0x2
01002F1A |. C1E0 05 SHL EAX,0x5 ; eax =(高度的值+0x2)*32
01002F1D |. 8D90 40530001 LEA EDX,DWORD PTR DS:[EAX+<雷区界面基址>] ; 这两行应该也是要填充边界的为止,具体的就不看了
01002F23 |. 8D8408 415300>LEA EAX,DWORD PTR DS:[EAX+ECX+0x1005341] ; edx 和eax应该是最下一行,最左边和最右边的值
01002F2A |> 83EA 20 /SUB EDX,0x20 ; 0x20 应该是2行
01002F2D |. 83E8 20 |SUB EAX,0x20
01002F30 |. 4E |DEC ESI ; esi -=1
01002F31 |. C602 10 |MOV BYTE PTR DS:[EDX],0x10 ; 进行边界填充
01002F34 |. C600 10 |MOV BYTE PTR DS:[EAX],0x10 ; 边界填充
01002F37 |.^ 75 F1 \JNZ SHORT winmine.01002F2A
01002F39 |> 5E POP ESI
01002F3A \. C3 RETN
通过od可以看到上面的代码,验证是不是真的,下个断点还原下场景,看看是不是回断下来
结果是断下来,说明这个地方很可能就是对界面进行初始化的函数,先来大致的分析一下这个函数到底做了什么
第一遍大体流程分析
0100367A /$ A1 AC560001 MOV EAX,DWORD PTR DS:[<宽度1>] ; eax == 宽度,暂且规定宽度是x轴坐标
0100367F |. 8B0D A8560001 MOV ECX,DWORD PTR DS:[<高度1>]
01003685 |. 53 PUSH EBX
01003686 |. 56 PUSH ESI
01003687 |. 57 PUSH EDI ; 保存寄存器环境
01003688 |. 33FF XOR EDI,EDI ; edi == 0
0100368A |. 3B05 34530001 CMP EAX,DWORD PTR DS:[<宽度0>] ; 宽度1 和宽度0 的数据进行对比
01003690 |. 893D 64510001 MOV DWORD PTR DS:[0x1005164],EDI ; 把0 移动到这个地址所在的空间内
01003696 |. 75 0C JNZ SHORT winmine.010036A4 ; 宽度0 和宽度1 不想等,跳转
01003698 |. 3B0D 38530001 CMP ECX,DWORD PTR DS:[<高度0>] ; 高度1和高度0进行对比
0100369E |. 75 04 JNZ SHORT winmine.010036A4 ; 高度0和高度1进行对比,不想等跳转,现在我有点怀疑这两组数据应该是用户设置的数据和实际填充的数据进行对比,下面可能是对数据的填充
010036A0 |. 6A 04 PUSH 0x4
010036A2 |. EB 02 JMP SHORT winmine.010036A6
010036A4 |> 6A 06 PUSH 0x6 ; push 04和push 06很有可能判断用户是不是进行了跟更改界面大小的设置,现在的ebx可能就是对这个设置进行保存
010036A6 |> 5B POP EBX ; 猜测是保存是否更改界面的类型的值,ebx ==06 或0x4
010036A7 |. A3 34530001 MOV DWORD PTR DS:[<宽度0>],EAX ; eax == 宽度1 ,把宽度1 赋值给宽度0,说明宽度1才是真正的宽度的基址
010036AC |. 890D 38530001 MOV DWORD PTR DS:[<高度0>],ECX ; ecx == 高度0,把高度0赋值给高度1,说明高度0才是真正的高度的基址
010036B2 |. E8 1EF8FFFF CALL winmine.01002ED5 ; call 的这个函数目测是一个循环,先不搞,把整个大体流程分析完之后再搞
010036B7 |. A1 A4560001 MOV EAX,DWORD PTR DS:[<地雷1>] ; eax == 地雷1的地址
010036BC |. 893D 60510001 MOV DWORD PTR DS:[0x1005160],EDI ; edi=0,把0 移动到这个地址,暂时不明白是干嘛的
010036C2 |. A3 30530001 MOV DWORD PTR DS:[<地雷0>],EAX ; eax == 地雷1的地址,把地雷1中的值赋值给地雷0,说明地雷1才是地雷的真正的基址
010036C7 |> FF35 34530001 PUSH DWORD PTR DS:[<宽度0>] ; 把宽度0(x轴的坐标)入栈
010036CD |. E8 6E020000 CALL <winmine.产生随机数> ; ctrl+a 发现这个是rand函数,然后根据入栈的数据就可以说明这个是根据某个值产生随机的值,这个函数可以不用看了
010036D2 |. FF35 38530001 PUSH DWORD PTR DS:[<高度0>] ; 高度0入栈
010036D8 |. 8BF0 MOV ESI,EAX ; esi = eax应该是函数返回值
010036DA |. 46 INC ESI ; esi +1 ,根据宽度产生的随机数+1,esi保存根据宽度产生的随机值
010036DB |. E8 60020000 CALL <winmine.产生随机数>
010036E0 |. 40 INC EAX ; 根据高度产生的随机值+1
010036E1 |. 8BC8 MOV ECX,EAX ; ecx = 根据高度产生的随机值
010036E3 |. C1E1 05 SHL ECX,0x5 ; ecx =ecx*32,根据高度产生的随机值左移5位
010036E6 |. F68431 405300>TEST BYTE PTR DS:[ECX+ESI+<雷区界面基址>],0x80 ; ecx是随机数,esi也是随机数,+一个地址应该是去锁定其中的数组为止,说明这个值就是数组基址
010036EE |.^ 75 D7 JNZ SHORT winmine.010036C7 ; 跟80进行比较,可能说明80就是非雷的区域,如果不是非雷的区域,要么是边界要么是雷,所以需要跳转重新计算
010036F0 |. C1E0 05 SHL EAX,0x5 ; eax左移5位就是ecx中的值
010036F3 |. 8D8430 405300>LEA EAX,DWORD PTR DS:[EAX+ESI+<雷区界面基址>] ; 把区域具体的数组的地址放到eax中
010036FA |. 8008 80 OR BYTE PTR DS:[EAX],0x80 ; 把eax中第一个字节火上80,哎呦可能是设置雷了,说明80是并不是空地,运行起来发现0f是空地,8f是地雷
010036FD |. FF0D 30530001 DEC DWORD PTR DS:[<地雷0>] ; 设置好雷之后对设置的地雷数量进行-1
01003703 |.^ 75 C2 JNZ SHORT winmine.010036C7 ; 判断地雷0中保存的值是否为0,不是的化重复设置地雷
01003705 |. 8B0D 38530001 MOV ECX,DWORD PTR DS:[<高度0>] ; ecx = 高度0的值
0100370B |. 0FAF0D 345300>IMUL ECX,DWORD PTR DS:[<宽度0>] ; ecx = 用户设置的高度的值* 用户设置的宽度的值
01003712 |. A1 A4560001 MOV EAX,DWORD PTR DS:[<地雷1>] ; eax = 地雷的值
01003717 |. 2BC8 SUB ECX,EAX ; ecx = 高度*宽度 -地雷数,这是干啥的?
01003719 |. 57 PUSH EDI ; 根据后面有pop edi,应该是保存环境
0100371A |. 893D 9C570001 MOV DWORD PTR DS:[0x100579C],EDI
01003720 |. A3 30530001 MOV DWORD PTR DS:[<地雷0>],EAX
01003725 |. A3 94510001 MOV DWORD PTR DS:[0x1005194],EAX
0100372A |. 893D A4570001 MOV DWORD PTR DS:[0x10057A4],EDI
01003730 |. 890D A0570001 MOV DWORD PTR DS:[0x10057A0],ECX
01003736 |. C705 00500001>MOV DWORD PTR DS:[0x1005000],0x1
01003740 |. E8 25FDFFFF CALL winmine.0100346A
01003745 |. 53 PUSH EBX
01003746 |. E8 05E2FFFF CALL winmine.01001950
0100374B |. 5F POP EDI
0100374C |. 5E POP ESI
0100374D |. 5B POP EBX
0100374E \. C3 RETN
接下来对几个关键函数进行分析,比如产生随机雷之前有个有很多循环的函数,估计事初始化界面数组的函数,进去分析一下
01002ED5 /$ B8 60030000 MOV EAX,0x360 ; eax = 0x360 ,不知道要干嘛,接着看
01002EDA |> 48 /DEC EAX ; eax -=1
01002EDB |. C680 40530001>|MOV BYTE PTR DS:[EAX+<雷区界面基址>],0xF ; 通过流程分析可以知道,0xf是空地的类型值,说明在初始化空地
01002EE2 |.^ 75 F6 \JNZ SHORT winmine.01002EDA ; 说明数组应该是0x360 这么大
01002EE4 |. 8B0D 34530001 MOV ECX,DWORD PTR DS:[<宽度0>] ; ecx =宽度的数值,x轴
01002EEA |. 8B15 38530001 MOV EDX,DWORD PTR DS:[<高度0>] ; edx= 高度的数值,y轴
01002EF0 |. 8D41 02 LEA EAX,DWORD PTR DS:[ECX+0x2] ; 额,没看懂,把宽度+2 保存到eax中,eax == 宽度+2
01002EF3 |. 85C0 TEST EAX,EAX ; 判断eax是不是为空
01002EF5 |. 56 PUSH ESI ; 根据函数最后有pop esi,应该是保存环境
01002EF6 |. 74 19 JE SHORT winmine.01002F11 ; 如果eax=0,跳转
01002EF8 |. 8BF2 MOV ESI,EDX ; esi =高度的数值,y轴
01002EFA |. C1E6 05 SHL ESI,0x5 ; esi = 代表y轴的高度*32
01002EFD |. 8DB6 60530001 LEA ESI,DWORD PTR DS:[ESI+0x1005360] ; esi = [数组基址+0x20 +esi(高度*32)],应该是这个高度下的数组的最下一行
01002F03 |> 48 /DEC EAX ; eax = 宽度+2,说明左右的边界各占1,eax-=1
01002F04 |. C680 40530001>|MOV BYTE PTR DS:[EAX+<雷区界面基址>],0x10 ; 10应该就是边界的类型了,这个应该是填充数组第一行的边界,从右往左
01002F0B |. C60406 10 |MOV BYTE PTR DS:[ESI+EAX],0x10 ; 这个高度下区域最下面的边界,也是从右往左填充的
01002F0F |.^ 75 F2 \JNZ SHORT winmine.01002F03 ; eax!=0 循环填充边界
01002F11 |> 8D72 02 LEA ESI,DWORD PTR DS:[EDX+0x2] ; 同样的,esi 保存的是高度的数值+2,从而说明上下边界高度是1,而且不占游戏界面的区域
01002F14 |. 85F6 TEST ESI,ESI ; 判断esi 是不是为空
01002F16 |. 74 21 JE SHORT winmine.01002F39 ; 为空直接跳出了
01002F18 |. 8BC6 MOV EAX,ESI ; eax =高度的值+0x2
01002F1A |. C1E0 05 SHL EAX,0x5 ; eax =(高度的值+0x2)*32
01002F1D |. 8D90 40530001 LEA EDX,DWORD PTR DS:[EAX+<雷区界面基址>] ; 这两行应该也是要填充边界的为止,具体的就不看了
01002F23 |. 8D8408 415300>LEA EAX,DWORD PTR DS:[EAX+ECX+0x1005341] ; edx 和eax应该是最下一行,最左边和最右边的值
01002F2A |> 83EA 20 /SUB EDX,0x20 ; 0x20 应该是2行
01002F2D |. 83E8 20 |SUB EAX,0x20
01002F30 |. 4E |DEC ESI ; esi -=1
01002F31 |. C602 10 |MOV BYTE PTR DS:[EDX],0x10 ; 进行边界填充
01002F34 |. C600 10 |MOV BYTE PTR DS:[EAX],0x10 ; 边界填充
01002F37 |.^ 75 F1 \JNZ SHORT winmine.01002F2A
01002F39 |> 5E POP ESI
01002F3A \. C3 RETN
通过分析可以看出,这个函数只是对空地和边界进行填充
通过上述的分析,大体知道了初始化雷区的的公式
划重点:数组基址+0x20 +esi(高度*32),如果这个位置的opcode是0x8f,说明是雷,0xf说明是空地
然后去看一下内存图
01005340 <>10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10
01005350 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F
01005360 10 8F 8F 8F 0F 0F 0F 0F 8F 0F 0F 8F 0F 0F 8F 10 弿????
01005370 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F
01005380 10 8F 8F 0F 0F 0F 8F 0F 0F 0F 0F 8F 8F 0F 0F 10 弿?弿
01005390 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F
010053A0 10 0F 8F 0F 0F 0F 8F 8F 0F 8F 0F 0F 8F 8F 8F 10 ?弿?弿?
010053B0 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F
010053C0 10 0F 8F 8F 0F 0F 8F 8F 0F 0F 8F 0F 8F 0F 0F 10 弿弿??
010053D0 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F
010053E0 10 0F 0F 0F 0F 8F 8F 0F 0F 8F 0F 8F 0F 8F 8F 10 弿??弿
010053F0 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F
01005400 10 8F 8F 8F 0F 0F 8F 8F 8F 8F 8F 8F 0F 8F 0F 10 弿?弿弿弿?
01005410 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F
01005420 10 8F 0F 0F 8F 8F 0F 8F 0F 0F 8F 8F 8F 0F 0F 10 ?弿?弿?
01005430 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F
01005440 10 0F 8F 0F 0F 0F 0F 0F 0F 8F 0F 0F 0F 8F 8F 10 ??弿
01005450 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F
01005460 10 8F 8F 8F 8F 0F 8F 0F 8F 0F 8F 0F 8F 0F 8F 10 弿弿?????
01005470 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F
01005480 10 8F 8F 8F 0F 0F 0F 8F 0F 0F 8F 0F 0F 8F 8F 10 弿???弿
01005490 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F
010054A0 10 0F 8F 8F 0F 0F 0F 0F 0F 8F 0F 0F 8F 0F 8F 10 弿???
010054B0 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F
010054C0 10 8F 8F 0F 0F 8F 8F 8F 0F 8F 8F 8F 8F 0F 8F 10 弿弿?弿弿?
010054D0 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F
010054E0 10 8F 8F 0F 8F 0F 0F 8F 8F 8F 0F 8F 0F 8F 8F 10 弿?弿??弿
010054F0 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F
01005500 10 8F 8F 8F 0F 8F 0F 0F 0F 8F 8F 0F 8F 8F 0F 10 弿??弿弿
01005510 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F
01005520 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10
从内存图可以看出,是每隔一行进行填充 ,因此可以总结数组的遍历公式
具体的某个x,y坐标下的值= 数组基址+ 高<<5(也就是+32,每隔一行进行填充)+x
而且通过填充的时候add eax ,2 等类似的消息可以看出,边界应该是0,内容是从1开始的,循环的时候要注意
现在去分析一下窗口回调函数(主要是找怎么把鼠标按下的点转换成数组下标的方法)
祭出 大微软的 spy ++
od直接定位到这个回调函数的位置
主要是想看看怎么把鼠标点击的屏幕坐标转换成数组下标的
定位之后发现 代码真长,可能超过300行了QAQ
01001BC9 /. 55 PUSH EBP ; 回调函数
01001BCA |. 8BEC MOV EBP,ESP
01001BCC |. 83EC 40 SUB ESP,0x40 ; 开辟局部空间
01001BCF |. 8B55 0C MOV EDX,[ARG.2] ; edx =uMsg
01001BD2 |. 8B4D 14 MOV ECX,[ARG.4] ; edx = lParam
01001BD5 |. 53 PUSH EBX ; 保存寄存器环境
01001BD6 |. 56 PUSH ESI ; 保存寄存器环境
01001BD7 |. 33DB XOR EBX,EBX ; ebx = 0
01001BD9 |. 57 PUSH EDI ; 保存寄存器环境
01001BDA |. BE 00020000 MOV ESI,0x200 ; esi =0x200,通过下面确定esi是wm_mousemove
01001BDF |. 43 INC EBX ; ebx =1
01001BE0 |. 33FF XOR EDI,EDI ; edi =0
01001BE2 |. 3BD6 CMP EDX,ESI ; edx和esi进行比较,也就是uMsg和0x200进行比较,现在可以确定esi是WM_MOUSEMOVE
01001BE4 |. 0F87 75030000 JA winmine.01001F5F ; 大于跳到这个地方去,需要看LBUTTONDOWN或LBUTTONUP的处理
01001BEA |. 0F84 95040000 JE winmine.01002085 ; 等于mousemove跳转到这个地方,小于的话就继续下面的代码,因为LBUTTONDOWN和LBUTTONUP都大于0x200,暂时先不看下面的了
01001BF0 |. B8 00010000 MOV EAX,0x100
01001BF5 |. 3BD0 CMP EDX,EAX
01001BF7 |. 0F87 5C010000 JA winmine.01001D59
01001BFD |. 0F84 97000000 JE winmine.01001C9A
01001C03 |. 8BC2 MOV EAX,EDX
01001C05 |. 48 DEC EAX ; Switch (cases 2..47)
01001C06 |. 48 DEC EAX
01001C07 |. 74 76 JE SHORT winmine.01001C7F
01001C09 |. 83E8 04 SUB EAX,0x4
01001C0C |. 74 57 JE SHORT winmine.01001C65
01001C0E |. 83E8 09 SUB EAX,0x9
01001C11 |. 74 2B JE SHORT winmine.01001C3E
01001C13 |. 83E8 38 SUB EAX,0x38
01001C16 |. 0F85 8D050000 JNZ winmine.010021A9
01001C1C |. F605 00500001>TEST BYTE PTR DS:[0x1005000],0x8 ; Case 47 of switch 01001C05
01001C23 |. 0F85 80050000 JNZ winmine.010021A9
....
代码省略
只贴上回调函数的图了,其实还分析了很多的函数来掌握流程
通过
01002099 |. 8B45 14 MOV EAX,[ARG.4] ; 从这里的代码开始,下面的就是坐标转成数组的公式了eax = lparam
0100209C |. C1E8 10 SHR EAX,0x10 ; eax 右移16位,相当于eax =(HIWORD)lparam
0100209F |. 83E8 27 SUB EAX,0x27 ; eax(HIWORD的lParam)-0x27
010020A2 |. C1F8 04 SAR EAX,0x4 ; eax算数右移4位
010020A5 |. 50 PUSH EAX ; eax 入栈
010020A6 |. 0FB745 14 MOVZX EAX,WORD PTR SS:[EBP+0x14] ; 入栈的第四个参数,就是lparam,eax = (LOWORD)lparam
010020AA |. 83C0 04 ADD EAX,0x4 ; (LOWROD)lparam 加0x4
010020AD |. C1F8 04 SAR EAX,0x4 ; 然后右移4为
010020B0 |. 50 PUSH EAX ; 找到,这个地方就是转换为数组的下标了
这几行的汇编代码可以看出,再LBUTTONDOWN消息中,通过lParam的高低分分别获得y,x轴坐标
然后通过公式转换为具体的数组下标,转换方式如下
//首先获取x和y轴的坐标 WORD xPos = GET_X_LPARAM(lParam); WORD yPos = GET_Y_LPARAM(lParam); //然后进行转换 //y轴转换 yPos -= 0x27; yPos /= 16; //x轴转换 xPos += 0x4; xPos /= 16;
地图填充的公式和屏幕坐标转换为数组下标的方式已经找到了,
总结一下找到的数据:
数组基地址:0x1005340
高度基地址:0x10056A8
宽度基地址: 0x10056AC
地雷数基地址: 0x10056A4
查找具体某个数组下标中内容的公式:数组基址+ 高<<5(也就是+32,每隔一行进行填充)+宽
坐标与数组下标转换方法在上面
开写
DLL代码
MFC dll 静态编译
// MFCSAOLEI.cpp : 定义 DLL 的初始化例程。
//
#include "stdafx.h"
#include "MFCSAOLEI.h"
#include <windows.h>
#include <minwindef.h>
#include <Windef.h>
#pragma once
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
//
//TODO: 如果此 DLL 相对于 MFC DLL 是动态链接的,
// 则从此 DLL 导出的任何调入
// MFC 的函数必须将 AFX_MANAGE_STATE 宏添加到
// 该函数的最前面。
//
// 例如:
//
// extern "C" BOOL PASCAL EXPORT ExportedFunction()
// {
// AFX_MANAGE_STATE(AfxGetStaticModuleState());
// // 此处为普通函数体
// }
//
// 此宏先于任何 MFC 调用
// 出现在每个函数中十分重要。 这意味着
// 它必须作为函数中的第一个语句
// 出现,甚至先于所有对象变量声明,
// 这是因为它们的构造函数可能生成 MFC
// DLL 调用。
//
// 有关其他详细信息,
// 请参阅 MFC 技术说明 33 和 58。
//
// CMFCSAOLEIApp
BEGIN_MESSAGE_MAP(CMFCSAOLEIApp, CWinApp)
END_MESSAGE_MAP()
// CMFCSAOLEIApp 构造
CMFCSAOLEIApp::CMFCSAOLEIApp()
{
// TODO: 在此处添加构造代码,
// 将所有重要的初始化放置在 InitInstance 中
}
// 唯一的一个 CMFCSAOLEIApp 对象
CMFCSAOLEIApp theApp;
//扫雷窗口句柄
HWND g_hWnd =NULL;
//旧的回调函数的
WNDPROC g_oldProc =NULL;
BYTE *g_BaseOfArray = (BYTE*)0x1005340;
DWORD *g_BaseOfHight= (DWORD*)0x10056A8;
DWORD *g_BaseOfWidth =(DWORD*)0x10056AC;
DWORD *g_BaseOfLandmine =(DWORD*)0x10056A4;
//是不是要写一个函数,用来获取
//自定义回调函数
LRESULT WINAPI CustomWinProc(
_In_ HWND hWnd,
_In_ UINT Msg,
_In_ WPARAM wParam,
_In_ LPARAM lParam
)
{
//用来获取地图的信息
int * pMapArray = new int[(*g_BaseOfHight)* (*g_BaseOfWidth)]{};
//进行数组遍历
//主要作用,保留
for (DWORD y =1;y<*g_BaseOfHight+1;y++)
{
for (DWORD x = 1;x<*g_BaseOfWidth+1;x++)
{
BYTE code = *(BYTE*)(g_BaseOfArray + y * 32+x);
//然后判断是不是地雷
if (code ==0x8F)
{
//说明是地雷
pMapArray[(y - 1)*(*g_BaseOfWidth)+(x-1)] = 0x8f;
}
else
{
pMapArray[(y - 1)*(*g_BaseOfWidth) + (x - 1)] = 0xf;
}
}
}
//移动鼠标的设置
if (Msg == WM_MOUSEMOVE)
{
/*
功能实现要求:
实现能在窗口
dll 注入代码就不贴了
看一下效果图 :标题栏显示坐标并提示有无雷,F5 一键扫雷
最后:迷之logo
//不知道图床正常加载了没
本文由看雪论坛 八岛 原创
转载请注明来自看雪社区
热门阅读
点击阅读原文/read,
更多干货等着你~