asm2Больше месяца назад автор статьи Decrypting Java Malware using ASM прислал мне оригинальный текст на русском языке для публикации в блоге, с целью донести широкому кругу лиц определенные аспекты защиты программ написанных на java от действий недоброжелателей. Автор статьи — разработчик, который долгое время занимается разработкой средств для противодействия реверс-инжирингу и взлому ПО, специалист в области информационной безопасности.
К сожалению, в связи с большой загруженностью (казалось бы лето, все должны быть в отпусках, а нет — работы только привалило), никак не мог заняться блогом и выложить эту статью.

Исправляюсь, надеюсь статья всё еще актуальна.

В настоящее время очень популярны Java Malware, использующие уязвимости в Java. Многие из них для скрытия своих действий используют шифрование строк. Как правило в них могут храниться ключи реестра, команды для запуска различных программ и т.д.

В этой статье мы рассмотрим самый распространенный метод шифрования строк в Java Malware и напишем утилиту для снятия такой защиты.

1) Небольшое введение в формат байткода class-файлов.

Для дальнейшего понимания, необходимо знать следующие вещи - все константы (строки/примитивные типы) хранятся в специальной структуре внути class-файла, которая называется Constant Pool и для получения элемента из Constant Pool используется инструкция 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

ConstanPool:

  ... 
    const #3 = String	#20;	//  HelloWorld
    const #20 = Asciz	HelloWorld;
 
   3:	ldc	#3; //String HelloWorld
   5:	invokevirtual	#4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V


2) Dark side
Для шифрования строк авторы Malware обычно применяют следующий подход:
- шифруют строку с помощью некоторой функции
- заменяют исходное значение строки на зашифрованное
- перед использование строки вызывают функцию расшифрования

Вот как выглядит байткод после применения такого подхода:
ConstanPool:

  ... 
    const #3 = String	#20;	//  CryptedString
    const #20 = Asciz	CryptedString;
 
   3:	ldc	#3; //String CryptedString
   5:	invokestatic	#4; //Method decrypt:(Ljava/lang/String;)Ljava/lang/String;
   8:	invokevirtual	#5; //Method java/io/PrintStream.println:(Ljava/lang/String;)V

3) Джедай side

Для написания автоматического расшифровщика нам понадобится ASM. Основная идея нашего подхода - грузим class, содержащий функцию дешифрования, заменяем зашифрованные строки на расшифрованные и вырезаем инструкцию вызова функции дешифрования из байткода.

Главный метод:

        // input file
        JarFile jin = new JarFile(new File(args[0]), false);
        // output file
        JarOutputStream jon = new JarOutputStream(new FileOutputStream(args[1]));
        Enumeration en = 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 LCDec(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(opcode, owner, name, desc) {
                if (decClassName.equals(owner) && decMethodName.equals(decMethodName) && "(Ljava/lang/String;)Ljava/lang/String;".equals(desc)) {
                } else {
                    super.visitMethodInsn(opcode, owner, name, desc);
                }
            }
 
            @Override
            public void visitLdcInsn(Object o) {
                if (o instanceof String) {
                    try {
                        o = decMethod.invoke(null, new Object[]{o});
                    } catch (Exception ex) {
                    }
                }
                super.visitLdcInsn(o);
            }
        }
    }

Выводы:
Cтатическое шифрование строк не обеспечивает никакой защиты.
ASM очень мощный инструмент для манипулирования байт-кодом.
Guys не пишите malware пишите лучше полезные open-source программы.