diff --git a/G-Earth/src/main/java/gearth/protocol/connection/proxy/shockwave/ShockwaveProxy.java b/G-Earth/src/main/java/gearth/protocol/connection/proxy/shockwave/ShockwaveProxy.java
index ca3e7f2..611f71e 100644
--- a/G-Earth/src/main/java/gearth/protocol/connection/proxy/shockwave/ShockwaveProxy.java
+++ b/G-Earth/src/main/java/gearth/protocol/connection/proxy/shockwave/ShockwaveProxy.java
@@ -9,6 +9,7 @@ import gearth.protocol.connection.HStateSetter;
import gearth.protocol.connection.proxy.ProxyProvider;
import gearth.protocol.interceptor.ConnectionInterceptor;
import gearth.protocol.interceptor.ConnectionInterceptorCallbacks;
+import gearth.protocol.memory.Rc4Obtainer;
import gearth.protocol.packethandler.PacketHandler;
import gearth.protocol.packethandler.shockwave.ShockwavePacketIncomingHandler;
import gearth.protocol.packethandler.shockwave.ShockwavePacketOutgoingHandler;
@@ -92,9 +93,12 @@ public class ShockwaveProxy implements ProxyProvider, ConnectionInterceptorCallb
final ShockwavePacketOutgoingHandler outgoingHandler = new ShockwavePacketOutgoingHandler(server.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.
+ final Rc4Obtainer rc4Obtainer = new Rc4Obtainer(hConnection);
+
+ rc4Obtainer.setFlashPacketHandlers(outgoingHandler, incomingHandler);
+ // TODO: Non hardcoded version "24". Not exactly sure yet how to deal with this for now.
// Lets revisit when origins is more mature.
- proxy.verifyProxy(incomingHandler, outgoingHandler, "20", "SHOCKWAVE");
+ proxy.verifyProxy(incomingHandler, outgoingHandler, "24", "SHOCKWAVE");
hProxySetter.setProxy(proxy);
onConnect();
@@ -107,7 +111,6 @@ public class ShockwaveProxy implements ProxyProvider, ConnectionInterceptorCallb
try {
if (!server.isClosed()) server.close();
if (!client.isClosed()) client.close();
- if (HConnection.DEBUG) System.out.println("STOP");
onConnectEnd();
} catch (IOException e) {
logger.error("Error occurred while closing sockets.", e);
diff --git a/G-Earth/src/main/java/gearth/protocol/crypto/RC4.java b/G-Earth/src/main/java/gearth/protocol/crypto/RC4.java
index 4e51cbe..91a88ab 100644
--- a/G-Earth/src/main/java/gearth/protocol/crypto/RC4.java
+++ b/G-Earth/src/main/java/gearth/protocol/crypto/RC4.java
@@ -54,9 +54,9 @@ import java.util.Arrays;
*
* @author Clarence Ho
*/
-public class RC4 {
+public class RC4 implements RC4Cipher {
- private byte state[] = new byte[256];
+ private byte[] state = new byte[256];
private int x;
private int y;
@@ -79,9 +79,8 @@ public class RC4 {
* @param key the encryption/decryption key
*/
public RC4(byte[] key) throws NullPointerException {
-
- for (int i=0; i < 256; i++) {
- state[i] = (byte)i;
+ for (int i = 0; i < 256; i++) {
+ state[i] = (byte) i;
}
x = 0;
@@ -96,8 +95,7 @@ public class RC4 {
throw new NullPointerException();
}
- for (int i=0; i < 256; i++) {
-
+ for (int i = 0; i < 256; i++) {
index2 = ((key[index1] & 0xff) + (state[i] & 0xff) + index2) & 0xff;
tmp = state[i];
@@ -106,9 +104,6 @@ public class RC4 {
index1 = (index1 + 1) % key.length;
}
-
-
-
}
public RC4(byte[] state, int x, int y) {
@@ -117,51 +112,29 @@ public class RC4 {
this.state = state;
}
- //copyconstructor
- public RC4 deepCopy() {
- return new RC4(Arrays.copyOf(state, 256), x, y);
- }
-
/**
* RC4 encryption/decryption.
*
- * @param data the data to be encrypted/decrypted
+ * @param data the data to be encrypted/decrypted
* @return the result of the encryption/decryption
*/
- public byte[] rc4(String data) {
+ @Override
+ public byte[] cipher(byte[] data) {
+ return cipher(data, 0, data.length);
+ }
+
+ @Override
+ public byte[] cipher(byte[] data, int offset, int length) {
+ int xorIndex;
+ byte tmp;
if (data == null) {
return null;
}
- byte[] tmp = data.getBytes();
+ byte[] result = new byte[length];
- this.rc4(tmp);
-
- return tmp;
- }
-
- /**
- * RC4 encryption/decryption.
- *
- * @param buf the data to be encrypted/decrypted
- * @return the result of the encryption/decryption
- */
- public byte[] rc4(byte[] buf) {
-
- //int lx = this.x;
- //int ly = this.y;
-
- int xorIndex;
- byte tmp;
-
- if (buf == null) {
- return null;
- }
-
- byte[] result = new byte[buf.length];
-
- for (int i=0; i < buf.length; i++) {
+ for (int i = 0; i < length; i++) {
x = (x + 1) & 0xff;
y = ((state[x] & 0xff) + y) & 0xff;
@@ -170,26 +143,50 @@ public class RC4 {
state[x] = state[y];
state[y] = tmp;
- xorIndex = ((state[x] &0xff) + (state[y] & 0xff)) & 0xff;
- result[i] = (byte)(buf[i] ^ state[xorIndex]);
+ xorIndex = ((state[x] & 0xff) + (state[y] & 0xff)) & 0xff;
+ result[i] = (byte) (data[offset + i] ^ state[xorIndex]);
}
- //this.x = lx;
- //this.y = ly;
-
return result;
}
+ @Override
+ public byte[] decipher(byte[] data) {
+ return cipher(data);
+ }
+
+ @Override
+ public byte[] decipher(byte[] data, int offset, int length) {
+ return cipher(data, offset, length);
+ }
+
+ @Override
+ public byte[] getState () {
+ return state;
+ }
+
+ @Override
+ public int getQ() {
+ return x;
+ }
+
+ @Override
+ public int getJ() {
+ return y;
+ }
+
+ public RC4 deepCopy() {
+ return new RC4(Arrays.copyOf(state, 256), x, y);
+ }
+
public boolean couldBeFresh() {
return (x == 0 && y == 0);
}
public void undoRc4(byte[] buf) {
-
byte tmp;
- for (int i = buf.length - 1; i >= 0; i--) {
-
+ for (int i = 0; i < buf.length; i++) {
tmp = state[x];
state[x] = state[y];
state[y] = tmp;
@@ -197,10 +194,5 @@ public class RC4 {
y = (y - (state[x] & 0xff)) & 0xff;
x = (x - 1) & 0xff;
}
-
- }
-
- public byte[] getState () {
- return state;
}
}
\ No newline at end of file
diff --git a/G-Earth/src/main/java/gearth/protocol/crypto/RC4Base64.java b/G-Earth/src/main/java/gearth/protocol/crypto/RC4Base64.java
new file mode 100644
index 0000000..15b206d
--- /dev/null
+++ b/G-Earth/src/main/java/gearth/protocol/crypto/RC4Base64.java
@@ -0,0 +1,195 @@
+package gearth.protocol.crypto;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+
+import java.io.ByteArrayOutputStream;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+
+/**
+ * Non-standard RC4 algorithm using base64.
+ * Thanks to Joni Aromaa for the original implementation.
+ *
+ * https://github.com/aromaa/Skylight3/blob/72ec3a07d126de09f6de4251c91001329f77a8a2/src/Skylight.Server/Net/Crypto/RC4Base64.cs
+ *
+ */
+public class RC4Base64 implements RC4Cipher {
+
+ private static final byte[] BASE64_ENCODING_MAP = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".getBytes(StandardCharsets.US_ASCII);
+
+ private static final byte[] BASE64_DECODING_MAP = new byte[]{
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
+ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
+ -1, 0, 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, -1, -1, -1, -1, -1,
+ -1, 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, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ };
+
+ private final byte[] state;
+ private int q;
+ private int j;
+
+ public RC4Base64(byte[] state, int q, int j) {
+ if (state.length != 256) {
+ throw new IllegalArgumentException(String.format("State must be 256 bytes long, was %d", state.length));
+ }
+
+ this.q = q;
+ this.j = j;
+ this.state = state;
+ }
+
+ @Override
+ public byte[] cipher(byte[] data) {
+ return cipher(data, 0, data.length);
+ }
+
+ @Override
+ public byte[] cipher(byte[] data, int offset, int length) {
+ final ByteArrayOutputStream result = new ByteArrayOutputStream();
+
+ for (int i = 0; i < length; i += 3) {
+ int firstByte = data[offset + i] ^ moveUp();
+ int secondByte = data.length > i + 1 ? (data[i + 1] ^ moveUp()) : 0;
+
+ result.write(BASE64_ENCODING_MAP[(firstByte & 0xFC) >> 2]);
+ result.write(BASE64_ENCODING_MAP[((firstByte & 0x03) << 4) | ((secondByte & 0xF0) >> 4)]);
+
+ if (data.length > i + 1) {
+ int thirdByte = data.length > i + 2 ? (data[i + 2] ^ moveUp()) : 0;
+
+ result.write(BASE64_ENCODING_MAP[((secondByte & 0x0F) << 2) | ((thirdByte & 0xC0) >> 6)]);
+
+ if (data.length > i + 2) {
+ result.write(BASE64_ENCODING_MAP[thirdByte & 0x3F]);
+ }
+ }
+ }
+
+ return result.toByteArray();
+ }
+
+ @Override
+ public byte[] decipher(byte[] data) {
+ return decipher(data, 0, data.length);
+ }
+
+ @Override
+ public byte[] decipher(byte[] data, int offset, int length) {
+ final ByteBuffer buffer = ByteBuffer.wrap(data, offset, length);
+ final ByteArrayOutputStream resultBuffer = new ByteArrayOutputStream();
+
+ while (buffer.hasRemaining()) {
+ int firstByte = BASE64_DECODING_MAP[buffer.get()];
+ int secondByte = BASE64_DECODING_MAP[buffer.get()];
+
+ int byte1a = firstByte << 2;
+ int byte1b = (secondByte & 0x30) >> 4;
+
+ resultBuffer.write((byte) ((byte1a | byte1b) ^ moveUp()));
+
+ if (buffer.hasRemaining()) {
+ int thirdByte = BASE64_DECODING_MAP[buffer.get()];
+
+ int byte2a = (secondByte & 0x0F) << 4;
+ int byte2b = (thirdByte & 0x3C) >> 2;
+
+ resultBuffer.write((byte) ((byte2a | byte2b) ^ moveUp()));
+
+ if (buffer.hasRemaining()) {
+ int fourthByte = BASE64_DECODING_MAP[buffer.get()];
+
+ int byte3a = (thirdByte & 0x03) << 6;
+ int byte3b = fourthByte & 0x3F;
+
+ resultBuffer.write((byte) ((byte3a | byte3b) ^ moveUp()));
+ }
+ }
+ }
+
+ return resultBuffer.toByteArray();
+ }
+
+ @Override
+ public byte[] getState () {
+ return state;
+ }
+
+ @Override
+ public int getQ() {
+ return q;
+ }
+
+ @Override
+ public int getJ() {
+ return j;
+ }
+
+ @Override
+ public RC4Base64 deepCopy() {
+ return new RC4Base64(Arrays.copyOf(state, 256), q, j);
+ }
+
+ public byte moveUp() {
+ q = (q + 1) & 0xff;
+ j = ((state[q] & 0xff) + j) & 0xff;
+
+ byte tmp = state[q];
+ state[q] = state[j];
+ state[j] = tmp;
+
+ if ((q & 0x3F) == 0x3F) {
+ int x2 = 297 * (q + 67) & 0xff;
+ int y2 = (j + state[x2]) & 0xff;
+
+ tmp = state[x2];
+ state[x2] = state[y2];
+ state[y2] = tmp;
+ }
+
+ int xorIndex = ((state[q] &0xff) + (state[j] & 0xff)) & 0xff;
+
+ return state[xorIndex];
+ }
+
+ public boolean moveDown() {
+ byte tmp;
+
+ if ((q & 0x3F) == 0x3F) {
+ // Unsupported.
+ return false;
+ }
+
+ tmp = state[q];
+ state[q] = state[j];
+ state[j] = tmp;
+
+ j = (j - (state[q] & 0xff)) & 0xff;
+ q = (q - 1) & 0xff;
+
+ return true;
+ }
+
+ public boolean undoRc4(int length) {
+ for (int i = 0; i < length; i++) {
+ if (!moveDown()) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/G-Earth/src/main/java/gearth/protocol/crypto/RC4Cipher.java b/G-Earth/src/main/java/gearth/protocol/crypto/RC4Cipher.java
new file mode 100644
index 0000000..ecada39
--- /dev/null
+++ b/G-Earth/src/main/java/gearth/protocol/crypto/RC4Cipher.java
@@ -0,0 +1,21 @@
+package gearth.protocol.crypto;
+
+public interface RC4Cipher {
+
+ byte[] cipher(byte[] data);
+
+ byte[] cipher(byte[] data, int offset, int length);
+
+ byte[] decipher(byte[] data);
+
+ byte[] decipher(byte[] data, int offset, int length);
+
+ byte[] getState();
+
+ int getQ();
+
+ int getJ();
+
+ RC4Cipher deepCopy();
+
+}
diff --git a/G-Earth/src/main/java/gearth/protocol/memory/Rc4Obtainer.java b/G-Earth/src/main/java/gearth/protocol/memory/Rc4Obtainer.java
index 8911632..b741dc3 100644
--- a/G-Earth/src/main/java/gearth/protocol/memory/Rc4Obtainer.java
+++ b/G-Earth/src/main/java/gearth/protocol/memory/Rc4Obtainer.java
@@ -4,11 +4,16 @@ import gearth.GEarth;
import gearth.protocol.HConnection;
import gearth.protocol.HMessage;
import gearth.protocol.crypto.RC4;
+import gearth.protocol.crypto.RC4Base64;
import gearth.protocol.memory.habboclient.HabboClient;
import gearth.protocol.memory.habboclient.HabboClientFactory;
import gearth.protocol.packethandler.EncryptedPacketHandler;
-import gearth.protocol.packethandler.PayloadBuffer;
+import gearth.protocol.packethandler.flash.FlashBuffer;
import gearth.protocol.packethandler.flash.BufferChangeListener;
+import gearth.protocol.packethandler.flash.FlashPacketHandler;
+import gearth.protocol.packethandler.shockwave.ShockwavePacketOutgoingHandler;
+import gearth.protocol.packethandler.PayloadBuffer;
+import gearth.protocol.packethandler.shockwave.buffers.ShockwaveOutBuffer;
import gearth.ui.titlebar.TitleBarController;
import gearth.ui.translations.LanguageBundle;
import javafx.application.Platform;
@@ -18,24 +23,51 @@ import javafx.scene.control.Hyperlink;
import javafx.scene.control.Label;
import javafx.scene.layout.FlowPane;
import javafx.scene.layout.Region;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Rc4Obtainer {
- public static final boolean DEBUG = false;
+ private static final Logger logger = LoggerFactory.getLogger(Rc4Obtainer.class);
- private final HabboClient client;
- private List flashPacketHandlers;
+ private final HConnection hConnection;
+ private final List flashPacketHandlers;
public Rc4Obtainer(HConnection hConnection) {
- client = HabboClientFactory.get(hConnection);
+ this.hConnection = hConnection;
+ this.flashPacketHandlers = new ArrayList<>();
+ }
+
+ private static void showErrorDialog() {
+ Alert alert = new Alert(Alert.AlertType.WARNING, LanguageBundle.get("alert.somethingwentwrong.title"), ButtonType.OK);
+
+ FlowPane fp = new FlowPane();
+ Label lbl = new Label(LanguageBundle.get("alert.somethingwentwrong.content").replaceAll("\\\\n", System.lineSeparator()));
+ Hyperlink link = new Hyperlink("https://github.com/sirjonasxx/G-Earth/wiki/Troubleshooting");
+ fp.getChildren().addAll(lbl, link);
+ link.setOnAction(event -> {
+ GEarth.main.getHostServices().showDocument(link.getText());
+ event.consume();
+ });
+
+ alert.getDialogPane().setMinHeight(Region.USE_PREF_SIZE);
+ alert.getDialogPane().setContent(fp);
+ alert.setOnCloseRequest(event -> GEarth.main.getHostServices().showDocument(link.getText()));
+ try {
+ TitleBarController.create(alert).showAlert();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
}
public void setFlashPacketHandlers(EncryptedPacketHandler... flashPacketHandlers) {
- this.flashPacketHandlers = Arrays.asList(flashPacketHandlers);
+ this.flashPacketHandlers.addAll(Arrays.asList(flashPacketHandlers));
+
for (EncryptedPacketHandler handler : flashPacketHandlers) {
BufferChangeListener bufferChangeListener = new BufferChangeListener() {
@Override
@@ -55,10 +87,18 @@ public class Rc4Obtainer {
flashPacketHandlers.forEach(EncryptedPacketHandler::block);
- new Thread(() -> {
+ logger.info("Caught encrypted packet, attempting to find decryption keys");
+
+ final HabboClient client = HabboClientFactory.get(hConnection);
+ if (client == null) {
+ logger.info("Unsupported platform / client combination, aborting connection");
+ hConnection.abort();
+ return;
+ }
+
+ new Thread(() -> {
+ final long startTime = System.currentTimeMillis();
- long startTime = System.currentTimeMillis();
- if (DEBUG) System.out.println("[+] send encrypted");
boolean worked = false;
int i = 0;
@@ -70,49 +110,93 @@ public class Rc4Obtainer {
}
if (!worked) {
- System.err.println("COULD NOT FIND RC4 TABLE");
+ try {
+ Platform.runLater(Rc4Obtainer::showErrorDialog);
+ } catch (IllegalStateException e) {
+ // ignore, thrown in tests.
+ }
- Platform.runLater(() -> {
- Alert alert = new Alert(Alert.AlertType.WARNING, LanguageBundle.get("alert.somethingwentwrong.title"), ButtonType.OK);
-
- FlowPane fp = new FlowPane();
- Label lbl = new Label(LanguageBundle.get("alert.somethingwentwrong.content").replaceAll("\\\\n", System.lineSeparator()));
- Hyperlink link = new Hyperlink("https://github.com/sirjonasxx/G-Earth/wiki/Troubleshooting");
- fp.getChildren().addAll(lbl, link);
- link.setOnAction(event -> {
- GEarth.main.getHostServices().showDocument(link.getText());
- event.consume();
- });
-
- alert.getDialogPane().setMinHeight(Region.USE_PREF_SIZE);
- alert.getDialogPane().setContent(fp);
- alert.setOnCloseRequest(event -> GEarth.main.getHostServices().showDocument(link.getText()));
- try {
- TitleBarController.create(alert).showAlert();
- } catch (IOException e) {
- e.printStackTrace();
- }
- });
+ logger.error("Failed to find RC4 table, aborting connection");
+ hConnection.abort();
+ return;
}
final long endTime = System.currentTimeMillis();
- if (DEBUG)
- System.out.println("Cracked RC4 in " + (endTime - startTime) + "ms");
+ logger.info("Cracked decryption keys in {}ms", endTime - startTime);
flashPacketHandlers.forEach(EncryptedPacketHandler::unblock);
}).start();
}
private boolean onSendFirstEncryptedMessage(EncryptedPacketHandler flashPacketHandler, List potentialRC4tables) {
+ if (potentialRC4tables == null || potentialRC4tables.isEmpty()) {
+ return false;
+ }
- for (byte[] possible : potentialRC4tables)
- if (isCorrectRC4Table(flashPacketHandler, possible))
+ for (byte[] possible : potentialRC4tables) {
+ if (flashPacketHandler instanceof FlashPacketHandler && bruteFlash(flashPacketHandler, possible))
return true;
+ if (flashPacketHandler instanceof ShockwavePacketOutgoingHandler && bruteShockwaveHeader(flashPacketHandler, possible)) {
+ return true;
+ }
+ }
+
return false;
}
- private boolean isCorrectRC4Table(EncryptedPacketHandler flashPacketHandler, byte[] possible) {
+ private boolean bruteShockwaveHeader(EncryptedPacketHandler packetHandler, byte[] tableState) {
+ final int encBufferSize = packetHandler.getEncryptedBuffer().size();
+
+ if (encBufferSize < ShockwaveOutBuffer.PACKET_SIZE_MIN_ENCRYPTED) {
+ return false;
+ }
+
+ // Copy buffer.
+ final byte[] encBuffer = new byte[encBufferSize];
+ for (int i = 0; i < encBufferSize; i++) {
+ encBuffer[i] = packetHandler.getEncryptedBuffer().get(i);
+ }
+
+ // Brute force q and j.
+ for (int q = 0; q < 256; q++) {
+ for (int j = 0; j < 256; j++) {
+ final byte[] tableStateCopy = Arrays.copyOf(tableState, tableState.length);
+ final RC4Base64 rc4 = new RC4Base64(tableStateCopy, q, j);
+
+ if (packetHandler.getDirection() == HMessage.Direction.TOSERVER) {
+ // Encoded 3 headers, 4 * 3 = 12
+ if (!rc4.undoRc4(12)) {
+ continue;
+ }
+ }
+
+ final byte[] encDataCopy = Arrays.copyOf(encBuffer, encBuffer.length);
+ final RC4Base64 rc4Test = rc4.deepCopy();
+
+ // Attempt to exhaust buffer.
+ final ShockwaveOutBuffer buffer = new ShockwaveOutBuffer();
+
+ buffer.setCipher(rc4Test);
+ buffer.push(encDataCopy);
+
+ try {
+ final byte[][] packets = buffer.receive();
+
+ if (packets.length == 3 && buffer.isEmpty()) {
+ packetHandler.setRc4(rc4);
+ return true;
+ }
+ } catch (IllegalArgumentException e) {
+ // ignore
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private boolean bruteFlash(EncryptedPacketHandler flashPacketHandler, byte[] possible) {
try {
@@ -136,12 +220,13 @@ public class Rc4Obtainer {
final RC4 rc4TryCopy = rc4Tryout.deepCopy();
try {
- final PayloadBuffer payloadBuffer = new PayloadBuffer();
- final byte[] decoded = rc4TryCopy.rc4(encDataCopy);
+ final PayloadBuffer payloadBuffer = new FlashBuffer();
+ final byte[] decoded = rc4TryCopy.cipher(encDataCopy);
- payloadBuffer.pushAndReceive(decoded);
+ payloadBuffer.push(decoded);
+ payloadBuffer.receive();
- if (payloadBuffer.peak().length == 0) {
+ if (payloadBuffer.isEmpty()) {
flashPacketHandler.setRc4(rc4Tryout);
return true;
}
diff --git a/G-Earth/src/main/java/gearth/protocol/memory/habboclient/HabboClientFactory.java b/G-Earth/src/main/java/gearth/protocol/memory/habboclient/HabboClientFactory.java
index 48cba93..3032a93 100644
--- a/G-Earth/src/main/java/gearth/protocol/memory/habboclient/HabboClientFactory.java
+++ b/G-Earth/src/main/java/gearth/protocol/memory/habboclient/HabboClientFactory.java
@@ -2,8 +2,10 @@ package gearth.protocol.memory.habboclient;
import gearth.misc.OSValidator;
import gearth.protocol.HConnection;
+import gearth.protocol.connection.HClient;
import gearth.protocol.memory.habboclient.linux.LinuxHabboClient;
import gearth.protocol.memory.habboclient.macOs.MacOsHabboClient;
+import gearth.protocol.memory.habboclient.shockwave.ShockwaveMemoryClient;
import gearth.protocol.memory.habboclient.windows.WindowsHabboClient;
/**
@@ -11,16 +13,18 @@ import gearth.protocol.memory.habboclient.windows.WindowsHabboClient;
*/
public class HabboClientFactory {
-
public static HabboClient get(HConnection connection) {
- if (OSValidator.isUnix()) return new LinuxHabboClient(connection);
- if (OSValidator.isWindows()) return new WindowsHabboClient(connection);
- if (OSValidator.isMac()) return new MacOsHabboClient(connection);
+ if (connection.getClientType() == HClient.SHOCKWAVE) {
+ return new ShockwaveMemoryClient(connection);
+ } else {
+ if (OSValidator.isWindows()) return new WindowsHabboClient(connection);
+ if (OSValidator.isUnix()) return new LinuxHabboClient(connection);
+ if (OSValidator.isMac()) return new MacOsHabboClient(connection);
+ }
// todo use rust if beneficial
return null;
}
-
}
diff --git a/G-Earth/src/main/java/gearth/protocol/memory/habboclient/shockwave/ShockwaveMemoryClient.java b/G-Earth/src/main/java/gearth/protocol/memory/habboclient/shockwave/ShockwaveMemoryClient.java
new file mode 100644
index 0000000..03f8365
--- /dev/null
+++ b/G-Earth/src/main/java/gearth/protocol/memory/habboclient/shockwave/ShockwaveMemoryClient.java
@@ -0,0 +1,83 @@
+package gearth.protocol.memory.habboclient.shockwave;
+
+import gearth.encoding.HexEncoding;
+import gearth.misc.OSValidator;
+import gearth.protocol.HConnection;
+import gearth.protocol.memory.habboclient.HabboClient;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+
+public class ShockwaveMemoryClient extends HabboClient {
+
+ private static final Logger logger = LoggerFactory.getLogger(ShockwaveMemoryClient.class);
+
+ public ShockwaveMemoryClient(HConnection connection) {
+ super(connection);
+ }
+
+ @Override
+ public List getRC4cached() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public List getRC4possibilities() {
+ final List result = new ArrayList<>();
+
+ try {
+ final HashSet potentialTables = dumpTables();
+
+ for (String potentialTable : potentialTables) {
+ result.add(HexEncoding.toBytes(potentialTable));
+ }
+ } catch (IOException | URISyntaxException e) {
+ logger.error("Failed to read RC4 possibilities from the Shockwave client", e);
+ }
+
+ // Reverse the list so that the most likely keys are at the top.
+ Collections.reverse(result);
+
+ return result;
+ }
+
+ private HashSet dumpTables() throws IOException, URISyntaxException {
+ String filePath = new File(this.getClass().getProtectionDomain().getCodeSource().getLocation().toURI()).getParent();
+
+ if (OSValidator.isWindows()) {
+ filePath += "\\G-MemZ.exe";
+ } else {
+ filePath += "/G-MemZ";
+ }
+
+ final ProcessBuilder pb = new ProcessBuilder(filePath);
+ final Process p = pb.start();
+
+ final HashSet possibleData = new HashSet<>();
+
+ try {
+ final BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()));
+
+ String line;
+
+ while ((line = reader.readLine()) != null) {
+ if (line.length() == 512) {
+ possibleData.add(line);
+ }
+ }
+ } finally {
+ p.destroy();
+ }
+
+ return possibleData;
+ }
+}
diff --git a/G-Earth/src/main/java/gearth/protocol/packethandler/EncryptedPacketHandler.java b/G-Earth/src/main/java/gearth/protocol/packethandler/EncryptedPacketHandler.java
index 5495a3f..f19ba56 100644
--- a/G-Earth/src/main/java/gearth/protocol/packethandler/EncryptedPacketHandler.java
+++ b/G-Earth/src/main/java/gearth/protocol/packethandler/EncryptedPacketHandler.java
@@ -4,7 +4,7 @@ import gearth.misc.listenerpattern.Observable;
import gearth.protocol.HConnection;
import gearth.protocol.HMessage;
import gearth.protocol.TrafficListener;
-import gearth.protocol.crypto.RC4;
+import gearth.protocol.crypto.RC4Cipher;
import gearth.protocol.packethandler.flash.BufferChangeListener;
import gearth.services.extension_handler.ExtensionHandler;
import org.slf4j.Logger;
@@ -27,8 +27,8 @@ public abstract class EncryptedPacketHandler extends PacketHandler {
private volatile boolean isEncryptedStream;
private volatile List tempEncryptedBuffer;
- private RC4 encryptCipher;
- private RC4 decryptCipher;
+ private RC4Cipher encryptCipher;
+ private RC4Cipher decryptCipher;
protected EncryptedPacketHandler(ExtensionHandler extensionHandler, Observable[] trafficObservables, HMessage.Direction direction) {
super(extensionHandler, trafficObservables);
@@ -73,16 +73,16 @@ public abstract class EncryptedPacketHandler extends PacketHandler {
tempEncryptedBuffer.add(buffer[i]);
}
} else {
- writeBuffer(decryptCipher.rc4(buffer));
+ writeBuffer(buffer);
}
}
protected byte[] encrypt(byte[] buffer) {
- return encryptCipher.rc4(buffer);
+ return encryptCipher.cipher(buffer);
}
protected byte[] decrypt(byte[] buffer) {
- return decryptCipher.rc4(buffer);
+ return decryptCipher.decipher(buffer);
}
protected abstract void writeOut(byte[] buffer) throws IOException;
@@ -105,7 +105,11 @@ public abstract class EncryptedPacketHandler extends PacketHandler {
isTempBlocked = false;
}
- public void setRc4(RC4 rc4) {
+ public boolean isCiphersSet() {
+ return encryptCipher != null && decryptCipher != null;
+ }
+
+ public void setRc4(RC4Cipher rc4) {
this.decryptCipher = rc4.deepCopy();
this.encryptCipher = rc4.deepCopy();
diff --git a/G-Earth/src/main/java/gearth/protocol/packethandler/PayloadBuffer.java b/G-Earth/src/main/java/gearth/protocol/packethandler/PayloadBuffer.java
index 36c7045..0cd6830 100644
--- a/G-Earth/src/main/java/gearth/protocol/packethandler/PayloadBuffer.java
+++ b/G-Earth/src/main/java/gearth/protocol/packethandler/PayloadBuffer.java
@@ -1,42 +1,28 @@
package gearth.protocol.packethandler;
-import gearth.protocol.HPacket;
+import gearth.protocol.crypto.RC4Cipher;
-import java.util.ArrayList;
-import java.util.Arrays;
+public abstract class PayloadBuffer {
-public class PayloadBuffer {
+ protected byte[] buffer;
- private byte[] buffer = new byte[0];
-
- public HPacket[] pushAndReceive(byte[] tcpData){
- push(tcpData);
- return receive();
- }
- public void push(byte[] tcpData) {
- buffer = buffer.length == 0 ? tcpData.clone() : ByteArrayUtils.combineByteArrays(buffer, tcpData);
- }
- public HPacket[] receive() {
- if (buffer.length < 6) return new HPacket[0];
- HPacket total = new HPacket(buffer);
-
- ArrayList all = new ArrayList<>();
- while (total.getBytesLength() >= 4 && total.getBytesLength() - 4 >= total.length()){
- all.add(new HPacket(Arrays.copyOfRange(buffer, 0, total.length() + 4)));
- buffer = Arrays.copyOfRange(buffer, total.length() + 4, buffer.length);
- total = new HPacket(buffer);
- }
- return all.toArray(new HPacket[all.size()]);
+ public PayloadBuffer() {
+ this.buffer = new byte[0];
}
+ /**
+ * Make sure to call deepCopy on the cipher if you use it.
+ */
+ public abstract void setCipher(RC4Cipher cipher);
- public byte[] peak() {
- return buffer;
+ public void push(byte[] data) {
+ buffer = buffer.length == 0 ? data.clone() : ByteArrayUtils.combineByteArrays(buffer, data);
}
- public byte[] forceClear() {
- byte[] buff = buffer;
- buffer = new byte[0];
- return buff;
+
+ public abstract byte[][] receive();
+
+ public boolean isEmpty() {
+ return buffer.length == 0;
}
}
diff --git a/G-Earth/src/main/java/gearth/protocol/packethandler/flash/FlashBuffer.java b/G-Earth/src/main/java/gearth/protocol/packethandler/flash/FlashBuffer.java
new file mode 100644
index 0000000..e9540dd
--- /dev/null
+++ b/G-Earth/src/main/java/gearth/protocol/packethandler/flash/FlashBuffer.java
@@ -0,0 +1,30 @@
+package gearth.protocol.packethandler.flash;
+
+import gearth.protocol.HPacket;
+import gearth.protocol.crypto.RC4Cipher;
+import gearth.protocol.packethandler.PayloadBuffer;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+public class FlashBuffer extends PayloadBuffer {
+
+ @Override
+ public void setCipher(RC4Cipher cipher) {
+ // not needed
+ }
+
+ public byte[][] receive() {
+ if (buffer.length < 6) return new byte[0][];
+ HPacket total = new HPacket(buffer);
+
+ final ArrayList all = new ArrayList<>();
+ while (total.getBytesLength() >= 4 && total.getBytesLength() - 4 >= total.length()) {
+ all.add(Arrays.copyOfRange(buffer, 0, total.length() + 4));
+ buffer = Arrays.copyOfRange(buffer, total.length() + 4, buffer.length);
+ total = new HPacket(buffer);
+ }
+ return all.toArray(new byte[0][]);
+ }
+
+}
diff --git a/G-Earth/src/main/java/gearth/protocol/packethandler/flash/FlashPacketHandler.java b/G-Earth/src/main/java/gearth/protocol/packethandler/flash/FlashPacketHandler.java
index 4b00b1c..f5cb1f6 100644
--- a/G-Earth/src/main/java/gearth/protocol/packethandler/flash/FlashPacketHandler.java
+++ b/G-Earth/src/main/java/gearth/protocol/packethandler/flash/FlashPacketHandler.java
@@ -5,7 +5,6 @@ import gearth.protocol.HMessage;
import gearth.protocol.HPacket;
import gearth.protocol.TrafficListener;
import gearth.protocol.packethandler.EncryptedPacketHandler;
-import gearth.protocol.packethandler.PayloadBuffer;
import gearth.services.extension_handler.ExtensionHandler;
import java.io.IOException;
@@ -14,13 +13,13 @@ import java.io.OutputStream;
public abstract class FlashPacketHandler extends EncryptedPacketHandler {
private final OutputStream out;
- private final PayloadBuffer payloadBuffer;
+ private final FlashBuffer payloadBuffer;
private volatile boolean isDataStream;
FlashPacketHandler(HMessage.Direction direction, OutputStream outputStream, Observable[] trafficObservables, ExtensionHandler extensionHandler) {
super(extensionHandler, trafficObservables, direction);
this.out = outputStream;
- this.payloadBuffer = new PayloadBuffer();
+ this.payloadBuffer = new FlashBuffer();
this.isDataStream = false;
}
@@ -32,6 +31,7 @@ public abstract class FlashPacketHandler extends EncryptedPacketHandler {
isDataStream = true;
}
+ @Override
public void act(byte[] buffer) throws IOException {
if (!isDataStream) {
synchronized (sendLock) {
@@ -40,7 +40,11 @@ public abstract class FlashPacketHandler extends EncryptedPacketHandler {
return;
}
- super.act(buffer);
+ if (isEncryptedStream() && isCiphersSet()) {
+ super.act(decrypt(buffer));
+ } else {
+ super.act(buffer);
+ }
if (!isBlocked()) {
flush();
@@ -59,6 +63,7 @@ public abstract class FlashPacketHandler extends EncryptedPacketHandler {
payloadBuffer.push(buffer);
}
+ @Override
public boolean sendToStream(byte[] buffer) {
return sendToStream(buffer, isEncryptedStream());
}
@@ -77,10 +82,9 @@ public abstract class FlashPacketHandler extends EncryptedPacketHandler {
public void flush() throws IOException {
synchronized (flushLock) {
- HPacket[] hpackets = payloadBuffer.receive();
-
- for (HPacket hpacket : hpackets){
- HMessage hMessage = new HMessage(hpacket, getDirection(), currentIndex);
+ for (final byte[] packet : payloadBuffer.receive()){
+ HPacket hPacket = new HPacket(packet);
+ HMessage hMessage = new HMessage(hPacket, getDirection(), currentIndex);
boolean isencrypted = isEncryptedStream();
if (isDataStream) {
diff --git a/G-Earth/src/main/java/gearth/protocol/packethandler/nitro/NitroPacketHandler.java b/G-Earth/src/main/java/gearth/protocol/packethandler/nitro/NitroPacketHandler.java
index 3f845ad..2d78656 100644
--- a/G-Earth/src/main/java/gearth/protocol/packethandler/nitro/NitroPacketHandler.java
+++ b/G-Earth/src/main/java/gearth/protocol/packethandler/nitro/NitroPacketHandler.java
@@ -6,7 +6,7 @@ import gearth.protocol.HPacket;
import gearth.protocol.TrafficListener;
import gearth.protocol.connection.proxy.nitro.websocket.NitroSession;
import gearth.protocol.packethandler.PacketHandler;
-import gearth.protocol.packethandler.PayloadBuffer;
+import gearth.protocol.packethandler.flash.FlashBuffer;
import gearth.services.extension_handler.ExtensionHandler;
import org.eclipse.jetty.websocket.api.Session;
import org.slf4j.Logger;
@@ -21,14 +21,14 @@ public class NitroPacketHandler extends PacketHandler {
private final HMessage.Direction direction;
private final NitroSession session;
- private final PayloadBuffer payloadBuffer;
+ private final FlashBuffer payloadBuffer;
private final Object payloadLock;
public NitroPacketHandler(HMessage.Direction direction, NitroSession session, ExtensionHandler extensionHandler, Observable[] trafficObservables) {
super(extensionHandler, trafficObservables);
this.direction = direction;
this.session = session;
- this.payloadBuffer = new PayloadBuffer();
+ this.payloadBuffer = new FlashBuffer();
this.payloadLock = new Object();
}
@@ -61,8 +61,9 @@ public class NitroPacketHandler extends PacketHandler {
payloadBuffer.push(buffer);
synchronized (payloadLock) {
- for (HPacket packet : payloadBuffer.receive()) {
- HMessage hMessage = new HMessage(packet, direction, currentIndex);
+ for (final byte[] packet : payloadBuffer.receive()) {
+ HPacket hPacket = new HPacket(packet);
+ HMessage hMessage = new HMessage(hPacket, direction, currentIndex);
awaitListeners(hMessage, hMessage1 -> sendToStream(hMessage1.getPacket().toBytes()));
currentIndex++;
}
diff --git a/G-Earth/src/main/java/gearth/protocol/packethandler/shockwave/ShockwavePacketHandler.java b/G-Earth/src/main/java/gearth/protocol/packethandler/shockwave/ShockwavePacketHandler.java
index 111e279..1bcac8d 100644
--- a/G-Earth/src/main/java/gearth/protocol/packethandler/shockwave/ShockwavePacketHandler.java
+++ b/G-Earth/src/main/java/gearth/protocol/packethandler/shockwave/ShockwavePacketHandler.java
@@ -1,66 +1,45 @@
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.HPacketFormat;
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.protocol.crypto.RC4Cipher;
+import gearth.protocol.packethandler.EncryptedPacketHandler;
+import gearth.protocol.packethandler.PayloadBuffer;
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;
-public abstract class ShockwavePacketHandler extends PacketHandler {
-
- // The first 20 bytes of the artificialKey.
- public static final byte[] ARTIFICIAL_KEY = Hex.decode("14d288cdb0bc08c274809a7802962af98b41dec8");
+public abstract class ShockwavePacketHandler extends EncryptedPacketHandler {
protected static final Logger logger = LoggerFactory.getLogger(ShockwavePacketHandler.class);
private final HMessage.Direction direction;
- private final ShockwaveBuffer payloadBuffer;
+ private final HPacketFormat format;
+ private final PayloadBuffer payloadBuffer;
private final Object flushLock;
protected final OutputStream outputStream;
- private boolean isEncrypted;
- private final RC4Shockwave decryptCipher;
- private final RC4Shockwave encryptCipher;
-
- ShockwavePacketHandler(HMessage.Direction direction, ShockwaveBuffer payloadBuffer, OutputStream outputStream, ExtensionHandler extensionHandler, Observable[] trafficObservables) {
- super(extensionHandler, trafficObservables);
+ ShockwavePacketHandler(HMessage.Direction direction, PayloadBuffer payloadBuffer, OutputStream outputStream, ExtensionHandler extensionHandler, Observable[] trafficObservables) {
+ super(extensionHandler, trafficObservables, direction);
this.direction = direction;
+ this.format = direction == HMessage.Direction.TOSERVER ? HPacketFormat.WEDGIE_OUTGOING : HPacketFormat.WEDGIE_INCOMING;
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));
- }
+ outputStream.write(buffer);
return true;
} catch (IOException e) {
logger.error("Failed to send packet to stream", e);
@@ -71,20 +50,40 @@ public abstract class ShockwavePacketHandler extends PacketHandler {
@Override
public void act(byte[] buffer) throws IOException {
- if (!isEncrypted) {
- payloadBuffer.push(buffer);
- } else {
- payloadBuffer.push(decryptCipher.crypt(Hex.decode(buffer)));
- }
+ super.act(buffer);
- flush();
+ if (!isBlocked()) {
+ flush();
+ }
+ }
+
+ @Override
+ protected void writeOut(byte[] buffer) throws IOException {
+ synchronized (sendLock) {
+ outputStream.write(buffer);
+ }
+ }
+
+ @Override
+ protected void writeBuffer(byte[] buffer) {
+ payloadBuffer.push(buffer);
+ }
+
+ @Override
+ public void setRc4(RC4Cipher rc4) {
+ payloadBuffer.setCipher(rc4);
+ super.setRc4(rc4);
}
public void flush() throws IOException {
synchronized (flushLock) {
- final HPacket[] packets = payloadBuffer.receive();
+ final byte[][] packets = payloadBuffer.receive();
+
+ for (final byte[] rawPacket : packets) {
+ final HPacket packet = isEncryptedStream()
+ ? format.createPacket(decrypt(rawPacket))
+ : format.createPacket(rawPacket);
- for (final HPacket packet : packets){
packet.setIdentifierDirection(direction);
final HMessage message = new HMessage(packet, direction, currentIndex);
diff --git a/G-Earth/src/main/java/gearth/protocol/packethandler/shockwave/ShockwavePacketIncomingHandler.java b/G-Earth/src/main/java/gearth/protocol/packethandler/shockwave/ShockwavePacketIncomingHandler.java
index bfef136..795eb3d 100644
--- a/G-Earth/src/main/java/gearth/protocol/packethandler/shockwave/ShockwavePacketIncomingHandler.java
+++ b/G-Earth/src/main/java/gearth/protocol/packethandler/shockwave/ShockwavePacketIncomingHandler.java
@@ -34,7 +34,7 @@ public class ShockwavePacketIncomingHandler extends ShockwavePacketHandler {
if (packet.headerId() == ID_SECRET_KEY) {
logger.info("Received SECRET_KEY from server, enabling encryption / decryption.");
trafficObservables[0].removeListener(this);
- outgoingHandler.setEncrypted();
+ outgoingHandler.setEncryptedStream();
}
}
});
diff --git a/G-Earth/src/main/java/gearth/protocol/packethandler/shockwave/ShockwavePacketOutgoingHandler.java b/G-Earth/src/main/java/gearth/protocol/packethandler/shockwave/ShockwavePacketOutgoingHandler.java
index 313e501..2ed26f8 100644
--- a/G-Earth/src/main/java/gearth/protocol/packethandler/shockwave/ShockwavePacketOutgoingHandler.java
+++ b/G-Earth/src/main/java/gearth/protocol/packethandler/shockwave/ShockwavePacketOutgoingHandler.java
@@ -4,22 +4,55 @@ import gearth.encoding.Base64Encoding;
import gearth.misc.listenerpattern.Observable;
import gearth.protocol.HMessage;
import gearth.protocol.TrafficListener;
+import gearth.protocol.crypto.RC4Cipher;
import gearth.protocol.packethandler.ByteArrayUtils;
import gearth.protocol.packethandler.shockwave.buffers.ShockwaveOutBuffer;
import gearth.services.extension_handler.ExtensionHandler;
import java.io.OutputStream;
+import java.util.concurrent.ThreadLocalRandom;
public class ShockwavePacketOutgoingHandler extends ShockwavePacketHandler {
+ private RC4Cipher headerEncoder;
+
public ShockwavePacketOutgoingHandler(OutputStream outputStream, ExtensionHandler extensionHandler, Observable[] trafficObservables) {
super(HMessage.Direction.TOSERVER, new ShockwaveOutBuffer(), outputStream, extensionHandler, trafficObservables);
}
@Override
public boolean sendToStream(byte[] packet) {
- byte[] bufferLen = Base64Encoding.encode(packet.length, 3);
- byte[] buffer = ByteArrayUtils.combineByteArrays(bufferLen, packet);
+ byte[] bufferLen;
+
+ if (isEncryptedStream()) {
+ if (headerEncoder == null) {
+ throw new IllegalStateException("Expected header encoder to be set for an encrypted stream.");
+ }
+
+ // Encrypt packet.
+ packet = encrypt(packet);
+
+ // Encrypt header.
+ final byte[] newPacketLen = Base64Encoding.encode(packet.length, 3);
+ final byte[] header = new byte[4];
+
+ header[0] = (byte) ThreadLocalRandom.current().nextInt(0, 127);
+ header[1] = newPacketLen[0];
+ header[2] = newPacketLen[1];
+ header[3] = newPacketLen[2];
+
+ bufferLen = headerEncoder.cipher(header);
+ } else {
+ bufferLen = Base64Encoding.encode(packet.length, 3);
+ }
+
+ final byte[] buffer = ByteArrayUtils.combineByteArrays(bufferLen, packet);
return super.sendToStream(buffer);
}
+
+ @Override
+ public void setRc4(RC4Cipher rc4) {
+ this.headerEncoder = rc4.deepCopy();
+ super.setRc4(rc4);
+ }
}
diff --git a/G-Earth/src/main/java/gearth/protocol/packethandler/shockwave/buffers/ShockwaveBuffer.java b/G-Earth/src/main/java/gearth/protocol/packethandler/shockwave/buffers/ShockwaveBuffer.java
deleted file mode 100644
index 81f6622..0000000
--- a/G-Earth/src/main/java/gearth/protocol/packethandler/shockwave/buffers/ShockwaveBuffer.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package gearth.protocol.packethandler.shockwave.buffers;
-
-import gearth.protocol.HPacket;
-
-public interface ShockwaveBuffer {
-
- void push(byte[] data);
-
- HPacket[] receive();
-
-}
diff --git a/G-Earth/src/main/java/gearth/protocol/packethandler/shockwave/buffers/ShockwaveInBuffer.java b/G-Earth/src/main/java/gearth/protocol/packethandler/shockwave/buffers/ShockwaveInBuffer.java
index 041572d..f18049a 100644
--- a/G-Earth/src/main/java/gearth/protocol/packethandler/shockwave/buffers/ShockwaveInBuffer.java
+++ b/G-Earth/src/main/java/gearth/protocol/packethandler/shockwave/buffers/ShockwaveInBuffer.java
@@ -1,44 +1,39 @@
package gearth.protocol.packethandler.shockwave.buffers;
-import gearth.protocol.HPacket;
-import gearth.protocol.packethandler.ByteArrayUtils;
-import gearth.protocol.packethandler.shockwave.packets.ShockPacket;
-import gearth.protocol.packethandler.shockwave.packets.ShockPacketIncoming;
+import gearth.protocol.crypto.RC4Cipher;
+import gearth.protocol.packethandler.PayloadBuffer;
import java.util.ArrayList;
import java.util.Arrays;
-public class ShockwaveInBuffer implements ShockwaveBuffer {
-
- private byte[] buffer = new byte[0];
+public class ShockwaveInBuffer extends PayloadBuffer {
@Override
- public void push(byte[] data) {
- buffer = buffer.length == 0 ? data.clone() : ByteArrayUtils.combineByteArrays(buffer, data);
+ public void setCipher(RC4Cipher cipher) {
+ // We don't need to decrypt incoming packet headers, for now.
}
@Override
- public HPacket[] receive() {
+ public byte[][] receive() {
if (buffer.length < 3) {
- return new ShockPacket[0];
+ return new byte[0][];
}
// Incoming packets are delimited by chr(1).
// We need to split the buffer by chr(1) and then parse each packet.
- ArrayList packets = new ArrayList<>();
+ final ArrayList packets = new ArrayList<>();
int curPos = 0;
for (int i = 0; i < buffer.length; i++) {
if (buffer[i] == 1) {
- byte[] packetData = Arrays.copyOfRange(buffer, curPos, i);
- packets.add(new ShockPacketIncoming(packetData));
+ packets.add(Arrays.copyOfRange(buffer, curPos, i));
curPos = i + 1;
}
}
buffer = Arrays.copyOfRange(buffer, curPos, buffer.length);
- return packets.toArray(new ShockPacket[0]);
+ return packets.toArray(new byte[0][]);
}
}
diff --git a/G-Earth/src/main/java/gearth/protocol/packethandler/shockwave/buffers/ShockwaveOutBuffer.java b/G-Earth/src/main/java/gearth/protocol/packethandler/shockwave/buffers/ShockwaveOutBuffer.java
index 9bc5fd6..18df6df 100644
--- a/G-Earth/src/main/java/gearth/protocol/packethandler/shockwave/buffers/ShockwaveOutBuffer.java
+++ b/G-Earth/src/main/java/gearth/protocol/packethandler/shockwave/buffers/ShockwaveOutBuffer.java
@@ -1,50 +1,65 @@
package gearth.protocol.packethandler.shockwave.buffers;
import gearth.encoding.Base64Encoding;
-import gearth.protocol.HPacket;
-import gearth.protocol.packethandler.ByteArrayUtils;
-import gearth.protocol.packethandler.shockwave.packets.ShockPacket;
-import gearth.protocol.packethandler.shockwave.packets.ShockPacketOutgoing;
+import gearth.protocol.crypto.RC4Cipher;
+import gearth.protocol.packethandler.PayloadBuffer;
import java.util.ArrayList;
import java.util.Arrays;
-public class ShockwaveOutBuffer implements ShockwaveBuffer {
+public class ShockwaveOutBuffer extends PayloadBuffer {
- private static final int PACKET_LENGTH_SIZE = 3;
- private static final int PACKET_HEADER_SIZE = 2;
+ public static final int PACKET_HEADER_SIZE = 2;
- private static final int PACKET_SIZE_MIN = PACKET_LENGTH_SIZE + PACKET_HEADER_SIZE;
+ public static final int PACKET_LENGTH_SIZE_ENCRYPTED = 6;
+ public static final int PACKET_LENGTH_SIZE = 3;
- private byte[] buffer = new byte[0];
+ public static final int PACKET_SIZE_MIN = PACKET_HEADER_SIZE + PACKET_LENGTH_SIZE;
+ public static final int PACKET_SIZE_MIN_ENCRYPTED = PACKET_HEADER_SIZE + PACKET_LENGTH_SIZE_ENCRYPTED;
+
+ private RC4Cipher cipher;
@Override
- public void push(byte[] data) {
- buffer = buffer.length == 0 ? data.clone() : ByteArrayUtils.combineByteArrays(buffer, data);
+ public void setCipher(RC4Cipher cipher) {
+ this.cipher = cipher.deepCopy();
}
@Override
- public HPacket[] receive() {
- if (buffer.length < PACKET_SIZE_MIN) {
- return new ShockPacket[0];
+ public byte[][] receive() {
+ final int packetLengthSize = this.cipher != null ? PACKET_LENGTH_SIZE_ENCRYPTED : PACKET_LENGTH_SIZE;
+ final int minPacketSize = this.cipher != null ? PACKET_SIZE_MIN_ENCRYPTED : PACKET_SIZE_MIN;
+
+ if (buffer.length < minPacketSize) {
+ return new byte[0][];
}
- ArrayList out = new ArrayList<>();
+ final ArrayList out = new ArrayList<>();
- while (buffer.length >= PACKET_SIZE_MIN) {
- int length = Base64Encoding.decode(new byte[]{buffer[0], buffer[1], buffer[2]});
- if (buffer.length < length + PACKET_LENGTH_SIZE) {
+ while (buffer.length >= minPacketSize) {
+ int length;
+
+ if (this.cipher != null) {
+ final byte[] decData = this.cipher.decipher(buffer, 0, PACKET_LENGTH_SIZE_ENCRYPTED);
+ final int decDataLen = Base64Encoding.decode(new byte[]{decData[1], decData[2], decData[3]});
+
+ // TODO: Store length in a variable for if we don't have enough bytes.
+
+ length = decDataLen;
+ } else {
+ length = Base64Encoding.decode(new byte[]{buffer[0], buffer[1], buffer[2]});
+ }
+
+ if (buffer.length < length + packetLengthSize) {
break;
}
- int endPos = length + PACKET_LENGTH_SIZE;
- byte[] packet = Arrays.copyOfRange(buffer, PACKET_LENGTH_SIZE, endPos);
+ int endPos = length + packetLengthSize;
- out.add(new ShockPacketOutgoing(packet));
+ out.add(Arrays.copyOfRange(buffer, packetLengthSize, endPos));
buffer = Arrays.copyOfRange(buffer, endPos, buffer.length);
}
- return out.toArray(new ShockPacket[0]);
+ return out.toArray(new byte[0][]);
}
}
diff --git a/G-Earth/src/main/java/gearth/protocol/packethandler/shockwave/crypto/RC4Shockwave.java b/G-Earth/src/main/java/gearth/protocol/packethandler/shockwave/crypto/RC4Shockwave.java
deleted file mode 100644
index 102f491..0000000
--- a/G-Earth/src/main/java/gearth/protocol/packethandler/shockwave/crypto/RC4Shockwave.java
+++ /dev/null
@@ -1,68 +0,0 @@
-package gearth.protocol.packethandler.shockwave.crypto;
-
-/**
- * Habbo Shockwave RC4 is broken, meaning this is not a standard RC4 implementation.
- * Thanks to Joni and DarkStar851 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;
- }
-}
diff --git a/G-Earth/src/main/resources/build/mac/G-MemZ b/G-Earth/src/main/resources/build/mac/G-MemZ
new file mode 100644
index 0000000000000000000000000000000000000000..8f529c66c1294188503b33e95f09b4b137cd4c46
GIT binary patch
literal 33656
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
a+mr7p!#L^iZ1imN^oV5|4U_*L%l`q!bQawJ
literal 0
HcmV?d00001
diff --git a/G-Earth/src/main/resources/build/windows/64bit/G-MemZ.exe b/G-Earth/src/main/resources/build/windows/64bit/G-MemZ.exe
new file mode 100644
index 0000000000000000000000000000000000000000..b80e7e8af56df195b685d6fb039b0bfa9a0f21a8
GIT binary patch
literal 18944
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
Lo=5#R@xcEBR)6jd
literal 0
HcmV?d00001
diff --git a/G-Earth/src/test/java/TestRc4Shockwave.java b/G-Earth/src/test/java/TestRc4Shockwave.java
new file mode 100644
index 0000000..cb8c367
--- /dev/null
+++ b/G-Earth/src/test/java/TestRc4Shockwave.java
@@ -0,0 +1,212 @@
+import gearth.protocol.HConnection;
+import gearth.protocol.connection.HClient;
+import gearth.protocol.crypto.RC4Base64;
+import gearth.protocol.crypto.RC4Cipher;
+import gearth.protocol.memory.Rc4Obtainer;
+import gearth.protocol.memory.habboclient.HabboClientFactory;
+import gearth.protocol.memory.habboclient.shockwave.ShockwaveMemoryClient;
+import gearth.protocol.packethandler.EncryptedPacketHandler;
+import gearth.protocol.packethandler.shockwave.ShockwavePacketOutgoingHandler;
+import org.bouncycastle.util.encoders.Hex;
+import org.junit.jupiter.api.Test;
+import org.mockito.MockedStatic;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.atomic.AtomicReference;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.mockStatic;
+
+public class TestRc4Shockwave {
+
+ private final byte[] encryptedBuffer = Hex.decode("724148504d776b51545841624c6776583352355750713537696931482f39684e2f566466507841756a484f62684e6b4d666c44636e55676d30396c625573304b71754a303966786a5555503342536c32336b633239715577373956512f2b43384577657a5a4d65756b6258347436775061496a6b5539517a324b654a53326e4e4e4c3351666334");
+
+ private final byte[][] potentialTables = new byte[][] {
+ Hex.decode("cfa4debb4f28d4279d0f668aabba13810b8f7f4c7917eb1618296937c852d05a5d5f41ffc65c4d63bdacf22ebea27e913f56016fd6dae11ab749e068d59a99656ca8c91bf5779b4b03eda560fdc2742a2421517afa6242470e87f8c45edfbc59e650826e4a4810b9ca7df31e1db16175723354c564a67820e3b4dd5b3dd7b39f73a30d96053c71c795ae5707b611a04ead268eb5220cf9e7b2ee23f6bf9e76a7e5a1e9587c408583af3a8b7be8d13000fc86452f929738350206b0c398fb8dfe09f7c1e41ff18470d86d672d1c8ccc806bd332f025ef90a9ea08d26a89dbb8cbec4615f4e29c5544aa53c0d9883b433493ce0adc12cd2c1914942b36043e3139"),
+ Hex.decode("b5c5af8ace02ab824f16481a18427a795929e7654b05c2373226b1198f13f684563abf8ecd2b4a519da36285fd4935ffd3bb249ba4b725acec1e5b28886d14b6236c41f299c8a043960d986e4d12e66a1f898b520704fef081dc635e4caeebe8707d5ad8d95d4583013c06e1665f74333b916bf5c1ed5840e2fc3fde8761c303933d2a76d722f78d9ae5750a0ee31139b2e4646f0f69c7a2ba0cf4dd7b3e57c69ca5dff11cb3da47d0310b72e995093420b87c5ceafbb9fa1055bdcbeec4c9bc2fc01bca67a11daa447308d66053d546949fd42e382d97f9f3be21ad71b4d1ef2ccf68807f78cc86150092dba7e050a89e904e17a9367e7754278c30b0a6d2f8"),
+ Hex.decode("b5c5af8acedbabb84f16481afa427a795967d1654b05c237382609191013f684e995bf8ecd2b4aad9da3a585fd4935ffd3bb249ba4b72572ec1e5b28886014b623d6ddf299787c43960d7b6e4dee80731fa87d520704fef08194635e4caeebcb708b5a47d9d5cf8301570644665f74338691daf5c1ed5840e2fc21de8761c303933d2a76d700f78d3ed0750ae0e3113953e4646f3169c02eaad24e46989aefb36ccc5cb9d4ba6b55e51dcaaca7fbc9f4e7081ca10e3a20188fd80fe81229b197020b2cdfc4f350c6716a82b46d7f6841bcc7f91b32c8bea09fdc3f51e19cf13ca2455de6b22d923b2215622f56eabd899e903417a9367e7754278c30b0a60cf8"),
+ Hex.decode("b5c5af8ace02ab824f16481a18427a795929e7654b05c2373226b1198f13f684563abf8ecd2b4a519da36285fd4935ffd3bb249ba4b725acec1e5b28886d14b6236c41f299c8a043960d986e4d12e66a1f898b520704fef081dc635e4caeebe8707d5ad8d95d4583013c06e1665f74333b916bf5c1ed5840e2fc3fde8761c303933d2a76d722f78d9ae5750a0ee31139b2e4646f0f69c7a2ba0cf4dd7b3e57c69ca5dff11cb3da47d0310b72e995093420b87c5ceafbb9fa1055bdcbeec4c9bc2fc01bca67a11daa447308d66053d546949fd42e382d97f9f3be21ad71b4d1ef2ccf68807f78cc86150092dba7e050a89e904e17a9367e7754278c30b0a6d2f8"),
+ Hex.decode("b5c5af8ace02ab824f16481a18427a795929e7654b05c2373226b1198f13f684563abf8ecd2b4a519da36285fd4935ffd3bb249ba4b725acec1e5b28886d14b6239c41f299c8a043960d7b6e4d12e66a1f898b520704fef081dc635e4caeebe8707d5ad8d95d4583013c06e1665f74333b916bf5c1ed5840e2fc3fde8761c303933d2a76d722f78d3ee5750a0ee31139b2e4646f0f69c7a2bad24e46989aefb36ccca1b9d4c6da47d0310b72e995093420b87c5ceafbf1fa1055bdcbeec4c9bc2fc01bca67df1daa447308d66053d5dd949f1c2e382d97f9f3be21ad71b4d1572ccf68807f78a586150092dba7e050a89e90f417a9367e7754278c30b0a60cf8"),
+ };
+
+ private final Semaphore waitSemaphore = new Semaphore(0);
+ private final AtomicReference cipher = new AtomicReference<>();
+
+ private final ShockwaveMemoryClient mockShockwaveMemoryClient = new ShockwaveMemoryClient(null) {
+ @Override
+ public List getRC4cached() {
+ return new ArrayList<>();
+ }
+
+ @Override
+ public List getRC4possibilities() {
+ return Arrays.asList(potentialTables);
+ }
+ };
+
+ private final HConnection mockConnection = new HConnection() {
+ @Override
+ public HClient getClientType() {
+ return HClient.SHOCKWAVE;
+ }
+
+ @Override
+ public void abort() {
+ waitSemaphore.release();
+ }
+ };
+
+ private final EncryptedPacketHandler mockEncryptedPacketHandler = new ShockwavePacketOutgoingHandler(null, null, null) {
+ @Override
+ public boolean isEncryptedStream() {
+ return true;
+ }
+
+ @Override
+ public boolean sendToStream(byte[] buffer) {
+ return false;
+ }
+
+ @Override
+ protected void writeOut(byte[] buffer) { }
+
+ @Override
+ public void setRc4(RC4Cipher rc4) {
+ cipher.set(rc4);
+ waitSemaphore.release();
+ }
+ };
+
+ @Test
+ public void testMoveUpDown() {
+ final RC4Base64 rc = new RC4Base64(potentialTables[0], 0, 0);
+
+ final byte[] tableA = rc.getState().clone();
+ final int tableA_X = rc.getQ();
+ final int tableA_Y = rc.getJ();
+
+ rc.moveUp();
+ rc.moveDown();
+
+ final byte[] tableB = rc.getState().clone();
+ final int tableB_X = rc.getQ();
+ final int tableB_Y = rc.getJ();
+
+ assertArrayEquals(tableA, tableB);
+ assertEquals(tableA_X, tableB_X);
+ assertEquals(tableA_Y, tableB_Y);
+ }
+
+ @Test
+ public void testRc4Base64() {
+ final RC4Base64 c = new RC4Base64(
+ Hex.decode("D6EAA2D902B1797E759D5F8C26175B93BEC1235764E6F26972A6D85343B259CA715CB9418A19CC984EDB617F3E9E0947EB5A7D46ECAEC26E1C5D62E33D226D39337B0BD4783F49AC6A1FB8AB0A14CD7CC6F3D701895EE4E8D3F9FF8BF628E70058A183BD1B32813B31060F1DDC9B35E58D7740A320EE731584D5B30536A01116DF4854FA37742E2C50B6AF4B9A4D0D4F6BBF9CC3666CCE45D2A525FB4A8E182F3C2776A7F499C438210EA87AB5043463136512958F4CFC68C9B7C7C042BA109786A43A08DA2B1E55B4D0DDE9871A6FCF30F0E185FE5192800CDE29BCADEF2D03C5CBE2E0A9EDD12A5652FDF896F1F79491F5679F24600790BBC84482B070AA88"),
+ 152,
+ 211
+ );
+
+ final byte[] out = c.decipher(Hex.decode("3270635A4F67"));
+
+ assertEquals("01020304", Hex.toHexString(out));
+
+ final RC4Base64 c2 = new RC4Base64(
+ Hex.decode("D6EAA2D902B1797E759D5F8C26175B93BEC1235764E6F26972A6D85343B259CA715CB9418A19CC984EDB617F3E9E0947EB5A7D46ECAEC26E1C5D62E33D226D39337B0BD4783F49AC6A1FB8AB0A14CD7CC6F3D701895EE4E8D3F9FF8BF628E70058A183BD1B32813B31060F1DDC9B35E58D7740A320EE731584D5B30536A01116DF4854FA37742E2C50B6AF4B9A4D0D4F6BBF9CC3666CCE45D2A525FB4A8E182F3C2776A7F499C438210EA87AB5043463136512958F4CFC68C9B7C7C042BA109786A43A08DA2B1E55B4D0DDE9871A6FCF30F0E185FE5192800CDE29BCADEF2D03C5CBE2E0A9EDD12A5652FDF896F1F79491F5679F24600790BBC84482B070AA88"),
+ 152,
+ 211
+ );
+
+ final byte[] out2 = c2.decipher(Hex.decode("3270635A4F714A4D742F43545551"));
+
+ assertEquals("0102030405060708090a", Hex.toHexString(out2));
+
+ // Test with undo.
+ final RC4Base64 c3 = new RC4Base64(
+ Hex.decode("F2FD7883352075B654143213705596EBE2D166331F49A8A9B750D7DDE580F77BFC3982AA7D28F5E92E1785005947194136275BE0254F91F8606EC09A05FA5161C87FFF5286CD9BFBC4A15DB06C694EEEB388E399AE72F01C5608ADA44C93373F9D6D34121558BA84C60D7E897A8DF4D8D96A3A8C31A6EA90CF7C4A57B8D6ED792AAF7607DC03733C5F6230CEDF6511F9F11B2C106394FEB2BCDB640CCB2DCADE9E2FCCDA040AE1C240BD2B6838290EE7B1AC81928A2224425CC1B5A3EF71B477AB9F1E9723A0F6443B3D5A4B95C3438F450BD3A57467D2069821BF09C5161AE8F36B9C8BD5A2A701876F5EC7BBE68E48B93E0FE4D4ECC9465302D01D264D18BE"),
+ 156,
+ 238
+ );
+
+ c3.undoRc4(4);
+
+ final byte[] out3 = c3.decipher(Hex.decode("4A422B2B4441"));
+
+ assertEquals("01020304", Hex.toHexString(out3));
+ }
+
+ @Test
+ public void testRc4Obtainer() throws Exception {
+ final byte[] initialTable = Hex.decode("b5c5af8ace02ab824f16481a18427a795929e7654b05c2373226b1198f13f684563abf8ecd2b4a519da36285fd4935ffd3bb249ba4b725acec1e5b28886d14b6236c41f299c8a043960d986e4d12e66a1f898b520704fef081dc635e4caeebe8707d5ad8d95d4583013c06e1665f74333b916bf5c1ed5840e2fc3fde8761c303933d2a76d722f78d9ae5750a0ee31139b2e4646f0f69c7a2ba0cf4dd7b3e57c69ca5dff11cb3da47d0310b72e995093420b87c5ceafbb9fa1055bdcbeec4c9bc2fc01bca67a11daa447308d66053d546949fd42e382d97f9f3be21ad71b4d1ef2ccf68807f78cc86150092dba7e050a89e904e17a9367e7754278c30b0a6d2f8");
+ final int initialQ = 152;
+ final int initialJ = 242;
+
+ // Mock HabboClientFactory to inject our mocked G-MemZ client.
+ final MockedStatic mock = mockStatic(HabboClientFactory.class);
+
+ mock.when(() -> HabboClientFactory.get(mockConnection)).thenReturn(mockShockwaveMemoryClient);
+
+ // Run the RC4 obtainer.
+ final Rc4Obtainer obtainer = new Rc4Obtainer(mockConnection);
+
+ obtainer.setFlashPacketHandlers(mockEncryptedPacketHandler);
+
+ mockEncryptedPacketHandler.act(encryptedBuffer);
+
+ waitSemaphore.acquire();
+
+ final RC4Cipher c = cipher.get();
+
+ // Validate an exact match.
+ assertNotNull(c);
+ assertArrayEquals(initialTable, c.getState());
+ assertEquals(initialQ, c.getQ());
+ assertEquals(initialJ, c.getJ());
+
+ mock.close();
+ }
+
+ @Test
+ public void testRc4StateMutation() {
+ final byte[] startTable = Hex.decode("b5c5af8ace02ab824f16481a18427a795929e7654b05c2373226b1198f13f684563abf8ecd2b4a519da36285fd4935ffd3bb249ba4b725acec1e5b28886d14b6236c41f299c8a043960d986e4d12e66a1f898b520704fef081dc635e4caeebe8707d5ad8d95d4583013c06e1665f74333b916bf5c1ed5840e2fc3fde8761c303933d2a76d722f78d9ae5750a0ee31139b2e4646f0f69c7a2ba0cf4dd7b3e57c69ca5dff11cb3da47d0310b72e995093420b87c5ceafbb9fa1055bdcbeec4c9bc2fc01bca67a11daa447308d66053d546949fd42e382d97f9f3be21ad71b4d1ef2ccf68807f78cc86150092dba7e050a89e904e17a9367e7754278c30b0a6d2f8");
+ final int startQ = 152;
+ final int startJ = 242;
+
+ final byte[] h1Table = Hex.decode("b5c5af8ace02ab824f16481a18427a795929e7654b05c2373226b1198f13f684563abf8ecd2b4a519da36285fd4935ffd3bb249ba4b725acec1e5b28886d14b6236c41f299c8a043960d7b6e4d12e66a1f898b520704fef081dc635e4caeebe8707d5ad8d95d4583013c06e1665f74333b916bf5c1ed5840e2fc3fde8761c303933d2a76d722f78d9ae5750a0ee31139b2e4646f0f69c7a2bad24e46983e57c69ca5dff11cb3da47d0310b72e995093420b87c5ceafbb9fa1055bdcbeec4c9bc2fc01bca67a11daa447308d66053d5dd949fd42e382d97f9f3be21ad71b4d1ef2ccf68807f78cc86150092dba7e050a89e90f417a9367e7754278c30b0a60cf8");
+ final int h1Q = 156;
+ final int h1J = 74;
+
+ final byte[] h2Table = Hex.decode("b5c5af8ace02ab824f16481a18427a795929e7654b05c2373226b1198f13f684563abf8ecd2b4a519da36285fd4935ffd3bb249ba4b725acec1e5b28886d14b6239c41f299c8a043960d7b6e4d12e66a1f898b520704fef081dc635e4caeebe8707d5ad8d95d4583013c06e1665f74333b916bf5c1ed5840e2fc3fde8761c303933d2a76d722f78d3ee5750a0ee31139b2e4646f0f69c7a2bad24e46989aefb36ca5dff11cc6da47d0310b72e995093420b87c5ceafbb9fa1055bdcbeec4c9bc2fc01bca67a11daa447308d66053d5dd949fd42e382d97f9f3be21ad71b4d1572ccf68807f78cc86150092dba7e050a89e90f417a9367e7754278c30b0a60cf8");
+ final int h2Q = 160;
+ final int h2J = 65;
+
+ final byte[] h3Table = Hex.decode("b5c5af8ace02ab824f16481a18427a795929e7654b05c2373226b1198f13f684563abf8ecd2b4a519da36285fd4935ffd3bb249ba4b725acec1e5b28886d14b6239c41f299c8a043960d7b6e4d12e66a1f898b520704fef081dc635e4caeebe8707d5ad8d95d4583013c06e1665f74333b916bf5c1ed5840e2fc3fde8761c303933d2a76d722f78d3ee5750a0ee31139b2e4646f0f69c7a2bad24e46989aefb36ccca1b9d4c6da47d0310b72e995093420b87c5ceafbf1fa1055bdcbeec4c9bc2fc01bca67df1daa447308d66053d5dd949f1c2e382d97f9f3be21ad71b4d1572ccf68807f78a586150092dba7e050a89e90f417a9367e7754278c30b0a60cf8");
+ final int h3Q = 164;
+ final int h3J = 210;
+
+ // Create cipher.
+ final RC4Base64 cipher = new RC4Base64(startTable, startQ, startJ);
+
+ // First header.
+ cipher.cipher(new byte[4]);
+
+ assertArrayEquals(h1Table, cipher.getState());
+ assertEquals(h1Q, cipher.getQ());
+ assertEquals(h1J, cipher.getJ());
+
+ // Second header.
+ cipher.cipher(new byte[4]);
+
+ assertArrayEquals(h2Table, cipher.getState());
+ assertEquals(h2Q, cipher.getQ());
+ assertEquals(h2J, cipher.getJ());
+
+ // Third header.
+ cipher.cipher(new byte[4]);
+
+ assertArrayEquals(h3Table, cipher.getState());
+ assertEquals(h3Q, cipher.getQ());
+ assertEquals(h3J, cipher.getJ());
+ }
+}