В обход стражи. Как вскрывают приложения, защищенные аппаратным ключом Sentinel

Ре­верс‑инжи­ниринг написан­ных на Java при­ложе­ний, если раз­работ­чики не позабо­тились заранее об их защите, обыч­но не пред­став­ляет труд­ностей. Имен­но что­бы усложнить жизнь хакерам и иссле­дова­телям, исполь­зуют­ся раз­личные инс­тру­мен­ты, один из которых — защита с аппа­рат­ным клю­чом Sentinel. Сегод­ня мы рас­смот­рим спо­соб обхо­да такой защиты.

Тем, кто читал мои пре­дыду­щие статьи, пос­вящен­ные за­щите при­ложе­ний на Java, извес­тно самое сла­бое мес­то такой защиты: проз­рачность байт‑кода и невыно­симая лег­кость вос­ста­нов­ления его до исходни­ков.

По­это­му раз­работ­чики таких инс­тру­мен­тов стре­мят­ся как мож­но хит­рее спря­тать код от любопыт­ных глаз хакеров, исполь­зуя раз­личные при­емы: обфуска­цию, ком­пиляцию в натив, шиф­рование. Наг­лядный при­мер пос­ледне­го мы начали раз­бирать в статье «Без­защит­ная Java. Лома­ем Java bytecode encryption». Тог­да мы оста­нови­лись на инлайн‑пат­чинге рас­шифро­ван­ного байт‑кода пря­мо из биб­лиоте­ки аген­та JVMTI. Сегод­ня мы про­дол­жим эту тему и научим­ся пат­чить непос­редс­твен­но шиф­рован­ный код.

В качес­тве при­мера мы возь­мем при­ложе­ние, защищен­ное Sentinel Licensing API Java Class Library — спе­циаль­ной биб­лиоте­кой защиты Java-клас­сов, пос­тавля­емой к клю­чам типа Hasp. Почитать о том, как устро­ена эта биб­лиоте­ка, мож­но на сай­те раз­работ­чика. Само при­ложе­ние защище­но клю­чом Hasp, в его рабочем катало­ге при­сутс­тву­ют харак­терные для Sentinel фай­лы hasp_rt.exe, hasp_windows_x64_34344.dll, haspvlib_34344.dll и спе­цифи­чес­кие для Java HASPJava.dll, HASPJava_x64.dll, sntljavaclsrt.dll, sntljavaclsrt_x64.dll. Detect It Easy пред­ска­зуемо ука­зыва­ет на NetHASP dongle reference и Hardlock dongle reference.

Мы по опы­ту зна­ем, что ломать эти биб­лиоте­ки «в лоб» луч­ше даже не пытать­ся, они весь­ма сурово вир­туали­зова­ны и защище­ны от отладки и модифи­кации. Поэто­му смот­рим на ком­пилиро­ван­ные клас­сы, содер­жащи­еся внут­ри JAR. Там тоже все мрач­но: кро­ме глав­ного клас­са, все осталь­ные пошиф­рованы с высокой энтро­пией, при­мер­но как опи­сано в статье «Без­защит­ная Java. Лома­ем Java bytecode encryption». За исклю­чени­ем того, что крип­тор под­клю­чает­ся не как JavaAgent при запус­ке при­ложе­ния, а из глав­ного клас­са, по сути пред­став­ляюще­го собой заг­рузчик при­ложе­ния c единс­твен­но откры­тым кодом:

public static void main(String[] stringArray) {try { String string; String string2 = System.getProperty("java.class.path"); int n = Math.max(string2.lastIndexOf(""), string2.lastIndexOf("/")); String string3 = string = string2.substring(0, ++n); if (JavaClsEntry.isWindows()) { string3 = string3 + "sntljavaclsrt"; if (0 == System.getProperty("sun.arch.data.model").compareTo("64")) { string3 = string3 + "_x64"; } string3 = string3 + ".dll"; } else if (JavaClsEntry.isLinux()) { string3 = string3 + "libsntljavaclsrt_x86_64.so"; } else { return; } File file = new File(string3); string3 = file.getAbsolutePath(); System.load(string3); Class<?> clazz = Class.forName("com.MainApp"); Method method = clazz.getMethod("main", String[].class); method.invoke(null, new Object[]{stringArray});}catch (Exception exception) { exception.printStackTrace();}

Заг­рузчик ничего не дела­ет, кро­ме как заг­ружа­ет сен­тинелов­скую натив­ную биб­лиоте­ку защиты, а затем глав­ный класс прог­раммы. Если ключ не в поряд­ке, то глав­ный класс поп­росту не рас­шифро­выва­ется и при­ложе­ние рушит­ся по неп­равиль­ному фор­мату байт‑кода клас­са. Собс­твен­но, даже такую ошиб­ку мож­но получить толь­ко пос­ле того, как биб­лиоте­ка выдаст окош­ко отсутс­твия клю­ча.

Как же нам теперь быть? Ведь, пос­коль­ку здесь не про­исхо­дит пря­мая под­мена JIT-ком­пилято­ра через JavaAgent, пред­ложен­ный в упо­мяну­той выше статье спо­соб не годит­ся. Поп­робу­ем под­клю­чить­ся отладчи­ком непос­редс­твен­но к JIT-ком­пилято­ру. Нем­ного покурив докумен­тацию, обна­ружи­ваем в биб­лиоте­ке JVM.DLL такую фун­кцию:

JVM_DefineClassWithSource(JNIEnv *env, const char *name, jobject loader, const jbyte *buf, jsize len, jobject pd, const char *source)

Как вид­но из опи­сания, эта фун­кция ком­пилиру­ет класс из мас­сива байт‑кода. Дли­на мас­сива, наз­вание клас­са и даже исходно­го Java-модуля при­лага­ется. При наличии клю­ча, уста­новив в нашем любимом отладчи­ке x64dbg точ­ку оста­нова на вход этой фун­кции, мы получа­ем в регис­тре R9 рас­шифро­ван­ный класс, начиная с сиг­натуры CAFEBABE, и его дли­ну в регис­тре R14. В ито­ге его мож­но лег­ко сдам­пить на диск сле­дующей коман­дой:

savedata MainApp.class, R9, R14

Источник: xakep.ru

Ответить

Ваш адрес email не будет опубликован. Обязательные поля помечены *