В настоящее время вредоносное ПО Java, использующее различные уязвимости в Java, очень широко распространено. Многие из них используют строковое шифрование, чтобы скрыть свое присутствие и действия на зараженном хосте. Как правило, строки содержат разделы реестра, некоторые команды для выполнения различных программ и т. Д.
В этой статье мы рассмотрим наиболее распространенный метод шифрования строк в вредоносных программах Java и напишем небольшой инструмент для удаления этого шифрования в целях анализа вредоносного программного обеспечения.
Краткое введение в формат байт-кода файла класса Java
Для дальнейшего понимания необходимо знать, что все константы в Java (строки и типы примитивов) должны храниться в специальной структуре внутри файла класса, который называется Constant Pool. Чтобы получить элемент из пула констант, есть инструкция JVM — ldc.
Давайте посмотрим, как байт-код из
System.out.println("HelloWorld");
вызов метода выглядит так.
Написание простого класса:
public class Hello { public static void main(String[] args) throws Exception { System.out.println("HelloWorld"); } }
Компиляция:
$ javac Hello.java
И декомпилируем его, используя javap:
javap -c -v Hello
Постоянный пул:
... const #3 = String #20; // HelloWorld const #20 = Asciz HelloWorld;
Код:
ldc #3; //String HelloWorld invokevirtual #4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
Темная сторона
В целях шифрования плохие парни обычно применяют следующий подход:
- Зашифровать строку с помощью некоторой функции
- Заменить исходное значение строки на зашифрованное значение
- Перед использованием строкового вызова сначала расшифруйте функцию
Вот байт-код после применения этого подхода:
ConstanPool:
... const #3 = String #20; // CryptedString const #20 = Asciz CryptedString;
Код:
ldc #3; //String CryptedString invokestatic #4; //Method decrypt:(Ljava/lang/String;)Ljava/lang/String; invokevirtual #5; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
Сторона джедая
Для написания нашего инструмента автоматического дешифрования нам нужен ASM ( http://asm.ow2.org/ ). Идея нашего пути: загрузка класса (который содержит функцию дешифрования), замена зашифрованных строк на дешифрованные и отключение инструкции, которая вызывает функцию дешифрования, из байт-кода.
Основной метод:
// input file JarFile jin = new JarFile(new File(args[0]), false); // output file JarOutputStream jon = new JarOutputStream(new FileOutputStream(args[1])); Enumerationen = jin.entries(); byte[] buffer = new byte[1024]; URLClassLoader jcl = new URLClassLoader(new URL[]{(new File(args[0])).toURI().toURL()}); Method decMethod = jcl.loadClass(decClassName).getDeclaredMethod(decMethoName, new Class[]{String.class}); while (en.hasMoreElements()) { JarEntry je = en.nextElement(); if (je.isDirectory()) { jon.putNextEntry(new JarEntry(je)); } else if (je.getName().endsWith(".class")) { System.out.println("Processing class " + je.getName()); ClassReader cr = new ClassReader(jin.getInputStream(je)); ClassWriter cw = new ClassWriter(0); Decryptor transformer = new Decryptor(cw, decMethod); cr.accept(transformer, 0); jon.putNextEntry(new JarEntry(je.getName())); jon.write(cw.toByteArray()); } else { jon.putNextEntry(new JarEntry(je)); BufferedInputStream bis = new BufferedInputStream(jin.getInputStream(je)); int readed = bis.read(buffer); while (readed > 0) { jon.write(buffer, 0, readed); readed = bis.read(buffer); } bis.close(); } } jon.close();
ASM часть:
class Decryptor extends ClassVisitor implements Opcodes { Method decMethod; public Decryptor(ClassVisitor cv, Method decMethod) throws Exception { super(ASM4, cv); this.decMethod = decMethod; } @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions); return new MethodDecryptor(mv, decMethod); } class MethodDecryptor extends MethodVisitor implements Opcodes { Method decMethod; String decClassName; String decMethodName; public MethodDecryptor(MethodVisitor mv, Method decMethod) { super(ASM4, mv); this.decMethod = decMethod; decClassName = decMethod.getDeclaringClass().getName().replace(".","/"); decMethodName = decMethod.getName(); } @Override public void visitMethodInsn(int opcode, String owner, String name, String desc) { if (decClassName.equals(owner) && decMethodName.equals(name) && "(Ljava/lang/String;)Ljava/lang/String;".equals(desc)) { // skipping decryption function } else { super.visitMethodInsn(opcode, owner, name, desc); } } @Override public void visitLdcInsn(Object o) { if (o instanceof String) { try { // decrypt string o = decMethod.invoke(null, new Object[]{o}); } catch (Exception ex) { } } super.visitLdcInsn(o); } } }
Вывод
Статическое шифрование строк не дает нам никакой защиты. ASM — очень мощный инструмент для манипулирования с помощью байт-кода. Ребята, не пишите вредоносные программы, лучше находите, поддерживайте и помогайте проектам с открытым исходным кодом. Это принесет вам столько удовольствия (и прибыли), поверьте мне!