parent
1edcadc33a
commit
642f49a2c3
@ -0,0 +1,4 @@
|
||||
*.iml
|
||||
.idea/
|
||||
.DS_Store
|
||||
target/
|
@ -0,0 +1,72 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>io.zhile.research</groupId>
|
||||
<artifactId>ja-netfilter</artifactId>
|
||||
<version>1.0.0</version>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
<finalName>ja-netfilter</finalName>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.7.0</version>
|
||||
<configuration>
|
||||
<source>8</source>
|
||||
<target>8</target>
|
||||
<encoding>UTF-8</encoding>
|
||||
<compilerArgument>-XDignore.symbol.file</compilerArgument>
|
||||
<fork>true</fork>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>3.1.0</version>
|
||||
<configuration>
|
||||
<archive>
|
||||
<addMavenDescriptor>false</addMavenDescriptor>
|
||||
</archive>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<version>3.0.0</version>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifest>
|
||||
<addClasspath>true</addClasspath>
|
||||
</manifest>
|
||||
<manifestEntries>
|
||||
<Premain-Class>io.zhile.research.ja.netfilter.Launcher</Premain-Class>
|
||||
<Main-Class>io.zhile.research.ja.netfilter.Launcher</Main-Class>
|
||||
<Can-Redefine-Classes>true</Can-Redefine-Classes>
|
||||
<Can-Retransform-Classes>true</Can-Retransform-Classes>
|
||||
</manifestEntries>
|
||||
</archive>
|
||||
<descriptorRefs>
|
||||
<descriptorRef>jar-with-dependencies</descriptorRef>
|
||||
</descriptorRefs>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>make-assembly</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>single</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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<String, MyTransformer> 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;
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package io.zhile.research.ja.netfilter.enums;
|
||||
|
||||
public enum RuleType {
|
||||
PREFIX,
|
||||
SUFFIX,
|
||||
KEYWORD,
|
||||
REGEXP,
|
||||
EQUAL
|
||||
}
|
@ -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<FilterRule> 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;
|
||||
}
|
||||
}
|
@ -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<FilterRule> 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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package io.zhile.research.ja.netfilter.transformers;
|
||||
|
||||
public interface MyTransformer {
|
||||
byte[] transform(String className, byte[] classBytes) throws Exception;
|
||||
}
|
Loading…
Reference in new issue