Исследование Star-Force: КАЗАКИ
v1.03
 
Конец 2000-го года ознаменовался выходом игры Казаки: Европейские войны - первой игры, использующей систему защиты Star-Force. Своим появлением Star-Force поставила много вопросов перед исследователями программ. Пришло время дать на них ответы.
 
Что представляет из себя защита?
Ядро защиты - это интерпретатор псевдокода, что само по себе сильно затрудняет ее исследование. Часть импортируемых функций копируются из своих библиотек и видоизменяются. Часть кода расшифровывается лишь при выполнении. Кроме этого, активно используются следующие приемы: проверка CRC различных участков памяти, постоянное обнуление отладочных регистров DRx, контроль времени выполнения различных участков кода при помощи инструкции RDTSC, причем последние выполняются из ring0 используя перехваченный INT 0.
Останавливаться на всем этом более подробно я не стану, потому что "мы пойдем другим путем", и многим из этих защитных механизмов так и не будет суждено предстать перед нами во всей красе.
 
Снятие дампа
Получение дампа исполняемого файла в этой версии 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
asmax@imail.ru