首页
社区
课程
招聘
[原创]win7自动扫雷
发表于: 2010-9-10 00:04 26598

[原创]win7自动扫雷

2010-9-10 00:04
26598

【文章标题】: win7自动扫雷
【作者声明】: 参考了看雪的一篇帖子http://bbs.pediy.com/showthread.php?t=40295,感谢该帖作者!

打开win7扫雷,感觉无从下手,来看雪搜索了一下,找到一篇帖子《Vista 的扫雷》,看完之后豁然开朗。该帖子分析得很清楚了,我就不多说了,直接开干了。
首先下断bp rand,直接就断下来了,执行到返回,发现是一个小函数,很多地方都会调用这个函数,而且会不停中断。

008DD7F3  /$  8BFF          mov     edi, edi  ;鼠标点到这一行的时候,OD会在代码窗口下方提示“本地调用来自。。。”
008DD7F5  |.  55            push    ebp
008DD7F6  |.  8BEC          mov     ebp, esp
008DD7F8  |.  FF15 B8168B00 call    dword ptr [<&msvcrt.rand>]       ; [rand
008DD7FE  |.  8B4D 0C       mov     ecx, dword ptr [ebp+C]
008DD801  |.  2B4D 08       sub     ecx, dword ptr [ebp+8]
008DD804  |.  99            cdq
008DD805  |.  41            inc     ecx
008DD806  |.  F7F9          idiv    ecx
008DD808  |.  8BC2          mov     eax, edx
008DD80A  |.  0345 08       add     eax, dword ptr [ebp+8]
008DD80D  |.  5D            pop     ebp
008DD80E  \.  C2 0800       retn    8

在每个调用该函数的call都下断,然后清除掉一直中断的一个call(rand的中断也要清除掉),然后回到游戏,点击鼠标发现完全不会中断下来,于是开始新游戏,点击一个格子,断下来了。

008D0163  |. /EB 31         jmp     short 008D0196
008D0165  |> |33FF          xor     edi, edi
008D0167  |. |EB 2D         jmp     short 008D0196
008D0169  |> |8B45 FC       /mov     eax, dword ptr [ebp-4]
008D016C  |. |8B00          |mov     eax, dword ptr [eax]
008D016E  |. |85C0          |test    eax, eax
008D0170  |. |76 2B         |jbe     short 008D019D
008D0172  |. |48            |dec     eax
008D0173  |. |50            |push    eax
008D0174  |. |6A 00         |push    0
008D0176  |. |E8 78D60000   |call    008DD7F3
008D017B  |. |8BD8          |mov     ebx, eax
008D017D  |. |8B45 FC       |mov     eax, dword ptr [ebp-4]
008D0180  |. |8B40 0C       |mov     eax, dword ptr [eax+C]
008D0183  |. |FF3498        |push    dword ptr [eax+ebx*4]
008D0186  |. |8BCF          |mov     ecx, edi
008D0188  |. |E8 C4580100   |call    008E5A51
008D018D  |. |8B4D FC       |mov     ecx, dword ptr [ebp-4]
008D0190  |. |53            |push    ebx
008D0191  |. |E8 08EB0000   |call    008DEC9E
008D0196  |> \8B07           mov     eax, dword ptr [edi]
008D0198  |.  3B46 04       |cmp     eax, dword ptr [esi+4]
008D019B  |.^ 75 CC         \jnz     short 008D0169

此处代码跟《Vista 的扫雷》完全一样,我就不献丑了,直接Ctrl+F9,回到这里:

008D0C7F  |.  A1 B4689300   mov     eax, dword ptr [9368B4]        ;这里就是我们要的了
008D0C84  |.  3848 18       cmp     byte ptr [eax+18], cl
008D0C87  |.  74 5C         je      short 008D0CE5
008D0C89  |.  51            push    ecx
008D0C8A  |.  51            push    ecx
008D0C8B  |.  51            push    ecx
008D0C8C  |.  E8 58F90000   call    008E05E9
008D0C91  |.  33C9          xor     ecx, ecx
008D0C93  |.  EB 50         jmp     short 008D0CE5
008D0C95  |>  394E 18       cmp     dword ptr [esi+18], ecx
008D0C98  |.  75 20         jnz     short 008D0CBA
008D0C9A  |.  57            push    edi
008D0C9B  |.  53            push    ebx
008D0C9C  |.  8BCE          mov     ecx, esi
008D0C9E  |.  E8 18F4FFFF   call    008D00BB
008D0CA3  |.  6A 00         push    0                        ;Ctrl+F9后出现在这里
008D0CA5  |.  57            push    edi
008D0CA6  |.  53            push    ebx
008D0CA7  |.  6A 00         push    0
008D0CA9  |.  57            push    edi
008D0CAA  |.  53            push    ebx
008D0CAB  |.  8BCE          mov     ecx, esi
008D0CAD  |.  E8 90FDFFFF   call    008D0A42

现在我们得到一个稳定的地址:minesweeper.exe+868B4,从这里可以知道某个格子有没有雷。公式是这样的:[[[[
minesweeper.exe+868B4]+10]+44]+c]+4*(x*高度+y),这个(x,y)的设定是假设扫雷的左上角是(0,0),往右x增加,往下y增加。

那么现在知道哪里有雷哪里没雷,想做手脚就很容易了,比如可以透视出哪里有雷,或者直接自动扫雷。《Vista 的扫雷》中是模拟鼠标的移动和点击来实现的,本文则是使用CE(Cheat Engine)注入代码,创建远程线程直接调用扫雷程序的call来扫雷,优点是不用计算客户区和格子的坐标,缺点是不稳定,容易使扫雷程序崩溃。。。

下面我们来找一下程序用来翻开格子的call,先在minesweeper.exe+868B4下硬件访问断点,发现一只断,只能清除掉,试试[
minesweeper.exe+868B4]+10,还是一直断,继续试[[
minesweeper.exe+868B4]+10]+44,不会断了,回到游戏,翻开一个格子,中断了。清除硬件断点,按Ctrl+F9四次之后来到这里,就是关键的call了:

008EA3F3  |> \8B0D 54749300 mov     ecx, dword ptr [937454]
008EA3F9  |.  85C9          test    ecx, ecx
008EA3FB  |.  0F84 5E030000 je      008EA75F
008EA401  |.  6A 00         push    0
008EA403  |.  FF75 10       push    dword ptr [ebp+10]
008EA406  |.  53            push    ebx
008EA407  |.  E8 635DFFFF   call    008E016F                ;刚才我们就在这个call里
008EA40C  |. /E9 4E030000   jmp     008EA75F

为什么要按四次呢?为什么判定这里就是关键call呢?因为我已经跟过了所以我知道。。。
现在马后炮分析一下,在刚才硬件断点中断的地方按一次Ctrl+F9,出来之后是一个switch的分支,在每个分支上下断点,回到游戏继续点格子,发现case 7是鼠标按下,case 14是鼠标弹起,我们不关心鼠标按下,所以只要分析case 14就好了。
看看case 14,发现这里除了用到minesweeper.exe+868B4之外,还有一个ESI的参数也参与了重要计算,追随一下ESI,发现在函数开头有一个Mov esi,ecx,于是继续Ctrl+F9,来到这里:

008E6835  /$  8BFF          mov     edi, edi
008E6837  |.  55            push    ebp
008E6838  |.  8BEC          mov     ebp, esp
008E683A  |.  53            push    ebx
008E683B  |.  8B5D 08       mov     ebx, dword ptr [ebp+8]
008E683E  |.  56            push    esi
008E683F  |.  8BF1          mov     esi, ecx
008E6841  |.  57            push    edi
008E6842  |.  33FF          xor     edi, edi
008E6844  |.  8973 04       mov     dword ptr [ebx+4], esi
008E6847  |.  39BE 90000000 cmp     dword ptr [esi+90], edi
008E684D  |.  76 17         jbe     short 008E6866
008E684F  |>  8B86 9C000000 /mov     eax, dword ptr [esi+9C]
008E6855  |.  8B0CB8        |mov     ecx, dword ptr [eax+edi*4]
008E6858  |.  8B01          |mov     eax, dword ptr [ecx]
008E685A  |.  53            |push    ebx
008E685B  |.  FF10          |call    dword ptr [eax]
008E685D  |.  47            |inc     edi                        ;出来的时候在这里
008E685E  |.  3BBE 90000000 |cmp     edi, dword ptr [esi+90]
008E6864  |.^ 72 E9         \jb      short 008E684F
008E6866  |>  5F            pop     edi
008E6867  |.  5E            pop     esi
008E6868  |.  5B            pop     ebx
008E6869  |.  5D            pop     ebp
008E686A  \.  C2 0400       retn    4

可以看出,call的时候ecx是这么来的:[[esi+9C]+edi*4],edi是计数的不管他,esi呢?又一个Mov esi,ecx,那么再一次Ctrl+F9,来到这里:

008E048B  |.  8B4D 08       mov     ecx, dword ptr [ebp+8]
008E048E  |.  8B01          mov     eax, dword ptr [ecx]
008E0490  |.  57            push    edi
008E0491  |.  FF50 08       call    dword ptr [eax+8]
008E0494  |>  838E 88000000>or      dword ptr [esi+88], FFFFFFFF        ;出来的时候在这里

看到一个mov     ecx, dword ptr [ebp+8],感觉好像是参数传进来的,要不就是个临时变量,我们来分析一下这个[ebp+8]怎么来的。再一次Ctrl+F9(就是关键call啦),下个断点,回到游戏,翻开一个格子,中断在这里:

008EA407  |.  E8 635DFFFF   call    008E016F

F7跟进去,一路只注意[ebp+8]的内存变化,发现在执行完这个call之后,[ebp+8]就被赋值了:

008E0470  |.  8BCE          mov     ecx, esi
008E0472  |.  0F94C0        sete    al
008E0475  |.  8847 0C       mov     byte ptr [edi+C], al
008E0478  |.  8D45 08       lea     eax, dword ptr [ebp+8]
008E047B  |.  50            push    eax
008E047C  |.  FFB6 88000000 push    dword ptr [esi+88]
008E0482  |.  E8 BAF40000   call    008EF941

那么在这个call下断,回到游戏,翻开一个格子,中断到这个call的时候,F7跟进去:

008EF941  /$  8BFF          mov     edi, edi
008EF943  |.  55            push    ebp
008EF944  |.  8BEC          mov     ebp, esp
008EF946  |.  56            push    esi
008EF947  |.  8BF1          mov     esi, ecx
008EF949  |.  FF76 08       push    dword ptr [esi+8]
008EF94C  |.  6A 00         push    0
008EF94E  |.  FF75 08       push    dword ptr [ebp+8]
008EF951  |.  E8 06F3FEFF   call    008DEC5C
008EF956  |.  85C0          test    eax, eax
008EF958  |.  7D 04         jge     short 008EF95E
008EF95A  |.  32C0          xor     al, al
008EF95C  |.  EB 0D         jmp     short 008EF96B
008EF95E  |>  8B4E 04       mov     ecx, dword ptr [esi+4]
008EF961  |.  8B0481        mov     eax, dword ptr [ecx+eax*4]
008EF964  |.  8B4D 0C       mov     ecx, dword ptr [ebp+C]
008EF967  |.  8901          mov     dword ptr [ecx], eax
008EF969  |.  B0 01         mov     al, 1
008EF96B  |>  5E            pop     esi
008EF96C  |.  5D            pop     ebp
008EF96D  \.  C2 0800       retn    8

啥也不说了,还是一个mov     esi, ecx,那这个函数就不看了,回到上层,找一下ECX吧。。。往上找了几行,就看到了:

008E0470  |.  8BCE          mov     ecx, esi

好,现在任务又变成找ESI了,往上找,正如你想到的,函数的开头有个mov     esi, ecx,绕的好晕啊,Ctrl+F9回上层找吧:

008EA3F3  |> \8B0D 54749300 mov     ecx, dword ptr [937454]
008EA3F9  |.  85C9          test    ecx, ecx
008EA3FB  |.  0F84 5E030000 je      008EA75F
008EA401  |.  6A 00         push    0
008EA403  |.  FF75 10       push    dword ptr [ebp+10]
008EA406  |.  53            push    ebx
008EA407  |.  E8 635DFFFF   call    008E016F

看到了,mov     ecx, dword ptr [937454],真不容易啊。。。ecx可以找到了,那么这个call的几个参数分别是干啥的呢?跟了几次,发现全是0。。。那么就当作全是push 0好了。

接下来分析ecx指向的结构,经过几次实验和观察,发现call内部使用到的地方也不多,而有意义的就这么几个:

[ecx+b8]:0表示鼠标按下,1表示鼠标弹起
[ebx+88],[ebx+10]:这两个地址的值都是格子的index=x*高度+y+1,因为这个index是从1开始计数的,所以后面有个+1。
[ebx+7c]:值为[[ecx+4]+4*(x*高度+y)],一个指针,指向的地方比较有意思,记录了翻开的格子上面显示的数字,也记录了没翻开的和有雷的格子,具体内容大家自己跟一下看就知道了。

大家可以试一下再Ctrl+F9,发现已经出了程序的领空了,来到USER32里面了,所以不能再往上找了,就在这里折腾就好了。

那么现在要这么做:
循环每个格子,遇到没有雷的格子,就mov     ecx, dword ptr [937454],然后设置一下几个重要的参数,调用call,继续循环。

用CE的Auto assemble,输入以下内容:

alloc(myscript,1024)
globalalloc(mydata,256)
define(adr1,minesweeper.exe+868B4)
define(adr2,minesweeper.exe+87454)
define(adrcall,minesweeper.exe+3016F)

define(height,mydata+0)
define(width,mydata+4)

label(end)
label(in_width)
label(in_height)

myscript:
//Call this code to execute the script from assembler
mov ecx, dword ptr [adr2]
test ecx, ecx
je end
mov edi, dword ptr [adr1]
mov edi,[edi+10]
mov ebx,[edi+8]
mov [height],ebx
mov ebx,[edi+c]
mov [width],ebx
mov edi,[edi+44]
mov edi,[edi+c]

mov ecx,[width]
in_width:
test ecx,ecx
je end
dec ecx
mov eax,[edi+ecx*4]
mov eax,[eax+c]

mov edx,[height]
in_height:
test edx,edx
je in_width
dec edx
xor ebx,ebx
mov bl,byte ptr [eax+edx]
cmp bl,0
jnz in_height
pushad
mov ebx, dword ptr [adr2]
inc esi
mov [ebx+b8],esi
mov eax,[height]
push edx
mul ecx
pop edx
add eax,edx
push eax
shl eax,2
mov edx,[ebx+4]
mov edx,[edx+eax]
mov [ebx+7c],edx
pop eax
inc eax
mov [ebx+88],eax
mov [ebx+10],eax
mov ecx,ebx
push 0
push 0
push 0
call adrcall
push 32
call Sleep
popad
jmp in_height

end:

ret

execute一下,CE会告诉你代码注入到什么位置了,记下来,然后开多一个Auto assemble的窗口,输入这个:

createthread(地址)

这个地址就是刚才代码注入的地址了,然后execute,启动远程线程,就开始刷刷刷的自动扫雷了。

脚本写的不好请大家不吝赐教!还有就是这样注入经常会导致程序崩溃,哪位高人知道的请指点一下,多谢!


[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

收藏
免费 7
支持
分享
最新回复 (12)
雪    币: 205
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
下面是我的实现,似乎没有进程CRASH的情况,这里只贴下代码
我BLOG里有更多说明,这里不直接贴了
使用CALL调用实现扫雷 ---- 原理
使用CALL调用实现扫雷 ---- 编码

void GameForm::OnBnClickedBtnAuto2()
{
        HMODULE hMine = GetModuleHandle(_T("Minesweeper.exe"));
        if(hMine==NULL)
        {
                AfxMessageBox(_T("无法找到模块mineswpeer"));
                return;
        }
        //模块句柄即为基址
        DWORD mineBaseAddr = (DWORD)hMine;
        const int RVA_top = 0x868B4;        //数据基址 RVA
        const int RVA_fun = 0x21418;        //扫雷CALL RVA
        const int RVA_fun2 = 0x26BCD;        //后续函数 RVA
        const int RVA_fun3 = 0x200BB;        //布雷函数 RVA

        m_VATOP = mineBaseAddr + (DWORD)RVA_top;
        m_VAFUN = mineBaseAddr + (DWORD)RVA_fun;
        m_VAFUN2 = mineBaseAddr + (DWORD)RVA_fun2;
        m_VAFUN3 = mineBaseAddr + (DWORD)RVA_fun3;

        HitBlock2(0,0);
}

void GameForm::HitBlock2(int arow, int acol)
{
        int va_top = m_VATOP;
        int va_fun = m_VAFUN;
        int va_fun2 = m_VAFUN2;
        int va_fun3 = m_VAFUN3;
        _asm
        {
                mov esi,va_top
                mov esi,[esi]
                mov eax,[esi+0x10]
                mov edi,arow                //行
                mov ebx,acol                //列
//                mov [eax+0x18],0                //edi+18=0,zf=1,第一次
                mov [esi+0xC5],1
                mov ecx,esi
                push edi
                push ebx
                xor bl,bl
                mov eax,va_fun
                call eax                                //第一次点击方块

                mov edi,0
                mov ebx,0
        /*        mov eax,[esi+0x10]*/
        /*        mov [eax+0x18],1*/
row_:
                push edi
column_:
                push ebx
                mov     eax, dword ptr [esi+0x10]
                mov     eax, dword ptr [eax+0x44]
                mov     eax, dword ptr [eax+0x0C]
                mov     eax, dword ptr [eax+ebx*4]
                mov     eax, dword ptr [eax+0x0C]
                xor                ecx, ecx
                cmp     byte ptr [edi+eax], cl      //  判断是不是雷
                jnz     end_                                                //  雷跳转到最后
                mov [esi+0xC5],1
                mov ecx,esi
                push edi
                push ebx
                xor bl,bl
                mov eax,va_fun
                call eax                                //调用点击方块CALL
                test eax,eax
                jg here_
                jmp end_
here_:
                push eax
                mov eax,va_fun2
                call eax                                //后续处理CALL
end_:
                pop ebx
                inc ebx
                mov eax,[esi+0x10]
                cmp ebx,[eax+0x0C]
                jb column_
                pop edi
                mov ebx,0
                inc edi
                cmp edi,[eax+0x08]
                jb row_
        }
}

汇编码也写的不好。
2010-9-24 18:10
0
雪    币: 182
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
mark一下, 慢慢看.
2011-3-1 00:50
0
雪    币: 437
活跃值: (110)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
4
慢慢看看
2011-3-1 10:49
0
雪    币: 206
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
我觉得可以HOOK他的布雷函数,记录下布雷数据就好了。
2011-3-1 16:10
0
雪    币: 365
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
写得挺好啊,先MARK一下,以后慢慢看!
2011-4-28 08:58
0
雪    币: 219
活跃值: (38)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
同上!  ~~
2011-7-15 17:01
0
雪    币: 22
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
牛牛牛牛牛牛牛牛牛牛牛牛牛牛牛牛牛牛牛牛牛牛牛牛牛牛牛牛牛牛牛牛牛牛牛牛牛牛牛牛牛牛牛牛牛牛牛牛........................................................................................................................................
2011-7-15 19:41
0
雪    币: 100
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
tjk
9
thanks for your sharing
2011-10-13 15:06
0
雪    币: 20
活跃值: (99)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
10
分析的不错,是好帖,值得深入学习!
2011-10-14 07:57
0
雪    币: 59
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
唉,我捣鼓好几天了,也没搞出个所以然
希望楼主帮忙看看是哪里出了问题
2012-5-13 23:09
0
雪    币: 59
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
我的是直接游戏 无法响应 了,
2012-5-13 23:11
0
雪    币: 327
活跃值: (30)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
13
2012-12-12 11:41
0
游客
登录 | 注册 方可回帖
返回
//