fix Method code to large (#513)

* fix Method code to large

* format
This commit is contained in:
Bob Pan 2021-10-31 01:15:29 +08:00 committed by GitHub
parent 80a81b317f
commit 62eba635fd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 386 additions and 45 deletions

View File

@ -292,7 +292,10 @@ public class DecryptStringCmd extends BaseCmd {
// convert ir to m3
MethodNode m3 = new MethodNode();
m3.tryCatchBlocks = new ArrayList<>();
new IR2JConverter(true).convert(irMethod, m3);
new IR2JConverter()
.ir(irMethod)
.asm(m3)
.convert();
// copy back m3 to m
m.maxLocals = -1;

View File

@ -34,24 +34,44 @@ import com.googlecode.dex2jar.ir.stmt.Stmt.ST;
import org.objectweb.asm.*;
import java.lang.reflect.Array;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.HashMap;
import java.util.Map;
@SuppressWarnings("incomplete-switch")
public class IR2JConverter implements Opcodes {
public static final int MAX_FILL_ARRAY_BYTES = 500;
private boolean optimizeSynchronized = false;
Dex2Asm.ClzCtx clzCtx;
IrMethod ir;
MethodVisitor asm;
public IR2JConverter() {
super();
}
public IR2JConverter(boolean optimizeSynchronized) {
super();
public IR2JConverter optimizeSynchronized(boolean optimizeSynchronized) {
this.optimizeSynchronized = optimizeSynchronized;
return this;
}
public void convert(IrMethod ir, MethodVisitor asm) {
public IR2JConverter clzCtx(Dex2Asm.ClzCtx clzCtx) {
this.clzCtx = clzCtx;
return this;
}
public IR2JConverter ir(IrMethod ir) {
this.ir = ir;
return this;
}
public IR2JConverter asm(MethodVisitor asm) {
this.asm = asm;
return this;
}
public void convert() {
mapLabelStmt(ir);
reBuildInstructions(ir, asm);
reBuildTryCatchBlocks(ir, asm);
@ -229,15 +249,45 @@ public class IR2JConverter implements Opcodes {
} else {
elementType = "I";
}
int iastoreOP = getOpcode(elementType, IASTORE);
accept(e2.getOp1(), asm);
for (int i = 0; i < arraySize; i++) {
asm.visitInsn(DUP);
asm.visitLdcInsn(i);
asm.visitLdcInsn(Array.get(arrayData, i));
asm.visitInsn(iastoreOP);
boolean genBig = false;
try {
if (this.clzCtx != null
&& "BSIJ".contains(elementType)) {
byte[] data = toLittleEndianArray(arrayData);
if (data != null && data.length > MAX_FILL_ARRAY_BYTES) {
accept(e2.getOp1(), asm);
asm.visitLdcInsn(0);
constLargeArray(asm, data, elementType);
asm.visitLdcInsn(0);
asm.visitLdcInsn(arraySize);
asm.visitMethodInsn(Opcodes.INVOKESTATIC,
"java/lang/System",
"arraycopy",
"(Ljava/lang/Object;ILjava/lang/Object;II)V",
false
);
genBig = true;
}
}
} catch (Exception ignore) {
// any exception, revert to normal
}
if (!genBig) {
int iastoreOP = getOpcode(elementType, IASTORE);
accept(e2.getOp1(), asm);
for (int i = 0; i < arraySize; i++) {
asm.visitInsn(DUP);
asm.visitLdcInsn(i);
asm.visitLdcInsn(Array.get(arrayData, i));
asm.visitInsn(iastoreOP);
}
asm.visitInsn(POP);
}
asm.visitInsn(POP);
} else {
FilledArrayExpr filledArrayExpr = (FilledArrayExpr) e2.getOp2();
int arraySize = filledArrayExpr.ops.length;
@ -248,15 +298,47 @@ public class IR2JConverter implements Opcodes {
} else {
elementType = "I";
}
int iastoreOP = getOpcode(elementType, IASTORE);
accept(e2.getOp1(), asm);
for (int i = 0; i < arraySize; i++) {
asm.visitInsn(DUP);
asm.visitLdcInsn(i);
accept(filledArrayExpr.ops[i], asm);
asm.visitInsn(iastoreOP);
boolean genBig = false;
try {
if (this.clzCtx != null
&& "BSIJ".contains(elementType)
&& isConstant(filledArrayExpr.ops)) {
// create a 500-len byte array, may cause 'Method code too large!'
// convert it to a base64 decoding
byte[] data = collectDataAsByteArray(filledArrayExpr.ops, elementType);
if (data != null && data.length > MAX_FILL_ARRAY_BYTES) {
accept(e2.getOp1(), asm);
asm.visitLdcInsn(0);
constLargeArray(asm, data, elementType);
asm.visitLdcInsn(0);
asm.visitLdcInsn(arraySize);
asm.visitMethodInsn(INVOKESTATIC,
"java/lang/System",
"arraycopy",
"(Ljava/lang/Object;ILjava/lang/Object;II)V",
false
);
genBig = true;
}
}
} catch (Exception ignore) {
// any exception, revert to normal
}
if (!genBig) {
int iastoreOP = getOpcode(elementType, IASTORE);
accept(e2.getOp1(), asm);
for (int i = 0; i < arraySize; i++) {
asm.visitInsn(DUP);
asm.visitLdcInsn(i);
accept(filledArrayExpr.ops[i], asm);
asm.visitInsn(iastoreOP);
}
asm.visitInsn(POP);
}
asm.visitInsn(POP);
}
}
break;
@ -390,6 +472,35 @@ public class IR2JConverter implements Opcodes {
}
}
private void constLargeArray(MethodVisitor asm, byte[] data, String elementType) {
String cst = hexEncode(data);
if (cst.length() > 65535) { // asm have the limit
asm.visitTypeInsn(Opcodes.NEW, "java/lang/StringBuilder");
asm.visitInsn(Opcodes.DUP);
asm.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false);
for (int i = 0; i < cst.length(); i += 65500) {
int a = Math.min(65500, cst.length() - i);
asm.visitLdcInsn(cst.substring(i, i + a));
asm.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder",
"append",
"(Ljava/lang/String;)Ljava/lang/StringBuilder;",
false
);
}
asm.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder",
"toString",
"()Ljava/lang/String;",
false
);
} else {
asm.visitLdcInsn(cst);
}
asm.visitMethodInsn(Opcodes.INVOKESTATIC, toInternal(this.clzCtx.classDescriptor),
this.clzCtx.buildHexDecodeMethodName(elementType), "(Ljava/lang/String;)[" + elementType, false);
}
private static boolean isLocalWithIndex(Value v, int i) {
return v.vt == VT.LOCAL && ((Local) v)._ls_index == i;
}
@ -556,7 +667,7 @@ public class IR2JConverter implements Opcodes {
}
}
private static void accept(Value value, MethodVisitor asm) {
private void accept(Value value, MethodVisitor asm) {
switch (value.et) {
case E0:
@ -595,10 +706,17 @@ public class IR2JConverter implements Opcodes {
}
}
private static void reBuildEnExpression(EnExpr value, MethodVisitor asm) {
public static String hexEncode(byte[] data) {
StringBuilder sb = new StringBuilder();
for (byte b : data) {
sb.append(String.format("%02x", b & 0xFF));
}
return sb.toString();
}
private void reBuildEnExpression(EnExpr value, MethodVisitor asm) {
if (value.vt == VT.FILLED_ARRAY) {
FilledArrayExpr fae = (FilledArrayExpr) value;
reBuildE1Expression(Exprs.nNewArray(fae.type, Exprs.nInt(fae.ops.length)), asm);
String tp1 = fae.valueType;
int xastore = IASTORE;
String elementType = null;
@ -607,6 +725,25 @@ public class IR2JConverter implements Opcodes {
xastore = getOpcode(elementType, IASTORE);
}
try {
if (this.clzCtx != null
&& elementType != null
&& "BSIJ".contains(elementType)
&& isConstant(fae.ops)) {
byte[] data = collectDataAsByteArray(fae.ops, elementType);
if (data != null && data.length > MAX_FILL_ARRAY_BYTES) {
constLargeArray(asm, data, elementType);
return;
}
}
} catch (Exception ignore) {
// any exception, revert to normal
}
reBuildE1Expression(Exprs.nNewArray(fae.type, Exprs.nInt(fae.ops.length)), asm);
for (int i = 0; i < fae.ops.length; i++) {
if (fae.ops[i] == null)
continue;
@ -720,6 +857,91 @@ public class IR2JConverter implements Opcodes {
}
}
private static byte[] collectDataAsByteArray(Value[] ops, String t) {
switch (t) {
case "B": {
byte[] d = new byte[ops.length];
for (int i = 0, opsLength = ops.length; i < opsLength; i++) {
Value op = ops[i];
Constant cst = (Constant) op;
d[i] = ((Number) cst.value).byteValue();
}
return d;
}
case "S": {
short[] d = new short[ops.length];
for (int i = 0, opsLength = ops.length; i < opsLength; i++) {
Value op = ops[i];
Constant cst = (Constant) op;
d[i] = ((Number) cst.value).shortValue();
}
return toLittleEndianArray(d);
}
case "I": {
int[] d = new int[ops.length];
for (int i = 0, opsLength = ops.length; i < opsLength; i++) {
Value op = ops[i];
Constant cst = (Constant) op;
d[i] = ((Number) cst.value).intValue();
}
return toLittleEndianArray(d);
}
case "J": {
long[] d = new long[ops.length];
for (int i = 0, opsLength = ops.length; i < opsLength; i++) {
Value op = ops[i];
Constant cst = (Constant) op;
d[i] = ((Number) cst.value).longValue();
}
return toLittleEndianArray(d);
}
}
return null;
}
private static byte[] toLittleEndianArray(Object d) {
if (d instanceof byte[]) {
return (byte[]) d;
} else if (d instanceof short[]) {
return toLittleEndianArray((short[]) d);
} else if (d instanceof int[]) {
return toLittleEndianArray((int[]) d);
} else if (d instanceof long[]) {
return toLittleEndianArray((long[]) d);
}
return null;
}
private static byte[] toLittleEndianArray(long[] d) {
ByteBuffer b = ByteBuffer.allocate(d.length *8);
b.order(ByteOrder.LITTLE_ENDIAN);
b.asLongBuffer().put(d);
return b.array();
}
private static byte[] toLittleEndianArray(int[] d) {
ByteBuffer b = ByteBuffer.allocate(d.length *4);
b.order(ByteOrder.LITTLE_ENDIAN);
b.asIntBuffer().put(d);
return b.array();
}
private static byte[] toLittleEndianArray(short[] d) {
ByteBuffer b = ByteBuffer.allocate(d.length *2);
b.order(ByteOrder.LITTLE_ENDIAN);
b.asShortBuffer().put(d);
return b.array();
}
private static boolean isConstant(Value[] ops) {
for (Value op : ops) {
if (op.vt != VT.CONSTANT) {
return false;
}
}
return true;
}
private static void box(String provideType, String expectedType, MethodVisitor asm) {
if(provideType.equals(expectedType)){
return;
@ -848,7 +1070,7 @@ public class IR2JConverter implements Opcodes {
}
}
private static void reBuildE1Expression(E1Expr e1, MethodVisitor asm) {
private void reBuildE1Expression(E1Expr e1, MethodVisitor asm) {
accept(e1.getOp(), asm);
switch (e1.vt) {
case STATIC_FIELD: {
@ -924,7 +1146,7 @@ public class IR2JConverter implements Opcodes {
}
}
private static void reBuildE2Expression(E2Expr e2, MethodVisitor asm) {
private void reBuildE2Expression(E2Expr e2, MethodVisitor asm) {
String type = e2.op2.valueType;
accept(e2.op1, asm);
if ((e2.vt == VT.ADD || e2.vt == VT.SUB) && e2.op2.vt == VT.CONSTANT) {

View File

@ -1,5 +1,7 @@
package com.googlecode.d2j.dex;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import com.googlecode.d2j.converter.Dex2IRConverter;
@ -14,7 +16,19 @@ import com.googlecode.dex2jar.ir.ts.*;
import com.googlecode.dex2jar.ir.ts.array.FillArrayTransformer;
public class Dex2Asm {
public static class ClzCtx {
public String classDescriptor;
public String hexDecodeMethodNamePrefix;
public String buildHexDecodeMethodName(String x) {
if (hexDecodeMethodNamePrefix == null) {
byte[] d = new byte[4];
new Random().nextBytes(d);
hexDecodeMethodNamePrefix = "$d2j$hex$" + IR2JConverter.hexEncode(d);
}
return hexDecodeMethodNamePrefix + "$decode_" + x;
}
}
protected static class Clz {
public int access;
public Clz enclosingClass;
@ -440,17 +454,42 @@ public class Dex2Asm {
}
}
if (classNode.methods != null) {
ClzCtx clzCtx = new ClzCtx();
clzCtx.classDescriptor = classNode.className;
for (DexMethodNode methodNode : classNode.methods) {
convertMethod(classNode, methodNode, cv);
convertMethod(classNode, methodNode, cv, clzCtx);
}
if (clzCtx.hexDecodeMethodNamePrefix != null) {
addHexDecodeMethod(cv, clzCtx.hexDecodeMethodNamePrefix);
}
}
cv.visitEnd();
}
public void convertCode(DexMethodNode methodNode, MethodVisitor mv) {
private void addHexDecodeMethod(ClassVisitor outCV, String hexDecodeMethodNameBase) {
// the .data is a class file compiled from res.Hex
try (InputStream is = Dex2Asm.class.getResourceAsStream("/d2j_hex_decode_stub.data")) {
ClassReader cr = new ClassReader(is);
cr.accept(new ClassVisitor(Opcodes.ASM5) {
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
if (name.startsWith("decode")) {
return outCV.visitMethod(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC,
hexDecodeMethodNameBase + "$" + name,
desc, signature, exceptions
);
} else {
return super.visitMethod(access, name, desc, signature, exceptions);
}
}
}, ClassReader.EXPAND_FRAMES);
} catch (IOException e) {
throw new RuntimeException("fail to add hex.decode", e);
}
}
public void convertCode(DexMethodNode methodNode, MethodVisitor mv, ClzCtx clzCtx) {
IrMethod irMethod = dex2ir(methodNode);
optimize(irMethod);
ir2j(irMethod, mv);
ir2j(irMethod, mv, clzCtx);
}
public void convertDex(DexFileNode fileNode, ClassVisitorFactory cvf) {
@ -546,7 +585,7 @@ public class Dex2Asm {
return ele;
}
public void convertMethod(DexClassNode classNode, DexMethodNode methodNode, ClassVisitor cv) {
public void convertMethod(DexClassNode classNode, DexMethodNode methodNode, ClassVisitor cv, ClzCtx clzCtx) {
MethodVisitor mv = collectBasicMethodInfo(methodNode, cv);
@ -593,7 +632,7 @@ public class Dex2Asm {
if ((NO_CODE_MASK & methodNode.access) == 0) { // has code
if (methodNode.codeNode != null) {
mv.visitCode();
convertCode(methodNode, mv);
convertCode(methodNode, mv, clzCtx);
}
}
@ -624,8 +663,13 @@ public class Dex2Asm {
return clz;
}
public void ir2j(IrMethod irMethod, MethodVisitor mv) {
new IR2JConverter(false).convert(irMethod, mv);
public void ir2j(IrMethod irMethod, MethodVisitor mv, ClzCtx clzCtx) {
new IR2JConverter()
.optimizeSynchronized(false)
.clzCtx(clzCtx)
.ir(irMethod)
.asm(mv)
.convert();
mv.visitMaxs(-1, -1);
}
@ -645,15 +689,16 @@ public class Dex2Asm {
T_agg.transform(irMethod);
T_multiArray.transform(irMethod);
T_voidInvoke.transform(irMethod);
T_type.transform(irMethod);
{
// https://github.com/pxb1988/dex2jar/issues/477
// dead code found in unssa, clean up
T_deadCode.transform(irMethod);
T_removeLocal.transform(irMethod);
T_removeConst.transform(irMethod);
T_unssa.transform(irMethod);
}
T_type.transform(irMethod);
T_unssa.transform(irMethod);
T_trimEx.transform(irMethod);
T_ir2jRegAssign.transform(irMethod);
}

View File

@ -124,12 +124,12 @@ public class Dex2jar {
};
new ExDex2Asm(exceptionHandler) {
public void convertCode(DexMethodNode methodNode, MethodVisitor mv) {
public void convertCode(DexMethodNode methodNode, MethodVisitor mv, ClzCtx clzCtx) {
if ((readerConfig & DexFileReader.SKIP_CODE) != 0 && methodNode.method.getName().equals("<clinit>")) {
// also skip clinit
return;
}
super.convertCode(methodNode, mv);
super.convertCode(methodNode, mv, clzCtx);
}
@Override
@ -169,8 +169,13 @@ public class Dex2jar {
}
@Override
public void ir2j(IrMethod irMethod, MethodVisitor mv) {
new IR2JConverter(0 != (V3.OPTIMIZE_SYNCHRONIZED & v3Config)).convert(irMethod, mv);
public void ir2j(IrMethod irMethod, MethodVisitor mv, ClzCtx clzCtx) {
new IR2JConverter()
.optimizeSynchronized(0 != (V3.OPTIMIZE_SYNCHRONIZED & v3Config))
.clzCtx(clzCtx)
.ir(irMethod)
.asm(mv)
.convert();
}
}.convertDex(fileNode, cvf);

View File

@ -32,12 +32,12 @@ public class ExDex2Asm extends Dex2Asm {
}
@Override
public void convertCode(DexMethodNode methodNode, MethodVisitor mv) {
public void convertCode(DexMethodNode methodNode, MethodVisitor mv, ClzCtx clzCtx) {
MethodVisitor mw = AsmBridge.searchMethodWriter(mv);
MethodNode mn = new MethodNode(Opcodes.ASM5, methodNode.access, methodNode.method.getName(),
methodNode.method.getDesc(), null, null);
try {
super.convertCode(methodNode, mn);
super.convertCode(methodNode, mn, clzCtx);
} catch (Exception ex) {
if (exceptionHandler == null) {
throw new DexException(ex, "fail convert code for %s", methodNode.method);

View File

@ -296,9 +296,9 @@ public abstract class TestUtils {
// 1. convert to .class
Dex2Asm dex2Asm = new Dex2Asm() {
@Override
public void convertCode(DexMethodNode methodNode, MethodVisitor mv) {
public void convertCode(DexMethodNode methodNode, MethodVisitor mv, ClzCtx clzCtx) {
try {
super.convertCode(methodNode, mv);
super.convertCode(methodNode, mv, clzCtx);
} catch (Exception ex) {
BaksmaliDumper d = new BaksmaliDumper();
try {

View File

@ -0,0 +1,65 @@
package res;
import java.nio.*;
public class Hex {
public static long[] decode_J(String src) {
byte[] d = decode_B(src);
ByteBuffer b = ByteBuffer.wrap(d);
b.order(ByteOrder.LITTLE_ENDIAN);
LongBuffer s = b.asLongBuffer();
long[] data = new long[d.length / 8];
s.get(data);
return data;
}
public static int[] decode_I(String src) {
byte[] d = decode_B(src);
ByteBuffer b = ByteBuffer.wrap(d);
b.order(ByteOrder.LITTLE_ENDIAN);
IntBuffer s = b.asIntBuffer();
int[] data = new int[d.length / 4];
s.get(data);
return data;
}
public static short[] decode_S(String src) {
byte[] d = decode_B(src);
ByteBuffer b = ByteBuffer.wrap(d);
b.order(ByteOrder.LITTLE_ENDIAN);
ShortBuffer s = b.asShortBuffer();
short[] data = new short[d.length / 2];
s.get(data);
return data;
}
public static byte[] decode_B(String src) {
char[] d = src.toCharArray();
byte[] ret = new byte[src.length() / 2];
for (int i = 0; i < ret.length; i++) {
char h = d[2 * i];
char l = d[2 * i + 1];
int hh = 0;
if (h >= '0' && h <= '9') {
hh = h - '0';
} else if (h >= 'a' && h <= 'f') {
hh = h - 'a' + 10;
} else if (h >= 'A' && h <= 'F') {
hh = h - 'A' + 10;
} else {
throw new RuntimeException();
}
int ll = 0;
if (l >= '0' && l <= '9') {
ll = h - '0';
} else if (l >= 'a' && l <= 'f') {
ll = h - 'a' + 10;
} else if (l >= 'A' && l <= 'F') {
ll = h - 'A' + 10;
} else {
throw new RuntimeException();
}
d[i] = (char) ((hh << 4) | ll);
}
return ret;
}
}

View File

@ -1,5 +1,6 @@
.class Lcode.Large;
.class Lcode/Large;
.super Lcode/LargeS;
.method private static constructor <clinit>()V
.catchall { :L2 .. :L3 } :L0