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

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

Программа, конечно, получилась неплохая и работала исправно... но со временем модули химического калькулятора и перевода температур стали давать сбой. Причем сбой происходил только на новых, современных компьютерах. К сожалению, исходники я потерял почти сразу после релиза версии 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, программа выведет сообщение:

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

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

Вы тоже это видите? Встроенная функция 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.

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

Удалось.