parent
3fc69ee29f
commit
5586939a8f
@ -0,0 +1,64 @@
|
||||
package io.zhile.research.ja.netfilter;
|
||||
|
||||
import io.zhile.research.ja.netfilter.commons.DebugInfo;
|
||||
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.*;
|
||||
|
||||
public class Dispatcher implements ClassFileTransformer {
|
||||
private static Dispatcher INSTANCE;
|
||||
|
||||
private final Map<String, List<MyTransformer>> transformerMap = new HashMap<>();
|
||||
|
||||
public static synchronized Dispatcher getInstance() {
|
||||
if (null == INSTANCE) {
|
||||
INSTANCE = new Dispatcher();
|
||||
}
|
||||
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
public void addTransformer(MyTransformer transformer) {
|
||||
List<MyTransformer> transformers = transformerMap.computeIfAbsent(transformer.getHookClassName(), k -> new ArrayList<>());
|
||||
|
||||
transformers.add(transformer);
|
||||
}
|
||||
|
||||
public void addTransformers(List<MyTransformer> transformers) {
|
||||
for (MyTransformer transformer : transformers) {
|
||||
addTransformer(transformer);
|
||||
}
|
||||
}
|
||||
|
||||
public void addTransformers(MyTransformer[] transformers) {
|
||||
addTransformers(Arrays.asList(transformers));
|
||||
}
|
||||
|
||||
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classFileBuffer) throws IllegalClassFormatException {
|
||||
do {
|
||||
if (null == className) {
|
||||
break;
|
||||
}
|
||||
|
||||
List<MyTransformer> transformers = transformerMap.get(className);
|
||||
if (null == transformers) {
|
||||
break;
|
||||
}
|
||||
|
||||
int order = 0;
|
||||
|
||||
try {
|
||||
for (MyTransformer transformer : transformers) {
|
||||
classFileBuffer = transformer.transform(className, classFileBuffer, order++);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
DebugInfo.output("Transform class failed: " + e.getMessage());
|
||||
}
|
||||
} while (false);
|
||||
|
||||
return classFileBuffer;
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
package io.zhile.research.ja.netfilter;
|
||||
|
||||
import io.zhile.research.ja.netfilter.commons.ConfigDetector;
|
||||
import io.zhile.research.ja.netfilter.commons.ConfigParser;
|
||||
import io.zhile.research.ja.netfilter.commons.DebugInfo;
|
||||
import io.zhile.research.ja.netfilter.models.FilterConfig;
|
||||
import io.zhile.research.ja.netfilter.plugin.PluginManager;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.instrument.Instrumentation;
|
||||
import java.lang.instrument.UnmodifiableClassException;
|
||||
|
||||
public class Initializer {
|
||||
public static void init(String args, Instrumentation inst, File currentDirectory) {
|
||||
File configFile = ConfigDetector.detect(currentDirectory, args);
|
||||
if (null == configFile) {
|
||||
DebugInfo.output("Could not find any configuration files.");
|
||||
} else {
|
||||
DebugInfo.output("Current config file: " + configFile.getPath());
|
||||
}
|
||||
|
||||
try {
|
||||
FilterConfig.setCurrent(new FilterConfig(ConfigParser.parse(configFile)));
|
||||
} catch (Exception e) {
|
||||
DebugInfo.output(e.getMessage());
|
||||
}
|
||||
|
||||
PluginManager.getInstance().loadPlugins(inst, currentDirectory);
|
||||
|
||||
for (Class<?> c : inst.getAllLoadedClasses()) {
|
||||
try {
|
||||
inst.retransformClasses(c);
|
||||
} catch (UnmodifiableClassException e) {
|
||||
// ok, ok. just ignore
|
||||
}
|
||||
}
|
||||
|
||||
inst.addTransformer(Dispatcher.getInstance(), true);
|
||||
}
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
package io.zhile.research.ja.netfilter;
|
||||
|
||||
import io.zhile.research.ja.netfilter.commons.DebugInfo;
|
||||
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) {
|
||||
DebugInfo.output("Transform class failed: " + e.getMessage());
|
||||
}
|
||||
} while (false);
|
||||
|
||||
return classFileBuffer;
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
package io.zhile.research.ja.netfilter.plugin;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.zip.ZipEntry;
|
||||
|
||||
public class PluginClassLoader extends ClassLoader {
|
||||
private final JarFile jarFile;
|
||||
|
||||
public PluginClassLoader(JarFile jarFile) {
|
||||
this.jarFile = jarFile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> findClass(String name) throws ClassNotFoundException {
|
||||
byte[] bytes = loadClassFromFile(name);
|
||||
|
||||
return defineClass(name, bytes, 0, bytes.length);
|
||||
}
|
||||
|
||||
private byte[] loadClassFromFile(String fileName) throws ClassNotFoundException {
|
||||
String classFile = fileName.replace('.', '/') + ".class";
|
||||
ZipEntry entry = jarFile.getEntry(classFile);
|
||||
if (null == entry) {
|
||||
throw new ClassNotFoundException("Class not found: " + fileName);
|
||||
}
|
||||
|
||||
int length;
|
||||
byte[] buffer = new byte[1024];
|
||||
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
|
||||
|
||||
try (InputStream is = jarFile.getInputStream(entry)) {
|
||||
while (-1 != (length = is.read(buffer))) {
|
||||
byteStream.write(buffer, 0, length);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new ClassNotFoundException("Can't access class: " + fileName, e);
|
||||
}
|
||||
|
||||
return byteStream.toByteArray();
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package io.zhile.research.ja.netfilter.plugin;
|
||||
|
||||
import io.zhile.research.ja.netfilter.models.FilterRule;
|
||||
import io.zhile.research.ja.netfilter.transformers.MyTransformer;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface PluginEntry {
|
||||
default void init(List<FilterRule> filterRules) {
|
||||
// get plugin config
|
||||
}
|
||||
|
||||
String getName();
|
||||
|
||||
default String getVersion() {
|
||||
return "v1.0.0";
|
||||
}
|
||||
|
||||
default String getDescription() {
|
||||
return "A ja-netfilter plugin.";
|
||||
}
|
||||
|
||||
List<MyTransformer> getTransformers();
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
package io.zhile.research.ja.netfilter.plugin;
|
||||
|
||||
import io.zhile.research.ja.netfilter.Dispatcher;
|
||||
import io.zhile.research.ja.netfilter.commons.DebugInfo;
|
||||
import io.zhile.research.ja.netfilter.models.FilterConfig;
|
||||
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 io.zhile.research.ja.netfilter.utils.StringUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.instrument.Instrumentation;
|
||||
import java.util.Arrays;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.jar.Manifest;
|
||||
|
||||
public class PluginManager {
|
||||
private static final String PLUGINS_DIR = "plugins";
|
||||
private static final String ENTRY_NAME = "JANF-Plugin-Entry";
|
||||
|
||||
private static PluginManager INSTANCE;
|
||||
|
||||
public static synchronized PluginManager getInstance() {
|
||||
if (null == INSTANCE) {
|
||||
INSTANCE = new PluginManager();
|
||||
}
|
||||
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
public void loadPlugins(Instrumentation inst, File currentDirectory) {
|
||||
File pluginsDirectory = new File(currentDirectory, PLUGINS_DIR);
|
||||
if (!pluginsDirectory.exists() || !pluginsDirectory.isDirectory()) {
|
||||
return;
|
||||
}
|
||||
|
||||
File[] pluginFiles = pluginsDirectory.listFiles((d, n) -> n.endsWith(".jar"));
|
||||
if (null == pluginFiles) {
|
||||
return;
|
||||
}
|
||||
|
||||
Dispatcher.getInstance().addTransformers(new MyTransformer[]{ // built-in transformers
|
||||
new HttpClientTransformer(),
|
||||
new InetAddressTransformer()
|
||||
});
|
||||
|
||||
for (File pluginFile : pluginFiles) {
|
||||
try {
|
||||
JarFile jarFile = new JarFile(pluginFile);
|
||||
Manifest manifest = jarFile.getManifest();
|
||||
String entryClass = manifest.getMainAttributes().getValue(ENTRY_NAME);
|
||||
if (StringUtils.isEmpty(entryClass)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
PluginClassLoader classLoader = new PluginClassLoader(jarFile);
|
||||
Class<?> klass = Class.forName(entryClass, false, classLoader);
|
||||
if (!Arrays.asList(klass.getInterfaces()).contains(PluginEntry.class)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
inst.appendToBootstrapClassLoaderSearch(jarFile);
|
||||
|
||||
PluginEntry pluginEntry = (PluginEntry) Class.forName(entryClass).newInstance();
|
||||
pluginEntry.init(FilterConfig.getBySection(pluginEntry.getName()));
|
||||
Dispatcher.getInstance().addTransformers(pluginEntry.getTransformers());
|
||||
|
||||
DebugInfo.output("Plugin loaded: {name=" + pluginEntry.getName() + ", version=" + pluginEntry.getVersion() + "}");
|
||||
} catch (Exception e) {
|
||||
DebugInfo.output("Load plugin failed: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,9 @@
|
||||
package io.zhile.research.ja.netfilter.transformers;
|
||||
|
||||
public interface MyTransformer {
|
||||
byte[] transform(String className, byte[] classBytes) throws Exception;
|
||||
String getHookClassName();
|
||||
|
||||
default byte[] transform(String className, byte[] classBytes, int order) throws Exception {
|
||||
return classBytes;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in new issue