Write Up : MCG/Scan:轻量级字节码扫描的原理与实现
-
Write Up : MCG/Scan:轻量级字节码扫描的原理与实现
MCG/Scan模块对字节码实现跨版本、抗混淆扫描的细节【警告】
MCG是一个EOL项目,而本系列文章的公开无疑会再次降低MCG的安全性。无论如何,请不要再使用MCG。
本系列文章旨在分享MCG的思路而不是源码;请不要尝试通过简单的复制粘贴来完成对MCG的重建。
本文涉及到大量JVM的无/少文档内部实现,其中有部分已经不适合最新版的实现。请自行查证最新版是否一致。▌Part 1 Class文件的常量池结构
1.1 Class文件结构
算了这不重要;我没解析。
LOL1.2 常量池解析(部分)
常量池位于 Class 文件开头的魔数、版本号之后。先是一个u2 类型,表示常量池中常量项的数量(从 1 开始计数,实际有效索引为 1 到 constant_pool_count - 1);然后由多个 cp_info 结构组成,每个项的第一个字节是 tag,标识常量类型。
别跑!我这里有一个你看得懂的版本:package cf.huzpsb.mcguard.parser; import java.util.ArrayList; import java.util.List; public class ClassParser { public static String[] parseClass(byte[] classFile) { try { int cpCount = Byte.toUnsignedInt(classFile[8]) * 256 + Byte.toUnsignedInt(classFile[9]); cpCount--; int offset = 10; List<String> cp = new ArrayList<>(); for (int i = 0; i < cpCount; i++) { int tag = classFile[offset]; offset++; switch (tag) { case 1: // Utf8 int len = Byte.toUnsignedInt(classFile[offset]) * 256 + Byte.toUnsignedInt(classFile[offset + 1]); offset += 2; String s = new String(classFile, offset, len); cp.add(s); offset += len; break; case 7: // Class case 8: case 16: // MethodType case 19: case 20: offset += 2; break; case 3: case 4: case 9: case 10: case 11: case 12: case 17: case 18: offset += 4; break; case 15: offset += 3; break; case 5: case 6: offset += 8; i++; break; case 2: case 13: case 14: throw new Exception("非标准的常量池标签: " + tag); default: throw new Exception("无法识别的常量池标签: " + tag); } } return cp.toArray(new String[0]); } catch (Exception ex) { return null; } } }
▌Part 2 基于规则的常量池扫描
Why it works?
西江月·证明
即得易见平凡,仿照上例显然。
留作习题答案略,读者自证不难。
反之亦然同理,推论自然成立,略去过程QED,由上可知证毕。package cf.huzpsb.mcguard.scan; import cf.huzpsb.mcguard.core.Level; import cf.huzpsb.mcguard.parser.ClassParser; import cf.huzpsb.mcguard.utils.Utils; import java.util.List; public class ScanClass { public static void scan(byte[] bytes, List<String> list, String name) { String[] results = ClassParser.parseClass(bytes); if (results == null) { if (Level.level >= 1) list.add("[错误] " + name + " 无法被解析。"); return; } if (Utils.matchesInArray(results, "setOp") && !Utils.matchesInArray(results, "isOp")) { list.add("[严重] " + name + " 很有可能存在获取OP类后门 (r:set-only)。"); } if (Utils.matchesInArray(results, "&e[&4Broadcast&e] &2[message]") && !Utils.matchesInArray(results, "com/Zrips/CMI/Modules/CustomText/CTextManager")) { list.add("[严重] " + name + " 很有可能存在获取OP类后门 (r:cmi-exploit)。"); } if (Utils.matchesInArray(results, "java/awt/Robot")) { list.add("[严重] " + name + " 很有可能存在远程控制类后门 (r:awt)。"); } if (Utils.matchesAll(results, "java/lang/ProcessBuilder", "start") || Utils.matchesAll(results, "java/lang/Runtime", "exec")) { list.add("[严重] " + name + " 很有可能存在远程命令类后门 (r:processbuilder)。"); } if (Utils.matchesInArray(results, "%E5%96%B5%E2%99%82%E5%91%9C")) { list.add("[严重] " + name + " 很有可能存在rce (r:yum-core)。"); } if (Utils.matchesInArray(results, "Lnet/md_5/bungee/api/chat/TranslatableComponentDeserializer;")) { list.add("[严重] " + name + " 很有可能被感染 (r:ectasy)。"); } if (Level.level < 1) return; if (Utils.matchesInArray(results, "onEnable") && !Utils.matchesInArray(results, "org/bukkit/plugin/java/JavaPlugin")) { list.add("[严重] " + name + " 有一定可能存在注入类 (r:scalpel)。"); } if (Utils.matchesAny(results, "setOp", "dispatchCommand") && Utils.matchesAny(results, "getName", "org/bukkit/event/player/AsyncPlayerChatEvent", "org/bukkit/event/player/PlayerCommandPreprocessEvent") && Utils.matchesAny(results, "startsWith", "endsWith", "contains", "matches", "equalsIgnoreCase")) { list.add("[中等] " + name + " 有一定可能存在获取OP类后门 (r:gt-name)。"); } if (Utils.matchesAny(results, "java/net/HttpURLConnection", "java/net/URLConnection") && Utils.matchesAny(results, "java/nio/file/Files", "java/io/FileOutputStream")) { list.add("[中等] " + name + " 很有可能存在下载类后门或自动更新 (r:write)。"); } if (Utils.matchesAll(results, "java/util/Iterator", "getOnlinePlayers") && Utils.matchesAny(results, "CREATIVE", "shutdown", "setOp")) { list.add("[中等] " + name + " 有一定可能存在报复类后门 (r:itall-sen)。"); } if (Utils.matchesAll(results, "getConsoleSender", "getName", "equals", "dispatchCommand")) { list.add("[中等] " + name + " 有一定可能存在命令执行类后门 (r:disp)。"); } if (Utils.matchesAll(results, "java/io/File", "setLastModified")) { list.add("[轻微] " + name + " 有一定可能存在注入 (r:modtime)。"); } if (Level.level < 2) return; if (Utils.matchesInArray(results, "setOp")) { list.add("[轻微] " + name + " 有存在获取OP类后门的可能性 (r:set-method-ref)。"); } if (Utils.matchesAll(results, "java/lang/System", "exit")) { list.add("[轻微] " + name + " 不常见的退出服务器方式 (r:exit)。"); } if (Utils.matchesInArray(results, "getMethod")) { list.add("[轻微] " + name + " 不常见的反射 (r:getpubmethod)。"); } if (Utils.containsInArray(results, "java/lang/reflect")) { if (Utils.matchesInArray(results, "setAccessible")) { list.add("[轻微] " + name + " 不常见的反射 (r:noseta)。"); } else { if (!Utils.containsAny(results, "net.minecraft", "org.bukkit")) { list.add("[轻微] " + name + " 不常见的反射 (r:nnms/obc)。"); } } if (Utils.containsInArray(results, "Methods")) { list.add("[轻微] " + name + " 不常见的反射 (r:mul-methods)。"); } } } }
没了!真的!level就是那个设置里面的“普通 专家 开发者”。
——END——