mirror of
https://github.com/sirjonasxx/G-Earth.git
synced 2024-11-23 17:00:52 +01:00
Update to Habbo Origins v24
This commit is contained in:
parent
c908bcbc44
commit
d0c0e05725
@ -345,6 +345,12 @@
|
|||||||
<version>5.10.2</version>
|
<version>5.10.2</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.mockito</groupId>
|
||||||
|
<artifactId>mockito-inline</artifactId>
|
||||||
|
<version>4.11.0</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<repositories>
|
<repositories>
|
||||||
|
@ -9,6 +9,7 @@ import gearth.protocol.connection.HStateSetter;
|
|||||||
import gearth.protocol.connection.proxy.ProxyProvider;
|
import gearth.protocol.connection.proxy.ProxyProvider;
|
||||||
import gearth.protocol.interceptor.ConnectionInterceptor;
|
import gearth.protocol.interceptor.ConnectionInterceptor;
|
||||||
import gearth.protocol.interceptor.ConnectionInterceptorCallbacks;
|
import gearth.protocol.interceptor.ConnectionInterceptorCallbacks;
|
||||||
|
import gearth.protocol.memory.Rc4Obtainer;
|
||||||
import gearth.protocol.packethandler.PacketHandler;
|
import gearth.protocol.packethandler.PacketHandler;
|
||||||
import gearth.protocol.packethandler.shockwave.ShockwavePacketIncomingHandler;
|
import gearth.protocol.packethandler.shockwave.ShockwavePacketIncomingHandler;
|
||||||
import gearth.protocol.packethandler.shockwave.ShockwavePacketOutgoingHandler;
|
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 ShockwavePacketOutgoingHandler outgoingHandler = new ShockwavePacketOutgoingHandler(server.getOutputStream(), hConnection.getExtensionHandler(), hConnection.getTrafficObservables());
|
||||||
final ShockwavePacketIncomingHandler incomingHandler = new ShockwavePacketIncomingHandler(client.getOutputStream(), hConnection.getExtensionHandler(), hConnection.getTrafficObservables(), outgoingHandler);
|
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.
|
// Lets revisit when origins is more mature.
|
||||||
proxy.verifyProxy(incomingHandler, outgoingHandler, "20", "SHOCKWAVE");
|
proxy.verifyProxy(incomingHandler, outgoingHandler, "24", "SHOCKWAVE");
|
||||||
hProxySetter.setProxy(proxy);
|
hProxySetter.setProxy(proxy);
|
||||||
onConnect();
|
onConnect();
|
||||||
|
|
||||||
@ -107,7 +111,6 @@ public class ShockwaveProxy implements ProxyProvider, ConnectionInterceptorCallb
|
|||||||
try {
|
try {
|
||||||
if (!server.isClosed()) server.close();
|
if (!server.isClosed()) server.close();
|
||||||
if (!client.isClosed()) client.close();
|
if (!client.isClosed()) client.close();
|
||||||
if (HConnection.DEBUG) System.out.println("STOP");
|
|
||||||
onConnectEnd();
|
onConnectEnd();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logger.error("Error occurred while closing sockets.", e);
|
logger.error("Error occurred while closing sockets.", e);
|
||||||
|
@ -54,9 +54,9 @@ import java.util.Arrays;
|
|||||||
* <p>
|
* <p>
|
||||||
* @author Clarence Ho
|
* @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 x;
|
||||||
private int y;
|
private int y;
|
||||||
|
|
||||||
@ -79,7 +79,6 @@ public class RC4 {
|
|||||||
* @param key the encryption/decryption key
|
* @param key the encryption/decryption key
|
||||||
*/
|
*/
|
||||||
public RC4(byte[] key) throws NullPointerException {
|
public RC4(byte[] key) throws NullPointerException {
|
||||||
|
|
||||||
for (int i = 0; i < 256; i++) {
|
for (int i = 0; i < 256; i++) {
|
||||||
state[i] = (byte) i;
|
state[i] = (byte) i;
|
||||||
}
|
}
|
||||||
@ -97,7 +96,6 @@ public class RC4 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < 256; i++) {
|
for (int i = 0; i < 256; i++) {
|
||||||
|
|
||||||
index2 = ((key[index1] & 0xff) + (state[i] & 0xff) + index2) & 0xff;
|
index2 = ((key[index1] & 0xff) + (state[i] & 0xff) + index2) & 0xff;
|
||||||
|
|
||||||
tmp = state[i];
|
tmp = state[i];
|
||||||
@ -106,9 +104,6 @@ public class RC4 {
|
|||||||
|
|
||||||
index1 = (index1 + 1) % key.length;
|
index1 = (index1 + 1) % key.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public RC4(byte[] state, int x, int y) {
|
public RC4(byte[] state, int x, int y) {
|
||||||
@ -117,51 +112,29 @@ public class RC4 {
|
|||||||
this.state = state;
|
this.state = state;
|
||||||
}
|
}
|
||||||
|
|
||||||
//copyconstructor
|
|
||||||
public RC4 deepCopy() {
|
|
||||||
return new RC4(Arrays.copyOf(state, 256), x, y);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* RC4 encryption/decryption.
|
* 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
|
* @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) {
|
if (data == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] tmp = data.getBytes();
|
byte[] result = new byte[length];
|
||||||
|
|
||||||
this.rc4(tmp);
|
for (int i = 0; i < length; i++) {
|
||||||
|
|
||||||
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++) {
|
|
||||||
|
|
||||||
x = (x + 1) & 0xff;
|
x = (x + 1) & 0xff;
|
||||||
y = ((state[x] & 0xff) + y) & 0xff;
|
y = ((state[x] & 0xff) + y) & 0xff;
|
||||||
@ -171,25 +144,49 @@ public class RC4 {
|
|||||||
state[y] = tmp;
|
state[y] = tmp;
|
||||||
|
|
||||||
xorIndex = ((state[x] & 0xff) + (state[y] & 0xff)) & 0xff;
|
xorIndex = ((state[x] & 0xff) + (state[y] & 0xff)) & 0xff;
|
||||||
result[i] = (byte)(buf[i] ^ state[xorIndex]);
|
result[i] = (byte) (data[offset + i] ^ state[xorIndex]);
|
||||||
}
|
}
|
||||||
|
|
||||||
//this.x = lx;
|
|
||||||
//this.y = ly;
|
|
||||||
|
|
||||||
return result;
|
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() {
|
public boolean couldBeFresh() {
|
||||||
return (x == 0 && y == 0);
|
return (x == 0 && y == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void undoRc4(byte[] buf) {
|
public void undoRc4(byte[] buf) {
|
||||||
|
|
||||||
byte tmp;
|
byte tmp;
|
||||||
|
|
||||||
for (int i = buf.length - 1; i >= 0; i--) {
|
for (int i = 0; i < buf.length; i++) {
|
||||||
|
|
||||||
tmp = state[x];
|
tmp = state[x];
|
||||||
state[x] = state[y];
|
state[x] = state[y];
|
||||||
state[y] = tmp;
|
state[y] = tmp;
|
||||||
@ -197,10 +194,5 @@ public class RC4 {
|
|||||||
y = (y - (state[x] & 0xff)) & 0xff;
|
y = (y - (state[x] & 0xff)) & 0xff;
|
||||||
x = (x - 1) & 0xff;
|
x = (x - 1) & 0xff;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getState () {
|
|
||||||
return state;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
195
G-Earth/src/main/java/gearth/protocol/crypto/RC4Base64.java
Normal file
195
G-Earth/src/main/java/gearth/protocol/crypto/RC4Base64.java
Normal file
@ -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.
|
||||||
|
* <a href="https://github.com/aromaa/Skylight3/blob/72ec3a07d126de09f6de4251c91001329f77a8a2/src/Skylight.Server/Net/Crypto/RC4Base64.cs">
|
||||||
|
* https://github.com/aromaa/Skylight3/blob/72ec3a07d126de09f6de4251c91001329f77a8a2/src/Skylight.Server/Net/Crypto/RC4Base64.cs
|
||||||
|
* </a>
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
21
G-Earth/src/main/java/gearth/protocol/crypto/RC4Cipher.java
Normal file
21
G-Earth/src/main/java/gearth/protocol/crypto/RC4Cipher.java
Normal file
@ -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();
|
||||||
|
|
||||||
|
}
|
@ -4,11 +4,16 @@ import gearth.GEarth;
|
|||||||
import gearth.protocol.HConnection;
|
import gearth.protocol.HConnection;
|
||||||
import gearth.protocol.HMessage;
|
import gearth.protocol.HMessage;
|
||||||
import gearth.protocol.crypto.RC4;
|
import gearth.protocol.crypto.RC4;
|
||||||
|
import gearth.protocol.crypto.RC4Base64;
|
||||||
import gearth.protocol.memory.habboclient.HabboClient;
|
import gearth.protocol.memory.habboclient.HabboClient;
|
||||||
import gearth.protocol.memory.habboclient.HabboClientFactory;
|
import gearth.protocol.memory.habboclient.HabboClientFactory;
|
||||||
import gearth.protocol.packethandler.EncryptedPacketHandler;
|
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.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.titlebar.TitleBarController;
|
||||||
import gearth.ui.translations.LanguageBundle;
|
import gearth.ui.translations.LanguageBundle;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
@ -18,61 +23,27 @@ import javafx.scene.control.Hyperlink;
|
|||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
import javafx.scene.layout.FlowPane;
|
import javafx.scene.layout.FlowPane;
|
||||||
import javafx.scene.layout.Region;
|
import javafx.scene.layout.Region;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class Rc4Obtainer {
|
public class Rc4Obtainer {
|
||||||
|
|
||||||
public static final boolean DEBUG = false;
|
private static final Logger logger = LoggerFactory.getLogger(Rc4Obtainer.class);
|
||||||
|
|
||||||
private final HabboClient client;
|
private final HConnection hConnection;
|
||||||
private List<EncryptedPacketHandler> flashPacketHandlers;
|
private final List<EncryptedPacketHandler> flashPacketHandlers;
|
||||||
|
|
||||||
public Rc4Obtainer(HConnection hConnection) {
|
public Rc4Obtainer(HConnection hConnection) {
|
||||||
client = HabboClientFactory.get(hConnection);
|
this.hConnection = hConnection;
|
||||||
|
this.flashPacketHandlers = new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setFlashPacketHandlers(EncryptedPacketHandler... flashPacketHandlers) {
|
private static void showErrorDialog() {
|
||||||
this.flashPacketHandlers = Arrays.asList(flashPacketHandlers);
|
|
||||||
for (EncryptedPacketHandler handler : flashPacketHandlers) {
|
|
||||||
BufferChangeListener bufferChangeListener = new BufferChangeListener() {
|
|
||||||
@Override
|
|
||||||
public void onPacket() {
|
|
||||||
if (handler.isEncryptedStream()) {
|
|
||||||
onSendFirstEncryptedMessage(handler);
|
|
||||||
handler.getPacketReceivedObservable().removeListener(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
handler.getPacketReceivedObservable().addListener(bufferChangeListener);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onSendFirstEncryptedMessage(EncryptedPacketHandler flashPacketHandler) {
|
|
||||||
if (!HConnection.DECRYPTPACKETS) return;
|
|
||||||
|
|
||||||
flashPacketHandlers.forEach(EncryptedPacketHandler::block);
|
|
||||||
|
|
||||||
new Thread(() -> {
|
|
||||||
|
|
||||||
long startTime = System.currentTimeMillis();
|
|
||||||
if (DEBUG) System.out.println("[+] send encrypted");
|
|
||||||
|
|
||||||
boolean worked = false;
|
|
||||||
int i = 0;
|
|
||||||
while (!worked && i < 4) {
|
|
||||||
worked = (i % 2 == 0) ?
|
|
||||||
onSendFirstEncryptedMessage(flashPacketHandler, client.getRC4cached()) :
|
|
||||||
onSendFirstEncryptedMessage(flashPacketHandler, client.getRC4possibilities());
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!worked) {
|
|
||||||
System.err.println("COULD NOT FIND RC4 TABLE");
|
|
||||||
|
|
||||||
Platform.runLater(() -> {
|
|
||||||
Alert alert = new Alert(Alert.AlertType.WARNING, LanguageBundle.get("alert.somethingwentwrong.title"), ButtonType.OK);
|
Alert alert = new Alert(Alert.AlertType.WARNING, LanguageBundle.get("alert.somethingwentwrong.title"), ButtonType.OK);
|
||||||
|
|
||||||
FlowPane fp = new FlowPane();
|
FlowPane fp = new FlowPane();
|
||||||
@ -92,27 +63,140 @@ public class Rc4Obtainer {
|
|||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
|
public void setFlashPacketHandlers(EncryptedPacketHandler... flashPacketHandlers) {
|
||||||
|
this.flashPacketHandlers.addAll(Arrays.asList(flashPacketHandlers));
|
||||||
|
|
||||||
|
for (EncryptedPacketHandler handler : flashPacketHandlers) {
|
||||||
|
BufferChangeListener bufferChangeListener = new BufferChangeListener() {
|
||||||
|
@Override
|
||||||
|
public void onPacket() {
|
||||||
|
if (handler.isEncryptedStream()) {
|
||||||
|
onSendFirstEncryptedMessage(handler);
|
||||||
|
handler.getPacketReceivedObservable().removeListener(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
handler.getPacketReceivedObservable().addListener(bufferChangeListener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onSendFirstEncryptedMessage(EncryptedPacketHandler flashPacketHandler) {
|
||||||
|
if (!HConnection.DECRYPTPACKETS) return;
|
||||||
|
|
||||||
|
flashPacketHandlers.forEach(EncryptedPacketHandler::block);
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
|
||||||
|
boolean worked = false;
|
||||||
|
int i = 0;
|
||||||
|
while (!worked && i < 4) {
|
||||||
|
worked = (i % 2 == 0) ?
|
||||||
|
onSendFirstEncryptedMessage(flashPacketHandler, client.getRC4cached()) :
|
||||||
|
onSendFirstEncryptedMessage(flashPacketHandler, client.getRC4possibilities());
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!worked) {
|
||||||
|
try {
|
||||||
|
Platform.runLater(Rc4Obtainer::showErrorDialog);
|
||||||
|
} catch (IllegalStateException e) {
|
||||||
|
// ignore, thrown in tests.
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.error("Failed to find RC4 table, aborting connection");
|
||||||
|
hConnection.abort();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final long endTime = System.currentTimeMillis();
|
final long endTime = System.currentTimeMillis();
|
||||||
if (DEBUG)
|
logger.info("Cracked decryption keys in {}ms", endTime - startTime);
|
||||||
System.out.println("Cracked RC4 in " + (endTime - startTime) + "ms");
|
|
||||||
|
|
||||||
flashPacketHandlers.forEach(EncryptedPacketHandler::unblock);
|
flashPacketHandlers.forEach(EncryptedPacketHandler::unblock);
|
||||||
}).start();
|
}).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean onSendFirstEncryptedMessage(EncryptedPacketHandler flashPacketHandler, List<byte[]> potentialRC4tables) {
|
private boolean onSendFirstEncryptedMessage(EncryptedPacketHandler flashPacketHandler, List<byte[]> potentialRC4tables) {
|
||||||
|
if (potentialRC4tables == null || potentialRC4tables.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
for (byte[] possible : potentialRC4tables)
|
for (byte[] possible : potentialRC4tables) {
|
||||||
if (isCorrectRC4Table(flashPacketHandler, possible))
|
if (flashPacketHandler instanceof FlashPacketHandler && bruteFlash(flashPacketHandler, possible))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
if (flashPacketHandler instanceof ShockwavePacketOutgoingHandler && bruteShockwaveHeader(flashPacketHandler, possible)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
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 {
|
try {
|
||||||
|
|
||||||
@ -136,12 +220,13 @@ public class Rc4Obtainer {
|
|||||||
final RC4 rc4TryCopy = rc4Tryout.deepCopy();
|
final RC4 rc4TryCopy = rc4Tryout.deepCopy();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final PayloadBuffer payloadBuffer = new PayloadBuffer();
|
final PayloadBuffer payloadBuffer = new FlashBuffer();
|
||||||
final byte[] decoded = rc4TryCopy.rc4(encDataCopy);
|
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);
|
flashPacketHandler.setRc4(rc4Tryout);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,10 @@ package gearth.protocol.memory.habboclient;
|
|||||||
|
|
||||||
import gearth.misc.OSValidator;
|
import gearth.misc.OSValidator;
|
||||||
import gearth.protocol.HConnection;
|
import gearth.protocol.HConnection;
|
||||||
|
import gearth.protocol.connection.HClient;
|
||||||
import gearth.protocol.memory.habboclient.linux.LinuxHabboClient;
|
import gearth.protocol.memory.habboclient.linux.LinuxHabboClient;
|
||||||
import gearth.protocol.memory.habboclient.macOs.MacOsHabboClient;
|
import gearth.protocol.memory.habboclient.macOs.MacOsHabboClient;
|
||||||
|
import gearth.protocol.memory.habboclient.shockwave.ShockwaveMemoryClient;
|
||||||
import gearth.protocol.memory.habboclient.windows.WindowsHabboClient;
|
import gearth.protocol.memory.habboclient.windows.WindowsHabboClient;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -11,16 +13,18 @@ import gearth.protocol.memory.habboclient.windows.WindowsHabboClient;
|
|||||||
*/
|
*/
|
||||||
public class HabboClientFactory {
|
public class HabboClientFactory {
|
||||||
|
|
||||||
|
|
||||||
public static HabboClient get(HConnection connection) {
|
public static HabboClient get(HConnection connection) {
|
||||||
if (OSValidator.isUnix()) return new LinuxHabboClient(connection);
|
if (connection.getClientType() == HClient.SHOCKWAVE) {
|
||||||
|
return new ShockwaveMemoryClient(connection);
|
||||||
|
} else {
|
||||||
if (OSValidator.isWindows()) return new WindowsHabboClient(connection);
|
if (OSValidator.isWindows()) return new WindowsHabboClient(connection);
|
||||||
|
if (OSValidator.isUnix()) return new LinuxHabboClient(connection);
|
||||||
if (OSValidator.isMac()) return new MacOsHabboClient(connection);
|
if (OSValidator.isMac()) return new MacOsHabboClient(connection);
|
||||||
|
}
|
||||||
|
|
||||||
// todo use rust if beneficial
|
// todo use rust if beneficial
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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<byte[]> getRC4cached() {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<byte[]> getRC4possibilities() {
|
||||||
|
final List<byte[]> result = new ArrayList<>();
|
||||||
|
|
||||||
|
try {
|
||||||
|
final HashSet<String> 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<String> 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<String> 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;
|
||||||
|
}
|
||||||
|
}
|
@ -4,7 +4,7 @@ import gearth.misc.listenerpattern.Observable;
|
|||||||
import gearth.protocol.HConnection;
|
import gearth.protocol.HConnection;
|
||||||
import gearth.protocol.HMessage;
|
import gearth.protocol.HMessage;
|
||||||
import gearth.protocol.TrafficListener;
|
import gearth.protocol.TrafficListener;
|
||||||
import gearth.protocol.crypto.RC4;
|
import gearth.protocol.crypto.RC4Cipher;
|
||||||
import gearth.protocol.packethandler.flash.BufferChangeListener;
|
import gearth.protocol.packethandler.flash.BufferChangeListener;
|
||||||
import gearth.services.extension_handler.ExtensionHandler;
|
import gearth.services.extension_handler.ExtensionHandler;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@ -27,8 +27,8 @@ public abstract class EncryptedPacketHandler extends PacketHandler {
|
|||||||
private volatile boolean isEncryptedStream;
|
private volatile boolean isEncryptedStream;
|
||||||
private volatile List<Byte> tempEncryptedBuffer;
|
private volatile List<Byte> tempEncryptedBuffer;
|
||||||
|
|
||||||
private RC4 encryptCipher;
|
private RC4Cipher encryptCipher;
|
||||||
private RC4 decryptCipher;
|
private RC4Cipher decryptCipher;
|
||||||
|
|
||||||
protected EncryptedPacketHandler(ExtensionHandler extensionHandler, Observable<TrafficListener>[] trafficObservables, HMessage.Direction direction) {
|
protected EncryptedPacketHandler(ExtensionHandler extensionHandler, Observable<TrafficListener>[] trafficObservables, HMessage.Direction direction) {
|
||||||
super(extensionHandler, trafficObservables);
|
super(extensionHandler, trafficObservables);
|
||||||
@ -73,16 +73,16 @@ public abstract class EncryptedPacketHandler extends PacketHandler {
|
|||||||
tempEncryptedBuffer.add(buffer[i]);
|
tempEncryptedBuffer.add(buffer[i]);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
writeBuffer(decryptCipher.rc4(buffer));
|
writeBuffer(buffer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected byte[] encrypt(byte[] buffer) {
|
protected byte[] encrypt(byte[] buffer) {
|
||||||
return encryptCipher.rc4(buffer);
|
return encryptCipher.cipher(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected byte[] decrypt(byte[] buffer) {
|
protected byte[] decrypt(byte[] buffer) {
|
||||||
return decryptCipher.rc4(buffer);
|
return decryptCipher.decipher(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract void writeOut(byte[] buffer) throws IOException;
|
protected abstract void writeOut(byte[] buffer) throws IOException;
|
||||||
@ -105,7 +105,11 @@ public abstract class EncryptedPacketHandler extends PacketHandler {
|
|||||||
isTempBlocked = false;
|
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.decryptCipher = rc4.deepCopy();
|
||||||
this.encryptCipher = rc4.deepCopy();
|
this.encryptCipher = rc4.deepCopy();
|
||||||
|
|
||||||
|
@ -1,42 +1,28 @@
|
|||||||
package gearth.protocol.packethandler;
|
package gearth.protocol.packethandler;
|
||||||
|
|
||||||
import gearth.protocol.HPacket;
|
import gearth.protocol.crypto.RC4Cipher;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
public abstract class PayloadBuffer {
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
public class PayloadBuffer {
|
protected byte[] buffer;
|
||||||
|
|
||||||
private byte[] buffer = new byte[0];
|
public PayloadBuffer() {
|
||||||
|
this.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<HPacket> 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()]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make sure to call deepCopy on the cipher if you use it.
|
||||||
|
*/
|
||||||
|
public abstract void setCipher(RC4Cipher cipher);
|
||||||
|
|
||||||
public byte[] peak() {
|
public void push(byte[] data) {
|
||||||
return buffer;
|
buffer = buffer.length == 0 ? data.clone() : ByteArrayUtils.combineByteArrays(buffer, data);
|
||||||
}
|
}
|
||||||
public byte[] forceClear() {
|
|
||||||
byte[] buff = buffer;
|
public abstract byte[][] receive();
|
||||||
buffer = new byte[0];
|
|
||||||
return buff;
|
public boolean isEmpty() {
|
||||||
|
return buffer.length == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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<byte[]> 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][]);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -5,7 +5,6 @@ import gearth.protocol.HMessage;
|
|||||||
import gearth.protocol.HPacket;
|
import gearth.protocol.HPacket;
|
||||||
import gearth.protocol.TrafficListener;
|
import gearth.protocol.TrafficListener;
|
||||||
import gearth.protocol.packethandler.EncryptedPacketHandler;
|
import gearth.protocol.packethandler.EncryptedPacketHandler;
|
||||||
import gearth.protocol.packethandler.PayloadBuffer;
|
|
||||||
import gearth.services.extension_handler.ExtensionHandler;
|
import gearth.services.extension_handler.ExtensionHandler;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -14,13 +13,13 @@ import java.io.OutputStream;
|
|||||||
public abstract class FlashPacketHandler extends EncryptedPacketHandler {
|
public abstract class FlashPacketHandler extends EncryptedPacketHandler {
|
||||||
|
|
||||||
private final OutputStream out;
|
private final OutputStream out;
|
||||||
private final PayloadBuffer payloadBuffer;
|
private final FlashBuffer payloadBuffer;
|
||||||
private volatile boolean isDataStream;
|
private volatile boolean isDataStream;
|
||||||
|
|
||||||
FlashPacketHandler(HMessage.Direction direction, OutputStream outputStream, Observable<TrafficListener>[] trafficObservables, ExtensionHandler extensionHandler) {
|
FlashPacketHandler(HMessage.Direction direction, OutputStream outputStream, Observable<TrafficListener>[] trafficObservables, ExtensionHandler extensionHandler) {
|
||||||
super(extensionHandler, trafficObservables, direction);
|
super(extensionHandler, trafficObservables, direction);
|
||||||
this.out = outputStream;
|
this.out = outputStream;
|
||||||
this.payloadBuffer = new PayloadBuffer();
|
this.payloadBuffer = new FlashBuffer();
|
||||||
this.isDataStream = false;
|
this.isDataStream = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,6 +31,7 @@ public abstract class FlashPacketHandler extends EncryptedPacketHandler {
|
|||||||
isDataStream = true;
|
isDataStream = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void act(byte[] buffer) throws IOException {
|
public void act(byte[] buffer) throws IOException {
|
||||||
if (!isDataStream) {
|
if (!isDataStream) {
|
||||||
synchronized (sendLock) {
|
synchronized (sendLock) {
|
||||||
@ -40,7 +40,11 @@ public abstract class FlashPacketHandler extends EncryptedPacketHandler {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isEncryptedStream() && isCiphersSet()) {
|
||||||
|
super.act(decrypt(buffer));
|
||||||
|
} else {
|
||||||
super.act(buffer);
|
super.act(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
if (!isBlocked()) {
|
if (!isBlocked()) {
|
||||||
flush();
|
flush();
|
||||||
@ -59,6 +63,7 @@ public abstract class FlashPacketHandler extends EncryptedPacketHandler {
|
|||||||
payloadBuffer.push(buffer);
|
payloadBuffer.push(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public boolean sendToStream(byte[] buffer) {
|
public boolean sendToStream(byte[] buffer) {
|
||||||
return sendToStream(buffer, isEncryptedStream());
|
return sendToStream(buffer, isEncryptedStream());
|
||||||
}
|
}
|
||||||
@ -77,10 +82,9 @@ public abstract class FlashPacketHandler extends EncryptedPacketHandler {
|
|||||||
|
|
||||||
public void flush() throws IOException {
|
public void flush() throws IOException {
|
||||||
synchronized (flushLock) {
|
synchronized (flushLock) {
|
||||||
HPacket[] hpackets = payloadBuffer.receive();
|
for (final byte[] packet : payloadBuffer.receive()){
|
||||||
|
HPacket hPacket = new HPacket(packet);
|
||||||
for (HPacket hpacket : hpackets){
|
HMessage hMessage = new HMessage(hPacket, getDirection(), currentIndex);
|
||||||
HMessage hMessage = new HMessage(hpacket, getDirection(), currentIndex);
|
|
||||||
boolean isencrypted = isEncryptedStream();
|
boolean isencrypted = isEncryptedStream();
|
||||||
|
|
||||||
if (isDataStream) {
|
if (isDataStream) {
|
||||||
|
@ -6,7 +6,7 @@ import gearth.protocol.HPacket;
|
|||||||
import gearth.protocol.TrafficListener;
|
import gearth.protocol.TrafficListener;
|
||||||
import gearth.protocol.connection.proxy.nitro.websocket.NitroSession;
|
import gearth.protocol.connection.proxy.nitro.websocket.NitroSession;
|
||||||
import gearth.protocol.packethandler.PacketHandler;
|
import gearth.protocol.packethandler.PacketHandler;
|
||||||
import gearth.protocol.packethandler.PayloadBuffer;
|
import gearth.protocol.packethandler.flash.FlashBuffer;
|
||||||
import gearth.services.extension_handler.ExtensionHandler;
|
import gearth.services.extension_handler.ExtensionHandler;
|
||||||
import org.eclipse.jetty.websocket.api.Session;
|
import org.eclipse.jetty.websocket.api.Session;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@ -21,14 +21,14 @@ public class NitroPacketHandler extends PacketHandler {
|
|||||||
|
|
||||||
private final HMessage.Direction direction;
|
private final HMessage.Direction direction;
|
||||||
private final NitroSession session;
|
private final NitroSession session;
|
||||||
private final PayloadBuffer payloadBuffer;
|
private final FlashBuffer payloadBuffer;
|
||||||
private final Object payloadLock;
|
private final Object payloadLock;
|
||||||
|
|
||||||
public NitroPacketHandler(HMessage.Direction direction, NitroSession session, ExtensionHandler extensionHandler, Observable<TrafficListener>[] trafficObservables) {
|
public NitroPacketHandler(HMessage.Direction direction, NitroSession session, ExtensionHandler extensionHandler, Observable<TrafficListener>[] trafficObservables) {
|
||||||
super(extensionHandler, trafficObservables);
|
super(extensionHandler, trafficObservables);
|
||||||
this.direction = direction;
|
this.direction = direction;
|
||||||
this.session = session;
|
this.session = session;
|
||||||
this.payloadBuffer = new PayloadBuffer();
|
this.payloadBuffer = new FlashBuffer();
|
||||||
this.payloadLock = new Object();
|
this.payloadLock = new Object();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,8 +61,9 @@ public class NitroPacketHandler extends PacketHandler {
|
|||||||
payloadBuffer.push(buffer);
|
payloadBuffer.push(buffer);
|
||||||
|
|
||||||
synchronized (payloadLock) {
|
synchronized (payloadLock) {
|
||||||
for (HPacket packet : payloadBuffer.receive()) {
|
for (final byte[] packet : payloadBuffer.receive()) {
|
||||||
HMessage hMessage = new HMessage(packet, direction, currentIndex);
|
HPacket hPacket = new HPacket(packet);
|
||||||
|
HMessage hMessage = new HMessage(hPacket, direction, currentIndex);
|
||||||
awaitListeners(hMessage, hMessage1 -> sendToStream(hMessage1.getPacket().toBytes()));
|
awaitListeners(hMessage, hMessage1 -> sendToStream(hMessage1.getPacket().toBytes()));
|
||||||
currentIndex++;
|
currentIndex++;
|
||||||
}
|
}
|
||||||
|
@ -1,66 +1,45 @@
|
|||||||
package gearth.protocol.packethandler.shockwave;
|
package gearth.protocol.packethandler.shockwave;
|
||||||
|
|
||||||
import gearth.encoding.HexEncoding;
|
|
||||||
import gearth.misc.listenerpattern.Observable;
|
import gearth.misc.listenerpattern.Observable;
|
||||||
import gearth.protocol.HMessage;
|
import gearth.protocol.HMessage;
|
||||||
import gearth.protocol.HPacket;
|
import gearth.protocol.HPacket;
|
||||||
|
import gearth.protocol.HPacketFormat;
|
||||||
import gearth.protocol.TrafficListener;
|
import gearth.protocol.TrafficListener;
|
||||||
import gearth.protocol.packethandler.PacketHandler;
|
import gearth.protocol.crypto.RC4Cipher;
|
||||||
import gearth.protocol.packethandler.shockwave.buffers.ShockwaveBuffer;
|
import gearth.protocol.packethandler.EncryptedPacketHandler;
|
||||||
import gearth.protocol.packethandler.shockwave.crypto.RC4Shockwave;
|
import gearth.protocol.packethandler.PayloadBuffer;
|
||||||
import gearth.services.extension_handler.ExtensionHandler;
|
import gearth.services.extension_handler.ExtensionHandler;
|
||||||
import org.bouncycastle.util.encoders.Hex;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
|
||||||
public abstract class ShockwavePacketHandler extends PacketHandler {
|
public abstract class ShockwavePacketHandler extends EncryptedPacketHandler {
|
||||||
|
|
||||||
// The first 20 bytes of the artificialKey.
|
|
||||||
public static final byte[] ARTIFICIAL_KEY = Hex.decode("14d288cdb0bc08c274809a7802962af98b41dec8");
|
|
||||||
|
|
||||||
protected static final Logger logger = LoggerFactory.getLogger(ShockwavePacketHandler.class);
|
protected static final Logger logger = LoggerFactory.getLogger(ShockwavePacketHandler.class);
|
||||||
|
|
||||||
private final HMessage.Direction direction;
|
private final HMessage.Direction direction;
|
||||||
private final ShockwaveBuffer payloadBuffer;
|
private final HPacketFormat format;
|
||||||
|
private final PayloadBuffer payloadBuffer;
|
||||||
private final Object flushLock;
|
private final Object flushLock;
|
||||||
|
|
||||||
protected final OutputStream outputStream;
|
protected final OutputStream outputStream;
|
||||||
|
|
||||||
private boolean isEncrypted;
|
ShockwavePacketHandler(HMessage.Direction direction, PayloadBuffer payloadBuffer, OutputStream outputStream, ExtensionHandler extensionHandler, Observable<TrafficListener>[] trafficObservables) {
|
||||||
private final RC4Shockwave decryptCipher;
|
super(extensionHandler, trafficObservables, direction);
|
||||||
private final RC4Shockwave encryptCipher;
|
|
||||||
|
|
||||||
ShockwavePacketHandler(HMessage.Direction direction, ShockwaveBuffer payloadBuffer, OutputStream outputStream, ExtensionHandler extensionHandler, Observable<TrafficListener>[] trafficObservables) {
|
|
||||||
super(extensionHandler, trafficObservables);
|
|
||||||
this.direction = direction;
|
this.direction = direction;
|
||||||
|
this.format = direction == HMessage.Direction.TOSERVER ? HPacketFormat.WEDGIE_OUTGOING : HPacketFormat.WEDGIE_INCOMING;
|
||||||
this.payloadBuffer = payloadBuffer;
|
this.payloadBuffer = payloadBuffer;
|
||||||
this.outputStream = outputStream;
|
this.outputStream = outputStream;
|
||||||
this.flushLock = new Object();
|
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
|
@Override
|
||||||
public boolean sendToStream(byte[] buffer) {
|
public boolean sendToStream(byte[] buffer) {
|
||||||
return sendToStream(buffer, isEncrypted);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean sendToStream(byte[] buffer, boolean isEncrypted) {
|
|
||||||
synchronized (sendLock) {
|
synchronized (sendLock) {
|
||||||
try {
|
try {
|
||||||
if (!isEncrypted) {
|
|
||||||
outputStream.write(buffer);
|
outputStream.write(buffer);
|
||||||
} else {
|
|
||||||
outputStream.write(HexEncoding.toHex(encryptCipher.crypt(buffer), true));
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logger.error("Failed to send packet to stream", e);
|
logger.error("Failed to send packet to stream", e);
|
||||||
@ -71,20 +50,40 @@ public abstract class ShockwavePacketHandler extends PacketHandler {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void act(byte[] buffer) throws IOException {
|
public void act(byte[] buffer) throws IOException {
|
||||||
if (!isEncrypted) {
|
super.act(buffer);
|
||||||
payloadBuffer.push(buffer);
|
|
||||||
} else {
|
if (!isBlocked()) {
|
||||||
payloadBuffer.push(decryptCipher.crypt(Hex.decode(buffer)));
|
flush();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
public void flush() throws IOException {
|
||||||
synchronized (flushLock) {
|
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);
|
packet.setIdentifierDirection(direction);
|
||||||
|
|
||||||
final HMessage message = new HMessage(packet, direction, currentIndex);
|
final HMessage message = new HMessage(packet, direction, currentIndex);
|
||||||
|
@ -34,7 +34,7 @@ public class ShockwavePacketIncomingHandler extends ShockwavePacketHandler {
|
|||||||
if (packet.headerId() == ID_SECRET_KEY) {
|
if (packet.headerId() == ID_SECRET_KEY) {
|
||||||
logger.info("Received SECRET_KEY from server, enabling encryption / decryption.");
|
logger.info("Received SECRET_KEY from server, enabling encryption / decryption.");
|
||||||
trafficObservables[0].removeListener(this);
|
trafficObservables[0].removeListener(this);
|
||||||
outgoingHandler.setEncrypted();
|
outgoingHandler.setEncryptedStream();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -4,22 +4,55 @@ import gearth.encoding.Base64Encoding;
|
|||||||
import gearth.misc.listenerpattern.Observable;
|
import gearth.misc.listenerpattern.Observable;
|
||||||
import gearth.protocol.HMessage;
|
import gearth.protocol.HMessage;
|
||||||
import gearth.protocol.TrafficListener;
|
import gearth.protocol.TrafficListener;
|
||||||
|
import gearth.protocol.crypto.RC4Cipher;
|
||||||
import gearth.protocol.packethandler.ByteArrayUtils;
|
import gearth.protocol.packethandler.ByteArrayUtils;
|
||||||
import gearth.protocol.packethandler.shockwave.buffers.ShockwaveOutBuffer;
|
import gearth.protocol.packethandler.shockwave.buffers.ShockwaveOutBuffer;
|
||||||
import gearth.services.extension_handler.ExtensionHandler;
|
import gearth.services.extension_handler.ExtensionHandler;
|
||||||
|
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
|
|
||||||
public class ShockwavePacketOutgoingHandler extends ShockwavePacketHandler {
|
public class ShockwavePacketOutgoingHandler extends ShockwavePacketHandler {
|
||||||
|
private RC4Cipher headerEncoder;
|
||||||
|
|
||||||
public ShockwavePacketOutgoingHandler(OutputStream outputStream, ExtensionHandler extensionHandler, Observable<TrafficListener>[] trafficObservables) {
|
public ShockwavePacketOutgoingHandler(OutputStream outputStream, ExtensionHandler extensionHandler, Observable<TrafficListener>[] trafficObservables) {
|
||||||
super(HMessage.Direction.TOSERVER, new ShockwaveOutBuffer(), outputStream, extensionHandler, trafficObservables);
|
super(HMessage.Direction.TOSERVER, new ShockwaveOutBuffer(), outputStream, extensionHandler, trafficObservables);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean sendToStream(byte[] packet) {
|
public boolean sendToStream(byte[] packet) {
|
||||||
byte[] bufferLen = Base64Encoding.encode(packet.length, 3);
|
byte[] bufferLen;
|
||||||
byte[] buffer = ByteArrayUtils.combineByteArrays(bufferLen, packet);
|
|
||||||
|
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);
|
return super.sendToStream(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRc4(RC4Cipher rc4) {
|
||||||
|
this.headerEncoder = rc4.deepCopy();
|
||||||
|
super.setRc4(rc4);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +0,0 @@
|
|||||||
package gearth.protocol.packethandler.shockwave.buffers;
|
|
||||||
|
|
||||||
import gearth.protocol.HPacket;
|
|
||||||
|
|
||||||
public interface ShockwaveBuffer {
|
|
||||||
|
|
||||||
void push(byte[] data);
|
|
||||||
|
|
||||||
HPacket[] receive();
|
|
||||||
|
|
||||||
}
|
|
@ -1,44 +1,39 @@
|
|||||||
package gearth.protocol.packethandler.shockwave.buffers;
|
package gearth.protocol.packethandler.shockwave.buffers;
|
||||||
|
|
||||||
import gearth.protocol.HPacket;
|
import gearth.protocol.crypto.RC4Cipher;
|
||||||
import gearth.protocol.packethandler.ByteArrayUtils;
|
import gearth.protocol.packethandler.PayloadBuffer;
|
||||||
import gearth.protocol.packethandler.shockwave.packets.ShockPacket;
|
|
||||||
import gearth.protocol.packethandler.shockwave.packets.ShockPacketIncoming;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
public class ShockwaveInBuffer implements ShockwaveBuffer {
|
public class ShockwaveInBuffer extends PayloadBuffer {
|
||||||
|
|
||||||
private byte[] buffer = new byte[0];
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void push(byte[] data) {
|
public void setCipher(RC4Cipher cipher) {
|
||||||
buffer = buffer.length == 0 ? data.clone() : ByteArrayUtils.combineByteArrays(buffer, data);
|
// We don't need to decrypt incoming packet headers, for now.
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public HPacket[] receive() {
|
public byte[][] receive() {
|
||||||
if (buffer.length < 3) {
|
if (buffer.length < 3) {
|
||||||
return new ShockPacket[0];
|
return new byte[0][];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Incoming packets are delimited by chr(1).
|
// Incoming packets are delimited by chr(1).
|
||||||
// We need to split the buffer by chr(1) and then parse each packet.
|
// We need to split the buffer by chr(1) and then parse each packet.
|
||||||
ArrayList<ShockPacket> packets = new ArrayList<>();
|
final ArrayList<byte[]> packets = new ArrayList<>();
|
||||||
|
|
||||||
int curPos = 0;
|
int curPos = 0;
|
||||||
|
|
||||||
for (int i = 0; i < buffer.length; i++) {
|
for (int i = 0; i < buffer.length; i++) {
|
||||||
if (buffer[i] == 1) {
|
if (buffer[i] == 1) {
|
||||||
byte[] packetData = Arrays.copyOfRange(buffer, curPos, i);
|
packets.add(Arrays.copyOfRange(buffer, curPos, i));
|
||||||
packets.add(new ShockPacketIncoming(packetData));
|
|
||||||
curPos = i + 1;
|
curPos = i + 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer = Arrays.copyOfRange(buffer, curPos, buffer.length);
|
buffer = Arrays.copyOfRange(buffer, curPos, buffer.length);
|
||||||
|
|
||||||
return packets.toArray(new ShockPacket[0]);
|
return packets.toArray(new byte[0][]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,50 +1,65 @@
|
|||||||
package gearth.protocol.packethandler.shockwave.buffers;
|
package gearth.protocol.packethandler.shockwave.buffers;
|
||||||
|
|
||||||
import gearth.encoding.Base64Encoding;
|
import gearth.encoding.Base64Encoding;
|
||||||
import gearth.protocol.HPacket;
|
import gearth.protocol.crypto.RC4Cipher;
|
||||||
import gearth.protocol.packethandler.ByteArrayUtils;
|
import gearth.protocol.packethandler.PayloadBuffer;
|
||||||
import gearth.protocol.packethandler.shockwave.packets.ShockPacket;
|
|
||||||
import gearth.protocol.packethandler.shockwave.packets.ShockPacketOutgoing;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
public class ShockwaveOutBuffer implements ShockwaveBuffer {
|
public class ShockwaveOutBuffer extends PayloadBuffer {
|
||||||
|
|
||||||
private static final int PACKET_LENGTH_SIZE = 3;
|
public static final int PACKET_HEADER_SIZE = 2;
|
||||||
private 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
|
@Override
|
||||||
public void push(byte[] data) {
|
public void setCipher(RC4Cipher cipher) {
|
||||||
buffer = buffer.length == 0 ? data.clone() : ByteArrayUtils.combineByteArrays(buffer, data);
|
this.cipher = cipher.deepCopy();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public HPacket[] receive() {
|
public byte[][] receive() {
|
||||||
if (buffer.length < PACKET_SIZE_MIN) {
|
final int packetLengthSize = this.cipher != null ? PACKET_LENGTH_SIZE_ENCRYPTED : PACKET_LENGTH_SIZE;
|
||||||
return new ShockPacket[0];
|
final int minPacketSize = this.cipher != null ? PACKET_SIZE_MIN_ENCRYPTED : PACKET_SIZE_MIN;
|
||||||
|
|
||||||
|
if (buffer.length < minPacketSize) {
|
||||||
|
return new byte[0][];
|
||||||
}
|
}
|
||||||
|
|
||||||
ArrayList<ShockPacket> out = new ArrayList<>();
|
final ArrayList<byte[]> out = new ArrayList<>();
|
||||||
|
|
||||||
while (buffer.length >= PACKET_SIZE_MIN) {
|
while (buffer.length >= minPacketSize) {
|
||||||
int length = Base64Encoding.decode(new byte[]{buffer[0], buffer[1], buffer[2]});
|
int length;
|
||||||
if (buffer.length < length + PACKET_LENGTH_SIZE) {
|
|
||||||
|
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;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
int endPos = length + PACKET_LENGTH_SIZE;
|
int endPos = length + packetLengthSize;
|
||||||
byte[] packet = Arrays.copyOfRange(buffer, PACKET_LENGTH_SIZE, endPos);
|
|
||||||
|
|
||||||
out.add(new ShockPacketOutgoing(packet));
|
out.add(Arrays.copyOfRange(buffer, packetLengthSize, endPos));
|
||||||
|
|
||||||
buffer = Arrays.copyOfRange(buffer, endPos, buffer.length);
|
buffer = Arrays.copyOfRange(buffer, endPos, buffer.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
return out.toArray(new ShockPacket[0]);
|
return out.toArray(new byte[0][]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 <a href="https://github.com/aromaa">Joni</a> and <a href="https://github.com/scottstamp">DarkStar851</a> for discovering this.
|
|
||||||
*/
|
|
||||||
public class RC4Shockwave {
|
|
||||||
|
|
||||||
private static final int TABLE_SIZE = 256;
|
|
||||||
|
|
||||||
private final int[] table;
|
|
||||||
|
|
||||||
private int x;
|
|
||||||
private int y;
|
|
||||||
|
|
||||||
public RC4Shockwave(int key, byte[] artificialKey) {
|
|
||||||
this.table = buildTable(key, artificialKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] crypt(byte[] data) {
|
|
||||||
byte[] result = new byte[data.length];
|
|
||||||
|
|
||||||
for (int i = 0; i < data.length; i++) {
|
|
||||||
x = (x + 1) % TABLE_SIZE;
|
|
||||||
y = (y + table[x] & 0xff) % TABLE_SIZE;
|
|
||||||
|
|
||||||
swap(table, x, y);
|
|
||||||
|
|
||||||
int xorIndex = ((table[x] & 0xff) + (table[y] & 0xff)) % TABLE_SIZE;
|
|
||||||
|
|
||||||
result[i] = (byte) (data[i] ^ table[xorIndex & 0xff]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int[] buildTable(int key, byte[] artificialKey) {
|
|
||||||
byte[] modKey = new byte[20];
|
|
||||||
|
|
||||||
for (int i = 0, j = 0; i < modKey.length; i++, j++) {
|
|
||||||
if (j >= artificialKey.length) {
|
|
||||||
j = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
modKey[i] = (byte) (key & modKey[j]);
|
|
||||||
}
|
|
||||||
|
|
||||||
int[] table = new int[TABLE_SIZE];
|
|
||||||
|
|
||||||
for (int i = 0; i < TABLE_SIZE; i++) {
|
|
||||||
table[i] = (byte) i;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int q = 0, j = 0; q < TABLE_SIZE; q++) {
|
|
||||||
j = (j + (table[q] & 0xff) + modKey[q % modKey.length]) % TABLE_SIZE;
|
|
||||||
|
|
||||||
swap(table, q, j);
|
|
||||||
}
|
|
||||||
|
|
||||||
return table;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void swap(int[] table, int i, int j) {
|
|
||||||
int temp = table[i];
|
|
||||||
table[i] = table[j];
|
|
||||||
table[j] = temp;
|
|
||||||
}
|
|
||||||
}
|
|
BIN
G-Earth/src/main/resources/build/mac/G-MemZ
Normal file
BIN
G-Earth/src/main/resources/build/mac/G-MemZ
Normal file
Binary file not shown.
BIN
G-Earth/src/main/resources/build/windows/32bit/G-MemZ.exe
Normal file
BIN
G-Earth/src/main/resources/build/windows/32bit/G-MemZ.exe
Normal file
Binary file not shown.
BIN
G-Earth/src/main/resources/build/windows/64bit/G-MemZ.exe
Normal file
BIN
G-Earth/src/main/resources/build/windows/64bit/G-MemZ.exe
Normal file
Binary file not shown.
212
G-Earth/src/test/java/TestRc4Shockwave.java
Normal file
212
G-Earth/src/test/java/TestRc4Shockwave.java
Normal file
@ -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<RC4Cipher> cipher = new AtomicReference<>();
|
||||||
|
|
||||||
|
private final ShockwaveMemoryClient mockShockwaveMemoryClient = new ShockwaveMemoryClient(null) {
|
||||||
|
@Override
|
||||||
|
public List<byte[]> getRC4cached() {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<byte[]> 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<HabboClientFactory> 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());
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user