From d0c0e05725ee86b13308d8c6f165022de6e8b863 Mon Sep 17 00:00:00 2001 From: UnfamiliarLegacy <74633542+UnfamiliarLegacy@users.noreply.github.com> Date: Thu, 27 Jun 2024 01:00:28 +0200 Subject: [PATCH] Update to Habbo Origins v24 --- G-Earth/pom.xml | 6 + .../proxy/shockwave/ShockwaveProxy.java | 9 +- .../main/java/gearth/protocol/crypto/RC4.java | 106 ++++----- .../gearth/protocol/crypto/RC4Base64.java | 195 ++++++++++++++++ .../gearth/protocol/crypto/RC4Cipher.java | 21 ++ .../gearth/protocol/memory/Rc4Obtainer.java | 165 ++++++++++---- .../habboclient/HabboClientFactory.java | 14 +- .../shockwave/ShockwaveMemoryClient.java | 83 +++++++ .../packethandler/EncryptedPacketHandler.java | 18 +- .../protocol/packethandler/PayloadBuffer.java | 46 ++-- .../packethandler/flash/FlashBuffer.java | 30 +++ .../flash/FlashPacketHandler.java | 20 +- .../nitro/NitroPacketHandler.java | 11 +- .../shockwave/ShockwavePacketHandler.java | 79 ++++--- .../ShockwavePacketIncomingHandler.java | 2 +- .../ShockwavePacketOutgoingHandler.java | 37 ++- .../shockwave/buffers/ShockwaveBuffer.java | 11 - .../shockwave/buffers/ShockwaveInBuffer.java | 25 +-- .../shockwave/buffers/ShockwaveOutBuffer.java | 59 +++-- .../shockwave/crypto/RC4Shockwave.java | 68 ------ G-Earth/src/main/resources/build/mac/G-MemZ | Bin 0 -> 33656 bytes .../resources/build/windows/32bit/G-MemZ.exe | Bin 0 -> 33280 bytes .../resources/build/windows/64bit/G-MemZ.exe | Bin 0 -> 18944 bytes G-Earth/src/test/java/TestRc4Shockwave.java | 212 ++++++++++++++++++ 24 files changed, 903 insertions(+), 314 deletions(-) create mode 100644 G-Earth/src/main/java/gearth/protocol/crypto/RC4Base64.java create mode 100644 G-Earth/src/main/java/gearth/protocol/crypto/RC4Cipher.java create mode 100644 G-Earth/src/main/java/gearth/protocol/memory/habboclient/shockwave/ShockwaveMemoryClient.java create mode 100644 G-Earth/src/main/java/gearth/protocol/packethandler/flash/FlashBuffer.java delete mode 100644 G-Earth/src/main/java/gearth/protocol/packethandler/shockwave/buffers/ShockwaveBuffer.java delete mode 100644 G-Earth/src/main/java/gearth/protocol/packethandler/shockwave/crypto/RC4Shockwave.java create mode 100644 G-Earth/src/main/resources/build/mac/G-MemZ create mode 100644 G-Earth/src/main/resources/build/windows/32bit/G-MemZ.exe create mode 100644 G-Earth/src/main/resources/build/windows/64bit/G-MemZ.exe create mode 100644 G-Earth/src/test/java/TestRc4Shockwave.java diff --git a/G-Earth/pom.xml b/G-Earth/pom.xml index 36e305f..03f836f 100644 --- a/G-Earth/pom.xml +++ b/G-Earth/pom.xml @@ -345,6 +345,12 @@ 5.10.2 test + + org.mockito + mockito-inline + 4.11.0 + test + 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 zcmeHweRNyJweOLwB#Lo#6!K+CAPN!0rp8GAl=+I~$jK4N#3X@)5Res1isjgn(MRk+ zC{AUUoC?wOmfmu^=qk52eWfk8uPrI0tQ72o#tBdeDVK&DE=l;x5hf65NdkfB{q~$W zwyXs1?ON}Tw_bHt=FIGwJ$v@-nc1^vA7%W~XUEP=5(HhLAP5BrCImY&283TDB?uQF zNC+NJi(_%qs-~q&D0lp$8xNinB09Zjz~foov~u})6skYiG(Jy*Cm@>wSYnUI6Z7}R zIDjaZ-b(5W@_R2)aR{b6Nq@b z{2Dx--kt`P-YL!>!8CWj?4f!05Q4|!bS!sx7B0DQnL7JgR63k3K>$pT1`|yRG?ysP z<5?RX8-@QFy==Z{eoRBq9^$JMf>8O=auK(xZ9b+|224*khbf{kHs3nEp6G_IHQ`QA zcP#Ry^hgHmX&o;wT30jF686UY$eC{>w=sl=T2=nG^Fbt7V|5H`Eama|yfJS!Jf2=l zn@Z2XQ3(EOda?G1-|O?N_eN;d=E`p+r&q%15uE*Gj{)UFpQ!ZKM41xErPs~rY5Y;0 zZ2r!tjI77w;e>czF1=n(uR5zvHa(he+0U5%b-Ql7p~>k|XD=p%DmTH*u>sT5%R5MP zvT0o+2z^#U0G+Efn_( zLhE^gfPdOj60Sp<0^v3w%tZLIr?&^|;T(lVkJ}m z=VjCnK&YY{;|2UI+YpWUyDAzhz*le$LlPwUxFmln4V;jk`2~L@vFTGORsB0ckVNNk z6vn1Wn|5bJz?XwBlM?TnUGfP@J}sqc3U3tzcWR@>;FdE= zAEt~$d@^hb3>=HkaVIG|>9d$n=9W*mffV4XmBbq0b-=JxSXqqTaEmW~fZgbqhdAXUm+4TO(B5*1{FUiX-Rxsw0 zKbKNFEGxmxQ#8m=XV5cc>s%mFHsq9*okBL6k>od)q^_xUZ5lS5%4C{sC)}xi%Su#s z{XxiNGN%!wRHp?DueOv*@+?s54SQx|AZ2zz0%HF%40`%|V3JK2DYcr~Kg4ffrQtDKIz=@oxpd1#f2u(q#Ipd02S{RA_t$emw=t`)N%LDsKQ}gFIYFi^ZMVYU#${y5!jwjO!Yx02jvC0FsRu z`3`ZFTRy5>i;Y$xLcD`+xk@xm_+S|_rY~>`fZ|V>=(YBA4_5mbOuDk=fiyk^u$^cMw$RJYc+X{)hL;3LW8FG72sCF`y4LN6(4b5uxmt&v zSZBaap~Lt+sLlicCGu*W*_C;=S^gk>0hA>(KR!@|lhB)vX9|S$Q3J7n?e7C+P zZtOBiM+vqe%W>+o+-Md}j#Hw^Wf04*$7WYRa%lidV%hzc7RFk?r4=!a3+1~Z90_rg zZNEEp(Nx%NV{1sAqi)))R;!PJZe(?1g{xH;)Flr#^#?UqkT zPmL~H(Ye!=__T91~8BYd!&bN5LxPFf<S6;zbDZB9c3P~*bBu5gBM8UX9 znZr3)&*%=8Xj0h7^UuNjEUIc)rM!)^0QG&0`Aw`k_5D#^{anny2`plME9xt=!1w6< zNKW|KHMCpHAGqcBl$UYpvF$|zlQ40q?_&SIU`Uh$IKjhSEKx_CnTJfY6TE*@I|&m( zHTv$NmP%GBS8-13SSJt8QajnA{D9{(E?a1r$3(o6b4iL_xf4y&GI|K@yRouOZqjF1 zMbCqlE3-2_{5hINKOO-B=8@B~lI^yzbGNenMZn6kx<(H)Ou3-Qj9y`mO=xwiwc5q0 zG>WA5psj@DYITdmv?feR)>~BT?hzD{COCk&WAd%dvf@tN&#EcQU>=g`@MBZ}$JC*d z`yMS62R0Ce|r;o%K^ReUR2P8}?xAA19QXAOu(aAESOr9%{z! z0YkGr=9jj^{G>;e>W9Q1U?h5O!oW#GZ^DFs-X-sG4IS3A-2MKNs1A%MkZT$^$`TQGX=X^u6{5RSzNZ^uA-C)zDINLzePZScTbT|@WE;B*V zzHGuk(VA}&O`u8IoHV1RgxtxkmQ$dlybQ*_V#P22C}+jXm>3-sS1DIOXENRQPoUD# zfF1oXG~?@3>hnm6Q@+S7sNj<)#V4oV5|AG(4%56z44oNy3x$(=&MUTV?k}ckH6n^n z>KEZesG?-{;;d3EZ5pPs4#yo**^AI;A(CT?RKoYwcH``D2A!Iv4o?87W6CP!pGcEU zU<0y^XV%D?=T zTYP=UR|lH5J{lVXMm71cy3%2%(1t|Ew-yXHsU6HrH`Z^9qKDlK^i&~|3TfDv`Se3V}phWK7bi-;#4C+ zPqICnZMX_~gQ?ginzTJ0TL+yCQ#B703G7p!!$u(-oW)haDd(Xb<#uo)D>&WKnJ?>f z3vr&0#iStv|&&IK)n5t_7nu|cOCBCVFX)`=;c{v~;~QrFrk z{Zr=UI-|q1is)^7NA7?N3gh-ycv(~j*jwZPb0=#GfDNmnBjZ@9`ip*FU?OvGuhK#! z5~nh;=}_R3ZkJm=eq0iNbJ0Et%XpHaLoqxxIKAW8EXjLzE!$3%UAjSOc!-F*=I)Vn zJEghLJ&r@k&e$w>+0OMQw{EvYZmCxltZX{!ACwQ=wyQ-+!+Ms7SEfJ3fDuCKx7mKW z*DyM!IBkt4{T^Kal1kaFf&%+=-` z5ZpQ?bJ#YB5x65g85M~yy^ye2R7TUEKu{^^oy4bxo_Uxj^+1u@iZ@HTq0Esn+`#rC zA^jXs6Q@jK|KlvtB=&!sC2Ga~@3O?q__XXQQ-m~~R0G>*3h4(ZG1_!s`*dOC8`-cZ zjD)gbIBM?5hR+v9Zp?;_!pL>m@Oi>WMK(NH7`Zqbo*}4%q;@^9y?As)V8EyYq8}R& zOuA9mkT^#e!p?Xf#^5?0z6baRSuo}IAnZf_0>m@3!j)O+IY>+LK1u$F86@ynjXF}` zPyR>}o%_h^gflnXKue%<^cHv0WrCd|$-|D!kzKs ziI|5S7TEtZ&`?dKuH*t&vIqKm#uuamQQ54J-;WBN>9=SxXG)sca)cSX2vUg^qD>! zAjJND=w_yl!hoMlDF#vPhu-j=V5*B}Z z^?aR5WiarEIRYy6i31Oz5?R&Q%?`NH3;7Gj&^r@{J}gQcodeIdrD*7=sY!hDm}Bx6 z=-~qRyiYqPe+Y-6B=2JK3y{l?XoF|saFFQ6nWb)!zZg$z?jJ!-CDbH7anLb&B;dqX z4})XwF3054fj^i9$EMTt)j_;(3oQchUYsTiG4akE0`%y}#Bm)_J3%HQbwZ!h;KVO! zP_H%!LES6fw+NLb@+Syp1o{ADNPmu0;+;uf)#fWeIcJ;bBIGqDuE`&gi1fK45(mhR z|9dGEW(#@wIcxtneXidnerpO;EF>)mQ-#R&YTqvbGwBReGkL-c_|mpPFAW_v$Cu!g zj5(h^3MGcNHyssD%@HRecs!X)7rf_>c~bn4FteTZ%>=$LWrYsz-n} zQr#?nj0NZ}+wT?^zPbJ)^3jzf8_n|2rX!im93k=Py*<|Cl-aOMMs8yjp+;fWv3>kw zrHriOP0!L|&)R(WmnuVYih{W_GEe~Qhz;3{n;kaE& zU2!)u)4u`9p|lB$FYT1mSbKCVs$qwWw&DjQ-A9=to1K>F=|12QvrG)1k{Dv!<)NW; zao^sFph) zIy`rV15=Ye8BQ0UXtX?97;8-&E@a11GBDio5$WYL7ag;R)uS*eLa23dU*e=3QuoMwp0bXiKcb zgSGheF{CZXOHUzqjzC`#Ct{-h5b;Y0Iolch6tA)^_j7zybII>Wndf5{<7=+oB9_4- z;HKZl$_aeXr0wj`+1nYJvSt{OuwbQBPqCDoMH_Ijx_O(_&HIm-mb7^*zf$SM^-^LU zHhg?pW>$fNZ?I;VzOX|<&7Hi=MK(#Q+(0K@?8)m_(D$4N&=+E)V8lybUn#%N1=HP) zk;G?gn5ysM+lyQNNVxz`zvg6|zIQjlAKP369|bMRW@7-U;*q&T|Hf1~zS&*_c7}`j z>R_&3x!htK{7OTbeG_M&^wb`(&+ZZqCd_=fwi>Ry%q%G>z>ZjPo z>72KMJm=JqjWsmm1oqvIB77WnTgcO|T#9T~J{1Y_u7DK#0+_0KhDgAlIb9?10eGW^ zXKM{JS3bTqdda|g7?joHm9kT-u>&=TC_MfeQEDHD$7kSzX6=&el89P3QTZtKMeRff zabSi2gL(R|z5Z%Q<9~zC<7cK^g>|GrW z$0~g1#;-9@O*(m^Idwf8Cx3SIJ#S5|qL1J(2L23%dC(~~{^z32tEU^A<>#b&oGgc< z&yV2vrvC;r<3w)5iEu7)8_q!jOFY{`A8hFmw?8%)ACcYoh}==Q66Z#Fr-OY%j^U`( zh7YoG;m9<&TfHQ>Nl3IKp?)*IZCB>f%WEOPOUv22g1xQmUCG|n>|M*=cJ{uCy{}>K z>)5-2y&Kuv$=-|DTVn6)+1t(DH{wl3<3$tdVQnqTT>8691z}4&Wv1W73U9~xTS(Jk zx!ppk{gkp%Y7eEDR(zUL>nLvTwk0|xel=?ZP?x)m~l=>#6_EV~t zQXf((L@7N)lJ-$*2BlU}s*F-ClyXvP5v6EBr>~*ZI!aX{1sYio^c;J#vRD$>=X|i> z*`t+P0cm->h(+XVpeGma!)KU{t`>g9+l(@zJCETI6@oCq@hwX7u2~bV@b~(KyQB9M z2}|Rl5PtA!UVKHfziU;IaNFJY+-|;mLG&K;6=sI8u-)Ibjxf4|zN^f4-&1Z5;rEwF zQSC+EV5i?_j)l$PZhy$!9SOJjqftUbSvVf@A!Y9JcZDMx%w68@sFvFuj`>5eptsY! zbYZnQ=3Ue2H+y4OnXSEd_g-ba&Q8^Xp@#!?lv!TEpBnOG@0AI zAz!CI!nhHvR;$fgX|1wWTWhSf);g=*T5q%3Y_>{Um95%VW2?2*+3dFZN^7O9va+(O zvbwUSvbM6W(q36#Wv#MRRaRA1Raezi)mGJ2*{kZSt<|>b%Id1>>gt;6+UmM$dv$${ zwZ>LcSyNS0T~kw2TT@qKuc@!K*4k<-YpZIjYinw2YwK$5we@w@I$K?3T~%FmT}@qW zU0t2MuHJ67+w7J0DtooP#$Ic$v)k?U^8HA8uUb-JcB~0UV#ui|5^fGLkz!nY@vd%jH0EtvhZC{44Fl)% zuZgcU(=T$79|?AMlZ=<0T?JgsLR9dU^Lbe^hH4}8t?7~alfyn(;EwfBVD6N zOEB&d8wD-P8(p_396?2%p;B%3b_;51DSn{o3blp1x;ys2j}1V|WWr|N3B?zcC)&fb8|&&Y;f`SsU-dToN2@H1(YR zP|)ua8YAI#{!mM>+b^iUzgy~$hT{=n2w*tkjRnIYCNALK7>-dV!1IbokmM)0!)@zs z35L;Cpb0C&@lIc3C-Q`a;ZVrmM!BHpk2P*+@#A;gH;4Sa-N**5QQ{!FOp_V!Hzd#v z>f=gU{E@C;Gzx;8acbJ{Qw8M?b_HWiy={KK52RbvwYA8{1T>nhb}u(~$$Ebz&>8O0 zq;zvEU>E4s#5qMz(ABuwLn@#eo#D{hCX9@agut)~@JE-$F(KE&fg*W31FT>nvb)2< zPz+<__D0tFSuP)vWif9AvccFck8sBe8$-3GHxvrTSS$YVbkC-_JlN$AW7L@*oXxcu zZ58xTtm@8!0%5wLfPTOR6N?4<9X>q-lh)fPT!P<-&K63b04Gl`Dkv~aEU+W}5W*f( zih6wk>;t{IfIimi3mOo&BJ>fgFDSt8ne|1*6AKO?HWv!|2833GhY+?SC5X_a0u{8}$eK6JiNbohMXyf4ldiYHGKrp%Zo z81W-d@fo<~|1?c7y){jkx^}v7zI&!HO`a)C-#t?hUY{w<`0gx0_ZB|Ore_H=Czc4a z?ko{XS}zuKzr9$v;Ny#huWg$nT)1JbAly4wm|c5`a1r>(KY0n{C6JduUIKXur`9-`utkqw_ z#@S&s(>Sj(mLkPwlvza}x;s$sN4y@~KjR)Jpx048>wwDlKWmJiJ>CvuL-ueNEHg@t z#s*G}+Oc46Z+lIpb1lyo;>4eU_0Ng&m%pT(`sYKr{y)|Fztiet9%G%LSBs5@%?bSj zh2JSCXf!Hj2SDIZyY#K`HC}JjQPrfjTaNM_D1Vig(}y7TIE~ipjdrZOMx)(fv=%I8 zk>Ez*`@o-iQ03!wd~{^b+45LyJg&QsR{S;Tb8gP_~ zcihpZxYv1n>4hq+4b?-fDqP3Qwfx-py(?5aTr9wz6CBn)o=vJy%kgu3_FTweT${w6 z3Jz=d3ph;wv5lT39M*n+;N!5?UN?s)tLVaA94_MUKo0(QbIKp)u-4vV9M-;1|2_x* z*&O^=IIQvi4u_}m_D^zn3Wq0Lsmep+&%|N6`G=m%a>{Kv<%>Cdfr>78IjprG$tk~| z!`gT6f8(&$-tRfA{eQWg9MM-XrujlS}MR<$2ZwLq1%IfXpaxJ+aR9o;Bfc z3>Qt{{)d~U_8pkG1wCeE9(K)zFm(&A8w08*PFE~=dc46{CCfB5c)WC@1p`WW!*mOU z$LIHU0vQ()urf0*Ti@kjfBY+qs|%{m_vbuCFn-&;3_lR3@?l<{#$Ui8_#lV>QmVoyIPB&1i_kS{?`q!O0EhR^QSl!k z81&!xx(fe0FaHVW{{;^Jmcz5~n{%SK6>>r()N%N~czKe;M|k-!35F9v`+INOIJ~({ ztv^UGoGgQ_D*PvcLHJ_czjp}+e{XUA{>sZWzt|M~Hk|Msyg!$5_yOFDLHylHFzRc6 z&oRK^zjAs}j(-QQ{~cbg=kk7-!*e?63>Hmzw4{>;i<7@uI*Ey{D z5kDaq{Ctb^TL1?gwSP62#|0cNp0CosoM7~63zuIVFW-KtTE2|nVyqY5et=-`x0S;^ z9DbC;-zFICC%8O*z~SF>cq@m$&*9$?4E|m&Roi=-V9X!Q5BeIxQ?Twh{U*#yBLe+B zMN|`(<-nf+Uqk`@-8?F{pDGxS?nqer*nXT@JiB z2fif-?#zL4ebhMm-_L=6ngjnb2Yx&U-kAgcDF;571E+J~<2mpI$a9>$a^OXf>SBaO z1PS4Kgc}f?2u%oXgl2?g2=sqcZbTp*+k$X2!a{_b5a?=S7lH%f|0Yd=HqQ!&x(SCP z6@iG~@9}s;;gH8>t*){Qp6+lo*jvH3&KzuCygM7aK9?HX+(*gPTB`+5FkG?NAM!_n zZFEPIKT;ZqR?rnu<;;e-JL?Xq^3gk_%E#`ID$lw@s(kDYDP)hkL#jOc4yp37JEY2U z?vN@Uz2=GEAyt0X9a80P7zU$vNR^M>Ayt0PJEY3T?vT2tyflDYljxEu^L(~(&o3*h zh;8WhH$}WrzmExEQAqG`5mcy~JM6MxsG^N7Yr{oTO9BD1W?(7duCEHRLzcySO_2!x zqA4FH+(*X5s>)L(f&m5Du2==#@a2uULNN`ys>W8OQB&<2jow)!b2)MX!Ct@5vnC!0 z_#?0jVU=}ZxRBS#vX-%&(%Wv2+sn=#!T-4cMmYu9QBJFDwWGbPwAyMg7x|rK75sX- zinh*h)X(qJ5BLa z1?0}->{U@^t+)Q2O>qWYxTuXr1M}hUZ__5gV6DIfUF%uGA6g%bzyj80L1iVA4_m8P zNultl6mc2eD3%SE=LuvQc2~4`*ZRknJzJ>bdbKtZ?pf%?eQv?nhFozlY`PXKx*`~B zXA-81+eVwLsfOOrpz6A8zz!8KHZPZBEogXERn~08`YfQP5?pI|wKX8Yff{QiW(5Z- ztu^XG7_VPw7OV3Y6Pc~BNQF9i`3j7bR$J?9%g>tr|6q#g9P9TVM+5UIR9#0iEk9w{ zZHWKfPF7YC=!{3(watWGYN;*KO1A28S!E6XLjxSH59p=ZZs z(G9HQGPEvoIn>y)bXrYimL9}?rC?XJ$5emHxq7&s-N8D(1;&L1E*uN4Vuj3@yCB!6 zbC&CeJ8iOD6Pl-mVrG?ixa$WxINRx?x|VW1JZCwa)Qq#8Jz4q8&7(STH1|!814adz x>qSj!PM7fx6z(}Wr`IPpllyzl>96se!^wi)`=ViwdWEgPP(NR%RCpx-BF*^eT%)r15FeF4#NkRyTs9+cbLV%JXL8Wyf z5V-=|Y)x(IldWl+wy90G**4V}^9M|#M59t0YhtpNG_ejPRn%-lG=}f@p8MY6@-J?8 z_xZl(+0T>b-gEE!zUTdS-t(Sw&s=WqTGA**NRp(-p)FqR%_P^2fE#YuVUV^>ePhP< zh`cvu6jW5Mu+-JpK2%@2+_JQ^rlvMvdC+gEZ>X_U)>wS^EU+xEE%VQ^N>YA~B;`d+ zk(L`CDOcI|NxFzjBMg!h3&?{CbeS2oNu}^A*E&hMm_ceS^#Z^~L`vRP+(jtu(3snY z*#POymLxaxn6T;syNf2o2w)uA$4r3U3AlFxxC)vT@HYl1R^%lR=#+IZbpii%^ zUP;Q*+L#acoJ`>gKfcO-jP`tTGE0X{_vrlp)%^w$41OWyeo! zUR;~ny%w;`?ZkmP`cuou(7`_hk&qw zz##{$&MM>maD!WZU4AQg+}QSp{CaTjqTn00;Mq%z&%a@O{-`nQh}_>D(zk6ecc8s- z)7jWNT<2fHrF8XxyuW@wx~UU4ozW&J6Mg2r{EA+v7}`Z*mq2biq_q324v4WeTqQ?a zp|4&(-g(7AkX1#5-Zg?S^zk%Fsvw_&T|QiwkGcR}$`7SnC8ELp*VSJV-q`YENPDs+ zJ`dPE6h58ns`HSKW_~>LW4XYZ2QTHp$|h@mbD6b3lJl(507av=z)RLHi&v7n+x7)d z8rwc-JHghTgSB4@Yya5QuWu_hN8RBz$~~*zg-ufx(=23~gG~DqX}lzdeA@C5daeJb z(5RhyAAOA;F!w(RK)Bnew;E$BgsLzOs^)hVTMHJ1?n5%BIQLn7P}e6P*+DdLPaHsT zP&S^rG{54c9YU82p^({dH@q1KZ^n0CaSzmiVR~6_m5;-h?Fg5}T1|-l`sV#n&96lD zzj1DOxP1FmX+UT1TD^}(;QbHWd*9xH&uH+*+Pma8TR;r`<64IUmi8=t`z7+{7|GTu zY1o9p3s%$QGrN%a%*{WX_O>K$Peq=GovupBjnkrre7=%Ht&nYaHqTg zaAq`8!*L)Y+OHg585L?ocNaC6>!s%MDEJ`i{>cc}j3cB3vc1D9NudEu>Y-m;s&RGa zPHPK*og1tz7{5E_k$cYaXu09lIePM^99d-yJqWgoLyw^C53L|-pJu_C!s9s<#7&%p zQ@pgB=2mow15UOTR!Iv&Z-Tv&n}X=$W{Z?xD8IiTGzhT$Xf{Pe0UTRF?Ftu#9#yFA zf88Su4iA%BXaq4P5`$VyqpicnmI!ow`!;tA%xe9cvE>p3I>ZS!p=XH65Qt8rvD}T= zC`Qv_n(p%#R8jw%<_Sq8{R7anPZKtQrnA6W27HBLAFj&KXZiV|OU@4u7a3nDwicDQ z9yPYEpNd9#)&c}pK8RxGLDM!cS^(3d@2W+_-kHaV@cXx#eg>IhZ>@Z)q>EnwIT1Lq@Ek((pFx2G}g3 zOqUM{@x%FgP}!RInKnlRZto{GI=6}L>_0@dIjm?MJ@73R_&k7Tj>|7YV0(*T)^fD1 z*qU!_-3{-yzFl?h-oSN=u>2~W=xq_r@?LV+GoV=pyUh7WQ|7`2p#zsG{fqvVKV2A_ zf(R)>zNBPof^29Q4J+d@(#@hB23riQGePG((8S5+2ssd;m#ZV9n#&@h5k?Uv$_>## zMzPKu2QXS6`U%N)oOpcJ<~+v2*+F+Nj_^e&Uw}V#5O@vHB_j>bspSc zN`q8m%Quk;m%VImFNVx7rWzl=75D9qi1z9TeS1?xbh|IYB&f4Uwo! zR!6C`^WZZy*g+XEKQt91OF`}iQbtrzwMB$#XNS1!yrK*ut8}5&VMgCk7)7@p2wc=| zwhlzSc$P@?ONcWkR}*oj$*SQYVvCVQY*7(gs;;P_Hj$xQnQemyy6K67j9Eb>=!1k3 zJZ>T$@Eq|#MLejwqK5sK{t53&zDwDxr>_FYVb zq3dB9B0LINECzx?hcJKHk0OuB-D1JU0c2da*XZl%KA}T6M^P^NlC)CdFhBGZ8nt&= z*FuVtr>~sqSNZE1*p zWAks3W98phSqc|IO>?LSq9`NeA``FPXa!4FF+PQ$T5*4;{vDFnzD_9L>3BojiD?RBtpw@D%(=4=p6mesDv>|yD1TyY zpybc1+GDN#Pot^)UjK{44!7h5Qr-%)ghcr@<5S&2Y|8;->vdQ^cbbK8N}l&moIGD4 zR}Immg@o9t-%r}*!;dkAp(il4VumCQ5&0x-K}g0Ua1n$2Ch$FaIktzxByx-JVSgqjF7hV`kRX)+z=gsEZghxT887mtm6kH0cGxp+ zh<*F3hgk8*5L2e@s*6MMFoT9z!^$eleezKAP+9%W_P49b7N7Y9j~bVq2Uk2I#5W^2 zoA;j|_+vRzWB=obisqrIt1gYUwY9|4xEYvke4#~Bs9OZ}U7Z~QQ#C_mrI~->oYF`F zY0Md0?gblmM(Ab5@mBbk_%(kVVSM~NT+&{l$0AwsO2#KXq+1(261{_l%)-!Be-l&w z1~GKRtbc?^TEq~>7!!;(iQFI|XglFs1Xcb*3gUT%)U~1OFx3+!;H_N&udBbojd@US zKOnzXB`vIy3PU%1ipQWMh&_2Pxhb(qzZh|#XKVzp=w-9O4_s+K>gqRscW?KJ$kwi^ zDMi5OjlRRpZmYDN#x2r9U|NL~L}W|zLchU8OtA*d*=lx1Lo6PZ`p+URXvPV3{0BW7 z4yf~!fHeX}sVL+mCdy=O5iX$U%<6xh9^N<~a`s1;!^+Mpz6amQpHxi?*%3yI`yYpx z@|Upu-AZTHVBp80ThKY9f(*T~0Ua8%^KMqw`AWs(HO~HUvh)N25hj<~1sV4#0 zbtKL!rXqlM9L9W#1&LLT6mxH-UcS0&O6V@qiTW+C(+Auq^5-0epAbT%{seNq@XVix z#np199y6Mm8}5c1q=xC-L7_HG1Ot)wzs(L!2fQ%!OU#fJ`h2ex`Z=!lDwl84B_2pV zkH89f#pPA{3fiZdUnPZh0E0M-*c)1dD|^@81%>E5Gev=`d*#(ThLN#%)4Dq1cM-ia zKAAD->NY;TcVI?m$0dY{=>1K^>f?l;*|RdbLh>S-=Y^Jmm3;XAd-rl=%?~Xm1?Zsy z=^}=ng7EY+tNogCh;Lt}Z@)8Y<{K+5t}l&QUq)WKh{nOZ&?Xp$6c_Pm6dp8(@I3Xl z@rBo9JoCtR>%}uwVLl#r`l^(aNNt7_LSLd0%rUp?typTkaDeHX_v!E7D>#-n4_zEs zVtk>ic_=DiZ62Bt$Y~z(2As`9%LCV?pFjWxu1_Drv>KR_9)tO#0osl0%$4V%tPy=!ol>5SeOL}{3U)wJ9_(C_+W#lJrpYuw(0 zQPgtuVSC#jw;#l1@WZ~~a97|)-1dsw!QgOT;Bxu!u0y!B4!H-nhmci_zWwFRPOMEj zA;I{(SBD3ltK|L7A57^io+i0IgAyj=6Lmngy&C**N^m&B*b*&BL1&%f))`xV0vnpY zj5N0DX?i+ge8p$gN#(n)!2}j-eX`gXAs@DOxq}TWjW6h}!4n>Ne|PAT?hh_*?z^E4 zq}_cc;|qVCy5IQx-l-v_ncn(jo>3?Fh-Mz+3$fN+w4D|_?lC@pKt9|(a7lOH#my&f zfUde3q3#o=sfVX_m&?)C8U5wEu9c=9!7d%#Z+zl0XjD#5DE)T{-EM$xmjHqXs1e1C ztv3T{d;zOQMa&6^IV$&b_X{z{Z)nT2UIH=4O;dZO?iGz^3`~8!`HRPlPh3iDtyX#v z*Ix@i^nXCkI5A~nIlF)~zJTx?=LJa8dI8-|3hETk8Cxk@{+@=B?DBj5ZmV1a#x=KL zj$8eOBu$sCA zhvlA|rJ)<7bO{qugF$6SzcCE#RLP^Q-TfwbSvT-=tiWg*D;x00pMo#)(#+psRhiYh z@&=5WAev!{lDk(QA0ECzYCikes`&P46!`;-cu>(aO-!(Ei;zDr4DJ15czAL1zG{(7 zmAF_H)jo}e$iPj?ZZ6%^7#X;nRg#=Z;lMqHTOj}4@DSTpM#cgS&k(4!h3E!6vW8y{Vqfvq=mRSL{ALL+OhT5ShfgS zQ2ON0iKa000Cu~T=f$UJQH`9XlH|$nd5PwCq5Oel6&NohaNC>0Z5L@+JLLDMnV9cL zgFA%={SK8xXj-5UDLT#^(b1q%LC50$&k&b?KyJMgYmGmmHltQ%lPGMaSdjAHl27E^ z6}TSiO_7htpF*(S3dymdC$QO1b_I?r(+GZaz%-)FM9S3mX6L*p`2^%(*`qGZA{T}3 z=7tS=u`x-Lo;nLewWkWPZz8&5+vn&zW8*N^Op>%RLp~eYh@nRUbpt*uzX^K1d_w+Q zK0;5Hme9v%u<~Bqo~>`cjUvP}a6LE>BXH^TO^I4KFF!ONj=+p3KcKgFA!lI`AkUiG zBhS{;KB{8D!FlP#8)`?@n^*VKb?emIsS@y2r)GAyPVK}Xmk+k*o*TB!?9(imFdocU ziJRuy|Mp~Tg6osESX&@+_P}RY2#HKU(=TYEpDaLpztVQ)?70n}-@kYG=AGFX7B}w` z=N@rBD9*j&d`O%Ri}NvY?i1&e;v5p^Q{p@*&Zluc_;vYT=mCVUbP)~=Q~Hrc@>sl* zAIGn8sKtZ?oLlUr=uK4C9()@;+n#!7oEOTzZ%fEcdCDOf* ze)B%dqn;JtwA^kH1lbk-rOSw-uCgrE^5{3?Ej6Xf{kWpmvuhh_$^f)1_b;!lf7r6T zv~Gn8t*Z_AYXX&})t33$4ojf)!D_#yG>~esH9p#yYRhn<`AETM$Ip5rq&S=o zSCTEso|KT3nB+)GN=i;jNpdE+l5NTMqJWJhvRa&mG?vNPG0VoR~7B%~y!I8u^Q zl2cMroGC7+&1rWgI1`-?XOc77nc{RhT`s8Tf_N8LyU@%95ZO(=M?He1i{A9t*Vopw zQvn!NEVv`e*`<}${<4DFoSKH^`SrC+{VP`ZS4j6X1nwzE-@*OT!kQHgb#=A%0e@M3 zb!niywtjiI)jf6on%vTwvTDByDqXQ`c5QurWtj@7o!8*6f0%hG)qHj*#LX&38P{aO|AJDve%8xh%meVM9i`9`)s@6Hs4vW#O2Dd@!mQ$CNST3r7*-W(>Iz@5<0Wl&;4{;h-0!Nr`q`5**_tXvr z{JurCN~`i8drveMlBM#~)NawlgJw9?G8n$b3hXiM!S*Uvt7o+t)$TE`Z3V-!6e_wO z1q;GgBSkBVt|f`^6CG+B(p#&$kQY?3BW)E3cv5*xoCSY^53&y78axG@>#z+$g`a|@ zGxH*T;!vX@Oz~Cz5VnP>$Mc)ecCF9;Gxm?-4^{uW_6HvY$KZ(HZF?07wrr&i#}82YAS*aHtU$F2eo}bN5>v-agS2BM6MU8BlzS zaBY{UrLp5|Hud%@-mU)0o?%$fWaFy+*}5ma-cu9=YyAL;jIOtjY)Q#(`bKmypKmXqMzyJLso~J~Af8bH$ zgX0D5{`>aOPbo+Lp&;qVU-a9hlJ_%$mS?o~!j!T0<*Rx2EA9My?IXWYhf9*>ibr2R z>XAyG&A;6Ddr$IhpG^Pk{NnU?e)-I6&UG(goU7%3#{NUkIA4^uVmyoT|HNL-0~V$J z(Z{pSUGyQ@860!a?%V4)NLTou#=CYb0IIan)atybjuSO5`_P_Bc)aud$ovCjP z7kR$)j-EG1=0}BIoj1>tzL7X+{??Wt-*8>$RPM#RM6mGjUzI;1m;5!Z5PvE`ah68O z2Z;Pbai*4`$NsB&us_0ncr&qM?Pu40-}A<`-`n%zgUz1vOV(U>px_styHB4w_sjb# zJoV3h-}&Wb-JWL;#5nF+_zvr?=9jU0s`{yRNI^wjzh_FZIq{aCu6D0jP;&J3hR=5Y z^gAgV+rDM?MEF1b^sq166Bk){;Kt?4Jt=>_nq%Ky2$fXOHKcYTORkE&-~4`JAU{J&r*@6M(e4JXLVeY zj^s~m45;f5RsSxw)6%6t)Oc0nR9#=xah|H{R$Fo%RO`vtPqdB*uM^VQPkU554!OZ8cFl+$-BFb8E_LaerrG!sx9fz-Vt}M3mScnvI<}2F+bw`jG!_YzK<T3M=aa4W-qY)#C3>unVEw&#w2A=6ojv<}?Bz(e{p?HkPECKv{M5 zEF99EIrHz%$xBQSH$?QmzNlmOf4!k!8~C*czV^U4577Q6m54{?OA$>`5mK~X--d@@ zD`5260KM*gE7h5Xs7yn2w!yq6@(JA?h8UkAc8!0nZ(CAL)nygj(DyFnwlSJ}z?5WDbB2GV%`8xq#&| z8(pi0D8R@ZMwV!B_v4zlLq)P}2*<9=y;3piux)W<+r5VPEQ2k_5GU-nz!z7Wy%PQ4 zO?24?(;D4;GR-9LUf{DP!EeJ8X$kPlfZt6U&x#Kf-`ru?I9K(}O2ekPvvFUCI?J$m zZl+<&++5t>iTiB6&%u2@?lU!bYDe@rpeKA0ppMNiQ-$WkqHe0!IE( z&CbKUFuR*C$Pd(rI!jo82VW3EwNmH94K*WolQN_wx!Y&hCKOuCa8(}?O==Lw*<)~d zfX!#z64YwQ2*bPp(1jXP7^{k8>Tp3(%7D8CKdK&^wEg(ECU9xp2k}R@btU#U4&Q0!J5cs-D4t8WEx^X zFUHea(9v(t^t%hm%@TY_y7FhxTX8{u33w0Twc|-0OJyQ8Wg0BG$WD?JhvOvL7-x8; ze;H}BMu!264CNElSq2Nqw*qgvNRqaIHbp~=q);hrfu6IO&I!5`pi3N0=hxENfGYvr zl{b1N-w2&D{<94hRpkc^)=Wd3&tL%!>AVhYmfWcPMnyKR(J6ncz6J1lz)L|hVtcm1 zu}1#{e0hh#nQ3rjiF1=UXVcl8i&Q18MUzu#|9iAwje4X#ypU}OMySdxGPFcw;y!YV zj<0mT!o_TW9$~;76>$a^K0{l?93U4nQ3>iS+!yeDv4)0ttMpJI9!C(b#g@f+@hZ$X zonlp$9xzyD8xUmFms>&qL(q#2gmC|78)6k76f>##k?0s`>E-DsFTi`1!QwMm=NMw| zHCQr5Kje}q(GS#Y?7T?*LwG7>bmIP@zIM<(%FA>L>jwWKZd6<58DepFhd7h{6-0~j zIp&E`ZL$qtUD}&)oJH!=Gl z1UIhVh5bzT+p%;s%y~IUbeov&$7AU(1cMZiIaILzmX&3Q=knlmqcj0<0p7P8_ z#=pw#u{Kxdm3Y-hx&nhO(-5y~6P1#m4s*M^nQyqA#%Cf&S=4M8BR)Su^CFee?Ua~Z zAu?PhWeI8}<{R>%kK2fGtFa%cOw5bKuZd0yPSIagzQ7QJsFW-~cQBu8i8jj+qcLAb)LT8qItReu{qNuU^J4ARb|P*+|S{?p&@ak3>A)0K7XPQyZ2zXLb78jV;F~g8kte`DoTJg$OSdY=;k9g9&NaZFvh3OP! z?-Kr=L({sLr?xQdjZ91NO|tee{2L6PsIQ6c6w_@POGir!9&bg!nK|GcG;YE=LHBG! zqe`dyfuSkW&=~or2t!jYmI0ZV@^i67$Tc)RYG|T`0<9kA0Fr6wAShT-|J5 zF&=WsUIo9F@%yxR&3M_$c%K%}?a5EQjE^Qf3c*e?Oq-jB7^cYy;W(l;F;Ucd^qdgS zFzwj)GHmSFr+ie*I5no!d=y}K90^eTOAn;NKWiB`cLW#Ki+FBj{Pm1idG=g@=gAB3 zr1;lQC1!ZPi%uGSC}u2-dv^qr8-$HfN8*uwa@@ww2g%AgoK#B}dy`DkM3G~>5w@f<@W$9xGP@X4C5kmF{$UlOqz z%NjYwjK72VjLrQ`j31hyJ*_b|Fh21DdgzuIyjc5-JaCZd|IGZ9JfP$f-AZLONcNp( zI(lhfR9|W42BM3ly$qCFzk<%kbYfko#E;VVWB64w-ECv}Y36(CzjaJk#dKP~kzckk zp8l$Fl>XU<5;YEVIfk-KLy7RqNv6MiJbjr;PjaIo&&E9`75Usw2tR%5?XS@CoORgN$1{f*U2{6yv{Y{Fphx z)l9<2!T4W~!jGQY$R_-$n<$y%Z3>T93n*Hc?q7&H96#9xn-aRB4>vQ-p;0tqP72Rm zB(s<4zBgWGI5xEH))1=>X%x^QmT@Bsw?KJ#ztBl(Wm2u7LSr zc`4SOO$@IXkrRH#TF& z@D(CDsq+Gqr+9WU?#U6{s92+~HBhmR_l;AHXW|oNx=tciWMmuSMMw)d8yWxH1bDKg zi}Cc=_am_*;-W~EL-Tov>AppLFn3Yga}0Z)VBxqMA5-{pmne5%Kt?<$@|bRP9D|=G z59*NXa4YsWnwUOF+_mzRxNlNc#>(8dnQ3d8R-2p1mLA3*8_O%(5FTsf??I+h`&97J z%zY+)@%hAfKBJz=oJ{w|c)IX@7s)GOxaXmq-h_P~zX9yEaw5Q|Q|N@a)bgP?cQC%4@!|Za(fJ#^ zmlOw@GNxB!H%za@`EqTXuVvZ?n75FviO;Q!e|4;E?18F&r`&az>F(0~3nPCG^zB{-?A zmGR=Ur<%5e^D^$vk+xy~7c)-VUv&(dGXZ_a7<$rwBg4KqLa(d?)L;jkZ0?z$?JXxP4;=Oh=CUzzy%V%8U|8DN+^ES9PRMIwy6eW$X?`=&y+xb#mP&Ef z?GP39*>-OGBJWXXbCeJG!;JUPvz}Ie{J^18ZHSDIFtq6KD~K+#FMRb$B*R2bwNNLc z=)|^e;qb?{Bm56>Q7Cq5M3|R{d7EC_;(hHpCP8Zd6IsOxrP|vZ^_2n z8(vouA1~AWhaNYo&p1A4T$V6?F|M)hj3XZf7&gK4XgC%kUyTsKP%qAkkG60-d?s^5 zmS(@B z1p_E9o9hL?}KwLH9MfJn@qotzqIA_mAVp*uL->10C@xVA?L0L%Yuu zMg*>7oRi~9yS^dRTE>4wlg5~MBGgvKm$Dw(d}IT@m+=-{D>lqke=$Jz4e#A!`#V?s zmPl>%VuKPNJf?jTrO%W(X0~A{Y+Gbpgy9_W$B<6^ChO!jS>wlfIPd8K;s=R)#Q9$3 z#|aBW1i0-?9_vb8oPK|9k@3)V_)F zeG~L0;Wse;ySN_d7sW{h0PSS__ECO>3$n58uFi1}VEaACuvTovGG1Lnk$&44M&nY|uZLkj()Ja`J<*?J+&rR3As9X#BZ_!Fz!m8dZ4n?TR?CC) zE7=*(_}xUN+Q~9<8TWm{p%A}f3tizmm|EMIAL-C!A*>|*OsPbhwHD=OGJkDl{6ZpA zbzpxI&tArB{dJOIs=tK&467hp(V1jl^bDL(WUc+UCtKneH<@0!jGs)e3dU>oYGRmH zueA&_ML;O#Aku3S!x{*t+KfxmtBY}y>D9;h$@G$L1QQgkUS@`+5Q68yIEIlvYRtJA zW?`5b_vGIq#;J21wX0^>Or}@oMhouS7^lunYEJZWPNeo*n5KiDKh4@^Gvf!ec-E2l_b~pi6Xa4W@FB*p;TTf)h!~|-8*3V0*ch++ zg4*~P_DgCAUx0VE!K>_Hl0V8Ae=Fn3e~k=FVwl=rM8A%4oZiKM_pzB_8<;+@E@)%5U{}%{9jmpF{7vI>ZZer1v@IIho$EHVK4Kf{lZ7;09CeBQ^D17!tYVevkvoZd5 z{dhd-me2T$6gFb6DPx$L=S2TA>?V#gU6t~77wNc;ao^R(phm}SjGt^gA7cDuIqx*% zwK_)qgQBoj$5@8>RUNBGbaXQAuaoFl#Q4c{Y-IdoI<9BDR>v(2)9Tp8u)8ME@fhQd zOrqmC#!seWtPq6nnvrYNSl7(8ZpLeM%x9RYBgI4+!+u75cus3%SU15`-nbO~&$!9- z+Q#@2Z47I2)FH-e^$IaeRFFq8c#dH|(E11K8N$VglFXQKGZPMTe8UF~&5qVdGH;E*T|G#7c zJoTlG@v80w^D#{IE!k7Vun(D!#-8x+C}dBN>D2eT#_F??@ss(wi}92Bx{vW%eNQt? z)tCA^>NZjozx8ST#%o(EE-`Em;;rhP^!8L&5U))E7x{1mLFW)Olc1S~eT)L%Ivo3$cM{K8 z8r~pR7YJq4EX&YCtPTPP48IHCCH zr20UU8;clEAwb23qncsr{$f~9RYmatPj440@AQ)|H*%Y+i3tkv+4gtxaidL$+oTf{ z=zBrg*eSuEJ%#n4_HnbQ;mBMtuKh#mY74sOjBZbMRB-!gf{XGD+QcxU2DWw#9r=0_ z!^rj#-IWIe$~F5Kzi1R5Yg^T?fRhfVCXlCW_R{xRl|po8(LwlI3WkvmaSWSGhg`-l zr8ZjsYWDnzcOBDRL3Ai)94!nJ+mmQZ8w&*Az;LmZ&v4RZ8^b5lQ=|_vAVS}{Ht`^%5)Rx7XIzP1bq%O-4xOdgp6k@sB5uV(RZhxZ?e zj{QpZ#xwjzhNDlzd;7VJyOeM!B(IoZ+MHK6hK}N?g<&^S!?3?cVr3KKlSbi3#|p_i zIEFXD`WU9w;q(|fvN=iwswAm8P<&G?SQws(>k-{X%cl0ZOlM&_b$paCoOr3eB>Kh) z=!s@6!|!DJaq|q|TU>yuoe;W$-z2?2ch2jtXbnlD5_{*Vpe|@s&@x2lw$C%&! zBpCW>_w^`-&M|&DIZyY`OrR{{NXJ;waijM}Z41`D+jS zCp>@w-3YQOdy9P(z(NV+pv%G8Rq_tXBI_AB=R_eJT6>GRV! zq#w>Odv5Yndg?qs_Wa6o#Pg{~vMA4px8lRwPbK^s-?+|BoS#^lSe2NNT%No#c}?<< zle?35IuC>EN!MRogRZ~1&bf4{QK^PhbLuszmegBP>7lOxx|?ifTa3+Wi^pfT-4354 z&r#qgc9c1)9RWv^qs7tTSm)T_*yPya*yh;j*ylLtIOI6yIO#a$IPEy+&?iMFnUi9Z ztV!`S7kHKVFD@}2ANY18(u4F)fGxIM+c}%n?z5NKV<9IG+}D9$$YD-$CzU0wPuiL^ znADQIEx8Zh>CR7SN!gZiC`FGCcjr4BotvFIoxRSJ&LL;C%YqMg=eo*Vjjr{sF4rN~ zX;*BjGqoVKE_HosS86C#N{dZ%rsbuTq&1~&NZXlqIxXH^;%;(pb{}+~a$D1L(*y8m zPkLW^RE901BV$X(!HiQGlE>_Ecyc|(o<`4F&lb-?&q>$T+EVjVgQ;6m52l_@HNo1Fv|!riw4StMX+vqz?l`yG zUFL3dZ**^SA9D}7_34&$Z+dZhV|qvW=JcKEC)0=0Eg6oC;*7eC^%;jUPG`7bcZ;XX zgYSr~MgL*minIA_)wWHxeYR6JGd|^BY;Ur!wfDf@sD!u#cS3$bHEiCHa4_LiLKLj_ zCDtW&ByNG#rxGoQpS9@QbBkN^VM7 zN+Yb^l5#NRREp#@JL8-_XPL9bx!$?O+2uUuJm-wXN85d_B3HoG=Gx@yK}1W4V6W)) zmeftD`%+J)>eJ#yuLslCrEN{ymlor8y7Sx>==omvNq2O5e0pAbb$T#;UHaDa-t@tA zQ-(FenNg4t$XJ)LEu$ynSjJFBjK}FI@>F|*o^_tBo?Z{WZ?+NZRro`X5#q4r+v;p< z;gy5%O0+%Ro@Xzy2kaZ{JMG8p=MV|j1aCq?LR~^f!WMX@FX42838Nq%zG+G9ODw>6 zYQqTX#hCFWwZJP|lDd-mlA@Ah@j-iEa#?am^2X%t$-T*g$uTMMDZZ5Al%|wTDce(e zQ$i_G_?W%VS?mlr*I_gqb`CnD;1%*o2Yhl6;~*+E7Lk#gT9(?F+L5|ZjD~ZF4{Mq` ztqh}KUD}qkLl_TH?pU`CzM&Xdr->1FdXezYX2eGfe3O?^0`IhCY{=M}u`lB=kCHf# 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 zcmeHv3wTpiy7o$Mv{=~THliLi)qn+&lk}Q6&?F_r9o>Zj1=Ml0g{IJsxEDbSS;Kndul~DR| z$|_%7(`0C9tY6hwxz@0#1$0m(osREKPx8HR}LEH6oc+v;ZQr)Uk05&Wrab zY?UTEGM}+IYMWOTjC~LfZ-u_`a6NI@5x-JlSDK*$O_ zL|2R_uGop+yv5~&BGp&}9-?y+FjDM9=c#QX40SV~+JH~ui=E^)x@+rKs=nNcUK$|A zQ-Rr8nvCv05B=wPVA*of|EWck4~xN##%xg@jvSiEn4ljcOh^;uLD9e0a@(zY9LpWc z97`SL%a$))BDQ?mK9(^_J}k*j<7!d^b4&$DPWdK!-ytTo!D;G@QNSSkgt`OW1d&}pm? z<&DN0ORf6^|JNAcln29+WMNxK*nY2*B#AA<(I{h{%xeF=66q5aqlf1S+cM3<_WL

~sa^q=|~gl6=J6OQOawwtlKB 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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()); + } +}