Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@ public static boolean isAccess(int access, int opcode) {
return (access & opcode) != 0;
}

public static boolean isAccess(int opcode, int... access) {
return Arrays.stream(access).allMatch(access1 -> (access1 & opcode) != 0);
}

/**
* Convert constant value to instruction that represents this constant
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@
import uwu.narumi.deobfuscator.core.other.composed.general.ComposedPeepholeCleanTransformer;
import uwu.narumi.deobfuscator.core.other.impl.clean.LocalVariableNamesCleanTransformer;
import uwu.narumi.deobfuscator.core.other.impl.pool.InlineLocalVariablesTransformer;
import uwu.narumi.deobfuscator.core.other.impl.sb27.SuperblaubeereInvokeDynamicTransformer;
import uwu.narumi.deobfuscator.core.other.impl.sb27.SuperblaubeereStringTransformer;
import uwu.narumi.deobfuscator.core.other.impl.universal.InlinePureFunctionsTransformer;
import uwu.narumi.deobfuscator.core.other.impl.universal.UniversalFlowTransformer;
import uwu.narumi.deobfuscator.core.other.impl.universal.UniversalNumberTransformer;
import uwu.narumi.deobfuscator.core.other.impl.universal.pool.UniversalNumberPoolTransformer;
import uwu.narumi.deobfuscator.core.other.impl.universal.pool.UniversalStringPoolTransformer;
import uwu.narumi.deobfuscator.core.other.impl.sb27.SuperblaubeereStringTransformer;
import uwu.narumi.deobfuscator.core.other.impl.universal.UniversalFlowTransformer;

/**
* https://github.com/superblaubeere27/obfuscator
Expand All @@ -19,14 +21,16 @@ public ComposedSuperblaubeereTransformer() {
// Remove var names as they are obfuscated and names are useless
LocalVariableNamesCleanTransformer::new,

UniversalNumberTransformer::new,
UniversalNumberPoolTransformer::new,

// Fix flow
UniversalFlowTransformer::new,

// Inline number pools
UniversalNumberPoolTransformer::new,
// Decrypt strings
SuperblaubeereStringTransformer::new,
UniversalStringPoolTransformer::new,
SuperblaubeereInvokeDynamicTransformer::new,

InlinePureFunctionsTransformer::new,
InlineLocalVariablesTransformer::new,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package uwu.narumi.deobfuscator.core.other.impl.sb27;

import org.objectweb.asm.Type;
import org.objectweb.asm.tree.*;
import uwu.narumi.deobfuscator.api.asm.MethodContext;
import uwu.narumi.deobfuscator.api.asm.matcher.Match;
import uwu.narumi.deobfuscator.api.asm.matcher.MatchContext;
import uwu.narumi.deobfuscator.api.asm.matcher.group.SequenceMatch;
import uwu.narumi.deobfuscator.api.asm.matcher.impl.*;
import uwu.narumi.deobfuscator.api.transformer.Transformer;

import java.util.*;
import java.util.regex.Pattern;

public class SuperblaubeereInvokeDynamicTransformer extends Transformer {

Match indyMatch = SequenceMatch.of(
FieldMatch.getStatic().capture("type"),
NumberMatch.numInteger().capture("position"),
Match.of(ctx -> ctx.insn().isType()).or(StringMatch.of()).capture("ldc"),
OpcodeMatch.of(AASTORE)
);

private final Pattern INTEGER_PATTERN = Pattern.compile("^-?\\d+$");

@Override
protected void transform() throws Exception {
context().classes().forEach(classWrapper -> {
Map<Integer, String> callInfos = new HashMap<>();
Map<Integer, Type> types = new HashMap<>();

Set<MethodNode> methodsToRemove = new HashSet<>();
Set<FieldInsnNode> fieldsToRemove = new HashSet<>();

classWrapper.methods().forEach(methodNode -> {
MethodContext methodContext = MethodContext.of(classWrapper, methodNode);

if (isAccess(methodNode.access, ACC_PRIVATE, ACC_STATIC) && methodNode.desc.equals("()V")) {
indyMatch.findAllMatches(methodContext).forEach(matchContext -> {
FieldInsnNode fieldInsnNode = matchContext.captures().get("type").insn().asFieldInsn();
int position = matchContext.captures().get("position").insn().asInteger();
AbstractInsnNode ldc = matchContext.captures().get("ldc").insn();
if (ldc.isType()) {
types.put(position, ldc.asType());
} else {
callInfos.put(position, ldc.asString());
}
fieldsToRemove.add(fieldInsnNode);
methodsToRemove.add(methodNode);
markChange();
});
}
});

classWrapper.methods().forEach(methodNode -> {
MethodContext methodContext = MethodContext.of(classWrapper, methodNode);
Match indyCallMatch = Match.of(ctx -> ctx.insn() instanceof InvokeDynamicInsnNode indy && INTEGER_PATTERN.matcher(indy.name).matches() && callInfos.containsKey(Integer.parseInt(indy.name)));
indyCallMatch.findAllMatches(methodContext).forEach(matchContext -> {
InvokeDynamicInsnNode node = matchContext.insn().asInvokeDynamicInsn();
String[] parts = callInfos.get(Integer.parseInt(node.name)).split(":");
String owner = parts[0].replace('.', '/');
String name = parts[1];
String desc = parts[2];
int type = parts[3].length();

Type fieldType = null;
if (type > 2) {
fieldType = types.get(Integer.parseInt(desc));
if (fieldType == null) return;
}
switch (type) {
case 2:
methodNode.instructions.set(node, new MethodInsnNode(INVOKEVIRTUAL, owner, name, desc, false));
break;
case 3:
methodNode.instructions.set(node, new FieldInsnNode(GETFIELD, owner, name, fieldType.getDescriptor()));
break;
case 4:
methodNode.instructions.set(node, new FieldInsnNode(GETSTATIC, owner, name, fieldType.getDescriptor()));
break;
case 5:
methodNode.instructions.set(node, new FieldInsnNode(PUTFIELD, owner, name, fieldType.getDescriptor()));
break;
default:
if (type <= 2) {
methodNode.instructions.set(node, new MethodInsnNode(INVOKESTATIC, owner, name, desc, false));
} else {
methodNode.instructions.set(node, new FieldInsnNode(PUTSTATIC, owner, name, fieldType.getDescriptor()));
}
break;
}
});
});
findClInit(classWrapper.classNode()).ifPresent(clinit -> {
MethodContext methodContext = MethodContext.of(classWrapper, clinit);
Match.of(ctx -> {
if (ctx.insn() instanceof MethodInsnNode node) {
if (node.getOpcode() == INVOKESTATIC &&
node.owner.equals(classWrapper.name()) &&
methodsToRemove.stream().anyMatch(method -> method.name.equals(node.name) && method.desc.equals(node.desc))) {
return true;
}
}
return false;
}).findAllMatches(methodContext).forEach(MatchContext::removeAll);
});

fieldsToRemove.forEach(fieldInsnNode -> classWrapper.fields().removeIf(fieldNode -> fieldNode.name.equals(fieldInsnNode.name)
&& fieldNode.desc.equals(fieldInsnNode.desc)));
classWrapper.methods().removeAll(methodsToRemove);
classWrapper.methods().removeIf(methodNode -> methodNode.desc.equals("(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;"));
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,13 @@ public class SuperblaubeereStringTransformer extends Transformer {
MethodMatch.invokeSpecial().owner("javax/crypto/spec/SecretKeySpec").name("<init>").desc("([BLjava/lang/String;)V")
);

private static final Match STRING_DECRYPT_AES_MATCH = SequenceMatch.of(
MethodMatch.invokeVirtual().owner("java/lang/String").name("getBytes").desc("(Ljava/nio/charset/Charset;)[B"),
MethodMatch.invokeVirtual().owner("java/security/MessageDigest").name("digest").desc("([B)[B"),
StringMatch.of("AES"),
MethodMatch.invokeSpecial().owner("javax/crypto/spec/SecretKeySpec").name("<init>").desc("([BLjava/lang/String;)V")
);

/*
new java/lang/String
dup
Expand Down Expand Up @@ -108,6 +115,8 @@ protected void transform() throws Exception {
decryptMethods.put(MethodRef.of(classWrapper.classNode(), methodNode), SuperblaubeereStringTransformer::decryptStringBlowfish);
} else if (STRING_DECRYPT_DES_MATCH.findFirstMatch(methodContext) != null) {
decryptMethods.put(MethodRef.of(classWrapper.classNode(), methodNode), SuperblaubeereStringTransformer::decryptStringDES);
} else if (STRING_DECRYPT_AES_MATCH.findFirstMatch(methodContext) != null) {
decryptMethods.put(MethodRef.of(classWrapper.classNode(), methodNode), SuperblaubeereStringTransformer::decryptStringAES);
} else if (STRING_DECRYPT_XOR_MATCH.findFirstMatch(methodContext) != null) {
decryptMethods.put(MethodRef.of(classWrapper.classNode(), methodNode), SuperblaubeereStringTransformer::decryptXOR);
}
Expand Down Expand Up @@ -173,6 +182,17 @@ private static String decryptStringDES(String encryptedString, String key) {
}
}

private static String decryptStringAES(String encryptedString, String key) {
try {
SecretKeySpec secretKeySpec = new SecretKeySpec(MessageDigest.getInstance("SHA-256").digest(key.getBytes(StandardCharsets.UTF_8)), "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(2, secretKeySpec);
return new String(cipher.doFinal(Base64.getDecoder().decode(encryptedString.getBytes(StandardCharsets.UTF_8))), StandardCharsets.UTF_8);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}

private static String decryptXOR(String encryptedString, String key) {
encryptedString = new String(Base64.getDecoder().decode(encryptedString.getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8);
StringBuilder builder = new StringBuilder();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,15 @@
package uwu.narumi.deobfuscator.core.other.impl.universal.pool;

import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.FieldInsnNode;
import org.objectweb.asm.tree.LdcInsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.TypeInsnNode;
import org.objectweb.asm.tree.*;
import uwu.narumi.deobfuscator.api.asm.MethodContext;
import uwu.narumi.deobfuscator.api.asm.matcher.Match;
import uwu.narumi.deobfuscator.api.asm.matcher.MatchContext;
import uwu.narumi.deobfuscator.api.asm.matcher.impl.FieldMatch;
import uwu.narumi.deobfuscator.api.asm.matcher.impl.FrameMatch;
import uwu.narumi.deobfuscator.api.asm.matcher.impl.NumberMatch;
import uwu.narumi.deobfuscator.api.asm.matcher.impl.OpcodeMatch;
import uwu.narumi.deobfuscator.api.asm.matcher.impl.StringMatch;
import uwu.narumi.deobfuscator.api.asm.matcher.impl.*;
import uwu.narumi.deobfuscator.api.transformer.Transformer;

import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;

/**
* Replaces string pool references with actual values.
Expand All @@ -33,73 +27,79 @@ public class UniversalStringPoolTransformer extends Transformer {
@Override
protected void transform() throws Exception {
scopedClasses().forEach(classWrapper -> {
MatchContext stringPoolMatchCtx = classWrapper.methods().stream()
Set<MethodNode> toRemoveMn = new HashSet<>();
Set<FieldNode> toRemoveFn = new HashSet<>();
classWrapper.methods().stream()
.map(methodNode -> STRING_POOL_METHOD_MATCH.findFirstMatch(MethodContext.of(classWrapper, methodNode)))
.filter(Objects::nonNull)
.findFirst()
.orElse(null);

// No number pool method found
if (stringPoolMatchCtx == null) return;

//System.out.println("Found string pool method in " + classWrapper.name() + "." + stringPoolMatchCtx.insnContext().methodNode().name);

int stringPoolSize = stringPoolMatchCtx.captures().get("size").insn().asInteger();
FieldInsnNode stringPoolFieldInsn = (FieldInsnNode) stringPoolMatchCtx.captures().get("stringPoolField").insn();

// Get whole number pool
String[] stringPool = new String[stringPoolSize];
STORE_STRING_TO_ARRAY_MATCH.findAllMatches(stringPoolMatchCtx.insnContext().methodContext()).forEach(storeNumberMatchCtx -> {
int index = storeNumberMatchCtx.captures().get("index").insn().asInteger();
String value = storeNumberMatchCtx.captures().get("value").insn().asString();

stringPool[index] = value;
});

for (String string : stringPool) {
if (string == null) {
// String pool is not fully initialized
return;
}
}

Match stringPoolReferenceMatch = OpcodeMatch.of(AALOAD) // AALOAD - Load array reference
// Index
.and(FrameMatch.stack(0, NumberMatch.of().capture("index")
// Load number pool field
.and(FrameMatch.stack(0, FieldMatch.getStatic().owner(stringPoolFieldInsn.owner).name(stringPoolFieldInsn.name).desc(stringPoolFieldInsn.desc)))
));

// Replace number pool references with actual values
classWrapper.methods().forEach(methodNode -> {
MethodContext methodContext = MethodContext.of(classWrapper, methodNode);

stringPoolReferenceMatch.findAllMatches(methodContext).forEach(numberPoolReferenceCtx -> {
int index = numberPoolReferenceCtx.captures().get("index").insn().asInteger();
String value = stringPool[index];

// Value
methodNode.instructions.insert(numberPoolReferenceCtx.insn(), new LdcInsnNode(value));
numberPoolReferenceCtx.removeAll();
markChange();
});
});

// Cleanup
classWrapper.methods().remove(stringPoolMatchCtx.insnContext().methodNode());
classWrapper.fields().removeIf(fieldNode -> fieldNode.name.equals(stringPoolFieldInsn.name) && fieldNode.desc.equals(stringPoolFieldInsn.desc));
// Remove string pool initialization from clinit
classWrapper.findClInit().ifPresent(clinit -> {
for (AbstractInsnNode insn : clinit.instructions.toArray()) {
if (insn.getOpcode() == INVOKESTATIC && insn instanceof MethodInsnNode methodInsn &&
methodInsn.name.equals(stringPoolMatchCtx.insnContext().methodNode().name) && methodInsn.desc.equals(stringPoolMatchCtx.insnContext().methodNode().desc) &&
methodInsn.owner.equals(classWrapper.name())
) {
// Remove invocation
clinit.instructions.remove(insn);
}
}
});
.filter(Objects::nonNull).forEach(stringPoolMatchCtx -> {
AtomicBoolean changedForThisContext = new AtomicBoolean(false);
//System.out.println("Found string pool method in " + classWrapper.name() + "." + stringPoolMatchCtx.insnContext().methodNode().name);

int stringPoolSize = stringPoolMatchCtx.captures().get("size").insn().asInteger();
FieldInsnNode stringPoolFieldInsn = (FieldInsnNode) stringPoolMatchCtx.captures().get("stringPoolField").insn();

// Get whole number pool
String[] stringPool = new String[stringPoolSize];
STORE_STRING_TO_ARRAY_MATCH.findAllMatches(stringPoolMatchCtx.insnContext().methodContext()).forEach(storeNumberMatchCtx -> {
int index = storeNumberMatchCtx.captures().get("index").insn().asInteger();
String value = storeNumberMatchCtx.captures().get("value").insn().asString();
//System.out.println(classWrapper.name());
stringPool[index] = value;
});

for (String string : stringPool) {
if (string == null) {
// String pool is not fully initialized
return;
}
}

Match stringPoolReferenceMatch = OpcodeMatch.of(AALOAD) // AALOAD - Load array reference
// Index
.and(FrameMatch.stack(0, NumberMatch.of().capture("index")
// Load number pool field
.and(FrameMatch.stack(0, FieldMatch.getStatic().owner(stringPoolFieldInsn.owner).name(stringPoolFieldInsn.name).desc(stringPoolFieldInsn.desc)))
));

// Replace number pool references with actual values
classWrapper.methods().forEach(methodNode -> {
MethodContext methodContext = MethodContext.of(classWrapper, methodNode);

stringPoolReferenceMatch.findAllMatches(methodContext).forEach(numberPoolReferenceCtx -> {
int index = numberPoolReferenceCtx.captures().get("index").insn().asInteger();
String value = stringPool[index];

// Value
methodNode.instructions.insert(numberPoolReferenceCtx.insn(), new LdcInsnNode(value));
numberPoolReferenceCtx.removeAll();
markChange();
changedForThisContext.set(true);
});
});

if (changedForThisContext.get()) {
toRemoveMn.add(stringPoolMatchCtx.insnContext().methodNode());
classWrapper.fields().forEach(fieldNode -> {
if (fieldNode.name.equals(stringPoolFieldInsn.name) && fieldNode.desc.equals(stringPoolFieldInsn.desc)) {
toRemoveFn.add(fieldNode);
}
});
// Remove string pool initialization from clinit
classWrapper.findClInit().ifPresent(clinit -> {
for (AbstractInsnNode insn : clinit.instructions.toArray()) {
if (insn.getOpcode() == INVOKESTATIC && insn instanceof MethodInsnNode methodInsn &&
methodInsn.name.equals(stringPoolMatchCtx.insnContext().methodNode().name) && methodInsn.desc.equals(stringPoolMatchCtx.insnContext().methodNode().desc) &&
methodInsn.owner.equals(classWrapper.name())
) {
// Remove invocation
clinit.instructions.remove(insn);
}
}
});
}
});
classWrapper.methods().removeAll(toRemoveMn);
classWrapper.fields().removeAll(toRemoveFn);
});
}
}