赛尔号通信数据的逆向分析与还原(思路篇)

赛尔号通信数据的逆向分析与还原(思路篇)

0x01 前言

flash游戏即将淘汰,被称为最强的as加密方式Alchemy被应用于一些主流的页游,很多游戏都转战h5或者手游端。今天闲来没事,就上手一款童年游戏。“赛尔号”,应该是全网首发吧

0x02 准备工具

FFDEC(强大的免费开源swf反编译工具) 、 Chrome浏览器 、 在线Hex编辑

0x03分析过程

使用 浏览器加载游戏并 通过 开发者工具抓取到数据包
可以发现一个dll.xml的配置文件
Dll信息
此游戏的习惯将核心数据存放于TaomeeLibraryDLL.swf文件当中。
此时直接将swf文件拖入FFDec会发现,读取失败。
读取失败
需要拖入Hex编辑器搞一下
CWS
CWS 是标识码 具体可以参考 Swf文件头信息,前面则是一些无关紧要的数据。
可以看到在Client.swf里对于swf文件的加载是这样处理的

private function onComplete(e:Event) : void
		{
			var info:DLLInfo = this._dllList[0];
			var byteArray:ByteArray = new ByteArray();
			if(this._isRelease)
			{
				this._stream.readBytes(new ByteArray(),0,7);//相当于设置了位置 前面可以看出到,标识码前面有7个字节是无用的数据 >>  this._stream.potion=7
			}
			this._stream.readBytes(byteArray);//
			if(this._isRelease)
			{
				byteArray.uncompress();//再进行Zlib解压处理
			}
			this._stream.close();//关闭字节流
			//然后就加载
			this._loader.loadBytes(byteArray,new LoaderContext(false,ApplicationDomain.currentDomain));
		}

思路清晰,可以干代码,这里使用了易语言
啦啦啦

导出文件
是

可以,再将其导入FFdec看看效果。
完美加载
解压后,开始找与通讯的类。一般搜索flash.net.Socke即可
send(param1, param2) #4CAF50发送数据包的函数

public function send(param1:uint, param2:Array) : uint
		{
			var loc3_:* = null;
			var loc4_:* = null;
			if(this.connected)
			{
				loc3_ = this.pack(this.userID,param1,param2);
				loc3_.position = 0;
				loc4_ = MessageEncrypt.encrypt(loc3_);//这里就开始加密了
				writeBytes(loc4_);//整个数据丢给
				flush();//这里就是发送数据出去了
				this.sendDataError(param1);
				DebugTrace.show(">>Socket[" + this.ip + ":" + this.port.toString() + "][cmdID:" + param1 + "]",getCmdLabel(param1),"[data length:" + this._sendBodyLen + "]");
				//这里可以清晰的看出 param1是数据包的命令码,param2为数据的整体
				return this._result;
			}
			return 0;
		}
	public function pack(param1:uint, param2:uint, param3:Array) : ByteArray
		{
			var loc4_:ByteArray = new ByteArray();
			this.serializeBinary(loc4_,param3);//这里是将数组序列化
			this._sendBodyLen = loc4_.length;
			var loc5_:ByteArray = this.packHead(param1,param2,loc4_);
			var loc6_:ByteArray = new ByteArray();
			loc6_.writeBytes(loc5_);
			loc6_.writeBytes(loc4_);
			return loc6_;
		}	
private function packHead(param1:uint, param2:uint, param3:ByteArray) : ByteArray
		{
			var loc6_:* = 0;
			var loc7_:int = 0;
			var loc4_:ByteArray = new ByteArray();
			var loc5_:uint = param3.length + HEAD_LENGTH;
			loc4_.writeUnsignedInt(loc5_);//写入数据包的长度
			loc4_.writeUTFBytes(VERSION);//写入通信的协议号
			loc4_.writeUnsignedInt(param2);//写入CmdId
			loc4_.writeUnsignedInt(param1);//写入UserID
			if(param2 > 1000)
			{
				loc7_ = 0;
				while(loc7_ < param3.length)
				{
					loc6_ = uint(loc6_ ^ param3[loc7_] & 255);
					loc7_++;
				}
				this._result = MSerial(this._result,param3.length + HEAD_LENGTH,loc6_,param2);//计算序列号
				loc4_.writeInt(this._result);//写入序列号
			}
			else
			{
				loc4_.writeInt(0);
			}
			return loc4_;
		}
  • 这里有1个知识点,记一下
    public class SocketEncryptImpl extends Socket 这个类是继承了 Socket,所以在发送数据包的时候writeBytes(loc4_);和flush(); 其实是省略了 Socket.writeBytes和Socket.flush()
private static var NO_ENCRYPT_LEN:int = 4;
		public function MessageEncrypt()
		{
			super();
		}
		
		public static function encrypt(param1:ByteArray) : ByteArray
		{
			var loc2_:int = param1.readUnsignedInt() - NO_ENCRYPT_LEN;
			var loc3_:ByteArray = new ByteArray();
			loc3_.writeUnsignedInt(0);
			MEncrypt(param1,loc2_,loc3_);
			loc3_.position = 0;
			loc3_.writeUnsignedInt(loc3_.length);
			trace("outData.length:" + loc3_.length);
			loc3_.position = 0;
			return loc3_;
		}
		
		public static function decrypt(param1:ByteArray) : ByteArray
		{
			var loc2_:int = param1.readUnsignedInt() - NO_ENCRYPT_LEN;
			var loc3_:ByteArray = new ByteArray();
			loc3_.writeUnsignedInt(0);
			MDecrypt(param1,loc2_,loc3_);
			loc3_.position = 0;
			loc3_.writeUnsignedInt(loc3_.length);
			loc3_.position = 0;
			return loc3_;
		}

根据分析可得,MEncrypt(param1,loc2_,loc3_);
传入的参数是 原始数据param1 、加密的长度、加密后的数据loc3_
继续找 MEncrypt函数内容

public function MEncrypt(_arg_1:ByteArray, _arg_2:int, _arg_3:ByteArray):void
    {
        var _local_16:*;
        var _local_15:int;
        var _local_7:int;
        var _local_10:int;
        var _local_11:int;
        var _local_13:int;
        var _local_14:int;
        var _local_6:int;
        var _local_9:int;
        var _local_4:int = ESP;
        _local_15 = _local_4;
        _local_4 = (_local_4 - 16);
        ESP = (_local_4 & 0xFFFFFFF0);
//获取尺寸,可忽略
var _local_5:int = getDefinitionByName("org.taomee.net.SocketEncryptImpl").size;

        if (_local_5 == 51706)
        {
            ESP = (_local_4 & 0xFFFFFFF0);
            _local_6 = _arg_2;
            _local_4 = (_local_4 - 16);
            si32(_local_6, _local_4);
            ESP = _local_4;
            F_malloc();
            _local_4 = (_local_4 + 16);
            _local_7 = eax;
            ESP = (_local_4 & 0xFFFFFFF0);
            CModule.writeBytes(_local_7, _local_6, _arg_1);
            _local_9 = 0;
            si32(_local_9, (_local_15 - 4));
            ESP = (_local_4 & 0xFFFFFFF0);
            ESP = (_local_4 & 0xFFFFFFF0);
			//_local_10此时是取解密的密钥Key
            _local_10 = CModule.mallocString(getDefinitionByName("com.robot.core.net.SocketConnection").key);
            _local_11 = (_local_10 & 0xFFFFFFFC);
            var _local_12:* = li32(_local_11);
            var _temp_1:* = (_local_12 + -16843009);
            _local_12 = (_local_12 & 0x80808080);
            _local_12 = (_local_12 ^ 0x80808080);
            _local_5 = (_local_12 & _temp_1);
                
            outer_block:
            {

                if (_local_5 != 0)
                {
                    _local_13 = (_local_11 + 4);
                    while ((_local_14 = (_local_10 + _local_9)) < _local_13)
                    {
                        _local_5 = li8(_local_14);
                        if (_local_5 == 0) break outer_block;
                        _local_9 = (_local_9 + 1);
                    };
                };
                _local_13 = (_local_11 + 4);
                do 
                {
                    _local_14 = li32(_local_13);
                    _local_5 = (_local_14 + -16843009);
                    _local_12 = (_local_14 & 0x80808080);
                    _local_12 = (_local_12 ^ 0x80808080);
                    _local_5 = (_local_12 & _local_5);
                    if (_local_5 != 0)
                    {
                        _local_5 = (_local_14 & 0xFF);
                        if (_local_5 == 0)
                        {
                            _local_9 = (_local_13 - _local_10);
                            break;
                        };
                        _local_5 = li8(_local_13 + 1);
                        if (_local_5 == 0)
                        {
                            _local_5 = (1 - _local_10);
                            _local_9 = (_local_5 + _local_13);
                            break;
                        };
                        _local_5 = li8(_local_13 + 2);
                        if (_local_5 == 0)
                        {
                            _local_5 = (2 - _local_10);
                            _local_9 = (_local_5 + _local_13);
                            break;
                        };
                        _local_5 = li8(_local_13 + 3);
                        if (_local_5 == 0)
                        {
                            _local_5 = (3 - _local_10);
                            _local_9 = (_local_5 + _local_13);
                            break;
                        };
                    };
                    _local_13 = (_local_13 + 4);
                } while (true);
            
            }//outer_block
            _local_4 = (_local_4 - 32);
            _local_5 = (_local_15 - 4);
            si32(_local_5, (_local_4 + 16));
            si32(_local_9, (_local_4 + 12));
            si32(_local_10, (_local_4 + 8));
            si32(_local_6, (_local_4 + 4));
            si32(_local_7, _local_4);
            ESP = _local_4;
			
            F__Z15MEncrypt_x86_32PKhiS0_iPi();
            _local_4 = (_local_4 + 32);
            _local_6 = eax;
            if (_local_7 != 0)
            {
                _local_4 = (_local_4 - 16);
                si32(_local_7, _local_4);
                ESP = _local_4;
                F_idalloc();
                _local_4 = (_local_4 + 16);
            };
            _local_5 = li32(_local_15 - 4);
            ESP = (_local_4 & 0xFFFFFFF0);
            CModule.readBytes(_local_6, _local_5, _arg_3);
            if (_local_6 != 0)
            {
                _local_4 = (_local_4 - 16);
                si32(_local_6, _local_4);
                ESP = _local_4;
                F_idalloc();
                _local_4 = (_local_4 + 16);
            };
            if (_local_10 != 0)
            {
                _local_4 = (_local_4 - 16);
                si32(_local_10, _local_4);
                ESP = _local_4;
                F_idalloc();
                _local_4 = (_local_4 + 16);
            };
        };
        _local_4 = _local_15;
        ESP = _local_4;
        return (_local_16);
    }
	
	 public function F__Z15MEncrypt_x86_32PKhiS0_iPi():void
    {
        var _local_17:int;
        var _local_2:int;
        var _local_9:int;
        var _local_7:int;
        var _local_5:int;
        var _local_3:int;
        var _local_12:int;
        var _local_6:int;
        var _local_14:int;
        var _local_11:int;
        var _local_15:int;
        var _local_16:int;
        var _local_4:int;
        var _local_10:int;
        var _local_1:int = ESP;
        _local_17 = _local_1;
        _local_2 = li32(_local_17 + 4);
        _local_3 = (_local_2 + 1);
        _local_5 = li32(_local_17 + 16);
        si32(_local_3, _local_5);
        _local_1 = (_local_1 - 16);
        si32(_local_3, _local_1);
        ESP = _local_1;
        F_malloc();
        _local_7 = li32(_local_17 + 12);
        _local_9 = li32(_local_17 + 8);
        _local_11 = li32(_local_17);
        _local_1 = (_local_1 + 16);
        _local_12 = eax;
        _local_14 = _local_12;
        _local_15 = _local_2;
        _local_16 = 0;
        if (_local_2 >= 1)
        {
            do 
            {
                _local_6 = li8(_local_11);
                _local_4 = 0;
                _local_10 = _local_9;
                if (_local_16 != _local_7)
                {
                    _local_10 = (_local_9 + _local_16);
                    _local_4 = (_local_16 + 1);
                };
                var _local_8:* = li8(_local_10);
                _local_8 = (_local_8 ^ _local_6);
                si8(_local_8, _local_14);
                _local_14 = (_local_14 + 1);
                _local_11 = (_local_11 + 1);
                _local_15 = (_local_15 + -1);
                _local_16 = _local_4;
            } while (_local_15 != 0);
        };
        _local_14 = (_local_12 + _local_2);
        si8(0, _local_14);
        _local_8 = (_local_2 + -1);
        if (_local_8 >= 0)
        {
            _local_11 = (0 - _local_2);
            if (_local_11 <= -1)
            {
                _local_11 = -1;
            };
            _local_8 = (_local_2 + _local_11);
            _local_11 = (_local_8 + 1);
            do 
            {
                _local_8 = li8(_local_14);
                var _local_13:* = li8(_local_14 - 1);
                _local_13 = (_local_13 >>> 3);
                _local_8 = (_local_13 | _local_8);
                si8(_local_8, _local_14);
                _local_8 = li8(_local_14 - 1);
                _local_8 = (_local_8 << 5);
                si8(_local_8, (_local_14 - 1));
                _local_14 = (_local_14 + -1);
                _local_11 = (_local_11 + -1);
            } while (_local_11 != 0);
        };
        _local_8 = li8(_local_12);
        _local_8 = (_local_8 | 0x03);
        si8(_local_8, _local_12);
        _local_8 = (_local_2 % _local_7);
        _local_8 = (_local_9 + _local_8);
        _local_8 = li8(_local_8);
        _local_8 = (_local_8 * 13);
        _local_9 = (_local_8 % _local_3);
        if (_local_9 != 0)
        {
            _local_1 = (_local_1 - 16);
            si32(__ZZ15MEncrypt_x86_32PKhiS0_iPiE10temp_bytes, _local_1);
            _local_8 = (_local_12 + _local_9);
            si32(_local_8, (_local_1 + 4));
            _local_8 = (_local_3 - _local_9);
            si32(_local_8, (_local_1 + 8));
            ESP = _local_1;
            Fmemcpy();
            _local_1 = (_local_1 + 16);
            _local_1 = (_local_1 - 16);
            si32(_local_9, (_local_1 + 8));
            si32(_local_12, (_local_1 + 4));
            _local_8 = (__ZZ15MEncrypt_x86_32PKhiS0_iPiE10temp_bytes + _local_8);
            si32(_local_8, _local_1);
            ESP = _local_1;
            Fmemcpy();
            _local_1 = (_local_1 + 16);
            var _temp_1:* = li32(_local_5);
            _local_1 = (_local_1 - 16);
            si32(_temp_1, (_local_1 + 8));
            si32(__ZZ15MEncrypt_x86_32PKhiS0_iPiE10temp_bytes, (_local_1 + 4));
            si32(_local_12, _local_1);
            ESP = _local_1;
            Fmemcpy();
            _local_1 = (_local_1 + 16);
        };
        eax = _local_12;
        _local_1 = _local_17;
        ESP = _local_1;
    }

我们来看这个Key是如何获取的 com.robot.core.net.SocketConnectio 找到此类
RobotCoreDLL.swf此dll上面可以找出

public static function setEncryptKeyStringArr(param1:Array) : void
		{
			_encryptKeyStringArr = param1;
		}
public static function get key() : String
		{
			var loc2_:int = 0;
			var loc3_:* = null;
			var loc1_:String = "";
			if(_encryptKeyStringArr == null)
			{
				loc1_ = "!crAckmE4nOthIng:-)";
			}
			else
			{
				loc2_ = 0;
				while(loc2_ < _encryptKeyStringArr.length)
				{
					loc3_ = StringUtil.replace(_encryptKeyStringArr[loc2_],"*","");
					loc1_ = loc1_ + loc3_;
					loc2_++;
				}
			}
			return loc1_;
		}

这位程序员也是挺可爱的,默认的密钥为 crack me Nothing 😃
然后找出设置 setEncryptKeyStringArr 的地方,一般设置密钥如果不是固定的,则可能是服务器返回的数据。如登录的时候,返回密钥。所以我们可以找登录的函数

private static function onLogin(param1:SocketEvent) : void
		{
			if(MainManager.isNewUser)
			{
				StatManager.sendStat2014("_newtrans_","fOnlineSucc","");
			}
			SocketConnection.removeCmdListener(CommandID.LOGIN_IN,onLogin);
			EventManager.addEventListener(RobotEvent.CREATED_ACTOR,onCreatedActor);
			MainManager.setup(param1.data);
			var loc2_:ByteArray = param1.data as ByteArray;
			var loc3_:int = loc2_.readUnsignedInt();
			initKey(loc3_);
			MapConfig.setup();
			MapSeatPointConfig.setup();
			sendSystemInfo();
			SocketConnection.send(1022,86066824);
		}
		
private static function initKey(param1:int) : void
		{
			var loc2_:String = "c&o&m.--rob-ot.c--o-r-e.&n-et.S-oc-ke-t&C-on-n-e-c-t-i-on";
			var loc3_:* = "s*e*tE&&&n*c";
			loc3_ = loc3_ + "r*yp*t&&&&Ke*yS*tr*i&n&&g*Arr";
			loc2_ = StringUtil.replace(loc2_,"-","");
			loc2_ = StringUtil.replace(loc2_,"&","");
			loc3_ = StringUtil.replace(loc3_,"*","");
			loc3_ = StringUtil.replace(loc3_,"&","");
			param1 = param1 ^ MainManager.actorInfo.userID;
			var loc4_:String = MD5.hash(param1 + "");
			var loc5_:* = MainManager.actorInfo.userID + "";
			var loc6_:* = [];
			var loc7_:int = 0;
			while(loc7_ < 10)
			{
				loc6_[loc7_] = "*" + loc4_.charAt(loc7_) + "*";
				loc7_++;
			}
			getDefinitionByName(loc2_)[loc3_](loc6_);
		}
		

RobotAppDLL.swf此dll中,可以找到登陆包返回处理的函数。里面包含初始化密钥的处理方式,而且是只取前10位
当然直接搜索 setEncryptKeyStringArr 是无效的,因为是getDefinitionByName
综合以上就基本上完成数据的解密工作了。

我使用的是易语言来进行操作
seer

MDe

Key

_De

Key获取
Key处理Key获取

效果图

0x04 写在最后

tm的加解密方式是通用的,只是密钥不同。
如赛尔号的是从返回包截取的
摩尔庄园 “^FStx,wl6NquAVRF@f%6”
赛尔号2 “taomee_seer2_k_~#”
小花仙 “v#93ta0mee+fl0WErd0t2eatMee&#$+@n0”

从09年到现在,tm的游戏从序列号自加1到封包的加密,也是历经了一段历史了吧,如今各种技术层出不穷。写这篇文章也纯属爱好和对技术的热爱。以前期盼着每周五的更新,到现在也是对过去的一种致敬吧。

  • 4
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 10
    评论
以前分享过摩尔庄园、奥比岛等发包例子,后来官方更改了加密手段,这次分享个赛尔号的发送数据例子 切勿用于非法用途,本贴只用于技术交流与学习 赛尔号通信数据逆向分析还原(思路) 0x01 前言 flash游戏即将淘汰,被称为最强的as加密方式Alchemy被应用于一些主流的页游,很多游戏都转战h5或者手游端。今天闲来没事,就上手一款童年游戏。"赛尔号",应该是全网首发吧 0x02 准备工具 FFDEC(强大的免费开源swf反编译工具) 、 Chrome浏览器 、在线Hex编辑 0x03分析过程 使用 浏览器加载游戏并 通过 开发者工具抓取到数据包可以发现一个dll.xml的配置文件此游戏的习惯将核心数据存放于==TaomeeLibraryDLL.swf==文件当中。此时直接将swf文件拖入FFDec会发现,读取失败。需要拖入Hex编辑器搞一下==CWS== 是标识码 具体可以参考 Swf文件头信息,前面则是一些无关紧要的数据。可以看到在Client.swf里对于swf文件的加载是这样处理的 private function onComplete(e:Event) : void         {             var info:DLLInfo = this._dllList[0];             var byteArray:ByteArray = new ByteArray();             if(this._isRelease)             {                 this._stream.readBytes(new ByteArray(),0,7);//相当于设置了位置 前面可以看出到,标识码前面有7个字节是无用的数据 gt;gt;  this._stream.potion=7             }             this._stream.readBytes(byteArray);//             if(this._isRelease)             {                 byteArray.uncompress();//再进行Zlib解压处理             }             this._stream.close();//关闭字节流             //然后就加载             this._loader.loadBytes(byteArray,new LoaderContext(false,ApplicationDomain.currentDomain));         } 思路清晰,可以干代码,这里使用了易语言 导出文件 可以,再将其导入FFdec看看效果。解压后,开始找与通讯的类。一般搜索flash.net.Socke即可==send(param1, param2) #4CAF50==发送数据包的函数 public function send(param1:uint, param2:Array) : uint         {             var loc3_:* = null;             var loc4_:* = null;             if(this.connected)             {                 loc3_ = this.pack(this.userID,param1,param2);                 loc3_.position = 0;                 loc4_ = MessageEncrypt.encrypt(loc3_);//这里就开始加密了                 writeBytes(loc4_);//整个数据丢给                 flush();//这里就是发送数据出去了                 this.sendDataError(param1);                 DebugTrace.show("gt;gt;Socket[" + this.ip + ":" + this.port.toString() + "][cmdID:" + param1 + "]",getCmdLabel(param1),"[data length:" + this._sendBodyLen + "]");                 //这里可以清晰的看出 param1是数据包的命令码,param2为数据的整体                 retur
"macOS软件安全与逆向分析"是一本关于MAC操作系统软件安全和逆向分析的PDF书籍。本书主要介绍了MAC操作系统的安全性与保护机制,以及逆向分析的基础知识和工具。 首先,MAC操作系统以其高度的安全性而闻名。它采用了各种技术来保护用户的数据和隐私,如设备锁定和文件加密。这本书将详细介绍MAC操作系统的安全特性,并教授读者如何利用这些特性来保护他们的软件和数据。 其次,逆向分析是一种研究和分析软件的方法。它可以帮助人们理解软件的内部机制和算法,以及在必要时修改其行为。逆向分析在软件开发、安全性测试和漏洞修复等领域都有重要的应用。这本书将介绍逆向分析的基本概念和技术,并提供实用的指导,帮助读者掌握这一技能。 "macOS软件安全与逆向分析"的PDF版本为读者提供了便捷的阅读方式。读者可以通过电子设备随时随地访问并学习其中的内容。该书通过详细的案例和实例来解释概念,并提供丰富的图表和代码示例来帮助读者理解和应用所学知识。 总的来说,"macOS软件安全与逆向分析"是一本有关MAC操作系统软件安全和逆向分析的重要参考资料。它为读者提供了全面的知识和技能,帮助他们更好地理解和保护MAC软件,同时学习和应用逆向分析技术。无论是从事软件开发还是安全性测试的人员,都可以从这本书中获得实用的指导和技巧。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值