feat(core): update privacy protection rules

This commit is contained in:
zpj80231
2026-06-16 17:18:33 +08:00
parent 1c218010fe
commit a70b9798c6
10 changed files with 530 additions and 107 deletions

View 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);
}
}

View File

@@ -1,13 +1,19 @@
package com.novitechie; package com.novitechie;
import com.janetfilter.core.commons.DebugInfo;
import com.janetfilter.core.plugin.MyTransformer; import com.janetfilter.core.plugin.MyTransformer;
import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter; import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.tree.*; import org.objectweb.asm.tree.*;
import java.util.ListIterator;
import static org.objectweb.asm.Opcodes.*; import static org.objectweb.asm.Opcodes.*;
public class LicensingFacadeTransformer implements MyTransformer { public class LicensingFacadeTransformer implements MyTransformer {
private static final int ASM_VERSION = ASM9;
@Override @Override
public String getHookClassName() { public String getHookClassName() {
return "com/intellij/ui/LicensingFacade"; return "com/intellij/ui/LicensingFacade";
@@ -15,9 +21,14 @@ public class LicensingFacadeTransformer implements MyTransformer {
@Override @Override
public byte[] transform(String className, byte[] classBytes, int order) throws Exception { 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); ClassReader reader = new ClassReader(classBytes);
ClassNode node = new ClassNode(ASM5); ClassNode node = new ClassNode(ASM_VERSION);
reader.accept(node, 0); reader.accept(node, 0);
for (MethodNode m : node.methods) { for (MethodNode m : node.methods) {
if ("getLicenseExpirationDate".equals(m.name)) { if ("getLicenseExpirationDate".equals(m.name)) {
InsnList list = new InsnList(); InsnList list = new InsnList();
@@ -29,10 +40,51 @@ public class LicensingFacadeTransformer implements MyTransformer {
list.add(L0); list.add(L0);
list.add(new InsnNode(POP)); list.add(new InsnNode(POP));
m.instructions.insert(list); m.instructions.insert(list);
} else if ("toJson".equals(m.name) && "()Ljava/lang/String;".equals(m.desc)) {
insertToJsonFilterSecure(m);
} }
} }
ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
ClassWriter writer = new SafeClassWriter(null, null, ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
node.accept(writer); node.accept(writer);
return writer.toByteArray(); 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);
}
}
}
} }

View File

@@ -1,5 +1,6 @@
package com.novitechie; package com.novitechie;
import com.janetfilter.core.commons.DebugInfo;
import com.janetfilter.core.plugin.MyTransformer; import com.janetfilter.core.plugin.MyTransformer;
import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter; import org.objectweb.asm.ClassWriter;
@@ -19,6 +20,7 @@ public class PluginManagerCoreTransformer implements MyTransformer {
@Override @Override
public byte[] transform(String className, byte[] classBytes, int order) throws Exception { public byte[] transform(String className, byte[] classBytes, int order) throws Exception {
try {
ClassReader reader = new ClassReader(classBytes); ClassReader reader = new ClassReader(classBytes);
ClassNode node = new ClassNode(ASM5); ClassNode node = new ClassNode(ASM5);
reader.accept(node, 0); reader.accept(node, 0);
@@ -70,6 +72,24 @@ public class PluginManagerCoreTransformer implements MyTransformer {
list.add(L0); list.add(L0);
m.instructions.insert(list); m.instructions.insert(list);
} else if ("isPluginInstalled".equals(m.name)) { } 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;
}
}
private void insertIsPluginInstalledGuard(MethodNode m) {
InsnList list = new InsnList(); InsnList list = new InsnList();
LabelNode L0 = new LabelNode(); LabelNode L0 = new LabelNode();
list.add(new MethodInsnNode(INVOKESTATIC, "com/novitechie/rules/StackTraceRule", "check", "()Z", false)); list.add(new MethodInsnNode(INVOKESTATIC, "com/novitechie/rules/StackTraceRule", "check", "()Z", false));
@@ -82,7 +102,9 @@ public class PluginManagerCoreTransformer implements MyTransformer {
list.add(new InsnNode(IRETURN)); list.add(new InsnNode(IRETURN));
list.add(L0); list.add(L0);
m.instructions.insert(list); m.instructions.insert(list);
} else if ("isDisabled".equals(m.name)) { }
private void insertIsDisabledGuard(MethodNode m) {
InsnList list = new InsnList(); InsnList list = new InsnList();
LabelNode L0 = new LabelNode(); LabelNode L0 = new LabelNode();
list.add(new MethodInsnNode(INVOKESTATIC, "com/novitechie/rules/StackTraceRule", "check", "()Z", false)); list.add(new MethodInsnNode(INVOKESTATIC, "com/novitechie/rules/StackTraceRule", "check", "()Z", false));
@@ -96,9 +118,4 @@ public class PluginManagerCoreTransformer implements MyTransformer {
list.add(L0); list.add(L0);
m.instructions.insert(list); m.instructions.insert(list);
} }
}
ClassWriter writer = new SafeClassWriter(null, null, ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
node.accept(writer);
return writer.toByteArray();
}
} }

View 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);
}
}
}
}

View File

@@ -55,6 +55,9 @@ public class PrivacyPlugin implements PluginEntry {
new SystemTransformer(), new SystemTransformer(),
new ClassTransformer(), new ClassTransformer(),
new ClassLoaderTransformer(), new ClassLoaderTransformer(),
new MethodTransformer()); new MethodTransformer(),
new JarFileTransformer(),
new PluginManagerTransformer()
);
} }
} }

View File

@@ -19,7 +19,7 @@ public class IdeaPluginRule {
} }
public static boolean check(String name) { public static boolean check(String name) {
if (checkHidePlugin(name)) { if (name == null || name.isEmpty() || checkHidePlugin(name)) {
DebugInfo.output("======================Hide Plugin: " + name); DebugInfo.output("======================Hide Plugin: " + name);
LogUtil.printStackTrace(); LogUtil.printStackTrace();
return true; return true;

View 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;
}
}

View 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);
}
}

View 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;
}
}
}

View File

@@ -2,13 +2,7 @@ package com.novitechie.rules;
import com.janetfilter.core.commons.DebugInfo; import com.janetfilter.core.commons.DebugInfo;
import java.io.BufferedReader; import java.io.*;
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.nio.file.Path; import java.nio.file.Path;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@@ -20,16 +14,17 @@ import java.util.Locale;
public class VMOptionsRule { public class VMOptionsRule {
private static final String VMOPTIONS_SUFFIX = ".vmoptions"; private static final String VMOPTIONS_SUFFIX = ".vmoptions";
private static final String FAKE_VMOPTIONS_FILENAME = "idea64.vmoptions";
public static Path hook() { public static Path hook() {
DebugInfo.output("======================Hide VMOptions"); DebugInfo.output("======================Hide VMOptions");
String tmpdir = System.getProperty("java.io.tmpdir"); 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()) { if (file.exists()) {
return file.toPath(); return file.toPath();
} }
try (FileOutputStream fos = new FileOutputStream(file)) { try (FileOutputStream fos = new FileOutputStream(file)) {
fos.write("# This file is created by plugin-privacy".getBytes()); fos.write("#".getBytes());
return file.toPath(); return file.toPath();
} catch (IOException e) { } catch (IOException e) {
DebugInfo.output("create temp file error", e); DebugInfo.output("create temp file error", e);