mirror of
https://github.com/sirjonasxx/G-Earth.git
synced 2024-11-30 04:00:50 +01:00
Add shockwave crypto
This commit is contained in:
parent
b1d5faef11
commit
e126048fe4
23
G-Earth/src/main/java/gearth/encoding/HexEncoding.java
Normal file
23
G-Earth/src/main/java/gearth/encoding/HexEncoding.java
Normal file
@ -0,0 +1,23 @@
|
||||
package gearth.encoding;
|
||||
|
||||
import org.bouncycastle.util.encoders.Hex;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public class HexEncoding {
|
||||
|
||||
public static byte[] toBytes(String s) {
|
||||
return Hex.decode(s);
|
||||
}
|
||||
|
||||
public static byte[] toHex(byte[] bytes, boolean upperCase) {
|
||||
String data = Hex.toHexString(bytes);
|
||||
|
||||
if (upperCase) {
|
||||
data = data.toUpperCase();
|
||||
}
|
||||
|
||||
return data.getBytes(StandardCharsets.ISO_8859_1);
|
||||
}
|
||||
|
||||
}
|
@ -24,7 +24,7 @@ public class HConnection {
|
||||
|
||||
private volatile ExtensionHandler extensionHandler = null;
|
||||
|
||||
private volatile Object[] trafficObservables = {new Observable<TrafficListener>(), new Observable<TrafficListener>(), new Observable<TrafficListener>()};
|
||||
private volatile Observable<TrafficListener>[] trafficObservables = new Observable[]{new Observable<TrafficListener>(), new Observable<TrafficListener>(), new Observable<TrafficListener>()};
|
||||
private volatile Observable<StateChangeListener> stateObservable = new Observable<>();
|
||||
private volatile Observable<Consumer<Boolean>> developerModeChangeObservable = new Observable<>();
|
||||
|
||||
@ -136,7 +136,7 @@ public class HConnection {
|
||||
return extensionHandler;
|
||||
}
|
||||
|
||||
public Object[] getTrafficObservables() {
|
||||
public Observable<TrafficListener>[] getTrafficObservables() {
|
||||
return trafficObservables;
|
||||
}
|
||||
|
||||
|
@ -88,7 +88,7 @@ public class ShockwaveProxy implements ProxyProvider, ConnectionInterceptorCallb
|
||||
final Semaphore abort = new Semaphore(0);
|
||||
|
||||
final ShockwavePacketOutgoingHandler outgoingHandler = new ShockwavePacketOutgoingHandler(server.getOutputStream(), hConnection.getExtensionHandler(), hConnection.getTrafficObservables());
|
||||
final ShockwavePacketIncomingHandler incomingHandler = new ShockwavePacketIncomingHandler(client.getOutputStream(), hConnection.getExtensionHandler(), hConnection.getTrafficObservables());
|
||||
final ShockwavePacketIncomingHandler incomingHandler = new ShockwavePacketIncomingHandler(client.getOutputStream(), hConnection.getExtensionHandler(), hConnection.getTrafficObservables(), outgoingHandler);
|
||||
|
||||
// TODO: Non hardcoded version "20". Not exactly sure yet how to deal with this for now.
|
||||
// Lets revisit when origins is more mature.
|
||||
|
@ -1,9 +1,11 @@
|
||||
package gearth.protocol.memory.habboclient.macOs;
|
||||
|
||||
import gearth.encoding.HexEncoding;
|
||||
import gearth.misc.Cacher;
|
||||
import gearth.protocol.HConnection;
|
||||
import gearth.protocol.HMessage;
|
||||
import gearth.protocol.memory.habboclient.HabboClient;
|
||||
import org.bouncycastle.util.encoders.Hex;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
|
||||
@ -44,7 +46,7 @@ public class MacOsHabboClient extends HabboClient {
|
||||
return new ArrayList<>();
|
||||
|
||||
for (String s : possibleResults)
|
||||
result.add(hexStringToByteArray(s));
|
||||
result.add(HexEncoding.toBytes(s));
|
||||
} catch (IOException | URISyntaxException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
@ -124,25 +126,11 @@ public class MacOsHabboClient extends HabboClient {
|
||||
ArrayList<String> possibleData = readPossibleBytes(false);
|
||||
|
||||
for (String possibleHexStr : possibleData) {
|
||||
result.add(hexStringToByteArray(possibleHexStr));
|
||||
result.add(HexEncoding.toBytes(possibleHexStr));
|
||||
}
|
||||
} catch (IOException | URISyntaxException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static byte[] hexStringToByteArray(String s) {
|
||||
int len = s.length();
|
||||
if (len % 2 == 1) {
|
||||
s = "0" + s;
|
||||
len += 1;
|
||||
}
|
||||
byte[] data = new byte[len / 2];
|
||||
for (int i = 0; i < len; i += 2) {
|
||||
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
|
||||
+ Character.digit(s.charAt(i+1), 16));
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package gearth.protocol.memory.habboclient.rust;
|
||||
|
||||
import gearth.encoding.HexEncoding;
|
||||
import gearth.protocol.HConnection;
|
||||
import gearth.protocol.memory.habboclient.HabboClient;
|
||||
|
||||
@ -48,18 +49,8 @@ public class RustHabboClient extends HabboClient {
|
||||
List<byte[]> ret = new ArrayList<>();
|
||||
|
||||
for (String possibleHexStr : possibleData)
|
||||
ret.add(hexStringToByteArray(possibleHexStr));
|
||||
ret.add(HexEncoding.toBytes(possibleHexStr));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static byte[] hexStringToByteArray(String s) {
|
||||
int len = s.length();
|
||||
byte[] data = new byte[len / 2];
|
||||
for (int i = 0; i < len; i += 2) {
|
||||
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
|
||||
+ Character.digit(s.charAt(i+1), 16));
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package gearth.protocol.memory.habboclient.windows;
|
||||
|
||||
import gearth.encoding.HexEncoding;
|
||||
import gearth.misc.Cacher;
|
||||
import gearth.protocol.HConnection;
|
||||
import gearth.protocol.HMessage;
|
||||
@ -44,7 +45,7 @@ public class WindowsHabboClient extends HabboClient {
|
||||
return new ArrayList<>();
|
||||
|
||||
for (String s : possibleResults)
|
||||
result.add(hexStringToByteArray(s));
|
||||
result.add(HexEncoding.toBytes(s));
|
||||
} catch (IOException | URISyntaxException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
@ -126,21 +127,11 @@ public class WindowsHabboClient extends HabboClient {
|
||||
ArrayList<String> possibleData = readPossibleBytes(false);
|
||||
|
||||
for (String possibleHexStr : possibleData) {
|
||||
result.add(hexStringToByteArray(possibleHexStr));
|
||||
result.add(HexEncoding.toBytes(possibleHexStr));
|
||||
}
|
||||
} catch (IOException | URISyntaxException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static byte[] hexStringToByteArray(String s) {
|
||||
int len = s.length();
|
||||
byte[] data = new byte[len / 2];
|
||||
for (int i = 0; i < len; i += 2) {
|
||||
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
|
||||
+ Character.digit(s.charAt(i+1), 16));
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
@ -1,18 +1,27 @@
|
||||
package gearth.protocol.packethandler.shockwave;
|
||||
|
||||
import gearth.encoding.HexEncoding;
|
||||
import gearth.misc.listenerpattern.Observable;
|
||||
import gearth.protocol.HMessage;
|
||||
import gearth.protocol.HPacket;
|
||||
import gearth.protocol.TrafficListener;
|
||||
import gearth.protocol.packethandler.PacketHandler;
|
||||
import gearth.protocol.packethandler.shockwave.buffers.ShockwaveBuffer;
|
||||
import gearth.protocol.packethandler.shockwave.crypto.RC4Shockwave;
|
||||
import gearth.services.extension_handler.ExtensionHandler;
|
||||
import org.bouncycastle.util.encoders.Hex;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public abstract class ShockwavePacketHandler extends PacketHandler {
|
||||
|
||||
// The first 20 bytes of the artificialKey.
|
||||
public static final byte[] ARTIFICIAL_KEY = Hex.decode("14d288cdb0bc08c274809a7802962af98b41dec8");
|
||||
|
||||
protected static final Logger logger = LoggerFactory.getLogger(ShockwavePacketHandler.class);
|
||||
|
||||
private final HMessage.Direction direction;
|
||||
@ -21,19 +30,53 @@ public abstract class ShockwavePacketHandler extends PacketHandler {
|
||||
|
||||
protected final OutputStream outputStream;
|
||||
|
||||
ShockwavePacketHandler(HMessage.Direction direction, ShockwaveBuffer payloadBuffer, OutputStream outputStream, ExtensionHandler extensionHandler, Object[] trafficObservables) {
|
||||
private boolean isEncrypted;
|
||||
private final RC4Shockwave decryptCipher;
|
||||
private final RC4Shockwave encryptCipher;
|
||||
|
||||
ShockwavePacketHandler(HMessage.Direction direction, ShockwaveBuffer payloadBuffer, OutputStream outputStream, ExtensionHandler extensionHandler, Observable<TrafficListener>[] trafficObservables) {
|
||||
super(extensionHandler, trafficObservables);
|
||||
this.direction = direction;
|
||||
this.payloadBuffer = payloadBuffer;
|
||||
this.outputStream = outputStream;
|
||||
this.flushLock = new Object();
|
||||
this.isEncrypted = false;
|
||||
this.decryptCipher = new RC4Shockwave(0, ARTIFICIAL_KEY);
|
||||
this.encryptCipher = new RC4Shockwave(0, ARTIFICIAL_KEY);
|
||||
}
|
||||
|
||||
protected void setEncrypted() {
|
||||
isEncrypted = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sendToStream(byte[] buffer) {
|
||||
return sendToStream(buffer, isEncrypted);
|
||||
}
|
||||
|
||||
private boolean sendToStream(byte[] buffer, boolean isEncrypted) {
|
||||
synchronized (sendLock) {
|
||||
try {
|
||||
if (!isEncrypted) {
|
||||
outputStream.write(buffer);
|
||||
} else {
|
||||
outputStream.write(HexEncoding.toHex(encryptCipher.crypt(buffer), true));
|
||||
}
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
logger.error("Failed to send packet to stream", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void act(byte[] buffer) throws IOException {
|
||||
logger.info("Direction {} Received {} bytes", this.direction, buffer.length);
|
||||
|
||||
payloadBuffer.push(buffer);
|
||||
if (!isEncrypted) {
|
||||
payloadBuffer.push(buffer);
|
||||
} else {
|
||||
payloadBuffer.push(decryptCipher.crypt(Hex.decode(buffer)));
|
||||
}
|
||||
|
||||
flush();
|
||||
}
|
||||
@ -43,6 +86,8 @@ public abstract class ShockwavePacketHandler extends PacketHandler {
|
||||
final HPacket[] packets = payloadBuffer.receive();
|
||||
|
||||
for (final HPacket packet : packets){
|
||||
packet.setIdentifierDirection(direction);
|
||||
|
||||
final HMessage message = new HMessage(packet, direction, currentIndex);
|
||||
|
||||
awaitListeners(message, x -> sendToStream(x.getPacket().toBytes()));
|
||||
|
@ -1,28 +1,47 @@
|
||||
package gearth.protocol.packethandler.shockwave;
|
||||
|
||||
import gearth.misc.listenerpattern.Observable;
|
||||
import gearth.protocol.HMessage;
|
||||
import gearth.protocol.TrafficListener;
|
||||
import gearth.protocol.packethandler.ByteArrayUtils;
|
||||
import gearth.protocol.packethandler.shockwave.buffers.ShockwaveInBuffer;
|
||||
import gearth.protocol.packethandler.shockwave.packets.ShockPacket;
|
||||
import gearth.services.extension_handler.ExtensionHandler;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
public class ShockwavePacketIncomingHandler extends ShockwavePacketHandler {
|
||||
public ShockwavePacketIncomingHandler(OutputStream outputStream, ExtensionHandler extensionHandler, Object[] trafficObservables) {
|
||||
|
||||
private static final byte[] PACKET_END = new byte[] {0x01};
|
||||
private static final int ID_SECRET_KEY = 1;
|
||||
|
||||
public ShockwavePacketIncomingHandler(OutputStream outputStream, ExtensionHandler extensionHandler, Observable<TrafficListener>[] trafficObservables, ShockwavePacketHandler outgoingHandler) {
|
||||
super(HMessage.Direction.TOCLIENT, new ShockwaveInBuffer(), outputStream, extensionHandler, trafficObservables);
|
||||
|
||||
trafficObservables[0].addListener(new TrafficListener() {
|
||||
@Override
|
||||
public void onCapture(HMessage message) {
|
||||
if (!(message.getPacket() instanceof ShockPacket)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final ShockPacket packet = (ShockPacket) message.getPacket();
|
||||
|
||||
if (!packet.canSendToClient()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (packet.headerId() == ID_SECRET_KEY) {
|
||||
logger.info("Received SECRET_KEY from server, enabling encryption / decryption.");
|
||||
trafficObservables[0].removeListener(this);
|
||||
outgoingHandler.setEncrypted();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sendToStream(byte[] buffer) {
|
||||
synchronized (sendLock) {
|
||||
try {
|
||||
outputStream.write(buffer);
|
||||
outputStream.write(new byte[] {0x01});
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
logger.error("Error while sending packet to stream.", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public boolean sendToStream(byte[] packet) {
|
||||
return super.sendToStream(ByteArrayUtils.combineByteArrays(packet, PACKET_END));
|
||||
}
|
||||
}
|
||||
|
@ -1,31 +1,25 @@
|
||||
package gearth.protocol.packethandler.shockwave;
|
||||
|
||||
import gearth.encoding.Base64Encoding;
|
||||
import gearth.misc.listenerpattern.Observable;
|
||||
import gearth.protocol.HMessage;
|
||||
import gearth.protocol.TrafficListener;
|
||||
import gearth.protocol.packethandler.ByteArrayUtils;
|
||||
import gearth.protocol.packethandler.shockwave.buffers.ShockwaveOutBuffer;
|
||||
import gearth.services.extension_handler.ExtensionHandler;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
public class ShockwavePacketOutgoingHandler extends ShockwavePacketHandler {
|
||||
public ShockwavePacketOutgoingHandler(OutputStream outputStream, ExtensionHandler extensionHandler, Object[] trafficObservables) {
|
||||
public ShockwavePacketOutgoingHandler(OutputStream outputStream, ExtensionHandler extensionHandler, Observable<TrafficListener>[] trafficObservables) {
|
||||
super(HMessage.Direction.TOSERVER, new ShockwaveOutBuffer(), outputStream, extensionHandler, trafficObservables);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sendToStream(byte[] buffer) {
|
||||
synchronized (sendLock) {
|
||||
try {
|
||||
byte[] bufferLen = Base64Encoding.encode(buffer.length, 3);
|
||||
public boolean sendToStream(byte[] packet) {
|
||||
byte[] bufferLen = Base64Encoding.encode(packet.length, 3);
|
||||
byte[] buffer = ByteArrayUtils.combineByteArrays(bufferLen, packet);
|
||||
|
||||
outputStream.write(bufferLen);
|
||||
outputStream.write(buffer);
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
logger.error("Error while sending packet to stream.", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return super.sendToStream(buffer);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,68 @@
|
||||
package gearth.protocol.packethandler.shockwave.crypto;
|
||||
|
||||
/**
|
||||
* Habbo Shockwave RC4 is broken, meaning this is not a standard RC4 implementation.
|
||||
* Thanks to <a href="https://github.com/aromaa">Joni</a> and <a href="https://github.com/scottstamp">DarkStar851</a> for discovering this.
|
||||
*/
|
||||
public class RC4Shockwave {
|
||||
|
||||
private static final int TABLE_SIZE = 256;
|
||||
|
||||
private final int[] table;
|
||||
|
||||
private int x;
|
||||
private int y;
|
||||
|
||||
public RC4Shockwave(int key, byte[] artificialKey) {
|
||||
this.table = buildTable(key, artificialKey);
|
||||
}
|
||||
|
||||
public byte[] crypt(byte[] data) {
|
||||
byte[] result = new byte[data.length];
|
||||
|
||||
for (int i = 0; i < data.length; i++) {
|
||||
x = (x + 1) % TABLE_SIZE;
|
||||
y = (y + table[x] & 0xff) % TABLE_SIZE;
|
||||
|
||||
swap(table, x, y);
|
||||
|
||||
int xorIndex = ((table[x] & 0xff) + (table[y] & 0xff)) % TABLE_SIZE;
|
||||
|
||||
result[i] = (byte) (data[i] ^ table[xorIndex & 0xff]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static int[] buildTable(int key, byte[] artificialKey) {
|
||||
byte[] modKey = new byte[20];
|
||||
|
||||
for (int i = 0, j = 0; i < modKey.length; i++, j++) {
|
||||
if (j >= artificialKey.length) {
|
||||
j = 0;
|
||||
}
|
||||
|
||||
modKey[i] = (byte) (key & modKey[j]);
|
||||
}
|
||||
|
||||
int[] table = new int[TABLE_SIZE];
|
||||
|
||||
for (int i = 0; i < TABLE_SIZE; i++) {
|
||||
table[i] = (byte) i;
|
||||
}
|
||||
|
||||
for (int q = 0, j = 0; q < TABLE_SIZE; q++) {
|
||||
j = (j + (table[q] & 0xff) + modKey[q % modKey.length]) % TABLE_SIZE;
|
||||
|
||||
swap(table, q, j);
|
||||
}
|
||||
|
||||
return table;
|
||||
}
|
||||
|
||||
private static void swap(int[] table, int i, int j) {
|
||||
int temp = table[i];
|
||||
table[i] = table[j];
|
||||
table[j] = temp;
|
||||
}
|
||||
}
|
@ -36,17 +36,16 @@ public class AdminService {
|
||||
}
|
||||
|
||||
public void onMessage(HMessage message) {
|
||||
if (!enabled) return;
|
||||
HPacket packet = message.getPacket();
|
||||
if (message.getDestination() == HMessage.Direction.TOCLIENT
|
||||
&& (originalPacket == null || packet.headerId() == originalPacket.headerId())
|
||||
&& packet.length() == 11 && (packet.readByte(14) == 0 || packet.readByte(14) == 1)) {
|
||||
originalPacket = new HPacket(packet);
|
||||
|
||||
if (enabled) {
|
||||
packet.replaceInt(6, 7);
|
||||
packet.replaceInt(10, 7);
|
||||
packet.replaceBoolean(14, true);
|
||||
}
|
||||
packet.replaceInt(6, 7);
|
||||
packet.replaceInt(10, 7);
|
||||
packet.replaceBoolean(14, true);
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user