Поиск:

Обзор защиты NevoSoft

Отечественные дистрибьюторы игровых программ мыслят шаблонно и не делают выводов из своих и чужих просчётов. По крайней мере, такое впечатление сложилось у меня после недавнего изучения защиты игр NevoSoft, которое заняло немногим больше времени, чем писалась эта краткая статья.

Честно говоря, здесь я не планирую детально разбирать механизм защиты, будут показаны лишь основные моменты, поскольку (как показал опыт предыдущей заметки про Alawar и письма читателей) технические детали в программировании неинтересны обычным пользователям. Те из читателей, для кого представляет интерес сам процесс изучения защиты, смогут проследить весь путь исследования по приведённым ниже выводам. Особого труда это не составит, справятся даже новички.

Для наглядного примера к статье возьмём первую попавшуюся игру от производителя. Я скачал «Остаться в живых», которая заявлена на сайте хитом сезона. Устанавливаем игру обычным способом:

10.jpg


Скачиваем с сайта программу-загрузчик, запускаем, задаём ей путь для установки и ждём, пока скачается собственно сама игра:

11.jpg

12.jpg

Для чистоты эксперимента, после установки игры следует перезагрузиться, чтобы элементы защиты 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» текущего пользователя:

13.jpg

Стоит заметить, что оболочка блокирует файл базы данных от чтения/записи сторонними программами (держит файл открытым эксклюзивно). При любых манипуляциях с файлом базы сначала нужно завершить процесс '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 (картинка кликабельна):

14.jpg

Можно даже отредактировать данные (триальный лимит игры), зашифровать полученный файл базы тем же ключом и заменить оригинальную базу своей – оболочка подхватит наши изменения. Кстати, это является одним из вариантов обхода защиты – получение «вечного» триала, но это некрасивое решение, «грязный хак».

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, но чисто технически здесь много рутинных операций и дело не ограничивается правкой двух байт. В связи с этим решено было написать утилиту, автоматизирующую процесс исследования (собственно, куски кода, приведённые выше, взяты из неё):

15.jpg
Скачать утилиту можно здесь; исходники (на Delphi 7) здесь.

Учтите, что данная программа представлена лишь для образовательных целей и ни в коем случае не может использоваться для изготовления пиратских копий игр; прочтите на сайте отказ от ответственности.

Обсуждение

Сергей Сальцев, 14/09/2010 07:22

Добрй день, Денис!

Честно говоря не пробовал запускать программу, смотрел только код. Но видно, что меняется только одна секция .text. На самом деле изменяемых секций две. Вторая всегда размером в 3FH. Её надо дополнитьельно создать. Часто и без неё программа работает, но, к примеру Шоколатор 2 без неё запускается, работает, но при выходе происходит сбой, помогает только перезагрузка.

PS: WriteProcessMemory, так же показывает, что процессов замены два.

Денис Фатеев, 15/09/2010 08:30

Может быть, защита уже изменилась; я смотрел примерно десяток игр от Невософт, но везде меняется одна секция. Брейки на 'WriteProcessMemory' я не ставил, поскольку не видел в том необходимости.

Вообще странно, конечно. Получается, что производитель оригинальной игры линкует лишнюю секцию мусора? Иначе, как объяснить её присутствие в незащищенном/расшифрованном EXE? Производители пытаются интерактивно взаимодействовать с wrapper-ом, используя секцию в памяти как буфер?

Как вернусь из отпуска, посмотрю на «Шоколатор 2» :-)

Сергей Сальцев, 05/10/2010 06:50

Возможно я спутал название игры (смотрел много игр) но повторно сломанный Шоколатора 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 может быть рарбита на несколько секций и заменяться будет какая угодно. Честно говоря не задумывался зачем нужна новая секция, ведь получается, что всё работает и без неё.

Денис Фатеев, 05/10/2010 07:24

Я тоже посмотрел Шоколатора 2, видел два вызова 'WriteProcessMemory()', но дальше с 'drm.exe' копать не стал (проверил, что работает с одной секцией и «забил» на детали).

Renegade1979, 19/06/2011 16:57

Скачал только что «Потерянные Души» с их сайта, распаковывал только StraySouls.exe, потом вынес всю DRM и файл .db, папку с игрой зипнул и унёс маме на ноут, и ещё услал сестре в Италию. У мамы пока работает. thanx bro

Дмитрий, 01/12/2011 20:16

Всё работает отлично, СПАСИБО!!! ОГРОМММММММНОЕЕЕЕЕ!!!

Алина, 08/12/2011 20:38

подскажите, пожалуйста, что делать??при запуске игры выдает ошибку CANNOT OPEN DATABASE похоже я что-то напутала с base.db , но что именно понять не могу я в этом плохо разбираюсь.

Денис Фатеев, 09/12/2011 07:18

Если вы меняли там что-то вручную, произвольным образом, то неудивительно. Общий совет: сохраните данные игры, удалите повреждённый 'base.db' и переустановите игру заново. Рабочий 'base.db' появится автоматически.

Алина, 09/12/2011 12:54

Спасибо большое, я все исправила, только оболочка игр качается в папку не drm как было раньше ,а в Nevosoft.Games.

Евгений, 27/12/2011 05:05

Подскажите плиз а почему при разшифровки БД пишет ошибка расшифровки БД!?

Денис Фатеев, 27/12/2011 05:36

Укажите точный текст ошибки.

Евгений, 27/12/2011 07:51

Извените что побеспокоил вас. все теперь нормально. ваша програмка работает на ура! спасибо огромное. А почему была ошибка расшифровки я так и не понял. :-\

Electronic Arts, 05/02/2012 17:26

Вопрос автору статьи…

как вы выяснили что база зашифрована XOR-ом по ключевому массиву размером 256 байт?? а исполняемый файл - массивом длиной 1024 байта??

я просто этому хочу научиться….

заранее благодарю

Денис Фатеев, 06/02/2012 07:04

Исследовал wrapper, ставил брейки. На каком-то моменте шло обращение к внешнему файлу (это можно и FileMon/ProcessMon отследить). При этом было чтение из ресурсов, редактором ресурсов можно посмотреть на запрошенные данные. Это был ключевой массив. Оттуда же и размер виден, для перестраховки можно поставить брейк и посмотреть, сколько байт читается из ресурсов. Их будет 256 или 1024 (учетверенный исходный массив, типа оптимизация чтения-записи).

Касательно ключевого массива из БД, там чисто эмпирически. Посмотрел на нескольких игрушках, размер данных в поле 'crpt_inf' всегда был 1024 байт. Где-то во wrapper-e это значение прописано явно. Или размером поля в SQLite прописано, сейчас точно не помню.

Артём Дубинский, 10/03/2012 11:20

Ребят подскажите где скачать Delphi 7 просто где не скачиваю везде вирус дайте пожалуйста нормальную ссылку зарание большое спасибо))))

Денис Фатеев, 10/03/2012 11:36

На трекерах ищите или в старых сборниках ПО.

Анатолий, 04/04/2012 14:55

Подскажите,пожалуйста,как сохранить изменения после редактирования базы данных в программе SQLiteExpertProfessional3.Заранее спасибо!

Денис Фатеев, 04/04/2012 15:15

Для сохранения в 'SQLite Database Browser' есть стандартная кнопка «Сохранить». Для изменения значений, если программа не даёт изменять напрямую, пользуйтесь SQL-запросами.

Анатолий, 05/04/2012 11:34

Моя проблема в том,что я не знаю английского и опыта нет. В 'SQLite Database Browser'есть кнопка «Сохранить»,но редактировать не дает. В 'SQLite Expert Pro 3.4.Portable'редактирую,но не знаю,как сохранить(кнопки нет). Дальше у меня будет проблема,как обратно зашифровать базу. Что такое SQL-запросы,я не в курсе. Если можете,объясните мне поподробнее.Заранее благодарю!

Денис Фатеев, 05/04/2012 13:56

Если у вас задача поэкспериментировать с wrapper-ом и значениями в базе, вам нужно почитать про основы SQL. В программе есть вкладка «Execute SQL» специально для выполнения запросов. Это несложно. Например, запрос

 select count(*) from games 

вернёт количество строк в таблице 'games', а запрос

 select crpt_inf from games where id=374 

вернёт ключевой массив, которым зашифрована EP-секция исполнимого файла. Есть запросы для изменения данных в базе, примеры запросов посмотрите в коде утилиты 'nevootools'. Касательно зашифровки, поскольку используется 'XOR', используется та же функция, что и для расшифровки (см. выше код DecryptNevoDatabase).

Анатолий, 06/04/2012 15:10

Большое спасибо,Денис!

Дмитрий, 12/04/2012 06:01

Здравствуйте, что посоветуете на счёт новой защиты Nevosoft?

Денис Фатеев, 12/04/2012 13:33

Пока нет времени смотреть, что там придумали.

Людмила, 26/01/2013 18:18

Доброй ночи! Подскажите, пожалуйста, что делать?? При запуске игры выдает ошибку CANNOT OPEN DATABASE. base.db я не могу найти (у меня Windows 7). Что мне делать?

Денис Фатеев, 26/01/2013 18:38

Переустановить игру, если не поможет – то в техподдержку Nevosoft.

Анастасия, 07/03/2013 16:23

А если в службе поддержки не могут помочт с этой ошибкой что делать???

Анатолий, 19/04/2013 14:25

Здравствуйте Денис!Поставил сегодня Windows7 и 'nevotools' не распаковывает экзешник,а расшифровывает базу и выдает ошибку.Не подскажете в чем дело?

Денис Фатеев, 19/04/2013 14:45

Давно туда не смотрел, поэтому не знаю. Или прав на чтение/запись не хватает, или алгоритм изменили.

Анатолий, 21/04/2013 11:38

Спасибо.Будем разбираться.Он вообще не открывает файл,а срузу пытается расшифровать базу,хотя никто его об этом и не просит.

Анатолий, 21/04/2013 15:36

Извини,Денис.Совсем вылетело из головы,что в трейде нужно закрывать значок.В XP он навиду,а в «семерке» скрыт.



 
© 2009–2013 Денис Фатеев (Danger)
Копирование контента без указания автора преследуется сотрудниками ада.
Recent changes RSS feed
Valid XHTML 1.0
Valid CSS