Как исправлялась Талбица

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

 

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

 

03 С тех пор прошло 5 лет. Периодически мне приходили письма с просьбами починить программу, которые приходилось раз за разом отметать: я не мог этого сделать. За это время я научился очень многому: окончил институт, освоил программирование и реверсинг, поработал в Касперском. И знаете что? Плевал я на исходники, мы исправим программу, не видя её кода.

Гвоздь программы

04 Для исправления неплохо бы знать небольшой секрет. Эти два сбоящих модуля, калькулятор и переводчик температур, работают через невероятную задницу. В папке программы лежит библиотека svdsolver.dll, которая хранит в себе алгоритм уравнивания реакции. Когда я стал подключать её к программе, я наткнулся на неразрешимую проблему: программа просто падала при попытке обратиться к библиотеке. Виноват был Вижуал Бейсик 6, на котором написана Талбица: он имел очень кривую поддержку библиотек С++.

05 Тогда мне пришлось применить абсолютно безобразный прием. Талбица стала записывать реакцию в файл и вызывать специальную программу-посредник на Делфи, которая принимала переданный файл с реакцией; без проблем обращалась к многострадальной библиотеке; решала реакцию и записывала результат в другой файл, который, в свою очередь, цепляла Талбица и выдавала ответ пользователю.

06 С первого взгляда все работало. Однако эта схема была реализована прямо в лоб: я не то чтобы не дожидался появления файла прежде чем его прочесть, — я даже не удосужился выставить задержку перед чтением. В итоге на моем стареньком Целероне-900 все работало безупречно, а на более быстром Пентиуме-3000 Талбица пыталась считать результат еще до того, как он был записан.

07 Что же можно сделать в такой ситуации? Ну, переписать все с нуля или дожидаться появления файла было бы лучшим вариантом, но в этом уже нет смысла. Я предлагаю найти то место, где производится чтение результатов, и вставить перед ним задержку на полсекунды. Этого должно хватить.

Декомпиляция

08 Скачиваем программу, устанавливаем, идем в папку. Копируем Talbica.exe в отдельную директорию, чтобы не мучиться с админскими правами. Загружаем Talbica.exe в PEiD и видим, что программа упакована ASPack.

 

09 Ну что ж, не страшно. Берем ASPackDie и распаковываем Талбицу, на выходе получаем файл unpacked.exe.

 

10 PEiD показывает Вижуал Бейсик 5/6 в качестве компилятора. Это просто замечательно, потому что для Бейсика существует отличный декомпилятор VB Decompiler Pro. Это не настоящий декомпилятор — он не способен выдать исходный код программы — но с его помощью можно очень легко найти нужную процедуру, в которую мы собираемся внедрять задержку. Давайте загрузим в него наш распакованный файл.

11 После минутной декомпиляции перед нами предстает такая картина:

 

12 Талбица состоит из нескольких окон, а в терминах Бейсика они называются формами. Слева приведен список всех форм и процедур, отсортированных по формам; справа — ресурсы и код процедур.

13 Окно химического калькулятора называется «Marcus Chemistry Calculator 1.0beta». Ему соответствует форма Form5, в ресурсах которой указан точно такой же заголовок.

 

14 Ресурсы нам не нужны совсем. Взглянем на процедуры данного окна:

 

15 Среди них есть процедура с названием на ent_, видимо, это сокращение от enter. Похоже, что-то важное, не так ли? Да, это та самая подпрограмма, которая вызывается при нажатии на кнопку «Уравнять!». Она-то нам и нужна. Кликаем на нее дважды и смотрим, что интересного покажет нам правая панель с кодом.

 

16 Ничего себе, пасхальное яйцо. Оказывается, если ввести реакцию H2 + BrCl + PbCrO4 + NaAlF4 + KI + MgSiO3 + H3PO4 + FeSO4 + SO2 + CaC2N2 + CF2Cl2 = CaF2 + KAlO4H4 + MgCO3 + Na2SiO3 + PI3 + FeS3C3N3 + PbBr2 + CrCl3 + H2O, программа выведет сообщение:

Ну чувак, ты даёшь!!! Неужели ты думаешь, что эта прога не уравняет такую простенькую реакцию!? Заметь, у меня тоже есть эта энциклопедия по химии и я тоже заглядывал на страницу 74. Заметь: реакция в этой энциклопедии уравнена НЕПРАВИЛЬНО! Проверь содержание водорода. Так что даже не пытайся найти баг в моём калькуляторе. Если хочешь знать, я специально сделал калькулятор именно для этой реакции. Теперь нажми ОК, закрой этот прикол и наблюдай, как Marcus Chemistry Calculator расправится с этой реакцией в два счёта и выдаст ПРАВИЛЬНЫЙ ответ!!!

17 Какой пиздец. Ну да ладно. Листаем дальше и натыкаемся на интересные строки:

 

18 Вы тоже это видите? Встроенная функция Shell запускает tsu.exe, а дальше идет открытие файла fromsolve.tsf. То что нужно! Запишем адрес вызова Shell (00BE459C) и загрузим Талбицу в Олли-Дебагер.

Олли-оп!

 

19 Нажмем Ctrl + G и введем записанный адрес для перехода по нему.

 

20 Окажемся на том самом месте, что видели в декомпиляторе. Вот прямо после этой строчки нужно внедрить задержку.

21 Как известно, задержку в Винде осуществляет АПИ-функция Sleep, которая принимает один параметр — время задержки в миллисекундах. Однако в таблице импорта Талбицы нет функции Sleep. Придется её туда сначала добавить. Для этого загрузим Талбицу в программу PE Tools и откроем модуль PE Editor. Нажмем кнопку Directories, выберем пункт Import Directories, нажмем правой кнопкой по свободному месту и выберем пункт меню Add Imports. Имя функции — Sleep, а находится она в библиотеке Kernel32.dll.

 

22 Сохраним изменения.

 

Мест нет?

23 Все готово, давайте внедрять... стоп, а куда? В коде же совсем нет места. Вот незадача. Придется написать задержку где-то еще, в свободном от кода месте. Тогда нам придется прыгать в то место с текущего адреса, выполнять там задержку, а потом прыгать обратно. Только чтобы прыгать, нужно ведь написать команду прыжка JMP. Следовательно, пару строк все равно нужно занять. Так что придется их перенести.

 

24 Сохраним в блокноте следующие команды:

CPU Disasm
Address Hex dump Command Comments
00BE45A2 |. DDD8 FSTP ST
00BE45A4 |. 8D8D B4FEFFFF LEA ECX,[EBP-14C]

25 И найдем пустое место в коде. Например, здесь:

 

26 Выделим команды по адресам 00BE45A2 и 00BE45A4 и нажмем пробел, откроется окно редактирования кода. Введем в него команду перехода на выбранный адрес.

 

27 После нажатия кнопки Assemble код примет такой вид:

 

28 Олли внедрил команду JMP и забил оставшееся место нулями. Теперь отправимся по нашему адресу и перенесем туда сохраненные команды. Для этого выделяем область, нажимаем пробел и вводим команды по очереди аналогично тому, как мы вводили JMP.

 

29 Напишем саму задержку. Придется вспомнить ассемблер. Для начала надо, как и перед вызовом любой функции, сохранить все регистры в стек при помощи команды PUSHAD. Затем нужно поместить в стек все параметры функции. В нашем случае параметр один — задержка в миллисекундах. Пусть она будет равна 500. После этого нужно вызвать функцию и вернуть регистры из стека командой POPAD.

30 Вызов функции записывается в формате CALL DWORD PTR DS:[address]. В нашем случае адрес функции Sleep — 007F904A, так показал нам PE Tools. Только он показал нам относительный виртуальный адрес, а нам нужен абсолютный. К счастью, в PE Tools есть калькулятор, он вызывается кнопкой FLC. Переведем наш адрес и получим 0BF904A.

 

31 Вводим команды:

PUSHAD
PUSH 1F4 // 1F4 = 500
CALL DWORD PTR DS:[0BF904A] // 0BF904A — адрес функции Sleep в таблице IAT
POPAD

32 И завершающим штрихом будет возврат в основной код JMP 00BE45AA.

 

2.3

33 Ну что ж, всё. Теперь надо сохранить. Выделяем весь код в Олли (для этого нужно подняться вверх, выделить первую строчку, спутиться вниз, зажать шифт и выделить последнюю строчку), жмем правой кнопкой — Edit — Copy To Executable — Правой кнопкой — Save file.

34 Скопируем полученный файл в папку программы и проверим, удалось ли нам починить программу. Вводим реакцию, и...

 

Удалось.

Июль 2010
Андрей Маркелов
Библиотека