Библиотека
|
ваш профиль |
Кибернетика и программирование
Правильная ссылка на статью:
Коробейников А.Г., Кутузов И.М.
Обфускация сокрытия вызовов при помощи инструкции invokedynamic
// Кибернетика и программирование.
2016. № 5.
С. 33-37.
DOI: 10.7256/2306-4196.2016.5.18686 URL: https://nbpublish.com/library_read_article.php?id=18686
Обфускация сокрытия вызовов при помощи инструкции invokedynamic
DOI: 10.7256/2306-4196.2016.5.18686Дата направления статьи в редакцию: 07-04-2016Дата публикации: 19-10-2016Аннотация: Объектом исследования в данной статье являются технологии сокрытия вызовов методов. Сокрытие вызовов требуется для сокрытия: зависимости сущностей; логики обработки данных; алгоритмов. Методы, используемые для сокрытия вызовов сильно ограничены технологиями языка и их производительностью. Вызов метода может осуществляться как напрямую, так и: через бутстрап-метод; из нативного кода (JNI); через Reflection; используя JRE 1.7, InvokeDynamic. Представлены примеры с исходным кодом. Сделан вывод, что наиболее перспективной среди рассмотренных методов является технология invokedynamic. В данной работе дан анализ технологий сокрытия вызовов методов, таких как Бутстрап-метод, вызов методов через нативный код, обфускация вызовов с помощью Reflection и InvokeDynamic. В данной статье рассмотрены различные способы сокрытия вызова методов. Рассмотрены характерные особенности обфускации для наиболее популярных из них. Наиболее перспективной среди рассмотренных методов выглядит технология invokedynamic. Она позволяет полностью убрать из исходных кодов сигнатуру метода, оставив лишь служебную информацию для бутстрап-метода. При надлежащей реализации бутстрап-метода возможно создание байт-кода, который будет невозможно декомпилировать в валидный код Java, Groovy или Scala. Ключевые слова: Защита информации, обфускация, Бутстрап-метод, нативный код, механизм Reflection, InvokeDynamic, Java, исходный текс, вызов метода, листинг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Введение Защита интеллектуальной собственности и конкурентных преимуществ в современных условиях является обязательными требованиями. В настоящее время для защиты программного обеспечения используются различные технологии, затрудняющие анализ приложений потенциальными злоумышленниками. Обфускация, как защита программного кода является очень перспективным направлением по защите программных продуктов от несанкционированных действий [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. Библиография
1. Коробейников А.Г., Кутузов И.М., Колесников П.Ю. Применение методов обфускации // Информационные технологии в профессиональной деятельности и научной работе: сборник материалов Всероссийской научно-практической конференции с международным участием: в 2 ч. Йошкар-Ола: Марийский государственный технический университет, 2012. Т. 1. С. 24-28. - ISBN 978-5-8158-1002-0.
2. Коробейников А.Г., Кутузов И.М., Колесников П.Ю., Макаров С.П., Ахапкина И.Б. Анализ применения методов обфускации // В книге “Труды конгресса по интеллектуальным системам и информационным технологиям AIS-IT’12. Научное издание в 4-х томах. М.: Физматлит, 2012. Т. 2. С. 203-208. 3. Коробейников А.Г., Кутузов И.М. Исследование алгоритма обфускации // Информационные технологии в профессиональной деятельности и научной работе: сборник материалов Всероссийской научно-практической конференции с международным участием: в 2 ч. Йошкар-Ола: Поволжский государственный технологический университет, 2013. Ч. 1. 227 с. – ISBN 978-5-8158-1002-0. 4. Коробейников А.Г., Кутузов И.М. Алгоритм обфускации // NB: Кибернетика и программирование. 2013. № 3. С. 1-8. DOI: 0.7256/2306-4196.2013.3.9356. URL: http://e-notabene.ru/kp/article_9356.html 5. Коробейников А.Г., Ахапкина И.Б, Безрук Н.В., Демина Е.А., Ямщикова Н.В., Кутузов И.М. Модификация и анализ алгоритма обфускации // В книге “Труды конгресса по интеллектуальным системам и информационным технологиям AIS-IT’13. Научное издание в 4-х томах. М.: Физматлит, 2013. Т. 2. С. 163-166. – ISBN 978-5-9221-1479-0. 6. Коробейников А.Г., Кутузов И.М., Колесников П.Ю. Анализ методов обфускации // Кибернетика и программирование. 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. 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. |