Реконструирование Java-программ

Дата публикации 21 сен 2002

Реконструирование Java-программ — Архив WASM.RU

Декомпиляция Java-программ

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

Что такое Java?

Термин "Java" включает в себя несколько различных концепций:

  • Язык программирования
  • Спецификации языка
  • Среда выполнения и API

Вот небольшое пояснение к вышесказанному: после многочисленных экспериментов по созданию языка программирования для своих инженеров SUN решила изобрести современный (по ее утверждению) язык, основанный на принципах объектно-ориентированного программирования, очень модному в то время. В архитектуру ООП входят (среди прочих) такие концепции как наследование и полиморфизм.

Это означает, что объекты расширяют и/или реализуют другие объекты, используя функции вышестоящих класов, от которых они наследуются, что в результате ведет к повышению реюзабельности кода (панацея программирования).

В то время, как SUN выпускала спецификации языка, она решила, что компиляция программы, написанной на Java, будет происходить в байткод, который будет выполняться специальным интерпретатор. Этот интерпретатор называется виртуальной JAVA-машиной (JVM).

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

Что представляет из себя JAVA?

Каждый объект в JAVA находится в отдельном файле. Один или более объектов можно сгруппировать в пакет (pakage'и). Файлы группируются в Java-архив (JAR) или в архив, сходный по формату с ZIP.

Каждый класс объявляется определенным образом. Например:

Код (Text):
  1.  
  2. public class MyClase
  3. {
  4. }

У каждого класса может быть (а может и не быть) один или несколько конструкторов, которые отличаются друг от друга количеством или типом аргументов:

Код (Text):
  1.  
  2. public class MiClase
  3. {
  4.         public MiClase()
  5.         {
  6.         }
  7.  
  8.         public MiClase(int i)
  9.         {
  10.         }
  11. }

У каждого класса может быть (а может и не быть) один или больше методов.

Также классы бывают разных типов: публичными, частными, финальными и виртуальными... Методы могут быть публичными, частными, статическими, финальными, ...

Класс может расширять функциональность другого.

Базовые типы аргументов методов и переменных могут быть следующие: boolean, char, byte, short, int, long, float, double. Эти типы являются системными. Им соответствуют следующие классы: java.lang.Integer, java.lang.String, java.net.ContentHandler, java.rmi.NotBoundException, ...

Важным классом является java.lang.Class. Экземпляры класса Class представляют классы и интерфейсов Java-приложения. JVM предоставляет механизм установки каждого объекта, как экземпляра класса Class.

Таким образом, то, насколько быстро будет работать программа, зависит не только от того, как написана программа, как она скомпилирована или вычислительной мощности компьютера, но и от того, какие виды оптимизации умеет выполнять JVM: техники кэширования, дублирование объектов, таблиц, сборка мусора, индексация поиска методов, ...

Первые шаги

Предположим, что у нас есть программа, написанная на Java, которую необходимо скомпилировать. Для этого нам необходимо иметь компилятор Java. В частности, SUN бесплатно распространяет JDK, который включает в себя компилятор javac.

Вызываем

Код (Text):
  1.  
  2. javac  MiClase.java

Он запускается следующим образом:

Код (Text):
  1.  
  2. javac  MiClase.java

На выходе получаем файл 'MiClase.class', который содержит Java-байткод.

Теперь, чтобы эта программа была переведена в машинные коды и выполнена, необходим Java-интерпретатор (который также включен в JDK). Код, генерируемый интерпретатором, зависит от процессора, на котором выполняется программа.

Запустить программу можно следующим образом:

Код (Text):
  1.  
  2. java MiClase

Введение в суть дела

Всю необходимую информацию относительно спецификации JavaTM Virtual Machine, можно получить на сайте java.sun.com.

Архив каждого класса начинается со структуры типа ClassFile

Код (Text):
  1.  
  2.     {
  3.         u4 magic;
  4.         u2 minor_version;
  5.         u2 major_version;
  6.         u2 constant_pool_count;
  7.         cp_info constant_pool[constant_pool_count-1];
  8.         u2 access_flags;
  9.         u2 this_class;
  10.         u2 super_class;
  11.         u2 interfaces_count;
  12.         u2 interfaces[interfaces_count];
  13.         u2 fields_count;
  14.         field_info fields[fields_count];
  15.         u2 methods_count;
  16.         method_info methods[methods_count];
  17.         u2 attributes_count;
  18.         attribute_info attributes[attributes_count];
  19.     }

Например, поле 'magic' занимает 4 байта и всегда равно 0xCAFEBABE. В общем случае данные этой структуры представляют массивы элементов размером в слово, при этом каждому массиву предшествует поле, указывающее размер этого массива. Например, methods_count содержит в себе количество методов (функций или подпрограмм), которые содержит класс, в то время как methods[methods_count] является массивом структур типа method_info. Эта информация генерируется компилятором и предназначается для JVM.

Поскольку эта информация показывает, сколько класс занимает места в памяти, JVM имеет механизм верификации классов.

Этот процесс выполняется следующим образом:

Фаза 1:
- поле magic верно

Фаза 2:
- у класса с типом final (финальный) не может быть наследников
- класс наследуется от другого, хотя бы java.lang.Class или java.lang.Object
- область констант непрерывна
- у всех поля и методы принадлежат верным классам, имена и типы полей и методов также верны.

Фаза 3:

- запук программы возможно только, если:

  • присутствуют все необходимые операторы
  • куча содержит данные верных типов
  • переменные имеют определенное значение (включая void и null)
  • вызовы методов используют правильные аргументы
  • у всех опкодов верные аргументы

Фаза 4:

- некоторые проверки фазы 3 выполняются во время запуска класса.

Как и все языки, основывающиеся на байткодах, JVM имеет ряд регистров и структур для внутреннего использования. Например, программный счетчик (pc) содержит адрес выполняющейся JVM инструкции для каждого треда. Помните, что по определению при выполнении JAVA-программы создается несколько тредов, выполняющихся одновременно. Также существуют различные кучи данных (стек) по одной для каждого из выполняющихся тредов. Существует одна уникальная куча, использующаяся для хранения объектов.

Другой важной зоной является область методов, в которой хранятся байткоды классов. И еще одна зона с константами. И еще одна с "родными"(native)-методами (процедуры, написанные не на JAVA, а на другом языке, как правило C, и предназначающиеся для вызовов в Java и вызовов методов DLL; соответственно, эти процедуры хранятся не в байткодах).

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

Более подробное рассмотрение

Опкоды организованы по типам:

  • Присвоение и чтение
  • Арифметические инструкции
  • Конвертирование типов
  • Создание объектов и манипулирование ими
  • Управление кучей
  • Контроль передачи данных
  • Вызов методов
  • Исключения

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

Чтобы байткоды могли быть интерпретированы какой-либо JVA, они должны соответствовать определенным спецификациям (также установленным SUN'ом), в которых задаются возможные инструкции и их операнды.

Вот таблица возможных опкодов:

Код (Text):
  1.  
  2. <++>java/opcodes.txt
  3. 00 (0x00) nop
  4. 01 (0x01) aconst_null
  5. 02 (0x02) iconst_m1
  6. 03 (0x03) iconst_0
  7. 04 (0x04) iconst_1
  8. 05 (0x05) iconst_2
  9. 06 (0x06) iconst_3
  10. 07 (0x07) iconst_4
  11. 08 (0x08) iconst_5
  12. 09 (0x09) lconst_0
  13. 10 (0x0a) lconst_1
  14. 11 (0x0b) fconst_0
  15. 12 (0x0c) fconst_1
  16. 13 (0x0d) fconst_2
  17. 14 (0x0e) dconst_0
  18. 15 (0x0f) dconst_1
  19. 16 (0x10) bipush
  20. 17 (0x11) sipush
  21. 18 (0x12) ldc
  22. 19 (0x13) ldc_w
  23. 20 (0x14) ldc2_w
  24. 21 (0x15) iload
  25. 22 (0x16) lload
  26. 23 (0x17) fload
  27. 24 (0x18) dload
  28. 25 (0x19) aload
  29. 26 (0x1a) iload_0
  30. 27 (0x1b) iload_1
  31. 28 (0x1c) iload_2
  32. 29 (0x1d) iload_3
  33. 30 (0x1e) lload_0
  34. 31 (0x1f) lload_1
  35. 32 (0x20) lload_2
  36. 33 (0x21) lload_3
  37. 34 (0x22) fload_0
  38. 35 (0x23) fload_1
  39. 36 (0x24) fload_2
  40. 37 (0x25) fload_3
  41. 38 (0x26) dload_0
  42. 39 (0x27) dload_1
  43. 40 (0x28) dload_2
  44. 41 (0x29) dload_3
  45. 42 (0x2a) aload_0
  46. 43 (0x2b) aload_1
  47. 44 (0x2c) aload_2
  48. 45 (0x2d) aload_3
  49. 46 (0x2e) iaload
  50. 47 (0x2f) laload
  51. 48 (0x30) faload
  52. 49 (0x31) daload
  53. 50 (0x32) aaload
  54. 51 (0x33) baload
  55. 52 (0x34) caload
  56. 53 (0x35) saload
  57. 54 (0x36) istore
  58. 55 (0x37) lstore
  59. 56 (0x38) fstore
  60. 57 (0x39) dstore
  61. 58 (0x3a) astore
  62. 59 (0x3b) istore_0
  63. 60 (0x3c) istore_1
  64. 61 (0x3d) istore_2
  65. 62 (0x3e) istore_3
  66. 63 (0x3f) lstore_0
  67. 64 (0x40) lstore_1
  68. 65 (0x41) lstore_2
  69. 66 (0x42) lstore_3
  70. 67 (0x43) fstore_0
  71. 68 (0x44) fstore_1
  72. 69 (0x45) fstore_2
  73. 70 (0x46) fstore_3
  74. 71 (0x47) dstore_0
  75. 72 (0x48) dstore_1
  76. 73 (0x49) dstore_2
  77. 74 (0x4a) dstore_3
  78. 75 (0x4b) astore_0
  79. 76 (0x4c) astore_1
  80. 77 (0x4d) astore_2
  81. 78 (0x4e) astore_3
  82. 79 (0x4f) iastore
  83. 80 (0x50) lastore
  84. 81 (0x51) fastore
  85. 82 (0x52) dastore
  86. 83 (0x53) aastore
  87. 84 (0x54) bastore
  88. 85 (0x55) castore
  89. 86 (0x56) sastore
  90. 87 (0x57) pop
  91. 88 (0x58) pop2
  92. 89 (0x59) dup
  93. 90 (0x5a) dup_x1
  94. 91 (0x5b) dup_x2
  95. 92 (0x5c) dup2
  96. 93 (0x5d) dup2_x1
  97. 94 (0x5e) dup2_x2
  98. 95 (0x5f) swap
  99. 96 (0x60) iadd
  100. 97 (0x61) ladd
  101. 98 (0x62) fadd
  102. 99 (0x63) dadd
  103. 100 (0x64) isub
  104. 101 (0x65) lsub
  105. 102 (0x66) fsub
  106. 103 (0x67) dsub
  107. 104 (0x68) imul
  108. 105 (0x69) lmul
  109. 106 (0x6a) fmul
  110. 107 (0x6b) dmul
  111. 108 (0x6c) idiv
  112. 109 (0x6d) ldiv
  113. 110 (0x6e) fdiv
  114. 111 (0x6f) ddiv
  115. 112 (0x70) irem
  116. 113 (0x71) lrem
  117. 114 (0x72) frem
  118. 115 (0x73) drem
  119. 116 (0x74) ineg
  120. 117 (0x75) lneg
  121. 118 (0x76) fneg
  122. 119 (0x77) dneg
  123. 120 (0x78) ishl
  124. 121 (0x79) lshl
  125. 122 (0x7a) ishr
  126. 123 (0x7b) lshr
  127. 124 (0x7c) iushr
  128. 125 (0x7d) lushr
  129. 126 (0x7e) iand
  130. 127 (0x7f) land
  131. 128 (0x80) ior
  132. 129 (0x81) lor
  133. 130 (0x82) ixor
  134. 131 (0x83) lxor
  135. 132 (0x84) iinc
  136. 133 (0x85) i2l
  137. 134 (0x86) i2f
  138. 135 (0x87) i2d
  139. 136 (0x88) l2i
  140. 137 (0x89) l2f
  141. 138 (0x8a) l2d
  142. 139 (0x8b) f2i
  143. 140 (0x8c) f2l
  144. 141 (0x8d) f2d
  145. 142 (0x8e) d2i
  146. 143 (0x8f) d2l
  147. 144 (0x90) d2f
  148. 145 (0x91) i2b
  149. 146 (0x92) i2c
  150. 147 (0x93) i2s
  151. 148 (0x94) lcmp
  152. 149 (0x95) fcmpl
  153. 150 (0x96) fcmpg
  154. 151 (0x97) dcmpl
  155. 152 (0x98) dcmpg
  156. 153 (0x99) ifeq
  157. 154 (0x9a) ifne
  158. 155 (0x9b) iflt
  159. 156 (0x9c) ifge
  160. 157 (0x9d) ifgt
  161. 158 (0x9e) ifle
  162. 159 (0x9f) if_icmpeq
  163. 160 (0xa0) if_icmpne
  164. 161 (0xa1) if_icmplt
  165. 162 (0xa2) if_icmpge
  166. 163 (0xa3) if_icmpgt
  167. 164 (0xa4) if_icmple
  168. 165 (0xa5) if_acmpeq
  169. 166 (0xa6) if_acmpne
  170. 167 (0xa7) goto
  171. 168 (0xa8) jsr
  172. 169 (0xa9) ret
  173. 170 (0xaa) tableswitch
  174. 171 (0xab) lookupswitch
  175. 172 (0xac) ireturn
  176. 173 (0xad) lreturn
  177. 174 (0xae) freturn
  178. 175 (0xaf) dreturn
  179. 176 (0xb0) areturn
  180. 177 (0xb1) return
  181. 178 (0xb2) getstatic
  182. 179 (0xb3) putstatic
  183. 180 (0xb4) getfield
  184. 181 (0xb5) putfield
  185. 182 (0xb6) invokevirtual
  186. 183 (0xb7) invokespecial
  187. 184 (0xb8) invokestatic
  188. 185 (0xb9) invokeinterface
  189. 186 (0xba) xxxunusedxxx1
  190. 187 (0xbb) new
  191. 188 (0xbc) newarray
  192. 189 (0xbd) anewarray
  193. 190 (0xbe) arraylength
  194. 191 (0xbf) athrow
  195. 192 (0xc0) checkcast
  196. 193 (0xc1) instanceof
  197. 194 (0xc2) monitorenter
  198. 195 (0xc3) monitorexit
  199. 196 (0xc4) wide
  200. 197 (0xc5) multianewarray
  201. 198 (0xc6) ifnull
  202. 199 (0xc7) ifnonnull
  203. 200 (0xc8) goto_w
  204. 201 (0xc9) jsr_w
  205. зарезервированные опкоды:
  206. 202 (0xca) breakpoint
  207. 254 (0xfe) impdep1
  208. 255 (0xff) impdep2
  209. <-->

Как видно из вышеприведенного, язык достаточно краток, чтобы его можно было реализовать на системах с небольшим количеством ресурсов, таких как встроенные микропроцессоры, микроконтроллеры, ... , в то же время предусмотрена возможность эффективного выполнения на машинах типа RISC.

Установленные факты:

- Некоторые опкоды не используются. Их обработка, если они встретятся в каком-то классе, будет зависеть от реализации JVM.

- В общем случае каждый опкод имеет форму ixxxx, dxxxx, lxxxx, fxxxx, в зависимости от типа данных, с которым он работает (целые числа, двойные, длинные или плавающие).

- Для сохранения значения в куче используется aload_n, где 0 <= n <= 3. Хочу сказатель, что хотя существует опкод для сохранения числа 2, но для сохранения номера отдельный опкод aload отсутствует. Отдельные опкоды были выделены для наиболее часто используемых значений: 0, 1, 2, 3.

Инструменты

Первым и основным является JAVA-компилятор. Легче всего достать JDK от SUN и, хотя у него не слишком много возможностей и сервисов по сравнению с другими компиляторами, генерируемый им код более-менее приемлем.

Где: java.sun.com

Для того, чтобы совершать путешествия внутрь ява-программ, следует ознакомиться с соответствующими спецификацими на http://java.sun.com/docs/books/vmspec/2nd-edition/html/.

Поскольку мы предполагаем, что у вас установлен JAVA-компилятор, по-видимому, у вас установлен и декомпилятор. Он называется javap и вызывается с помощью команды 'javap -c MiClase', что отобразит на экране дизассемблированный код.

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

И, поскольку мы находимся в андерграундном мире, необходимо найти инструмент, умеющий декомпилировать Java-программы. Наиболее часто используемый - это JAD, а лично мне нравится DJ Java Decompiler, который можно найти на http://members.fortunecity.com/neshkov/dj.html.

Генерируемый декомпилятором код выдается на языке JAVA, а не опкоды, хотя если встречаются невалидные опкоды или какое-нибудь другое несоответствие с языком Java, скомпилировать декомпилированный код не получится.

Задачу декомпиляции затрудняет то, что существуют программы для усложнения скомпилированного кода насколько это возможно. Найти такие программы можно, например, на www.zelix.com или www.condensity.com.

Jasmin - компилятор опкодов. Он трансформирует опкоды в классы. На вход он принимает то, что мы получили с помощью javap.

jas - другой JAVA-компилятор. Он позволяет генерировать динамический код и модифицировать программу на лету, что является полезной техникой для создания мутирующих программ, чтобы спрятать реальный код.

D-java - это еще один JAVA-дизассемблер, написанный на языке C (доступны исходники). Он так же прост, как и javac, но он несколько более устойчив.

Больше информации вы можете получить здесь: http://www.meurrens.org/ip-Links/java/codeEngineering/#tocDecompilersToJava

Практика

Теперь, когда вам известна основные теоретические сведения, мы можем начать с простого примера. Жертвой будет программа под названием BEA-WebLogic. Это приложение работает в Windows так же как и в UNIX, так как полностью написано на JAVA, и представляет собой JAR-архив, в котором упакованны все соответствующие классы. В своей основе это сервер веб-приложений, поэтому в него включен веб-сервер, сервлеты и EJB. Загрузить BEA-WebLogic можно с www.bea.com или www.beasys.com. Использовалась версия 5.1. Уже вышла 6.0, но для того, чтобы понять технику, хватит и 5.1.

Будучи проинсталлированной в c:\weblogic, система лицензий устанавливает в c:\weblogic\WebLogicLicense.xml типа XML (т.е. текстовый) со примерно следующими строками

Код (Text):
  1.  
  2. &lt;LICENSE PRODUCT="WebLogic" IP="127.0.0.1"
  3.       UNITS="1" EXPIRATION="never" KEY="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" /&gt;

Игра с приложением

Модифицирование KEY приводит к LicenseKeyInvalidException, которое находится в классе, располагающемся в файле c:\weblogic\classes\weblogic\common\LicenseKeyInvalidException.class

Сейчас мы должны найти все файлы, которые обращаются к этому классу, то есть содержать строку LicenseKeyInvalidException, что дает нам c:\weblogic\classes\weblogic\t3\svr\T3Srvr.class.

Декомпилируем его с помощью DJ Java Decompiler и получаем

Код (Text):
  1.  
  2. private void handleLicenseException(LicenseException licenseexception)
  3. {
  4. .......
  5. if(licenseexception.getClass().isInstance(new LicenseKeyInvalidException()))
  6.  {
  7.  if(licenseExceptions == null)
  8.   {
  9.   licenseExceptions = new StringBuffer("");
  10.   licenseExceptions.append("\n" + licenseexception.getMessage());
  11.   return;
  12.   }
  13.  licenseExceptions.append("\nAnd also: " + licenseexception.getMessage());
  14.  }
  15.  .......
  16. }

после чего вызывается

Код (Text):
  1.  
  2. private boolean checkAccess()
  3. {
  4. .......
  5. Object obj = null;
  6. try
  7.         {
  8.         FileInfo fileinfo = ServerUtil.findFile("WebLogic" + logSuffix());
  9.         fileinfo.checkAccess(0, logVal());
  10.         x86 = true;
  11.         }
  12. catch(LicenseException licenseexception)
  13.         {
  14.         handleLicenseException(licenseexception);
  15.         }
  16. .......
  17. }

после чего вызывается

Код (Text):
  1.  
  2. public static void main(String args[], String s, String s1)
  3. {
  4. .......
  5. t3srvr.checkAccess();
  6. .......
  7. }

Также в c:\weblogic\classes\weblogic\common\internal\FileInfo.class встречаем

Код (Text):
  1.  
  2. public void checkAccess(int i, String s) вызывается исключение LicenseException
  3. {
  4. .......
  5. if(!getFullName(s).equals(getPermissions()))
  6.  throw new LicenseKeyInvalidException("License key incorrect for "
  7.  + toString());
  8. .......
  9. else
  10.         return;
  11. }

Пачкаем руки

Хоть и не очень сильно.

Один из наиболее часто применяемых методов - это не вызывать checkAccess в main, хотя также можно не вызывать исключение LicenseException в fileinfo.checkAccess или узнать, какой метод сохраняет KEY.

Благодаря JAD'у мы можем отредактировать полученный исходный код: удаляем вызов checkAccess и компилируем новый T3Srvr.java.

Делайте так:

Код (Text):
  1.  
  2. jad -d T3Srvr.class &gt; T3Srvr.java
  3. комментируем строку 't3srvr.checkAccess();'
  4. javac T3Srvr.java
запускаем программу

И вуаля! Код работает.

Если, в силу какой-либо причины, сгенерированный JAD'ом код нельзя скомпилировать, остается другой способ: дизассемблирование вручную. Нам нужно избежать вызова checkAccess.

Дизассемблирование с помощью D-java или javap дает:

Код (Text):
  1.  
  2. Method public static void main(String[],String,String)
  3.  0 iconst_0
  4.  1 istore_3
  5.  2 invokestatic #595 &lt;Method weblogic.kernel.Kernel.setIsServer():void&gt;
  6.  5 new #353 &lt;Class weblogic.t3.srvr.T3Srvr&gt;
  7.  8 dup
  8.  9 invokespecial #678 &lt;Method weblogic.t3.srvr.T3Srvr.&lt;init&gt;():void&gt;
  9. 12 putstatic #732 &lt;Field weblogic.t3.srvr.T3Srvr.theT3Server:
  10.                          weblogic.t3.srvr.T3Srvr&gt;
  11. 15 sipush 26160
  12. 18 invokestatic #491 &lt;Method weblogic.html.HtmlElement.setAnchorMode(int):
  13.                              void&gt;
  14. 21 invokestatic #525 &lt;Method weblogic.t3.srvr.T3Srvr.getT3Srvr():
  15.                              weblogic.t3.srvr.T3Srvr&gt;
  16. 24 astore local4
  17. 26 aload local4
  18. 28 aload_1
  19. 29 putfield #435 &lt;Field weblogic.t3.srvr.T3Srvr.logval:String&gt;
  20. 32 aload local4
  21. 34 aload_2
  22. 35 putfield #805 &lt;Field weblogic.t3.srvr.T3Srvr.logsuffix:String&gt;
  23. 38 invokestatic #804 &lt;Method weblogic.t3.srvr.T3Srvr.configure():
  24.                              weblogic.t3.services.Config&gt;
  25. 41 pop
  26. 42 aload local4
  27. 44 invokespecial #485 &lt;Method weblogic.t3.srvr.T3Srvr.checkAccess():boolean&gt;
  28. 47 pop
  29. 48 aload local4
  30. 50 invokevirtual #650 &lt;Method weblogic.t3.srvr.T3Srvr.start():void&gt;
  31. 53 goto 70
  32. .......

Вызов, который нам нужно избежать, находится в строке 44. У инструкции invokespecial опкод 0xB7, а 485=0x01E5. Посему загружаем T3Srvr.class в наш любимый шестнадцатиричный редактор, ищем последовательность B701E5, которая оказывается по смещению 8B27 и заменяем ее на 000000 (три NOP'а). И остается перекомпилировать.

Другой пример

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

Одной из таких программ является Jindent, который можно получить на www.jindent.de. Шароварная версия неудобна, так как не форматирует файлы объемом более 400 строк. Мы попытаемся убрать это ограничение.

Программа вызывается следующим образом:

Код (Text):
  1.  
  2. java -jar Jindent.jar Jindent MiClase

но так как в MiClase.java больше 400 строк, то получаем ошибку

Код (Text):
  1.  
  2. Error: ".\MiClase.java" exceeds 400 lines of code.
  3. Parsing terminated.

Это весьма огорчительно.

Код находится в файле Jindent.jar, который, как нам хорошо известно, является не более чем Java ARchive, поэтому мы можем использовать jar -xvf Jindent.jar. Переходив в директорию jindent\, где находятся все архивы. Ищем строку "exceeds 400 lines of code", но не находим. Ищем "exceeds" и снова не находим.

В чем же дело? По-видимому, код был прогнан через обфускатор. У нас есть архивы под названием a.class, b.class, c.class, ..., чье название нам никак не помогает, а просмотр каждого класса будет весьма утомительным.

Распаковываем файлы и пишем

Код (Text):
  1.  
  2. java Jindent MiClase.java

Чтобы получить немного больше информации пишем

Код (Text):
  1.  
  2. java -verbose:class Jindent MiClase.java

Код (Text):
  1.  
  2. .......
  3. [Loaded java.awt.LightweightDispatcher$2 from c:\jdk1.3\JAVA2\lib\rt.jar]
  4. [Loaded sun.awt.ScreenUpdater from c:\jdk1.3\JAVA2\lib\rt.jar]
  5. [Loaded sun.awt.ScreenUpdater$1 from c:\jdk1.3\JAVA2\lib\rt.jar]
  6.  
  7. Parsing from file  ".\MiClase.java".
  8. [Loaded jindent.m]
  9. [Loaded java.awt.geom.Rectangle2D$Double from c:\jdk1.3\JAVA2\lib\rt.jar]
  10. [Loaded java.awt.geom.GeneralPath from c:\jdk1.3\JAVA2\lib\rt.jar]
  11.  
  12. [Loaded jindent.y]
  13.  
  14. Error: ".\MiClase.java" exceeds 400 lines of code.
  15. Parsing terminated.

Похоже, что m.class отвечает за загрузку файла, а класс y.class жалуется на превышение лимита. С помощью нашего друга JAD'а декомпилируем m.class. У нас есть некоторое количество функций с разными именами. Например, у нас есть 15 функций с именем a, но с различным количеством и типом аргументов. Это так называемые перегруженные методы, что для нас не слишком хорошо. В частности, последняя функция:

Код (Text):
  1.  
  2. private static String a(String s1)
  3. {
  4.         char ac[];
  5.         int i1;
  6.         int j1;
  7.         ac = s1.toCharArray();
  8.         i1 = ac.length;
  9.         j1 = 0;
  10.         goto _L1
  11. _L9:
  12.         ac;
  13.         j1;
  14.         JVM INSTR dup2 ;
  15.         JVM INSTR caload ;
  16.         j1 % 5;
  17.         JVM INSTR tableswitch 0 3: default 76
  18.     //          0 52
  19.     //          1 58
  20.     //          2 64
  21.     //          3 70;
  22.         goto _L2 _L3 _L4 _L5 _L6
  23. _L3:
  24.         0x30;
  25.         goto _L7
  26. _L4:
  27.         102;
  28.         goto _L7
  29. _L5:
  30.         9;
  31.         goto _L7
  32. _L6:
  33.         80;
  34.         goto _L7
  35. _L2:
  36.         44;
  37. _L7:
  38.         JVM INSTR ixor ;
  39.         (char);
  40.         JVM INSTR castore ;
  41.         j1++;
  42. _L1:
  43.         if(j1 &lt; i1) goto _L9; else goto _L8
  44. _L8:
  45.         return new String(ac);
  46. }

Как видим, опкоды невозможно перекомпилировать с помощью простого вызова javac. Но так как они закоментированы, это не важно. Просто изменяем последнюю функцию в файле m.java следующим образом:

Код (Text):
  1.  
  2. private static String a(String s1)
  3. {
  4.         return new String("funcion   a   ha sido llamada "+s1);
  5. }

Теперь, поскольку теперь не вызывается ничего из того, что вызывалось раньше, нам не о чем беспокоиться.

И помещаем в каждый методод следующую строку

Код (Text):
  1.  
  2. System.out.println("estoy en la funcion xxxx ");

в каждый метод.

Запускаем программу и видим, что последние строки выполняются вместе с методом o() и после метода b().

Код (Text):
  1.  
  2.     public void b()
  3.     {
  4.         System.out.println("estoy en la funcion b() ");
  5.         r = null;
  6.         b = null;
  7.         k = null;
  8.         l = null;
  9.     }

Похоже, что очищаются какие-то указатели. Ничего особенного.

С другой стороны

Код (Text):
  1.  
  2.     public int o()
  3.     {
  4.         System.out.println("estoy en la funcion o() ");
  5.         int i1 = 0;
  6.         for(int j1 = 0; j1 &lt; s; j1++)
  7.             if(r[j1] == '\n')
  8.                 i1++;
  9.         System.out.println("estoy en la funcion o() . Retorno i1="+i1);
  10.         return i1;
  11.     }

Запускаем программу и в результатет получаем

Код (Text):
  1.  
  2.         "estoy en la funcion o() . Retorno i1=2000"

Случайно в нашем файле оказывается 2000 строк. Все становится ясным: функция o() возвращает количество строк в файле.

В редких случаях эта функция не вызывается из этого модуля. Тогда делаем printStackTrace(), пока не будет возвращен i1, получаем:

Код (Text):
  1.  
  2.         at jindent.m.o(m.java, Compiled Code)
  3.         at jindent.JindentParser.a(JindentParser.java)
  4.         at jindent.JindentParser.c(JindentParser.java, Compiled Code)
  5.         at jindent.JindentParser.d(JindentParser.java)
  6.         at jindent.JindentParser.invoke(JindentParser.java, Compiled Code)
  7.         at Jindent.main(Jindent.java)

До того, как пропатчить, исследуем программу еще немного.

Дизассемблируем JindentParser.class и ищем, где происходит первый вызов o().

Код (Text):
  1.  
  2.     void i(k k1)
  3.     {
  4.         if(k1.n())
  5.         {
  6.             int i1 = k1.o();
  7.             C.setVariable(E("p\016]xvl?P}a"), k1.p());
  8.             for(int j1 = 0; j1 &lt; i1; j1++)
  9.             {
  10.                 String s1 = k1.i(j1);
  11.                 s1 = e(s1);
  12.                 g(s1);
  13.             }
  14.         }
  15.     }

В цикле переменной i1 присваивается результат o(). Пока лучше не патчить o() (В любом случае, мы несколько смущены. Откуда мы знаем, что k1 - это объект типа m?).

второй вызов:

Код (Text):
  1.  
  2. строка 11255
  3. void a(Reader reader, Writer writer) throws JindentException
  4. {
  5. mW();
  6. cA = new m(reader, 1, 1, e, u);
  7. .........
  8. if(cA.o() &gt; bi - 512)
  9.  {
  10.  bY();
  11.  throw new JindentException(E("K\005Yxp\"\016Qnag\017Z-02[\taml\016Z-kdKJb`gE"));
  12.  }
  13. .........

Это очень интересно. cA - это один экземпляр объекта типа m, и где-то в конце вызывается m.o().

Код (Text):
  1.  
  2.         if(cA.o() &gt; bi - 512)

где bi равно ?

Переходим к определению переменных и (нам везет) находим константу bi:

Код (Text):
  1.  
  2.   bi = 912;

Ах, теперь все понятно: bi - 512 = 912 - 512 = 400, все просто!

третий вызов (строка 8158):

Код (Text):
  1.  
  2. void a(String s1, String s2, String s3) throws JindentException
  3. {
  4. Object obj = null;
  5. .........
  6. cA = new m(filereader, 1, 1, e, u);
  7. if(cA.o() &gt; bi - 512)
  8.  {
  9.  bY();
  10.  throw new JindentException("\"" + s2 + E(" KLugg\016M~$6[\031-hk\005L~$m\r\tnkf\016\007"));
  11.  }
  12. .........

Теперь просто необходимо пропатчить значение bi JindentParset.class, чтобы утилита принимала файлы размером до 65535-512.

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

С другой стороны, две наиболее используемые среды разработки, VisualAge и VisualCafe имеют возможность посмотреть, что делает класс без необходимости иметь исходный код.

Стоит упомянуть о SUN'овском Hotspot'е, прекрасой JVM с JIT'овским компилятором с прилагающимся исходным кодом. Он может не просто выполнять инструкции, но создавать файл, который затем запустит.

Туда включен графический пошаговый отладчик.

Есть еще много тем, касающихся Java, которых было бы интересно коснуться.

Например:

  • реализация JVM для новичков
  • функционирование Sandbox и как подгружать классы с помощью другого ClassLoader'а
  • генерация java-кода на лету для повышения безопасности
  • вызовы DLL и библиотек S.O. с помощью jni
  • сервлеты, RMI, EJB

Наконец, Java - это язык, который быстро развивается, для которого уже написано много приложений и еще больше появится в будущем (все оказалось не так радужно - прим.пер.)

Если предсказания известных аналитиков софтверного рынка оправдаются, 90% процентов приложений будут интегрированы с Web, а это значит, что они будут переведены на Java (автор статьи не подозревал, что Miscrosoft замутит .Net и связанную с ней ересь :smile3: - прим.пер.). Как бы то ни было, можно назвать такие примеры как Oracle, SmartCard, Vantive, Lotus и IBM. © FCA00000 / SET#25, пер. Aquila


0 1.417
archive

archive
New Member

Регистрация:
27 фев 2017
Публикаций:
532