Хакер - Грязный Джо. Взламываем Java-приложения с помощью dirtyJOE

Хакер - Грязный Джо. Взламываем Java-приложения с помощью dirtyJOE

hacker_frei

https://t.me/hacker_frei

МВК 

Спо­собы обхо­да три­ала в раз­личных прог­раммах — одна из самых инте­рес­ных тем прик­ладно­го реверс‑инжи­нирин­га, и я не уже не раз пос­вящал ей свои статьи. Нас­тало вре­мя вер­нуть­ся к этой темати­ке сно­ва. Наш сегод­няшний паци­ент — при­ложе­ние, выпол­ненное в виде JAR-модуля, которое мы иссле­дуем без пол­ного ревер­са и перес­борки про­екта.

WARNING

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

В замет­ке «В обход стра­жи. Отла­жива­ем код на PHP, упа­кован­ный SourceGuardian» мы рас­смат­ривали прог­рамму, реали­зован­ную в виде локаль­ного веб‑интерфей­са. Работа­ет она так: под Windows запус­кает­ся локаль­ный сер­вер Apache c набором PHP-модулей, а поль­зователь вза­имо­дей­ству­ет с при­ложе­нием через бра­узер, в котором набира­ет адрес localhost. Прог­рамма, взло­мом которой мы зай­мем­ся сегод­ня, дей­ству­ет похожим обра­зом, толь­ко написа­на она на Java и пос­тавля­ется в виде фай­ла .JAR. Наша задача — оту­чить при­ложе­ние от деморе­жима.

По счастью, нам извес­тно, где лежат стар­тующие в виде сер­виса исполня­емые модули прог­раммы в фор­мате .EXE и соот­ветс­тву­ющий JAR-файл. По сво­ей сути JAR — это обыч­ный ZIP-архив, в который упа­кова­ны час­ти про­екта. Пос­коль­ку мы собира­емся пра­вить код, нас инте­ресу­ют модули *.CLASS, содер­жащие откомпи­лиро­ван­ный JVM-байт‑код. Деком­пилято­ров и спо­собов их при­мене­ния мно­жес­тво, сущес­тву­ют даже инс­тру­мен­ты вро­де JD-GUI, спо­соб­ные пол­ностью вос­ста­новить про­ект из исполня­емо­го фай­ла. Чаще все­го взлом­щики исполь­зуют обще­извес­тный JAD, который из‑за его рас­простра­нен­ности лов­кие обфуска­торы дав­но научи­лись обма­нывать, что, в свою оче­редь, ста­ло при­чиной появ­ления более прод­винутых деком­пилято­ров вро­де CFR. Эта вой­на щитов и мечей, пуль и бро­нежи­летов обе­щает быть дол­гой, нам оста­ется толь­ко запас­тись поп­корном. Но не будем тут оста­нав­ливать­ся, а вмес­то это­го пред­положим, что мы деком­пилиро­вали про­ект одним из опи­сан­ных спо­собов до Java-исходни­ков и даже про­ана­лизи­рова­ли получен­ный код.

При­мени­тель­но к нашему подопыт­ному при­ложе­нию это выг­лядело при­мер­но так. Деком­пилиро­вав все‑все‑все CLASS-фай­лы, мы так и не обна­ружи­ли ничего похоже­го на обра­щение к лицен­зии, одна­ко в под­катало­ге BOOT-INF/lib нашего JAR-архи­ва наш­лось мно­жес­тво упа­кован­ных JAR-биб­лиотек, сре­ди которых сра­зу бро­силась в гла­за биб­лиоте­ка license-1.2.12.jar. Рас­паковав и деком­пилиро­вав ее, мы нат­кну­лись на два CLASS-модуля, содер­жащих две любопыт­ные фун­кции. Одна воз­вра­щает демонс­тра­цион­ный режим, вто­рая акти­виру­ет опцию 1 по умол­чанию:

public boolean isDemo() {

return this.getPublicDataHash().isEmpty();

}

public void setDefault() {

if (this.hasModule(1)) {

Iterator iter = this.modulesItems.entrySet().iterator();

while (iter.hasNext()) {

Map.Entry item = iter.next();

if ((Integer)item.getKey() == 1) continue;

((BaseModule)item.getValue()).close();

iter.remove();

this.onModuleUpdated((Integer)item.getKey());

}

} else {

this.closeModules();

if (!this.modulesConfig.containsKey(1)) {

return;

}

BaseModule mod = this.getModule(1);

if (mod != null) {

mod.setEnabled(true);

this.modulesItems.put(1, mod);

log.info("Default module loaded {}", (Object)mod.getName());

this.onModuleUpdated(1);

}

}

}

На­ша задача — сде­лать так, что­бы фун­кция isDemo всег­да воз­вра­щала false, а в фун­кции setDefault нуж­но заменить опцию 1 опци­ей 256. Вот здесь и начина­ется самое инте­рес­ное, то, ради чего и написа­на эта статья.

Ты спро­сишь: раз у нас име­ются в наличии все исходни­ки и код, то почему бы прос­то не переком­пилиро­вать весь про­ект, поменяв эти две про­цеду­ры на нуж­ные? К сожале­нию, пря­мой метод не всег­да самый прос­той. В нашем слу­чае в инте­ресу­ющих нас модулях мно­го зависи­мос­тей, а про­ект очень боль­шой, мно­гие модули силь­но обфусци­рова­ны. Кро­ме того, код вос­ста­новил­ся час­тично с кучей оши­бок, из‑за чего про­ект пол­ностью не соберет­ся. Мож­но, конеч­но, покопать обфуска­цию и поп­робовать руками вытащить исходный текст прог­раммы, но решать эту (воз­можно, даже, гораз­до более слож­ную) задачу ради двух прос­тых пат­чей в коде как‑то лень. Вдо­бавок, перес­борке про­екта может помешать отсутс­твие уста­нов­ленно­го JDK на компь­юте­ре. Уста­нав­ливать его и раз­бирать­ся в осо­бен­ностях ком­пиляции Java-про­ектов мне тоже неохо­та. Поэто­му мы, как обыч­но, ищем самый прос­той путь — патч откомпи­лиро­ван­ного JVM-кода.

В этом нам поможет инте­рес­ная, но мало­извес­тная ути­лита dirtyJOE. Откры­ваем в ней наш CLASS-модуль, на вклад­ке Methods видим пол­ный спи­сок методов клас­са. Находим в нем иско­мую isDemo и тыка­ем в нее, откры­вая окно редак­тирова­ния.

Ок­но редак­тирова­ния dirtyJOE

Это, конеч­но, не исходник на Java, но здесь хотя бы мож­но редак­тировать байт‑код, све­ряясь с логикой исходни­ка. Воз­можнос­ти прог­раммы минима­лис­тичны: редак­тировать мож­но толь­ко в виде hex-зна­чений кодов инс­трук­ций. По счастью, мне­мони­ка и опи­сание текущей исправ­ленной инс­трук­ции отоб­ража­ется в окош­ке над окном кода, а сам спи­сок инс­трук­ций с опи­сани­ем каж­дой име­ется в хел­пе (при­чем толь­ко спи­сок, без опко­дов: явно, что­бы хакерам жизнь медом не казалась и приш­лось искать шес­тнад­цатерич­ные опко­ды инс­трук­ций самос­тоятель­но). По сути, нам надо закоро­тить дан­ную фун­кцию, сде­лав воз­вра­щаемым зна­чени­ем 0 (false). Находим в таб­лице инс­трук­цию помеще­ния 0 на стек (iconst_0), ее опкод (3) и ста­вим ее в самое начало метода, а пос­ле нее — сра­зу воз­врат (ireturn).

Ис­прав­ляем инс­трук­цию

Зак­рыва­ем окно редак­тирова­ния, сох­раня­ем CLASS-модуль, затем меня­ем исправ­ленный модуль в архи­ве license-1.2.12.jar, который, в свою оче­редь, копиру­ем на мес­то ста­рого в основном JAR-модуле. С пред­вку­шени­ем переза­пус­каем прог­рамму и обна­ружи­ваем, что она не работа­ет. Мы что‑то сде­лали не так.

Для понима­ния сути проб­лемы надо искать логи прог­раммы. По счастью, любое Java-при­ложе­ние прак­тичес­ки всег­да пишет свой сис­темный лог, при­чем не один. В нашем слу­чае в логе при­сутс­тву­ет вот такая ошиб­ка:

Caused by: java.lang.IllegalStateException: Unable to open nested entry 'BOOT-INF/lib/license-1.2.12.jar'. It has been compressed and nested jar files must be stored without compression. Please check the mechanism used to create your executable jar file

Те­перь все ясно: для успешно­го чте­ния биб­лиоте­ки Java тре­бует­ся файл с нулевой ком­прес­сией. Оно и понят­но — зачем сжи­мать уже ком­прес­сирован­ный файл? Что ж, сох­раня­ем дан­ную биб­лиоте­ку с нулевой ком­прес­сией, переза­пус­каем — сно­ва неуда­ча. Ошиб­ка в жур­нале на этот раз вооб­ще нев­разуми­тель­ная, исхо­дя из ее логики, сама биб­лиоте­ка license-1.2.12.jar соб­рана как‑то неп­равиль­но. Без­резуль­тат­но пома­явшись некото­рое вре­мя c раз­ными архи­вато­рами, дела­ем логич­ное пред­положе­ние, что проб­лема кро­ется в архи­вато­ре, которым мы собира­ем файл биб­лиоте­ки. Ска­чива­ем род­ной сбор­щик jar.exe из пакета JDK и про­буем соб­рать файл с его помощью. В ито­ге получа­ем новую ошиб­ку:

"C:\Program Files\Java\jdk-17.0.1\bin\jar.exe" -u -f license-1.2.12.jar com\license\service\LicenseHandler.class

java.util.zip.ZipException: duplicate entry: META-INF/maven/org.slf4j/slf4j-api/pom.properties

at java.base/java.util.zip.ZipOutputStream.putNextEntry(ZipOutputStream.java:241)

at java.base/java.util.jar.JarOutputStream.putNextEntry(JarOutputStream.java:115)

at jdk.jartool/sun.tools.jar.Main.update(Main.java:961)

at jdk.jartool/sun.tools.jar.Main.run(Main.java:338)

at jdk.jartool/sun.tools.jar.Main.main(Main.java:1665)

Вне­зап­ная проб­лема воз­никла на ров­ном мес­те: казалось бы, прос­тей­шую опе­рацию сбор­ки фай­лов в один архив не может про­делать кор­рек­тно ни один вин­довый архи­ватор, вклю­чая род­ной сбор­щик JAR. Раз­гадка прос­та: архи­вато­ры работа­ют с модуля­ми как с обыч­ными фай­лами, у которых регис­тро­неза­виси­мые име­на. А име­на Java-клас­сов впол­не себе регис­тро­зави­симые, и хит­рые обфуска­торы дав­но про­сек­ли эту лазей­ку, пере­име­новы­вая модули. В ито­ге про­ект содер­жит мно­жес­тво клас­сов, отли­чающих­ся толь­ко регис­тром одной или более букв в наз­вании. По счастью, дан­ная проб­лема отсутс­тву­ет у раритет­ных кон­соль­ных архи­вато­ров вро­де ZIP или PKZIP, которые в режиме update могут обновлять JAR-модули с регис­тро­зави­симы­ми име­нами. Итак, находим PKZIP, заменя­ем модуль через него, запус­каем — и сно­ва неуда­ча! На этот раз ошиб­ка в логе выг­лядит при­мер­но так:

Constructor threw exception; nested exception is java.lang.VerifyError: Expecting a stack map frame

Exception Details:

Location:

com/license/service/type/LicenseData.isDemo()Z @2: nop

Reason:

Error exists in the bytecode

Bytecode:

0x0000000: 03ac 0015 b600 16ac

В чем смысл дан­ной ошиб­ки? Что­бы понять это, нем­ного углу­бим­ся в теорию. Как извес­тно, Java, так же как и .NET, для опти­миза­ции работы не прос­то интер­пре­тиру­ет свой байт‑код, а ком­пилиру­ет его в натив во вре­мя выпол­нения. Этот про­цесс называ­ется ком­пиляци­ей just in time, или JIT, в од­ной из сво­их пре­дыду­щих ста­тей я рас­ска­зывал о нем при­мени­тель­но к дот­нету. Начиная с 7-й вер­сии Java вве­ла более стро­гую про­вер­ку и нем­ного изме­нила фор­мат клас­са — что­бы содер­жать кар­ту сте­ка, исполь­зуемую для про­вер­ки пра­виль­нос­ти кода. Дан­ная ошиб­ка воз­ника­ет при ком­пиляции байт‑кода метода isDemo: пер­вые две инс­трук­ции, которые мы испра­вили, ком­пилиру­ются успешно, а вот сле­дующая за ними по сме­щению 2 от начала метода (nop или опкод 0) вызыва­ет ошиб­ку верифи­кации, пос­коль­ку у нее нет допус­тимой соот­ветс­тву­ющей кар­ты сте­ка.

По идее, в качес­тве обходно­го пути мож­но было бы добавить -noverify в аргу­мен­ты JVM, что­бы отклю­чить про­вер­ку. В Java 7 так­же -XX:-usesplitverifier поз­воляла исполь­зовать менее стро­гий метод про­вер­ки, но эта опция была уда­лена в Java 8. Разуме­ется, это не наш метод, ведь мы хотим получить пос­ле пат­ча работос­пособ­ный код безо вся­ких кос­тылей, тем более наша задача, как я уже говорил, стар­тует в качес­тве служ­бы. Поп­робу­ем разоб­рать­ся, как про­исхо­дит верифи­кация.

Ком­пилятор раз­бива­ет байт‑код метода на учас­тки по опе­раци­ям вет­вле­ния. Кон­троль­ные точ­ки находят­ся или сра­зу за опе­рато­рами безус­ловных перехо­дов (воз­вра­тов и про­чих тупико­вых веток кода), или в мес­тах, на которые есть перехо­ды. В этих точ­ках кон­тро­лиру­ется сос­тояние сте­ка. Пос­коль­ку логика метода isDemo нас­толь­ко линей­на, что для ее верифи­кации ком­пилятор даже не стал заводить кар­ту сте­ка, то для при­мера возь­мем дру­гую про­цеду­ру, которую нам тре­бует­ся поп­равить, — setDefault. Код ее пос­ле ком­пиляции в JVM коман­ды выг­лядит вот так:

0: aload_0

1: iconst_1

2: invokevirtual #51 // Method hasModule:(I)Z

5: ifeq 101

8: aload_0

9: getfield #4 // Field modulesItems:Ljava/util/concurrent/ConcurrentMap;

12: invokeinterface #20, 1 // InterfaceMethod java/util/concurrent/ConcurrentMap.entrySet:()Ljava/util/Set;

17: invokeinterface #21, 1 // InterfaceMethod java/util/Set.iterator:()Ljava/util/Iterator;

22: astore_1

23: aload_1 // <-------------- Первая контрольная точка, начало цикла, пункт назначения безусловного перехода из #58, #95, стек пуст, локальная переменная iter класса iterator

24: invokeinterface #22, 1 // InterfaceMethod java/util/Iterator.hasNext:()Z

29: ifeq 98

32: aload_1

33: invokeinterface #23, 1 // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;

38: checkcast #24 // class java/util/Map$Entry

41: astore_2

42: aload_2

43: invokeinterface #46, 1 // InterfaceMethod java/util/Map$Entry.getKey:()Ljava/lang/Object;

48: checkcast #47 // class java/lang/Integer

51: invokevirtual #48 // Method java/lang/Integer.intValue:()I

54: iconst_1

55: if_icmpne 61

58: goto 23

61: aload_2 // <-------------- Вторая контрольная точка, сюда есть условный переход из #55, стек пуст, дополнительно к предыдущей локальная переменная Map.Entry item

62: invokeinterface #25, 1 // InterfaceMethod java/util/Map$Entry.getValue:()Ljava/lang/Object;

67: checkcast #8 // class com/license/modules/BaseModule

70: invokevirtual #44 // Method com/license/modules/BaseModule.close:()V

73: aload_1

74: invokeinterface #45, 1 // InterfaceMethod java/util/Iterator.remove:()V

79: aload_0

80: aload_2

81: invokeinterface #46, 1 // InterfaceMethod java/util/Map$Entry.getKey:()Ljava/lang/Object;

86: checkcast #47 // class java/lang/Integer

89: invokevirtual #48 // Method java/lang/Integer.intValue:()I

92: invokespecial #49 // Method onModuleUpdated:(I)V

95: goto 23

98: goto 171 // <-------------- Третья контрольная точка, мало того что следует за глухим безусловным переходом, вдобавок есть условный переход из #29, стек пуст, локальные переменные отсутствуют

101: aload_0 // <-------------- Четвертая контрольная точка, следует за глухим безусловным переходом, конец цикла #5, стек пуст, локальные переменные те же

102: invokespecial #52 // Method closeModules:()V

105: aload_0

106: getfield #7 // Field modulesConfig:Ljava/util/Map;

109: iconst_1

110: invokestatic #10 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;

113: invokeinterface #27, 2 // InterfaceMethod java/util/Map.containsKey:(Ljava/lang/Object;)Z

118: ifne 122

121: return

122: aload_0 // <-------------- Пятая контрольная точка, сюда условный переход из #118, стек пуст, локальные переменные те же

123: iconst_1

124: invokespecial #53 // Method getModule:(I)Lcom/license/modules/BaseModule;

127: astore_1

128: aload_1

129: ifnull 171

132: aload_1

133: iconst_1

134: invokevirtual #54 // Method com/license/modules/BaseModule.setEnabled:(Z)V

137: aload_0

138: getfield #4 // Field modulesItems:Ljava/util/concurrent/ConcurrentMap;

141: iconst_1

142: invokestatic #10 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;

145: aload_1

146: invokeinterface #55, 3 // InterfaceMethod java/util/concurrent/ConcurrentMap.put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;

151: pop

152: getstatic #31 // Field log:Lorg/slf4j/Logger;

155: ldc #56 // String Default module loaded {}

157: aload_1

158: invokevirtual #57 // Method com/license/modules/BaseModule.getName:()Ljava/lang/String;

161: invokeinterface #58, 3 // InterfaceMethod org/slf4j/Logger.info:(Ljava/lang/String;Ljava/lang/Object;)V

166: aload_0

167: iconst_1

168: invokespecial #49 // Method onModuleUpdated:(I)V

171: return // <-------------- Последняя контрольная точка, сюда условный переход из #129, стек пуст, локальные переменные те же

Те­перь рас­смот­рим кар­ту сте­ка, которую ком­пилятор сге­нери­ровал для дан­ной про­цеду­ры. К сожале­нию, dirtyJOE дос­таточ­но ста­рый и сырой инс­тру­мент, что­бы пра­вить или хотя бы отоб­ражать кар­ту сте­ка. Мак­симум, что он может показать, — это ее наличие в виде атри­бута метода StackMapTable. Поэто­му для прос­мотра кар­ты сте­ка вос­поль­зуем­ся стан­дар­тной ути­литой javap из пакета JDK:

"C:\Program Files\Java\jdk-17.0.1\bin\javap.exe" -v LicenseModules.class

StackMapTable: number_of_entries = 6 <----- Шесть фреймов всего

frame_type = 252 /* append */ <----- Первая точка, тип append означает, что фрейм имеет те же локальные переменные, что и предыдущий (которого у нас нет, так как фрейм первый), за исключением того, что определены k дополнительных локальных переменных и что стек операндов пуст. Значение k определяется формулой frame_type – 251 = 252 – 251 = 1, локальная переменная

offset_delta = 23 <----- Смещение от начала модуля

locals = [ class java/util/Iterator ] <----- Тип локальной переменной

frame_type = 252 /* append */ <---- То же самое, что и предыдущий, но добавилась локальная переменная 252 – 251 = 1

offset_delta = 37 <-----Смещение от предыдущего фрейма, то есть 24 + 37 = 61

locals = [ class java/util/Map$Entry ] <----- Тип новой локальной переменной

frame_type = 249 /* chop */ <---- Такой тип фрейма имеет те же локальные переменные, что и предыдущий фрейм, за исключением того, что отсутствуют последние k локальных переменных и что стек операндов пуст. Значение k определяется формулой 251 – frame_type = 251 – 249 = 2, две локальные переменные убираются

offset_delta = 36 <----- Смещение от предыдущего фрейма, то есть 62 + 36 = 98

frame_type = 2 /* same */ <----- Этот тип фрейма указывает, что фрейм имеет точно такие же локальные переменные, что и предыдущий фрейм, и что стек операндов пуст. Смещение определяется типом, то есть 99 + 2 = 101

frame_type = 20 /* same */ <----- То же, что и предыдущий, смещение 102 + 20 = 122

frame_type = 48 /* same */ <----- То же, что и предыдущий, смещение 123 + 48 = 171

Итак, я наде­юсь, мне уда­лось донес­ти в этом при­мере логику работы верифи­като­ра через stack map. Что нам это дает на прак­тике? Во‑пер­вых, ста­новит­ся понят­но, почему не работа­ет наш пер­воначаль­ный патч isDemo: исходный код был линей­ным и никакой верифи­кации через фрей­мы сте­ка ему не тре­бова­лось, а наша прав­ка мало того, что добави­ла кон­троль­ную точ­ку (сле­дующий байт за глу­хим ireturn), так еще и сде­лала хвост метода безум­ным для ком­пилято­ра. Пос­коль­ку спо­соба быс­тро и прос­то уко­ротить раз­мер кода метода через dirtyJOE нет, то самый прос­той метод добить­ся успешно­го про­хож­дения нашим кодом верифи­кации — забить все тело nop’ами и толь­ко в кон­це оста­вить return false:

00000000 : nop

00000001 : nop

00000002 : nop

00000003 : nop

00000003 : nop

00000005 : iconst_0

00000006 : ireturn

Во­обще говоря, стра­тегия пат­ча в дан­ном слу­чае — сле­дить за кон­троль­ными точ­ками фрей­мов сте­ка и при прав­ке кода ста­рать­ся не выходить за их пре­делы или хотя бы сле­дить, что­бы клас­сы локаль­ных перемен­ных и зна­чений на сте­ке пос­ле прав­ки соот­ветс­тво­вали друг дру­гу. Мож­но, конеч­но, при желании пра­вить и сами атри­буты StackMapTable в шес­тнад­цатерич­ном редак­торе, но этот край­ний слу­чай мы оста­вим для дру­гой статьи. Чуть не забыл напом­нить, что при слож­ной прав­ке сто­ит учи­тывать верифи­кацию области видимос­ти локаль­ных перемен­ных (атри­бут localVariableTable) и бло­ков обра­бот­ки исклю­чений (окно Exceptions). По счастью, редак­тирова­ние этих парамет­ров дос­таточ­но эле­мен­тарно и под­держи­вает­ся в dirtyJOE.

Ре­дак­тирова­ние парамет­ров в dirtyJOE

Мо­жет показать­ся, что учесть все выше­опи­сан­ные тре­бова­ния — чудовищ­но слож­ная задача, осо­бен­но ког­да метод исполь­зует весь­ма раз­вет­влен­ную логику, а прав­ки уве­личи­вают раз­мер кода. Тем не менее это толь­ко на пер­вый взгляд: при дос­таточ­ной сно­ров­ке впол­не реаль­но най­ти необя­затель­ные мес­та в коде, бла­года­ря опти­миза­ции которых мож­но рас­ширить нуж­ные. Я спе­циаль­но выб­рал такой метод (setDefault), в котором за счет раз­ницы в дли­нах команд (коман­да iconst_1 занима­ет один байт, а коман­да для замены sipush 256 — целых три) код сущес­твен­но удли­няет­ся. Тем не менее, имея пред­став­ление о прин­ципах верифи­кации, даже в этом слу­чае дос­таточ­но быс­тро мож­но смас­терить хоть и не иде­аль­ный, но впол­не рабочий патч, кор­рек­тно про­ходя­щий верифи­кацию и откры­вающий нуж­ный режим в прог­рамме:

00000000 2A aload_0

00000001 04 iconst_1

00000002 B6 00 33 invokevirtual boolean com.license.modules.LicenseModules.hasModule(int)

00000005 99 00 60 ifeq pos.00000065

00000008 2A aload_0

00000009 B4 00 04 getfield java.util.concurrent.ConcurrentMap com.license.modules.LicenseModules.modulesItems

0000000C B9 00 14 01 00 invokeinterface java.util.Set java.util.concurrent.ConcurrentMap.entrySet(), 1

00000011 B9 00 15 01 00 invokeinterface java.util.Iterator java.util.Set.iterator(), 1

00000016 4C astore_1

00000017 2B aload_1

00000018 B9 00 16 01 00 invokeinterface boolean java.util.Iterator.hasNext(), 1

0000001D 99 00 45 ifeq pos.00000062

00000020 2B aload_1

00000021 B9 00 17 01 00 invokeinterface java.lang.Object java.util.Iterator.next(), 1

00000026 C0 00 18 checkcast java.util.Map$Entry

00000029 4D astore_2

0000002A 2C aload_2

0000002B B9 00 2E 01 00 invokeinterface java.lang.Object java.util.Map$Entry.getKey(), 1

00000030 C0 00 2F checkcast java.lang.Integer

00000033 B6 00 30 invokevirtual int java.lang.Integer.intValue()

00000036 04 iconst_1

00000037 A0 00 06 if_icmpne pos.0000003D

0000003A A7 FF DD goto pos.00000017

0000003D 2C aload_2

0000003E B9 00 19 01 00 invokeinterface java.lang.Object java.util.Map$Entry.getValue(), 1

00000043 C0 00 08 checkcast com.license.modules.BaseModule

00000046 B6 00 2C invokevirtual void com.license.modules.BaseModule.close()

00000049 2B aload_1

0000004A B9 00 2D 01 00 invokeinterface void java.util.Iterator.remove(), 1

0000004F 2A aload_0

00000050 2C aload_2

00000051 B9 00 2E 01 00 invokeinterface java.lang.Object java.util.Map$Entry.getKey(), 1

00000056 C0 00 2F checkcast java.lang.Integer

00000059 B6 00 30 invokevirtual int java.lang.Integer.intValue()

0000005C B7 00 31 invokespecial void com.license.modules.LicenseModules.onModuleUpdated(int)

0000005F A7 FF B8 goto pos.00000017

00000062 A7 00 49 goto pos.000000AB

00000065 2A aload_0

00000066 B7 00 34 invokespecial void com.license.modules.LicenseModules.closeModules()

00000069 2A aload_0

0000006A B4 00 07 getfield java.util.Map com.license.modules.LicenseModules.modulesConfig

0000006D 04 iconst_1

0000006E B8 00 0A invokestatic java.lang.Integer java.lang.Integer.valueOf(int)

00000071 B9 00 1B 02 00 invokeinterface boolean java.util.Map.containsKey(java.lang.Object), 2

00000076 9A 00 04 ifne pos.0000007A

00000079 B1 return

0000007A 2A aload_0

0000007B 11 01 00 sipush 256

0000007E B7 00 35 invokespecial com.license.modules.BaseModule com.license.modules.LicenseModules.getModule(int)

00000081 4C astore_1

00000082 00 nop

00000083 00 nop

00000084 2B aload_1

00000085 04 iconst_1

00000086 B6 00 36 invokevirtual void com.license.modules.BaseModule.setEnabled(boolean)

00000089 2A aload_0

0000008A B4 00 04 getfield java.util.concurrent.ConcurrentMap com.license.modules.LicenseModules.modulesItems

0000008D 11 01 00 sipush 256

00000090 B8 00 0A invokestatic java.lang.Integer java.lang.Integer.valueOf(int)

00000093 2B aload_1

00000094 B9 00 37 03 00 invokeinterface java.lang.Object java.util.concurrent.ConcurrentMap.put(java.lang.Object, java.lang.Object), 3

00000099 57 pop

0000009A 2A aload_0

0000009B 11 01 00 sipush 256

0000009E 00 nop

0000009F B7 00 31 invokespecial void com.license.modules.LicenseModules.onModuleUpdated(int)

000000A2 00 nop

000000A3 00 nop

000000A4 00 nop

000000A5 00 nop

000000A6 00 nop

000000A7 00 nop

000000A8 00 nop

000000A9 00 nop

000000AA 00 nop

000000AB B1 return

Как видишь, нес­мотря на сырость и заб­рошен­ность про­екта (пос­ледняя вер­сия 1.7 (c529) была опуб­ликова­на на офи­циаль­ном сай­те аж в кон­це 2014 года), dirtyJOE пред­став­ляет собой весь­ма полез­ный инс­тру­мент, незаме­нимый для пат­ча обфусци­рован­ных про­ектов и при­ложе­ний, нак­рытых про­тек­торами. Помимо опи­сан­ных выше, у него мас­са дру­гих полез­ных фич: с его помощью мож­но редак­тировать и добав­лять новые кон­стан­ты и поля (мож­но добав­лять даже новые методы, прав­да пус­тые). Для рас­шифров­ки крип­тован­ных строк есть воз­можность под­клю­чить поль­зователь­ские скрип­ты на питоне, сама прог­рамма име­ет 32- и 64-бит­ные вер­сии и даже сущес­тву­ет в виде пла­гина к Total Commander. Наде­юсь, что зна­комс­тво с дан­ной ути­литой поможет тебе осва­ивать реверс и пат­чинг JVM-при­ложе­ний.

Читайте ещё больше платных статей бесплатно: https://t.me/hacker_frei

Report Page