Skip to content

Commit

Permalink
Stringer v9
Browse files Browse the repository at this point in the history
  • Loading branch information
samczsun committed Feb 17, 2018
1 parent 8c78c80 commit 58ebc9a
Show file tree
Hide file tree
Showing 10 changed files with 250 additions and 7 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@
<dependency>
<groupId>com.javadeobfuscator</groupId>
<artifactId>javavm</artifactId>
<version>2.0.0</version>
<version>3.0.0</version>
</dependency>

<!-- ASM related dependencies -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ public class Rules {
new RuleStringDecryptorWithThread(),
new RuleInvokedynamic1(),
new RuleInvokedynamic2(),
new RuleStringDecryptorV3(),

// Zelix
new RuleSuspiciousClinit(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,15 @@
public class RuleStringDecryptor implements Rule, Opcodes {
@Override
public String getDescription() {
return "Stringer's string encryption classes are very complex. The original variety can be identified by its use of Thread.currentThread().getStackTrace() and heavy math operations";
return "This variant of Stringer's string decryptor can be identified by its use of Thread.currentThread().getStackTrace() and heavy math operations";
}

@Override
public String test(Deobfuscator deobfuscator) {
for (ClassNode classNode : deobfuscator.getClasses().values()) {
for (MethodNode methodNode : classNode.methods) {
if (!methodNode.desc.equals("(Ljava/lang/String;)Ljava/lang/String;")) continue;

boolean isStringer = true;

isStringer = isStringer && TransformerHelper.containsInvokeStatic(methodNode, "java/lang/Thread", "currentThread", "()Ljava/lang/Thread;");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* Copyright 2018 Sam Sun <[email protected]>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.javadeobfuscator.deobfuscator.rules.stringer;

import com.javadeobfuscator.deobfuscator.*;
import com.javadeobfuscator.deobfuscator.rules.*;
import com.javadeobfuscator.deobfuscator.transformers.*;
import com.javadeobfuscator.deobfuscator.transformers.stringer.v9.*;
import com.javadeobfuscator.deobfuscator.utils.*;
import org.objectweb.asm.tree.*;

import java.util.*;

public class RuleStringDecryptorV3 implements Rule {
@Override
public String getDescription() {
return "This variant of Stringer's string decryptor makes use of invokedynamic obfuscation within the string decryption classes";
}

@Override
public String test(Deobfuscator deobfuscator) {
for (ClassNode classNode : deobfuscator.getClasses().values()) {
for (MethodNode methodNode : classNode.methods) {
if (!TransformerHelper.basicType(methodNode.desc).equals("(Ljava/lang/Object;III)Ljava/lang/Object;")) {
continue;
}

boolean isStringer = true;

isStringer = isStringer && TransformerHelper.containsInvokeStatic(methodNode, "java/lang/Thread", "currentThread", "()Ljava/lang/Thread;");
isStringer = isStringer && TransformerHelper.containsInvokeVirtual(methodNode, "java/lang/Thread", "getStackTrace", "()[Ljava/lang/StackTraceElement;");
isStringer = isStringer && TransformerHelper.countOccurencesOf(methodNode, IAND) > 20;
isStringer = isStringer && TransformerHelper.countOccurencesOf(methodNode, IXOR) > 20;
isStringer = isStringer && TransformerHelper.countOccurencesOf(methodNode, IUSHR) > 10;
isStringer = isStringer && TransformerHelper.countOccurencesOf(methodNode, ISHL) > 10;

if (!isStringer) {
continue;
}

return "Found possible string decryption class " + classNode.name;
}
}

return null;
}

@Override
public Collection<Class<? extends Transformer>> getRecommendTransformers() {
return Collections.singleton(StringEncryptionTransformer.class);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,7 @@ public Deobfuscator getDeobfuscator() {
protected void oops(String why, Object... args) {
logger.debug("oops: " + why, args);
}
protected void fail(String why, Object... args) {
logger.error("fail: " + why, args);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -77,17 +77,19 @@ public boolean transform() throws Throwable {
classNode.methods.add(decryptorMethod);
try {
vm.execute(classNode, decryptorMethod, null, Collections.<JavaWrapper>emptyList());
} catch (VMException e) {
vm.printException(e);
throw new RuntimeException();
} catch (Throwable e) {
// dont care lol
// vm.printException(e);
}
classNode.methods.remove(decryptorMethod);

if (capturedMethod.get() == null) {
throw new WrongTransformerException("Expected non-null java/lang/reflect/Method");
oops("Expected non-null java/lang/reflect/Method");
continue;
}
if (capturedMethod.get().getJavaClass() != reflectMethod) {
throw new WrongTransformerException("Expected java/lang/reflect/Method, got " + capturedMethod.get().getJavaClass());
oops("Expected java/lang/reflect/Method, got " + capturedMethod.get().getJavaClass());
continue;
}

JavaWrapper classObj = capturedMethod.get().asObject().getField("clazz", "Ljava/lang/Class;");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
* Copyright 2018 Sam Sun <[email protected]>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.javadeobfuscator.deobfuscator.transformers.stringer.v9;

import com.javadeobfuscator.deobfuscator.config.*;
import com.javadeobfuscator.deobfuscator.matcher.*;
import com.javadeobfuscator.deobfuscator.transformers.*;
import com.javadeobfuscator.deobfuscator.transformers.stringer.v9.utils.*;
import com.javadeobfuscator.deobfuscator.utils.*;
import com.javadeobfuscator.javavm.*;
import com.javadeobfuscator.javavm.exceptions.*;
import org.objectweb.asm.*;
import org.objectweb.asm.tree.*;

import java.util.*;

public class StringEncryptionTransformer extends Transformer<TransformerConfig> implements Opcodes {
@Override
public boolean transform() throws Throwable {
VirtualMachine vm = TransformerHelper.newVirtualMachine(this);

// vm.beforeCallHooks.add(info -> {
// if (!info.is("java/lang/Class", "forName0", "(Ljava/lang/String;ZLjava/lang/ClassLoader;Ljava/lang/Class;)Ljava/lang/Class;")) {
// return;
// }
// List<StackTraceHolder> stacktrace = vm.getStacktrace();
// if (stacktrace.size() < 3) {
// return;
// }
// if (!classes.containsKey(stacktrace.get(2).getClassNode().name)) {
// return;
// }
// info.getParams().set(1, vm.newBoolean(false));
// });

int decrypted = 0;

for (ClassNode classNode : classes.values()) {
for (MethodNode methodNode : new ArrayList<>(classNode.methods)) {
InstructionModifier modifier = new InstructionModifier();

Map<LabelNode, LabelNode> cloneMap = Utils.generateCloneMap(methodNode.instructions);

for (AbstractInsnNode insn : TransformerHelper.instructionIterator(methodNode)) {
InstructionMatcher matcher = Constants.DECRYPT_PATTERN.matcher(insn);
if (!matcher.find()) {
continue;
}

MethodNode decryptorMethod = new MethodNode(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, methodNode.name, "()Ljava/lang/String;", null, null);
for (AbstractInsnNode matched : matcher.getCapturedInstructions("all")) {
decryptorMethod.instructions.add(matched.clone(cloneMap));
}
decryptorMethod.instructions.add(new InsnNode(ARETURN));

classNode.methods.add(decryptorMethod);
try {
MethodExecution result = vm.execute(classNode, decryptorMethod);
classNode.methods.remove(decryptorMethod);

String decryptedStr = vm.convertJavaObjectToString(result.getReturnValue());

logger.debug("Decrypted {} {}{}, {}", classNode.name, methodNode.name, methodNode.desc, decryptedStr);
decrypted++;

modifier.removeAll(matcher.getCapturedInstructions("all"));
modifier.replace(matcher.getEnd(), new LdcInsnNode(decryptedStr));
} catch (VMException e) {
TransformerHelper.dumpMethod(decryptorMethod);
vm.convertException(e).printStackTrace();
return false;
} catch (Throwable t) {
TransformerHelper.dumpMethod(decryptorMethod);
t.printStackTrace();
return false;
}
}

modifier.apply(methodNode);
}
}

vm.shutdown();

logger.info("Decrypted {} strings", decrypted);
return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright 2018 Sam Sun <[email protected]>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.javadeobfuscator.deobfuscator.transformers.stringer.v9.utils;

import com.javadeobfuscator.deobfuscator.matcher.*;
import org.objectweb.asm.*;

public class Constants implements Opcodes {
public static final InstructionPattern DECRYPT_PATTERN = new InstructionPattern(
new OpcodeStep(LDC),
new InvocationStep(INVOKEVIRTUAL, "java/lang/String", "toCharArray", "()[C", false),
new OpcodeStep(DUP),
new OpcodeStep(DUP),
new LoadIntStep(),
new OpcodeStep(DUP_X1),
new OpcodeStep(CALOAD),
new LoadIntStep(),
new OpcodeStep(IXOR),
new OpcodeStep(I2C),
new OpcodeStep(CASTORE),
new LoadIntStep(),
new LoadIntStep(),
new OpcodeStep(ISHL),
new LoadIntStep(),
new OpcodeStep(IOR),
new LoadIntStep(),
new LoadIntStep(),
new InvocationStep(INVOKESTATIC, null, null, "(Ljava/lang/Object;III)Ljava/lang/Object;", true)
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,19 @@

package com.javadeobfuscator.deobfuscator.utils;

import com.google.common.base.*;
import com.google.common.primitives.*;
import com.javadeobfuscator.deobfuscator.transformers.*;
import com.javadeobfuscator.javavm.*;
import org.objectweb.asm.*;
import org.objectweb.asm.tree.*;
import org.objectweb.asm.tree.analysis.*;
import org.objectweb.asm.tree.analysis.Frame;
import org.slf4j.*;

import java.io.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.stream.*;

import static com.javadeobfuscator.deobfuscator.utils.Utils.*;
Expand Down Expand Up @@ -462,4 +467,21 @@ public static String insnToString(AbstractInsnNode insn) {
public static String insnsToString(Collection<AbstractInsnNode> insns) {
return insns.stream().map(Utils::prettyprint).collect(Collectors.joining(",", "[", "]"));
}

private static final Supplier<ExecutorService> ASYNC_SERVICE = Suppliers.memoize(Executors::newCachedThreadPool);

private static final Logger LOGGER = LoggerFactory.getLogger(TransformerHelper.class);

public static Frame<SourceValue>[] analyze(ClassNode classNode, MethodNode methodNode) {
Future<Frame<SourceValue>[]> future = ASYNC_SERVICE.get().submit(() -> new Analyzer<>(new SourceInterpreter()).analyze(classNode.name, methodNode));
try {
return future.get(10, TimeUnit.SECONDS);
} catch (InterruptedException | TimeoutException e) {
LOGGER.debug("timed out while analyzing {} {}{}", classNode.name, methodNode.name, methodNode.desc);
return null;
} catch (ExecutionException e) {
LOGGER.debug("exception while analyzing {} {}{}", classNode.name, methodNode.name, methodNode.desc, e);
return null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,7 @@ public static void printClass(ClassNode classNode) {

public static boolean isInteger(AbstractInsnNode ain)
{
if (ain == null) return false;
if(ain.getOpcode() >= Opcodes.ICONST_M1
&& ain.getOpcode() <= Opcodes.SIPUSH)
return true;
Expand Down

0 comments on commit 58ebc9a

Please sign in to comment.