diff --git a/src/main/java/com/novitechie/JarFileTransformer.java b/src/main/java/com/novitechie/JarFileTransformer.java new file mode 100644 index 0000000..64b6e1e --- /dev/null +++ b/src/main/java/com/novitechie/JarFileTransformer.java @@ -0,0 +1,60 @@ +package com.novitechie; + +import com.janetfilter.core.plugin.MyTransformer; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.tree.*; + +import static org.objectweb.asm.Opcodes.*; + +public class JarFileTransformer implements MyTransformer { + + @Override + public String getHookClassName() { + return "java/util/jar/JarFile"; + } + + @Override + public byte[] transform(String className, byte[] classBytes, int order) throws Exception { + ClassReader reader = new ClassReader(classBytes); + ClassNode node = new ClassNode(ASM5); + reader.accept(node, 0); + for (MethodNode m : node.methods) { + if (!"".equals(m.name)) { + continue; + } + if ("(Ljava/io/File;)V".equals(m.desc) + || "(Ljava/io/File;Z)V".equals(m.desc) + || "(Ljava/io/File;ZI)V".equals(m.desc) + || "(Ljava/io/File;ZILjava/lang/Runtime$Version;)V".equals(m.desc)) { + insertFileRedirect(m); + } else if ("(Ljava/lang/String;)V".equals(m.desc) + || "(Ljava/lang/String;Z)V".equals(m.desc)) { + insertStringRedirect(m); + } + } + ClassWriter writer = new SafeClassWriter(reader, null, ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS); + node.accept(writer); + return writer.toByteArray(); + } + + private void insertFileRedirect(MethodNode m) { + InsnList list = new InsnList(); + list.add(new VarInsnNode(ALOAD, 1)); + list.add(new MethodInsnNode(INVOKESTATIC, + "com/novitechie/rules/JarFileRule", "redirectFile", + "(Ljava/io/File;)Ljava/io/File;", false)); + list.add(new VarInsnNode(ASTORE, 1)); + m.instructions.insert(list); + } + + private void insertStringRedirect(MethodNode m) { + InsnList list = new InsnList(); + list.add(new VarInsnNode(ALOAD, 1)); + list.add(new MethodInsnNode(INVOKESTATIC, + "com/novitechie/rules/JarFileRule", "redirectPath", + "(Ljava/lang/String;)Ljava/lang/String;", false)); + list.add(new VarInsnNode(ASTORE, 1)); + m.instructions.insert(list); + } +} diff --git a/src/main/java/com/novitechie/LicensingFacadeTransformer.java b/src/main/java/com/novitechie/LicensingFacadeTransformer.java index a0a36ce..5d1408c 100644 --- a/src/main/java/com/novitechie/LicensingFacadeTransformer.java +++ b/src/main/java/com/novitechie/LicensingFacadeTransformer.java @@ -1,13 +1,19 @@ package com.novitechie; +import com.janetfilter.core.commons.DebugInfo; import com.janetfilter.core.plugin.MyTransformer; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.tree.*; +import java.util.ListIterator; + import static org.objectweb.asm.Opcodes.*; public class LicensingFacadeTransformer implements MyTransformer { + + private static final int ASM_VERSION = ASM9; + @Override public String getHookClassName() { return "com/intellij/ui/LicensingFacade"; @@ -15,24 +21,70 @@ public class LicensingFacadeTransformer implements MyTransformer { @Override public byte[] transform(String className, byte[] classBytes, int order) throws Exception { - ClassReader reader = new ClassReader(classBytes); - ClassNode node = new ClassNode(ASM5); - reader.accept(node, 0); - for (MethodNode m : node.methods) { - if ("getLicenseExpirationDate".equals(m.name)) { - InsnList list = new InsnList(); - LabelNode L0 = new LabelNode(); - list.add(new MethodInsnNode(INVOKESTATIC, "com/novitechie/rules/StackTraceRule", "hook", "()Ljava/util/Date;", false)); - list.add(new InsnNode(DUP)); - list.add(new JumpInsnNode(IFNULL, L0)); - list.add(new InsnNode(ARETURN)); - list.add(L0); - list.add(new InsnNode(POP)); - m.instructions.insert(list); + if (classBytes == null || classBytes.length == 0) { + return classBytes; + } + try { + ClassReader reader = new ClassReader(classBytes); + ClassNode node = new ClassNode(ASM_VERSION); + reader.accept(node, 0); + + for (MethodNode m : node.methods) { + if ("getLicenseExpirationDate".equals(m.name)) { + InsnList list = new InsnList(); + LabelNode L0 = new LabelNode(); + list.add(new MethodInsnNode(INVOKESTATIC, "com/novitechie/rules/StackTraceRule", "hook", "()Ljava/util/Date;", false)); + list.add(new InsnNode(DUP)); + list.add(new JumpInsnNode(IFNULL, L0)); + list.add(new InsnNode(ARETURN)); + list.add(L0); + list.add(new InsnNode(POP)); + m.instructions.insert(list); + } else if ("toJson".equals(m.name) && "()Ljava/lang/String;".equals(m.desc)) { + insertToJsonFilterSecure(m); + } + } + + ClassWriter writer = new SafeClassWriter(null, null, ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS); + node.accept(writer); + return writer.toByteArray(); + } catch (Exception e) { + DebugInfo.warn("[PRIVACY] Failed to transform LicensingFacade, skipping", e); + return classBytes; + } + } + + private void insertToJsonFilterSecure(MethodNode m) { + if (m.instructions == null || m.instructions.size() == 0) { + return; + } + + int originalJsonIdx = m.maxLocals; + int filteredJsonIdx = originalJsonIdx + 1; + m.maxLocals += 2; + + ListIterator iterator = m.instructions.iterator(); + while (iterator.hasNext()) { + AbstractInsnNode current = iterator.next(); + + if (current.getOpcode() == ARETURN) { + LabelNode useOriginalLabel = new LabelNode(); + LabelNode endLabel = new LabelNode(); + InsnList patch = new InsnList(); + patch.add(new VarInsnNode(ASTORE, originalJsonIdx)); + patch.add(new VarInsnNode(ALOAD, originalJsonIdx)); + patch.add(new MethodInsnNode(INVOKESTATIC, "com/novitechie/rules/LicenseRule", "filterJson", "(Ljava/lang/String;)Ljava/lang/String;", false)); + patch.add(new VarInsnNode(ASTORE, filteredJsonIdx)); + patch.add(new VarInsnNode(ALOAD, filteredJsonIdx)); + patch.add(new JumpInsnNode(IFNULL, useOriginalLabel)); + patch.add(new VarInsnNode(ALOAD, filteredJsonIdx)); + patch.add(new JumpInsnNode(GOTO, endLabel)); + patch.add(useOriginalLabel); + patch.add(new VarInsnNode(ALOAD, originalJsonIdx)); + patch.add(endLabel); + m.instructions.insertBefore(current, patch); } } - ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS); - node.accept(writer); - return writer.toByteArray(); } + } diff --git a/src/main/java/com/novitechie/PluginManagerCoreTransformer.java b/src/main/java/com/novitechie/PluginManagerCoreTransformer.java index 9809c32..a54400a 100644 --- a/src/main/java/com/novitechie/PluginManagerCoreTransformer.java +++ b/src/main/java/com/novitechie/PluginManagerCoreTransformer.java @@ -1,5 +1,6 @@ package com.novitechie; +import com.janetfilter.core.commons.DebugInfo; import com.janetfilter.core.plugin.MyTransformer; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassWriter; @@ -19,86 +20,102 @@ public class PluginManagerCoreTransformer implements MyTransformer { @Override public byte[] transform(String className, byte[] classBytes, int order) throws Exception { - ClassReader reader = new ClassReader(classBytes); - ClassNode node = new ClassNode(ASM5); - reader.accept(node, 0); - for (MethodNode m : node.methods) { - if ("getPlugins".equals(m.name)) { - InsnList list = new InsnList(); - LabelNode L0 = new LabelNode(); - LabelNode L1 = new LabelNode(); - LabelNode L2 = new LabelNode(); - LabelNode L3 = new LabelNode(); - list.add(new MethodInsnNode(INVOKESTATIC, "com/novitechie/rules/StackTraceRule", "check", "()Z", false)); - list.add(new JumpInsnNode(IFEQ, L0)); - list.add(new FieldInsnNode(GETSTATIC, "com/intellij/ide/plugins/PluginManagerCore", "nullablePluginSet", "Lcom/intellij/ide/plugins/PluginSet;")); - list.add(new FieldInsnNode(GETFIELD, "com/intellij/ide/plugins/PluginSet", "allPlugins", "Ljava/util/Set;")); - list.add(new VarInsnNode(ASTORE, 0)); - list.add(new TypeInsnNode(NEW, "java/util/HashSet")); - list.add(new InsnNode(DUP)); - list.add(new MethodInsnNode(INVOKESPECIAL, "java/util/HashSet", "", "()V", false)); - list.add(new VarInsnNode(ASTORE, 1)); - list.add(new VarInsnNode(ALOAD, 0)); - list.add(new MethodInsnNode(INVOKEINTERFACE, "java/util/Set", "iterator", "()Ljava/util/Iterator;", true)); - list.add(new VarInsnNode(ASTORE, 2)); - list.add(L2); - list.add(new VarInsnNode(ALOAD, 2)); - list.add(new MethodInsnNode(INVOKEINTERFACE, "java/util/Iterator", "hasNext", "()Z", true)); - list.add(new JumpInsnNode(IFEQ, L1)); - list.add(new VarInsnNode(ALOAD, 2)); - list.add(new MethodInsnNode(INVOKEINTERFACE, "java/util/Iterator", "next", "()Ljava/lang/Object;", true)); - list.add(new TypeInsnNode(CHECKCAST, "com/intellij/ide/plugins/IdeaPluginDescriptor")); - list.add(new VarInsnNode(ASTORE, 3)); - list.add(new VarInsnNode(ALOAD, 3)); - list.add(new MethodInsnNode(INVOKEINTERFACE, "com/intellij/ide/plugins/IdeaPluginDescriptor", "getPluginId", "()Lcom/intellij/openapi/extensions/PluginId;", true)); - list.add(new MethodInsnNode(INVOKEVIRTUAL, "com/intellij/openapi/extensions/PluginId", "getIdString", "()Ljava/lang/String;", false)); - list.add(new MethodInsnNode(INVOKESTATIC, "com/novitechie/rules/IdeaPluginRule", "check", "(Ljava/lang/String;)Z", false)); - list.add(new JumpInsnNode(IFNE, L3)); - list.add(new VarInsnNode(ALOAD, 1)); - list.add(new VarInsnNode(ALOAD, 3)); - list.add(new MethodInsnNode(INVOKEINTERFACE, "java/util/Set", "add", "(Ljava/lang/Object;)Z", true)); - list.add(new InsnNode(POP)); - list.add(L3); - list.add(new JumpInsnNode(GOTO, L2)); - list.add(L1); - list.add(new VarInsnNode(ALOAD, 1)); - list.add(new InsnNode(ICONST_0)); - list.add(new TypeInsnNode(ANEWARRAY, "com/intellij/ide/plugins/IdeaPluginDescriptor")); - list.add(new MethodInsnNode(INVOKEINTERFACE, "java/util/Set", "toArray", "([Ljava/lang/Object;)[Ljava/lang/Object;", true)); - list.add(new TypeInsnNode(CHECKCAST, "[Lcom/intellij/ide/plugins/IdeaPluginDescriptor;")); - list.add(new InsnNode(ARETURN)); - list.add(L0); - m.instructions.insert(list); - } else if ("isPluginInstalled".equals(m.name)) { - InsnList list = new InsnList(); - LabelNode L0 = new LabelNode(); - list.add(new MethodInsnNode(INVOKESTATIC, "com/novitechie/rules/StackTraceRule", "check", "()Z", false)); - list.add(new JumpInsnNode(IFEQ, L0)); - list.add(new VarInsnNode(ALOAD, 0)); - list.add(new MethodInsnNode(INVOKEVIRTUAL, "com/intellij/openapi/extensions/PluginId", "getIdString", "()Ljava/lang/String;", false)); - list.add(new MethodInsnNode(INVOKESTATIC, "com/novitechie/rules/IdeaPluginRule", "check", "(Ljava/lang/String;)Z", false)); - list.add(new JumpInsnNode(IFEQ, L0)); - list.add(new InsnNode(ICONST_0)); - list.add(new InsnNode(IRETURN)); - list.add(L0); - m.instructions.insert(list); - } else if ("isDisabled".equals(m.name)) { - InsnList list = new InsnList(); - LabelNode L0 = new LabelNode(); - list.add(new MethodInsnNode(INVOKESTATIC, "com/novitechie/rules/StackTraceRule", "check", "()Z", false)); - list.add(new JumpInsnNode(IFEQ, L0)); - list.add(new VarInsnNode(ALOAD, 0)); - list.add(new MethodInsnNode(INVOKEVIRTUAL, "com/intellij/openapi/extensions/PluginId", "getIdString", "()Ljava/lang/String;", false)); - list.add(new MethodInsnNode(INVOKESTATIC, "com/novitechie/rules/IdeaPluginRule", "check", "(Ljava/lang/String;)Z", false)); - list.add(new JumpInsnNode(IFEQ, L0)); - list.add(new InsnNode(ICONST_1)); - list.add(new InsnNode(IRETURN)); - list.add(L0); - m.instructions.insert(list); + try { + ClassReader reader = new ClassReader(classBytes); + ClassNode node = new ClassNode(ASM5); + reader.accept(node, 0); + for (MethodNode m : node.methods) { + if ("getPlugins".equals(m.name)) { + InsnList list = new InsnList(); + LabelNode L0 = new LabelNode(); + LabelNode L1 = new LabelNode(); + LabelNode L2 = new LabelNode(); + LabelNode L3 = new LabelNode(); + list.add(new MethodInsnNode(INVOKESTATIC, "com/novitechie/rules/StackTraceRule", "check", "()Z", false)); + list.add(new JumpInsnNode(IFEQ, L0)); + list.add(new FieldInsnNode(GETSTATIC, "com/intellij/ide/plugins/PluginManagerCore", "nullablePluginSet", "Lcom/intellij/ide/plugins/PluginSet;")); + list.add(new FieldInsnNode(GETFIELD, "com/intellij/ide/plugins/PluginSet", "allPlugins", "Ljava/util/Set;")); + list.add(new VarInsnNode(ASTORE, 0)); + list.add(new TypeInsnNode(NEW, "java/util/HashSet")); + list.add(new InsnNode(DUP)); + list.add(new MethodInsnNode(INVOKESPECIAL, "java/util/HashSet", "", "()V", false)); + list.add(new VarInsnNode(ASTORE, 1)); + list.add(new VarInsnNode(ALOAD, 0)); + list.add(new MethodInsnNode(INVOKEINTERFACE, "java/util/Set", "iterator", "()Ljava/util/Iterator;", true)); + list.add(new VarInsnNode(ASTORE, 2)); + list.add(L2); + list.add(new VarInsnNode(ALOAD, 2)); + list.add(new MethodInsnNode(INVOKEINTERFACE, "java/util/Iterator", "hasNext", "()Z", true)); + list.add(new JumpInsnNode(IFEQ, L1)); + list.add(new VarInsnNode(ALOAD, 2)); + list.add(new MethodInsnNode(INVOKEINTERFACE, "java/util/Iterator", "next", "()Ljava/lang/Object;", true)); + list.add(new TypeInsnNode(CHECKCAST, "com/intellij/ide/plugins/IdeaPluginDescriptor")); + list.add(new VarInsnNode(ASTORE, 3)); + list.add(new VarInsnNode(ALOAD, 3)); + list.add(new MethodInsnNode(INVOKEINTERFACE, "com/intellij/ide/plugins/IdeaPluginDescriptor", "getPluginId", "()Lcom/intellij/openapi/extensions/PluginId;", true)); + list.add(new MethodInsnNode(INVOKEVIRTUAL, "com/intellij/openapi/extensions/PluginId", "getIdString", "()Ljava/lang/String;", false)); + list.add(new MethodInsnNode(INVOKESTATIC, "com/novitechie/rules/IdeaPluginRule", "check", "(Ljava/lang/String;)Z", false)); + list.add(new JumpInsnNode(IFNE, L3)); + list.add(new VarInsnNode(ALOAD, 1)); + list.add(new VarInsnNode(ALOAD, 3)); + list.add(new MethodInsnNode(INVOKEINTERFACE, "java/util/Set", "add", "(Ljava/lang/Object;)Z", true)); + list.add(new InsnNode(POP)); + list.add(L3); + list.add(new JumpInsnNode(GOTO, L2)); + list.add(L1); + list.add(new VarInsnNode(ALOAD, 1)); + list.add(new InsnNode(ICONST_0)); + list.add(new TypeInsnNode(ANEWARRAY, "com/intellij/ide/plugins/IdeaPluginDescriptor")); + list.add(new MethodInsnNode(INVOKEINTERFACE, "java/util/Set", "toArray", "([Ljava/lang/Object;)[Ljava/lang/Object;", true)); + list.add(new TypeInsnNode(CHECKCAST, "[Lcom/intellij/ide/plugins/IdeaPluginDescriptor;")); + list.add(new InsnNode(ARETURN)); + list.add(L0); + m.instructions.insert(list); + } else if ("isPluginInstalled".equals(m.name)) { + DebugInfo.output("[PRIVACY] Inserting isPluginInstalled guard"); + insertIsPluginInstalledGuard(m); + } else if ("isDisabled".equals(m.name)) { + DebugInfo.output("[PRIVACY] Inserting isDisabled guard"); + insertIsDisabledGuard(m); + } } + ClassWriter writer = new SafeClassWriter(null, null, ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS); + node.accept(writer); + DebugInfo.output("[PRIVACY] PluginManagerCore transform success"); + return writer.toByteArray(); + } catch (Exception e) { + DebugInfo.warn("[PRIVACY] Failed to transform PluginManagerCore, skipping", e); + return classBytes; } - ClassWriter writer = new SafeClassWriter(null, null, ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS); - node.accept(writer); - return writer.toByteArray(); + } + + private void insertIsPluginInstalledGuard(MethodNode m) { + InsnList list = new InsnList(); + LabelNode L0 = new LabelNode(); + list.add(new MethodInsnNode(INVOKESTATIC, "com/novitechie/rules/StackTraceRule", "check", "()Z", false)); + list.add(new JumpInsnNode(IFEQ, L0)); + list.add(new VarInsnNode(ALOAD, 0)); + list.add(new MethodInsnNode(INVOKEVIRTUAL, "com/intellij/openapi/extensions/PluginId", "getIdString", "()Ljava/lang/String;", false)); + list.add(new MethodInsnNode(INVOKESTATIC, "com/novitechie/rules/IdeaPluginRule", "check", "(Ljava/lang/String;)Z", false)); + list.add(new JumpInsnNode(IFEQ, L0)); + list.add(new InsnNode(ICONST_0)); + list.add(new InsnNode(IRETURN)); + list.add(L0); + m.instructions.insert(list); + } + + private void insertIsDisabledGuard(MethodNode m) { + InsnList list = new InsnList(); + LabelNode L0 = new LabelNode(); + list.add(new MethodInsnNode(INVOKESTATIC, "com/novitechie/rules/StackTraceRule", "check", "()Z", false)); + list.add(new JumpInsnNode(IFEQ, L0)); + list.add(new VarInsnNode(ALOAD, 0)); + list.add(new MethodInsnNode(INVOKEVIRTUAL, "com/intellij/openapi/extensions/PluginId", "getIdString", "()Ljava/lang/String;", false)); + list.add(new MethodInsnNode(INVOKESTATIC, "com/novitechie/rules/IdeaPluginRule", "check", "(Ljava/lang/String;)Z", false)); + list.add(new JumpInsnNode(IFEQ, L0)); + list.add(new InsnNode(ICONST_1)); + list.add(new InsnNode(IRETURN)); + list.add(L0); + m.instructions.insert(list); } } diff --git a/src/main/java/com/novitechie/PluginManagerTransformer.java b/src/main/java/com/novitechie/PluginManagerTransformer.java new file mode 100644 index 0000000..cd3630c --- /dev/null +++ b/src/main/java/com/novitechie/PluginManagerTransformer.java @@ -0,0 +1,87 @@ +package com.novitechie; + +import com.janetfilter.core.commons.DebugInfo; +import com.janetfilter.core.plugin.MyTransformer; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.tree.*; + +import java.util.ListIterator; + +import static org.objectweb.asm.Opcodes.*; + +public class PluginManagerTransformer implements MyTransformer { + + private static final int ASM_VERSION = ASM5; + + @Override + public String getHookClassName() { + return "com/intellij/ide/plugins/PluginManager"; + } + + @Override + public byte[] transform(String className, byte[] classBytes, int order) throws Exception { + if (classBytes == null || classBytes.length == 0) { + return classBytes; + } + + try { + ClassReader reader = new ClassReader(classBytes); + ClassNode node = new ClassNode(ASM_VERSION); + reader.accept(node, 0); + + boolean isModified = false; + for (MethodNode m : node.methods) { + if ("getLoadedPlugins".equals(m.name) && "()Ljava/util/List;".equals(m.desc)) { + insertGetLoadedPluginsFilter(m); + isModified = true; + } + } + + if (!isModified) { + return classBytes; + } + + ClassWriter writer = new SafeClassWriter(null, null, ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS); + node.accept(writer); + return writer.toByteArray(); + } catch (Exception e) { + DebugInfo.warn("[PRIVACY] Failed to transform PluginManager, skipping", e); + return classBytes; + } + } + + private void insertGetLoadedPluginsFilter(MethodNode m) { + if (m.instructions == null || m.instructions.size() == 0) { + return; + } + + int originalListIdx = m.maxLocals; + int filteredListIdx = originalListIdx + 1; + m.maxLocals += 2; + + ListIterator iterator = m.instructions.iterator(); + while (iterator.hasNext()) { + AbstractInsnNode current = iterator.next(); + if (current.getOpcode() == ARETURN) { + LabelNode useOriginalLabel = new LabelNode(); + LabelNode endLabel = new LabelNode(); + InsnList patch = new InsnList(); + patch.add(new VarInsnNode(ASTORE, originalListIdx)); + patch.add(new MethodInsnNode(INVOKESTATIC, "com/novitechie/rules/StackTraceRule", "check", "()Z", false)); + patch.add(new JumpInsnNode(IFEQ, useOriginalLabel)); + patch.add(new VarInsnNode(ALOAD, originalListIdx)); + patch.add(new MethodInsnNode(INVOKESTATIC, "com/novitechie/rules/PluginListRule", "filter", "(Ljava/util/List;)Ljava/util/List;", false)); + patch.add(new VarInsnNode(ASTORE, filteredListIdx)); + patch.add(new VarInsnNode(ALOAD, filteredListIdx)); + patch.add(new JumpInsnNode(IFNULL, useOriginalLabel)); + patch.add(new VarInsnNode(ALOAD, filteredListIdx)); + patch.add(new JumpInsnNode(GOTO, endLabel)); + patch.add(useOriginalLabel); + patch.add(new VarInsnNode(ALOAD, originalListIdx)); + patch.add(endLabel); + m.instructions.insertBefore(current, patch); + } + } + } +} \ No newline at end of file diff --git a/src/main/java/com/novitechie/PrivacyPlugin.java b/src/main/java/com/novitechie/PrivacyPlugin.java index a0e008d..b8d1be0 100644 --- a/src/main/java/com/novitechie/PrivacyPlugin.java +++ b/src/main/java/com/novitechie/PrivacyPlugin.java @@ -55,6 +55,9 @@ public class PrivacyPlugin implements PluginEntry { new SystemTransformer(), new ClassTransformer(), new ClassLoaderTransformer(), - new MethodTransformer()); + new MethodTransformer(), + new JarFileTransformer(), + new PluginManagerTransformer() + ); } } diff --git a/src/main/java/com/novitechie/rules/IdeaPluginRule.java b/src/main/java/com/novitechie/rules/IdeaPluginRule.java index ce3f861..3898e4e 100644 --- a/src/main/java/com/novitechie/rules/IdeaPluginRule.java +++ b/src/main/java/com/novitechie/rules/IdeaPluginRule.java @@ -19,7 +19,7 @@ public class IdeaPluginRule { } public static boolean check(String name) { - if (checkHidePlugin(name)) { + if (name == null || name.isEmpty() || checkHidePlugin(name)) { DebugInfo.output("======================Hide Plugin: " + name); LogUtil.printStackTrace(); return true; diff --git a/src/main/java/com/novitechie/rules/JarFileRule.java b/src/main/java/com/novitechie/rules/JarFileRule.java new file mode 100644 index 0000000..62caa46 --- /dev/null +++ b/src/main/java/com/novitechie/rules/JarFileRule.java @@ -0,0 +1,75 @@ +package com.novitechie.rules; + +import com.janetfilter.core.commons.DebugInfo; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; + +public class JarFileRule { + + private static final String[] BLACKLISTED_CLASS_PREFIXES = { + "com/janetfilter/core/Launcher.class", + "com/novitechie/PrivacyPlugin.class", + "com/thirdpart/janetfilter/core/Launcher.class" + }; + + private static final String[] AGENT_JAR_INDICATORS = { + "janetfilter", + "ja-netfilter" + }; + + private JarFileRule() { + } + + public static File redirectFile(File file) { + if (file == null || !StackTraceRule.check()) { + return file; + } + String path = file.getPath().toLowerCase(); + if (!isAgentJar(path)) { + return file; + } + File emptyJar = getEmptyJar(); + return emptyJar != null ? emptyJar : file; + } + + public static String redirectPath(String path) { + if (path == null || !StackTraceRule.check()) { + return path; + } + String lowerPath = path.toLowerCase(); + if (!isAgentJar(lowerPath)) { + return path; + } + File emptyJar = getEmptyJar(); + return emptyJar != null ? emptyJar.getPath() : path; + } + + private static boolean isAgentJar(String lowerPath) { + for (String indicator : AGENT_JAR_INDICATORS) { + if (lowerPath.contains(indicator)) { + return true; + } + } + return false; + } + + private static File getEmptyJar() { + String tmpdir = System.getProperty("java.io.tmpdir"); + File file = new File(tmpdir, "empty.jar"); + if (file.exists()) { + return file; + } + try (JarOutputStream jos = new JarOutputStream(new FileOutputStream(file), new Manifest())) { + // 仅写入空的 MANIFEST + } catch (IOException e) { + DebugInfo.output("[PRIVACY-JAR] Create empty jar failed", e); + return null; + } + return file; + } + +} diff --git a/src/main/java/com/novitechie/rules/LicenseRule.java b/src/main/java/com/novitechie/rules/LicenseRule.java new file mode 100644 index 0000000..ad1eef9 --- /dev/null +++ b/src/main/java/com/novitechie/rules/LicenseRule.java @@ -0,0 +1,92 @@ +package com.novitechie.rules; + +import com.janetfilter.core.commons.DebugInfo; +import java.util.concurrent.ThreadLocalRandom; + +public class LicenseRule { + + private static final char[] ALPHABET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray(); + private static final int RANDOM_STR_LEN = 6; + + private LicenseRule() { + } + + public static String filterJson(String json) { + if (json == null || json.isEmpty()) { + return json; + } + + try { + String filtered = json; + + filtered = fastFilterField(filtered, "licenseeName"); + filtered = fastFilterField(filtered, "licenseId"); + filtered = fastFilterField(filtered, "licensedTo"); + filtered = fastFilterField(filtered, "licenseeEmail"); + filtered = fastFilterField(filtered, "metadata"); + filtered = fastFilterField(filtered, "fusMetadata"); + + if (!filtered.equals(json)) { + DebugInfo.warn("[PRIVACY-LICENSE] Anti-Blacklist: Sensitive license fields have been randomized."); + } + + return filtered; + } catch (Exception e) { + DebugInfo.warn("[PRIVACY-LICENSE] Error text filtering skipped", e); + return json; + } + } + + private static String fastFilterField(String json, String field) { + String searchKey = "\"" + field + "\""; + int keyIdx = json.indexOf(searchKey); + if (keyIdx == -1) { + return json; + } + + int colonIdx = json.indexOf(":", keyIdx + searchKey.length()); + if (colonIdx == -1) { + return json; + } + + int startQuoteIdx = json.indexOf("\"", colonIdx + 1); + if (startQuoteIdx == -1) { + return json; + } + + int endQuoteIdx = -1; + int jsonLen = json.length(); + for (int i = startQuoteIdx + 1; i < jsonLen; i++) { + char c = json.charAt(i); + if (c == '"') { + if (json.charAt(i - 1) != '\\') { + endQuoteIdx = i; + break; + } + } + } + + if (endQuoteIdx == -1) { + return json; + } + + String originalValue = json.substring(startQuoteIdx + 1, endQuoteIdx); + + if (originalValue.contains("idea-set") || "licensedTo".equals(field)) { + String randomNonce = generateRandomString(RANDOM_STR_LEN); + + return json.substring(0, startQuoteIdx + 1) + randomNonce + json.substring(endQuoteIdx); + } + + return json; + } + + private static String generateRandomString(int length) { + char[] buffer = new char[length]; + ThreadLocalRandom random = ThreadLocalRandom.current(); + for (int i = 0; i < length; i++) { + buffer[i] = ALPHABET[random.nextInt(ALPHABET.length)]; + } + return new String(buffer); + } +} \ No newline at end of file diff --git a/src/main/java/com/novitechie/rules/PluginListRule.java b/src/main/java/com/novitechie/rules/PluginListRule.java new file mode 100644 index 0000000..cafb3d7 --- /dev/null +++ b/src/main/java/com/novitechie/rules/PluginListRule.java @@ -0,0 +1,42 @@ +package com.novitechie.rules; + +import java.util.ArrayList; +import java.util.List; + +public class PluginListRule { + + private PluginListRule() { + } + + public static List filter(List list) { + if (list == null || list.isEmpty() || !StackTraceRule.check()) { + return null; + } + List filtered = new ArrayList(list.size()); + boolean changed = false; + for (Object item : list) { + String pluginId = getPluginId(item); + if (pluginId != null && IdeaPluginRule.check(pluginId)) { + changed = true; + } else { + filtered.add(item); + } + } + + return changed ? filtered : null; + } + + private static String getPluginId(Object descriptor) { + try { + java.lang.reflect.Method getPluginId = descriptor.getClass().getMethod("getPluginId"); + Object pluginId = getPluginId.invoke(descriptor); + if (pluginId == null) { + return null; + } + java.lang.reflect.Method getIdString = pluginId.getClass().getMethod("getIdString"); + return (String) getIdString.invoke(pluginId); + } catch (Exception e) { + return null; + } + } +} diff --git a/src/main/java/com/novitechie/rules/VMOptionsRule.java b/src/main/java/com/novitechie/rules/VMOptionsRule.java index a8b7572..a1f9fa5 100644 --- a/src/main/java/com/novitechie/rules/VMOptionsRule.java +++ b/src/main/java/com/novitechie/rules/VMOptionsRule.java @@ -2,13 +2,7 @@ package com.novitechie.rules; import com.janetfilter.core.commons.DebugInfo; -import java.io.BufferedReader; -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.StringReader; +import java.io.*; import java.nio.file.Path; import java.util.Collections; import java.util.List; @@ -20,16 +14,17 @@ import java.util.Locale; public class VMOptionsRule { private static final String VMOPTIONS_SUFFIX = ".vmoptions"; + private static final String FAKE_VMOPTIONS_FILENAME = "idea64.vmoptions"; public static Path hook() { DebugInfo.output("======================Hide VMOptions"); String tmpdir = System.getProperty("java.io.tmpdir"); - File file = new File(tmpdir, "fake.vmoptions"); + File file = new File(tmpdir, FAKE_VMOPTIONS_FILENAME); if (file.exists()) { return file.toPath(); } try (FileOutputStream fos = new FileOutputStream(file)) { - fos.write("# This file is created by plugin-privacy".getBytes()); + fos.write("#".getBytes()); return file.toPath(); } catch (IOException e) { DebugInfo.output("create temp file error", e);