能力值:
( LV2,RANK:10 )
|
-
-
2 楼
通过代码自动修改后的字节码,只修改了main方法,只剩一个INVOKEDYNAMIC没有替换,因为前面有标签,可以用recaf手动移动一下指令位置就可以识别到了,复杂的代码移动一下会导致栈不平衡,所以没有自动修改。再就是修改后添加了许多NOP空操作指令,因为修改指令会remove一些指令,导致list变化,有的指令会被跳过,所以就加上NOP保持list不会缩减,或者后期考虑旧指令不变,将指令都存入一个新的指令列表来解决。虽然不太完美,每次解析一个方法,但是自动化修改也省去了很大一部分手动解析指令的工作,效率提高了。
|
能力值:
( LV2,RANK:10 )
|
-
-
3 楼
批量处理的zkm的invokedynamic命令。
处理过程 首先设置代理类,输出方法的第一个invokedynamic的key,因为一个方法内的都是一致的,这里用到StringBuilder去字符拼接,一起输出,防止输出被多线程打乱。 for (MethodNode method : methods) {
InsnList srcList = method.instructions;
for (int i = 0; i < srcList.size(); i++) {//遍历指令
//查找invokedynamic指令
if (srcList.get(i) instanceof InvokeDynamicInsnNode) {
if (srcList.get(i - 1) instanceof VarInsnNode && srcList.get(i - 1).getOpcode() == Opcodes.LLOAD) {//判断是不是LLOAD
VarInsnNode varInsnNode = (VarInsnNode) srcList.get(i - 1);
//创建替换指令
InsnList tmpInsnList = new InsnList();
//组合成一条字符串输出,以免被打乱
tmpInsnList.add(new FieldInsnNode(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"));
tmpInsnList.add(new TypeInsnNode(Opcodes.NEW, "java/lang/StringBuilder"));
tmpInsnList.add(new InsnNode(Opcodes.DUP));
tmpInsnList.add(new MethodInsnNode(Opcodes.INVOKESPECIAL,"java/lang/StringBuilder","<init>","()V",false));
tmpInsnList.add(new LdcInsnNode(className + ":" + method.name + ":" + method.desc + ":"));
tmpInsnList.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL,"java/lang/StringBuilder","append","(Ljava/lang/String;)Ljava/lang/StringBuilder;",false));
tmpInsnList.add(new VarInsnNode(Opcodes.LLOAD, varInsnNode.var));
tmpInsnList.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL,"java/lang/StringBuilder","append","(J)Ljava/lang/StringBuilder;",false));
tmpInsnList.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL,"java/lang/StringBuilder","toString","()Ljava/lang/String;",false));
tmpInsnList.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V"));
srcList.insertBefore(srcList.get(i), tmpInsnList);
}
}
}
method.visitEnd();
}
然后通过代理运行zkm,点击需要解析的按钮和功能。这里只进行了混淆和保存。
java -javaagent:asm.jar=dynamic+com.zelix.+ -jar ZKM.jar > long.txt 然后处理字符,去重,228M文件得到166kb,446个类2719个方法。文件内容如下 com/zelix/ZKM:main:([Ljava/lang/String;)V:46979434589134 首先遍历类,通过类中的方法名和方法描述去处理,key可以查出,这样就可以批量处理了。
批量处理结果在附件,只是触发响应功能的类,其他没有运行加载的类和方法不会处理。只是去掉大部门invokedynamic的方法和加密字符串,有些被其他指令分割的没有处理。有的方法可能在recaf报类型不符,因为recaf严格匹配类名,没有判断调用者或者参数是不是其父类或者子类,实际类型应该是符合的。这个是批量处理的结果,应该会存在一些错误,仅供参考。
|
能力值:
( LV2,RANK:10 )
|
-
-
4 楼
项目基于在zkm15的进行了更新,把以前零星代码都整理进来了,基本可以解析大部分字符串和方法了,并对相应指令进行转换存到class文件了。大的类转换时间可能有点长,毕竟需要和arthas进行交互,调试的分析类和方法的提示去掉了,只保留了写入到文件的提示,可以耐心等待,class写入到classes文件夹下。
执行的命令可以自己在arthas客户端输入history命令进行查看,可以用方向键执行进行查看结果,或者复制进行执行
反编译效果不是很好,推荐还是用recaf进行查看
这只是个思路,其他用zkm进行混淆的,也可以分析套用。
|
能力值:
( LV2,RANK:10 )
|
-
-
5 楼
新的解析思路,半自动化解析混淆方法和字符串,只需要取触发相应功能,后续就会自动解析。这种只适用于可以触发的功能,如果那种无法触发的,比如程序启功开始的一些功能,就需要编写agent去拦截关键参数了,就是最开始zkm那种方式了。
|
能力值:
( LV2,RANK:10 )
|
-
-
6 楼
写了一个recaf的插件,实现加载类,然后再recaf修改字节码,修改完进行热更新,方便了一些操作。可以修改字节码了,就可以随意插入代码了,输出调试,打印调用堆栈,勉强算一个字节码级别的调试工具吧。
代码开源,开源地址在视频简介中。
https://www.bilibili.com/video/BV1Hr4y167jw/
|
|
|