diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5c32aa1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.iml +.idea/ +.DS_Store +target/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..1042685 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# ja-netfilter + +### A javaagent lib for network filter \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..f5c6388 --- /dev/null +++ b/pom.xml @@ -0,0 +1,72 @@ + + + 4.0.0 + + io.zhile.research + ja-netfilter + 1.0.0 + + + UTF-8 + + + + ja-netfilter + + + org.apache.maven.plugins + maven-compiler-plugin + 3.7.0 + + 8 + 8 + UTF-8 + -XDignore.symbol.file + true + + + + org.apache.maven.plugins + maven-jar-plugin + 3.1.0 + + + false + + + + + org.apache.maven.plugins + maven-assembly-plugin + 3.0.0 + + + + true + + + io.zhile.research.ja.netfilter.Launcher + io.zhile.research.ja.netfilter.Launcher + true + true + + + + jar-with-dependencies + + + + + make-assembly + package + + single + + + + + + + diff --git a/src/main/java/io/zhile/research/ja/netfilter/Launcher.java b/src/main/java/io/zhile/research/ja/netfilter/Launcher.java new file mode 100644 index 0000000..89cfdf3 --- /dev/null +++ b/src/main/java/io/zhile/research/ja/netfilter/Launcher.java @@ -0,0 +1,77 @@ +package io.zhile.research.ja.netfilter; + +import java.lang.instrument.Instrumentation; +import java.lang.instrument.UnmodifiableClassException; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.jar.JarFile; + +public class Launcher { + public static void main(String[] args) { + printUsage(); + } + + public static void premain(String args, Instrumentation inst) { + printUsage(); + + URL jarURL = getJarURL(); + if (null == jarURL) { + throw new RuntimeException("Can not locate ja-netfilter jar file."); + } + + try { + inst.appendToBootstrapClassLoaderSearch(new JarFile(jarURL.getPath())); + } catch (Throwable e) { + throw new RuntimeException("Can not access ja-netfilter jar file.", e); + } + + for (Class c : inst.getAllLoadedClasses()) { + try { + inst.retransformClasses(c); + } catch (UnmodifiableClassException e) { + // ok, ok. just ignore + } + } + + inst.addTransformer(new TransformDispatcher(), true); + } + + private static void printUsage() { + String content = "\n ============================================================================ \n" + + "\n" + + " A javaagent lib for network filter :)\n" + + "\n" + + " https://github.com/pengzhile/ja-netfilter\n" + + "\n" + + " ============================================================================ \n\n"; + + System.out.print(content); + System.out.flush(); + } + + private static URL getJarURL() { + URL url = Launcher.class.getProtectionDomain().getCodeSource().getLocation(); + if (null != url) { + return url; + } + + String resourcePath = "/442fcf28466515a81d5434931496ffa64611cc8e.txt"; + url = Launcher.class.getResource(resourcePath); + if (null == url) { + return null; + } + + String path = url.getPath(); + if (!path.endsWith("!" + resourcePath)) { + return null; + } + + path = path.substring(0, path.length() - resourcePath.length() - 1); + + try { + return new URL(path); + } catch (MalformedURLException e) { + return null; + } + } +} diff --git a/src/main/java/io/zhile/research/ja/netfilter/TransformDispatcher.java b/src/main/java/io/zhile/research/ja/netfilter/TransformDispatcher.java new file mode 100644 index 0000000..d6511e3 --- /dev/null +++ b/src/main/java/io/zhile/research/ja/netfilter/TransformDispatcher.java @@ -0,0 +1,42 @@ +package io.zhile.research.ja.netfilter; + +import io.zhile.research.ja.netfilter.transformers.HttpClientTransformer; +import io.zhile.research.ja.netfilter.transformers.InetAddressTransformer; +import io.zhile.research.ja.netfilter.transformers.MyTransformer; + +import java.lang.instrument.ClassFileTransformer; +import java.lang.instrument.IllegalClassFormatException; +import java.security.ProtectionDomain; +import java.util.HashMap; +import java.util.Map; + +public class TransformDispatcher implements ClassFileTransformer { + public static final Map TRANSFORMER_MAP; + + static { + TRANSFORMER_MAP = new HashMap<>(); + TRANSFORMER_MAP.put("sun/net/www/http/HttpClient", new HttpClientTransformer()); + TRANSFORMER_MAP.put("java/net/InetAddress", new InetAddressTransformer()); + } + + public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classFileBuffer) throws IllegalClassFormatException { + do { + if (null == className) { + break; + } + + MyTransformer transformer = TRANSFORMER_MAP.get(className); + if (null == transformer) { + break; + } + + try { + return transformer.transform(className, classFileBuffer); + } catch (Exception e) { + System.out.println("=== Transform class failed: " + e.getMessage()); + } + } while (false); + + return classFileBuffer; + } +} diff --git a/src/main/java/io/zhile/research/ja/netfilter/enums/RuleType.java b/src/main/java/io/zhile/research/ja/netfilter/enums/RuleType.java new file mode 100644 index 0000000..5ecc055 --- /dev/null +++ b/src/main/java/io/zhile/research/ja/netfilter/enums/RuleType.java @@ -0,0 +1,9 @@ +package io.zhile.research.ja.netfilter.enums; + +public enum RuleType { + PREFIX, + SUFFIX, + KEYWORD, + REGEXP, + EQUAL +} diff --git a/src/main/java/io/zhile/research/ja/netfilter/filters/DNSFilter.java b/src/main/java/io/zhile/research/ja/netfilter/filters/DNSFilter.java new file mode 100644 index 0000000..88f711c --- /dev/null +++ b/src/main/java/io/zhile/research/ja/netfilter/filters/DNSFilter.java @@ -0,0 +1,58 @@ +package io.zhile.research.ja.netfilter.filters; + +import io.zhile.research.ja.netfilter.enums.RuleType; +import io.zhile.research.ja.netfilter.models.FilterRule; + +import java.io.IOException; +import java.net.InetAddress; +import java.util.ArrayList; +import java.util.List; + +public class DNSFilter { + public static final List RULES; + + static { + RULES = new ArrayList<>(); // TODO read from config file + RULES.add(new FilterRule(RuleType.EQUAL, "zhile.io")); + } + + public static String testQuery(String host) throws IOException { + if (null == host) { + return null; + } + + for (FilterRule rule : RULES) { + switch (rule.getType()) { // TODO rewrite + case EQUAL: + if (host.equals(rule.getContent())) { + System.out.println("=== reject dns query: " + host); + throw new java.net.UnknownHostException(); + } + default: // TODO support more rule types + return host; + } + } + + return host; + } + + public static Object testReachable(InetAddress n) throws IOException { + if (null == n) { + return null; + } + + for (FilterRule rule : RULES) { + switch (rule.getType()) { // TODO rewrite + case EQUAL: + if (n.getHostName().equals(rule.getContent())) { + System.out.println("=== reject dns reachable test: " + n.getHostName()); + return false; + } + default: // TODO support more rule types + return null; + } + } + + return null; + } +} diff --git a/src/main/java/io/zhile/research/ja/netfilter/filters/URLFilter.java b/src/main/java/io/zhile/research/ja/netfilter/filters/URLFilter.java new file mode 100644 index 0000000..e7d1c0a --- /dev/null +++ b/src/main/java/io/zhile/research/ja/netfilter/filters/URLFilter.java @@ -0,0 +1,39 @@ +package io.zhile.research.ja.netfilter.filters; + +import io.zhile.research.ja.netfilter.enums.RuleType; +import io.zhile.research.ja.netfilter.models.FilterRule; + +import java.io.IOException; +import java.net.SocketTimeoutException; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; + +public class URLFilter { + public static final List RULES; + + static { + RULES = new ArrayList<>(); // TODO read from config file + RULES.add(new FilterRule(RuleType.PREFIX, "https://zhile.io")); + } + + public static URL testURL(URL url) throws IOException { + if (null == url) { + return null; + } + + for (FilterRule rule : RULES) { + switch (rule.getType()) { // TODO rewrite + case PREFIX: + if (url.toString().startsWith(rule.getContent())) { + System.out.println("=== reject url: " + url.toString()); + throw new SocketTimeoutException("connect timed out"); + } + default: // TODO support more rule types + return url; + } + } + + return url; + } +} diff --git a/src/main/java/io/zhile/research/ja/netfilter/models/FilterRule.java b/src/main/java/io/zhile/research/ja/netfilter/models/FilterRule.java new file mode 100644 index 0000000..3c61958 --- /dev/null +++ b/src/main/java/io/zhile/research/ja/netfilter/models/FilterRule.java @@ -0,0 +1,30 @@ +package io.zhile.research.ja.netfilter.models; + +import io.zhile.research.ja.netfilter.enums.RuleType; + +public class FilterRule { + private RuleType type; + + private String content; + + public FilterRule(RuleType type, String content) { + this.type = type; + this.content = content; + } + + public RuleType getType() { + return type; + } + + public void setType(RuleType type) { + this.type = type; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } +} diff --git a/src/main/java/io/zhile/research/ja/netfilter/transformers/HttpClientTransformer.java b/src/main/java/io/zhile/research/ja/netfilter/transformers/HttpClientTransformer.java new file mode 100644 index 0000000..bcd094f --- /dev/null +++ b/src/main/java/io/zhile/research/ja/netfilter/transformers/HttpClientTransformer.java @@ -0,0 +1,33 @@ +package io.zhile.research.ja.netfilter.transformers; + +import jdk.internal.org.objectweb.asm.ClassReader; +import jdk.internal.org.objectweb.asm.ClassWriter; +import jdk.internal.org.objectweb.asm.tree.*; + +import static jdk.internal.org.objectweb.asm.Opcodes.*; + +public class HttpClientTransformer implements MyTransformer { + @Override + public byte[] transform(String className, byte[] classBytes) throws Exception { + ClassReader reader = new ClassReader(classBytes); + ClassNode node = new ClassNode(ASM5); + reader.accept(node, 0); + + for (MethodNode mn : node.methods) { + if ("openServer".equals(mn.name) && "()V".equals(mn.desc)) { + InsnList list = new InsnList(); + list.add(new VarInsnNode(ALOAD, 0)); + list.add(new FieldInsnNode(GETFIELD, "sun/net/www/http/HttpClient", "url", "Ljava/net/URL;")); + list.add(new MethodInsnNode(INVOKESTATIC, "io/zhile/research/ja/netfilter/filters/URLFilter", "testURL", "(Ljava/net/URL;)Ljava/net/URL;", false)); + list.add(new InsnNode(POP)); + + mn.instructions.insert(list); + } + } + + ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS); + node.accept(writer); + + return writer.toByteArray(); + } +} diff --git a/src/main/java/io/zhile/research/ja/netfilter/transformers/InetAddressTransformer.java b/src/main/java/io/zhile/research/ja/netfilter/transformers/InetAddressTransformer.java new file mode 100644 index 0000000..c055eda --- /dev/null +++ b/src/main/java/io/zhile/research/ja/netfilter/transformers/InetAddressTransformer.java @@ -0,0 +1,50 @@ +package io.zhile.research.ja.netfilter.transformers; + +import jdk.internal.org.objectweb.asm.ClassReader; +import jdk.internal.org.objectweb.asm.ClassWriter; +import jdk.internal.org.objectweb.asm.tree.*; + +import static jdk.internal.org.objectweb.asm.Opcodes.*; + +public class InetAddressTransformer implements MyTransformer { + @Override + public byte[] transform(String className, byte[] classBytes) throws Exception { + ClassReader reader = new ClassReader(classBytes); + ClassNode node = new ClassNode(ASM5); + reader.accept(node, 0); + + for (MethodNode m : node.methods) { + if ("getAllByName".equals(m.name) && "(Ljava/lang/String;Ljava/net/InetAddress;)[Ljava/net/InetAddress;".equals(m.desc)) { + InsnList list = new InsnList(); + list.add(new VarInsnNode(ALOAD, 0)); + list.add(new MethodInsnNode(INVOKESTATIC, "io/zhile/research/ja/netfilter/filters/DNSFilter", "testQuery", "(Ljava/lang/String;)Ljava/lang/String;", false)); + list.add(new InsnNode(POP)); + + m.instructions.insert(list); + continue; + } + + if ("isReachable".equals(m.name) && "(Ljava/net/NetworkInterface;II)Z".equals(m.desc)) { + InsnList list = new InsnList(); + list.add(new VarInsnNode(ALOAD, 0)); + list.add(new MethodInsnNode(INVOKESTATIC, "io/zhile/research/ja/netfilter/filters/DNSFilter", "testReachable", "(Ljava/net/InetAddress;)Ljava/lang/Object;", false)); + list.add(new VarInsnNode(ASTORE, 4)); + list.add(new InsnNode(ACONST_NULL)); + list.add(new VarInsnNode(ALOAD, 4)); + + LabelNode label1 = new LabelNode(); + list.add(new JumpInsnNode(IF_ACMPEQ, label1)); + list.add(new InsnNode(ICONST_0)); + list.add(new InsnNode(IRETURN)); + list.add(label1); + + m.instructions.insert(list); + } + } + + ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS); + node.accept(writer); + + return writer.toByteArray(); + } +} diff --git a/src/main/java/io/zhile/research/ja/netfilter/transformers/MyTransformer.java b/src/main/java/io/zhile/research/ja/netfilter/transformers/MyTransformer.java new file mode 100644 index 0000000..1543cb1 --- /dev/null +++ b/src/main/java/io/zhile/research/ja/netfilter/transformers/MyTransformer.java @@ -0,0 +1,5 @@ +package io.zhile.research.ja.netfilter.transformers; + +public interface MyTransformer { + byte[] transform(String className, byte[] classBytes) throws Exception; +} diff --git a/src/main/resources/442fcf28466515a81d5434931496ffa64611cc8e.txt b/src/main/resources/442fcf28466515a81d5434931496ffa64611cc8e.txt new file mode 100644 index 0000000..e69de29