拿到手是一个匿名类,反编译后发现调用大量Java反射,方法名几乎全部混淆为无意义字符“lllIlIllII”
经过分析MainActivity将m57重命名为decrypt_name
,其余被调用方法根据功能分别重命名,查看逻辑

难点在于
1
| String temp_str = toString(append(new StringBuilder(), getMethodName(getStackTrace(new RuntimeException())[1])));
|
中temp_str
的取值。写个Java调用逻辑打印其值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
| import java.lang.reflect.Method;
public class test1 {
public static StackTraceElement[] getStackTrace(Object obj) throws Exception { Method method = RuntimeException.class.getMethod("getStackTrace"); method.setAccessible(true); return (StackTraceElement[]) method.invoke(obj); }
public static String getMethodName(Object obj) throws Exception { Method method = StackTraceElement.class.getMethod("getMethodName"); method.setAccessible(true); return (String) method.invoke(obj); }
public static StringBuilder append(Object obj, String str) throws Exception { Method method = StringBuilder.class.getMethod("append", String.class); method.setAccessible(true); return (StringBuilder) method.invoke(obj, str); }
public static String toString(Object obj) throws Exception { Method method = StringBuilder.class.getMethod("toString"); method.setAccessible(true); return (String) method.invoke(obj); }
public static void main(String[] args) { main1(args); }
public static void main1(String[] args) { try { StackTraceElement[] stackTrace = getStackTrace(new RuntimeException());
StringBuilder stringBuilder = new StringBuilder(); for (StackTraceElement stackTraceElement : stackTrace) { append(stringBuilder, getMethodName(stackTraceElement)); append(stringBuilder, "\n"); }
System.out.println(toString(stringBuilder));
String temp_str = toString( append(new StringBuilder(), getMethodName(getStackTrace(new RuntimeException())[1])) ); System.out.println(temp_str);
} catch (Exception e) { e.printStackTrace(); } } }
|
发现temp_str是调用decrypt_name函数的那个方法的方法名。也就是说,
这个函数实现了不同方法调用相同函数,key不同
因此遍历class文件里的方法名
写python解密发现方法名里的不可见字符其实就是\n
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| def decrypt_name(enc, method_name): length = len(method_name) - 1 length2 = len(enc) - 1 temp = ["" for _ in range(length2 + 1)]
while length2 >= 0: temp[length2] = chr((ord("k") ^ ord(enc[length2])) ^ ord(method_name[length]))
if length2 - 1 < 0: break
temp[length2 - 1] = chr( (ord("P") ^ ord(enc[length2])) ^ ord(method_name[length]) )
length -= 1 if length < 0: length = len(method_name) - 1
length2 -= 2
return "".join(temp)
|
接下来一个个解密出来,重命名
从生成method_list_enc的函数中,先调用decrypt_name解密出来method_list_enc中的每一项,然后使用不同的参数进行des解密每一项,最后从method_list中拿到要反射调用的函数名。最后一个个代回去,就可以恢复代码的逻辑
结果发现这一层只是gzip解压了一个加密过的class文件,后续内存马逻辑在另一个class里,分析思路与上述相同,解出来是一个RSA
遇到的主要难点
其大量使用“动态“的不同的key,使得几乎无法进行批量的解混淆工作。对于RuntimeException的调用栈的使用配合方法名的不可打印字符混淆,是阻碍解混淆工作的一大障碍,且利用调用栈和方法名混淆**:** 调用栈是运行时信息,方法名可能包含不可打印字符
后续不同的method使用的DES密钥不同,由下标控制,是解混淆工作的第二重障碍。通过对这个内存马样本的分析,发现增加混淆过程与程序执行流的耦合程度,可以较好地对抗批量解混淆的自动化脚本