Содержание статьи
Сегодня мы рассмотрим распаковку (далее анпак) игры, защищённой StarForce
(далее старка, стар). Сразу хочу развеять миф о неломаемости этой защиты и
поведать вам, что стар сможет распаковать даже ребёнок.
Инструменты
Что же нам потребуется? Самое главное - нам нужна игра. В качестве
подопытного кролика я выбрал "King’s Bounty - The Legend demo" - это демо-
версия, скачать её можно
тут. Второе по значимости - это отладчик, естественно мы будем использовать
OllyDbg (далее олька), не побрезговав я возьму сборку от арабов - Ollydbg 1.10
By Mouradpr + плагин OdbgScript последней на данный момент версии. Скачать
сборку вместе с плагином можно по
ссылке. Так же нам нужен
ImportRec v1.7
для восстановления импорта и
PeTools. Ещё блокнот, пару рук и капельку мозгов :).
Распаковка
Ну что же, начнём. Переходим в каталог с установленной игрой и видим там
исполняемый файл "kb.exe" (собственно его мы и будем отлаживать). Ещё мы видим
файлы: "protect.exe", "protect.x86" и "protect.dll". Чтобы узнать точную версию
StarForce надо посмотреть внимательно на файл "protect.dll", в данном случае мы
имеем дело с версией "5.50.11.15". Хорошо, загружаем файл "kb.exe" в ольку -
OllyDbg застопорился на команде "RETN 4". Смело жмём SHIFT+F9 пару раз, пока не
появится стандартное окно старфорса, теперь в этом окне нажмём кнопочку "Ок", а
в ольке на вопрос подвергать ли нашу игру повторному анализу ответим
отрицательно. Всё, теперь мы стоим на EP (Entry Point).
Немного поясню: если мы сейчас нажмём на ENTER, то попадём в библиотеку
защиты "protect.dll" где находится сама виртуальная машина (далее ВМ) старки.
Она расшифрует участки кода в нашем исполняемом файле и передаст ему управление.
В этой статье мы не будем разбираться с виртуальной машиной, а обсудим это в
следующий раз.
Итак, мы стоим на EP, давайте посмотрим что же представляет из себя точка
входа в программу. Мы видим с дюжину джампов в ВМ старфорса. Если присмотреться,
то все вызовы ВМ можно найти с помощью такой маски: "ff25????e901". В отличии от
других вызовов, инструкция по адресу "01E9A1C9" будет исполнена только один раз.
01E9A1C9 FF25 2835E901 JMP DWORD PTR DS:[1E93528]
Сразу скажу, что для корректного восстановления таблицы импорта нам
потребуется запомнить последний переход в ВМ перед EP, т.е. адрес "01E9A1C3".
01E9A1C3 FF25 2435E901 JMP DWORD PTR DS:[1E93524]
Двигаемся дальше. Нажав "ALT+M" переходим на вкладку "Memory Map", ставим
breakpoint на доступ, на секцию кода .text. Для этого нажимаем правой кнопкой
мыши на нужной секции и выбираем пункт "Set memory breakpoint on access". Теперь
запускаем наше приложение нажав SHIFT+F9. И олька тормозится на OEP (Original
Entry Point). Это хорошо, теперь нам надо избавиться от привязки к библиотеке "protect.dll",
а для этого нам нужно восстановить все переходы в ВМ. Приступим. Нажимаем CTRL+B
и ищем джампы в ВМ по нашей маске, т.е. "ff25????e901". Нажимаем "Ок" и поиск
приводит нас на адрес "0083443A":
0083443A FF25 0030E901 JMP DWORD PTR DS:[1E93000]
Давайте разберёмся что тут и как. Нажмём правой кнопкой мыши на инструкции по
адресу "0083443A" и выберем пункт "New origin here". Этой командой мы поменяли
текущий EIP, поэтому программа теперь будет исполнятся именно с этой инструкции.
Нажав F8 мы попадём в "protect.dll". Нам необходимо узнать адрес который вернёт
эта библиотека, т.е. тот адрес на который мы попадём после завершения работы "protect.dll".
Для этого не снимая бряку с секции мы нажмём SHIFT+F9, нажмём и увидим, что мы
всё ещё в библиотеке, поэтому будем запускать программу командой SHIFT+F9 пока
снова не вернёмся в "kb.exe".Нажам с десяток раз мы всё-таки попали на адресс
"00834468":
00834469 50 PUSH EAX
Теперь если мы запишем по адресу "0083443A" команду "JMP 00834469
",
то избавимся от одного перехода.
Так как у нас этих переходов целая куча, на восстановление руками каждой
процедуры нам потребуется значительное время. Чтобы не потратить на распаковку
одной программы целый день мы напишем простой скрипт, который будет выполнять за
нас описанную выше последовательность действий:
//Начало скрипта
//Объявляем переменные:
var amount
var code
var table
var ep
var temp
var temp2
var temp3
var reg_eax
var reg_ecx
var reg_edx
var reg_ebx
var reg_esp
var reg_ebp
var reg_esi
var reg_edi
var codebase
var codesize
@start:
bpmc //Снимаем бряку на памяти
gmi eip,codebase // Определяем начало секции .text
mov codebase,$RESUL // Записываем результат в переменную "codebase"
gmi eip,codesize // Определяем размер секции .text
mov codesize,$RESULT // Записываем результат в переменную "codesize"
//Сохраняем регистры:
mov reg_eax, eax
mov reg_ecx, ecx
mov reg_edx, edx
mov reg_ebx, ebx
mov reg_esp, esp
mov reg_ebp, ebp
mov reg_esi, esi
mov reg_edi, edi
mov ep, eip //Записываем OEP в переменную "ep"
mov address, 00401000 //Сюда вводим начальный адрес поиска jmp vm
mov table, 01EA2000 //Здесь указываем адрес куда будет записываться новая
табличка импорта (адрес начала секции .ps4)
@find:
xor ebx,ebx
findmem #ff25????e901#,address // Начинаем поиск инструкций по памяти начиная с
адреса 00401000
cmp $RESULT, 0 //Если не найдено
je @exit // тогда уходим
mov eip, $RESULT //Ставим найденный адрес на eip
mov code, $RESULT //Сохраним адрес прыжка по секции кода
mov address, $RESULT //Сместим начало поиска
gci eip, DESTINATION // Определяем конечный адрес прыжка
mov temp, eip //Запишем значение eip в переменную temp
GMI temp, MODULEBASE //Определяем текущую базу модуля
mov temp2, $RESULT //Сохраним результат в переменную temp2
bprm codebase,codesize //Установим бряку на чтение на секцию .text
sti //Трассируем (F7)
erun //Запустим приложение (SHIFT+F9)
@jumped:
mov temp, eip //Запишем значение eip в переменную temp
GMI temp, MODULEBASE //Определяем текущую базу модуля
mov temp3, $RESULT //Сохраним результат в переменную temp3
cmp temp2, temp3 //Сравним переменные, если они не равны, значит мы всё ещё в "protect.dll"
jne @next3
mov temp, eip //Запишем значение eip в переменную temp
@next:
GMI temp, MODULEBASE //Определяем текущую базу модуля
mov temp2, $RESULT //Сохраним результат в переменную temp2
GMI [table-4], MODULEBASE //Определим предыдущую базу модуля в табличке
mov temp3, $RESULT //Сохраним результат в переменную temp3
cmp temp2, temp3 //Сравним результаты
jne @separator //Если базы модулей не равны, поставим разделитель
next:
mov [table], temp //Адрес функции кладём в табличку
mov [code], 25ff //Правим адрес по коду, на опкод прыжка
mov [code+2], table //Запишем в переходник текущий адрес таблички
cmp code, 01E9A1C3 //Адрес последнего переходника (мы его запоминали в начале
статьи)
je @exit //Если это был последний переходник, тогда уходим
add table, 4 //Смещаем адрес таблички
add amount, 1 //Устанавливаем счётчик (сохраняем количество переходников в вм)
//Восстанавливаем регистры:
mov eax, reg_eax
mov ecx, reg_ecx
mov edx, reg_edx
xor ebx, ebx
mov esp, reg_esp
mov ebp, reg_ebp
mov esi, reg_esi
mov edi, reg_edi
mov eip, ep //Берём из переменной адрес OEP и переходим на него
bpmc //Снимаем бряку на памяти
jmp @find //Ищем дальше
@exit:
//Восстанавливаем регистры:
mov eax, reg_eax
mov ecx, reg_ecx
mov edx, reg_edx
mov ebx, reg_ebx
mov esp, reg_esp
mov ebp, reg_ebp
mov esi, reg_esi
mov edi, reg_edi
mov eip, ep //Берём из переменной адрес OEP и переходим на него
ITOA amount, 10. //Кодируем 16-ричное, в 10-ную систему счисления
eval "Found and recovery jmp_vm - {$RESULT} items."
msg $RESULT //Выводим отчёт о том сколько джампов мы нашли и исправили
ret //Завершаем работу скрипта
@separator:
add table, 4 //Смещаемся
jmp next //Работаем дальше
@next3:
erun //Запустим приложение (SHIFT+F9)
jmp @jumped //Работаем дальше
//Конец скрипта
Вот и весь скрипт, настал черёд проверить его. Перезапускаем нашу игру в
отладчике и топаем на OEP. Теперь запускаем скрипт и любуемся его безупречной
работой. Работа нашего скрипта займёт примерно минут 25-30, поэтому мы можем
пока попить кофе :).
Скрипт отработал, перед нами висит долгожданное сообщение о том, что найдено
и восстановлено 329 джампов в ВМ. Нажимаем "Ок". Итак, мы вновь стоим на OEP,
теперь можно дампить. Запускаем ImpRec и из выпадающего списка выбираем наш
процесс: "kb.exe". В поле "OEP" указываем наш - 40D583, т.е. 0080D583 - 00400000
= 40D583. Нажимаем на кнопку "AutoSearch", а затем на кнопку "Get Imports".
Видим, что табличка импорта успешно восстановилась. Теперь жмём правой кнопкой и
в выпадающем меню выполняем следующую последовательность действий: "Advanced
Commands > Select Code Section(s)", в появившемся окне отмечаем все секции и
нажимаем на кнопку "Full Dump". Сохраняем наш дамп под именем "kb_dump.exe" и
получаем сообщение "Dumped! ;-)" Теперь нам нужно прикрутить импорт к нашему
дампу, для этого нажимаем на кнопку "Fix Dump" и выбираем файл - "kb_dump.exe".
Всё, закрываем ImportRec, OllyDbg и переходим в каталог с нашей игрой.
Видим, что у нас есть два новых файла: "kb_dump.exe" и "kb_dump_.exe", первый
можно удалять, а вот со вторым мы немного поработаем :). Давайте попробуем
запустить наш дамп. Запускаем и... и видим противную ошибку: "R6002 floating
point". Об этой ошибке уже сказано немало слов, могу немного пояснить, что
возникает она после распаковки или же после упаковки приложения. Подробнее можно
почитать в
блоге ManHunter. Мы же не будем изобретать велосипед и воспользуемся
готовой тулзой для фикса этой ошибки, она кстати с исходниками. Запускаем
прогу, нажимаем на кнопочку patch и выбираем наш дамп, в итоге мы получаем
пропатченый файл: "Fixed.exe".
Теперь давайте запустим PeTools и нажмём сочетание "ALT+ENTER",
таким образом мы открыли окно настроек программы, ставим галочки на следующих
пунктах - "Pe Rebuilder": "Validate PE", "Rebuild PE" и "...save overlay", жмём
на кнопочку "Ок". Нажмём сочетание клавиш: "ALT+3" и в появившемся диалоговом
окне выбираем файл: "Fixed.exe". Получив заветное сообщение "Rebuilding finished."
жмём на кнопку "Close" и закрываем PeTools. Что же это нам дало? Давайте
перейдём вновь в каталог с игрой и посмотрим размер нашего дампа - он стал
меньше, примерно на 10 Мб. Ну всё, можем запускать игру. Видим, что игра
полностью функциональна, никаких ошибок и т.д. и т.п. Кстати, файлы защиты, т.е.
"protect.exe", "protect.x86" и "protect.dll", можем смело удалять.
Заключение
Вот мы и анпакнули мощный и ужасный StarForce, пугающий некоторых людей одним
лишь только названием. Данный способ (не совсем правильный и не всегда
подходящий) был опробован на нескольких приложениях, в 50% случаев распаковка по
данному методу даёт положительный результат. В следующей статье мы рассмотрим
распаковку 10 000 проекта StarForce - "S.T.A.L.K.E.R.: Call Of Pripyat" где
использованы дополнительные функции защиты. Благодарю всех читателей, а также
всех участников форумов cracklab.ru (особая благодарноть Nightshade, Bronco и
FrenFolio) и xakep.ru. До встречи!