Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
New Dexguard String decoder for JEB 1.5. Tested on GFE 3.1.3. This release auto parse decoder function.
import jeb.api.IScript;
import jeb.api.JebInstance;
import jeb.api.ast.*;
import jeb.api.ast.Class;
import jeb.api.dex.*;
import jeb.api.ui.JavaView;
import jeb.api.ui.View;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.regex.Pattern;
/**
* Created by AKosterin on 31/01/16.
*/
public class DexGuardDecoder implements IScript {
private static JebInstance mJebInstance;
private static HashMap<String,byte[]> staticByteArrays;
private static HashMap<String,Integer> staticIntegers;
private static String decoderMethodSignature;
private static boolean debug = false;
@Override
public void run(JebInstance jebInstance) {
mJebInstance = jebInstance;
mJebInstance.print("DexGuardDecoder_Start");
if (!mJebInstance.isFileLoaded()) {
mJebInstance.print("Please load a dex file");
return;
}
Dex mDex = mJebInstance.getDex();
JavaView view = (JavaView) jebInstance.getUI().getView(View.Type.JAVA);
String signature = view.getCodePosition().getSignature();
if (debug) mJebInstance.print(getClassFromSignature(signature));
Class mClass = mJebInstance.getDecompiledClassTree(getClassFromSignature(signature));
defineDecoderMethodSignature(mClass);
if(!decoderMethodSignature.isEmpty()){
normalizeDecoderMethod();
initDecoderValues(mClass);
for (Method mMethod : (List<Method>) mClass.getMethods()) {
decodeMethod(mMethod);
}
}
jebInstance.getUI().getView(View.Type.JAVA).refresh();
jebInstance.print("DexGuardStringDecoder_End");
}
private static void defineDecoderMethodSignature(Class mClass){
for (Method mMethod : (List<Method>) mClass.getMethods()) {
String mMethodSignature = mMethod.getSignature();
if(mMethod.isStatic() && Pattern.matches("^.*[(][IBS]{3}[)]Ljava[/]lang[/]String[;]$",mMethodSignature.split(">")[1])){
decoderMethodSignature = mMethodSignature;
if (debug) mJebInstance.print("decoderMethodSignature = " + decoderMethodSignature);
return;
}
}
}
private static void normalizeDecoderMethod(){
Method mMethod = mJebInstance.getDecompiledMethodTree(decoderMethodSignature);
Block mMethodBodyBlock = mMethod.getBody();
int ifStmLine = 0;
while(!(mMethodBodyBlock.get(ifStmLine) instanceof IfStm)){
ifStmLine++;
if(ifStmLine == mMethodBodyBlock.size()){
return;
}
}
Block mElseBlock = ((IfStm)mMethodBodyBlock.get(ifStmLine)).getDefaultBlock();
Block mWhileBlock = Block.build();
int labelLine = 0;
while(!(mElseBlock.get(0) instanceof Label)){
mMethodBodyBlock.insert(ifStmLine + labelLine, mElseBlock.get(0));
mElseBlock.remove(0);
labelLine++;
}
for (int i = 0; i < mElseBlock.size() - 1; i++) {
if(mElseBlock.get(i+1) instanceof IfStm){
IfStm mIfStm = (IfStm) mElseBlock.get(i+1);
Predicate mIfPredicate = mIfStm.getBranchPredicate(0);
Block mIfIfBlock = mIfStm.getBranchBody(0);
Block mIfElseBlock = mIfStm.getDefaultBlock();
mWhileBlock.insert(mWhileBlock.size(), IfStm.build(mIfPredicate,mIfIfBlock));
for (int k = 0; k < mIfElseBlock.size(); k++) {
mWhileBlock.insert(mWhileBlock.size(), mIfElseBlock.get(k));
}
} else {
mWhileBlock.insert(mWhileBlock.size(), mElseBlock.get(i + 1));
}
}
int size = mMethodBodyBlock.size() - ifStmLine - labelLine - 2;
for (int i = 0; i < size; i++) {
mWhileBlock.insert(mWhileBlock.size(), mMethodBodyBlock.get(ifStmLine + labelLine + 1));
mMethodBodyBlock.remove(ifStmLine+labelLine + 1);
}
mMethodBodyBlock.remove(ifStmLine + labelLine + 1);
IExpression mIExpression = new Constant.Builder(mJebInstance).buildInt(1);
Predicate mTruePredicate = Predicate.build(mIExpression, Operator.EQ, mIExpression);
WhileStm mWhileStm = WhileStm.build(mTruePredicate,mWhileBlock);
mMethodBodyBlock.replaceSubElement(mMethodBodyBlock.get(ifStmLine+labelLine), mWhileStm);
}
private static void initDecoderValues(Class mClass) {
staticByteArrays = new HashMap<String,byte[]>();
staticIntegers = new HashMap<String, Integer>();
for (Method mMethod : (List<Method>) mClass.getMethods()) {
if (mMethod.getName().equals("<clinit>")) {
Block clinitMethodBlock = mMethod.getBody();
for (int i = 0; i < clinitMethodBlock.size(); i++) {
if(clinitMethodBlock.get(i) instanceof Assignment){
Assignment mAssignment = (Assignment) clinitMethodBlock.get(i);
IExpression mLeftExpression = mAssignment.getLeft();
IExpression mRightExpression = mAssignment.getRight();
if(mLeftExpression instanceof StaticField) {
StaticField mStaticField = (StaticField) mLeftExpression;
if (mStaticField.getField().getType().equals("[B")) {
byte[] b = getByteArrayValue(mRightExpression, staticIntegers, staticByteArrays);
if (b == null){
mJebInstance.print("Incorrect static byte[] " + mStaticField.getField().getName() + " define - initDecoderValues");
return;
}
staticByteArrays.put(mStaticField.getField().getName(), b);
if (debug) mJebInstance.print("static byte[] " + mStaticField.getField().getName());
} else if (mStaticField.getField().getType().equals("I") || mStaticField.getField().getType().equals("S") || mStaticField.getField().getType().equals("B")) {
int a = getIntValueOfExpression(mRightExpression, staticIntegers, staticByteArrays);
if(a == Integer.MIN_VALUE) {
mJebInstance.print("Incorrect int " + mStaticField.getField().getName());
return;
}
staticIntegers.put(mStaticField.getField().getName(), a);
if (debug) mJebInstance.print("static int " + mStaticField.getField().getName() + " = " + a);
}
}
}
}
}
}
}
private static void decodeMethod(Method mMethod){
if (debug) mJebInstance.print("Start decode " + mMethod.getSignature());
HashMap<String, Integer> intValues = new HashMap<String,Integer>();
intValues.putAll(staticIntegers);
HashMap<String, byte[]> byteArrayValues = new HashMap<String, byte[]>();
byteArrayValues.putAll(staticByteArrays);
for (int i = 0; i < mMethod.getBody().size(); i++){
if(!mMethod.getSignature().equals(decoderMethodSignature)) decodeIElement(mMethod.getBody().get(i), mMethod.getBody(), intValues, byteArrayValues, 1);
}
}
public static String repeat(String str, int times){
return new String(new char[times]).replace("\0", str);
}
private static void decodeIElement(IElement mIElement, IElement mParentIElement, HashMap<String, Integer> intValues, HashMap<String, byte[]> byteArrayValues, int level){
if (debug) mJebInstance.print(repeat("\t", level) + "decodeIElement(" + mIElement.getClass().getName() + ", " + mParentIElement.getClass().getName() + ", ... )");
if (mIElement instanceof Definition && !(mParentIElement instanceof Assignment)) {
Definition mDefinition = (Definition) mIElement;
String type = mDefinition.getType();
if (type.equals("I") || type.equals("B") || type.equals("S")){
intValues.put(mDefinition.getIdentifier().getName(), Integer.MIN_VALUE);
if (debug) mJebInstance.print("\t\tint " + mDefinition.getIdentifier().getName());
}
} else if (mIElement instanceof Assignment){
Assignment mAssignment = (Assignment) mIElement;
IExpression mLeftExpression = mAssignment.getLeft();
IExpression mRightExpression = mAssignment.getRight();
if (mAssignment.isSimpleAssignment()) {
if (mLeftExpression instanceof Definition) {
Definition mDefinition = (Definition) mLeftExpression;
String type = mDefinition.getType();
if (type.equals("I") || type.equals("B") || type.equals("S")) {
int val = getIntValueOfExpression(mRightExpression, intValues, byteArrayValues);
if (val == Integer.MIN_VALUE) {
mJebInstance.print("Incorrect mRightExpression int " + mDefinition.getIdentifier().getName() + " == " + val + " for SimpleAssignment - decodeIElement");
return;
}
intValues.put(mDefinition.getIdentifier().getName(), val);
if (debug)
mJebInstance.print("\t\tint " + mDefinition.getIdentifier().getName() + " = " + val);
} /*else if (type.equals("[B")) {
byte[] val = getByteArrayValue(mRightExpression, intValues, byteArrayValues);
if (val == null) {
mJebInstance.print("Incorrect mRightExpression byte[] " + mDefinition.getIdentifier().getName() + " == " + val + " for SimpleAssignment - decodeIElement");
return;
}
byteArrayValues.put(mDefinition.getIdentifier().getName(), val);
if (debug)
mJebInstance.print("\t\tbyte[] " + mDefinition.getIdentifier().getName() + " = " + Arrays.toString(val));
}*/
} else if (mLeftExpression instanceof Identifier) {
Identifier mIdentifier = (Identifier) mLeftExpression;
if (intValues.containsKey(mIdentifier.getName())) {
int val = getIntValueOfExpression(mRightExpression, intValues, byteArrayValues);
if (val == Integer.MIN_VALUE) {
mJebInstance.print("Incorrect mRightExpression " + mIdentifier.getName() + " == " + val + " for SimpleAssignment - decodeIElement");
return;
}
intValues.put(mIdentifier.getName(), val);
if (debug) mJebInstance.print("\t\t" + mIdentifier.getName() + " = " + val);
} /*else if (byteArrayValues.containsKey(mIdentifier.getName())) {
byte[] val = getByteArrayValue(mRightExpression, intValues, byteArrayValues);
if (val == null) {
mJebInstance.print("Incorrect mRightExpression " + mIdentifier.getName() + " == " + val + " for SimpleAssignment - decodeIElement");
return;
}
byteArrayValues.put(mIdentifier.getName(), val);
if (debug) mJebInstance.print("\t\t" + mIdentifier.getName() + " = " + val);
}*/
} /*else if (mLeftExpression instanceof ArrayElt) {
ArrayElt mArrayElt = (ArrayElt) mLeftExpression;
if (mArrayElt.getArray() instanceof Identifier) {
String arrayName = ((Identifier) mArrayElt.getArray()).getName();
if (!byteArrayValues.containsKey(arrayName)) {
mJebInstance.print("Unavailable byte[] " + arrayName + " for SimpleAssignment - decodeIElement");
return;
}
int position = getIntValueOfExpression(mArrayElt.getIndex(), intValues, byteArrayValues);
if (position < 0 || position >= byteArrayValues.get(arrayName).length) {
mJebInstance.print("Incorrect position " + position + " for SimpleAssignment - decodeIElement");
return;
}
byte[] result = byteArrayValues.get(arrayName);
int val = getIntValueOfExpression(mRightExpression, intValues, byteArrayValues);
if (val == Integer.MIN_VALUE) {
mJebInstance.print("Incorrect mRightExpression " + arrayName + "[" + position + "] == " + val + " for SimpleAssignment - decodeIElement");
return;
}
result[position] = (byte) val;
if (debug) mJebInstance.print("\t\t" + arrayName + "[" + position + "] = " + val);
byteArrayValues.put(arrayName, result);
}
}*/
} else if (mAssignment.isCombinedOperatorAssignment()) {
if (!(mLeftExpression instanceof Identifier)) {
mJebInstance.print("Incorrect left IExpression type " + mLeftExpression.getClass().getName() + " for CombinedOperatorAssignment - decodeIElement");
return;
}
if (mRightExpression == null) {
mJebInstance.print("Incorrect right IExpression " + mRightExpression.getClass().getName() + " == null for CombinedOperatorAssignment - decodeIElement");
return;
}
Identifier mIdentifier = (Identifier) mLeftExpression;
if (intValues.containsKey(mIdentifier.getName())) {
int val = getIntValueOfExpression(mRightExpression, intValues, byteArrayValues);
if (val == Integer.MIN_VALUE) {
mJebInstance.print("Incorrect mRightExpression int value " + val + " for CombinedOperatorAssignment - decodeIElement");
return;
}
Operator mOperator = mAssignment.getCombinedOperator();
if(mOperator.equals(Operator.ADD)){
int val2 = intValues.get(mIdentifier.getName());
if (debug) mJebInstance.print("\t\t" + mIdentifier.getName() + " += " + val);
intValues.put(mIdentifier.getName(), val2 + val);
} else if (mOperator.equals(Operator.SUB)){
int val2 = intValues.get(mIdentifier.getName());
if (debug) mJebInstance.print("\t\t" + mIdentifier.getName() + " -= " + val);
intValues.put(mIdentifier.getName(), val2 - val);
} else if (mOperator.equals(Operator.MUL)){
int val2 = intValues.get(mIdentifier.getName());
if (debug) mJebInstance.print("\t\t" + mIdentifier.getName() + " *= " + val);
intValues.put(mIdentifier.getName(), val * val2);
}
}
} else if (mAssignment.isUnaryOperatorAssignment()) {
if (!(mLeftExpression instanceof Identifier)) {
mJebInstance.print("Incorrect left IExpretion type " + mLeftExpression.getClass().getName() + " for UnaryOperatorAssignment - decodeIElement");
return;
}
if (mRightExpression != null) {
mJebInstance.print("Incorrect right IExpretion " + mRightExpression.getClass().getName() + " != null for UnaryOperatorAssignment - decodeIElement");
return;
}
Identifier mIdentifier = (Identifier) mLeftExpression;
if (intValues.containsKey(mIdentifier.getName())) {
boolean[] unaryFlags = new boolean[2];
mAssignment.getUnaryOperator(unaryFlags);
if (unaryFlags[0]) {
intValues.put(mIdentifier.getName(), intValues.get(mIdentifier.getName()) + 1);
if (debug) mJebInstance.print("\t\t" + mIdentifier.getName() + "++");
} else {
intValues.put(mIdentifier.getName(), intValues.get(mIdentifier.getName()) - 1);
if (debug) mJebInstance.print("\t\t" + mIdentifier.getName() + "--");
}
}
}
} else if (mIElement instanceof Call) {
Call mCall = (Call) mIElement;
String methodSignature = mCall.getMethod().getSignature();
if(methodSignature.equals(decoderMethodSignature) && !(mParentIElement instanceof Block)){
List<IExpression> params = mCall.getArguments();
int param1 = getIntValueOfExpression(params.get(0),intValues,byteArrayValues);
int param2 = getIntValueOfExpression(params.get(1),intValues,byteArrayValues);
int param3 = getIntValueOfExpression(params.get(2),intValues,byteArrayValues);
String result = decodeDexGuardString(param1,param2,param3);
mJebInstance.print("decodeDexGuardString(" + param1 + ", " + param2 + ", " + param3 + ") = " + result);
if(result != null) mParentIElement.replaceSubElement(mIElement, (new Constant.Builder(mJebInstance)).buildString(result));
else if (debug) return;
} else {
for(int i = 0; i < mIElement.getSubElements().size(); i++){
decodeIElement((IElement)mIElement.getSubElements().get(i), mIElement, intValues, byteArrayValues, level+1);
}
}
}
for (IElement element : (List<IElement>) mIElement.getSubElements()) {
if (!((element instanceof Class) || (element instanceof Field) || (element instanceof Method))) {
decodeIElement(element, mIElement, intValues, byteArrayValues, level + 1);
}
}
}
private static String decodeDexGuardString(int param1, int param2, int param3) {
if (debug) mJebInstance.print("decodeDexGuardString(" + param1 + ", " + param2 + ", " + param3 + ")");
Method mMethod = mJebInstance.getDecompiledMethodTree(decoderMethodSignature);
List<Definition> params = mMethod.getParameters();
Block mMethodBlock = mMethod.getBody();
HashMap<String, Integer> intValuesMap = new HashMap<String, Integer>();
intValuesMap.put(params.get(0).getIdentifier().getName(), param1);
intValuesMap.put(params.get(1).getIdentifier().getName(), param2);
intValuesMap.put(params.get(2).getIdentifier().getName(), param3);
intValuesMap.putAll(staticIntegers);
HashMap<String, byte[]> byteArrayValuesMap = new HashMap<String, byte[]>();
byteArrayValuesMap.putAll(staticByteArrays);
for (int i = 0; i < mMethodBlock.size(); i++) {
if (debug) mJebInstance.print(mMethodBlock.get(i).getClass().getName());
if(mMethodBlock.get(i) instanceof Definition){
Definition mDefinition = (Definition) mMethodBlock.get(i);
String type = mDefinition.getType();
if (type.equals("I") || type.equals("B") || type.equals("S")){
intValuesMap.put(mDefinition.getIdentifier().getName(), Integer.MIN_VALUE);
if (debug) mJebInstance.print("\t\tint " + mDefinition.getIdentifier().getName());
} else if (type.equals("[B")){
byteArrayValuesMap.put(mDefinition.getIdentifier().getName(), null);
if (debug) mJebInstance.print("\t\tbyte[] " + mDefinition.getIdentifier().getName());
} else {
mJebInstance.print("Unsupported Definition variable " + mDefinition.getIdentifier().getName() + " type \"" + type + "\" - decodeDexGuardString / Definition");
return null;
}
} else if (mMethodBlock.get(i) instanceof Assignment){
Assignment mAssignment = (Assignment) mMethodBlock.get(i);
IExpression mLeftExpression = mAssignment.getLeft();
IExpression mRightExpression = mAssignment.getRight();
if (mAssignment.isSimpleAssignment()) {
if (mLeftExpression instanceof Definition) {
Definition mDefinition = (Definition) mLeftExpression;
String type = mDefinition.getType();
if (type.equals("I") || type.equals("B") || type.equals("S")) {
int val = getIntValueOfExpression(mRightExpression, intValuesMap, byteArrayValuesMap);
if (val == Integer.MIN_VALUE) {
mJebInstance.print("Incorrect mRightExpression int " + mDefinition.getIdentifier().getName() + " == " + val + " for SimpleAssignment - decodeDexGuardString / Assignment");
return null;
}
intValuesMap.put(mDefinition.getIdentifier().getName(), val);
if (debug)
mJebInstance.print("\t\tint " + mDefinition.getIdentifier().getName() + " = " + val);
} else if (type.equals("[B")) {
byte[] val = getByteArrayValue(mRightExpression, intValuesMap, byteArrayValuesMap);
if (val == null) {
mJebInstance.print("Incorrect mRightExpression byte[] " + mDefinition.getIdentifier().getName() + " == " + val + " for SimpleAssignment - decodeDexGuardString / Assignment");
return null;
}
byteArrayValuesMap.put(mDefinition.getIdentifier().getName(), val);
if (debug)
mJebInstance.print("\t\tbyte[] " + mDefinition.getIdentifier().getName() + " = " + Arrays.toString(val));
} else {
mJebInstance.print("Unsupported Definition variable " + mDefinition.getIdentifier().getName() + " type \"" + type + "\" - decodeDexGuardString / Definition");
return null;
}
} else if (mLeftExpression instanceof Identifier) {
Identifier mIdentifier = (Identifier) mLeftExpression;
if (intValuesMap.containsKey(mIdentifier.getName())) {
int val = getIntValueOfExpression(mRightExpression, intValuesMap, byteArrayValuesMap);
if (val == Integer.MIN_VALUE) {
mJebInstance.print("Incorrect mRightExpression " + mIdentifier.getName() + " == " + val + " for SimpleAssignment - decodeDexGuardString / Assignment");
return null;
}
intValuesMap.put(mIdentifier.getName(), val);
if (debug) mJebInstance.print("\t\t" + mIdentifier.getName() + " = " + val);
} else if (byteArrayValuesMap.containsKey(mIdentifier.getName())) {
byte[] val = getByteArrayValue(mRightExpression, intValuesMap, byteArrayValuesMap);
if (val == null) {
mJebInstance.print("Incorrect mRightExpression " + mIdentifier.getName() + " == " + val + " for SimpleAssignment - decodeDexGuardString / Assignment");
return null;
}
byteArrayValuesMap.put(mIdentifier.getName(), val);
if (debug) mJebInstance.print("\t\t" + mIdentifier.getName() + " = " + val);
} else {
mJebInstance.print("Unknown variable " + mIdentifier.getName() + " type - decodeDexGuardString / Definition");
return null;
}
} else if (mLeftExpression instanceof ArrayElt) {
ArrayElt mArrayElt = (ArrayElt) mLeftExpression;
if(mArrayElt.getArray() instanceof Identifier) {
String arrayName = ((Identifier)mArrayElt.getArray()).getName();
if(!byteArrayValuesMap.containsKey(arrayName)){
mJebInstance.print("Unavailable byte[] " + arrayName + " for SimpleAssignment - decodeDexGuardString / Assignment / ArrayElt");
return null;
}
int position = getIntValueOfExpression(mArrayElt.getIndex(), intValuesMap, byteArrayValuesMap);
if(position < 0 || position >= byteArrayValuesMap.get(arrayName).length){
mJebInstance.print("Incorrect position " + position + " for SimpleAssignment - decodeDexGuardString / Assignment / ArrayElt");
return null;
}
byte[] result = byteArrayValuesMap.get(arrayName);
int val = getIntValueOfExpression(mRightExpression, intValuesMap, byteArrayValuesMap);
if (val == Integer.MIN_VALUE) {
mJebInstance.print("Incorrect mRightExpression " + arrayName + "[" + position + "] == " + val + " for SimpleAssignment - decodeDexGuardString / Assignment");
return null;
}
result[position] = (byte) val;
if (debug) mJebInstance.print("\t\t" + arrayName + "[" + position + "] = " + val);
byteArrayValuesMap.put(arrayName, result);
} else {
mJebInstance.print("Incorrect Array IExpression type " + mArrayElt.getArray().getClass().getName() + " for SimpleAssignment - decodeDexGuardString / Assignment / ArrayElt");
return null;
}
} else {
mJebInstance.print("Incorrect left IExpression type " + mLeftExpression.getClass().getName() + " for SimpleAssignment - decodeDexGuardString / Assignment");
return null;
}
} else if (mAssignment.isCombinedOperatorAssignment()){
if(!(mLeftExpression instanceof Identifier)){
mJebInstance.print("Incorrect left IExpression type " + mLeftExpression.getClass().getName() + " for CombinedOperatorAssignment - decodeDexGuardString / Assignment");
return null;
}
if(mRightExpression == null){
mJebInstance.print("Incorrect right IExpression " + mRightExpression.getClass().getName() + " == null for CombinedOperatorAssignment - decodeDexGuardString / Assignment");
return null;
}
Identifier mIdentifier = (Identifier) mLeftExpression;
if(!intValuesMap.containsKey(mIdentifier.getName())){
mJebInstance.print("Unavailable int " + mIdentifier.getName() + " for CombinedOperatorAssignment - decodeDexGuardString / Assignment");
return null;
}
int val = getIntValueOfExpression(mRightExpression, intValuesMap, byteArrayValuesMap);
if(val == Integer.MIN_VALUE){
mJebInstance.print("Incorrect mRightExpression int value " + val + " for CombinedOperatorAssignment - decodeDexGuardString / Assignment");
return null;
}
Operator mOperator = mAssignment.getCombinedOperator();
if(mOperator.equals(Operator.ADD)){
int val2 = intValuesMap.get(mIdentifier.getName());
if (debug) mJebInstance.print("\t\t" + mIdentifier.getName() + " += " + val);
intValuesMap.put(mIdentifier.getName(), val2 + val);
} else if (mOperator.equals(Operator.SUB)){
int val2 = intValuesMap.get(mIdentifier.getName());
if (debug) mJebInstance.print("\t\t" + mIdentifier.getName() + " -= " + val);
intValuesMap.put(mIdentifier.getName(), val2 - val);
} else if (mOperator.equals(Operator.MUL)){
int val2 = intValuesMap.get(mIdentifier.getName());
if (debug) mJebInstance.print("\t\t" + mIdentifier.getName() + " *= " + val);
intValuesMap.put(mIdentifier.getName(), val * val2);
} else {
mJebInstance.print("Unsopported combinedOperator type \"" + mOperator.toString() + "=\" for CombinedOperatorAssignment - decodeDexGuardString / Assignment");
return null;
}
} else if (mAssignment.isUnaryOperatorAssignment()){
if(!(mLeftExpression instanceof Identifier)){
mJebInstance.print("Incorrect left IExpretion type " + mLeftExpression.getClass().getName() + " for UnaryOperatorAssignment - decodeDexGuardString / Assignment");
return null;
}
if(mRightExpression != null){
mJebInstance.print("Incorrect right IExpretion " + mRightExpression.getClass().getName() + " != null for UnaryOperatorAssignment - decodeDexGuardString / Assignment");
return null;
}
Identifier mIdentifier = (Identifier) mLeftExpression;
if(!intValuesMap.containsKey(mIdentifier.getName())){
mJebInstance.print("Unavailable int " + mIdentifier.getName() + " - decodeDexGuardString / Assignment");
return null;
}
boolean[] unaryFlags = new boolean[2];
mAssignment.getUnaryOperator(unaryFlags);
if(unaryFlags[0]){
intValuesMap.put(mIdentifier.getName(), intValuesMap.get(mIdentifier.getName()) + 1);
if (debug) mJebInstance.print("\t\t" + mIdentifier.getName() + "++");
} else {
intValuesMap.put(mIdentifier.getName(), intValuesMap.get(mIdentifier.getName()) - 1);
if (debug) mJebInstance.print("\t\t" + mIdentifier.getName() + "--");
}
} else {
mJebInstance.print("Incorrect type Assignment - decodeDexGuardString");
}
} else if (mMethodBlock.get(i) instanceof WhileStm){
WhileStm mWhileStm = (WhileStm)mMethodBlock.get(i);
Block mWhileBlock = mWhileStm.getBody();
int controller = 0;
while (controller < 70) {
controller++;
for (int j = 0; j < mWhileBlock.size(); j++) {
if (debug) mJebInstance.print("\t\t\t\t"+ mWhileBlock.get(j).getClass().getName());
if(mWhileBlock.get(j) instanceof Definition){
Definition mDefinition = (Definition) mWhileBlock.get(j);
String type = mDefinition.getType();
if (type.equals("I") || type.equals("B") || type.equals("S")){
intValuesMap.put(mDefinition.getIdentifier().getName(), Integer.MIN_VALUE);
if (debug) mJebInstance.print("\t\t\t\t\t\tint " + mDefinition.getIdentifier().getName());
} else if (type.equals("[B")){
byteArrayValuesMap.put(mDefinition.getIdentifier().getName(), null);
if (debug) mJebInstance.print("\t\t\t\t\t\tbyte[] " + mDefinition.getIdentifier().getName());
} else {
mJebInstance.print("Unsupported Definition variable " + mDefinition.getIdentifier().getName() + " type \"" + type + "\" - decodeDexGuardString / Definition");
return null;
}
} else if (mWhileBlock.get(j) instanceof Assignment){
Assignment mAssignment = (Assignment) mWhileBlock.get(j);
IExpression mLeftExpression = mAssignment.getLeft();
IExpression mRightExpression = mAssignment.getRight();
if (mAssignment.isSimpleAssignment()){
if(mLeftExpression instanceof Definition){
Definition mDefinition = (Definition) mLeftExpression;
String type = mDefinition.getType();
if (type.equals("I") || type.equals("B") || type.equals("S")){
int val = getIntValueOfExpression(mRightExpression, intValuesMap, byteArrayValuesMap);
if (val == Integer.MIN_VALUE) {
mJebInstance.print("Incorrect mRightExpression int " + mDefinition.getIdentifier().getName() + " == " + val + " for SimpleAssignment - decodeDexGuardString / Assignment");
return null;
}
intValuesMap.put(mDefinition.getIdentifier().getName(), val);
if (debug) mJebInstance.print("\t\t\t\t\t\tint " + mDefinition.getIdentifier().getName() + " = " + val);
} else if (type.equals("[B")){
byte[] val = getByteArrayValue(mRightExpression, intValuesMap, byteArrayValuesMap);
if (val == null) {
mJebInstance.print("Incorrect mRightExpression byte[] " + mDefinition.getIdentifier().getName() + " == " + val + " for SimpleAssignment - decodeDexGuardString / Assignment");
return null;
}
byteArrayValuesMap.put(mDefinition.getIdentifier().getName(), val);
if (debug) mJebInstance.print("\t\t\t\t\t\tbyte[] " + mDefinition.getIdentifier().getName() + " = " + Arrays.toString(val));
} else {
mJebInstance.print("Unsupported Definition variable " + mDefinition.getIdentifier().getName() + " type \"" + type + "\" - decodeDexGuardString / Definition");
return null;
}
} else if (mLeftExpression instanceof Identifier) {
Identifier mIdentifier = (Identifier) mLeftExpression;
if (intValuesMap.containsKey(mIdentifier.getName())){
int val = getIntValueOfExpression(mRightExpression, intValuesMap, byteArrayValuesMap);
if (val == Integer.MIN_VALUE) {
mJebInstance.print("Incorrect mRightExpression " + mIdentifier.getName() + " == " + val + " for SimpleAssignment - decodeDexGuardString / Assignment");
return null;
}
intValuesMap.put(mIdentifier.getName(), val);
if (debug) mJebInstance.print("\t\t\t\t\t\t" + mIdentifier.getName() + " = " + val);
} else if (byteArrayValuesMap.containsKey(mIdentifier.getName())){
byte[] val = getByteArrayValue(mRightExpression, intValuesMap, byteArrayValuesMap);
if (val == null) {
mJebInstance.print("Incorrect mRightExpression " + mIdentifier.getName() + " == " + val + " for SimpleAssignment - decodeDexGuardString / Assignment");
return null;
}
byteArrayValuesMap.put(mIdentifier.getName(), val);
if (debug) mJebInstance.print("\t\t\t\t\t\t" + mIdentifier.getName() + " = " + val);
} else {
mJebInstance.print("Unknown variable " + mIdentifier.getName() + " type - decodeDexGuardString / Definition");
return null;
}
} else if (mLeftExpression instanceof ArrayElt) {
ArrayElt mArrayElt = (ArrayElt) mLeftExpression;
if(mArrayElt.getArray() instanceof Identifier) {
String arrayName = ((Identifier)mArrayElt.getArray()).getName();
if(!byteArrayValuesMap.containsKey(arrayName)){
mJebInstance.print("Unavailable byte[] " + arrayName + " for SimpleAssignment - decodeDexGuardString / Assignment / ArrayElt");
return null;
}
int position = getIntValueOfExpression(mArrayElt.getIndex(), intValuesMap, byteArrayValuesMap);
if(position < 0 || position >= byteArrayValuesMap.get(arrayName).length){
mJebInstance.print("Incorrect position " + position + " for SimpleAssignment - decodeDexGuardString / Assignment / ArrayElt");
return null;
}
byte[] result = byteArrayValuesMap.get(arrayName);
int val = getIntValueOfExpression(mRightExpression, intValuesMap, byteArrayValuesMap);
if (val == Integer.MIN_VALUE) {
mJebInstance.print("Incorrect mRightExpression " + arrayName + "[" + position + "] == " + val + " for SimpleAssignment - decodeDexGuardString / Assignment");
return null;
}
result[position] = (byte) val;
if (debug) mJebInstance.print("\t\t" + arrayName + "[" + position + "] = " + val);
byteArrayValuesMap.put(arrayName, result);
} else {
mJebInstance.print("Incorrect Array IExpression type " + mArrayElt.getArray().getClass().getName() + " for SimpleAssignment - decodeDexGuardString / Assignment / ArrayElt");
return null;
}
} else {
mJebInstance.print("Incorrect left IExpression type " + mLeftExpression.getClass().getName() + " for SimpleAssignment - decodeDexGuardString / Assignment");
return null;
}
} else if (mAssignment.isCombinedOperatorAssignment()){
if(!(mLeftExpression instanceof Identifier)){
mJebInstance.print("Incorrect left IExpression type " + mLeftExpression.getClass().getName() + " for CombinedOperatorAssignment - decodeDexGuardString / Assignment");
return null;
}
if(mRightExpression == null){
mJebInstance.print("Incorrect right IExpression " + mRightExpression.getClass().getName() + " == null for CombinedOperatorAssignment - decodeDexGuardString / Assignment");
return null;
}
Identifier mIdentifier = (Identifier) mLeftExpression;
if(!intValuesMap.containsKey(mIdentifier.getName())){
mJebInstance.print("Unavailable int " + mIdentifier.getName() + " for CombinedOperatorAssignment - decodeDexGuardString / Assignment");
return null;
}
int val = getIntValueOfExpression(mRightExpression, intValuesMap, byteArrayValuesMap);
if(val == Integer.MIN_VALUE){
mJebInstance.print("Incorrect mRightExpression int value " + val + " for CombinedOperatorAssignment - decodeDexGuardString / Assignment");
return null;
}
Operator mOperator = mAssignment.getCombinedOperator();
if(mOperator.equals(Operator.ADD)){
int val2 = intValuesMap.get(mIdentifier.getName());
if (debug) mJebInstance.print("\t\t" + mIdentifier.getName() + " += " + val);
intValuesMap.put(mIdentifier.getName(), val2 + val);
} else if (mOperator.equals(Operator.SUB)){
int val2 = intValuesMap.get(mIdentifier.getName());
if (debug) mJebInstance.print("\t\t" + mIdentifier.getName() + " -= " + val);
intValuesMap.put(mIdentifier.getName(), val2 - val);
} else if (mOperator.equals(Operator.MUL)){
int val2 = intValuesMap.get(mIdentifier.getName());
if (debug) mJebInstance.print("\t\t" + mIdentifier.getName() + " *= " + val);
intValuesMap.put(mIdentifier.getName(), val * val2);
} else {
mJebInstance.print("Unsopported combinedOperator type \"" + mOperator.toString() + "=\" for CombinedOperatorAssignment - decodeDexGuardString / Assignment");
return null;
}
} else if (mAssignment.isUnaryOperatorAssignment()){
if(!(mLeftExpression instanceof Identifier)){
mJebInstance.print("Incorrect left IExpretion type " + mLeftExpression.getClass().getName() + " for UnaryOperatorAssignment - decodeDexGuardString / Assignment");
return null;
}
if(mRightExpression != null){
mJebInstance.print("Incorrect right IExpretion " + mRightExpression.getClass().getName() + " != null for UnaryOperatorAssignment - decodeDexGuardString / Assignment");
return null;
}
Identifier mIdentifier = (Identifier) mLeftExpression;
if(!intValuesMap.containsKey(mIdentifier.getName())){
mJebInstance.print("Unavailable int " + mIdentifier.getName() + " - decodeDexGuardString / Assignment");
return null;
}
boolean[] unaryFlags = new boolean[2];
mAssignment.getUnaryOperator(unaryFlags);
if(unaryFlags[0]){
intValuesMap.put(mIdentifier.getName(), intValuesMap.get(mIdentifier.getName()) + 1);
if (debug) mJebInstance.print("\t\t\t\t\t\t" + mIdentifier.getName() + "++");
} else {
intValuesMap.put(mIdentifier.getName(), intValuesMap.get(mIdentifier.getName()) - 1);
if (debug) mJebInstance.print("\t\t\t\t\t\t" + mIdentifier.getName() + "--");
}
} else {
mJebInstance.print("Incorrect type Assignment - decodeDexGuardString");
}
} else if (mWhileBlock.get(j) instanceof IfStm){
IfStm mIfStm = (IfStm) mWhileBlock.get(j);
Predicate mPredicate = mIfStm.getBranchPredicate(0);
String lVarName = ((Identifier) mPredicate.getLeft()).getName();
String rVarName = ((Identifier) mPredicate.getRight()).getName();
Block ifBlock = mIfStm.getBranchBody(0);
if(intValuesMap.get(lVarName).equals(intValuesMap.get(rVarName))){
String resultByteArray = "";
for (int k = 0; k < ifBlock.size(); k++) {
if(ifBlock.get(k) instanceof Return){
Return mReturn = (Return) ifBlock.get(k);
if(mReturn.getExpression() instanceof New){
New mNew = (New) mReturn.getExpression();
List args = mNew.getArguments();
if (args.size() > 0 && args.get(0) instanceof Identifier) {
Identifier mIdentifier = (Identifier) args.get(0);
resultByteArray = mIdentifier.getName();
} else {
mJebInstance.print("String init params incorrect");
return null;
}
} else if (mReturn.getExpression() instanceof Call) {
Call mCall = (Call) mReturn.getExpression();
List<IExpression> par = mCall.getArguments();
if(par.size() <= 0) {
mJebInstance.print("Unavailable String Creater");
return null;
}
if (par.get(0) instanceof New) {
New mNew = (New) par.get(0);
List args = mNew.getArguments();
if (args.size() > 0 && args.get(0) instanceof Identifier) {
Identifier mIdentifier = (Identifier) args.get(0);
resultByteArray = mIdentifier.getName();
} else {
mJebInstance.print("String init params incorrect");
return null;
}
} else {
mJebInstance.print("Unavailable String Creater in Call");
return null;
}
} else {
mJebInstance.print("Incorrect Return Expression " + mReturn.getExpression().getClass().getName());
return null;
}
} else {
mJebInstance.print("Statement is not Return (" + ifBlock.get(k).getClass().getName() + ")");
return null;
}
}
//mJebInstance.print("t4 - " + resultByteArray + " = " + Arrays.toString(byteArrayValuesMap.get(resultByteArray)) + " " + new String(byteArrayValuesMap.get(resultByteArray)));
return new String(byteArrayValuesMap.get(resultByteArray));
}
} else {
mJebInstance.print("Unsupported Statement type " + mWhileBlock.get(j).getClass().getName() + " - decodeDexGuardString / Assignment");
return null;
}
}
}
} else {
mJebInstance.print("Unsupported Statement type " + mMethodBlock.get(i).getClass().getName() + " - decodeDexGuardString / Assignment");
return null;
}
}
return "";
}
private static byte[] getByteArrayValue(IExpression mIExpression, HashMap<String,Integer> intValues, HashMap<String, byte[]> byteArrayValues){
if (debug) mJebInstance.print("\t\t\t\t\t\t\t\tgetByteArrayValue for " + mIExpression.getClass().getName());
if (mIExpression instanceof Call) {
//TODO
mJebInstance.print("Unsupported IExpression parametr type - getByteArrayValue of Call");
return null;
} else if (mIExpression instanceof Identifier) {
Identifier mIdentifier = (Identifier) mIExpression;
if(!byteArrayValues.containsKey(mIdentifier.getName())) {
mJebInstance.print("Unavailable byte[] " + mIdentifier.getName() + " - getByteArrayValue of Identifier");
return null;
}
return byteArrayValues.get(mIdentifier.getName());
} else if (mIExpression instanceof InstanceField) {
//TODO
mJebInstance.print("Unsupported IExpression parametr type - getByteArrayValue of InstanceField");
return null;
} else if (mIExpression instanceof NewArray) {
NewArray mNewArray = (NewArray) mIExpression;
if(!mNewArray.getType().equals("[B")){
mJebInstance.print("Unsupported NewArray type " + mNewArray.getType() + " - getByteArrayValue of NewArray");
return null;
}
if(mNewArray.getInitialValues() != null){
List<Constant> initialValues = mNewArray.getInitialValues();
if(initialValues.isEmpty()){
return null;
}
byte[] rezult = new byte[initialValues.size()];
for (int i = 0; i < initialValues.size(); i++) {
rezult[i] = initialValues.get(i).getByte();
}
return rezult;
} else if (mNewArray.getSizes() != null) {
if (mNewArray.getSizes().size() > 1) {
mJebInstance.print("Unsupported NewArray multiSizes array " + mNewArray.getSizes().size() + " - getByteArrayValue of NewArray");
return null;
}
IExpression mSizeIExpression = (IExpression) mNewArray.getSizes().get(0);
int sizeArray = getIntValueOfExpression(mSizeIExpression, intValues, byteArrayValues);
if (sizeArray < 0) {
mJebInstance.print("Incorrect array size " + sizeArray + " - getByteArrayValue of NewArray");
return null;
}
return new byte[sizeArray];
} else {
mJebInstance.print("Error - getByteArrayValue of NewArray");
return null;
}
} else if (mIExpression instanceof StaticField) {
StaticField mStaticField = (StaticField) mIExpression;
String fType = mStaticField.getField().getType();
if(!fType.equals("[B")){
mJebInstance.print("Incorrect StaticField type " + fType + " - getByteArrayValue of StaticField");
return null;
}
if(!byteArrayValues.containsKey(mStaticField.getField().getName())){
mJebInstance.print("Unavailable byte[] " + mStaticField.getField().getName() + " - getByteArrayValue of StaticField");
return null;
}
return byteArrayValues.get(mStaticField.getField().getName());
} else {
mJebInstance.print("Unsupported IExpression parametr type - getByteArrayValue of " + mIExpression.getClass().getName());
return null;
}
}
private static int getIntValueOfExpression(IExpression mIExpression, HashMap<String, Integer> intValues, HashMap<String,byte[]> byteArrayValues) {
if (debug) mJebInstance.print("\t\t\t\t\t\t\t\tgetIntValueOfExpression for " + mIExpression.getClass().getName());
if(mIExpression instanceof ArrayElt){
ArrayElt mArrayElt = (ArrayElt) mIExpression;
if(!(mArrayElt.getArray() instanceof Identifier || mArrayElt.getArray() instanceof StaticField)){
mJebInstance.print("Unsupported type of Array " + mArrayElt.getArray().getClass().getName() + " - getIntValueOfExpression of ArrayElt");
return Integer.MIN_VALUE;
}
String byteArrayName = (mArrayElt.getArray() instanceof Identifier) ? ((Identifier)mArrayElt.getArray()).getName() : ((StaticField)mArrayElt.getArray()).getField().getName();
if(!byteArrayValues.containsKey(byteArrayName)){
mJebInstance.print("Unavailable byteArray " + byteArrayName + " - getIntValueOfExpression of ArrayElt");
return Integer.MIN_VALUE;
}
byte[] mBytes = byteArrayValues.get(byteArrayName);
int position = getIntValueOfExpression(mArrayElt.getIndex(), intValues, byteArrayValues);
if(mBytes.length <= position){
mJebInstance.print("Incorrect position (byte[].length " + mBytes.length + " <= position " + position + ") - getIntValueOfExpression of ArrayElt");
return Integer.MIN_VALUE;
} else if (position < 0){
mJebInstance.print("Incorrect position (position " + position + " < 0) - getIntValueOfExpression of ArrayElt");
return Integer.MIN_VALUE;
}
return mBytes[position];
} else if (mIExpression instanceof Call) {
//TODO
mJebInstance.print("Unsupported IExpression parametr type - getIntValueOfExpression of Call");
return Integer.MIN_VALUE;
} else if (mIExpression instanceof Constant) {
Constant mConstant = (Constant) mIExpression;
if(mConstant.isFalse() || mConstant.isNull() || mConstant.isTrue() || mConstant.isString()){
mJebInstance.print("Unsupported type of Constant - getIntValueOfExpression of Constant");
return Integer.MIN_VALUE;
}
String type = ((Constant) mIExpression).getType();
if(type.equals("B")){
return (int) ((Constant) mIExpression).getByte();
} else if (type.equals("S")){
return (int) ((Constant) mIExpression).getShort();
} else {
return ((Constant) mIExpression).getInt();
}
} else if (mIExpression instanceof Expression) {
Expression mExpression = (Expression) mIExpression;
int lValue = Integer.MIN_VALUE;
if(mExpression.getLeft() != null) {
lValue = getIntValueOfExpression(mExpression.getLeft(), intValues, byteArrayValues);
if (lValue == Integer.MIN_VALUE) {
mJebInstance.print("Incorrect lValue " + lValue + " - getIntValueOfExpression of Expression");
return Integer.MIN_VALUE;
}
}
int rValue = getIntValueOfExpression(mExpression.getRight(), intValues, byteArrayValues);
if (rValue == Integer.MIN_VALUE) {
mJebInstance.print("Incorrect rValue " + rValue + " - getIntValueOfExpression of Expression");
return Integer.MIN_VALUE;
}
Operator mOperator = mExpression.getOperator();
if(mOperator.equals(Operator.ADD)){
return lValue + rValue;
} else if (mOperator.equals(Operator.SUB)){
return lValue - rValue;
} else if (mOperator.equals(Operator.MUL)){
return lValue * rValue;
} else if (mOperator.equals(Operator.DIV)){
return lValue / rValue;
} else if (mOperator.equals(Operator.REM)){
return lValue % rValue;
} else if (mOperator.equals(Operator.OR)){
return lValue | rValue;
} else if (mOperator.equals(Operator.AND)){
return lValue & rValue;
} else if (mOperator.equals(Operator.XOR)){
return lValue ^ rValue;
} else if (mOperator.equals(Operator.SHL)){
return lValue << rValue;
} else if (mOperator.equals(Operator.SHR)){
return lValue >> rValue;
} else if (mOperator.equals(Operator.USHR)){
return lValue >>> rValue;
} else if (mOperator.equals(Operator.NEG)) {
return -rValue;
} else if (mOperator.equals(Operator.CAST_TO_BYTE)) {
return rValue;
} else if (mOperator.equals(Operator.CAST_TO_SHORT)) {
return rValue;
} else if (mOperator.equals(Operator.CAST_TO_INT)) {
return rValue;
} else {
mJebInstance.print("Unsupported operation type \"" + mOperator.toString() + "\" - getIntValueOfExpression of Expression");
return Integer.MIN_VALUE;
}
} else if (mIExpression instanceof Identifier) {
Identifier mIdentifier = (Identifier) mIExpression;
if(!intValues.containsKey(mIdentifier.getName())){
mJebInstance.print("intValues.keys = " + Arrays.toString(intValues.keySet().toArray()));
mJebInstance.print("Unavailable int " + mIdentifier.getName() + " - getIntValueOfExpression of Identifier");
return Integer.MIN_VALUE;
}
return intValues.get(mIdentifier.getName());
} else if (mIExpression instanceof InstanceField) {
//TODO
mJebInstance.print("Unsupported IExpression parametr type - getIntValueOfExpression of InstanceField");
return Integer.MIN_VALUE;
} else if (mIExpression instanceof StaticField) {
StaticField mStaticField = (StaticField) mIExpression;
String fType = mStaticField.getField().getType();
if(!(fType.equals("I") || fType.equals("B") || fType.equals("S") )){
mJebInstance.print("Incorrect StaticField type " + fType + " - getIntValueOfExpression of StaticField");
return Integer.MIN_VALUE;
}
if(!intValues.containsKey(mStaticField.getField().getName())){
mJebInstance.print("Unavailable int " + mStaticField.getField().getName() + " - getIntValueOfExpression of StaticField");
return Integer.MIN_VALUE;
}
return intValues.get(mStaticField.getField().getName());
} else {
mJebInstance.print("Unsupported IExpression parametr type - getIntValueOfExpression of " + mIExpression.getClass().getName());
return Integer.MIN_VALUE;
}
}
private static String getClassFromSignature(String signature){
String pattern = "^(L[a-zA-Z/$]*[;])(.*)$";
return signature.replaceAll(pattern, "$1");
}
}
@bactis

This comment has been minimized.

Copy link

commented Feb 9, 2016

thank you :)

@AKosterin

This comment has been minimized.

Copy link
Owner Author

commented Feb 9, 2016

I continue to plug testing found a number of serious errors. In the near future I will update it

@virqdroid

This comment has been minimized.

Copy link

commented Feb 9, 2016

Thanks AKosterin 👍

@idanr1986

This comment has been minimized.

Copy link

commented Feb 9, 2016

first thank you very much!
any idea how to fix this ?

DexGuardDecoder_Start
Error executing java script:
java.lang.NullPointerException
DexGuardDecoder.run(DexGuardDecoder.java:47)

@AKosterin

This comment has been minimized.

Copy link
Owner Author

commented Feb 9, 2016

idanr1986, it means that the script did not find the decryption method. Сan You send me the apk, that are you trying to decipher?

@idanr1986

This comment has been minimized.

Copy link

commented Feb 9, 2016

ahh ok can you write an md5 for example?

@jumbofreak

This comment has been minimized.

Copy link

commented Feb 12, 2016

one more
DexGuardDecoder_Start
Error executing script:
java.lang.NullPointerException
DexGuardDecoder.run(DexGuardDecoder.java:47)

This is the file https://www.virustotal.com/en/file/c655e01b40b7bc5e9c88429360828c046549ac07d7ba6d591b7bdda92597bafd/analysis/

@virqdroid

This comment has been minimized.

@AKosterin

This comment has been minimized.

Copy link
Owner Author

commented Feb 20, 2016

Can you send me apk?

@alxchk

This comment has been minimized.

Copy link

commented Apr 7, 2016

DexGuardDecoder.java:304: error: cannot find symbol
} else if (mAssignment.isCombinedOperatorAssignment()) {

:(

@mbazaliy

This comment has been minimized.

Copy link

commented Apr 20, 2016

Is it compatible with JEB 2.xx plugins API ?

@enovella

This comment has been minimized.

Copy link

commented Jul 6, 2016

@jumbofreak && @virqdroid:

Can we download these APKs? Or could you upload them somewhere?

The script does work with some versions of DexGuard.

@parind

This comment has been minimized.

Copy link

commented Nov 24, 2018

Do they work with DexProtector ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.