Содержание статьи
«Если ассоциировать SecuROM v7.33.17 с танком Абрамсом без динамической защиты, OllyDbg – с гранатометом РПГ-7, а X-code injection — с кумулятивной гранатой для гранатомета, то, как и в реальности, такой выстрел навзничь прошьет броню этой тяжелой неповоротливой машины и достигнет поставленной цели – OEP… Выведенную из строя машину изучают Российские инженеры…»
Введение
Стратегия Tiberium Wars до сих пор является одной из самых популярных игр серии Command & Conquer от небезызвестной конторы Electronic Arts. Последняя лояльно относится к Sony Digital Audio Disc Corporation (SONY DADC), чью мать вспоминают, когда очередная игрушка, запротекченная SecuROM, просит вставить оригинальный диск. Несмотря на сумасшедшую «популярность» SecuROM во всем мире, в нашей стране редко когда можно услышать о нем публично (exelab.ru в расчет не берем). В первую очередь это объясняется наличием своего звездного протектора, но если Sony после нескольких судебных разбирательств отказалась от услуг нулевого кольца, то ребята из Protection Technology продолжают пользоваться низкоуровневыми функциями, где абсолютным чемпионом по вызову является KeBugCheckEx.
Warning!
Вся информация предоставлена исключительно в ознакомительных целях. Ни редакция, ни автор не несут ответственности за любой возможный вред, причиненный материалами данной статьи.
Таким образом у разработчиков осталась одна арена для битвы – прикладной уровень. И, естественно, они приложили максимум усилий для обеспечения надежной защиты… которая была пробита, разобрана и демонтирована :).
What is target
Сначала определимся с нашим инструментарием. Собственно, понадобится сама игрушка Tiberium Wars (с последним патчем 1.9), для снятия дампа — OllyDbg 1.10 с OllyDmp (собственно как дампер) и OllyDbg 2.0 без каких-либо плагинов. Несмотря на такой столь скромный набор, в нашем случае работать можно с одним отладчиком, а основным оружием будет ассемблерный код. Но прежде всего ответим на вопрос, какой исполняемый файл требуется загрузить в отладчик. Переходим в папку с установленной игрой. Первым на глаза попадается безобидный CNC3.exe: судя по точке входа, откомпилирован Microsoft C++ 7.0, стандартные секции .text, .rdata… Правда, если его запустить, нас вежливо попросят вставить оригинальный диск. Извините, имеется только Daemon Tools с образом, поэтому возникает вполне нормальное желание развязать четвертую тибериумную драку и, в конце концов, отодрать проверку диска от программы. Так как я был знаком с предыдущими играми из серии CnC, то знал, что CNC3.exe играет роль обертки и просто через WinAPI CreateProcess запускает нужный файл с расширением .dat (на самом деле обычный Microsoft PE EXE format) с нужными параметрами. Несложно догадаться, что искомая цель — \RetailExe\1.9\cnc3game.dat. Грузим в отладчик упомянутый файл и по секции .securom понимаем, с чем имеем дело. F9… Милое окошко с надписью «Не удалось запустить требуемый модуль безопасности». Еще раз его можно прочитать, если запустить любой API-шпион, ProcMon… Одним словом, разработчики прекрасно осведомлены о всех программах, которые создают неприятности их продуктам. Присоединиться к процессу тоже нельзя – он просто завершится. Впрочем, эта гадость действует только на время проверки диска. Неплохо! Для меня тогда это означало, что ближайший месяц OEP и рабочий дамп я точно не увижу. Конечно, спустя несколько месяцев я не хуже разработчиков знал, что и как работает в SecuROM 7.33. Причем вся линейка 7.3x практически идентична, особенно это касается виртуальной машины (самое заметное изменение: в более поздних версиях заменено на «You Are Now Entering a Restricted Area»). Но сейчас у меня только один отладчик и никакой надежды выйти победителем такого серьезного врага, над созданием которого трудились весьма не глупые люди.
Хакер #159. Подделка контрольной суммы и ЭЦП с помощью коллизий
Философия взлома
Пробить навесную броню и доехать до OEP тупым ковырянием в отладчике — слишком долго и неэффективно. Будем бить в борт! Первая дыра в защите не заставила себя долго искать: я сразу заметил, что протектор не проверяет целостность всего файла. Ну, не то чтобы совсем не проверяет, нечто подобное имеется, но вот только идет оно как проверка участков и направлена, прежде всего, на выявление программных точек останова (так называемый «перекрывающий код»). Она мне никак не мешала, поэтому в практическом плане это означает, что возможно внедрение X-кода в образ файла статическим путем. Строго говоря, X-код — это осмысленная совокупность байт, внедренных посторонним лицом или программой в целевой код процесса с целью выполнения определенной задачи.
Различают следующие типы:
- On-line patching. Через WinAPI ReadProcessMemory/WriteProcessMemory читаем/пишем в адресное пространство процесса. К примеру, таким образом, ставят флаг регистрации в NtExplorer под эгидой AsPack 2.11c, чтобы не заморачиваться с распаковкой. Однако современные протекторы типа Themida не дают доступ в свое адресное пространство.
- Offline patching. Или статический патчинг. Собственно, наш случай, когда ничто не мешает писать прямо в исполняемый образ и перехватывать в нем управление. Обнаружить такого «Штирлица» у себя в тылу протекторам на порядок сложнее. При необходимости одурачивают защиту, делая оригинальную копию исполняемого файла и через GetCommandLine/GetFilePath возвращают ссылку на него.
- Dll-hijacking. С учетом того, что при загрузке образа первыми получают управление динамические библиотеки, записанные в его таблице импорта (точнее, функция DllMain), то мы первыми получаем бразды правления над защитой и успеваем скрыть себя задолго до работы навесной брони.
Напоминаю, что для быстрой загрузки можно прописаться в HKLM\Software\Microsoft\WindowsNT\CurrentVersion\Windows\AppInit.
Так вот, наша задача — перехватить управление в нужном месте и показать адрес возврата, чтобы в нужное время присоединиться к процессу. Почему я иду таким путем? Как уже было упомянуто выше, вся проблема состоит в том, что разработчики прекрасно осведомлены о любых программах, которые создают проблемы их продуктам. Этот факт несложно проверить, прослушав CreateFile, FindWindow в исследуемом протекторе. Механизмы межпроцессного взаимодействия – вчерашний день; передовой способ борьбы с навороченными защищалками – непосредственное внедрение в целевой образ защищенной программы. Обнаружить его на порядок сложнее, ведь в этом случае мы используем ресурсы самого протектора. Дабы противостоять такому раскладу, прежде всего вводят проверку целостности файла (динамическая защита). Ту самую, которая отсутствует в нашем случае (чем я, собственно, воспользовался). Едем дальше. Я уже сказал, что для компиляции CNC3.exe использован Microsoft C++ 7.0. А ведь очевидно, что и cnc3game.dat откомпилирован тем же компилятором. Точка входа выглядит как:
004628DA CALL 004784B8
004628DF JMP 004626FA
Реально точка входа находится по операнду прыжка — 004626FAh. Функция по адресу 004784B8h ничем полезным не занимается, и я ее всегда игнорирую. Однако в ней мы находим, что она вызывает несколько API (GetSystemTimeAsFileTime, GetCurrentProcessId…). Для нас это означает только одно: если в GetSystemTimeAsFileTime внедрить X-код, который перехватит управление и покажет нам адрес возврата, то у нас будет замечательная возможность попасть в окрестности OEP и снять рабочий дамп, при условии, что после распаковки стартовый код действительно на своем законном месте. И хотя SecuROM 7.33 проверяет прологи некоторых значимых WinAPI, этот список весьма неполон, плюс сама проверка идет только в самом начале. Вот тебе и следующий недочет! Теперь пора собрать нашу кумулятивную гранату. Место для базирования я выбрал в конце секции .est и приступил к сборке – написанию asm-кода, который самостоятельно выполнит всю работу. Весь код состоит из двух частей. Первая доставит саму гранату на место: заменит пролог GetSystemTimeAsFileTime на безусловный переход ко второй части. Собственно, в этом случае получаем управление из распаковщика (определить место которого несложно, поставив точку останова на секцию .text) и туда же его возвратим после того, как переход будет установлен. Кстати, первоначально, чтобы получить доступ на запись в секцию kernel32.text (дело было на висте), я офлайн на 2k3 через PeTools устанавливал атрибут Write, пересчитывал контрольную сумму… и ведь работало! Способ весьма дурной, учитывая, что есть WinAPI VirtualProtect, но нестандартные подходы иногда тоже на руку. Переходим ко второй части нашего представления. Первоочередной задачей здесь является показ адреса возврата и удержание управления до прибытия на место отладчика. Я реализовал свой алгоритм, который переводит large integer в ACSIIZ-строку (об ltoa я тогда не знал), а задачу показа и удержания управления элегантно решил с помощью MessageBox, но затем управление передается обратно в WinAPI. Впрочем, для показа можно нагло использовать функционал самого протектора – CreateThread с адресатом 00F9AD0E, но это непроизводительно. Итак, граната собрана, попробуем! Монтируем мини-дамп. Запускаем игру. Нас интересуют все MessageBox после проверки: 00DDCE77, 76B414D4, 7C34207B, 0040A5AE… Сто-ооп! Последний, да ведь вызов идет из секции .text! We need attach now! Присоединяемся к процессу ольгой 1.10, которая с дампером, и переходим по последнему адресу. Невероятно, но мы остановились практически в OEP (точка входа расположена по адресу 0040A2C7). Немногим позже, когда виртуальная машина (VM) стала более-менее изучена, было сделано еще одно умошокирующее открытие (снова просчет команды из SONY DADC) — попасть в OEP можно с ювелирной точностью и при этом еще более безопасным путем. И как вы думаете, что этому помогло? SecuROM v7.33 Virtual Machine, чья задача — наоборот защищать от взломщиков, но никак уж не помогать им!
SecuROM v7 Virtual Machine
Инженеры Sony DADC как в воду глядели: с готовым образом добраться до OEP и снять дамп — по факту дело нескольких минут, матерых взломщиков это никак не остановит. Навесная защита бессильна, значит, выход один – сделать максимально неработоспособным дамп! Следуя этой логике, большинство не хочет заморачиваться с VM, прописывая ее сложную структуру, в которой нереально разобраться за приемлемое время, и предпочитая дампить всю выделенную виртуальную память, и имеющую, и не имеющую отношение к VM, и приваривают ее в виде отдельной секции, предварительно догадавшись вставить после конструкции «MOV EAX, 1; CPUID;» инструкцию «MOV AL, CURRENT_CPUID_KEY_DELTA_DECODE» и проделать оное еще 254 раза. В итоге не выигрываем ни в размерах дампа, ни в быстродействии.
В общей сложности на полный разбор VM ушло около двух месяцев, причем первые полтора я попросту копался во всем без разбору, пока не «догадался» начать исследование с самого начала цепочки — и вот тут началось… Сама SecuROM v7.3x VM по существу не является виртуальной машиной в привычном представлении, никакой асм-код там не эмулируется. Все куда проще: по существу процедура с двумя аргументами (LPDWORD и VOID) и тремя вариантами работы. Ну а дальше остается только удивляться. Во-первых, чтобы понять, как оно работает, просто внимательно изучи первый островок (кусок кода от spin-блокировки до JMP EAX), который, как ни странно, выполняется всегда, и всегда первый.
REP STOS DWORD PTR ES:[EDI]
….
MOV DWORD PTR DS:[EBX+4],EAX
MOV EAX,DWORD PTR SS:[ESP]
MOV DWORD PTR DS:[EBX+8],EAX
MOV DWORD PTR DS:[EBX+0C],EDX
MOV BYTE PTR DS:[EBX+10],95
MOV DWORD PTR DS:[EBX+14],EBX
MOV DWORD PTR DS:[EBX+1C],ESP
Код, приведенный выше, с потрохами выдает главное хранилище, которое играет ключевую роль в VM. Разберешься в переменных и в области хранения промежуточных данных – считай, что VM на 90% взломана! Во-вторых, стало очевидно, что по существу здесь все наштамповано методом copy/paste. В-третьих, сумасшедшее количество закономерностей вплоть до использования регистров CPU на островках. А тот факт, что один из островков находится без обфускации – вообще убило! Единственное, что здесь реально доставляет, это ROL-байт (crypt-byte), так как без знания того, какие точно матоперации с ним выполняют все 255 островков виртуальной машины, невозможно построить кулхацкерскую автоматизированную ломалку и снять все за раз. Собственно, кому хочется понимать, о чем все-таки идет речь — читайте полный вариант статьи на диске или на нашем сайте. Следующий вопрос – реально ли отодрать VM от тибериума? Интуиция подсказывает, что более чем! Я уже успел проболтаться, что вариантов работы три. Начнем с последнего (способ №2), собственно нумерующихся по мере их появления в защищенной программе. Способ №2 полностью завязан с двумя противоположными по логике работы алгоритмами, представленными в виде двух процедур с двумя аргументами, которые отрабатывают друг за другом. В первом заносим любое число (например, 1), второй — ссылка на массив ключей (offset 00B93AFC), которые копируются в стек VM. На выходе в EAX получается что-то типа закодированного числа (0790A442), которое было передано в первом аргументе. Если это закодированное число (0790A442) скормить в первый аргумент алгоритма обратного хода, то на выходе в EAX мы получаем ту же 1, при условии, что массив ключей был одинаков (offset 00B93AFC). Короче, имеем две функции, дающие в сумме нулевой эффект. Собственно, роль VM в них такова: в первом случае крышуются асм-инструкции
MOV ECX, DWORD PTR SS:[EBP+8]
MOV ECX, DWORD PTR DS:[ECX]
MOV EAX, DWORD PTR SS:[EBP+0C]
AND EAX, ECX
MOV ECX, EAX
Во втором – инструкция NOP в переносном смысле. Причем прийти к этому выводу можно и без вскрытия VM, просто сравнив две упомянутые процедуры (обратного и прямого хода).
Следующие в списке способы — №1А и №1. Они похожи, но с той лишь разницей, что первый после выхода из VM переносит нас в запрашиваемую WinAPI, а второй – в запрашиваемую внутреннюю функцию (к слову, это может быть одна асм-инструкция, чаще всего — операция с резервированием стека). Скрытых WinAPI не так уж и много, вот неполный список: SetUnhandledExceptionFilter, GetModuleFileNameA, DeleteFileA, GlobalFree. Обращение к VM в этом случае всегда ознаменовывается CALL EAX, информация черпается по известным смещениям в таблице импорта. В общей сумме в дампе набралось около десятка вызовов – смело выдираем и ставим нормальные вызовы WinAPI. Наиболее проблемным является самый первый способ (по причине сумасшедшего количества вызовов — около 10k). Тут два варианта:
- Дампить. Простая техническая реализация, но требуется перетыкать все возможные меню, да и вообще пройти чуть ли не всю игру. В отличие от «Косынки» и «Пасьянса» — верный способ убить время с пользой!
- Зная структуру всех 255 островков (на самом деле в способе №1 участвуют около 50), по сигнатуре базы запросов написать на С++ программу, которая ищет все базы запросов, затем строит всю цепь работы VM и в нужном месте виртуального стека вытаскивает и дешифрует (XOR SecretDATA, 43E2AB9D) упрятанные данные. Наиболее эффективное решение.
Особенностью способа №1 является переход к VM: CALL ANY_OFFSET -> JMP DWORD PTR DS:[перемещаемый адрес] -> база запроса -> JMP [VM_VIRTUAL_ADDRESS] -> VM. В остальных случаях все обходится прямым вызовом VM без второго элемента в цепочке.
Суть в том, что инструкция JMP DWORD PTR DS:[перемещаемый адрес] играет роль железнодорожной стрелки. Первый раз, когда вызывается внутренняя функция, «стрелка» переведена на VM, а точнее — в базу запросов, где происходит заправка упомянутых выше двух аргументов для виртуальной машины (LPDWORD и VOID). В процессе работы VM извлекает адрес запрашиваемой функции и ставит его как перемещаемый адрес – стрелка переведена! После выхода из VM происходит переход в нашу запрашиваемую функцию, ну а после первого вызова все, кто будет ее вызывать, попадают сразу куда нужно. Как видишь, оптимизация! Кстати, количество инструкций, которые выполняются за один прогон виртуальной машиной в способах №1 и №1А, колеблется около 3k, зато способ №2 бьет все рекорды – 30k. Причем островок без обфускации принадлежит именно последнему способу. Финишная работа с дампом включает перестроение в нормальный вид секции кода, удаление ненужных и посторонних внедрений типа
0044F4D2 DEC DWORD PTR DS:[158297A]
0044F4D8 JE NODVD.00482DE5
00482DE5 MOV DWORD PTR DS:[158297A],18D
00482DEF JMP NODVD.0044F4DE
Последним пунктом отсылаем дамп в Sony DADC. На этом тибериумная драка заканчивается.
LINK
- tuts4you.com/download.php?view.2090В самом конце, благодаря Nightshade, я совершенно случайно узнал, что существует англоязычная статья по виртуальной машине SecuROM 7.30. Было приятно увидеть, что в целом наши выводы совпали. Однако в статье структура VM излагается в несколько ином виде, да и некоторых аспектов работы VM я не увидел.
- playground.ru/cheats/4932/NoDVD для CnC3: Tiberium Wars v1.9. Отличительной особенностью является пристроенная в секцию .memory виртуальная машина. В статье взят в качестве основного примера. Новичкам рекомендуется начать с него.
Заключение
В заключение я просто поблагодарю SONY DADC за ее действительно интересный продукт (для хакеров в частности)! До встречи на страницах ][.