Статья 'Обфускация сокрытия вызовов при помощи инструкции invokedynamic' - журнал 'Кибернетика и программирование' - NotaBene.ru
по
Journal Menu
> Issues > Rubrics > About journal > Authors > About the Journal > Requirements for publication > Council of Editors > Peer-review process > Policy of publication. Aims & Scope. > Article retraction > Ethics > Online First Pre-Publication > Copyright & Licensing Policy > Digital archiving policy > Open Access Policy > Article Processing Charge > Article Identification Policy > Plagiarism check policy
Journals in science databases
About the Journal

MAIN PAGE > Back to contents
Cybernetics and programming
Reference:

Obfuscation of concealment calls using InvokeDynamic instructions

Korobeinikov Anatolii Grigor'evich

Doctor of Technical Science

professor, Pushkov institute of terrestrial magnetism, ionosphere and radio wave propagation of the Russian Academy of Sciences St.-Petersburg Filial

199034, Russia, g. Saint Petersburg, ul. Mendeleevskaya, 1

Korobeynikov_A_G@mail.ru
Other publications by this author
 

 
Kutuzov Il'ya Mikhailovich

graduate student, Department of Design and Security of Computer Systems, St. Petersburg National Research University of Information Technologies, Mechanics and Optics

197101, Russia, St. Petersburg, Kronverkskiy prospect, d. 49

formalizator@gmail.com
Other publications by this author
 

 

DOI:

10.7256/2306-4196.2016.5.18686

Received:

07-04-2016


Published:

19-10-2016


Abstract: The object of the study is technology of hiding method calls. Hidden calls are need to hide: entity dependencies; data processing logic; algorithms. The methods used to complete the task are limited by language technologies and its performance. Method can be called directly and indirectly: via the bootstrap method; from native code (JNI); using Reflection; using JRE 1.7, InvokeDynamic. The examples with source code are given. The authors conclude that the most promising methods among considered is invokedynamic technology. The study  present analysis of such methods as the bootstrap method, calling method from through native code, obfuscation calls via Reflection and InvokeDynamic. The article discusses various ways to conceal the method invocation. The characteristic features of obfuscation for most popular ones are reviewed. The most promising among the discussed methods is  invokedynamic technology. It allows completely removing method signature from the source code, leaving only the service information for the bootstrap method. With proper implementation of the bootstrap method it is possible to create bytecode, which will be impossible to decompile into valid Java code, Groovy's or Scala.


Keywords:

Data protection, obfuscate, bootstrap, native code, Reflection mechanism, InvokeDynamic, Java, source code, method call, listing

This article written in Russian. You can find original text of the article here .
Введение

Защита интеллектуальной собственности и конкурентных преимуществ в современных условиях является обязательными требованиями. В настоящее время для защиты программного обеспечения используются различные технологии, затрудняющие анализ приложений потенциальными злоумышленниками. Обфускация, как защита программного кода является очень перспективным направлением по защите программных продуктов от несанкционированных действий [1-6].

Ява - программы принадлежат к семейству уязвимых для декомпиляции программ. Это связано с тем, что язык программирования и бинарный формат содержат много метаинформации о вызываемых процедурах и функциях. Так, например, не имея исходных кодов программы можно выявить все имена вызываемых функций, подключившись к работающему приложению через отладочный порт. Если же имеется возможность выполнять свой код совместно с анализируемым, то технология Reflection позволяет загрузить все классы, все пакеты, все методы и поля, а также изменить их видимость. Данная технология направлена на открытость программного обеспечения с переиспользованием компонент. К сожалению, эта технология позволяет доподлинно узнать зависимость не только классов, но и методов, а также без труда восстановить имена полей и методов исходя из логики работы приложения, даже если они были затерты обфускатором. В рамках данной статьи будет рассматриваться сокрытие зависимости методов при помощи технологии invokedynamic.

Анализ существующих методов сокрытия вызовов

Одной из самых сложных задач обфускации является сокрытие вызовов методов. Сокрытие вызовов необходимо для того, чтобы скрыть зависимости сущностей, скрыть логику обработки данных, скрыть алгоритмы. Методы, используемые для сокрытия вызовов сильно ограничены технологиями языка и их производительностью. Вызов метода может осуществляться напрямую, через бутстрап-метод, из нативного кода (JNI), через Reflection и, с JRE 1.7, InvokeDynamic [7,8].

Бутстрап-метод

Бутстрап-метод - это метод, через который вызываются все остальные методы. Для работы такого метода требуется универсализировать возвращаемые значения всех методов а также их аргументы. Рассмотрим пример реализации этого метода.

public class PsvmExample {

private static final int MAXIMUM = 118;

private static int counter = 915;

public static void main(String[] args) {

printGreeting();

count();

printResult();

}

private static void printGreeting(){

System.out.println("Hello my dear friend!!!");

}

private static void count(){

for (int i = 17; i < MAXIMUM; i++) {

counter += i;

}

}

private static void printResult(){

System.out.println("Counter " + counter);

}

}

Листинг 1. Пример универсализированной программы.

Данная программа модифицируется таким образом, чтобы все вызовы проходили через бутстрап-метод. В зависимости от различных условий или аргументов бутстрап-метод вызывает различные методы изначального приложения.

public class PsvmExample {

private static final int MAXIMUM = 118;

private static int counter = 915;

private static int state = 9;

public static void main(String[] args) {

for (int i = 0; i < 3; i++) {

bootstrap(System.currentTimeMillis());

}

}

private static void bootstrap(long ignoredArgument) {

switch (state % 3) {

case 0:

printGreeting();

break;

case 1:

count();

break;

case 2:

printResult();

break;

}

state++;

}

private static void printGreeting() {

System.out.println("Hello my dear friend!!!");

}

private static void count() {

for (int i = 17; i < MAXIMUM; i++) {

counter += i;

}

}

private static void printResult() {

System.out.println("Counter " + counter);

}

}

Листинг 2. Обфусцированное приложение с помощью бутстрап-метода.

Как видно из листинга, из основного метода main ушли все вызовы методов print и count. Эти методы вызываются в зависимости от произвольных условий. В рамках данного примера в зависимости от состояния внутреннего счетчика. Подобная обфускация практически не влияет на производительность самого приложения. Сам процесс обфускации при этом крайне затруднителен, так как требуется реорганизовать работу всего приложения. В случае, если удается выявить логику работы бутстрап-метода, то вся обфускация всего приложения сводится на нет.

Вызов методов через нативный код

В Java имеется возможность интеграции с другими языками, посредством нативных вызовов. Так как декомпилировать нативную библиотеку в высокоуровневый код не всегда возможно, вызов некоторых из методов может быть замаскирован в нативном коде.

Часть защищаемых вызовов в рамках данной технологии вызывается через бутстрап-метод в нативном коде. К сожалению, при возможности декомпиляции нативного компонента пример сводится к предыдущему, если убрать конструкции JNI. Также данный метод теряет производительность на преобразовании типов данных. Также требуется поддержка нативного кода и ограничения, накладываемые платформами на нативный код.

Обфускация вызовов при помощи Reflection

Для обращения к произвольному методу произвольного класса существует механизм Reflection. Данный механизм позволяет реализовать вызов методов, которые на этапе компиляции не известны или не могут быть загружены. С помощью данной технологии обращение к методу происходит через строковые литералы. Эти литералы также могут быть получены на этапе работы приложения в целевой системе.

//Вызов напрямую

foo.doSomething();

//Вызов через Reflection

Method method = foo.getClass().getMethod("do”+”Something", null);

method.invoke(foo, null);

Листинг 3. Пример вызова через Reflection.

Листинг 3 показывает как изменяется вызов метода doSomething. Переход по ссылке и построение графа зависимостей в данном случае затруднено тем, что прямого указания на метод нет. Существует лишь описание сигнатуры метода, который ожидается в данном классе. Сигнатура, используемая Reflection, может вычисляться во время работы программы. Так, например имя метода складывается из двух независимых строчек.

К сожалению, реализация Reflection крайне трудемкое занятие. Как на стадии программирования, в связи с тем, что исходные коды становятся очень объемными и не читаемыми, так и на стадии выполнения, в связи с большим количеством проверок на наличие требуемой сигнатуры, соответствие типов данных и пр. В связи с вышесказанным данную технологию применяют крайне редко в реальных приложениях.

InvokeDynamic

Технология invokedynamic позволяет сделать обращение к “бутстрап-методу” для определения целевого метода, и, в дальнейшем, вызвать целевой метод с заданными аргументами. Начиная с 7 версии JRE официально начало поддержку динамических языков. Такими языками являются Scala, Groovy. Эти языки выполняются на стандартной JRE, по аналогии с программами на Java. Их байт-код не содержит никаких дополнительных инструкций для VM. В отличии от Java- программ в динамических языках не всегда известен класс и интерфейсная иерархия объектов, поля конкретного объекта могут меняться в процессе работы приложения, функции могут вызываться с различным типам аргументов и их количеством. Весь этот функционал официально поддерживается JVM с помощью инструкции InvokeDynamic. Данная функция на ровне с обычным invokeVirtual делает вызов определенного метода. Разница в том, запрос пройдет не напрямую в метод, а сначала пройдет анализ бутстрап-методом. Результатом такого анализа будет CallSite. Бутстрап-метод подбирает необходимый метод и необходимый проксирующий преобразователь аргументов.

Бутстрап метод подбирает необходимый метод исходя из того, что запрошено и передано в качестве аргументов. В частности бутстрап-методу можно передавать сигнатуру в зашифрованном виде, что позволит убрать из исходных кодов ссылки на конкретные вызовы. Такие вызовы не содержат прямой ссылки на вызываемый метод, а содержат лишь информацию, которая будет использована для его вычисления. Заменив все вызовы защищаемых методов на вызов через бутстрап-метод invokeDynamic можно убрать все сигнатурные ссылки.

Инструкция inbvokeDynamic в языке Java используется исключительно для работы с лямбда-выражениями, которые появились в 8 версии языка. Для работы с лямбда используется LambdaMetaFactory в качестве бутстрап метода. В рамках языка есть возможность написать свой собственный бутстрап-метод, который будет генерировать CallSite для вызова защищаемых методов. Такой CallSite не может быть корректно интерпретирован в рамках языка Java, поэтому и не может быть корректно декомпиллирован в Java. Это связано с тем, что компилятор Java вставляет инструкцию invokeDynamic в связке с LambdaMetafactory. При ручной модификации байт-кода с целью вызова своего бутстрап-метода декомпилятор не может корректно перевести указанный код в Java-код, так как в языке отсутствует альтернативная конструкция динамического вызова методов.

При этом данную структуру можно сгенерировать для JVM 1.7. При этом задача декомпилятора усложняется тем, что в данной версии языка отсутствует даже LambdaMetaFactory и конструкция лямбд. Некоторые декомпиляторы интерпретируют invokedynamic как вызов invokevirtual в бутстрап-метод с заданными аргументами, но не вызывают защищаемый метод.

//Исходный код классической программы

public static void main(String[] args) {

Runnable r = new Runnable() {

public void run() {

System.out.println("Anonymous class.");

}

};

Thread t = new Thread(r);

t.start();

}

//Байт-код Asmifier классической программы

mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);

mv.visitCode();

mv.visitTypeInsn(NEW, "ru/ifmo/obfuscation/research/PsvmExample$1");

mv.visitInsn(DUP);

mv.visitMethodInsn(INVOKESPECIAL, "ru/ifmo/obfuscation/research/PsvmExample$1", "", "()V", false);

mv.visitVarInsn(ASTORE, 1);

mv.visitTypeInsn(NEW, "java/lang/Thread");

mv.visitInsn(DUP);

mv.visitVarInsn(ALOAD, 1);

mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Thread", "", "(Ljava/lang/Runnable;)V", false);

mv.visitVarInsn(ASTORE, 2);

mv.visitVarInsn(ALOAD, 2);

mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Thread", "start", "()V", false);

mv.visitInsn(RETURN);

mv.visitMaxs(3, 3);

mv.visitEnd();

Листинг 4. Пример программы без использования лямбда-выражения

//Лямбда-метод

public static void main(String[] args) {

new Thread(()->System.out.println("Anonymous class.")).start();

}

//Байт-код Asmifier лямбда-метода

mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);

mv.visitCode();

mv.visitTypeInsn(NEW, "java/lang/Thread");

mv.visitInsn(DUP);

mv.visitInvokeDynamicInsn("run", "()Ljava/lang/Runnable;", new Handle(Opcodes.H_INVOKESTATIC, "java/lang/invoke/LambdaMetafactory", "metafactory", "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;"), new Object[]{Type.getType("()V"), new Handle(Opcodes.H_INVOKESTATIC, "ru/ifmo/obfuscation/research/PsvmExample", "lambda$main$0", "()V"), Type.getType("()V")});

mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Thread", "", "(Ljava/lang/Runnable;)V", false);

mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Thread", "start", "()V", false);

mv.visitInsn(RETURN);

mv.visitMaxs(3, 1);

mv.visitEnd();

mv = cw.visitMethod(ACC_PRIVATE + ACC_STATIC + ACC_SYNTHETIC, "lambda$main$0", "()V", null, null);

mv.visitCode();

mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");

mv.visitLdcInsn("Anonymous class.");

mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);

mv.visitInsn(RETURN);

mv.visitMaxs(2, 0);

mv.visitEnd();

Листинг 5. Пример программы с лямбда выражениями.

Возможно написание декомпилятора, который будет трактовать invokeDynamic как два вызова invokevirtual + invokestatic/invokevirtual/invokeinterface, но в рамках языка в таком случае возможно расхождение типов. В частности, с помощью invokedynamic можно вызывать методы с примитивами в качестве аргументов, которые будут комбинироваться и преобразовываться в нужные. Динамический вызов позволяет создавать классы, ранее не зарегистрированные в системе и отсутствующие в каком-либо из существующих классов.

Заключение

В данной статье рассмотрены различные способы сокрытия вызова методов. Рассмотрены характерные особенности обфускации для наиболее популярных из них. Наиболее перспективной среди рассмотренных методов выглядит технология invokedynamic. Она позволяет полностью убрать из исходных кодов сигнатуру метода, оставив лишь служебную информацию для бутстрап-метода. При надлежащей реализации бутстрап-метода возможно создание байт-кода, который будет невозможно декомпилировать в валидный код Java, Groovy или Scala.

References
1. Korobeinikov A.G., Kutuzov I.M., Kolesnikov P.Yu. Primenenie metodov obfuskatsii // Informatsionnye tekhnologii v professional'noi deyatel'nosti i nauchnoi rabote: sbornik materialov Vserossiiskoi nauchno-prakticheskoi konferentsii s mezhdunarodnym uchastiem: v 2 ch. Ioshkar-Ola: Mariiskii gosudarstvennyi tekhnicheskii universitet, 2012. T. 1. S. 24-28. - ISBN 978-5-8158-1002-0.
2. Korobeinikov A.G., Kutuzov I.M., Kolesnikov P.Yu., Makarov S.P., Akhapkina I.B. Analiz primeneniya metodov obfuskatsii // V knige “Trudy kongressa po intellektual'nym sistemam i informatsionnym tekhnologiyam AIS-IT’12. Nauchnoe izdanie v 4-kh tomakh. M.: Fizmatlit, 2012. T. 2. S. 203-208.
3. Korobeinikov A.G., Kutuzov I.M. Issledovanie algoritma obfuskatsii // Informatsionnye tekhnologii v professional'noi deyatel'nosti i nauchnoi rabote: sbornik materialov Vserossiiskoi nauchno-prakticheskoi konferentsii s mezhdunarodnym uchastiem: v 2 ch. Ioshkar-Ola: Povolzhskii gosudarstvennyi tekhnologicheskii universitet, 2013. Ch. 1. 227 s. – ISBN 978-5-8158-1002-0.
4. Korobeinikov A.G., Kutuzov I.M. Algoritm obfuskatsii // NB: Kibernetika i programmirovanie. 2013. № 3. S. 1-8. DOI: 0.7256/2306-4196.2013.3.9356. URL: http://e-notabene.ru/kp/article_9356.html
5. Korobeinikov A.G., Akhapkina I.B, Bezruk N.V., Demina E.A., Yamshchikova N.V., Kutuzov I.M. Modifikatsiya i analiz algoritma obfuskatsii // V knige “Trudy kongressa po intellektual'nym sistemam i informatsionnym tekhnologiyam AIS-IT’13. Nauchnoe izdanie v 4-kh tomakh. M.: Fizmatlit, 2013. T. 2. S. 163-166. – ISBN 978-5-9221-1479-0.
6. Korobeinikov A.G., Kutuzov I.M., Kolesnikov P.Yu. Analiz metodov obfuskatsii // Kibernetika i programmirovanie. 2012. № 1. C. 31 - 37. URL: http://www.e-notabene.ru/kp/article_13858.html
7. Ortin F., Conde P., Fernandez-Lanvin D. , Izquierdo R.: The Runtime Performance of invokedynamic: An evaluation with a java library, IEEE Software, 2014, Vol. 31, Art. 6493308. P. 82-90.
8. Ortin F., Redondo J.M., Baltasar García Perez-Schofield, J.: Efficient virtual machine support of runtime structural reflection. Science of Computer Programming, 2009, 74 (10), p. 836-860.
Link to this article

You can simply select and copy link from below text field.


Other our sites:
Official Website of NOTA BENE / Aurora Group s.r.o.