0x1 前言

​ 本文对摩尔庄园页游进行逆向与分析,解密并复现其核心加解密算法。本思路也适用于其他形式的加解密算法分析过程,尤其是对flascc技术(一种能够在flash中运行C++编译生成的字节码的技术)进行逆向分析。

0x2 所用工具

​ WireShark(数据包抓包)

​ ffdec(Flash反编译)

0x3 逆向分析过程

​ 首先,我们进入游戏,使用抓包工具抓得swf文件,尝试反编译,发现其加密。

​ 我们使用ffdec提供的“搜索内存中的SWF”功能,尝试直接从内存中dump出swf文件。

​ 这里有个小技巧。当下的浏览器为了追求稳定和渲染速度都采用一种技术,将多个页面分为多个进程,很难确定游戏页面到底在哪一个进程里面。我这里自己写了个单进程的小程序。

​ 这样就能轻松dump出我们想要的swf文件了。对文件大小排个序,剔除掉尺寸特别大的,然后逐个反编译。最终我们找到了核心加解密算法所在的文件。

​ 看看反编译的结果:

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
package com.fcc
{
import avm2.intrinsics.memory.li8;
import avm2.intrinsics.memory.si32;
import avm2.intrinsics.memory.si8;
import flash.utils.ByteArray;
import flash.utils.getDefinitionByName;
import sample.MEncrypt.CModule;
import sample.MEncrypt.ESP;
import sample.MEncrypt.F_malloc;
import sample.MEncrypt.eax;
import sample.MEncrypt__3A__5C_Development_5C_Crossbridge_5C_cygwin_5C_tmp_5C_cc4krDaY_2E_lto_2E_bc_3A_d3346e37_2D_9080_2D_43e6_2D_a632_2D_6710752e3a2f.F_idalloc;
import sample.MEncrypt__3A__5C_Development_5C_Crossbridge_5C_cygwin_5C_tmp_5C_cc4krDaY_2E_lto_2E_bc_3A_d3346e37_2D_9080_2D_43e6_2D_a632_2D_6710752e3a2f.L__2E_str5;

public function MDecrypt(param1:ByteArray, param2:int, param3:ByteArray) : void
{
var _loc6_:* = 0;
var _loc17_:* = 0;
var _loc16_:int = 0;
var _loc10_:* = 0;
var _loc13_:int = 0;
var _loc14_:int = 0;
var _loc12_:int = 0;
var _loc11_:* = 0;
var _loc9_:* = 0;
var _loc7_:* = 0;
var _loc5_:* = int(ESP);
_loc6_ = _loc5_;
ESP = _loc5_ & -16;
var _loc4_:* = int(getDefinitionByName("com.taomee.mole.net.ConnectionServerAgent").size);
if(_loc4_ == 5477)
{
ESP = _loc5_ & -16;
_loc17_ = param2;
_loc5_ = int(_loc5_ - 16);
si32(_loc17_,_loc5_);
ESP = _loc5_;
F_malloc();
_loc5_ = int(_loc5_ + 16);
_loc16_ = eax;
ESP = _loc5_ & -16;
CModule.writeBytes(_loc16_,_loc17_,param1);
_loc5_ = int(_loc5_ - 16);
_loc14_ = _loc17_ + -1;
si32(_loc14_,_loc5_);
ESP = _loc5_;
F_malloc();
_loc5_ = int(_loc5_ + 16);
_loc13_ = eax;
if(_loc14_ >= 1)
{
_loc12_ = _loc16_ + 1;
_loc11_ = int(_loc17_ + -1);
_loc10_ = li8(_loc16_);
_loc9_ = _loc13_;
do
{
_loc4_ = _loc10_ & 224;
var _loc8_:int = _loc4_ >>> 5;
_loc10_ = li8(_loc12_);
_loc4_ = _loc10_ << 3;
_loc4_ = _loc4_ | _loc8_;
si8(_loc4_,_loc9_);
_loc12_ = _loc12_ + 1;
_loc11_ = int(_loc11_ + -1);
_loc9_ = int(_loc9_ + 1);
}
while(_loc11_ != 0);

if(_loc14_ >= 1)
{
_loc12_ = _loc17_ + -1;
_loc11_ = _loc13_;
_loc7_ = 0;
do
{
_loc10_ = li8(_loc11_);
_loc9_ = 0;
_loc17_ = int(L__2E_str5);
if(_loc7_ != 21)
{
_loc17_ = int(L__2E_str5 + _loc7_);
_loc9_ = int(_loc7_ + 1);
}
_loc4_ = li8(_loc17_);
_loc4_ = _loc4_ ^ _loc10_;
si8(_loc4_,_loc11_);
_loc11_ = int(_loc11_ + 1);
_loc12_ = _loc12_ + -1;
_loc7_ = _loc9_;
}
while(_loc12_ != 0);

}
}
if(_loc16_ != 0)
{
_loc5_ = int(_loc5_ - 16);
si32(_loc16_,_loc5_);
ESP = _loc5_;
F_idalloc();
_loc5_ = int(_loc5_ + 16);
}
ESP = _loc5_ & -16;
CModule.readBytes(_loc13_,_loc14_,param3);
if(_loc13_ != 0)
{
_loc5_ = int(_loc5_ - 16);
si32(_loc13_,_loc5_);
ESP = _loc5_;
F_idalloc();
_loc5_ = int(_loc5_ + 16);
}
}
_loc5_ = _loc6_;
ESP = _loc5_;
return undefined;
}
}

​ 相信各位看了后也感觉这代码的可读性的确不佳,在此我对它进行简单的解读。

​ 首先介绍一下代码中所用到的一些函数:

函数签名 函数解释
CModule.readBytes(sourceAddress,count,target) 将源地址的数据写到as代码的变量中
CModule.writeBytes(targetAddress,count,source) 将as代码的变量的值写到目标地址中
si32/si16/si8(value,address) 将定长(8/16/32位)的值写到地址中
li32/li16/li8(address) 从地址中读取定长的数据(8/16/32位)

​ 因为Flash技术的过时,因此我之前并没有接触过ActionScript和Flascc技术,但是相信大家看到这里,应该敏感的发现,这类函数似乎是在模拟底层的内存读写。并且,我们还发现反编译代码中有“ESP”,“eax”这样的关键词,可以大胆的猜测,flascc技术应该是模拟了一个虚拟的运行环境,包含虚拟文件系统,标准的C函数等,这样的代码更接近于汇编代码。

​ 我们通过分析汇编代码的思路,直接分析这个反编译代码,发现其不过是先对每一个byte的8bit进行拆分的预处理,然后异或一个常量值(L__2E_str5),这里的关键就是找到这个常量值在哪。

​ 我们找到这个变量定义的地方:

​ 继续查找定义:

​ 发现其数据拷贝自DS2这个类,我们查看这个swf文件的二进制数据块:

​ 发现了我们想要的:

​ 查看其内容(计算数据偏移:208=0xD0):

​ 嗯,应该是这个没错了。我们直接写一个python脚本,抓取数据包测试我们的加解密函数。

​ 这里我们选择抓取喊话数据包,因为如果我们的解密算法正确,那么我们解密后的数据包中就能看到我们所发送的字符。

​ 抓到包了,丢进python脚本试试。

​ 成功了,看到了我发送的文本。

0x4 后记

​ 话说淘米这算法是真坑啊,当初以为是循环异或,下标应该是0,1,2…n,0,1,2…这样循环的,没想到nt开发者竟然是这样设计的:0,1,2…n,0,0,1,2…给我整了两个0,我都懵了。

​ 其实有了这算法,加上swf反编译后的那些代码,可以做一个真正的云玩家了,懂我意思吧。

0x5 附录

  1. 加解密脚本下载