用远程注入实现9屏winxp扫雷流畅 bad apple

终极目标和思考过程

终极目标:在不使用dll注入的情况下,使用python实现 9 连屏(3x3)的 流畅 badapple 播放。
思考:由于 windows 消息队列的处理能力有限,即便使用了前后两帧异或信息来减少点击量,使用 PostMessage 的时候也依然会卡,这就需要手动写帧补偿。而且当画面急剧变化时,4屏或9屏卡顿时间长达一两秒,这是不可接受的。所以就想绕过消息队列,直接给扫雷进程注入shellcode,调用它的点击右键时的响应函数。

以前有人做过winxp扫雷外挂并开源在github,使用这个代码作为辅助,进行了对扫雷的逆向工程。

注:本程序使用 python 实现,目标是不注入dll(因为注入dll甚至可以把帧写死在代码里,那就没意思了),也不使用多进程/多线程(因为 python 调用 windows API 的速度完全够用,这种项目里面代码不仅要高效,更要有艺术性)

试图通过直接更改内存来切换显示模式

github代码中有内存地址,所以就使用了 windows api 的 WriteProcessMemory 函数,结果搞崩了内存,后来在 ollydbg 中逆向才发现他是先改旗子后写内存的,也就是说这么做没用,不仅如此,(踩坑记录:)还阴差阳错地覆盖了内存中注册表存取函数控制的区域,导致注册表值异常,最后让扫雷在启动读取注册表的时候崩掉了,进不去UI了,删掉注册表才恢复正常。

动态注入 shellcode 手动触发右键点击旗子时扫雷自己调用的函数

通过OD逆向,发现,扫雷程序在接收到右键点击的时候,会触发地址为 0x0100374F 的函数。函数内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
0100374F  /$  53            PUSH EBX                       ; Winmine__XP.0100374F(推测 Arg1,Arg2)
01003750 |. 8B5C24 08 MOV EBX,DWORD PTR SS:[ARG.1]
01003754 |. 85DB TEST EBX,EBX
01003756 |. 0F8E 81000000 JLE 010037DD
0100375C |. 8B4424 0C MOV EAX,DWORD PTR SS:[ARG.2]
01003760 |. 85C0 TEST EAX,EAX
01003762 |. 7E 79 JLE SHORT 010037DD
01003764 |. 3B1D 34530001 CMP EBX,DWORD PTR DS:[1005334]
0100376A |. 7F 71 JG SHORT 010037DD
0100376C |. 3B05 38530001 CMP EAX,DWORD PTR DS:[1005338]
01003772 |. 7F 69 JG SHORT 010037DD
01003774 |. C1E0 05 SHL EAX,5
01003777 |. 56 PUSH ESI
01003778 |. 8DB418 405300 LEA ESI,[EBX+EAX+1005340]
0100377F |. 8A06 MOV AL,BYTE PTR DS:[ESI]
01003781 |. A8 40 TEST AL,40
01003783 |. 75 57 JNZ SHORT 010037DC
01003785 |. 24 1F AND AL,1F
01003787 |. 3C 0E CMP AL,0E
01003789 |. 57 PUSH EDI
0100378A |. 75 15 JNE SHORT 010037A1
0100378C |. 33C0 XOR EAX,EAX
0100378E |. 3905 BC560001 CMP DWORD PTR DS:[10056BC],EAX
01003794 |. 6A 01 PUSH 1
01003796 |. 0F94C0 SETE AL
01003799 |. 8D4400 0D LEA EAX,[EAX+EAX+0D]
0100379D |. 8BF8 MOV EDI,EAX
0100379F |. EB 0E JMP SHORT 010037AF
010037A1 |> 3C 0D CMP AL,0D
010037A3 |. 75 05 JNE SHORT 010037AA
010037A5 |. 6A 0F PUSH 0F
010037A7 |. 5F POP EDI
010037A8 |. EB 0A JMP SHORT 010037B4
010037AA |> 6A 0E PUSH 0E
010037AC |. 5F POP EDI
010037AD |. 6A FF PUSH -1
010037AF |> E8 B6FCFFFF CALL 0100346A ; \Winmine__XP.0100346A
010037B4 |> 57 PUSH EDI ; /Arg3
010037B5 |. FF7424 18 PUSH DWORD PTR SS:[ARG.2] ; |Arg2 => [ARG.2]
010037B9 |. 53 PUSH EBX ; |Arg1
010037BA |. E8 ECF6FFFF CALL 01002EAB ; \Winmine__XP.01002EAB
010037BF |. 8A06 MOV AL,BYTE PTR DS:[ESI]
010037C1 |. 24 1F AND AL,1F
010037C3 |. 3C 0E CMP AL,0E
010037C5 |. 5F POP EDI
010037C6 |. 75 14 JNE SHORT 010037DC
010037C8 |. A1 A4570001 MOV EAX,DWORD PTR DS:[10057A4]
010037CD |. 3B05 A0570001 CMP EAX,DWORD PTR DS:[10057A0]
010037D3 |. 75 07 JNE SHORT 010037DC
010037D5 |. 6A 01 PUSH 1 ; /Arg1 = 1
010037D7 |. E8 A0FCFFFF CALL 0100347C ; \Winmine__XP.0100347C
010037DC |> 5E POP ESI
010037DD |> 5B POP EBX
010037DE \. C2 0800 RETN 8

找到这个函数的方式,是给内存下断点。前文找到了雷区的内存地址,在 OD 里面很容易能看出来雷区的样子:

地址 data1 data2 data3 data4 ASCII (OEM - 多语言拉丁语1>
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 0F 0F 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 0E 0F 0F 0F 0F 0F 8F 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 8F 0F 0F 0F 0F 8F 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 0F 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 0F 0F 0F 0F 8F 0F 8F 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 0F 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 8F 0F 0F 8F 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 8F 8F 0F 0F 0F 0F 0F 0F 10 0F 0F 0F 0F 0F ÅÅ
01005450 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 
01005460 10 0F 0F 0F 0F 0F 0F 0F 8F 0F 10 0F 0F 0F 0F 0F Å
01005470 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 
01005480 10 10 10 10 10 10 10 10 10 10 10 0F 0F 0F 0F 0F 

其中8F是雷,以10开头的行,后面会记录这一行的方格信息,以10结尾。0F是空填充,以0F开头的行没用。
不断地右击插旗可以发现,旗子对应的记录值是0E。而且很容易就能找到某个格子对应的内存的位置。因此找一个格子,对它的内存地址下一个写入断点,会发现暂停在了上面的函数的第17行:

0100377F  |.  8A06          MOV AL,BYTE PTR DS:[ESI]

这明显是个赋值指令。它所属的函数正如上文所言,地址是0x0100374F。直接在这个地址下断点,然后在外面触发右击事件,果然断在了这里。说明我们的猜想是正确的。BTW,左键单击则不会触发,它触发的是0x01003512处的函数。
查看CS寄存器,得到这段代码的段地址0x0023。所以在调用绝对地址跳转的时候,地址的构造是0x00230100374f,然后使用小端序写入机器码,即 413700012300。
那么接下来就开始向程序注入shellcode。

1
2
3
4
push {row}
push {col}
call far 0x00230100374f
不断重复

至于手动构造机器码,则是另外一个故事了,此处不必多说。上面的过程无论是注入DLL还是远程调用,都是用得上的。那么接下来的过程只是用哪种艺术手法将其实现的问题。
如此这般就实现了远程注入。只要用python写一个合理的发送程序,实现定时播放、插帧补帧等细节,即可使用扫雷完美播放bad apple。
一个扫雷播放 bad apple 那只是一个扫雷播放 bad apple。
而9个扫雷同时播放 bad apple,它就不仅仅是9个扫雷同时播放 bad apple,
而是现代计算机理论、严肃活泼的黑客精神与小众亚文化的结合,是这个时代的缩影。


用远程注入实现9屏winxp扫雷流畅 bad apple
http://blog.yotubird.club/posts/2018/2b6a8b34.html
作者
nqr
发布于
2018年7月4日
许可协议