• Партнер

  • Сегодня мы рассмотрим распаковку (далее анпак) игры, защищённой 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. До встречи!

    Подписаться
    Уведомить о
    1 Комментарий
    Старые
    Новые Популярные
    Межтекстовые Отзывы
    Посмотреть все комментарии