Снятие дампа
Получение дампа исполняемого файла в этой версии
Star-Force представляет собой
сплошное удовольствие. Переход на OEP (в
Protect.dll) выглядит так:
push 64h
mov
eax,[Kernel32!Sleep]
call
eax
ret
Поэтому устанавливаем
bpx Sleep, запускаем игру, срабатывает breakpoint.
Выходим на вышеуказанный ret и зацикливаем программу. Все,
можно снимать дамп, например PEditor'ом.
Восстановление импорта
Метод защиты импорта в Star-Force применили куда более действенный, чем
во всех виденных мною пакерах/протекторах. В классических защитах это
просто переход на реальную функцию, либо переход чуть дальше после выполнения
нескольких первых инструкций функции. Здесь же функции, импортируемые из
библиотек kernel32.dll, user32.dll, advapi32.dll,
копируются в память защиты целиком (но без подпроцедур). И видоизменяются по
следующим правилам: код размежовывается командами
nop и jmp short $+2,
инструкции
вида push 12345678h заменяется комбинацией
mov
eax,12345678h
xchg eax,[esp]
вида push esi
комбинацией
xchg
eax,esi
push
eax
xchg
eax,esi
вида pop esi комбинацией
mov esi,eax
pop
eax
xchg
eax,esi
двухбайтовые переходы jxx
заменяются своими шестибайтовыми аналогами, двухбайтовые push 00h - пятибайтовыми, переходы вида jmp X (не short!) заменяются на jmp $+5, а код из X дописывается со
всеми правилами после данной инструкции.
Однако в данной версии игры подобным
образом были защищены лишь 11 импортирумых функций, поэтому я решил схалтурить
(данная проблема была решена впоследствии, при исследовании
Venom'а) и восстановил их руками, мысленно преобразуя их код к
нормальному виду, и ища затем их ~10 первых байт в системных
библиотеках прямо в Sice'е. Ими оказались (в порядке
возрастания их адреса в памяти защиты): ReadFile, GetUserNameA, FindNextFileA,
WriteFile, VirtualFree, GetFileType, VirtualAlloc, CreateFileA, DeleteFileA,
FindClose, MessageBoxA.
Настроив таким образом весь импорт на реальные адреса,
воспользуемся моей любимой утилитой для подобных ситуаций
ImportList by Boris
и получим нормальную секцию импорта. Помещаем ее в наш дамп. Он уже
почти готов.
Восстановление кода _DllDispatch
Размялись? Тогда переходим к
настоящей проблеме. В нашем дампе присутствуют более
20(!) вызовов _DllDispatch,
единственной экспортируемой функции из protect.dll. Ее вызовы выглядят
так:
push ID
call _DllDispatch
Параметр ID определяет,
какой именно блок кода нужно дешифровать и выполнить, при ID=0
дешифруется сама программа. Примечательно, что дешифрованный и выполняемый
код также содержит вызовы _DllDispatch. А эта функция
- это и есть тот самый интерпретатор псевдокода, который упоминался выше. Что же
делать? Протрассировать каждый вызов в лоб вплоть до искомого кода? Что
ж, можете попытаться. Но я предлагаю Вам более интересный способ, позволяющий
автоматически получить сразу весь отсутствующий код. Для этого нам придется немного попрограммировать.
Будем исходить из того, что когда-то (во
время выполнения) искомый код будет находиться в памяти в дешифрованном виде.
Как можно поймать этот момент? Очень просто. Каждая такая функция будет
обращаться к основному коду программы. То есть передавать туда управление.
Значит, если мы перехватим вызов _DllDispatch и перед
тем как отдать управление в protect.dll перезапишем секцию кода
значением 0CCh и станем обрабатывать
INT3, то при передаче управления в любое место секции кода наша
ловушка сработает. Дальше дело техники. Из стека
ring0 берем указатель стека ring3, и
оттуда получаем адрес внутри искомого кода. Код
найден.
Замечания по
реализации:
Для удобства проведения с игрой подобных
манипуляций, я рекомендую добавить к ее импорту собственную библиотеку
(deprotect.dll). Она получит управление после protect.dll,
однако в это время основной код еще не будет дешифрован, а будет содержать в
точке входа лишь call опять в
protect.dll. Исправим его таким образом
(см. исходник), чтобы protect.dll после дешифровки кодовой секции
вернул управление в нашу DLL. Теперь просканируем код игры
и найдем все вызовы _DllDispatch и их идентификаторы.
Идентификаторы сохраним в массив, чтобы в последствии избежать
дублирования. Теперь будем вызывать _DllDispatch
поочередно с каждым отличным идентификатором, а в обработчике
INT3 предусмотрим корректный возврат управления процедуре
поиска-восстановления, а также предусмотрим исправление всех call'ов внутри найденного кода с учетом будущего
местоположения дополнительной кодовой секции, и предусмотрим исправление call'а, вызвавшего текущий _DllDispatch. Поиск должен идти в два этапа: в секции кода,
а затем в найденном коде.
По завершении работы программы мы будем
иметь два файла: text.bin - исправленная секция кода и
code.bin - дополнительная секция с найденным кодом, которую
необходимо добавить к нашему дампу. Теперь, когда
новый EXE-файл полностью скомпонован, protect.dll можно удалить. Он больше не
нужен.
Исправление
багов
Радость от запуска игры без Star-Force быстро сменяется разочарованием. На
лицо баги: количество денег стремится к бесконечности, задание на миссию не
показывается, перед запуском карты предлагается соединиться по TCP/IP и,
наконец, не генерируется случайная карта. Причиной
первых двух багов оказалось имя дампа. При переименовнии в
dmcr.exe они исчезли. Причиной остальных - ошибка при
восстановлении кода одного из _DllDispatch, а именно
того, который не обращался к секции кода, и не был нами пойман. В итоге, в новой
секции образовался один совершенно посторонний call.
Потенциально, такие функции - слабое место данного
метода исследования, но не в этом случае. Путем классического реверсинга
определяем, что "лишний" call вкрался
по адресу (в моем случае) 0A3Ch новой секции. Заменяем
5 nop'ов по этому адресу на
13.
Вот теперь все. Окончательно и
бесповоротно. Star-Force
снята.
Заключение
Star-Force -
очень интересная защита. Не бойтесь потратить время на ее исследование - она
того стоит. Надеюсь, что моя статья помогла Вам в ее исследовании. Если у Вас возникли какие-либо вопросы - пишите, я с удовольствием
Вам отвечу.
ASMax