eXTracted INternals

eXTracted INternals
 
ФорумФорум  ЧаВоЧаВо  ПоискПоиск  РегистрацияРегистрация  ПользователиПользователи  ГруппыГруппы  Вход  

Поделиться | 
 

 Снятие конверта Sentinel без ключа

Предыдущая тема Следующая тема Перейти вниз 
АвторСообщение
Hex

avatar

Количество сообщений : 397
Возраст : 35
Дата регистрации : 2006-07-12

СообщениеТема: Снятие конверта Sentinel без ключа   Ср 12 Июл - 13:39

Про снятие конвертов различных донглов информации очень мало. Единственная более менее суразная статья на тему конвертов Sentinel была от Cyberheg называлась "Breaking The Shell". Но честно говоря, прочев ее я мало что понял. Smile

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

И, вот решил через два года взяться за конверт с новыми силами Smile Исследовал программу Intermech Show 3.5 естественно без ключа. На ней стоит конверт Sentinel версии RBS32SP : ML-5.31a/5.38.6.7. Версию пакета можно увидеть если объявить переменную окружения RBSINFO, ну или порыться в коде запакованого файла.

Структура конверта Sentinel
Что же из себя представляет конверт Sentinel. Конверт сентинела состоит из интерпритатора и скрипта работы конверта. Этот интерпритатор имеет свой указатель следующей команды и набор "регистров" для хранения указателя на свой стэк, и параметры текущей псевдокоманды. Выполнен этот интерпритатор в виде цикла с ветвлениями. Вот его главная часть:

Код:

004C3845 Continue:                              ; CODE XREF: start+C2j
004C3845                                        ; start+DCj ...
004C3845                mov    eax, ds:NextCommandPtr
004C384A                mov    ecx, ds:NextCommandPtr
004C3850                mov    ax, [eax+SentCommand.Excode]
004C3853                mov    ds:CurrentExCode, ax
004C3859                mov    dx, [ecx+SentCommand.DataType]
004C385D                mov    word ptr ds:CurrentWordParam, dx
004C3864                mov    eax, [ecx+SentCommand.DwordParam]
004C3867                mov    ds:CurrentDwordParam, eax
004C386C                add    ds:NextCommandPtr, ebx
004C3872                xor    eax, eax
004C3874                mov    ax, ds:CurrentExCode
NextCommandPtr - "регистр" указателя команд. В начале в нем лежит адрес первой команды скрипта.
CurrentExCode - код текущей псевдокоманды
CurrentDwordParam и CurrentWordParam - аргументы текущей псевдо команды.

Псевдокоманда представляет собой структуру:
Код:

SentCommand    struc ; (sizeof=0x8)
Excode          dw ?                    ; enum SentinelCommands
DataType        dw ?                    ; enum DataTypes
DwordParam      dd ?                    ; offset
SentCommand    ends

ExCode - код команды.
DataType - у большинства команд этот word используется для хранения типа данных параметра DwordParam.
DwordParam - собственно аргумент команды.

Типы данных бывают следующими:
Код:

enum DataTypes
BytePtr          = 068h - DwordParam содержит указатель на байт
ArrayPtr        = 0D4h - DwordParam содержит указатель на массив
DwordPtr        = 234h - DwordParam содержит указатель на dword
WordPtr          = 28Eh - DwordParam содержит указатель на word
Dword            = 3D5h - DwordParam содержит dword

Самих псевдокоманд немного, всего лишь 25. Их коды (т.е. поле Excode):
Код:

enum SentinelCommands
CallProc6Param_  = 00E4h - Вызов функции по адресу DwordParam с 6-ю параметрами
PushResult_      = 0342h - Сохранить результат предидущей команды в стэк.
Jmp_If_NonZero_  = 0611h - Сравнить результат предидущей команды с нолем. Если не равно перейти на адрес DwordParam
Increment_      = 0704h - Увеличить на 1 переменную по адресу DwordParam
Halt_            = 096Fh - Выйти из программы с кодом ошибки в DwordParam
AddPrevResult_  = 1092h - Выполнить Add значением в DwordParam над результатом предидущей команды
CallProc2Param_  = 1109h - Вызов функции по адресу DwordParam с 2-мя параметрами
StoreResult_    = 1992h - Сохранить текущее значение в стэке в переменную по адресу DwordParam
CallProcNoParam_ = 1A44h - Вызов функции по адресу DwordParam без параметров
Return          = 2245h - Вернуться из подпрограммы
SubPrevResult_  = 22B7h - Выполнить Sub значением в DwordParam над результатом предидущей команды
AndPrevResult_  = 3345h - Выполнить And значением в DwordParam над результатом предидущей команды
Decrement_      = 3DC8h - Уменьшить на 1 переменную по адресу DwordParam
SetValue_        = 5211h - Записать результат предидущей команды в переменную по адресу DwordParam
GotoScriptAddr_  = 5910h - Безусловный переход на адрес DwordParam
ORPrevResult_    = 654Dh - Выполнить Or значением в DwordParam над результатом предидущей команды
Call_If_Zero_    = 65ABh - Сравнить результат предидущей команды с нолем. Если равно перейти в подпрограмму по адресу DwordParam
SetZero_        = 698Ah - Записать 0 в переменную по адресу DwordParam
Jmp_If_Zero_    = 7123h - Сравнить результат предидущей команды с нолем. Если равно перейти на адрес DwordParam
XorPrevResult_  = 74BBh - Выполнить Xor значением в DwordParam над результатом предидущей команды
Call_If_NonZero_ = 8109h - Сравнить результат предидущей команды с нолем. Если не равно перейти в подпрограмму по адресу DwordParam
ImulPrevResult_  = 9104h - Выполнить Imul значением в DwordParam над результатом предидущей команды
CallProc3Param_  = B323h - Вызов функции по адресу DwordParam с 3-мя параметрами
CallApi_        = BA51h - Вызов функции по адресу DwordParam с 2-мя параметрами
LoadParam_      = CD03h - Загрузить в стэк значение из DwordParam

После того как у нас есть все эти знания можем приступать к исселодваниям. Применяем структуру SentCommand и получаем читабельный код скрипта. Начало, благодаря тому что структуры в IDA 4.8 можно складывать, выглядит вот так:
Код:

Start          SentCommand <LoadParam_, Dword, offset SentinelPacket>
                SentCommand <CallApi_, 1, offset sproFindNextUnit>
                SentCommand <SubPrevResult_, Dword, 0>
                SentCommand <Jmp_If_NonZero_, WordPtr, offset Halt4>
                SentCommand <CallProcNoParam_, 0, offset ShowSentinelVersion>
                SentCommand <LoadParam_, Dword, offset FirstCryptedBlock>
                SentCommand <LoadParam_, Dword, offset CorrectDecryptionResult>
                SentCommand <SubPrevResult_, Dword, offset FirstCryptedBlock>
                SentCommand <LoadParam_, DwordPtr, offset DecryptionKey>
                SentCommand <CallProc3Param_, 0, offset DecryptProc>
                SentCommand <XorPrevResult_, DwordPtr, offset CorrectDecryptionResult>
                SentCommand <Jmp_If_NonZero_, DwordPtr, offset Halt1>
                SentCommand <LoadParam_, BytePtr, offset ByteToCheck>
                SentCommand <XorPrevResult_, Dword, 0FFh>
                SentCommand <Call_If_Zero_, 0, offset AndXorVar3>
                SentCommand <LoadParam_, Dword, offset SentinelPacket>
                SentCommand <LoadParam_, WordPtr, offset developerID>
                SentCommand <CallProc2Param_, 0, offset sproFindFirstUnit>
                SentCommand <SubPrevResult_, Dword, 0>
                SentCommand <Jmp_If_Zero_, WordPtr, offset KeyFound>
                SentCommand <GotoScriptAddr_, 0, offset Halt4>
Если присмотреться, то можно понять, что этот скрипт на самом деле делает следующее:

if (sproFindNextUnit(&SentinelPacket))
goto Halt4;

ShowSentinelVersion();

if (DecryptProc(FirstCryptedBlock, CorrectDecryptionResult - FirstCryptedBlock, &DecryptionKey) ^ *CorrectDecryptionResult) goto Halt1;

if (*ByteToCheck==0xFF)
goto AndXorVar3;

if (!sproFindFirstUnit(&SentinelPacket,&developerID))
goto KeyFound;

goto Halt4;

Проверяют наличие ключа, дальше выводят версию конверта, Потом расшифровывают блок настроек конверта, и проверяют его целостность, а дальше опять проверяют наличие ключа. Если что не так, то сразу выходят.

То есть все читабельно и понятно. Интерпритатор использует паскалевскую конвенцию для вызовов, то есть первый параметр загружается в конец стэка. К примеру тот же вызов sproFindFirstUnit делается вот так:
SentCommand <LoadParam_, Dword, offset SentinelPacket>
SentCommand <LoadParam_, WordPtr, offset developerID>
SentCommand <CallProc2Param_, 0, offset sproFindFirstUnit>

Хотя прототип у нее:
unsigned short int RNBOsproFindFirstUnit( RB_SPRO_APIPACKET packet, unsigned short int developerID);
Вернуться к началу Перейти вниз
Посмотреть профиль
Hex

avatar

Количество сообщений : 397
Возраст : 35
Дата регистрации : 2006-07-12

СообщениеТема: Re: Снятие конверта Sentinel без ключа   Ср 12 Июл - 13:39

Поиск OEP
Скрипт уже читабельный, но куда же по нем двигаться? Двигаться надо к OEP. Это понятно, но как его найти? Смотрим обработчик Ex-кода Halt:
Код:

004C3EC8 Halt:                                  ; CODE XREF: start+F0j
004C3EC8                xor    eax, eax
004C3ECA                mov    ax, word ptr ds:CurrentWordParam
004C3ED0                mov    ds:uExitCode, eax
004C3ED5                cmp    eax, 0Ah
004C3ED8                ja      short @Default
004C3EDA                xor    ecx, ecx
004C3EDC                mov    cl, ds:byte_4C3F60[eax]
004C3EE2                jmp    ds:off_4C3F4C[ecx*4]
004C3EE9 ; ---------------------------------------------------------------------------
004C3EE9
004C3EE9 @Default:                              ; CODE XREF: start+708j
004C3EE9                                        ; DATA XREF: start+78Co
004C3EE9                mov    ds:uExitCode, 2
004C3EF3
004C3EF3 @Code_Two:                              ; DATA XREF: start+784o
004C3EF3                and    ds:FirstCryptedBlock, 0FFFEh
004C3EFC
004C3EFC @All_Other_Codes:                      ; DATA XREF: start+780o
004C3EFC                                        ; start+788o
004C3EFC                push    2010h
004C3F01                call    GetErrorCode
004C3F06                push    eax
004C3F07                call    ShowErrorMsgBox
004C3F0C                add    esp, 8
004C3F0F                test    byte ptr ds:FirstCryptedBlock, 1
004C3F16                jnz    short loc_4C3F24
004C3F18                mov    eax, ds:uExitCode
004C3F1D                push    eax            ; uExitCode
004C3F1E                call    ds:__imp_ExitProcess
004C3F24
004C3F24 loc_4C3F24:                            ; CODE XREF: start+746j
004C3F24                xor    eax, eax
004C3F26                pop    ebp
004C3F27                pop    edi
004C3F28                pop    esi
004C3F29                pop    ebx
004C3F2A                retn    0Ch
004C3F2D ; ---------------------------------------------------------------------------
004C3F2D
004C3F2D @Code_Zero:                            ; CODE XREF: start+Cj
004C3F2D                                        ; DATA XREF: start:off_4C3F4Co
004C3F2D                mov    eax, [esp+4+arg_8]
004C3F31                mov    ecx, [esp+4+arg_4]
004C3F35                mov    edx, [esp+4+arg_0]
004C3F39                push    eax
004C3F3A                push    ecx
004C3F3B                push    edx
004C3F3C                call    ds:CurrentHaltProc
004C3F42                pop    ebp
004C3F43                pop    edi
004C3F44                pop    esi
004C3F45                pop    ebx
004C3F46                retn    0Ch
004C3F46 ; ---------------------------------------------------------------------------
004C3F49                db 8Dh, 49h, 0
004C3F4C off_4C3F4C      dd offset @Code_Zero    ; DATA XREF: start+712r
004C3F50                dd offset @All_Other_Codes
004C3F54                dd offset @Code_Two
004C3F58                dd offset @All_Other_Codes
004C3F5C                dd offset @Default
004C3F60 byte_4C3F60    db 0, 1, 2, 3, 3        ; 0 ; DATA XREF: start+70Cr
004C3F60                db 3, 3, 3, 3, 3        ; 5
004C3F60                db 3                    ; 10
Как видно из switch по адресу 4C3EE2 все коды выхода, кроме кода 0, приводят к ExitProcess. Код 0 ведет к вызову какой-то функции с тремя параметрами... А не main() ли это? Смотрим что у нас там по адресу CurrentHaltProc... там у нас адрес какой-то функции с кодом:
Код:

004C3FC0 sub_4C3FC0      proc near              ; DATA XREF: _0000009:CurrentHaltProco
004C3FC0                mov    eax, 1
004C3FC5                retn    0Ch
004C3FC5 sub_4C3FC0      endp
Какая-то странная заглушка... А ну-ка пройдемся по Xref'ам на CurrentHaltProc. Есть еще xref из скрипта:
Код:

004B91A0 stru_0_4B91A0  SentCommand <LoadParam_, DwordPtr, offset dword_0_4B7120>
004B91A0                                        ; DATA XREF: _0000009:004B91D8o
004B91A0                                        ; _0000009:004B9200o ...
004B91A8                SentCommand <Jmp_If_Zero_, DwordPtr, offset Halt0>
004B91B0                SentCommand <LoadParam_, DwordPtr, offset dword_0_4B8B6C>
004B91B8                SentCommand <AddPrevResult_, DwordPtr, offset dword_0_4B7120>
004B91C0                SentCommand <StoreResult_, DwordPtr, offset CurrentHaltProc>
004B91C8                SentCommand <Halt_, 0, 0>
Вычисляют какое-то число и перезаписывают им адрес функции в CurrentHaltProc... По адрес dword_0_4B7120 находится в первом закриптованом блоке, а по адресу 4B8B6C пока что ноль. Запустим программу и посмотрим, что там будет записано после того как будет раскриптован блок настроек... Ага! По адресу 4B8B6C лежит ImageBase. А по адресу 4B7120 записано 8C7AC. Итого адрес функции = 48C7AC. То есть скрипт вычисляет адрес функции, на которую идет выход из интерпритатора, то есть это OEP.

Что у нас там по адресу OEP? Все зашифровано Sad Надо расшифровывать.

Расшифровка секций
Код перед переходом к OEP должен обязательно расшифровываться. Чем и как он расшифровывается?
Ищем Xref'ы на DecryptProc. Их только два: первый в начале, а второй вот.
Код:

DecryptOtherBlocks:
SentCommand <LoadParam_, DwordPtr, offset response>
SentCommand <SubPrevResult_, DwordPtr, offset LoopCount>
SentCommand <Jmp_If_Zero_, WordPtr, offset MakeRelocsAndImport>
SentCommand <CallProcNoParam_, 0, offset LoadSectionPointers>
SentCommand <Jmp_If_NonZero_, DwordPtr, offset Halt3>
SentCommand <LoadParam_, Dword, offset SentinelPacket> ; packet
SentCommand <LoadParam_, BytePtr, offset byte_0_4B700D> ; address
SentCommand <LoadParam_, Dword, offset CodeSectionQuery> ; queryData
SentCommand <LoadParam_, Dword, offset HashKey> ; response
SentCommand <LoadParam_, Dword, 0> ; response32
SentCommand <LoadParam_, Dword, 4> ; size
SentCommand <CallProc6Param_, 0, offset sproQuery>
SentCommand <SubPrevResult_, Dword, 0>
SentCommand <Jmp_If_NonZero_, WordPtr, offset Halt4>
SentCommand <LoadParam_, DwordPtr, offset pCryptedData> ; pCryptedData
SentCommand <LoadParam_, DwordPtr, offset queryData> ; DataSize
SentCommand <LoadParam_, DwordPtr, offset HashKey>
SentCommand <CallProc3Param_, 0, offset DecryptProc>
SentCommand <XorPrevResult_, DwordPtr, offset CodeCorrectCrc>
SentCommand <Jmp_If_NonZero_, DwordPtr, offset Halt1>
SentCommand <Increment_, DwordPtr, offset response>
SentCommand <GotoScriptAddr_, 0, offset DecryptOtherBlocks>

Вот такой скрипт. Переменная response - счетчик цикла. Функция LoadSectionPointers загружает в переменные pCryptedData и queryData параметры расшифровываемой секции. Далее делается sproQuery для получения ключа расшифровки текущей секции и расшифровывается секция. Когда все секции расшифрованы делается переход в функцию MakeRelocsAndImport.
Теперь самое интересное, функция расшифровки секций:
Код:

004C3F70 ; int __stdcall DecryptProc(int pCryptedData,int DataSize,int Hash)
004C3F70 DecryptProc    proc near             
004C3F70 pCryptedData    = dword ptr  8
004C3F70 DataSize        = dword ptr  0Ch
004C3F70 Hash            = dword ptr  10h
004C3F70
004C3F70                push    ebx
004C3F71                xor    eax, eax
004C3F73                push    esi
004C3F74                push    edi
004C3F75                mov    esi, [esp+8+DataSize]
004C3F79                shr    esi, 2
004C3F7C                mov    ecx, esi
004C3F7E                dec    esi
004C3F7F                test    ecx, ecx
004C3F81                jz      short loc_0_4C3FB4
004C3F83                mov    edx, [esp+8+pCryptedData]
004C3F87                mov    edi, [esp+8+Hash]
004C3F8B @loop:                           
004C3F8B                mov    ecx, edi
004C3F8D                mov    ebx, edi
004C3F8F                shl    ecx, 4
004C3F92                add    edx, 4
004C3F95                shl    ebx, 5
004C3F98                add    ecx, edi
004C3F9A                shr    ecx, 9
004C3F9D                xor    ecx, ebx
004C3F9F                add    edi, ecx
004C3FA1                mov    ecx, [edx-4]
004C3FA4                xor    ecx, edi
004C3FA6                add    eax, ecx
004C3FA8                mov    [edx-4], ecx
004C3FAB                xor    edi, eax
004C3FAD                mov    ecx, esi
004C3FAF                dec    esi
004C3FB0                test    ecx, ecx
004C3FB2                jnz    short @loop
004C3FB4 loc_0_4C3FB4:
004C3FB4                pop    edi
004C3FB5                pop    esi
004C3FB6                pop    ebx
004C3FB7                retn    0Ch
004C3FB7 DecryptProc    endp
То есть вся защита сводится к вызову этой функции с DWORD значением ключа полученым из донгла. Все, что нужно сделать для снятия конверта - это просто пробрутфорсить FFFFFFFF значений. И единственное, что нам нужно для брутфорса - это знать значение какого-нить DWORD'а по какому-то адресу. У программ на delphi для секции кода таким DWORD может быть стандартное начало секции кода:
Код:

04104000 0307426F 6F6C6561 6E010000 ..@...Boolean...

Ее благополучно отреверсил cyberheg и даже привел код, который раскриптовывает секцию по известному ключу. Ну а вот брутфорс которым ищется ключ:
Код:

unsigned long BruteForce(unsigned long Crypted, unsigned long CorrectValue)
{
   unsigned long tResponse;
   unsigned long counter=0;
   unsigned long UnCrypted;
   do
   {
      tResponse=counter;
      tResponse += (((tResponse << 4) + tResponse) >> 9) ^ (tResponse << 5);
      UnCrypted = Crypted ^ tResponse;

      if (UnCrypted==CorrectValue)
      {
         return counter;
      }

      counter++;

   } while (counter < 0xFFFFFFFF);

   return 0;
}
Crypted - зашифрованый dword, CorrectValue - расшифрованый. Код процедуры расшифровки примитивный, поэтому брутфорсится за 30 секунд.
Ну дальше уже вы и так догадываетесь что делать. Можете ради интереса пройти весь скрипт от начала до конца, подменяя в нужный момент значения ответов Smile
Вернуться к началу Перейти вниз
Посмотреть профиль
 
Снятие конверта Sentinel без ключа
Предыдущая тема Следующая тема Вернуться к началу 
Страница 1 из 1

Права доступа к этому форуму:Вы не можете отвечать на сообщения
eXTracted INternals :: Cтатьи :: Win32 reversing-
Перейти: