reimplement expression prediction + fixes

This commit is contained in:
sirjonasxx 2020-05-08 00:25:51 +02:00
parent 13b1bd055e
commit 4d7bc7ff62
10 changed files with 457 additions and 285 deletions

View File

@ -1,5 +1,6 @@
package gearth.misc.packetrepresentation;
import gearth.misc.packetrepresentation.prediction.StructurePredictor;
import gearth.protocol.HPacket;
import java.nio.ByteBuffer;
@ -9,15 +10,18 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
// for all the logistics behind bytes-string conversion
public class PacketStructure {
public class PacketStringUtils {
private static String replaceAll(String templateText, String regex,
Function<Matcher, String> replacer) {
Pattern pattern = Pattern.compile(regex);
Pattern pattern = Pattern.compile(regex, Pattern.MULTILINE);
Matcher matcher = pattern.matcher(templateText);
StringBuffer result = new StringBuffer();
while (matcher.find()) {
matcher.appendReplacement(result, replacer.apply(matcher));
matcher.appendReplacement(
result,
Matcher.quoteReplacement(replacer.apply(matcher))
);
}
matcher.appendTail(result);
return result.toString();
@ -129,6 +133,10 @@ public class PacketStructure {
else return;
}
}
public static String predictedExpression(HPacket packet) {
StructurePredictor structurePredictor = new StructurePredictor(packet);
return structurePredictor.getExpression();
}
public static boolean structureEquals(HPacket packet, String struct) {
if (packet.isCorrupted()) return false;

View File

@ -0,0 +1,111 @@
package gearth.misc.packetrepresentation.prediction;
import gearth.misc.packetrepresentation.PacketStringUtils;
import gearth.misc.packetrepresentation.prediction.checkers.TypeChecker;
import gearth.misc.packetrepresentation.prediction.checkers.TypeCheckerProducer;
import gearth.protocol.HPacket;
import java.util.List;
public class StructurePredictor {
HPacket packet;
String structure; // null if not found/ didnt try to find
public StructurePredictor(HPacket packet) {
this.packet = packet;
if (packet.isCorrupted()) {
structure = null;
}
else {
predictStructure();
}
}
private static class SubStructure {
int previous;
String type;
double logScore;
public SubStructure(int previous, String type, double logScore) {
this.previous = previous;
this.type = type;
this.logScore = logScore;
}
}
private void predictStructure() {
List<TypeChecker> typeCheckers = TypeCheckerProducer.getValidators(packet);
SubStructure[] dynamic = new SubStructure[packet.getBytesLength() - 6 + 1];
dynamic[0] = new SubStructure(-1, "", 0.0);
int index = 6;
while (index < packet.getBytesLength()) {
double currentLogScore = dynamic[index - 6].logScore;
for (TypeChecker typeChecker : typeCheckers) {
if (typeChecker.canRead(index)) {
double score = typeChecker.score(index);
double newScore = currentLogScore + Math.log(score);
int nextIndex = typeChecker.nextIndex(index) - 6;
if (dynamic[nextIndex] == null || newScore > dynamic[nextIndex].logScore) {
dynamic[nextIndex] = new SubStructure(
index - 6,
typeChecker.getStructCode(),
newScore
);
}
}
}
index++;
}
StringBuilder stringBuilder = new StringBuilder();
SubStructure current = dynamic[dynamic.length - 1];
while (current.previous != -1) {
stringBuilder.append(current.type);
current = dynamic[current.previous];
}
structure = stringBuilder.reverse().toString();
}
public String getExpression() {
if (structure == null) {
return "";
}
return PacketStringUtils.toExpressionFromGivenStructure(packet, structure);
}
public String getStructure() {
return structure;
}
public static void main(String[] args) {
HPacket[] packets = new HPacket[] {
new HPacket("{l}{u:500}{i:20}"),
new HPacket(4002, "test"),
new HPacket(4002, "test", 0, true),
new HPacket(4002, "test", "testtsd", 54452, true, false),
new HPacket(4002, "test", 46564554, "testtsd", 54452, true, false),
new HPacket(4002, "test", 0, 46564554, "testtsd", 54452, true, false),
new HPacket(4002, -1, "test", 0, 46564554, "testtsd", -1, 54452, true, false),
new HPacket(4002, -1, "test", 0, 46564554, "testtsd", -1, 54452, false, true, ""),
new HPacket(4002, -1, "test", 0, 46564554, "testtsd", -1, 54452, false, true, ""),
new HPacket(4002, -1, "test", 0, 46564554, "testtsd", -1, 54452, false, true, 0),
new HPacket(4002, -1, (byte) 5, "test", 0, 46564554, "testtsd", -1, 54452, false, true, 0),
new HPacket(4002, "", 20, 0),
new HPacket(4002, 1),
new HPacket(4002, 0, 0),
new HPacket(4002, 0, 0, 42, false),
new HPacket(4002, 0, "")
};
for (HPacket packet : packets) {
StructurePredictor structurePredictor = new StructurePredictor(packet);
System.out.println(structurePredictor.getStructure());
System.out.println(structurePredictor.getExpression());
}
}
}

View File

@ -0,0 +1,36 @@
package gearth.misc.packetrepresentation.prediction.checkers;
import gearth.protocol.HPacket;
public class BooleanChecker extends TypeChecker<Boolean> {
BooleanChecker(HPacket hPacket) {
super("B", hPacket);
}
@Override
public boolean canRead(int index) {
return index >= 6 && index < hPacket.getBytesLength() &&
(hPacket.toBytes()[index] == 1 || hPacket.toBytes()[index] == 0);
}
@Override
public double score(int index) {
// A [0] byte is not very likely to be a boolean
// A [1] byte is not very likely to be a boolean, but a little bit more
// 0.6 may seem pretty high but 4 bytes in a row would get score 0.6*0.6*0.6*0.6, which is low
return get(index) ? 0.6 : 0.5;
}
@Override
public Boolean get(int index) {
return hPacket.readBoolean(index);
}
@Override
int nextIndexSafe(int index) {
return index + 1;
}
}

View File

@ -0,0 +1,32 @@
package gearth.misc.packetrepresentation.prediction.checkers;
import gearth.protocol.HPacket;
public class ByteChecker extends TypeChecker<Byte> {
ByteChecker(HPacket hPacket) {
super("b", hPacket);
}
@Override
public boolean canRead(int index) {
return index >= 6 && index < hPacket.getBytesLength();
}
@Override
public double score(int index) {
// A byte is never likely to be parsed as a literal byte, only rarely
return 0.4;
}
@Override
public Byte get(int index) {
return hPacket.readByte(index);
}
@Override
int nextIndexSafe(int index) {
return index + 1;
}
}

View File

@ -0,0 +1,103 @@
package gearth.misc.packetrepresentation.prediction.checkers;
import gearth.protocol.HPacket;
import java.nio.charset.StandardCharsets;
public class IntegerChecker extends TypeChecker<Integer> {
IntegerChecker(HPacket hPacket) {
super("i", hPacket);
}
@Override
public boolean canRead(int index) {
return index >= 6 && !(index + 4 > hPacket.getBytesLength());
}
@Override
public double score(int index) {
int value = get(index);
int ushortTest = hPacket.readUshort(index);
int ushortTest2 = hPacket.readUshort(index + 2);
// if 4 bytes reads like xy[0][0] or xy[0][1], it is very unlikely to be an actual integer
if (ushortTest != 0 && (ushortTest2 == 0 || ushortTest2 == 256 || ushortTest2 == 1 || ushortTest2 == 257)) {
return 0.05;
}
// 4 bytes that read [0][0][1][0] are also unlikely to be an integer
if (value == 256) {
return 0.04;
}
// 4 bytes that read [0][2]xy could be a string
if (ushortTest >= 2 && ushortTest <= 6 && StringChecker.canReadString(hPacket, index)) {
return (1 - StringChecker.scoreString(hPacket.readString(index)));
}
// if 4 bytes read "abcd", it will most likely be part of a string
// so check if bytes are common
byte[] asBytes = hPacket.readBytes(4, index);
char[] asChars = new String(asBytes, StandardCharsets.ISO_8859_1).toCharArray();
int count = 0;
for (int i = 0; i < 4; i++) {
if (StringChecker.isCommon(asChars[i], asBytes[i]) == 2) {
count++;
}
}
if (count == 4) {
return 0.2;
}
// also this
if (StringChecker.canReadString(hPacket, index - 1)) {
String s = hPacket.readString(index - 1);
if (s.length() > 2 && s.length() < 10) {
if (StringChecker.scoreString(s) > 0.5) {
return 1 - StringChecker.scoreString(s);
}
}
}
// when ordering, it often appears that integers are placed before strings/booleans/etc
// in the case of empty strings or false booleans, it is as good as always the integer that comes first
// so we'll try to respect that here with a small score adjust, which doesnt affect anything else than ordering
double offset = ((double)index) / 1000000000;
// since -1 has a byte arrangement barely used by other packets, we can assign full score
if (value == -1) {
return 1 - offset;
}
if (value == 0) {
return 0.99 - offset;
}
// if the value is not 0, but the last byte is 0/1, we assign a little less score
// to keep the possibility open for a boolean
if (value % 256 == 0) {
return 0.87 - offset;
}
if (value != 1 && value % 256 == 1) {
return 0.88 - offset;
}
if (value >= -1) {
return 0.92 - offset;
}
return 0.8 - offset;
}
@Override
public Integer get(int index) {
return hPacket.readInteger(index);
}
@Override
int nextIndexSafe(int index) {
return index + 4;
}
}

View File

@ -0,0 +1,84 @@
package gearth.misc.packetrepresentation.prediction.checkers;
import gearth.protocol.HPacket;
import java.nio.charset.StandardCharsets;
public class StringChecker extends TypeChecker<String> {
StringChecker(HPacket hPacket) {
super("s", hPacket);
}
@Override
public boolean canRead(int index) {
return canReadString(hPacket, index);
}
@Override
public double score(int index) {
return scoreString(get(index));
}
@Override
public String get(int index) {
return hPacket.readString(index);
}
@Override
int nextIndexSafe(int index) {
return index + get(index).length() + 2;
}
// 2 = very common
// 1 = a bit common
// 0 = rare
public static int isCommon(char c, byte b) {
if (c == '\n' || c == '\r' ||c == '\t') {
return 1;
}
int uByte = (((int)b) + 256) % 256;
if (uByte >= 32 && uByte <= 126) {
return 2;
}
return 0;
}
public static double scoreString(String s) {
byte[] asBytes = s.getBytes(StandardCharsets.ISO_8859_1);
char[] asChars = s.toCharArray();
if (s.equals("")) {
return 0.8;
}
int len = s.length();
double score = 1;
double[] penalties = new double[]{
(-1.0/(len*0.3+2) + 0.5),
(-1.0/(len+2)) + 1,
s.length() == 1 ? 0.9 : (s.length() == 2 ? 0.98 : 1.0)
};
for (int i = 0; i < s.length(); i++) {
score *= penalties[isCommon(
asChars[i],
asBytes[i]
)];
if (score < 0.001) {
return 0;
}
}
return score;
}
public static boolean canReadString(HPacket packet, int index) {
int l = packet.getBytesLength();
return index >= 6 && !(index + 2 > l || packet.readUshort(index) + 2 + index > l);
}
}

View File

@ -0,0 +1,33 @@
package gearth.misc.packetrepresentation.prediction.checkers;
import gearth.protocol.HPacket;
public abstract class TypeChecker<T> {
protected String structCode;
protected HPacket hPacket;
protected TypeChecker(String structCode, HPacket hPacket) {
this.structCode = structCode;
this.hPacket = hPacket;
}
public abstract boolean canRead(int index);
public abstract double score(int index);
abstract T get(int index);
// -1 if cant read
public int nextIndex(int index) {
if (!canRead(index)) return -1;
return nextIndexSafe(index);
}
abstract int nextIndexSafe(int index);
public String getStructCode() {
return structCode;
}
}

View File

@ -0,0 +1,19 @@
package gearth.misc.packetrepresentation.prediction.checkers;
import gearth.protocol.HPacket;
import java.util.Arrays;
import java.util.List;
public class TypeCheckerProducer {
public static List<TypeChecker> getValidators(HPacket packet) {
return Arrays.asList(
new BooleanChecker(packet),
new ByteChecker(packet),
new IntegerChecker(packet),
new StringChecker(packet)
);
}
}

View File

@ -4,7 +4,7 @@ import gearth.misc.StringifyAble;
import gearth.misc.harble_api.HarbleAPI;
import gearth.misc.harble_api.HarbleAPIFetcher;
import gearth.misc.packetrepresentation.InvalidPacketException;
import gearth.misc.packetrepresentation.PacketStructure;
import gearth.misc.packetrepresentation.PacketStringUtils;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
@ -27,11 +27,11 @@ public class HPacket implements StringifyAble {
}
public HPacket(String packet) {
try {
packetInBytes = PacketStructure.fromString(packet).packetInBytes;
packetInBytes = PacketStringUtils.fromString(packet).packetInBytes;
} catch (InvalidPacketException e) {
packetInBytes = new byte[0];
// will be corrupted
e.printStackTrace();
// e.printStackTrace();
}
}
public HPacket(int header) {
@ -75,11 +75,11 @@ public class HPacket implements StringifyAble {
}
public String toString() {
return PacketStructure.toString(packetInBytes);
return PacketStringUtils.toString(packetInBytes);
}
public boolean structureEquals(String structure) {
return PacketStructure.structureEquals(this, structure);
return PacketStringUtils.structureEquals(this, structure);
}
public int isEOF() {
@ -269,11 +269,6 @@ public class HPacket implements StringifyAble {
replaceByte(index + i, bytes[i]);
i++;
}
//
// if (i < bytes.length) {
// appendBytes(Arrays.copyOfRange(bytes, i, bytes.length));
// fixLength();
// }
return this;
}
public HPacket replaceUShort(int index, int ushort) {
@ -515,7 +510,7 @@ public class HPacket implements StringifyAble {
if (HarbleAPIFetcher.HARBLEAPI != null &&
((msg = HarbleAPIFetcher.HARBLEAPI.getHarbleMessageFromHeaderId(direction, headerId())) != null)) {
if (msg.getStructure() != null) {
return PacketStructure.toExpressionFromGivenStructure(this, msg.getStructure());
return PacketStringUtils.toExpressionFromGivenStructure(this, msg.getStructure());
}
}
return toExpression();
@ -527,269 +522,7 @@ public class HPacket implements StringifyAble {
*/
public String toExpression() {
if (isCorrupted()) return "";
boolean[] mask = new boolean[packetInBytes.length];
String[] resultTest = new String[packetInBytes.length];
for (int i = 0; i < 6; i++) {
mask[i] = true;
}
resultTest[0] = "{l}";
resultTest[4] = "{u:"+headerId()+"}";
outerloop:
for (int i = 6; i < packetInBytes.length - 1; i++) {
int potentialstringlength = readUshort(i);
if ((potentialstringlength >= 0 && potentialstringlength < 3) || potentialstringlength > packetInBytes.length - i - 2) continue;
for (int j = i; j < potentialstringlength+i+2; j++) {
if (mask[j]) continue outerloop;
}
for (int j = i+2; j < potentialstringlength+i+2; j++) {
if (readByte(j) >= 0 && readByte(j) < 6) continue outerloop;
}
if (i + 2 + potentialstringlength >= packetInBytes.length - 3 ||
(packetInBytes[i+2+potentialstringlength] >= 0 &&
packetInBytes[i+2+potentialstringlength] < 6 )) {
for (int j = i; j < potentialstringlength+i+2; j++) {
mask[j] = true;
}
resultTest[i] = "{s:\""+readString(i).replace("\"", "\\\"")+"\"}";
i += (1 + potentialstringlength);
}
}
//TODO add special case for seperated 5, 6 and 7 bytes here
// long live the shitty code.
//5
out:
for (int i = 6; i < packetInBytes.length - 4; i++) {
for (int j = i; j < i+5; j++) {
if (mask[j]) {
i = j;
continue out;
}
}
if (!mask[i-1] || (i+5 < packetInBytes.length && !mask[i+5])) continue;
if ((readByte(i) == 0 || readByte(i) == 1) && (readInteger(i+1) > 1 || readInteger(i+1) < 0)) {
//decide the first byte to be the a boolean
resultTest[i] = "{b:"+(readBoolean(i) ? "true" : "false")+"}";
resultTest[i+1] = "{i:"+readInteger(i+1)+"}";
for (int j = i; j < i+5; j++) {
mask[j] = true;
}
}
}
// //6
// out:
// for (int i = 6; i < packetInBytes.length - 5; i++) {
// for (int j = i; j < i+6; j++) {
// if (mask[j]) {
// i = j;
// continue out;
// }
// }
// if (i+6 < packetInBytes.length && !mask[i+6]) continue;
//
//
//
// }
//
// //7
// out:
// for (int i = 6; i < packetInBytes.length - 6; i++) {
// for (int j = i; j < i+7; j++) {
// if (mask[j]) {
// i = j;
// continue out;
// }
// }
// if (i+7 < packetInBytes.length && !mask[i+7]) continue;
//
//
//
// }
lp22:
for (int i = 6; i < packetInBytes.length - 3; i++) {
for (int j = i; j < i + 4; j++) {
if (mask[j]) {
continue lp22;
}
}
int num = readInteger(i);
if (num == -1 || (num >= 0 && num < 256)) {
for (int j = i; j < i+4; j++) {
mask[j] = true;
}
resultTest[i] = "{i:"+num+"}";
i += 3;
}
}
boolean changed = true;
boolean isfirst = true;
while (changed) {
changed = false;
// filtering strings
outerloop:
for (int i = 6; i < packetInBytes.length - 1; i++) {
int potentialstringlength = readUshort(i);
if ((potentialstringlength >= 0 && potentialstringlength < 3) || potentialstringlength > packetInBytes.length - i - 2) continue;
for (int j = i; j < potentialstringlength+i+2; j++) {
if (mask[j]) continue outerloop;
}
for (int j = i+2; j < potentialstringlength+i+2; j++) {
if (readByte(j) >= 0 && readByte(j) < 6) continue outerloop;
}
if (i + 2 + potentialstringlength >= packetInBytes.length - 3 ||
(packetInBytes[i+2+potentialstringlength] >= 0 &&
packetInBytes[i+2+potentialstringlength] < 6 )) {
for (int j = i; j < potentialstringlength+i+2; j++) {
mask[j] = true;
}
changed = true;
resultTest[i] = "{s:\""+readString(i).replace("\"", "\\\"")+"\"}";
i += (1 + potentialstringlength);
}
}
if (isfirst) {
int count = 0;
for (int i = 6; i < packetInBytes.length; i++) {
if (!mask[i]) count++;
}
if (count > 300) return "";
}
isfirst = false;
// filtering integers
boolean hasfoundsomtin = true;
while (hasfoundsomtin) {
hasfoundsomtin = false;
outerloop2:
for (int i = 6; i < packetInBytes.length - 3; i++) {
for (int j = i; j < i + 4; j++) {
if (mask[j]) {
continue outerloop2;
}
}
if (i + 4 == packetInBytes.length || mask[i+4] || mask[i-1]) {
if (packetInBytes[i+1] == 2) { //could be an unfiltered string; don't filter yet
if (((packetInBytes[i+2] >= '0' && packetInBytes[i+2] <= '9') ||
(packetInBytes[i+2] >= 'a' && packetInBytes[i+2] <= 'z') ||
(packetInBytes[i+2] >= 'A' && packetInBytes[i+2] <= 'Z')) &&
((packetInBytes[i+3] >= '0' && packetInBytes[i+3] <= '9') ||
(packetInBytes[i+3] >= 'a' && packetInBytes[i+3] <= 'z') ||
(packetInBytes[i+3] >= 'A' && packetInBytes[i+3] <= 'Z'))) {
changed = true;
for (int j = i; j < i + 4; j++) {
mask[j] = true;
}
hasfoundsomtin = true;
resultTest[i] = "{i:"+readInteger(i)+"}";
continue;
}
continue ;
}
else {
for (int j = i; j < i + 4; j++) {
mask[j] = true;
}
hasfoundsomtin = true;
changed = true;
resultTest[i] = "{i:"+readInteger(i)+"}";
continue;
}
}
if (readInteger(i) < 65536) {
for (int j = i; j < i + 4; j++) {
mask[j] = true;
}
hasfoundsomtin = true;
resultTest[i] = "{i:"+readInteger(i)+"}";
changed = true;
continue;
}
}
}
// filtering strings
outerloop3:
for (int i = 6; i < packetInBytes.length - 1; i++) {
int potentialstringlength = readUshort(i);
if (potentialstringlength > packetInBytes.length - i - 2) continue;
for (int j = i; j < potentialstringlength+i+2; j++) {
if (mask[j]) continue outerloop3;
}
for (int j = i+2; j < potentialstringlength+i+2; j++) {
if (readByte(j) >= 0 && readByte(j) < 6) continue outerloop3;
}
for (int j = i; j < potentialstringlength+i+2; j++) {
mask[j] = true;
}
resultTest[i] = "{s:\""+readString(i).replace("\"", "\\\"")+"\"}";
i += (1 + potentialstringlength);
changed = true;
}
}
int opeenvolging = 0;
for (int i = 6; i < packetInBytes.length; i++) {
if (!mask[i]) {
opeenvolging++;
if (opeenvolging == 4) return "";
if (i+1 == packetInBytes.length || mask[i+1]) {
for (int j = i - opeenvolging + 1; j <= i; j++) {
mask[j] = true;
if (packetInBytes[j] == 1 || packetInBytes[j] == 0) {
resultTest[j] = "{b:"+(packetInBytes[j] == 1 ? "true" : "false")+"}";
}
else {
resultTest[j] = "{b:"+((((int)packetInBytes[j])+256)%256)+"}";
}
}
opeenvolging = 0;
}
}
}
// if all values in mask are true, go further
for (boolean bool : mask) {
if (!bool) return "";
}
StringBuilder expression = new StringBuilder();
for (int i = 0; i < resultTest.length; i++) {
if (resultTest[i] != null) expression.append(resultTest[i]);
}
return expression.toString().replace("{i:0}{b:false}{b:true}", "{s:\"\"}{i:1}");
return PacketStringUtils.predictedExpression(this);
}
@Override
@ -816,7 +549,7 @@ public class HPacket implements StringifyAble {
public static void main(String[] args) {
HPacket packet = new HPacket("{l}{u:4564}{i:3}{i:0}{s:\"hi\"}{i:0}{i:1}{s:\"how\"}{i:3}{b:1}{b:2}{b:3}{i:2}{s:\"r u\"}{i:1}{b:120}{i:2}{b:true}{d:1.4}");
String str = PacketStructure.toExpressionFromGivenStructure(packet, "i(isi(b))iBd");
String str = PacketStringUtils.toExpressionFromGivenStructure(packet, "i(isi(b))iBd");
HPacket packetverify = new HPacket(str);

View File

@ -28,14 +28,27 @@ public class InjectionController extends SubForm {
}
private boolean isPacketIncomplete(String line) {
int unmatchedBraces = 0;
for (int i = 0; i < line.length(); i++)
if (line.charAt(i) == '{')
unmatchedBraces++;
else if (line.charAt(i) == '}')
unmatchedBraces--;
boolean unmatchedBrace = false;
return unmatchedBraces != 0;
boolean ignoreBrace = false;
for (int i = 0; i < line.length(); i++) {
if (unmatchedBrace && line.charAt(i) == '"' && line.charAt(i - 1) != '\\') {
ignoreBrace = !ignoreBrace;
}
if (!ignoreBrace) {
if (line.charAt(i) == '{'){
unmatchedBrace = true;
}
else if (line.charAt(i) == '}') {
unmatchedBrace = false;
}
}
}
return unmatchedBrace;
}
private HPacket[] parsePackets(String fullText) {