EvilZone
Hacking and Security => Game Hacking, Modding & Discussing => : manulaiko December 05, 2014, 12:28:59 PM
-
Hi!!
I've made this thread in order to talk about DarkOrbit, everything related with reverse engineering, encryption...
So I'll start with the game client.
The flash file (http://test2.darkorbit.bigpoint.com/spacemap/main.swf) is packed with a simple xor starting at byte 0x71 and increasing 1 each loop, so the code to dexor it would look something like this:
public static void main(string[] args)
{
byte[] file = File.ReadAllBytes(args[0]);
uint start = (uint)(file[0] ^ 0x43) - 1;
for (int i = 0; i < file.Length; i++)
{
file[i] ^= (byte)++start;
}
File.WriteAllBytes(args[0], file);
}
I've made this tool to unpack it: https://mega.co.nz/#!3kJkUDRa!hTnEQkKvq2REH5MREAzl8SSAcxvoQFXnZC9gKsJCFLo
So, now we can look in the client's source, I've been lurking there and I realized that packets are encrypted with RC4 and a RSA Key, this is the RSA class:
ackage com.hurlant.crypto.rsa
{
import com.hurlant.math.BigInteger;
import com.hurlant.crypto.prng.Random;
import flash.utils.ByteArray;
import com.hurlant.util.Memory;
public class RSAKey extends Object
{
public function RSAKey(param1:BigInteger, param2:int, param3:BigInteger = null, param4:BigInteger = null, param5:BigInteger = null, param6:BigInteger = null, param7:BigInteger = null, param8:BigInteger = null)
{
super();
this.n = param1;
this.e = param2;
this.d = param3;
this.p = param4;
this.q = param5;
this.dmp1 = param6;
this.dmq1 = param7;
this.coeff = param8;
canEncrypt = !(n == null) && !(e == 0);
canDecrypt = (canEncrypt) && !(d == null);
}
protected static function bigRandom(param1:int, param2:Random) : BigInteger
{
var _loc3_:ByteArray = null;
var _loc4_:BigInteger = null;
if(param1 < 2)
{
return BigInteger.nbv(1);
}
_loc3_ = new ByteArray();
param2.nextBytes(_loc3_,param1 >> 3);
_loc3_.position = 0;
_loc4_ = new BigInteger(_loc3_);
_loc4_.primify(param1,1);
return _loc4_;
}
public static function parsePublicKey(param1:String, param2:String) : RSAKey
{
return new RSAKey(new BigInteger(param1,16),parseInt(param2,16));
}
public static function generate(param1:uint, param2:String) : RSAKey
{
var _loc3_:Random = null;
var _loc4_:uint = 0;
var _loc5_:RSAKey = null;
var _loc6_:BigInteger = null;
var _loc7_:BigInteger = null;
var _loc8_:BigInteger = null;
var _loc9_:BigInteger = null;
var _loc10_:BigInteger = null;
_loc3_ = new Random();
_loc4_ = param1 >> 1;
_loc5_ = new RSAKey(null,0,null);
_loc5_.e = parseInt(param2,16);
_loc6_ = new BigInteger(param2,16);
do
{
do
{
_loc5_.p = bigRandom(param1 - _loc4_,_loc3_);
}
while(!(_loc5_.p.subtract(BigInteger.ONE).gcd(_loc6_).compareTo(BigInteger.ONE) == 0 && (_loc5_.p.isProbablePrime(10))));
do
{
_loc5_.q = bigRandom(_loc4_,_loc3_);
}
while(!(_loc5_.q.subtract(BigInteger.ONE).gcd(_loc6_).compareTo(BigInteger.ONE) == 0 && (_loc5_.q.isProbablePrime(10))));
if(_loc5_.p.compareTo(_loc5_.q) <= 0)
{
_loc10_ = _loc5_.p;
_loc5_.p = _loc5_.q;
_loc5_.q = _loc10_;
}
_loc7_ = _loc5_.p.subtract(BigInteger.ONE);
_loc8_ = _loc5_.q.subtract(BigInteger.ONE);
_loc9_ = _loc7_.multiply(_loc8_);
}
while(_loc9_.gcd(_loc6_).compareTo(BigInteger.ONE) != 0);
_loc5_.n = _loc5_.p.multiply(_loc5_.q);
_loc5_.d = _loc6_.modInverse(_loc9_);
_loc5_.dmp1 = _loc5_.d.mod(_loc7_);
_loc5_.dmq1 = _loc5_.d.mod(_loc8_);
_loc5_.coeff = _loc5_.q.modInverse(_loc5_.p);
return _loc5_;
}
public static function parsePrivateKey(param1:String, param2:String, param3:String, param4:String = null, param5:String = null, param6:String = null, param7:String = null, param8:String = null) : RSAKey
{
if(param4 == null)
{
return new RSAKey(new BigInteger(param1,16),parseInt(param2,16),new BigInteger(param3,16));
}
return new RSAKey(new BigInteger(param1,16),parseInt(param2,16),new BigInteger(param3,16),new BigInteger(param4,16),new BigInteger(param5,16),new BigInteger(param6,16),new BigInteger(param7),new BigInteger(param8));
}
public function verify(param1:ByteArray, param2:ByteArray, param3:uint, param4:Function = null) : void
{
_decrypt(doPublic,param1,param2,param3,param4,1);
}
public function dump() : String
{
var _loc1_:String = null;
_loc1_ = "N=" + n.toString(16) + "\n" + "E=" + e.toString(16) + "\n";
if(canDecrypt)
{
_loc1_ = _loc1_ + ("D=" + d.toString(16) + "\n");
if(!(p == null) && !(q == null))
{
_loc1_ = _loc1_ + ("P=" + p.toString(16) + "\n");
_loc1_ = _loc1_ + ("Q=" + q.toString(16) + "\n");
_loc1_ = _loc1_ + ("DMP1=" + dmp1.toString(16) + "\n");
_loc1_ = _loc1_ + ("DMQ1=" + dmq1.toString(16) + "\n");
_loc1_ = _loc1_ + ("IQMP=" + coeff.toString(16) + "\n");
}
}
return _loc1_;
}
protected function doPrivate2(param1:BigInteger) : BigInteger
{
var _loc2_:BigInteger = null;
var _loc3_:BigInteger = null;
var _loc4_:BigInteger = null;
if(p == null && q == null)
{
return param1.modPow(d,n);
}
_loc2_ = param1.mod(p).modPow(dmp1,p);
_loc3_ = param1.mod(q).modPow(dmq1,q);
while(_loc2_.compareTo(_loc3_) < 0)
{
_loc2_ = _loc2_.add(p);
}
_loc4_ = _loc2_.subtract(_loc3_).multiply(coeff).mod(p).multiply(q).add(_loc3_);
return _loc4_;
}
public function decrypt(param1:ByteArray, param2:ByteArray, param3:uint, param4:Function = null) : void
{
_decrypt(doPrivate2,param1,param2,param3,param4,2);
}
private function _decrypt(param1:Function, param2:ByteArray, param3:ByteArray, param4:uint, param5:Function, param6:int) : void
{
var _loc7_:uint = 0;
var _loc8_:* = 0;
var _loc9_:BigInteger = null;
var _loc10_:BigInteger = null;
var _loc11_:ByteArray = null;
if(param5 == null)
{
param5 = pkcs1unpad;
}
if(param2.position >= param2.length)
{
param2.position = 0;
}
_loc7_ = getBlockSize();
_loc8_ = param2.position + param4;
while(param2.position < _loc8_)
{
_loc9_ = new BigInteger(param2,param4);
_loc10_ = param1(_loc9_);
_loc11_ = param5(_loc10_,_loc7_);
param3.writeBytes(_loc11_);
}
}
public var dmp1:BigInteger;
protected function doPublic(param1:BigInteger) : BigInteger
{
return param1.modPowInt(e,n);
}
protected var canDecrypt:Boolean;
public function dispose() : void
{
e = 0;
n.dispose();
n = null;
Memory.gc();
}
public var d:BigInteger;
public var e:int;
private function _encrypt(param1:Function, param2:ByteArray, param3:ByteArray, param4:uint, param5:Function, param6:int) : void
{
var _loc7_:uint = 0;
var _loc8_:* = 0;
var _loc9_:BigInteger = null;
var _loc10_:BigInteger = null;
if(param5 == null)
{
param5 = pkcs1pad;
}
if(param2.position >= param2.length)
{
param2.position = 0;
}
_loc7_ = getBlockSize();
_loc8_ = param2.position + param4;
while(param2.position < _loc8_)
{
_loc9_ = new BigInteger(param5(param2,_loc8_,_loc7_,param6),_loc7_);
_loc10_ = param1(_loc9_);
_loc10_.toArray(param3);
}
}
public var dmq1:BigInteger;
private function rawpad(param1:ByteArray, param2:int, param3:uint) : ByteArray
{
return param1;
}
public function encrypt(param1:ByteArray, param2:ByteArray, param3:uint, param4:Function = null) : void
{
_encrypt(doPublic,param1,param2,param3,param4,2);
}
private function pkcs1pad(param1:ByteArray, param2:int, param3:uint, param4:uint = 2) : ByteArray
{
var _loc5_:ByteArray = null;
var _loc6_:uint = 0;
var _loc7_:* = 0;
var _loc8_:Random = null;
var _loc9_:* = 0;
_loc5_ = new ByteArray();
_loc6_ = param1.position;
var param2:int = Math.min(param2,param1.length,_loc6_ + param3 - 11);
param1.position = param2;
_loc7_ = param2 - 1;
while(_loc7_ >= _loc6_ && param3 > 11)
{
_loc5_[--param3] = param1[_loc7_--];
}
_loc5_[--param3] = 0;
_loc8_ = new Random();
while(param3 > 2)
{
_loc9_ = 0;
while(_loc9_ == 0)
{
_loc9_ = param4 == 2?_loc8_.nextByte():255;
}
_loc5_[--param3] = _loc9_;
}
_loc5_[--param3] = param4;
var _loc12_:* = --param3;
_loc5_[_loc12_] = 0;
return _loc5_;
}
public var n:BigInteger;
public var p:BigInteger;
public var q:BigInteger;
private function pkcs1unpad(param1:BigInteger, param2:uint, param3:uint = 2) : ByteArray
{
var _loc4_:ByteArray = null;
var _loc5_:ByteArray = null;
var _loc6_:* = 0;
_loc4_ = param1.toByteArray();
_loc5_ = new ByteArray();
_loc6_ = 0;
while(_loc6_ < _loc4_.length && _loc4_[_loc6_] == 0)
{
_loc6_++;
}
if(!(_loc4_.length - _loc6_ == param2 - 1) || _loc4_[_loc6_] > 2)
{
trace("PKCS#1 unpad: i=" + _loc6_ + ", expected b[i]==[0,1,2], got b[i]=" + _loc4_[_loc6_].toString(16));
return null;
}
_loc6_++;
while(_loc4_[_loc6_] != 0)
{
if(++_loc6_ >= _loc4_.length)
{
trace("PKCS#1 unpad: i=" + _loc6_ + ", b[i-1]!=0 (=" + _loc4_[_loc6_ - 1].toString(16) + ")");
return null;
}
}
while(++_loc6_ < _loc4_.length)
{
_loc5_.writeByte(_loc4_[_loc6_]);
}
_loc5_.position = 0;
return _loc5_;
}
protected var canEncrypt:Boolean;
public function getBlockSize() : uint
{
return (n.bitLength() + 7) / 8;
}
public var coeff:BigInteger;
public function toString() : String
{
return "rsa";
}
public function sign(param1:ByteArray, param2:ByteArray, param3:uint, param4:Function = null) : void
{
_encrypt(doPrivate2,param1,param2,param3,param4,1);
}
protected function doPrivate(param1:BigInteger) : BigInteger
{
var _loc2_:BigInteger = null;
var _loc3_:BigInteger = null;
if(p == null || q == null)
{
return param1.modPow(d,n);
}
_loc2_ = param1.mod(p).modPow(dmp1,p);
_loc3_ = param1.mod(q).modPow(dmq1,q);
while(_loc2_.compareTo(_loc3_) < 0)
{
_loc2_ = _loc2_.add(p);
}
return _loc2_.subtract(_loc3_).multiply(coeff).mod(p).multiply(q).add(_loc3_);
}
}
}
But I'm lost, I can't go further u.u
-
Hi!!
I've been looking deeper and net.bigpoint.darkorbit.net.netty.CommandHandler is the class that encryptes and decryptes the data.
This is how it looks:
package net.bigpoint.darkorbit.net.netty
{
import flash.events.EventDispatcher;
import net.bigpoint.darkorbit.net.netty.commands.ModuleFactory;
import flash.utils.IDataInput;
import flash.utils.IDataOutput;
import flash.net.Socket;
import flash.utils.ByteArray;
import net.bigpoint.darkorbit.net.netty.utils.EncryptionFacade;
public class CommandHandler extends EventDispatcher
{
public function CommandHandler()
{
this._socketSessionCache = new SocketSessionCache();
this._messageByteArray = new ByteArray();
super();
this.initialize();
}
public static const EVENT_EXECUTE_COMMAND:String = "Event.ExecuteCommand";
private static const COMMAND_NOT_FOUND:String = "command not found for: ";
private static const HEADER_LENGTH:int = 2;
private static var _commandFactory:ModuleFactory = new ModuleFactory();
public static function retrieveMessage(param1:IDataInput) : ICommand
{
var _loc2_:uint = param1.readShort();
var _loc3_:ICommand = _commandFactory.createInstance(_loc2_);
_loc3_.read(param1);
return _loc3_;
}
public static function writeMessage(param1:ICommand, param2:IDataOutput) : void
{
param2.writeShort(param1.getId());
param1.write(param2);
}
private var _socketSessionCache:SocketSessionCache;
private var _socket:Socket;
private var _messageByteArray:ByteArray;
private var _encryptionFacade;
protected function initialize() : void
{
this._encryptionFacade = new EncryptionFacade();
}
public function reset() : void
{
this._encryptionFacade.reset();
}
public function readData() : void
{
this.parseData(this._socket);
}
private function parseData(param1:IDataInput) : void
{
var _loc2_:* = 0;
var _loc3_:ByteArray = null;
var _loc4_:ByteArray = null;
var _loc5_:ByteArray = null;
var _loc6_:ByteArray = null;
var _loc7_:uint = 0;
var _loc8_:ICommand = null;
do
{
_loc2_ = param1.bytesAvailable;
if(this._socketSessionCache.isReady())
{
if(_loc2_ >= this._socketSessionCache.getLength())
{
_loc3_ = new ByteArray();
this._socket.readBytes(_loc3_,0,this._socketSessionCache.getLength());
_loc4_ = this._encryptionFacade.decode(_loc3_);
_loc8_ = retrieveMessage(_loc4_);
dispatchEvent(new CommandHandlerEvent(EVENT_EXECUTE_COMMAND,_loc8_));
this._socketSessionCache.reset();
}
}
else if(_loc2_ > HEADER_LENGTH)
{
_loc5_ = new ByteArray();
this._socket.readBytes(_loc5_,0,2);
_loc6_ = this._encryptionFacade.decode(_loc5_);
_loc7_ = _loc6_.readUnsignedShort();
if(_loc7_ > 0)
{
this._socketSessionCache.setLength(_loc7_);
}
}
}
while(_loc2_ > HEADER_LENGTH && _loc2_ >= this._socketSessionCache.getLength());
}
public function sendMessage(param1:ICommand) : void
{
this._messageByteArray.clear();
writeMessage(param1,this._messageByteArray);
this._messageByteArray.position = 0;
this._messageByteArray.writeShort(this._messageByteArray.length - HEADER_LENGTH);
var _loc2_:ByteArray = this._encryptionFacade.encode(this._messageByteArray);
this._socket.writeBytes(_loc2_,0,_loc2_.length);
this._socket.flush();
}
public function setSocket(param1:Socket) : void
{
this._socket = param1;
}
public function setSecurityKey(param1:ByteArray) : void
{
this._encryptionFacade.setSecretKey(param1);
}
public function setEncryptionFacade(param1:Object) : void
{
this._encryptionFacade = param1;
}
public function injectObfuscation(param1:ByteArray, param2:uint, param3:Function) : void
{
this._encryptionFacade.injectObfuscation(param1,param2,param3);
}
}
}
The method "sendMessage" encrypts and sends the message through SocketManager:
public function sendMessage(param1:ICommand) : void
{
this._messageByteArray.clear();
writeMessage(param1,this._messageByteArray);
this._messageByteArray.position = 0;
this._messageByteArray.writeShort(this._messageByteArray.length - HEADER_LENGTH);
var _loc2_:ByteArray = this._encryptionFacade.encode(this._messageByteArray);
this._socket.writeBytes(_loc2_,0,_loc2_.length);
this._socket.flush();
}
It calls encryptionFacade to encode message's bytes, this is the method:
public function encode(param1:ByteArray) : ByteArray
{
if(this.activated)
{
this.currentEncodeAlgorithm.encrypt(param1);
}
var _loc2_:ByteArray = this.injection.encode(param1);
var _loc3_:ByteArray = new ByteArray();
_loc3_.writeBytes(_loc2_);
_loc3_.position = 0;
return _loc3_;
}
this.currentEncodeAlgorithm is an object of com.hurlant.crypto.prng.ARC4 and it looks like this:
package com.hurlant.crypto.prng
{
import com.hurlant.crypto.symmetric.IStreamCipher;
import flash.utils.ByteArray;
import com.hurlant.util.Memory;
public class ARC4 extends Object implements IPRNG, IStreamCipher
{
public function ARC4(param1:ByteArray = null)
{
i = 0;
j = 0;
super();
S = new ByteArray();
if(param1)
{
init(param1);
}
}
public function decrypt(param1:ByteArray) : void
{
encrypt(param1);
}
public function init(param1:ByteArray) : void
{
var _loc2_:* = 0;
var _loc3_:* = 0;
var _loc4_:* = 0;
_loc2_ = 0;
while(_loc2_ < 256)
{
S[_loc2_] = _loc2_;
_loc2_++;
}
_loc3_ = 0;
_loc2_ = 0;
while(_loc2_ < 256)
{
_loc3_ = _loc3_ + S[_loc2_] + param1[_loc2_ % param1.length] & 255;
_loc4_ = S[_loc2_];
S[_loc2_] = S[_loc3_];
S[_loc3_] = _loc4_;
_loc2_++;
}
this.i = 0;
this.j = 0;
}
private var S:ByteArray;
public function dispose() : void
{
var _loc1_:uint = 0;
_loc1_ = 0;
if(S != null)
{
_loc1_ = 0;
while(_loc1_ < S.length)
{
S[_loc1_] = Math.random() * 256;
_loc1_++;
}
S.length = 0;
S = null;
}
this.i = 0;
this.j = 0;
Memory.gc();
}
public function encrypt(param1:ByteArray) : void
{
var _loc2_:uint = 0;
_loc2_ = 0;
while(_loc2_ < param1.length)
{
param1[_loc2_++] = param1[_loc2_++] ^ next();
}
}
private var i:int = 0;
private var j:int = 0;
private const psize:uint = 256;
public function next() : uint
{
var _loc1_:* = 0;
i = i + 1 & 255;
j = j + S[i] & 255;
_loc1_ = S[i];
S[i] = S[j];
S[j] = _loc1_;
return S[_loc1_ + S[i] & 255];
}
public function getBlockSize() : uint
{
return 1;
}
public function getPoolSize() : uint
{
return psize;
}
public function toString() : String
{
return "rc4";
}
}
}
Once the packet is encrypted with ARC4 it's encrypted again with ObfuscationWrapper, it looks like this:
package net.bigpoint.darkorbit.net.netty.utils
{
import flash.events.EventDispatcher;
import flash.utils.ByteArray;
import flash.display.Loader;
import flash.display.LoaderInfo;
import flash.events.Event;
import flash.events.IOErrorEvent;
public class InjectedObfuscationWrapper extends EventDispatcher
{
public function InjectedObfuscationWrapper()
{
this.§_-zn§ = new ByteArray();
this.§_-EN§ = new ByteArray();
super();
}
public static const §_-i13§:uint = 4096;
public static const §_-x2f§:Array = [67,87,83,11,227,11,0,0,64,3,192,3,192,0,24,1,0,68,17,25,0,0,0,198,10,97,98,99,95,65,0];
public static const §_-1A§:Array = [10,19,1,0,0,0,100,105,100,73,68,0,64,0,0,0];
private var §_-zn§:ByteArray;
private var §_-EN§:ByteArray;
protected var §_-j1G§:Boolean;
protected var §_-A1E§:Object;
protected var §_-N24§:Function;
protected var §_-N1k§:ByteArray;
protected function §_-qz§() : void
{
var _loc3_:uint = 0;
this.§_-EN§ = new ByteArray();
var _loc1_:uint = §_-1A§.length;
var _loc2_:* = 0;
while(_loc2_ < _loc1_)
{
_loc3_ = §_-1A§[_loc2_];
this.§_-EN§.writeByte(_loc3_);
_loc2_++;
}
}
protected function §_-H37§() : void
{
var _loc3_:uint = 0;
this.§_-zn§ = new ByteArray();
var _loc1_:uint = §_-x2f§.length;
var _loc2_:* = 0;
while(_loc2_ < _loc1_)
{
_loc3_ = §_-x2f§[_loc2_];
this.§_-zn§.writeByte(_loc3_);
_loc2_++;
}
}
public function injectAndBuild(param1:ByteArray, param2:uint, param3:Function) : void
{
this.§_-N24§ = param3;
if(param2 > 0)
{
this.§_-F2O§(param2);
}
if(param1.length < §_-i13§)
{
this.§_-S37§(param1);
}
}
private function §_-F2O§(param1:uint) : void
{
var _loc2_:uint = param1 & 255;
var _loc3_:uint = (param1 & 65280) >> 8;
var _loc4_:uint = (param1 & 16711680) >> 16;
var _loc5_:uint = (param1 & 4.27819008E9) >> 24;
§_-x2f§[4] = _loc2_;
§_-x2f§[5] = _loc3_;
§_-x2f§[6] = _loc4_;
§_-x2f§[7] = _loc5_;
}
private function §_-S37§(param1:ByteArray) : void
{
this.§_-II§();
this.§_-N1k§ = new ByteArray();
this.§_-N1k§.writeBytes(this.§_-zn§);
this.§_-N1k§.writeBytes(param1);
this.§_-N1k§.writeBytes(this.§_-EN§);
this.§_-a3H§();
}
private function §_-II§() : void
{
this.§_-H37§();
this.§_-qz§();
}
protected function §_-a3H§() : void
{
var _loc1_:Loader = new Loader();
var _loc2_:LoaderInfo = _loc1_.contentLoaderInfo;
_loc2_.addEventListener(Event.COMPLETE,this.§_-Z1x§);
_loc2_.addEventListener(IOErrorEvent.IO_ERROR,this.§_-W3t§);
_loc1_.loadBytes(this.§_-N1k§);
}
private function §_-W3t§(param1:IOErrorEvent) : void
{
}
protected function §_-Z1x§(param1:Event = null) : void
{
var _loc2_:LoaderInfo = param1.target as LoaderInfo;
_loc2_.removeEventListener(Event.COMPLETE,this.§_-Z1x§);
_loc2_.removeEventListener(IOErrorEvent.IO_ERROR,this.§_-W3t§);
this.§_-A1E§ = _loc2_.content;
this.activate();
this.§_-N24§();
}
public function decode(param1:ByteArray) : ByteArray
{
if(this.§_-W3c§())
{
return this.§_-A1E§.decode(param1);
}
return param1;
}
public function encode(param1:ByteArray) : ByteArray
{
if(this.§_-j1G§)
{
return this.§_-A1E§.encode(param1);
}
return param1;
}
private function §_-r1Q§() : void
{
var _loc1_:ByteArray = new ByteArray();
_loc1_.writeByte(255);
_loc1_.writeByte(0);
var _loc2_:ByteArray = this.encode(_loc1_);
var _loc3_:ByteArray = this.decode(_loc2_);
}
public function activate() : void
{
this.§_-j1G§ = true;
}
public function deactivate() : void
{
this.§_-j1G§ = false;
}
public function §_-W3c§() : Boolean
{
return this.§_-j1G§;
}
}
}
Then it's encoded with Pandora's Box, but I couldn't find it yet, pff, 8 ecnryptions for 1 packet -.-