Отечественные дистрибьюторы игровых программ мыслят шаблонно и не делают выводов из своих и чужих просчётов. По крайней мере, такое впечатление сложилось у меня после недавнего изучения защиты игр NevoSoft, которое заняло немногим больше времени, чем писалась эта краткая статья.
Честно говоря, здесь я не планирую детально разбирать механизм защиты, будут показаны лишь основные моменты, поскольку (как показал опыт предыдущей заметки про Alawar и письма читателей) технические детали в программировании неинтересны обычным пользователям. Те из читателей, для кого представляет интерес сам процесс изучения защиты, смогут проследить весь путь исследования по приведённым ниже выводам. Особого труда это не составит, справятся даже новички.
Для наглядного примера к статье возьмём первую попавшуюся игру от производителя. Я скачал «Остаться в живых», которая заявлена на сайте хитом сезона. Устанавливаем игру обычным способом:
Скачиваем с сайта программу-загрузчик, запускаем, задаём ей путь для установки и ждём, пока скачается собственно сама игра:
Для чистоты эксперимента, после установки игры следует перезагрузиться, чтобы элементы защиты NevoSoft корректно запустились и мы имели дело с полноценно защищённой версией игры. Первое, что бросается в глаза после перезагрузки – в области уведомлений появился значок с фирменной эмблемой и в автозагрузку добавилась программа непонятного назначения. При запуске игры появляется типичное окно с уведомлением о временном ограничении и всякой рекламной шелухой от производителя.
Посмотрев на картинки – переходим к самой сути нашей задачи. В общем, механизм защиты здесь чем-то похож на Alawar, только без малейших намёков на серьёзный подход к делу. Alawar хотя бы пытался ограничивать доступ к оболочке игры посредством использования ASProtect в своих дистрибутивах, а здесь и этого нет. В основе лежит навесная защита и оболочка, контролирующая запуск программ. В отличие от игр производства Alawar, где эта оболочка («wrapper») прилагается к каждой игре, – здесь она единая для всех, устанавливается в систему раз и навсегда при инсталляции любой игры от Nevosoft. Исполнимый файл 'drm.exe' в автозагрузке, создающий значок в трее и время от времени показывающий рекламу, по совместительству является этой самой оболочкой-часовым, проверяющим ограничения, ведущим триальный отчет времени и всё такое прочее. Его грозное название, видимо, было призвано отпугнуть непрошенных исследователей потенциальным наличием сложных криптозащит, но на поверку оказалось, что ничего такого здесь нет и достаточно отладчика и дизассемблера. Реверсирование защиты Nevosoft сводится к изучению алгоритмов в этом исполнимом файле.
Алгоритм защиты состоит в следующем:
1) Установленная игра запускается ярлыком, указывающим на 'drm.exe' с уникальным идентификатором игры в параметре. Оболочка обращается к внутренней базе данных и по идентификатору игры ищет соответствующие ей параметры: бесплатное время для пользователя, исполнимый файл игры, параметры защиты исполнимого файла и прочую информацию. В результате проверок загружается файл игры, в процессе загрузки с него снимается защита и ему передается управление, одновременно с этим включается таймер бесплатной игры, если игра не «куплена». По завершении процесса игры, оболочка записывает текущее значение оставшегося бесплатного времени в базу. Естественно, как только значение бесплатного времени для игры в базе будет равно нулю, оболочка больше не даст запустить игру;
2) Внутренняя база данных представляет собой обычную БД SQLite3, зашифрованную XOR-ом по ключевому массиву размером 256 байт, хранящемуся в теле оболочки. Данные ключевого массива статичны и в целях совместимости не меняются на протяжении времени (здесь и ниже используются фрагменты кода на языке Delphi):
bKeyArraySize = 256; arKeyDecrypt: array[ 0..bKeyArraySize-1 ] of Byte = ( $69, $f7, $23, $a3, $a2, $5f, $86, $8d, $c6, $43, $6d, $5b, $35, $c9, $7b, $ff, $7f, $a6, $a5, $75, $89, $89, $d8, $ee, $77, $f2, $d3, $22, $e4, $7a, $f1, $b4, $5a, $45, $5a, $d0, $72, $a3, $f4, $65, $b2, $5c, $ff, $8b, $5a, $db, $61, $e3, $f1, $eb, $15, $a8, $22, $9b, $a7, $5c, $ae, $27, $77, $5c, $bb, $43, $87, $0c, $86, $23, $5d, $53, $31, $cf, $7a, $7e, $3f, $2a, $da, $0b, $1f, $b6, $dc, $48, $a4, $e1, $f4, $85, $53, $d6, $2c, $50, $28, $20, $f9, $84, $dd, $93, $0d, $f3, $79, $df, $07, $ad, $a3, $de, $60, $62, $b1, $cb, $45, $5d, $32, $4d, $bb, $38, $9c, $52, $de, $05, $18, $31, $d1, $ba, $5d, $e9, $68, $cb, $f1, $67, $57, $54, $2a, $0d, $6e, $d2, $7f, $27, $35, $05, $82, $c9, $b2, $f9, $ce, $2f, $6a, $d1, $e8, $d3, $39, $b2, $23, $19, $e6, $fc, $7e, $6f, $4b, $5e, $b8, $c7, $c4, $ac, $56, $6b, $71, $55, $3e, $4b, $ba, $9e, $cf, $ae, $99, $67, $8e, $0a, $1f, $e9, $f4, $0e, $e4, $2c, $e1, $fe, $32, $ab, $d5, $f5, $36, $2f, $65, $bd, $92, $1a, $8d, $a1, $54, $d1, $f8, $e5, $16, $34, $9a, $65, $6a, $d8, $76, $a5, $f5, $e5, $f2, $a1, $be, $e7, $c9, $af, $2a, $76, $bb, $ec, $8c, $e2, $49, $6c, $89, $67, $33, $34, $fb, $91, $51, $d1, $af, $43, $a1, $e3, $21, $b6, $2d, $1a, $64, $3d, $9e, $3f, $53, $52, $ba, $c4, $44, $ec, $76, $3b, $79, $69, $91, $72, $bd, $d2 );
Всякий раз, когда оболочке нужно прочитать или записать данные в БД, база расшифровывается в память, с ней производятся необходимые манипуляции, производится шифрование и результат в кладётся обратно на диск. Сама шифрованная база хранится в скрытом файле 'base.db' в каталоге «Application Data» текущего пользователя:
Стоит заметить, что оболочка блокирует файл базы данных от чтения/записи сторонними программами (держит файл открытым эксклюзивно). При любых манипуляциях с файлом базы сначала нужно завершить процесс 'drm.exe'.
При желании, мы можем расшифровать базу с помощью такого алгоритма:
function DecryptNevoDatabase( lpSrcFile, lpDestFile: PChar) : Boolean; // here I won't using MMF (memory-mapped files) due to approximately // small files size (suppose that the Nevosoft DB isn't bigger than 12-15Mb) var hFileRead, hFileWrite: THandle; i: Byte; iNumRead, iNumWrite: Integer; Buffer: array[ 0..bKeyArraySize-1 ] of Byte; begin Result:= false; hFileRead:= CreateFile( lpSrcFile, GENERIC_READ, 0, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0 ); if ( hFileRead = INVALID_HANDLE_VALUE ) then Exit; hFileWrite:= CreateFile( lpDestFile, GENERIC_READ + GENERIC_WRITE, 0, nil, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0 ); if ( hFileWrite = INVALID_HANDLE_VALUE ) then Exit; try // decrypt file repeat if ( ReadFile( hFileRead, Buffer, bKeyArraySize, Cardinal( iNumRead ), nil ) ) then begin for i:= 0 to iNumRead - 1 do // where is all street magic happens.. hehe Buffer[i]:= Buffer[i] xor arKeyDecrypt[i]; if ( iNumRead > 0 ) then begin if ( not ( WriteFile( hFileWrite, Buffer[0], iNumRead, Cardinal( iNumWrite ), nil ) ) ) then Exit; // checking bytes count to be written if ( iNumWrite <> iNumRead ) then Exit; end; end else iNumRead:= 0; until ( iNumRead < bKeyArraySize ); // setting the flag Result:= true; finally CloseHandle( hFileWrite ); CloseHandle( hFileRead ); end; end;
После расшифровки можно смотреть структуру и данные, хранящиеся в базе, при помощи любого вьювера SQLite, например, SQLite Browser (картинка кликабельна):
Можно даже отредактировать данные (триальный лимит игры), зашифровать полученный файл базы тем же ключом и заменить оригинальную базу своей – оболочка подхватит наши изменения. Кстати, это является одним из вариантов обхода защиты – получение «вечного» триала, но это некрасивое решение, «грязный хак».
3) Секция кода исполнимого файла игры, в которой находится точка входа PE EXE, побайтно зашифрована XOR-ом с массивом длиной 1024 байта, хранящимся в поле 'crpt_inf' в записи базы данных. Иными словами, по идентификатору игры оболочка загружает её исполнимый файл (получая имя файла из поля 'exec'), считывает из БД ключевой массив из поля 'crpt_inf' и производит расшифровку EP-секции ключевым массивом, после чего передает управление загруженному файлу.
Расшифровать секцию и получить «чистый» исполнимый файл игры можно так:
// decrypt PE file section function PEDecrypt_Section( lpPE, lpISH: Pointer; wSectionIdx: Word; lpCryptoData: PChar ): Boolean; var ish: IMAGE_SECTION_HEADER; a, b: Byte; i, j: DWord; begin Result:= true; // STUB Move( Pointer( DWord( lpISH ) + DWord( ( wSectionIdx - 1 ) * SizeOf( IMAGE_SECTION_HEADER ) ) )^, ish, SizeOf( ish ) ); j:= 0; for i:= 0 to ish.SizeOfRawData - 1 do begin Move( Pointer( DWord( lpPE ) + ish.PointerToRawData + i )^, a, 1 ); Move( Pointer( DWord( lpCryptoData ) + j )^, b, 1 ); // we explicitly know, that crypto array always has lenght of 1024 bytes if ( j < 1023 ) then Inc( j ) else j:= 0; // no, no, no, - david blaine.. a:= a xor b; Move( a, Pointer( DWord( lpPE ) + ish.PointerToRawData + i )^, 1 ); end; AddListBoxItem_Sep( ' Распаковка успешно завершена.'); AddListBoxItem( ' ' ); SendMessage( GetDlgItem( hMainDlg, ID_LIST_LOG ), WM_VSCROLL, SB_BOTTOM, 0 ); end; ... // unwrap executable file's EP section function Unwrap_ExecutableFile( lpFileName, lpCryptoData: PChar ): Boolean; var hFile, hMapFile: THandle; lpMap: Pointer; e_magic: Word; inh: IMAGE_NT_HEADERS; lpOffset: DWord; wEPSection: Word; strSaveFile: string; begin Result:= false; AddListBoxItem( ' Укажите имя файла для расшифрованного файла... '); SendMessage( GetDlgItem( hMainDlg, ID_LIST_LOG ), WM_VSCROLL, SB_BOTTOM, 0 ); if ( Get_SaveFileName( hMainDlg, nil, 'Исполнимые файлы (*.exe)'+#0+'*.exe'+#0#0, strSaveFile ) ) then begin AddListBoxItem( ' Файл ' + strSaveFile ); AddListBoxItem_Sep( ' Производится распаковка исполнимого файла...'); CopyFile( lpFileName, PChar( strSaveFile ), false ); hFile:= CreateFile( PChar( strSaveFile ), GENERIC_READ + GENERIC_WRITE, 0, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0 ); if ( hFile = INVALID_HANDLE_VALUE ) then begin AddListBoxItem_Sep( ' Ошибка открытия результирующего файла!'); Exit; end; hMapFile:= CreateFileMapping( hFile, nil, PAGE_READWRITE, 0, 0, nil ); CloseHandle( hFile ); if ( hMapFile = 0 ) then begin AddListBoxItem_Sep( ' Ошибка записи в результирующий файл!'); Exit; end; lpMap:= MapViewOfFile( hMapFile, FILE_MAP_READ + FILE_MAP_WRITE, 0, 0, 0 ); CloseHandle( hMapFile ); if ( lpMap = nil ) then begin AddListBoxItem_Sep( ' Ошибка открытия результирующего файла!'); Exit; end; // doing some checks (if this file really win32 PE EXE) Move( lpMap^, e_magic, SizeOf( e_magic ) ); // 'MZ' if ( e_magic = IMAGE_DOS_SIGNATURE ) then begin // lpOffset now points on PE headers lpOffset:= DWord( lpMap ) + PDword( DWord( lpMap ) + $3C )^; // read 'nt headers' Move( Pointer( lpOffset )^ , inh, SizeOf( inh ) ); if ( inh.Signature = IMAGE_NT_SIGNATURE ) then // 'PE00' // seems to valid win32 PE header begin // retrieving EP section index (1- based) wEPSection:= PESectionFromRVA( Pointer( lpOffset ), inh.OptionalHeader.AddressOfEntryPoint ); if ( wEPSection > 0 ) then begin // lpOffset now points to array of 'image secton headers' lpOffset:= lpOffset + SizeOf( IMAGE_NT_HEADERS ); Result:= PEDecrypt_Section( lpMap, Pointer( lpOffset ), wEPSection, lpCryptoData ); end else AddListBoxItem_Sep( ' Ошибка определения исполнимого файла (IMAGE_NT_SIGNATURE)!'); end else AddListBoxItem_Sep( ' Ошибка проверки исполнимого файла (IMAGE_NT_SIGNATURE)!'); end else AddListBoxItem_Sep( ' Ошибка проверки исполнимого файла (IMAGE_DOS_SIGNATURE)!'); UnmapViewOfFile( lpMap ); end else AddListBoxItem_Sep( ' Операция отменена пользователем. '); end;
В целом, данная защита не сложнее используемой в Alawar, но чисто технически здесь много рутинных операций и дело не ограничивается правкой двух байт. В связи с этим решено было написать утилиту, автоматизирующую процесс исследования (собственно, куски кода, приведённые выше, взяты из неё):
Скачать утилиту можно здесь; исходники (на Delphi 7) здесь.
Учтите, что данная программа представлена лишь для образовательных целей и ни в коем случае не может использоваться для изготовления пиратских копий игр; прочтите на сайте отказ от ответственности.
Обсуждение
Добрй день, Денис!
Честно говоря не пробовал запускать программу, смотрел только код. Но видно, что меняется только одна секция .text. На самом деле изменяемых секций две. Вторая всегда размером в 3FH. Её надо дополнитьельно создать. Часто и без неё программа работает, но, к примеру Шоколатор 2 без неё запускается, работает, но при выходе происходит сбой, помогает только перезагрузка.
PS: WriteProcessMemory, так же показывает, что процессов замены два.
Может быть, защита уже изменилась; я смотрел примерно десяток игр от Невософт, но везде меняется одна секция. Брейки на 'WriteProcessMemory' я не ставил, поскольку не видел в том необходимости.
Вообще странно, конечно. Получается, что производитель оригинальной игры линкует лишнюю секцию мусора? Иначе, как объяснить её присутствие в незащищенном/расшифрованном EXE? Производители пытаются интерактивно взаимодействовать с wrapper-ом, используя секцию в памяти как буфер?
Как вернусь из отпуска, посмотрю на «Шоколатор 2»
Возможно я спутал название игры (смотрел много игр) но повторно сломанный Шоколатора 2 прекрасно работал и без дополнительной секции. Но вообще-то процессов WriteProcessMemory 2. К примеру в игре Супер Корова
WriteProcessMemory 1 hProcess = 0000045C Address = 401000 Buffer = 0C4A0020 BytesToWrite = B9000 — WriteProcessMemory 2 hProcess = 0000045C Address = 140000 Buffer = drm.004A21A8 BytesToWrite = 3F
В данном случае полностью заменяется 1 сеция, но это не обязательно, .text может быть рарбита на несколько секций и заменяться будет какая угодно. Честно говоря не задумывался зачем нужна новая секция, ведь получается, что всё работает и без неё.
Я тоже посмотрел Шоколатора 2, видел два вызова 'WriteProcessMemory()', но дальше с 'drm.exe' копать не стал (проверил, что работает с одной секцией и «забил» на детали).
Скачал только что «Потерянные Души» с их сайта, распаковывал только StraySouls.exe, потом вынес всю DRM и файл .db, папку с игрой зипнул и унёс маме на ноут, и ещё услал сестре в Италию. У мамы пока работает. thanx bro
Всё работает отлично, СПАСИБО!!! ОГРОМММММММНОЕЕЕЕЕ!!!
подскажите, пожалуйста, что делать??при запуске игры выдает ошибку CANNOT OPEN DATABASE похоже я что-то напутала с base.db , но что именно понять не могу я в этом плохо разбираюсь.
Если вы меняли там что-то вручную, произвольным образом, то неудивительно. Общий совет: сохраните данные игры, удалите повреждённый 'base.db' и переустановите игру заново. Рабочий 'base.db' появится автоматически.
Спасибо большое, я все исправила, только оболочка игр качается в папку не drm как было раньше ,а в Nevosoft.Games.
Подскажите плиз а почему при разшифровки БД пишет ошибка расшифровки БД!?
Укажите точный текст ошибки.
Извените что побеспокоил вас. все теперь нормально. ваша програмка работает на ура! спасибо огромное. А почему была ошибка расшифровки я так и не понял.
Вопрос автору статьи…
как вы выяснили что база зашифрована XOR-ом по ключевому массиву размером 256 байт?? а исполняемый файл - массивом длиной 1024 байта??
я просто этому хочу научиться….
заранее благодарю
Исследовал wrapper, ставил брейки. На каком-то моменте шло обращение к внешнему файлу (это можно и FileMon/ProcessMon отследить). При этом было чтение из ресурсов, редактором ресурсов можно посмотреть на запрошенные данные. Это был ключевой массив. Оттуда же и размер виден, для перестраховки можно поставить брейк и посмотреть, сколько байт читается из ресурсов. Их будет 256 или 1024 (учетверенный исходный массив, типа оптимизация чтения-записи).
Касательно ключевого массива из БД, там чисто эмпирически. Посмотрел на нескольких игрушках, размер данных в поле 'crpt_inf' всегда был 1024 байт. Где-то во wrapper-e это значение прописано явно. Или размером поля в SQLite прописано, сейчас точно не помню.
Ребят подскажите где скачать Delphi 7 просто где не скачиваю везде вирус дайте пожалуйста нормальную ссылку зарание большое спасибо))))
На трекерах ищите или в старых сборниках ПО.
Подскажите,пожалуйста,как сохранить изменения после редактирования базы данных в программе SQLiteExpertProfessional3.Заранее спасибо!
Для сохранения в 'SQLite Database Browser' есть стандартная кнопка «Сохранить». Для изменения значений, если программа не даёт изменять напрямую, пользуйтесь SQL-запросами.
Моя проблема в том,что я не знаю английского и опыта нет. В 'SQLite Database Browser'есть кнопка «Сохранить»,но редактировать не дает. В 'SQLite Expert Pro 3.4.Portable'редактирую,но не знаю,как сохранить(кнопки нет). Дальше у меня будет проблема,как обратно зашифровать базу. Что такое SQL-запросы,я не в курсе. Если можете,объясните мне поподробнее.Заранее благодарю!
Если у вас задача поэкспериментировать с wrapper-ом и значениями в базе, вам нужно почитать про основы SQL. В программе есть вкладка «Execute SQL» специально для выполнения запросов. Это несложно. Например, запрос
вернёт количество строк в таблице 'games', а запрос
вернёт ключевой массив, которым зашифрована EP-секция исполнимого файла. Есть запросы для изменения данных в базе, примеры запросов посмотрите в коде утилиты 'nevootools'. Касательно зашифровки, поскольку используется 'XOR', используется та же функция, что и для расшифровки (см. выше код DecryptNevoDatabase).
Большое спасибо,Денис!
Здравствуйте, что посоветуете на счёт новой защиты Nevosoft?
Пока нет времени смотреть, что там придумали.
Доброй ночи! Подскажите, пожалуйста, что делать?? При запуске игры выдает ошибку CANNOT OPEN DATABASE. base.db я не могу найти (у меня Windows 7). Что мне делать?
Переустановить игру, если не поможет – то в техподдержку Nevosoft.
А если в службе поддержки не могут помочт с этой ошибкой что делать???
Здравствуйте Денис!Поставил сегодня Windows7 и 'nevotools' не распаковывает экзешник,а расшифровывает базу и выдает ошибку.Не подскажете в чем дело?
Давно туда не смотрел, поэтому не знаю. Или прав на чтение/запись не хватает, или алгоритм изменили.
Спасибо.Будем разбираться.Он вообще не открывает файл,а срузу пытается расшифровать базу,хотя никто его об этом и не просит.
Извини,Денис.Совсем вылетело из головы,что в трейде нужно закрывать значок.В XP он навиду,а в «семерке» скрыт.