I know the basics to java programming, but I'm new to reverse engineering APKs, so explanations would be nice!

I have an APK file file, but not the Java source. After decompiling the APK online:

the bulk of the application is hidden under

assets > classes.dex.dat

the only java file I found is

com > ... > util > ProtectedUtils.java

I have ProtectedUtils.java below: Link to full file if anyone is interested

import android.app.Application; import android.app.Instrumentation; import android.content.Context; import android.os.Build.VERSION; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.List; public class ProtectedApplicationUtils extends Application { private static Application f0d; private static boolean f1d; private static transient Object[] f2d; private static Application dd; private static boolean gfgf; public ProtectedApplicationUtils() { dd = this; } private static final int attachBaseContext(int i, int i2) { int i3 = (i2 + i) >> 24; return (i >>> i2) | (i << (-i2)); } public static Context attachBaseContext(Context context) { attachBaseContext(); return context == dd ? f0d : context; } private static void attachBaseContext() { if (!f1d) { f1d = true; Class cls = Class.forName(m1d("\u50b3\uc849\ue145\ud010\udf4f\u45e5\u6b13\u36e0\u0e7b\ucae7\u774e\uc2b0\ub84a\udeeb\u9071\u3fd2\u3dd6\u3676\u95ca\u031b\udc13\ufaca\u3bf1\u0935\u75af\ud3d6")); Class[] clsArr = new Class[0]; Object invoke = cls.getMethod(m1d("\u50b1\uc852\ue153\ud010\udf45\u45e2\u6b03\u368f\u0e79\ucae3\u7757\uc2e8\ub862\udefc\u907c\u3fef\u3dc8\u366d\u95db\u0303\udc23"), clsArr).invoke(null, new Object[0]); Field declaredField = cls.getDeclaredField(m1d("\u50bf\uc866\ue14d\ud00e\udf61\u45fc\u6b07\u36a2\u0e73\ucaf4\u775f\uc2ea\ub862\udee7\u906b\u3fc8")); declaredField.setAccessible(true); ((List) declaredField.get(invoke)).add(0, f0d); Field declaredField2 = cls.getDeclaredField(m1d("\u50bf\uc86e\ue14f\ud00b\udf54\u45e5\u6b16\u36a2\u0e5b\ucae7\u774e\uc2f2\ub862\udeeb\u9064\u3fcf\u3dc9\u3670\u95d0")); declaredField2.setAccessible(true); declaredField2.set(invoke, f0d); Field declaredField3 = cls.getDeclaredField(m1d("\u50bf\uc865\ue14e\ud017\udf4e\u45e8\u6b36\u36be\u0e6a\ucafb\u7757\uc2fd\ub86a\udefc\u906c\u3fd4\u3dce")); declaredField3.setAccessible(true); Object obj = declaredField3.get(invoke); Field declaredField4 = obj.getClass().getDeclaredField(m1d("\u50bb\uc849\ue147\ud00d")); declaredField4.setAccessible(true); Object obj2 = declaredField4.get(obj); Field declaredField5 = obj2.getClass().getDeclaredField(m1d("\u50bf\uc866\ue151\ud012\udf4c\u45e5\u6b14\u36af\u0e6e\ucafe\u7751\uc2f0")); declaredField5.setAccessible(true); declaredField5.set(obj2, f0d); Context baseContext = f0d.getBaseContext(); Field declaredField6 = baseContext.getClass().getDeclaredField(m1d("\u50bf\uc868\ue154\ud016\udf45\u45fe\u6b34\u36a1\u0e74\ucae3\u775b\uc2e6\ub87f")); declaredField6.setAccessible(true); declaredField6.set(baseContext, f0d); } } private static final int m0d(byte[] bArr, int i) { Object obj = null; int i2 = bArr[14] << 16; Object obj2 = null; while (obj2 == null) { obj2 = 3; try { return (bArr[(i >> 24) & 255] << 24) | (((bArr[i & 255] & 255) | ((bArr[(i >> 8) & 255] & 255) << 8)) | ((bArr[(i >> 16) & 255] & 255) << 16)); } catch (Exception e) { } } while (obj == null) { obj = 2; try { return bArr[i & 127] >> 8; } catch (Exception e2) { } } return i2; } static final String m1d(String str) { if (f2d == null) { mark(); } Object[] objArr = (Object[]) ((Method) f2d[8]).invoke(((Method) f2d[7]).invoke(null, null), null); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append(((Method) f2d[10]).invoke(objArr[((Integer) f2d[12]).intValue()], null)); int hashCode = stringBuilder.append(((Method) f2d[11]).invoke(objArr[((Integer) f2d[12]).intValue()], null)).toString().hashCode(); int[] iArr = (int[]) f2d[6]; int i = hashCode ^ iArr[0]; int i2 = hashCode ^ iArr[1]; int i3 = hashCode ^ iArr[2]; int i4 = hashCode ^ iArr[3]; iArr = (int[]) f2d[5]; int[] iArr2 = (int[]) f2d[1]; int[] iArr3 = (int[]) f2d[2]; int[] iArr4 = (int[]) f2d[3]; int[] iArr5 = (int[]) f2d[4]; byte[] bArr = (byte[]) f2d[0]; char[] cArr = (char[]) ((Method) f2d[9]).invoke(str, null); int i5 = i3; i3 = i2; i2 = i; i = i4; Object obj = null; while (obj == null) { try { int length = cArr.length; for (int i6 = 0; i6 < length; i6++) { if (i6 % 8 == 0) { int i7; int i8; int i9; int i10 = i2 ^ iArr[0]; int i11 = i3 ^ iArr[1]; int i12 = i5 ^ iArr[2]; i4 = iArr[3] ^ i; int i13 = 4; while (i13 < 36) { i7 = (((iArr2[i10 & 255] ^ iArr3[(i11 >> 8) & 255]) ^ iArr4[(i12 >> 16) & 255]) ^ iArr5[i4 >>> 24]) ^ iArr[i13]; i8 = (((iArr2[i11 & 255] ^ iArr3[(i12 >> 8) & 255]) ^ iArr4[(i4 >> 16) & 255]) ^ iArr5[i10 >>> 24]) ^ iArr[i13 + 1]; i9 = (((iArr2[i12 & 255] ^ iArr3[(i4 >> 8) & 255]) ^ iArr4[(i10 >> 16) & 255]) ^ iArr5[i11 >>> 24]) ^ iArr[i13 + 2]; i4 = (((iArr2[i4 & 255] ^ iArr3[(i10 >> 8) & 255]) ^ iArr4[(i11 >> 16) & 255]) ^ iArr5[i12 >>> 24]) ^ iArr[i13 + 3]; i13 += 4; i10 = (((iArr2[i7 & 255] ^ iArr3[(i8 >> 8) & 255]) ^ iArr4[(i9 >> 16) & 255]) ^ iArr5[i4 >>> 24]) ^ iArr[i13]; i11 = iArr[i13 + 1] ^ (((iArr2[i8 & 255] ^ iArr3[(i9 >> 8) & 255]) ^ iArr4[(i4 >> 16) & 255]) ^ iArr5[i7 >>> 24]); i12 = (((iArr2[i9 & 255] ^ iArr3[(i4 >> 8) & 255]) ^ iArr4[(i7 >> 16) & 255]) ^ iArr5[i8 >>> 24]) ^ iArr[i13 + 2]; i4 = (((iArr2[i4 & 255] ^ iArr3[(i7 >> 8) & 255]) ^ iArr4[(i8 >> 16) & 255]) ^ iArr5[i9 >>> 24]) ^ iArr[i13 + 3]; i13 += 4; } i7 = (((iArr2[i10 & 255] ^ iArr3[(i11 >> 8) & 255]) ^ iArr4[(i12 >> 16) & 255]) ^ iArr5[i4 >>> 24]) ^ iArr[i13]; i8 = (((iArr2[i11 & 255] ^ iArr3[(i12 >> 8) & 255]) ^ iArr4[(i4 >> 16) & 255]) ^ iArr5[i10 >>> 24]) ^ iArr[i13 + 1]; i9 = (((iArr2[i12 & 255] ^ iArr3[(i4 >> 8) & 255]) ^ iArr4[(i10 >> 16) & 255]) ^ iArr5[i11 >>> 24]) ^ iArr[i13 + 2]; i4 = (((iArr2[i4 & 255] ^ iArr3[(i10 >> 8) & 255]) ^ iArr4[(i11 >> 16) & 255]) ^ iArr5[i12 >>> 24]) ^ iArr[i13 + 3]; i12 = i13 + 4; i2 = iArr[i12 + 0] ^ ((((bArr[i7 & 255] & 255) ^ ((bArr[(i8 >> 8) & 255] & 255) << 8)) ^ ((bArr[(i9 >> 16) & 255] & 255) << 16)) ^ (bArr[i4 >>> 24] << 24)); i3 = iArr[i12 + 1] ^ ((((bArr[i8 & 255] & 255) ^ ((bArr[(i9 >> 8) & 255] & 255) << 8)) ^ ((bArr[(i4 >> 16) & 255] & 255) << 16)) ^ (bArr[i7 >>> 24] << 24)); i5 = iArr[i12 + 2] ^ ((((bArr[i9 & 255] & 255) ^ ((bArr[(i4 >> 8) & 255] & 255) << 8)) ^ ((bArr[(i7 >> 16) & 255] & 255) << 16)) ^ (bArr[i8 >>> 24] << 24)); i = iArr[i12 + 3] ^ ((((bArr[i4 & 255] & 255) ^ ((bArr[(i7 >> 8) & 255] & 255) << 8)) ^ ((bArr[(i8 >> 16) & 255] & 255) << 16)) ^ (bArr[i9 >>> 24] << 24)); } obj = null; while (obj == null) { obj = 3; try { switch (i6 % 8) { case 0: cArr[i6] = (char) ((i2 >> 16) ^ cArr[i6]); break; case 1: cArr[i6] = (char) (cArr[i6] ^ i2); break; case 2: cArr[i6] = (char) ((i3 >> 16) ^ cArr[i6]); break; case 3: cArr[i6] = (char) (cArr[i6] ^ i3); break; case 4: cArr[i6] = (char) ((i5 >> 16) ^ cArr[i6]); break; case 5: cArr[i6] = (char) (cArr[i6] ^ i5); break; case 6: cArr[i6] = (char) ((i >> 16) ^ cArr[i6]); break; case 7: cArr[i6] = (char) (cArr[i6] ^ i); break; default: break; } } catch (Throwable th) { } } } return new String(cArr); } catch (Throwable th2) { i4 = 1; } } return new String(cArr); } private void eee() { if (!gfgf) { ... byte[] bArr = new byte[length]; int i = 0; for (int i2 = 1; i2 < toCharArray.length; i2++) { char c = toCharArray[i2]; int i3 = i + 1; bArr[i] = (byte) (c >> 8); i = i3 + 1; bArr[i3] = (byte) c; } length -= toCharArray[0]; Class cls = Class.forName(m1d("\u6afc\u53ea\ue9e7\u77d1\ueefe\uac91\u5139\ubcbd\u7975\uc65a\ue12c\u66ac\uc08c\u48ca\u17b8\ua701")); Class cls2 = Class.forName(m1d("\u6afc\u53ea\ue9e7\u77d1\ueefe\uac94\u5137\ubcfd\u7954\uc61d\ue113\u66bd")); Constructor constructor = cls2.getConstructor(new Class[]{cls2, cls}); Method method = Class.forName(m1d("\u6af7\u53e5\ue9f5\u77c2\ueebf\uac94\u513c\ubcfd\u7971\uc61b\ue111\u66ac\uc09b\u48cd\u17a2\ua748\u6dfc\u8c7c\ud17e\u8ccc\u9348\ue1fb\u6b56")).getMethod(m1d("\u6af1\u53ee\ue9e5\u77f4\ueeb9\uac8f"), new Class[]{cls, Integer.TYPE}); Object invoke = method.invoke(this, new Object[]{m1d("\u6af2\u53ee\ue9e9"), Integer.valueOf(0)}); Object invoke2 = method.invoke(this, new Object[]{m1d("\u6af9\u53fe\ue9e5\u77d4\ueeb5\uac85"), Integer.valueOf(0)}); Object newInstance = constructor.newInstance(new Object[]{invoke, m1d("\u6af8\u53ee\ue9e6\u779e\ueeb1\uac8d\u5133")}); Object newInstance2 = constructor.newInstance(new Object[]{invoke2, m1d("\u6af8\u53ee\ue9e6\u779e\ueebf\uac99\u513d\ubcab")}); Class cls3 = Class.forName(m1d("\u6afc\u53ea\ue9e7\u77d1\ueefe\uac94\u5137\ubcfd\u7954\uc61d\ue113\u66bd\uc0b1\u48d6\u17a2\ua716\u6dca\u8c67\ud143\u8ccc\u935f\ue1e6\u6b43\u5edf")); Object newInstance3 = cls3.getConstructor(new Class[]{cls2}).newInstance(new Object[]{newInstance}); try { cls3.getMethod(m1d("\u6ae1\u53f9\ue9f8\u77c4\ueeb5"), new Class[]{byte[].class, Integer.TYPE, Integer.TYPE}).invoke(newInstance3, new Object[]{bArr, Integer.valueOf(0), Integer.valueOf(length)}); Class[] clsArr = new Class[0]; cls3.getMethod(m1d("\u6af5\u53e7\ue9fe\u77c3\ueeb5"), clsArr).invoke(newInstance3, new Object[0]); clsArr = new Class[0]; Method method2 = cls2.getMethod(m1d("\u6af1\u53ee\ue9e5\u77f3\ueeb1\uac93\u5137\ubcbd\u797b\uc617\ue11e\u66b4\uc0ae\u48c2\u17a2\ua70e"), clsArr); Class cls4 = Class.forName(m1d("\u6af2\u53ea\ue9fd\u77c6\ueeb9\uac96\u5176\ubca0\u796b\uc607\ue10b\u66bd\uc093\u488d\u1792\ua703\u6dc7\u8c55\ud179\u8cd4\u9348")); Method method3 = cls4.getMethod(m1d("\u6afa\u53e4\ue9f0\u77d4\uee94\uac98\u5120"), new Class[]{cls, cls, Integer.TYPE}); Object[] objArr = new Object[3]; objArr[0] = method2.invoke(newInstance, new Object[0]); objArr[1] = method2.invoke(newInstance2, new Object[0]); objArr[2] = Integer.valueOf(0); Object invoke3 = method3.invoke(null, objArr); clsArr = new Class[0]; Method method4 = cls2.getMethod(m1d("\u6af2\u53ee\ue9fd\u77d5\ueea4\uac98"), clsArr); method4.invoke(newInstance, new Object[0]); method4.invoke(newInstance2, new Object[0]); Method method5 = cls4.getMethod(m1d("\u6afa\u53e4\ue9f0\u77d4\uee93\uac91\u5139\ubca0\u7961"), new Class[]{cls, Class.forName(m1d("\u6afc\u53ea\ue9e7\u77d1\ueefe\uac91\u5139\ubcbd\u7975\uc65a\ue13c\u66b4\uc09f\u48d0\u17a5\ua72a\u6dd0\u8c72\ud174\u8cdd\u935f"))}); Class cls5 = Class.forName(m1d("\u6afc\u53ea\ue9e7\u77d1\ueefe\uac91\u5139\ubcbd\u7975\uc65a\ue130\u66ba\uc094\u48c6\u17b5\ua712")); ((Class) method5.invoke(invoke3, new Object[]{m1d("\u6af5\u53e4\ue9fc\u779e\ueeba\uac88\u512c\ubcb6\u7960\uc615\ue113\u66b9\uc09c\u48d0\u17f8\ua716\u6dda\u8c61\ud17b\u8ccc\u935b\ue1ad\u6b57\u5ec6\uac55\u9c90\u04b4\u6b93\u02ab\uabec\u14eb\u3f1e\u589d\ue4b6\ubf55\u7b7b\u67f0\ud0e1\u70f9\u6f15\u22d4\u6219\u6c03\u20df\ua4e9\ub5ca\ue4d1\uee2a\ubce9\ua0fc\u5d07\u1579\u6e23\uf7c8\u849d\u10a5\ucf27\u8cbd\ue95c\u3482\udec5\ua61d\u5956\u6e32\u7e60\ua68a\u87d6"), getClass().getClassLoader()})).getDeclaredMethod(m1d("\u6af3\u53ee\ue9f7\u77d4"), new Class[]{cls5, cls5}).invoke(this, new Object[]{this, invoke3}); gfgf = true; } catch (Throwable th) { Class[] clsArr2 = new Class[0]; cls3.getMethod(m1d("\u6af5\u53e7\ue9fe\u77c3\ueeb5"), clsArr2).invoke(newInstance3, new Object[0]); } } } private static final void mark() { int i; byte[] bArr; byte[] bArr2; byte[] bArr3; int[] iArr; int[] iArr2; Object[] objArr; char[] cArr; String str; int[] iArr3 = new int[256]; byte[] bArr4 = new byte[256]; int[] iArr4 = new int[256]; int[] iArr5 = new int[256]; int[] iArr6 = new int[256]; int[] iArr7 = new int[256]; int[] iArr8 = new int[30]; int i2 = 1; for (i = 0; i < 256; i++) { iArr3[i] = i2; i2 ^= (i2 << 1) ^ ((i2 >>> 7) * 283); } bArr4[0] = (byte) 99; Object obj = null; while (obj == null) { i2 = 0; while (i2 < 255) { try { i = iArr3[255 - i2]; i |= i << 8; bArr4[iArr3[i2]] = (byte) ((i ^ ((((i >> 4) ^ (i >> 5)) ^ (i >> 6)) ^ (i >> 7))) ^ 99); i2++; } catch (Exception e) { i2 = 2; } } ... for (i2 = 0; i2 < cArr.length; i2++) { cArr[i2] = (char) (cArr[i2] - bArr2[i2 % bArr2.length]); } objArr[7] = Class.forName(String.valueOf(cArr, 0, 16)).getMethod(String.valueOf(cArr, 16, 13), null); objArr[8] = Class.forName(String.valueOf(cArr, 0, 16)).getMethod(String.valueOf(cArr, 29, 13), null); objArr[9] = Class.forName(String.valueOf(cArr, 42, 16)).getMethod(String.valueOf(cArr, 58, 11), null); objArr[10] = Class.forName(String.valueOf(cArr, 69, 27)).getMethod(String.valueOf(cArr, 96, 12), null); objArr[11] = Class.forName(String.valueOf(cArr, 69, 27)).getMethod(String.valueOf(cArr, 108, 13), null); str = (String) Class.forName(String.valueOf(cArr, 121, 27)).getMethod(String.valueOf(cArr, 148, 3), new Class[]{Class.forName(String.valueOf(cArr, 42, 16))}).invoke(null, new Object[]{String.valueOf(cArr, 151, 25)}); if (str != null) { i2 = str.hashCode(); i2 = 4; objArr[12] = Integer.valueOf(i2); f2d = objArr; i2 = ((Integer) Class.forName(String.valueOf(cArr, 42, 16)).getMethod(String.valueOf(cArr, 214, 8), new Class[0]).invoke(Class.forName(String.valueOf(cArr, 176, 16)).getField(String.valueOf(cArr, 192, 6)).get(null), new Object[0])).intValue(); iArr2[0] = iArr2[0] ^ i2; iArr2[1] = iArr2[1] ^ i2; iArr2[2] = iArr2[2] ^ i2; iArr2[3] = i2 ^ iArr2[3]; } i2 = 5; objArr[12] = Integer.valueOf(i2); f2d = objArr; i2 = ((Integer) Class.forName(String.valueOf(cArr, 42, 16)).getMethod(String.valueOf(cArr, 214, 8), new Class[0]).invoke(Class.forName(String.valueOf(cArr, 176, 16)).getField(String.valueOf(cArr, 192, 6)).get(null), new Object[0])).intValue(); iArr2[0] = iArr2[0] ^ i2; iArr2[1] = iArr2[1] ^ i2; iArr2[2] = iArr2[2] ^ i2; iArr2[3] = i2 ^ iArr2[3]; } protected void m2attachBaseContext(Context context) { super.attachBaseContext(context); eee(); f0d = Instrumentation.newApplication(Class.forName(m1d("\u50b1\uc848\ue14c\ud04c\udf4a\u45f9\u6b03\u36ab\u0e68\ucaf6\u7752\uc2ff\ub869\udefb\u902b\u3fcb\u3dc5\u366d\u95d5\u0316\udc31\ufa8c\u3bf6\u0924\u75a7\ud3de\u453b\ub730\u0b09\uc6ea\u8620\u607e\u1f4d\u7ca3\uc9e9\uf8a9\ucc9e\u7f5a\ued21\u3a2a\ub4e4\u9bb3\uf59c\u075d")), context); } public void onCreate() { super.onCreate(); attachBaseContext(); f0d.onCreate(); } }

I think it is using some kind of encryption as well as the Java.Reflection API. It would be great if you could explain what this file is doing.

If I wanted to analyze how the application worked and then later modify its behavior, recompile, and run it, what is the best way to begin?

Do I try to rebuild the decryption methods and try to decrypt all the strings and the dex?

Are there any good tools to use?

(Let me know if you need to see the rest of the file)

Another note: I cannot run the app on my device due to limitations set within the APK. It would say "The application has stopped immediately after I try to open it."

Edit:

I've been trying to test the individual methods:

I pasted mark() and the methods/variables it depends on (eg. m0d() , attachBaseContext() , f2d , and apkversion ) in a new java class.

When I try to run it is stuck at "running" with the progress bar frozen at 0.