Ceci est l'article sur le 17ème jour du "Calendrier de l'Avent Java 2016".
Certains disent
Vous pouvez le démarrer à partir d'un chargeur qui crypte le fichier de classe Java, le déchiffre et le charge dynamiquement. Étant donné que le fichier de classe lui-même est chiffré, vous ne devriez pas pouvoir afficher le code simplement en le plaçant dans le décrypteur.
De manière appropriée comme ça
private Key key;// KeyGenerator#generateKey()De manière appropriée
void encrypt(File file) {
byte[] inByte = null;
try {
inByte = FileUtils.readFileToByteArray(file);
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] encrypted = cipher.doFinal(inByte);
FileUtils.writeByteArrayToFile(file, encrypted);
decryptTest(file, key, inByte);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
}
}
Spigot lit la classe principale du plug-in dans le fichier de configuration (plugin.yml) et la réécrit dans celle de la classe loader
Yaml yaml = new Yaml();
try {
String str = FileUtils.readFileToString(new File(appTmpDir, "plugin.yml"), "utf-8");
Map map = yaml.loadAs(str, Map.class);
map.put("main", getPackageName(getMainClass()) + ".PluginLoader");
str = yaml.dumpAsMap(map);
FileUtils.writeStringToFile(new File(appTmpDir, "plugin.yml"), str, "utf-8");
} catch (IOException e) {
e.printStackTrace();
}
Du fichier à la chaîne d'octets
private byte[] read(InputStream inputStream) {
byte[] buf = new byte[1024];
int len;
BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
try {
while ((len = bufferedInputStream.read(buf)) > 0) {
byteArrayOutputStream.write(buf, 0, len);
}
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
return byteArrayOutputStream.toByteArray();
}
Décryptage (la clé est lue par ObjectInputStream)
private byte[] decrypt(byte[] bytes, Key key) {
byte[] inByte = null;
try {
if (key == null) throw new IllegalArgumentException("La clé est nulle.");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, key);
inByte = cipher.doFinal(bytes);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
}
return inByte;
}
Lisez bien la chaîne d'octets en utilisant la réflexion La méthode defineClass0 de ClassLoader est importante
private void loadClass(byte[] bytes, String name) throws ClassFormatError {
try {
String packageName = name.replaceAll("/", ".").substring(0, name.length() - 6);
Method define0Method = ClassLoader.class.getDeclaredMethod("defineClass0", new Class[]{String.class, byte[].class, int.class, int.class, ProtectionDomain.class});
define0Method.setAccessible(true);
Class loadedClass = (Class) define0Method.invoke(getClassLoader(), packageName, bytes, 0, bytes.length, null);
if (packageName.equals(mainClassName)) {
this.mainClass = loadedClass;
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
Throwable cause = e.getCause();
if (cause instanceof ClassFormatError) {
throw (ClassFormatError) cause;
}
}
stub.add(name);
}
Étant donné que chaque plug-in est géré par le nom du package + le nom de la classe sur le serveur, s'il existe au moins deux plug-ins difficiles à décompiler en raison d'un conflit entre le nom du package du chargeur et le nom de la classe, le deuxième plug-in et les suivants seront chargés. Échouer. Il n'y a aucun problème lors de la distribution en tant que programme Java autonome.
Le programme de décompilation difficile JAR (provisoire) a le code source du chargeur dans le corps principal, et le nom du paquet est changé dynamiquement afin qu'il soit compilé à chaque fois. L'argument de getTask a été décidé par essais et erreurs, et il devrait fonctionner normalement.
private void addLoader(String packageName, File target, File bukkitJar) {
try {
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
String pluginLoaderJava = FileUtils.readFileToString(new File(ClassLoader.getSystemResource("PluginLoader.java").getFile()), "utf-8");
JavaFileObject file = new JavaSourceFromString("PluginLoader", pluginLoaderJava.replace("{{package}}", "package " + packageName + ";"));
String[] compileOptions = new String[]{"-d", target.getAbsolutePath(), "-classpath", bukkitJar.getAbsolutePath()};
Iterable<String> compilationOption = Arrays.asList(compileOptions);
Iterable<? extends JavaFileObject> compilationUnits = Arrays.asList(file);
JavaCompiler.CompilationTask task = compiler.getTask(
null,
null,
null,
compilationOption,
null,
compilationUnits);
task.call();
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
Le point qu'un JDK est nécessaire pour compiler le chargeur lors de l'exécution d'une décompilation difficile. (Est-il possible de se passer de JDK en modifiant directement le fichier de classe?) Même si les grandes entreprises deviennent open source, qu'en est-il de cacher le code source en premier lieu?
Recommended Posts