很早就听说看雪论坛了,一直觉得这是一个很神秘的地方.是权威和技术的象征.所以迟迟不敢来.今天索性建了个号.但是发现转正需要注册码.于是就特别写出此文.希望能混个注册码~呵呵
扫雷程序的外挂貌似很多人都做过.但我相信肯定还是有很多和我一样的初学者还没有做出来或者正在努力中.所以我希望以下的文章能对大家有帮助.高手就自行飘过啦~文中肯定会有很多的不足的地方,错误也在所难免.因为我是第一次做这样的东西.以前只做过星际争霸的单机一键修改器.那个相对简单很多,因为直接用金山游侠搜索改变的数值就能找到对应地址.但是扫雷却不能这样.因为没有特定的数值拿给我们作搜索参考.
下面是外挂的效果图,我并没做秒杀挂.感觉很没意思.至少现在这样还能点几下过过瘾.哈哈~~
下面我就把我制作的全过程和心得给大家分享分享吧~~
首先我们需要了解一个API函数:
BOOL BitBlt(
HDC hdcDest, //指向目标设备环境的句柄
int nXDest, //指定目标矩形区域左上角的X轴逻辑坐标。
int nYDest, //指定目标矩形区域左上角的Y轴逻辑坐标。
int nWidth, //指定源和目标矩形区域的逻辑宽度。
int nHeight, //指定源和目标矩形区域的逻辑高度。
HDC hdcSrc, //指向源设备环境的句柄。
int nXSrc, //指定源矩形区域左上角的X轴逻辑坐标。
int nYSrc, //指定源矩形区域左上角的Y轴逻辑坐标。
DWORD dwRop //指定光栅操作代码。这些代码将定义源矩形区域的颜色数据,如何
//与目标矩形区域的颜色数据组合以完成最后的颜色。通常使用SRCCOPY
);
大家可能都知道,那些比较老或者是比较简单的游戏都是采用BitBlt函数利用双缓冲技术来画图的.双缓冲技术说白了就是先创建一张白纸,然后用BitBlt在白纸上做画.最后再用一次BitBlt把画了画的纸打到屏幕上.这样能有效避免屏幕的闪烁.所以我的思路就是拦截BitBlt函数.看它都做了什么.
了解了以上知识以后就可以开工了~
1.先用PEID查看程序是否有壳.结果显示:Microsoft Visual C++ 7.0 Method2 [Debug].没有加壳
2.用OD打开扫雷程序.程序停留在此处
01003E21 > $ 6A 70 PUSH 70
01003E23 . 68 90130001 PUSH winmine.01001390
01003E28 . E8 DF010000 CALL winmine.0100400C
01003E2D . 33DB XOR EBX,EBX
01003E2F . 53 PUSH EBX ; /pModule => NULL
01003E30 . 8B3D 8C100001 MOV EDI,DWORD PTR DS:[<&KERNEL32.GetModu>; |kernel32.GetModuleHandleA
01003E36 . FFD7 CALL EDI ; \GetModuleHandleA
(以下地址省略)
此时程序还没有运行起来,还没有进行画图.我们先来看看到底它调用了哪些API函数,在反汇编窗口点击右键-查看-所有模块间的调用.通过出现的调用信息发现它确实调用了Bitblt函数.真是太好啦!!和我们预想的一样.
按照我们的原计划拦截BitBlt.在"所有模块间的调用"窗口中找到BitBlt(方法是输入首字母)然后按F2下断点..F9运行程序.扫雷被运行了起来.可能你会觉得奇怪.按照我们的理论拦截了BitBlt函数后它就不该画出图呀.为什么能够运行呢.而且扫雷也被画了出来??先别急.现在用鼠标随便点击一个小方格.是不是断下了呢?既然我们的理论是双缓冲那附近肯定还有一个BliBlt函数.结果确实被我们找到了.另一个BitBlt函数就在下面一点点.
这里是第一个BitBlt
01002646 /$ 56 PUSH ESI
01002647 |. FF35 245B0001 PUSH DWORD PTR DS:[1005B24] ; /hWnd = NULL
0100264D |. FF15 2C110001 CALL DWORD PTR DS:[<&USER32.GetDC>] ; \GetDC
01002653 |. 8B4C24 0C MOV ECX,DWORD PTR SS:[ESP+C]
01002657 |. 68 2000CC00 PUSH 0CC0020 ; /ROP = SRCCOPY
0100265C |. 8BF0 MOV ESI,EAX ; |
0100265E |. 8B4424 0C MOV EAX,DWORD PTR SS:[ESP+C] ; |
01002662 |. 8BD1 MOV EDX,ECX ; |
01002664 |. 6A 00 PUSH 0 ; |YSrc = 0
01002666 |. C1E2 05 SHL EDX,5 ; |
01002669 |. 0FBE9402 4053>MOVSX EDX,BYTE PTR DS:[EDX+EAX+1005340] ; |
01002671 |. 6A 00 PUSH 0 ; |XSrc = 0
01002673 |. 83E2 1F AND EDX,1F ; |
01002676 |. FF3495 205A00>PUSH DWORD PTR DS:[EDX*4+1005A20] ; |hSrcDC
0100267D |. C1E1 04 SHL ECX,4 ; |
01002680 |. 6A 10 PUSH 10 ; |Height = 10 (16.)
01002682 |. 6A 10 PUSH 10 ; |Width = 10 (16.)
01002684 |. 83C1 27 ADD ECX,27 ; |
01002687 |. C1E0 04 SHL EAX,4 ; |
0100268A |. 51 PUSH ECX ; |YDest
0100268B |. 83E8 04 SUB EAX,4 ; |
0100268E |. 50 PUSH EAX ; |XDest
0100268F |. 56 PUSH ESI ; |hDestDC
01002690 |. FF15 5C100001 CALL DWORD PTR DS:[<&GDI32.BitBlt>] ; \BitBlt
01002696 |. 56 PUSH ESI ; /hDC
01002697 |. FF35 245B0001 PUSH DWORD PTR DS:[1005B24] ; |hWnd = NULL
0100269D |. FF15 28110001 CALL DWORD PTR DS:[<&USER32.ReleaseDC>] ; \ReleaseDC
通过大致分析代码发现这个BitBlt貌似并没做什么有意义的事情,先不管它.
往下面翻一点点就会看见另一个BitBlt
010026A7 /$ 55 PUSH EBP ; 保存EBP
010026A8 |. 8BEC MOV EBP,ESP ; 指向栈顶
010026AA |. 83EC 0C SUB ESP,0C ; 申请3个局部变量每个4字节
010026AD |. 33C0 XOR EAX,EAX ; 清零EAX=0
010026AF |. 40 INC EAX ; EAX+1=1
010026B0 |. 3905 38530001 CMP DWORD PTR DS:[1005338],EAX ; 将EAX与[1005338]处进行比较
010026B6 |. C745 F8 37000>MOV DWORD PTR SS:[EBP-8],37 ; 保存变量1=37
010026BD |. 8945 F4 MOV DWORD PTR SS:[EBP-C],EAX ; 保存变量2=EAX=1
010026C0 |. 7C 68 JL SHORT winmine.0100272A ; 如EAX<[1005338]则跳(结束)
010026C2 |. 53 PUSH EBX
010026C3 |. 56 PUSH ESI
010026C4 |. BB 60530001 MOV EBX,winmine.01005360 ; 将地址1005360中的数据传给EBX
010026C9 |> 33F6 /XOR ESI,ESI ; ESI=0
010026CB |. 46 |INC ESI ; ESI+1=1
010026CC |. 3935 34530001 |CMP DWORD PTR DS:[1005334],ESI ; 比较[1005334]和ESI的大小
010026D2 |. C745 FC 0C000>|MOV DWORD PTR SS:[EBP-4],0C ; 保存变量3=0C
010026D9 |. 7C 38 |JL SHORT winmine.01002713 ; 如果[1005334]<ESI则跳(否则画图)
010026DB |> 68 2000CC00 |/PUSH 0CC0020 ; /ROP = SRCCOPY
010026E0 |. 33C0 ||XOR EAX,EAX ; |
010026E2 |. 8A0433 ||MOV AL,BYTE PTR DS:[EBX+ESI] ; |
010026E5 |. 6A 00 ||PUSH 0 ; |YSrc = 0
010026E7 |. 6A 00 ||PUSH 0 ; |XSrc = 0
010026E9 |. 83E0 1F ||AND EAX,1F ; |
010026EC |. FF3485 205A00>||PUSH DWORD PTR DS:[EAX*4+1005A20] ; |hSrcDC
010026F3 |. 6A 10 ||PUSH 10 ; |Height = 10 (16.)
010026F5 |. 6A 10 ||PUSH 10 ; |Width = 10 (16.)
010026F7 |. FF75 F8 ||PUSH DWORD PTR SS:[EBP-8] ; |YDest
010026FA |. FF75 FC ||PUSH DWORD PTR SS:[EBP-4] ; |XDest
010026FD |. FF75 08 ||PUSH DWORD PTR SS:[EBP+8] ; |hDestDC
01002700 FF15 5C100001 ||CALL DWORD PTR DS:[<&GDI32.BitBlt>] ; \BitBlt
01002706 |. 8345 FC 10 ||ADD DWORD PTR SS:[EBP-4],10 ; 画完后变量3加10
0100270A |. 46 ||INC ESI ; ESI+1
0100270B |. 3B35 34530001 ||CMP ESI,DWORD PTR DS:[1005334] ; 比较ESI和[1005334]大小
01002711 |.^ 7E C8 |\JLE SHORT winmine.010026DB ; ESI<=[1005334]则跳(继续画)
01002713 |> FF45 F4 |INC DWORD PTR SS:[EBP-C] ; 第二个变量加1
01002716 |. 8B45 F4 |MOV EAX,DWORD PTR SS:[EBP-C] ; 把第二个变量送回EAX
01002719 |. 8345 F8 10 |ADD DWORD PTR SS:[EBP-8],10 ; 把第一个变量加10
0100271D |. 83C3 20 |ADD EBX,20 ; EBX+20
01002720 |. 3B05 38530001 |CMP EAX,DWORD PTR DS:[1005338] ; 把EAX与[1005338]比较
01002726 |.^ 7E A1 \JLE SHORT winmine.010026C9 ; EAX<=[1005338]则跳否则结束
01002728 |. 5E POP ESI
01002729 |. 5B POP EBX
0100272A |> C9 LEAVE
0100272B \. C2 0400 RETN 4
通过对这个BitBlt的分析发现里面大有文章.为什么这么说呢?打眼一看就发现里面有很多的判断分支.我已经将大致的注解标在了后面.现在我们一句一句的看.
010026A7 /$ 55 PUSH EBP ; 保存EBP
010026A8 |. 8BEC MOV EBP,ESP ; 指向栈顶
这两句就不说了.调用函数的常规格式
010026AA |. 83EC 0C SUB ESP,0C ; 申请3个局部变量每个4字节
这句注意了.程序申请了三个变量.我们依次命名为变量1变量2变量3每个4字节
010026AD |. 33C0 XOR EAX,EAX ; 清零EAX=0
010026AF |. 40 INC EAX ; EAX+1=1
不知道什么作用.暂时不管它
010026B0 |. 3905 38530001 CMP DWORD PTR DS:[1005338],EAX ; 将EAX与[1005338]处进行比较
进行比较,为后面的判断(10026C0)做准备.我们查一下1005338中的值是9.记下来.
010026B6 |. C745 F8 37000>MOV DWORD PTR SS:[EBP-8],37 ; 保存变量1=37
010026BD |. 8945 F4 MOV DWORD PTR SS:[EBP-C],EAX ; 保存变量2=EAX=1
这两句就进行了变量的赋值!变量1=37.变量2=1
010026C0 |. /7C 68 JL SHORT winmine.0100272A ; 如EAX<[1005338]则跳(结束)
这里就进行了上面说的判断.即如果EAX<9则跳转...通过查看跳转的地址我们发现那实际是结束地址.
010026C4 |. BB 60530001 MOV EBX,winmine.01005360 ; 将地址1005360中的数据传给EBX
我们可以查到1005360地址处的值是10.记下
010026C9 |> /33F6 /XOR ESI,ESI ; ESI=0
010026CB |. |46 |INC ESI ; ESI+1=1
我们可以看见010026C9地址处有个箭头表明是可以从其他地方跳转上来. 即(1002726)处的条件为真则跳上来.
010026CC |. 3935 34530001 |CMP DWORD PTR DS:[1005334],ESI ; 比较[1005334]和ESI的大小
010026D2 |. C745 FC 0C000>|MOV DWORD PTR SS:[EBP-4],0C ; 保存变量3=0C
通过查看发现1005334的值也是9..至此我们开始申请的3个变量已经全部都有值了
变量1=37
变量2=1
变量3=0C
010026D9 |. /7C 38 |JL SHORT winmine.01002713 ; 如果[1005334]<ESI则跳(否则画图)
如果ESI>9则调用BitBlt画图.否则跳到1002713处..我们可以看到1002713处是的代码作用是增加变量2的值.
01002706 |. 8345 FC 10 ||ADD DWORD PTR SS:[EBP-4],10 ; 画完后变量3加10
0100270A |. 46 ||INC ESI ; ESI+1
0100270B |. 3B35 34530001 ||CMP ESI,DWORD PTR DS:[1005334] ; 比较ESI和[1005334]大小
01002711 |.^ 7E C8 |\JLE SHORT winmine.010026DB ; ESI<=[1005334]则跳(继续画)
我们可以清楚的看到其实这是一个循环.为什么我这么肯定呢.因为在1002711处程序又把ESI和9进行了比较如果还是没有9大的话又继续画每画一次就让ESI+1直到它大于9为止
类似于一个for循环
for(ESI=1;ESI<=9;ESI++)
{
BitBlt(......);
}
我们现在分析一下BitBlt函数的参数
010026DB |> /68 2000CC00 |/PUSH 0CC0020 ; /ROP = SRCCOPY
010026E0 |. |33C0 ||XOR EAX,EAX ; |
010026E2 |. |8A0433 ||MOV AL,BYTE PTR DS:[EBX+ESI] ; |
010026E5 |. |6A 00 ||PUSH 0 ; |YSrc = 0
010026E7 |. |6A 00 ||PUSH 0 ; |XSrc = 0
010026E9 |. |83E0 1F ||AND EAX,1F ; |
010026EC |. |FF3485 205A00>||PUSH DWORD PTR DS:[EAX*4+1005A20] ; |hSrcDC
010026F3 |. |6A 10 ||PUSH 10 ; |Height = 10 (16.)
010026F5 |. |6A 10 ||PUSH 10 ; |Width = 10 (16.)
010026F7 |. |FF75 F8 ||PUSH DWORD PTR SS:[EBP-8] ; |YDest
010026FA |. |FF75 FC ||PUSH DWORD PTR SS:[EBP-4] ; |XDest
010026FD |. |FF75 08 ||PUSH DWORD PTR SS:[EBP+8] ; |hDestDC
01002700 |FF15 5C100001 ||CALL DWORD PTR DS:[<&GDI32.BitBlt>] ; \BitBlt
我们还原成伪代码
BOOL BitBlt(
HDC hdcDest=hDestDC, //指向目标设备环境的句柄
int nXDest=XDest=第3个变量, //指定目标矩形区域左上角的X轴逻辑坐标。
int nYDest=YDest=第1个变量, //指定目标矩形区域左上角的Y轴逻辑坐标。
int nWidth=Width = 10 , //指定源和目标矩形区域的逻辑宽度。
int nHeight=Height = 10 , //指定源和目标矩形区域的逻辑高度。
HDC hdcSrc=DS:[EAX*4+1005A20], //指向源设备环境的句柄。
int nXSrc=XSrc = 0, //指定源矩形区域左上角的X轴逻辑坐标。
int nYSrc=YSrc = 0, //指定源矩形区域左上角的Y轴逻辑坐标。
DWORD dwRop=SRCCOPY //指定光栅操作代码。这些代码将定义源矩形区域的颜色数据,如何
//与目标矩形区域的颜色数据组合以完成最后的颜色。通常使用SRCCOPY
);
到目前位置我们已经了解了下列信息
1.变量3是X坐标...变量1是Y坐标
2.[1005338]=9,[1005360]=10,[1005334]=9
3.一个for循环在管理BitBlt的执行
4.X坐标一次是增加的10
01002713 |> \FF45 F4 |INC DWORD PTR SS:[EBP-C] ; 第二个变量加1
01002716 |. 8B45 F4 |MOV EAX,DWORD PTR SS:[EBP-C] ; 把第二个变量送回EAX
01002719 |. 8345 F8 10 |ADD DWORD PTR SS:[EBP-8],10 ; 把第一个变量加10
0100271D |. 83C3 20 |ADD EBX,20 ; EBX+20
01002720 |. 3B05 38530001 |CMP EAX,DWORD PTR DS:[1005338] ; 把EAX与[1005338]比较
01002726 |.^ 7E A1 \JLE SHORT winmine.010026C9 ; EAX<=[1005338]则跳否则结束
for循环执行完后执行上面这几句代码
我们可以清楚的看到.这是在给Y加10..所以很容易得出结论原来是一个而重for循环.外层管理Y坐标的增加内层管理X坐标的增加.每次增加10.结束条件都是执行到了9次就结束.为什么是9次呢?原来是我们选定是初级.是9X9的格子....
好了.这个函数分析完了.下面我们该找找看雷区在内存中对应的位置了...
我们找到1005334查看附近内存中的数据发现
01005330 0A 00 00 00 09 00 00 00 09 00 00 00 00 00 00 00 ................
01005340 10 10 10 10 10 10 10 10 10 10 10 0F 0F 0F 0F 0F
01005350 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F
01005360 10 8F 8F 0F 0F 0F 0F 0F 0F 0F 10 0F 0F 0F 0F 0F 弿
01005370 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
01005390 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F
010053A0 10 0F 0F 0F 8F 0F 0F 0F 0F 0F 10 0F 0F 0F 0F 0F ?
010053B0 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F
010053C0 10 0F 0F 0F 0F 0F 8F 0F 0F 0F 10 0F 0F 0F 0F 0F ?
010053D0 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F
010053E0 10 8F 0F 8F 8F 0F 0F 0F 0F 0F 10 0F 0F 0F 0F 0F ?弿
010053F0 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F
01005400 10 0F 8F 0F 0F 0F 0F 0F 0F 0F 10 0F 0F 0F 0F 0F ?
01005410 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
01005430 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F
01005440 10 0F 0F 8F 0F 0F 0F 0F 0F 0F 10 0F 0F 0F 0F 0F ?
注意观察...你会发现这个内存块其实是用10围成的一个9*9的格子.(其实每行中间还多了一行全是OF的还不清楚这是为什么不过对我们分析没影响)
现在我们通过随便点击一个格子再来观察发现有一个数据变了.继续...经过多次实验发现这个内存区域就是我们苦苦寻找的雷区了....我们只用知道8F是雷0F是安全的就行啦~~~接下来就是工具的编写了.这个相对就很轻松了....
我用的是VS2008.MFC编写的...
其实只用几个函数就搞定了.最重要的还是上面的分析过程.
下面是需要用到的函数.很简单的.....依顺序给出...
FindWindow
GetWindowThreadProcessId
OpenProcess
ReadProcessMemory
好了.就写这么多了...累死我了..附件超过1M发不上来..晕...如果需要的朋友只有加QQ给你了......
由于我的水平很菜,加之时间仓促,如果有错误的话请原谅..我也只是希望能够给需要做扫雷外挂或者类似东西的朋友一点帮助.如果能够帮到你那真是我的荣幸...呵呵
来到看雪希望能多认识一些志同道合的朋友一起学习..我的QQ是188166520..
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)