Как исправлялась Талбица
Когда я написал свою программу Талбица, я еще учился в школе и был знатным быдлокодером.
Программа, конечно, получилась неплохая и работала исправно... но со временем модули химического калькулятора и перевода температур стали давать сбой. Причем сбой происходил только на новых, современных компьютерах. К сожалению, исходники я потерял почти сразу после релиза версии 2.2, поэтому отладить программу и исправить ошибки не представлялось возможным.
С тех пор прошло 5 лет. Периодически мне приходили письма с просьбами починить программу, которые приходилось раз за разом отметать: я не мог этого сделать. За это время я научился очень многому: окончил институт, освоил программирование и реверсинг, поработал в Касперском. И знаете что? Плевал я на исходники, мы исправим программу, не видя её кода.
Гвоздь программы
Для исправления неплохо бы знать небольшой секрет. Эти два сбоящих модуля, калькулятор и переводчик температур, работают через невероятную задницу. В папке программы лежит библиотека svdsolver.dll, которая хранит в себе алгоритм уравнивания реакции. Когда я стал подключать её к программе, я наткнулся на неразрешимую проблему: программа просто падала при попытке обратиться к библиотеке. Виноват был Вижуал Бейсик 6, на котором написана Талбица: он имел очень кривую поддержку библиотек С++.
Тогда мне пришлось применить абсолютно безобразный прием. Талбица стала записывать реакцию в файл и вызывать специальную программу-посредник на Делфи, которая принимала переданный файл с реакцией; без проблем обращалась к многострадальной библиотеке; решала реакцию и записывала результат в другой файл, который, в свою очередь, цепляла Талбица и выдавала ответ пользователю.
С первого взгляда все работало. Однако эта схема была реализована прямо в лоб: я не то чтобы не дожидался появления файла прежде чем его прочесть, — я даже не удосужился выставить задержку перед чтением. В итоге на моем стареньком Целероне-900 все работало безупречно, а на более быстром Пентиуме-3000 Талбица пыталась считать результат еще до того, как он был записан.
Что же можно сделать в такой ситуации? Ну, переписать все с нуля или дожидаться появления файла было бы лучшим вариантом, но в этом уже нет смысла. Я предлагаю найти то место, где производится чтение результатов, и вставить перед ним задержку на полсекунды. Этого должно хватить.
Декомпиляция
Скачиваем программу, устанавливаем, идем в папку. Копируем Talbica.exe в отдельную директорию, чтобы не мучиться с админскими правами. Загружаем Talbica.exe в PEiD и видим, что программа упакована ASPack.
Ну что ж, не страшно. Берем ASPackDie и распаковываем Талбицу, на выходе получаем файл unpacked.exe.
PEiD показывает Вижуал Бейсик 5/6 в качестве компилятора. Это просто замечательно, потому что для Бейсика существует отличный декомпилятор VB Decompiler Pro. Это не настоящий декомпилятор — он не способен выдать исходный код программы — но с его помощью можно очень легко найти нужную процедуру, в которую мы собираемся внедрять задержку. Давайте загрузим в него наш распакованный файл.
После минутной декомпиляции перед нами предстает такая картина:
Талбица состоит из нескольких окон, а в терминах Бейсика они называются формами. Слева приведен список всех форм и процедур, отсортированных по формам; справа — ресурсы и код процедур.
Окно химического калькулятора называется «Marcus Chemistry Calculator 1.0beta». Ему соответствует форма Form5, в ресурсах которой указан точно такой же заголовок.
Ресурсы нам не нужны совсем. Взглянем на процедуры данного окна:
Среди них есть процедура с названием на ent_, видимо, это сокращение от enter. Похоже, что-то важное, не так ли? Да, это та самая подпрограмма, которая вызывается при нажатии на кнопку «Уравнять!». Она-то нам и нужна. Кликаем на нее дважды и смотрим, что интересного покажет нам правая панель с кодом.
Ничего себе, пасхальное яйцо. Оказывается, если ввести реакцию H2 + BrCl + PbCrO4 + NaAlF4 + KI + MgSiO3 + H3PO4 + FeSO4 + SO2 + CaC2N2 + CF2Cl2 = CaF2 + KAlO4H4 + MgCO3 + Na2SiO3 + PI3 + FeS3C3N3 + PbBr2 + CrCl3 + H2O, программа выведет сообщение:
Какой пиздец. Ну да ладно. Листаем дальше и натыкаемся на интересные строки:
Вы тоже это видите? Встроенная функция Shell запускает tsu.exe, а дальше идет открытие файла fromsolve.tsf. То что нужно! Запишем адрес вызова Shell (00BE459C) и загрузим Талбицу в Олли-Дебагер.
Олли-оп!
Нажмем Ctrl + G и введем записанный адрес для перехода по нему.
Окажемся на том самом месте, что видели в декомпиляторе. Вот прямо после этой строчки нужно внедрить задержку.
Как известно, задержку в Винде осуществляет АПИ-функция Sleep, которая принимает один параметр — время задержки в миллисекундах. Однако в таблице импорта Талбицы нет функции Sleep. Придется её туда сначала добавить. Для этого загрузим Талбицу в программу PE Tools и откроем модуль PE Editor. Нажмем кнопку Directories, выберем пункт Import Directories, нажмем правой кнопкой по свободному месту и выберем пункт меню Add Imports. Имя функции — Sleep, а находится она в библиотеке Kernel32.dll.
Сохраним изменения.
Мест нет?
Все готово, давайте внедрять... стоп, а куда? В коде же совсем нет места. Вот незадача. Придется написать задержку где-то еще, в свободном от кода месте. Тогда нам придется прыгать в то место с текущего адреса, выполнять там задержку, а потом прыгать обратно. Только чтобы прыгать, нужно ведь написать команду прыжка JMP. Следовательно, пару строк все равно нужно занять. Так что придется их перенести.
Сохраним в блокноте следующие команды:
И найдем пустое место в коде. Например, здесь:
Выделим команды по адресам 00BE45A2 и 00BE45A4 и нажмем пробел, откроется окно редактирования кода. Введем в него команду перехода на выбранный адрес.
После нажатия кнопки Assemble код примет такой вид:
Олли внедрил команду JMP и забил оставшееся место нулями. Теперь отправимся по нашему адресу и перенесем туда сохраненные команды. Для этого выделяем область, нажимаем пробел и вводим команды по очереди аналогично тому, как мы вводили JMP.
Напишем саму задержку. Придется вспомнить ассемблер. Для начала надо, как и перед вызовом любой функции, сохранить все регистры в стек при помощи команды PUSHAD. Затем нужно поместить в стек все параметры функции. В нашем случае параметр один — задержка в миллисекундах. Пусть она будет равна 500. После этого нужно вызвать функцию и вернуть регистры из стека командой POPAD.
Вызов функции записывается в формате CALL DWORD PTR DS:[address]. В нашем случае адрес функции Sleep — 007F904A, так показал нам PE Tools. Только он показал нам относительный виртуальный адрес, а нам нужен абсолютный. К счастью, в PE Tools есть калькулятор, он вызывается кнопкой FLC. Переведем наш адрес и получим 0BF904A.
Вводим команды:
И завершающим штрихом будет возврат в основной код JMP 00BE45AA.
2.3
Ну что ж, всё. Теперь надо сохранить. Выделяем весь код в Олли (для этого нужно подняться вверх, выделить первую строчку, спутиться вниз, зажать шифт и выделить последнюю строчку), жмем правой кнопкой — Edit — Copy To Executable — Правой кнопкой — Save file.
Скопируем полученный файл в папку программы и проверим, удалось ли нам починить программу. Вводим реакцию, и...
Удалось.