Заглянув в файл 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:0052AB4C CODE:0052AB4C cmp eax, 0FD1Dh CODE:0052AB51 jb short locret_52AB5F CODE:0052AB53 CODE:0052AB53 loc_52AB53: CODE:0052AB53 sub eax, 4381h CODE:0052AB58 cmp eax, 0FD1Dh CODE:0052AB5D jnb short loc_52AB53 CODE:0052AB5F CODE:0052AB5F locret_52AB5F: 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 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 CODE:0052AF6C call ShellToDecriptProc .......................................................... CODE:0052ACBE decript: CODE:0052ACBE movzx ecx, [ebp+intRTime] CODE:0052ACC2 shr ecx, 8 CODE:0052ACC5 xor cl, [esi] CODE:0052ACC7 mov [eax], cl CODE:0052ACC9 add cl, byte ptr [ebp+intRTime] CODE:0052ACCC and ecx, 0FFh CODE:0052ACD2 imul cx, word ptr [ebp+intRData] CODE:0052ACD7 add cx, word ptr [ebp+intRData+2] CODE:0052ACDB mov [ebp+intRTime], cx 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 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: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: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: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 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, думаю в них несложно
будет разобраться самостоятельно...
|