Programming
17 August 2011

Расширение функциональности не имея исходного кода

Думаю, у каждого из вас было ощущение, что в той, или иной программе не хватает какой-нибудь must have фичи. Если программа идет с исходным кодом, то проблем не возникает. Любой желающий может дописать нужную функциональность. А что если программа закрытая? Не стоит отчаиваться, это не пропащий случай. Сейчас расскажу, как можно дописать за автора то, чего не хватает.

Отступление

Моей жертвой вновь стал отладчик OllyDbg. После выпуска версии 2.00 он мне стал нравится все больше, появилось много интересных вещей. Но автор категорически отказывался интегрировать в него систему плагинов, а это можно сказать главное, что делало отладчик настоящим рабочим инструментом.
И вот в один момент, я для себя понял, что пора самому добавлять эту возможность.

Приступим

Первым вопросом встает — как добавить свой код?
Основных способов два:
  • Добавить новую секцию, и поместить весь свой код в ней.
  • Вынести код в dll, и потом в свободном месте программы поместить небольшой код вызова.

Есть и другие более изощренные варианты, но я их рассматривать не буду.

Я выбрал второй вариант, потому что он удобней в плане программирования, и не нужно каждый раз копировать свой код в exe-шник. Для подгрузки своей dll можно поменять точку входа на свой код, или же загрузить чуть позже. Но лучше грузиться в самом начале, тогда у нас будет доступ ко всем API, до того как ими воспользуется родительский процесс.
Патч мне делать не пришлось, нашел уже патченную, поменял только имя модуля загружаемого.
Было:
image
Стало:
image
И наш код. Хитрость первого call'а в том, что он одной командой и помещает в стек адрес строки, и перепрыгивает ее.
image
Jmp по адресу 00401000 это OEP от Borland C

Схема подгрузки плагинов банальная:
Читаем из ini путь до папки с плагинами, если ее нет, то берем путь по-умолчанию и проверяем через GetFileAttributes наличие папки. Ну а дальше по маске *.dll, при помощи FindFirstFile/FindNextFile, перечисляем все файлы.
Загружаем каждый через LoadLibrary. А плагин в свою очередь будет дергать экспортируемые нами функции. Так как в 1.10 версии все функции экспортировались из exe, то нам нужно сделать у себя такую же таблицу экспорта, с такими же ординалами, чтоб для плагинов тоже все было прозрачно. Но об этом позднее.

Так как почти все плагины управлялись из меню, нужно было как-то создать и обрабатывать свою ветку в главном меню. Для обработки сообщений нам нужно достать адрес оконной функции. А где его взять? Простой способ — перехватить RegisterClass и вытащить из поля структуры WNDCLASS->lpfnWndProc адрес на нее. Ну и конечно же на его место записать свой.
Второй вариант — найти в коде, и сделать jmp на себя. Я конечно люблю извращенные методы, но не в этот раз.

Любой перехват функций я буду делать напрямую в таблице импорта, подставляя вместо адреса API, свой на процедуру-обработчик, а из нее уже в API. При данном методе для самого отладчика все будет прозрачно, и он будет думать, что работает напрямую с системой.
Не забудьте перед любой правкой чужой памяти выставлять права на запись в эту область, иначе будут сыпаться ошибки доступа.
Так выглядит вызов API до правки:

И через несколько мгновений позже:

Даже повторный ре-анализ не помогает, оля так и думает что там вызов RegisterClassW, однако в обеих областях ниже можно убедиться, что по адресу 005EEA44 лежит далеко не адрес API.

Итак, «где» обрабатывать сообщения мы знаем, теперь бы нужно создать, то что обрабатывать. Для этого тем же способом перехватываем AppendMenu. И в обработчике ожидаем добавление меню, перед которым хотим воткнуть свое меню.
Как только появилось — нам простирается большой выбор по созданию меню. Но я ограничился пока вариантами MF_STRING для вывода одиночного пункта, MF_POPUP для выпадающих вложенных меню, ну и MF_SEPARATOR для разделителей. Все пункты меню добавлял через немного устаревший InsertMenu. Во-первых так меньше кода, во-вторых Олег (разработчик OllyDbg) сам использует данный способ.
Не забудем в самом конце вызвать оригинальную API AppendMenu для себя и для последующей за нами ветки меню.
Все добавление будет выглядеть обобщенно следующим образом:
Запрос от OllyDbg на AppendMenu(Help) -> наш CreateMenu() -> Несколько наших InsertMenu() -> наше AppendMenu(Plugins) -> выполняем запрос AppendMenu(Help).

Создавая меню, нужно заранее посмотреть, какие ID свободные, и которые не будут пересекаться. В WndProc для меню мы будем ловить WM_COMMAND. Отсеиваем по диапазону только наши и например используя ассоциативный массив по ID=>адрес_обработчика передаем управление нужному плагину.

Вот так выглядят окна OllyDbg 2.00c версии до моего вмешательства и после:


Теперь самый краеугольный вопрос — нужно найти адреса функций, которые должны экспортироваться плагинам. Не надейтесь найти в релизных версиях приложений debug-информацию, которая могла бы значительно помочь.
Поэтому пойдем следующим путем, грузим в Ida Pro:
  • Ищем текстовые строки, которые выводятся при ошибках, они могут нам подсказать назначение функций.
  • После того как все возможное выжали из строк, есть смысл найти обработчики различных меню, они так же значительно сужают круг поисков.
  • И наконец неприятный и требующий значительных временных затрат способ. Загружаем программу в отладчик, и ставим бряк на низкоуровневых специфических (или не очень) API. И постепенно поднимаемся на верхние уровни абстракции, параллельно поглядывая в Ida. И так, пока не выйдем на функции, когда можно четко обозначить что в целом она делает. Именуем для удобства функции в Ida, а себе отдельно сохраняем найденные адреса.


Весь мини менеджер плагинов я написал за день на коленке на Fasm'е. Потом еще за пару недель расковырял порядка 30 функций. Но на тот момент он не был совместим с PDK 1.10, что не мешало мне написать свои для скрытия отладчика, загрузки символов из map-файлов, генерируемых IdaPro, и простенького дампера.

Ложка дегтя

Вероятно Олег обратил внимание на мою затею, и все-таки сделал собственный PDK в 2.01 alpha 4, что должно было разом убить мой проект. Но есть пару моментов. Новый билд стал IMHO перегруженным и неудобным, да и старые плагины не поддерживаются.

На данный момент плагин-менеджер полностью с нуля переписывается на C++, с планами на совместимость со всеми версиями. И перехватом нескольких функций тут уже не отделаться...

Ну а вот тут можно посмотреть, как НЕ надо писать код, даже на коленке за один день.
Косяки и так знаю, потому и переписывается с нуля.
Сорец на fasm
Обновленный вариант будет позже, когда будет допилен.

Отдельная благодарность автору OllyDbg и участникам форума exel@b за активную поддержку, и хабровчанам за поправки.

+106
2.6k 76
Comments 31
Top of the day