Использование procmon от sysinternals для диагностики проблемных мест в исполняемом коде
Сразу хочу сказать, что это только демонстрация возможностей procmon для определения проблемных мест в программном обеспечении. 1С83 была выбрана для опытов из-за неочевидности способа поиска точки входа в процедуру проверки наличия установленных эмуляторов ключа. Она выполняется через различное время после старта порядка 3~10 мин, и вызывает появление окна «нарушение целостности системы» с последующим закрытием приложения. Я призываю всех использовать только лицензионное программное обеспечение. Рассматривать эту статью, как описание возможности взлома, нет смысла. Хотя бы потому, что 1С83 давно взломана и без меня. Любой 1с-ник за секунду вам скажет, как ее запустить без ключа.
Если кто не в курсе procmon от sysinternals умеет ставить перехватчик на системные события работы процессов с файлами и регистром виндуза. И хотя любой процесс плодит гигантское количество обращений к файлам и регистру при старте, да и в процессе работы тоже, использование фильтров и поиска по событиям упрощает нахождение нужного. Интересной особенностью procmon является сохранение стека вызовов у каждого события. Таким образом можно проследить какие модули и в каком месте породили то или иное событие.
Итак, ставим фильтр по имени процесса 1с.
Запускаем 1с, и потом только включаем захват событий. Таким образом мы пропустим большое количество ненужных нам событий, случающихся при старте. Ждем появления сообщения о нарушении целостности.
Останавливаем захват событий и начинаем просмотр с конца в поисках чего-то подозрительного. Практически сразу наталкиваемся на что-то интересное.
Щелкаем на любое событие и идем на вкладку Стек.
Все что выше горизонтальной черты очевидно уже принадлежит модулям ядра виндуза, а вот три адреса ниже представляют интерес. Какой-то из них наверняка есть адрес возврата в тело процедуры проверки. Запустим же 1с заново и приатачимся отладчиком из Visual Studio, для простоты, т.к. оно уже у нас есть под рукой.
Остановим процесс и введем искомый адрес в окно дизассемблера, например, самый первый в порядке вызовов.
Ведущие нули в таком количестве оно нам дописало, т.к. это 64-бит процесс. Вот он и вызов из которого предполагается возврат на искомый адрес. Поставим точку останова, запустим остановленный процесс, и будем ждать.
Через какое-то время точка останова действительно срабатывает. Проходим вызов одним шагом вперед по F10 и смотрим содержимое регистра AX, все знают, что вызовы функций чаще всего возвращают результат именно в нем. Видим какую-то подозрительную константу, которая совпадает с далее следующей командой сравнения CMP.
Запускаем выполнение дальше и убеждаемся, что ничего не поменялось, программа ругается как обычно и вылетает. Что ж, а что будет при отсутствии эмулятора? Запускаем 1с заново, останавливаем, переходим по нашему адресу, ставим точку останова. Все, как и раньше. Заходим в менеджер устройств виндуза и выключаем устройство эмулятора, переименовываем раздел регистра с дампами и файл драйвера в System32, так как в списке захваченных событий выше мы видели обращения к соответствующим именам. Запускаем процесс из Visual Studio и ждем.
Видим, что в данном случае возвращается другая константа, которая тоже рядом проверяется. Запускаем выполнение дальше и убеждаемся, что прога работает без проблем. Предположим, что 0xa19d это проверка пройдена нормально, а 0x91cb это нарушение целостности. Двумя строчками выше видим безусловный переход JMP и понимаем, что предыдущий перед точкой останова вызов call, это точка входа в процедуру проверки и, видимо, предыдущий вызов проверяет регистр виндуза, а вызов в точке останова - файлы, что вполне согласуется с порядком захваченных событий. Проходя отладчиком путь от точки входа до выхода из процедуры в режиме с включенным эмулятором, убеждаемся, что переход на адрес 0x10e67dfb1 приводит к аварийному завершению. Таким образом, если попробовать перескочить сразу из точки входа на адрес 0x10e67df99, то, возможно, проверка будет пройдена.
Лезть в справочники за кодами инструкций не придется, т.к., к счастью, сразу выше точки входа мы видим двухбайтовую инструкцию безусловного относительного перехода JMP. Понятно, что код инструкции это EB, а второй байт означает смещение. Т.к. все знают, что регистр адреса текущей инструкции всегда содержит адрес следующей за исполняемой в данный момент, то к адресу точки входа нам надо прибавить 2 и от этого места посчитать количество байтов до целевого адреса, так у нас получится нужное смещение (второй байт инструкции) 2E. Открываем окно дампа памяти и по адресу входа в процедуру проверки меняем два байта на нашу инструкцию.
Запускаем программу выполняться дальше. И что мы видим? Нам повезло и это работает :)
Здесь можно было бы сказать, что теоретически можно использовать функцию виндуз API WriteProcessMemory, и автоматизировать внесение изменений в оперативную память процесса после его старта, но исследования в этом направлении я не продолжал, так как не было такой цели.