Оригинальный DVD-ROM: eXeL@B DVD !
eXeL@B ВИДЕОКУРС !

Видеокурс программиста и крэкера 5D 2O17
(актуальность: декабрь 2O17)
Свежие инструменты, новые видеоуроки!

  • 400+ видеоуроков
  • 800 инструментов
  • 100+ свежих книг и статей

УЗНАТЬ БОЛЬШЕ >>
Домой | Статьи | RAR-cтатьи | FAQ | Форум | Скачать | Видеокурс
Новичку | Ссылки | Программирование | Интервью | Архив | Связь

написание кейгена для Psychometric Expert 7

Обсудить статью на форуме

Хорошая подборка видеоуроков, инструментов крэкера, книг и статей - здесь.

Автор: flyninja <rudolfzinger@mail.ru>

Цель: Psychometric Expert 7 (надо сказать,что защитный механизм в 5 версии абсолютно
идентичен, так что все описанное справедливо и для нее);

Инструменты: отдадчик OllyDebugger v.1.1, дизассемблер IDA Pro v.5.0, утилита PEiD,
упаковщик/распаковшик UPX v.3

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

Прежде всего, загрузив подопытного в PEiD, убеждаемся, что прога упакована UPX'ом.
Для комфортной работы под дизассемблером, лучше будет ее распаковать, благо это несложно
- можно скачать распаковщик с сайта проекта, а можно немного порыться в папках дистрибутива
самого PSY и обнаружить готовый к употреблению распаковщик в фолдере Compile
(спасибо разработчикам).
Поскольку мы не собираемся патчить исполняемый файл подопытного, его лучше скопировать для
дизассемблерного анализа в другое место и распаковать там (upx -d PSY.exe). Повторно вопросив PEiD по поводу соображений насчет известного персонажа, узнаем, что он написан на Delfi 6/7 (скорее всего 7), что исключает анализ с помощью DeDe, зато открывает оперативный простор для использования дизассемблера, а для удобства и ускорения работы, добавим к нему отладчик.

Использование именно IDA не принципиально, но предпочтительно, хотя я и не использовал
механизм FLIRT для анализа в полном объеме (в смысле ограничился только сигнатурами Borland
C++ Compiler, сигнатуры Delfi 7 было лень искать).
Я не слишком большой специалист по Delfi, но интуиция подсказала, что окно регистрации
выводится какой-либо ООП-процедурой из разряда CreateWindow(Form/Dialog...).

По методу "тупого перебора" перебираем функии в дизассемблере с похожими названиями и
ставим точки останова в отладчике по их адресам. Только прежде, чем этим заниматься, нужно
посмотреть адрес точки входа распакованной программы в дизассемблере (00638с98) и установить
на нее аппаратную точку останова в отладчике, а эксперименты с "тоглами" проводить уже в
распакованной программе. Буквально через 30-40 минут подобных манипуляций мы получим
срабатывание програмной ВР по адресу 004A39E4, который соответствует Forms::TApplication:
:CreateForm. Подсмотрев адрес разврата, мы получим функцию основного модуля, которая
показывает нам регистрационную форму - sub_638488, для удобства обзовем ее ViewRegistrationForm.
По перекрестной ссылке видно, что вызывается эта функция только из стартовой процедуры,
причем вызову предшествует проверка равенства некоторой глобальной переменной (назовем ее
RegistrationFlag) нулю:


CODE:0063921A                 cmp     ds:RegistrationFlag, 0
CODE:00639221                 jnz     short loc_63922D
CODE:00639223                 call    ViewRegistrationForm


инвертировав (из любопытства) флаг нуля перед переходом в отладчике, мы увидим главное
окно программы, вместо скучной формы регистрации, что укрепит нас в мысли правильности
выбранного пути, но понажимав на кнопки и пункты меню Психа познакомимся с птицей обломинго,
которая радостно сообщит, что писать генератор регкода все-таки придется, видимо проверка
подлинности выполняется по всему телу программы, что исключает (или сильно затрудняет) кряк
бит-хаком.
Анализируем алгоритм преобразования рег кода при проверке:


---------------------------------------------------------------------------------------------------------------------
CODE:00638DAD                 push    offset aMd33333a ; "MD33333A"
CODE:00638DB2                 lea     eax, [ebp+lpNameLocal]
CODE:00638DB5                 push    eax
CODE:00638DB6                 mov     ecx, offset asc_63953C ; "Name"
CODE:00638DBB                 mov     edx, offset lpTempBuff
CODE:00638DC0                 mov     eax, ebx
CODE:00638DC2                 mov     esi, [eax]
CODE:00638DC4                 call    dword ptr [esi]							этот блок кода
CODE:00638DC6                 mov     edx, [ebp+lpNameLocal]
CODE:00638DC9                 mov     eax, ds:lpNameGlobal
CODE:00638DCE                 call    System::__linkproc__ LStrAsg(void *,void *) распихивает сер номер
CODE:00638DD3                 push    offset a123456  ; "123456"
CODE:00638DD8                 lea     eax, [ebp+lpCodeLocal]					комп код и рег код по
CODE:00638DDB                 push    eax
CODE:00638DDC                 mov     ecx, offset Svrconst::_16422 ; "Code"		глобальным и локальным 
CODE:00638DE1                 mov     edx, offset lpTempBuff
CODE:00638DE6                 mov     eax, ebx									буфферам
CODE:00638DE8                 mov     esi, [eax]
CODE:00638DEA                 call    dword ptr [esi]
CODE:00638DEC                 mov     edx, [ebp+lpCodeLocal]
CODE:00638DEF                 mov     eax, ds:lpCodeGlobal
CODE:00638DF4                 call    System::__linkproc__ LStrAsg(void *,void *)
CODE:00638DF9                 push    offset a264832123910 ; "264832-1239-10"
CODE:00638DFE                 lea     eax, [ebp+lpACodeLocal]
CODE:00638E01                 push    eax
CODE:00638E02                 mov     ecx, offset aAcode_4 ; "ACode"
CODE:00638E07                 mov     edx, offset lpTempBuff
CODE:00638E0C                 mov     eax, ebx
CODE:00638E0E                 mov     esi, [eax]
CODE:00638E10                 call    dword ptr [esi]
CODE:00638E12                 mov     edx, [ebp+lpACodeLocal]
CODE:00638E15                 mov     eax, ds:lpACodeGlobal
CODE:00638E1A                 call    System::__linkproc__ LStrAsg(void *,void *)
-----------------------------------------------------------------------------------------------------------------

Проследив под отладчиком значение ESI в момент вызовов, узнаем, что эти строки получает функция
по адресу 00447D1C при помощи системного вызова GetPrivateProfileStringA из файла Info.ini в главной
папке программы, а строки, передаваемые при вызове (вроде 264832-1239-10) - это соответственно
значения по дефолту, что можно выяснить в MSDN.

Интересующая нас часть Info.ini выглядит так:


[PEx]
InstallDir=C:Program FilesPsychometric Expert
PathDll=C:Program FilesPsychometric ExpertSysmodMetodParams
DefaultBase=C:Program FilesPsychometric ExpertDDataDefaultData.pfd
StartupWindow=1
RName=$єжќсЊЄfш%
RData=26330
RTime=63125
Name=MD14545F
Code=C3831715
ACode=C38317-151A-555974

Идем дальше (коментарии и имена меток мои):

------------------------------------------------------------------------------------------------------------------
CODE:00639078                 mov     ebx, eax
CODE:0063907A                 mov     eax, ds:lpCodeGlobal
CODE:0063907F                 mov     eax, [eax]
CODE:00639081                 call    System::__linkproc__ DynArrayLength(void)	этот блок кода проверяет длины
CODE:00639086                 imul    ebx, eax								строк комп/рег кодов и сер номера
CODE:00639089                 mov     eax, ds:lpACodeGlobal
CODE:0063908E                 mov     eax, [eax]
CODE:00639090                 call    System::__linkproc__ DynArrayLength(void)
CODE:00639095                 imul    ebx, eax
CODE:00639098                 test    ebx, ebx
CODE:0063909A                 jle     CheckRegistration
CODE:006390A0                 mov     eax, ds:lpACodeGlobal
CODE:006390A5                 mov     eax, [eax]
CODE:006390A7                 call    System::__linkproc__ DynArrayLength(void)
CODE:006390AC                 cmp     eax, 0Ah
CODE:006390AF                 jle     CheckRegistration
CODE:006390B5                 mov     eax, ds:lpNameGlobal
CODE:006390BA                 mov     eax, [eax]
CODE:006390BC                 cmp     byte ptr [eax], 4Dh
CODE:006390BF                 jnz     short loc_6390E1
---------------------------------------------------------------------------------------------------------------

Как видно критерии к строкам следующие: во-первых ненулевые, во-вторых длина рег кода не менее
10 символов, в третьих,проверяется значение первого символа серийника(если он не "М", то, как будет
видно далеее, меняется алгоритм получения конфигурации компьютера).
Этот блок кода получает ID процессора при помощи инструкции CPUID и складывает результат с константой.

CODE:006390C1                 call    GetRawCompCode
CODE:006390C6                 add     eax, 3976Bh


Поскольку внутри себя процедура содержит большое количество незначимого кода, вот мой вариант этой
функции (она же первая функция в проекте генерации рег кода):

INT GetRawCode(VOID)
{
int res;
_asm {
	mov eax,1;
	cpuid;
	mov res,eax;
	mov eax,3;
	cpuid;
	add res,edx;
	add res,0x3976b;
}
return res;
}


Готовый результат приведен мной исключительно из экономии места, анализ самой функции не представляет проблемы даже для новичка, тем более, что защитный код уже локализован и анализировать нужно немного.
В случае же равенства первого кода серийника символу "М", RawCompCode будет равен параметру
lpVolumeSerialNumber,возвращаемому GetVolumeInformationA. Воспроизведение этого, думаю, также не
встретит каких-либо серьезных препятствий.

Далее, для строк с рег кодом и серийником вызывается функция, которую я назвал ChangeSymbols, что
понятно из ее алгоритмаю
Вот ее вызов:

CODE:0063911B                 mov     edx, ds:lpACodeGlobal
CODE:00639121                 mov     eax, ds:lpNameGlobal
CODE:00639126                 mov     eax, [eax]
CODE:00639128                 call    ChangeSymbols
а вот часть ее кода:
CODE:0052C734                 mov     edx, [ebx]
CODE:0052C736                 mov     dl, [edx]
CODE:0052C738                 mov     [eax], dl
CODE:0052C73A                 mov     eax, ebx
CODE:0052C73C                 call    System::_16779
CODE:0052C741                 mov     edx, [ebx]
CODE:0052C743                 mov     dl, [edx+4]
CODE:0052C746                 mov     [eax], dl
CODE:0052C748                 mov     eax, ebx
CODE:0052C74A                 call    System::_16779
CODE:0052C74F                 mov     edx, [ebp+var_8]
CODE:0052C752                 mov     dl, [edx]
CODE:0052C754                 mov     [eax+4], dl
CODE:0052C757                 lea     eax, [ebp+var_8]
CODE:0052C75A                 call    System::_16779

не нужно быть ученым, чтобы догнать, что этот блок кода меняет местами символы 0 и 4 в какой-то строке (в данном случае в строке рег кода).
Полная реконструкция алгоритма для рег кода выглядит так:

VOID ChangheRegCodeSymb(LPSTR lpszBuff)
{
	char t;
		t=lpszBuff[0];
	     lpszBuff[0]=lpszBuff[4];
		 lpszBuff[4]=t;
		 t=lpszBuff[1];
		 lpszBuff[1]=lpszBuff[3];
		 lpszBuff[3]=t;
}

С заменой символов в серийнике все не так очевидно, но тоже не слишком усложнено:

CODE:0052C784                 mov     eax, [ebp+var_4]
CODE:0052C787                 mov     al, [eax+7]
CODE:0052C78A                 cmp     al, 41h
CODE:0052C78C                 jnz     short loc_52C7ED
.........................................................
CODE:0052C7ED                 cmp     al, 42h
CODE:0052C7EF                 jnz     short loc_52C850
.........................................................
.........................................................
CODE:0052C910 loc_52C910:                             ; CODE XREF: ChangeSymbols+1BAj
CODE:0052C910                 cmp     al, 45h
CODE:0052C912                 jz      short loc_52C916
CODE:0052C914                 cmp     al, 46h
CODE:0052C916
CODE:0052C916 loc_52C916:                     

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

VOID ChangeCompCodeSym(LPSTR lpszStr)
{
char a=lpszStr[7];
char t;
switch(a)
{
case 0x41: t=lpszStr[7];
		  lpszStr[7]=lpszStr[2];
		  lpszStr[2]=t;
		  t=lpszStr[8];
		  lpszStr[8]=lpszStr[10];
		  lpszStr[10]=t;
		  break;
case 0x42: t=lpszStr[2];
		  lpszStr[2]=lpszStr[9];
		  lpszStr[9]=t;
		  t=lpszStr[5];
		  lpszStr[5]=lpszStr[7];
		  lpszStr[7]=t;
		  break;
case 0x43: t=lpszStr[5];
			lpszStr[5]=lpszStr[2];
			lpszStr[2]=t;
		  t=lpszStr[7];
		  lpszStr[7]=lpszStr[10];
		  lpszStr[10]=lpszStr[7];
		  break;
case 0x44: t=lpszStr[7];
		   lpszStr[7]=lpszStr[10];
		   lpszStr[10]=t;
		   t=lpszStr[5];
		   lpszStr[5]=lpszStr[9];
		   lpszStr[9]=t;
		   break;
default: break;
}
}

В моем случае, кстати работает только дефолтовый вариант, что, забегая вперед, обусловлено комплектацией поставки проги.У них их, видите ли, аж пять вариантов поставки (как у Ford Mondeo), алгоритм смены символов в рег коде, кстати, также зависит от комплектации.

Следующий блок кода манипулирует уже непосредственно с рег кодом:

CODE:00639132                 mov     eax, ds:lpACodeGlobal
CODE:00639137                 mov     eax, [eax]
CODE:00639139                 mov     ecx, 2
CODE:0063913E                 mov     edx, 0Ah
CODE:00639143                 call    System::__linkproc__ LStrCopy(void)
CODE:00639148                 mov     eax, ds:lpACodeGlobal
CODE:0063914D                 push    eax
CODE:0063914E                 mov     eax, ds:lpACodeGlobal
CODE:00639153                 mov     eax, [eax]
CODE:00639155                 mov     ecx, 9
CODE:0063915A                 mov     edx, 1
CODE:0063915F                 call    System::__linkproc__ LStrCopy(void)
CODE:00639164                 mov     eax, ds:lpACodeGlobal
CODE:00639169                 mov     ecx, 1
CODE:0063916E                 mov     edx, 7
CODE:00639173                 call    System::__linkproc__ LStrDelete(void)

а именно - вырезает из него первые 9 символов и удаляет из них седьмой (видимо на этом месте ожидается
незначимый символ скорее всего дефис). Таким образом, предварительный формат рег кода выглядит так:

хххххх-хх......

где - символы первого поля рег кода, при выделении поля Х, символы в нем
меняются в зависимости от комплектации программы.

Далее мы видим следующую функцию (я назвал ее SerialNumHash), основная часть кода которой выглядит так:

CODE:0063833C repeat:                                 ; CODE XREF: SerialNumHash+AAj
CODE:0063833C                 lea     eax, [ebp+lpMulBuff]
CODE:0063833F                 mov     edx, offset dword_6383D8
CODE:00638344                 call    System::__linkproc__ LStrLAsg(void *,void *)
CODE:00638349                 mov     ebx, esi
CODE:0063834B                 test    ebx, ebx
CODE:0063834D                 jle     short mul
CODE:0063834F
CODE:0063834F loc_63834F:                             ; CODE XREF: SerialNumHash+7Dj
CODE:0063834F                 lea     eax, [ebp+lpMulBuff]
CODE:00638352                 mov     edx, offset dword_6383E4
CODE:00638357                 call    System::__linkproc__ LStrCat(void)
CODE:0063835C                 dec     ebx
CODE:0063835D                 jnz     short loc_63834F
CODE:0063835F
CODE:0063835F mul:                                    ; CODE XREF: SerialNumHash+6Dj
CODE:0063835F                 mov     eax, [ebp+lpMulBuff]
CODE:00638362                 call    Sysutils::StrToInt64(System::AnsiString)
CODE:00638367                 push    edx
CODE:00638368                 push    eax
CODE:00638369                 mov     eax, [ebp+lpSerialNumLocal]
CODE:0063836C                 mov     al, [eax+esi-1]
CODE:00638370                 and     eax, 0FFh
CODE:00638375                 xor     edx, edx
CODE:00638377                 call    System::__linkproc__ _llmul(void)
CODE:0063837C                 add     eax, [ebp+MinorPartRes]
CODE:0063837F                 adc     edx, [ebp+MajorPartRes]
CODE:00638382                 mov     [ebp+MinorPartRes], eax
CODE:00638385                 mov     [ebp+MajorPartRes], edx
CODE:00638388                 inc     esi
CODE:00638389                 dec     edi
CODE:0063838A                 jnz     short repeat


что в переводе на С выглядит так:


INT GetCompCodeHash(LPSTR lpszStr)
{
INT64 temp=1;
INT64 res=0;
INT64 work=0;
int result, t;

CHAR c[4]={0,0,0,0};
LPSTR ch=(LPSTR)c;
int count=strlen(lpszStr);
if(count>0x11)count=0x11;
for(int i=0;i0xffffffff){
	result=(int)(res-0xf4c11db7);
}
else{
	result=(int)res;
}
return result;

}


Хэшем все это названо, разумеется, для краткости. Алгоритм полностью обратим.

Ну и, наконец, то, для чего это все делалось:

CODE:00639178                 mov     eax, ds:lpNameGlobal
CODE:0063917D                 mov     eax, [eax]
CODE:0063917F                 call    SerialNumHash
CODE:00639184                 mov     ebx, eax
........................
CODE:00639194                 mov     ecx, ds:lpACodeGlobal
CODE:0063919A                 mov     ecx, [ecx]
CODE:0063919C                 lea     eax, [ebp+iRegCode]
CODE:0063919F                 mov     edx, offset asc_639714 ; "$"
CODE:006391A4                 call    System::__linkproc__ LStrCat3(void)
CODE:006391A9                 mov     eax, [ebp+iRegCode]
CODE:006391AC                 call    Sysutils::StrToInt(System::AnsiString)
CODE:006391B1                 xor     eax, ds:RawCompCode
CODE:006391B7                 xor     ebx, eax


т.е. банально:

res=iRegCode^RawCode^CompCodehash;


Далее все это элементрано складывается потетрадно (ну разумеется не без дельфийских наворотов со
строково-целочисленными преобразованиями):

CODE:006391CB                 lea     ecx, [ebp+lpHexXorString]
CODE:006391CE                 mov     edx, 8
CODE:006391D3                 mov     eax, ebx
CODE:006391D5                 call    Sysutils::IntToHex(int,int)
CODE:006391DA                 mov     eax, [ebp+lpHexXorString]
CODE:006391DD                 call    CheckSummCalc


для краткости, функция CheckSummCalc выглядит так:


int t=iRegCode^RawCode^CompCodehash;
int sum=0;

	for(int i=0;i<8;i++)
	{
		sum=sum+((t>>(i*4))&0xf);
	}

А вот тут из результата и получается строка, которая и сравнивается с эталонной, взятой из рег кода,
что приводит к "взводу" флага регистрации:

CODE:006391E2                 lea     ecx, [ebp+lpCalcChekStr]
CODE:006391E5                 mov     edx, 2
CODE:006391EA                 call    Sysutils::IntToHex(int,int)
CODE:006391EF                 mov     eax, [ebp+lpCalcChekStr]
CODE:006391F2                 mov     edx, ds:lpEtalonCheckStr
CODE:006391F8                 call    System::__linkproc__ LStrCmp(void)
CODE:006391FD                 jnz     short RestoreSEH
CODE:006391FF                 mov     ds:RegistrationFlag, 1

Таким образом, проследив, откуда берется эталонная строка:
CODE:0063912D                 push    offset lpEtalonCheckStr
CODE:00639132                 mov     eax, ds:lpACodeGlobal
CODE:00639137                 mov     eax, [eax]
CODE:00639139                 mov     ecx, 2
CODE:0063913E                 mov     edx, 0Ah
CODE:00639143                 call    System::__linkproc__ LStrCopy(void)

получаем следующий формат рег кода:

хххххх-ххуу-.....

где х-поле, случайное 16-ричное число в строковом представлении из которого вычисляется
у-поле (двухзначное 16-ричное число в строковом представлении). Алгоритм его генерации вполне очевиден (достаточно использовать функции, описанные выше, при вычислении поля у).
Сгенерировав рег код по описанной схеме, подставив вместо точек пару случайных символов (например10), мы, скормив все это рег форме Психа, запустим его. Он будет прекрасно пахать, откликаясь на все наши нажатия кнопок и пунктов меню, предоставляя полный доступ к своему основному функционалу, но закрыв его и перезагрузив, мы снова увидим наглое окно с требованием регистрации. Вот незадача, хорошо, что еще заказчику не отправлял (был бы скандал).
Заглянув в файл Info.ini мы вместо нашего почти честно сгенерированного рег кода с удивлением обнаружим дефолтовое значение,которое, разумеется, ничего не регистрирует.
Все ясно, мы не полностью сгенерировали номер, в каком-то другом месте проверяется остальная часть, которая при обнаружении хака снимает регистрацию. Как можно найти место проверки? Элементарно. Обычным поиском инструкции CPUID
убеждаемся, что нужна она только знакомой нам функции GetRawCode, по перекрестным ссылкам к которой мы увидем еще шесть процедур, из которых она вызывается. Проанализируем одну из них по адресу 0052AD84 (назовем ее Checkregistration1).

CODE:0052AF1B                 call    GetRawCompCode
CODE:0052AF20                 mov     edi, eax
CODE:0052AF22                 add     edi, 3976Bh
CODE:0052AF28                 mov     eax, edi
CODE:0052AF2A                 call    GetRawCompCode_2
.............................................
CODE:0052AB4C GetRawCompCode_2 proc near              ; CODE XREF: CheckRegistration1+1A6p
CODE:0052AB4C                                         ; sub_5EBF64+21Dp ...
CODE:0052AB4C                 cmp     eax, 0FD1Dh
CODE:0052AB51                 jb      short locret_52AB5F
CODE:0052AB53
CODE:0052AB53 loc_52AB53:                             ; CODE XREF: GetRawCompCode_2+11j
CODE:0052AB53                 sub     eax, 4381h
CODE:0052AB58                 cmp     eax, 0FD1Dh
CODE:0052AB5D                 jnb     short loc_52AB53
CODE:0052AB5F
CODE:0052AB5F locret_52AB5F:                          ; CODE XREF: GetRawCompCode_2+5j
CODE:0052AB5F                 retn
CODE:0052AB5F GetRawCompCode_2 endp

Видим, что с нашим RawCode'oм проводятся манипуляции, приводящие его к двухбайтному виду путем вычитания константы.
А после этого, в следующей функции, результат изпользуется для декодирования некоторой строки(метки мои):

CODE:0052AF2F                 add     ax, word ptr [ebp+intRSize]
CODE:0052AF33                 mov     edi, eax
CODE:0052AF35                 mov     ax, [ebp+intRTime]
CODE:0052AF39                 push    eax
CODE:0052AF3A                 lea     eax, [ebp+lpDecriptTestStr]
CODE:0052AF3D                 push    eax
CODE:0052AF3E                 mov     ecx, edi        ; sun iRSize+RCC2
CODE:0052AF40                 mov     dx, [ebp+intRData]
CODE:0052AF44                 mov     eax, [ebp+lpRName]
CODE:0052AF47                 call    ShellToDecriptProc
CODE:0052AF4C                 mov     edx, [ebp+lpDecriptTestStr]
CODE:0052AF4F                 mov     eax, ebx
CODE:0052AF51                 call    System::__linkproc__ LStrAsg(void *,void *)
CODE:0052AF56                 push    30FBh
CODE:0052AF5B                 lea     eax, [ebp+lpEtalonDecriptStr]
CODE:0052AF5E                 push    eax
CODE:0052AF5F                 mov     cx, 0B140h
CODE:0052AF63                 mov     dx, 5557h
CODE:0052AF67                 mov     eax, offset aEtalonCript ; "|5db2/9((¦("
CODE:0052AF6C                 call    ShellToDecriptProc
..........................................................
CODE:0052ACBE decript:                                ; CODE XREF: DecriptString+42j
CODE:0052ACBE                 movzx   ecx, [ebp+intRTime]
CODE:0052ACC2                 shr     ecx, 8          ; major byte intRTime
CODE:0052ACC5                 xor     cl, [esi]       ; major byte intRTime=mbiRTime^RName[i]
CODE:0052ACC7                 mov     [eax], cl       ; dect[i]=mbiRTime
CODE:0052ACC9                 add     cl, byte ptr [ebp+intRTime] ; mbiRTime=mbiRTime+minor byte iRTime
CODE:0052ACCC                 and     ecx, 0FFh       ; (int)mbiRTime&0xff
CODE:0052ACD2                 imul    cx, word ptr [ebp+intRData] ; mbiRTime=(mbiRTime*iRData)&0xffff
CODE:0052ACD7                 add     cx, word ptr [ebp+intRData+2] ; mbiRTime=(mbiRTime+SumRSRCC2)&0xffff
CODE:0052ACDB                 mov     [ebp+intRTime], cx ; iRTime=mbiRTime
CODE:0052ACDF                 inc     esi
CODE:0052ACE0                 inc     eax
CODE:0052ACE1                 dec     edx
CODE:0052ACE2                 jnz     short decript


Как видно, ShellToDecriptProc вызывается дважды, один раз со случайными параметрами, второй раз с
эталонными, заглянув в эталонный вызов отладчиком, увидим результат дешифровки: строка License.txt,
которая далее по коду конкуется к полному пути в главный каталог, после чего проверяется существование полученного файла:

CODE:0052AF71                 push    [ebp+lpMainPathStr]
CODE:0052AF74                 push    offset aSlash   ; "\"
CODE:0052AF79                 push    dword ptr [ebx]
CODE:0052AF7B                 lea     eax, [ebp+lpTestLicenseName]
CODE:0052AF7E                 mov     edx, 3
CODE:0052AF83                 call    System::__linkproc__ LStrCatN(void)
CODE:0052AF88                 mov     eax, [ebp+lpTestLicenseName]
CODE:0052AF8B                 call    Sysutils::FileExists(System::AnsiString)
CODE:0052AF90                 test    al, al
CODE:0052AF92                 jz      short NotRegistration

Видимо, битом хакать этот переход тоже не стоит - гда гарантии, что таким же способом в других местах программы не расшифровывается какое-нибудь имя нужной библиотеки?
Лучше посмотрим, откуда берутся случайные параметры. RTime, RData и RName мы без труда обнаружем в том же Info.ini. А вот RSize, похоже является экспортируемой глобальной переменной класса регистрации, которая расчитывается при инициализации в конструкторе класса, поэтому ее придется поискать, благо разработчик хорошенько наследил. По перекрестной ссылке, находим откуда вызывается CheckRegistrarion1:

CODE:0062168F                 mov     edx, [ebx]
CODE:00621691                 mov     eax, ds:dword_640FC4
CODE:00621696                 call    InitRSize
CODE:0062169B                 lea     edx, [ebp+var_4]
CODE:0062169E                 mov     eax, [ebx]
CODE:006216A0                 call    CheckRegistration1
CODE:006216A5                 cmp     eax, 103BC1Dh
CODE:006216AA                 jnz     short loc_62170A

видно, что перед вызовом проверочной функции, вызывается некая функция, которую я обозвал InitRSize.
Внутри нее творится вот что:

CODE:0052AA2B                 lea     edx, [ebp+lpTempBuffer]
CODE:0052AA2E                 mov     ecx, offset lpDefis ; "-"
CODE:0052AA33                 mov     eax, [ebp+lpaArg1]
CODE:0052AA36                 call    SplitArray
CODE:0052AA3B                 lea     eax, [ebp+lpDefaultValue]
CODE:0052AA3E                 mov     edx, offset a777 ; "777"
CODE:0052AA43                 call    System::__linkproc__ LStrLAsg(void *,void *)
CODE:0052AA48                 mov     eax, [ebp+lpTempBuffer]
CODE:0052AA4B                 call    System::__linkproc__ DynArrayLength(void)
CODE:0052AA50                 cmp     eax, 3
CODE:0052AA53                 jl      short SetRSize
CODE:0052AA55                 lea     eax, [ebp+lpRSize]
CODE:0052AA58                 mov     edx, [ebp+lpTempBuffer]
CODE:0052AA5B                 mov     edx, [edx+8]
CODE:0052AA5E                 call    System::__linkproc__ LStrLAsg(void *,void *)
CODE:0052AA63                 mov     eax, [ebp+lpRSize]
CODE:0052AA66                 call    System::__linkproc__ DynArrayLength(void)
CODE:0052AA6B                 cmp     eax, 2
CODE:0052AA6E                 jl      short SetValue
CODE:0052AA70                 lea     eax, [ebp+var_18]
CODE:0052AA73                 call    System::__linkproc__ LStrClr(void *)
CODE:0052AA78                 mov     eax, [ebp+lpRSize]
CODE:0052AA7B                 cmp     byte ptr [eax], 31h
CODE:0052AA7E                 jnz     short loc_52AA8D
CODE:0052AA80                 lea     eax, [ebp+var_18]
CODE:0052AA83                 mov     edx, offset lpDefis ; "-"
CODE:0052AA88                 call    System::__linkproc__ LStrLAsg(void *,void *)
CODE:0052AA8D
CODE:0052AA8D loc_52AA8D:                             ; CODE XREF: InitRSize+86j
CODE:0052AA8D                 lea     eax, [ebp+lpRSize]
CODE:0052AA90                 mov     ecx, 1
CODE:0052AA95                 mov     edx, 1
CODE:0052AA9A                 call    System::__linkproc__ LStrDelete(void)
CODE:0052AA9F
CODE:0052AA9F SetValue:                               ; CODE XREF: InitRSize+76j
CODE:0052AA9F                 lea     eax, [ebp+lpDefaultValue]
CODE:0052AAA2                 mov     ecx, [ebp+lpRSize]
CODE:0052AAA5                 mov     edx, [ebp+var_18]
CODE:0052AAA8                 call    System::__linkproc__ LStrCat3(void)
CODE:0052AAAD
CODE:0052AAAD SetRSize:                               ; CODE XREF: InitRSize+5Bj
CODE:0052AAAD                 lea     eax, [ebp+var_28]
CODE:0052AAB0                 mov     edx, [ebp+lpDefaultValue]
CODE:0052AAB3                 call    GetVirtualAddr
CODE:0052AAB8                 push    [ebp+var_1C]
CODE:0052AABB                 push    [ebp+var_20]
CODE:0052AABE                 push    [ebp+var_24]
CODE:0052AAC1                 push    [ebp+var_28]
CODE:0052AAC4                 push    offset aMain_reg_rsize ; "Main.Reg_RSize2"
CODE:0052AAC9                 mov     eax, [ebp+var_8]
CODE:0052AACC                 push    eax
CODE:0052AACD                 mov     eax, [eax]
CODE:0052AACF                 call    dword ptr [eax+28h]

в переводе на русский это означает, что процедура берет из рег кода строку после второго дефиса
(если его нет - устанавливается дефолтовое значение), проверяет первый символ на равенство 0х31
(десятичная "1"), по-видимому означающий длину поля и записывает его в глобальную переменную RSize.
Заметим, что поле RSize хранится в строке рег кода именно в десятичном виде.
Таким образом, окончательный формат рег кода выглядит так:

хххххх-ххуу-с[z...]

где добавленные поля означают то, что описано выше.
С полем RSize можно разобраться двояко: сгенерировать его методом брута по схеме:

while(result<=0xffff){
		SizeSum=result+rawCC2;
		_asm{
			mov ax,word ptr rawCC2;
			add ax,word ptr result;
			mov word ptr RSizeSum,ax;
		}
		RSizeSum=result;
		DecriptString(iRData,RSizeSum,iRTime,lpRName,lpDectBuff,len);
		a=strcmp(lpLicenseFile,lpDectBuff);
		if(a==0)break;
		result++;
}
.....................
VOID DecriptString(INT RData, INT RSize, INT RTime, LPSTR RName, LPSTR DectBuff, INT len)
{
	int TempRTime=RTime;
	int i=len;
	_asm{
		mov esi,RName;
		mov edi,DectBuff;
        	}

	while(i>=0){
		_asm{
			mov ecx,TempRTime;
			shr ecx,8;
			xor cl,byte ptr[esi];
			mov byte ptr [edi],cl;
			add cl,byte ptr TempRTime;
			and ecx,0xff;
			imul cx,word ptr RData;
			add cx,word ptr RSize;
			mov word ptr TempRTime,cx;
			inc esi;
			inc edi;
		}
		i=i-1;
	}
	return;
}


Соответствующие поля RData, RTime и RName можно вытащить при помощи той же GetPrivateProfileString из Info.ini. Однако, лично я пошел по более короткому пути - при помощи WritePrivateProfileString пишу в нужные поля константы из эталонной функции вместе с эталонной декриптовой строкой. Это избавляет юзверя от дроча с копированием туда-сюда, а результат такой же.
К тому же, экзешник при этом не трогается - а значит все вполне в юридических рамках, хотя это дело вкуса.

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

P.S.Кстати, варианты замеса символов рег кода в зависимости от комплектации Психа, реализованы именно в функции, вызывающей InitRsize, думаю, в них несложно будет разобраться самостоятельно...






Обсуждение статьи: написание кейгена для Psychometric Expert 7 >>>


Материалы находятся на сайте http://e4unrusy7se5evw5.onion



Оригинальный DVD-ROM: eXeL@B DVD !


Вы находитесь на EXELAB.rU
Проект ReactOS