feat(core): update privacy protection rules
This commit is contained in:
60
src/main/java/com/novitechie/JarFileTransformer.java
Normal file
60
src/main/java/com/novitechie/JarFileTransformer.java
Normal file
@@ -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 (!"<init>".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);
|
||||
}
|
||||
}
|
||||
@@ -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<AbstractInsnNode> 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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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", "<init>", "()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", "<init>", "()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);
|
||||
}
|
||||
}
|
||||
|
||||
87
src/main/java/com/novitechie/PluginManagerTransformer.java
Normal file
87
src/main/java/com/novitechie/PluginManagerTransformer.java
Normal file
@@ -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<AbstractInsnNode> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -55,6 +55,9 @@ public class PrivacyPlugin implements PluginEntry {
|
||||
new SystemTransformer(),
|
||||
new ClassTransformer(),
|
||||
new ClassLoaderTransformer(),
|
||||
new MethodTransformer());
|
||||
new MethodTransformer(),
|
||||
new JarFileTransformer(),
|
||||
new PluginManagerTransformer()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
75
src/main/java/com/novitechie/rules/JarFileRule.java
Normal file
75
src/main/java/com/novitechie/rules/JarFileRule.java
Normal file
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
92
src/main/java/com/novitechie/rules/LicenseRule.java
Normal file
92
src/main/java/com/novitechie/rules/LicenseRule.java
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
42
src/main/java/com/novitechie/rules/PluginListRule.java
Normal file
42
src/main/java/com/novitechie/rules/PluginListRule.java
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user