Реконструирование Java-программ — Архив WASM.RU
Декомпиляция Java-программ
В данной статье я собираюсь рассказать об основах дизассемблирования программ, написанных на Java.
Что такое Java?
Термин "Java" включает в себя несколько различных концепций:
- Язык программирования
- Спецификации языка
- Среда выполнения и API
Вот небольшое пояснение к вышесказанному: после многочисленных экспериментов по созданию языка программирования для своих инженеров SUN решила изобрести современный (по ее утверждению) язык, основанный на принципах объектно-ориентированного программирования, очень модному в то время. В архитектуру ООП входят (среди прочих) такие концепции как наследование и полиморфизм.
Это означает, что объекты расширяют и/или реализуют другие объекты, используя функции вышестоящих класов, от которых они наследуются, что в результате ведет к повышению реюзабельности кода (панацея программирования).
В то время, как SUN выпускала спецификации языка, она решила, что компиляция программы, написанной на Java, будет происходить в байткод, который будет выполняться специальным интерпретатор. Этот интерпретатор называется виртуальной JAVA-машиной (JVM).
Java как API задает типы данных и вызовы объектов. Это позволяет расширить интерфейс классами и избавиться он необходимости использования статических библиотек, так как все объекты полностью динамические. В API входят самые различные библиотеки, в которые входит множество функций: математических, для работы со списками, массивами, работы с сетью, безопасностью, графикой...
Что представляет из себя JAVA?
Каждый объект в JAVA находится в отдельном файле. Один или более объектов можно сгруппировать в пакет (pakage'и). Файлы группируются в Java-архив (JAR) или в архив, сходный по формату с ZIP.
Каждый класс объявляется определенным образом. Например:
Код (Text):
public class MyClase { }У каждого класса может быть (а может и не быть) один или несколько конструкторов, которые отличаются друг от друга количеством или типом аргументов:
Код (Text):
public class MiClase { public MiClase() { } public MiClase(int i) { } }У каждого класса может быть (а может и не быть) один или больше методов.
Также классы бывают разных типов: публичными, частными, финальными и виртуальными... Методы могут быть публичными, частными, статическими, финальными, ...
Класс может расширять функциональность другого.
Базовые типы аргументов методов и переменных могут быть следующие: 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):
javac MiClase.javaОн запускается следующим образом:
Код (Text):
javac MiClase.javaНа выходе получаем файл 'MiClase.class', который содержит Java-байткод.
Теперь, чтобы эта программа была переведена в машинные коды и выполнена, необходим Java-интерпретатор (который также включен в JDK). Код, генерируемый интерпретатором, зависит от процессора, на котором выполняется программа.
Запустить программу можно следующим образом:
Код (Text):
java MiClaseВведение в суть дела
Всю необходимую информацию относительно спецификации JavaTM Virtual Machine, можно получить на сайте java.sun.com.
Архив каждого класса начинается со структуры типа ClassFile
Код (Text):
{ u4 magic; u2 minor_version; u2 major_version; u2 constant_pool_count; cp_info constant_pool[constant_pool_count-1]; u2 access_flags; u2 this_class; u2 super_class; u2 interfaces_count; u2 interfaces[interfaces_count]; u2 fields_count; field_info fields[fields_count]; u2 methods_count; method_info methods[methods_count]; u2 attributes_count; attribute_info attributes[attributes_count]; }Например, поле '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):
<++>java/opcodes.txt 00 (0x00) nop 01 (0x01) aconst_null 02 (0x02) iconst_m1 03 (0x03) iconst_0 04 (0x04) iconst_1 05 (0x05) iconst_2 06 (0x06) iconst_3 07 (0x07) iconst_4 08 (0x08) iconst_5 09 (0x09) lconst_0 10 (0x0a) lconst_1 11 (0x0b) fconst_0 12 (0x0c) fconst_1 13 (0x0d) fconst_2 14 (0x0e) dconst_0 15 (0x0f) dconst_1 16 (0x10) bipush 17 (0x11) sipush 18 (0x12) ldc 19 (0x13) ldc_w 20 (0x14) ldc2_w 21 (0x15) iload 22 (0x16) lload 23 (0x17) fload 24 (0x18) dload 25 (0x19) aload 26 (0x1a) iload_0 27 (0x1b) iload_1 28 (0x1c) iload_2 29 (0x1d) iload_3 30 (0x1e) lload_0 31 (0x1f) lload_1 32 (0x20) lload_2 33 (0x21) lload_3 34 (0x22) fload_0 35 (0x23) fload_1 36 (0x24) fload_2 37 (0x25) fload_3 38 (0x26) dload_0 39 (0x27) dload_1 40 (0x28) dload_2 41 (0x29) dload_3 42 (0x2a) aload_0 43 (0x2b) aload_1 44 (0x2c) aload_2 45 (0x2d) aload_3 46 (0x2e) iaload 47 (0x2f) laload 48 (0x30) faload 49 (0x31) daload 50 (0x32) aaload 51 (0x33) baload 52 (0x34) caload 53 (0x35) saload 54 (0x36) istore 55 (0x37) lstore 56 (0x38) fstore 57 (0x39) dstore 58 (0x3a) astore 59 (0x3b) istore_0 60 (0x3c) istore_1 61 (0x3d) istore_2 62 (0x3e) istore_3 63 (0x3f) lstore_0 64 (0x40) lstore_1 65 (0x41) lstore_2 66 (0x42) lstore_3 67 (0x43) fstore_0 68 (0x44) fstore_1 69 (0x45) fstore_2 70 (0x46) fstore_3 71 (0x47) dstore_0 72 (0x48) dstore_1 73 (0x49) dstore_2 74 (0x4a) dstore_3 75 (0x4b) astore_0 76 (0x4c) astore_1 77 (0x4d) astore_2 78 (0x4e) astore_3 79 (0x4f) iastore 80 (0x50) lastore 81 (0x51) fastore 82 (0x52) dastore 83 (0x53) aastore 84 (0x54) bastore 85 (0x55) castore 86 (0x56) sastore 87 (0x57) pop 88 (0x58) pop2 89 (0x59) dup 90 (0x5a) dup_x1 91 (0x5b) dup_x2 92 (0x5c) dup2 93 (0x5d) dup2_x1 94 (0x5e) dup2_x2 95 (0x5f) swap 96 (0x60) iadd 97 (0x61) ladd 98 (0x62) fadd 99 (0x63) dadd 100 (0x64) isub 101 (0x65) lsub 102 (0x66) fsub 103 (0x67) dsub 104 (0x68) imul 105 (0x69) lmul 106 (0x6a) fmul 107 (0x6b) dmul 108 (0x6c) idiv 109 (0x6d) ldiv 110 (0x6e) fdiv 111 (0x6f) ddiv 112 (0x70) irem 113 (0x71) lrem 114 (0x72) frem 115 (0x73) drem 116 (0x74) ineg 117 (0x75) lneg 118 (0x76) fneg 119 (0x77) dneg 120 (0x78) ishl 121 (0x79) lshl 122 (0x7a) ishr 123 (0x7b) lshr 124 (0x7c) iushr 125 (0x7d) lushr 126 (0x7e) iand 127 (0x7f) land 128 (0x80) ior 129 (0x81) lor 130 (0x82) ixor 131 (0x83) lxor 132 (0x84) iinc 133 (0x85) i2l 134 (0x86) i2f 135 (0x87) i2d 136 (0x88) l2i 137 (0x89) l2f 138 (0x8a) l2d 139 (0x8b) f2i 140 (0x8c) f2l 141 (0x8d) f2d 142 (0x8e) d2i 143 (0x8f) d2l 144 (0x90) d2f 145 (0x91) i2b 146 (0x92) i2c 147 (0x93) i2s 148 (0x94) lcmp 149 (0x95) fcmpl 150 (0x96) fcmpg 151 (0x97) dcmpl 152 (0x98) dcmpg 153 (0x99) ifeq 154 (0x9a) ifne 155 (0x9b) iflt 156 (0x9c) ifge 157 (0x9d) ifgt 158 (0x9e) ifle 159 (0x9f) if_icmpeq 160 (0xa0) if_icmpne 161 (0xa1) if_icmplt 162 (0xa2) if_icmpge 163 (0xa3) if_icmpgt 164 (0xa4) if_icmple 165 (0xa5) if_acmpeq 166 (0xa6) if_acmpne 167 (0xa7) goto 168 (0xa8) jsr 169 (0xa9) ret 170 (0xaa) tableswitch 171 (0xab) lookupswitch 172 (0xac) ireturn 173 (0xad) lreturn 174 (0xae) freturn 175 (0xaf) dreturn 176 (0xb0) areturn 177 (0xb1) return 178 (0xb2) getstatic 179 (0xb3) putstatic 180 (0xb4) getfield 181 (0xb5) putfield 182 (0xb6) invokevirtual 183 (0xb7) invokespecial 184 (0xb8) invokestatic 185 (0xb9) invokeinterface 186 (0xba) xxxunusedxxx1 187 (0xbb) new 188 (0xbc) newarray 189 (0xbd) anewarray 190 (0xbe) arraylength 191 (0xbf) athrow 192 (0xc0) checkcast 193 (0xc1) instanceof 194 (0xc2) monitorenter 195 (0xc3) monitorexit 196 (0xc4) wide 197 (0xc5) multianewarray 198 (0xc6) ifnull 199 (0xc7) ifnonnull 200 (0xc8) goto_w 201 (0xc9) jsr_w зарезервированные опкоды: 202 (0xca) breakpoint 254 (0xfe) impdep1 255 (0xff) impdep2 <-->Как видно из вышеприведенного, язык достаточно краток, чтобы его можно было реализовать на системах с небольшим количеством ресурсов, таких как встроенные микропроцессоры, микроконтроллеры, ... , в то же время предусмотрена возможность эффективного выполнения на машинах типа 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):
<LICENSE PRODUCT="WebLogic" IP="127.0.0.1" UNITS="1" EXPIRATION="never" KEY="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" />Игра с приложением
Модифицирование KEY приводит к LicenseKeyInvalidException, которое находится в классе, располагающемся в файле c:\weblogic\classes\weblogic\common\LicenseKeyInvalidException.class
Сейчас мы должны найти все файлы, которые обращаются к этому классу, то есть содержать строку LicenseKeyInvalidException, что дает нам c:\weblogic\classes\weblogic\t3\svr\T3Srvr.class.
Декомпилируем его с помощью DJ Java Decompiler и получаем
Код (Text):
private void handleLicenseException(LicenseException licenseexception) { ....... if(licenseexception.getClass().isInstance(new LicenseKeyInvalidException())) { if(licenseExceptions == null) { licenseExceptions = new StringBuffer(""); licenseExceptions.append("\n" + licenseexception.getMessage()); return; } licenseExceptions.append("\nAnd also: " + licenseexception.getMessage()); } ....... }после чего вызывается
Код (Text):
private boolean checkAccess() { ....... Object obj = null; try { FileInfo fileinfo = ServerUtil.findFile("WebLogic" + logSuffix()); fileinfo.checkAccess(0, logVal()); x86 = true; } catch(LicenseException licenseexception) { handleLicenseException(licenseexception); } ....... }после чего вызывается
Код (Text):
public static void main(String args[], String s, String s1) { ....... t3srvr.checkAccess(); ....... }Также в c:\weblogic\classes\weblogic\common\internal\FileInfo.class встречаем
Код (Text):
public void checkAccess(int i, String s) вызывается исключение LicenseException { ....... if(!getFullName(s).equals(getPermissions())) throw new LicenseKeyInvalidException("License key incorrect for " + toString()); ....... else return; }Пачкаем руки
Хоть и не очень сильно.
Один из наиболее часто применяемых методов - это не вызывать checkAccess в main, хотя также можно не вызывать исключение LicenseException в fileinfo.checkAccess или узнать, какой метод сохраняет KEY.
Благодаря JAD'у мы можем отредактировать полученный исходный код: удаляем вызов checkAccess и компилируем новый T3Srvr.java.
Делайте так:
запускаем программуКод (Text):
jad -d T3Srvr.class > T3Srvr.java комментируем строку 't3srvr.checkAccess();' javac T3Srvr.javaИ вуаля! Код работает.
Если, в силу какой-либо причины, сгенерированный JAD'ом код нельзя скомпилировать, остается другой способ: дизассемблирование вручную. Нам нужно избежать вызова checkAccess.
Дизассемблирование с помощью D-java или javap дает:
Код (Text):
Method public static void main(String[],String,String) 0 iconst_0 1 istore_3 2 invokestatic #595 <Method weblogic.kernel.Kernel.setIsServer():void> 5 new #353 <Class weblogic.t3.srvr.T3Srvr> 8 dup 9 invokespecial #678 <Method weblogic.t3.srvr.T3Srvr.<init>():void> 12 putstatic #732 <Field weblogic.t3.srvr.T3Srvr.theT3Server: weblogic.t3.srvr.T3Srvr> 15 sipush 26160 18 invokestatic #491 <Method weblogic.html.HtmlElement.setAnchorMode(int): void> 21 invokestatic #525 <Method weblogic.t3.srvr.T3Srvr.getT3Srvr(): weblogic.t3.srvr.T3Srvr> 24 astore local4 26 aload local4 28 aload_1 29 putfield #435 <Field weblogic.t3.srvr.T3Srvr.logval:String> 32 aload local4 34 aload_2 35 putfield #805 <Field weblogic.t3.srvr.T3Srvr.logsuffix:String> 38 invokestatic #804 <Method weblogic.t3.srvr.T3Srvr.configure(): weblogic.t3.services.Config> 41 pop 42 aload local4 44 invokespecial #485 <Method weblogic.t3.srvr.T3Srvr.checkAccess():boolean> 47 pop 48 aload local4 50 invokevirtual #650 <Method weblogic.t3.srvr.T3Srvr.start():void> 53 goto 70 .......Вызов, который нам нужно избежать, находится в строке 44. У инструкции invokespecial опкод 0xB7, а 485=0x01E5. Посему загружаем T3Srvr.class в наш любимый шестнадцатиричный редактор, ищем последовательность B701E5, которая оказывается по смещению 8B27 и заменяем ее на 000000 (три NOP'а). И остается перекомпилировать.
Другой пример
Как и в других языках, написанный на Java код важно привести в порядок и сделать более читаемым. Для этого есть специальные программы, форматирующие исходный код.
Одной из таких программ является Jindent, который можно получить на www.jindent.de. Шароварная версия неудобна, так как не форматирует файлы объемом более 400 строк. Мы попытаемся убрать это ограничение.
Программа вызывается следующим образом:
Код (Text):
java -jar Jindent.jar Jindent MiClaseно так как в MiClase.java больше 400 строк, то получаем ошибку
Код (Text):
Error: ".\MiClase.java" exceeds 400 lines of code. Parsing terminated.Это весьма огорчительно.
Код находится в файле Jindent.jar, который, как нам хорошо известно, является не более чем Java ARchive, поэтому мы можем использовать jar -xvf Jindent.jar. Переходив в директорию jindent\, где находятся все архивы. Ищем строку "exceeds 400 lines of code", но не находим. Ищем "exceeds" и снова не находим.
В чем же дело? По-видимому, код был прогнан через обфускатор. У нас есть архивы под названием a.class, b.class, c.class, ..., чье название нам никак не помогает, а просмотр каждого класса будет весьма утомительным.
Распаковываем файлы и пишем
Код (Text):
java Jindent MiClase.javaЧтобы получить немного больше информации пишем
Код (Text):
java -verbose:class Jindent MiClase.java
Код (Text):
....... [Loaded java.awt.LightweightDispatcher$2 from c:\jdk1.3\JAVA2\lib\rt.jar] [Loaded sun.awt.ScreenUpdater from c:\jdk1.3\JAVA2\lib\rt.jar] [Loaded sun.awt.ScreenUpdater$1 from c:\jdk1.3\JAVA2\lib\rt.jar] Parsing from file ".\MiClase.java". [Loaded jindent.m] [Loaded java.awt.geom.Rectangle2D$Double from c:\jdk1.3\JAVA2\lib\rt.jar] [Loaded java.awt.geom.GeneralPath from c:\jdk1.3\JAVA2\lib\rt.jar] [Loaded jindent.y] Error: ".\MiClase.java" exceeds 400 lines of code. Parsing terminated.Похоже, что m.class отвечает за загрузку файла, а класс y.class жалуется на превышение лимита. С помощью нашего друга JAD'а декомпилируем m.class. У нас есть некоторое количество функций с разными именами. Например, у нас есть 15 функций с именем a, но с различным количеством и типом аргументов. Это так называемые перегруженные методы, что для нас не слишком хорошо. В частности, последняя функция:
Код (Text):
private static String a(String s1) { char ac[]; int i1; int j1; ac = s1.toCharArray(); i1 = ac.length; j1 = 0; goto _L1 _L9: ac; j1; JVM INSTR dup2 ; JVM INSTR caload ; j1 % 5; JVM INSTR tableswitch 0 3: default 76 // 0 52 // 1 58 // 2 64 // 3 70; goto _L2 _L3 _L4 _L5 _L6 _L3: 0x30; goto _L7 _L4: 102; goto _L7 _L5: 9; goto _L7 _L6: 80; goto _L7 _L2: 44; _L7: JVM INSTR ixor ; (char); JVM INSTR castore ; j1++; _L1: if(j1 < i1) goto _L9; else goto _L8 _L8: return new String(ac); }Как видим, опкоды невозможно перекомпилировать с помощью простого вызова javac. Но так как они закоментированы, это не важно. Просто изменяем последнюю функцию в файле m.java следующим образом:
Код (Text):
private static String a(String s1) { return new String("funcion a ha sido llamada "+s1); }Теперь, поскольку теперь не вызывается ничего из того, что вызывалось раньше, нам не о чем беспокоиться.
И помещаем в каждый методод следующую строку
Код (Text):
System.out.println("estoy en la funcion xxxx ");в каждый метод.
Запускаем программу и видим, что последние строки выполняются вместе с методом o() и после метода b().
Код (Text):
public void b() { System.out.println("estoy en la funcion b() "); r = null; b = null; k = null; l = null; }Похоже, что очищаются какие-то указатели. Ничего особенного.
С другой стороны
Код (Text):
public int o() { System.out.println("estoy en la funcion o() "); int i1 = 0; for(int j1 = 0; j1 < s; j1++) if(r[j1] == '\n') i1++; System.out.println("estoy en la funcion o() . Retorno i1="+i1); return i1; }Запускаем программу и в результатет получаем
Код (Text):
"estoy en la funcion o() . Retorno i1=2000"Случайно в нашем файле оказывается 2000 строк. Все становится ясным: функция o() возвращает количество строк в файле.
В редких случаях эта функция не вызывается из этого модуля. Тогда делаем printStackTrace(), пока не будет возвращен i1, получаем:
Код (Text):
at jindent.m.o(m.java, Compiled Code) at jindent.JindentParser.a(JindentParser.java) at jindent.JindentParser.c(JindentParser.java, Compiled Code) at jindent.JindentParser.d(JindentParser.java) at jindent.JindentParser.invoke(JindentParser.java, Compiled Code) at Jindent.main(Jindent.java)До того, как пропатчить, исследуем программу еще немного.
Дизассемблируем JindentParser.class и ищем, где происходит первый вызов o().
Код (Text):
void i(k k1) { if(k1.n()) { int i1 = k1.o(); C.setVariable(E("p\016]xvl?P}a"), k1.p()); for(int j1 = 0; j1 < i1; j1++) { String s1 = k1.i(j1); s1 = e(s1); g(s1); } } }В цикле переменной i1 присваивается результат o(). Пока лучше не патчить o() (В любом случае, мы несколько смущены. Откуда мы знаем, что k1 - это объект типа m?).
второй вызов:
Код (Text):
строка 11255 void a(Reader reader, Writer writer) throws JindentException { mW(); cA = new m(reader, 1, 1, e, u); ......... if(cA.o() > bi - 512) { bY(); throw new JindentException(E("K\005Yxp\"\016Qnag\017Z-02[\taml\016Z-kdKJb`gE")); } .........Это очень интересно. cA - это один экземпляр объекта типа m, и где-то в конце вызывается m.o().
Код (Text):
if(cA.o() > bi - 512)где bi равно ?
Переходим к определению переменных и (нам везет) находим константу bi:
Код (Text):
bi = 912;Ах, теперь все понятно: bi - 512 = 912 - 512 = 400, все просто!
третий вызов (строка 8158):
Код (Text):
void a(String s1, String s2, String s3) throws JindentException { Object obj = null; ......... cA = new m(filereader, 1, 1, e, u); if(cA.o() > bi - 512) { bY(); throw new JindentException("\"" + s2 + E(" KLugg\016M~$6[\031-hk\005L~$m\r\tnkf\016\007")); } .........Теперь просто необходимо пропатчить значение 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 и связанную с ней ересь - прим.пер.). Как бы то ни было, можно назвать такие примеры как Oracle, SmartCard, Vantive, Lotus и IBM. © FCA00000 / SET#25, пер. Aquila
Реконструирование Java-программ
Дата публикации 21 сен 2002