'Cerberus Shield' keygenning practice.
======================================
1. Введение
~~~~~~~~~~~
В этой статье я хочу рассмотреть защиту под гордым названием 'Cerberus Shield' и написание кейгена для нее же.
Сразу хочу заметить, что если у вас есть время и желание, то лучше поковыряйтесь и сделайте все самостоятельно, т.к.
это того стоит. Хотя особой оригинальности в данном продукте и не наблюдается, но все же намного интереснее дойти до
всего самому. По крайней мере мне так кажется.
Что касается непосредственно 'Cerberus Shield' - я так и не понял кто его производитель.При беглом осмотре интернета
ничего толкового (например, сайт разработчиков) не нашлось. Возможно это самописный продукт Parallaxis? Тогда каким
образом он появился у AusLogics? Одна контора под разными названиями? :) В общем ладно, это не есть главное для нас.
Итак, в поле моего зрения попало несколько программ:
- Parallaxis Boost-It 2.2.9.360 (Cerberus DE)
- Parallaxis iAlbum 1.0.0.81 (Cerberus DE)
- Parallaxis Winclip 3.0.0.14 (Cerberus DE)
- AusLogics BoostSpeed 3.0.2.451 (Cerberus DE 1.1)
- Parallaxis Cuckoo Clock (Cerberus 2)
В скобках указаны версии защиты, на которые я разделил их лично для себя. Cerberus DE - это самая простая из всех
перечисленных. Написана на дельфях (DE - Delphi Edition :) и ничего сложного там нет. Поэтому свой рассказ я начну
именно с нее. Cerberus DE 1.1 - это "усовершенствованная" версия предыдущей. Слово я взял в кавычки потому, что
изменения - это несколько приписанных инструкций ассемблера. И наконец Cerberus 2 - основная тема моего рассказа.
Написана на С++ и по сравнению с предыдущими двумя выглядит более значимо. На ней мы остановимся более подробно.
В комплекте с каждой из перечисленных программ идет файл cerberus.dll, в котором и содержится интересующий нас код.
Он упакован UPX`м, но этот факт нас остановить не может и не должен. Можно даже не утруждать себя и не набивать лишний
раз "upx -d", т.к. это особой роли не играет.
Что понадобится в работе:
1) OllyDbg (http://home.t-online.de/home/Ollydbg/)
2) IDA (http://reversing.kulichki.net/files/dizas/dizas.htm - на момент написания статьи там был живой линк на
версию 4.3.0.740. Более новые ищите сами)
3) masm32 (http://www.masm32.com/)
4) Ну и конечно же правильно настроенный девайс "руки" (также известный как "hands.sys")
Ах да, чуть не забыл. Ссылки на софт, который будем исследовать:
1) Parallaxis (http://www.pxcompany.com/)
2) AusLogics BoostSpeed (http://www.boost-speed.com/)
Вот теперь можно приступать.
2. Cerberus DE
~~~~~~~~~~~~~~
Будем рассматривать Parallaxis iAlbum. Почему именно его? Потому что я первым его скачал =) Техника же для остальных
программ ничуть не меняется. Все различия (кроме смещений конечно) я постарался указать явно в тексте.
Как и говорилось раньше, у этой версии алгоритм не такой уж и сложный. В общем случае его можно записать так (далее
при исследовании рассмотрим все это подробнее):
1) Проверка длины имени и ключа
2) Проверка первой части ключа (входят ли символы в определенную группу) и формирование дворда на их основе
3) Получение хэша на основе имени
4) Сравнение дворда и хэша имени
5) Формирование дворда из второй части ключа
6) Получение хэша на основе дворда
7) Сравнение хэша с константой
Пока IDA дизасмит iAlbum.exe, мы загрузим его в Олю и найдем то место, где собственно начинается реализация
приведенного алгоритма. Для этого перейдем на OEP (если вы не знаете как это сделать - читайте туториалы по распаковке
UPX`а), поставим бряк на MessageBoxA, запустим программу по F9, введем левые регистрационные данные и нажмем кнопочку
Register. После этой незамысловатой процедуры Оля приземлится тут:
0049D936 E8 896FF6FF CALL iAlbum.004048C4
0049D93B 50 PUSH EAX
0049D93C 53 PUSH EBX
0049D93D E8 7A9CF6FF CALL iAlbum.004075BC ; <-- Мы тут
0049D942 8BD8 MOV EBX,EAX
0049D944 33C0 XOR EAX,EAX
Смотрим чуть выше и находим начало данной процедуры:
0049D8FC 55 PUSH EBP ; <-- Сюда бряк
0049D8FD 8BEC MOV EBP,ESP
0049D8FF 51 PUSH ECX
0049D900 53 PUSH EBX
Удаляем старый бряк и ставим новый в указанном месте (0x0049D8FC). Отпускаем программу и снова жмем Register. После
этого смотрим на текущий образ стека и находим там такую строчку:
0012F338 |004EDC65 RETURN to iAlbum.004EDC65 from iAlbum.0049D8A8
Это то место, куда вернется программа, если мы вводим неправильные данные регистрации. Попросим Олю перенести нас в
это место (Ctrl+G и введем адрес 0x004EDC65). Покрутим листинг чуть вверх и увидим желанные строчки:
004EDBF5 50 PUSH EAX
004EDBF6 E8 CDF9FFFF CALL iAlbum.004ED5C8 ; JMP to OFFSET cerberus.regVerifyKey
004EDBFB 84C0 TEST AL,AL
004EDBFD 74 57 JE SHORT iAlbum.004EDC56
Вот именно функция regVerifyKey нам и понадобится. Кстати, для большей наглядности можете заглянуть в экспорты
cerberus.dll (при помощи PE Tools например) и посмотреть на функции, которые там имеются. Все их имена несут
достаточную смысловую нагрузку для того, чтобы понять что именно они делают. Для осознания всех тонкостей этой защиты
можно поковырять и их ;)
Итак, заходим внутрь процедуры. Не удивляйтесь, что она такая маленькая - это всего лишь "переходник". Нам нужен
единственный call (0x0034CD9D), который и является функцией проверки. Трейсим по F7 до ее начала и, как говорится,
понеслась...
Первые строки можно смело пропускать - здесь идет всякая никому не нужная дельфовая хрень. Наш алгоритм начинается
по адресу 0x0034CAF4. И первое - это проверка длины имени пользователя:
0034CAF4 8B45 E4 MOV EAX,DWORD PTR SS:[EBP-1C] ; eax = имя
0034CAF7 E8 C074FEFF CALL cerberus.00333FBC ; получить длину строки
0034CAFC 83F8 03 CMP EAX,3
0034CAFF 0F8C 52020000 JL cerberus.0034CD57
Как видно из приведенного куска, имя должно быть >= 3 символов. Следует об этом помнить. Длина ключа проверяется
таким же образом чуть ниже. Кусок, который проверяет присутствие двух символов '-' в ключе мне не совсем понятен в
данной ситуации. Зачем это делать, если в передаваемой функции строке они есть ВСЕГДА? Возможно cerberus.dll писали
все-таки сторонние производители? :)
Следующий шаг - это проверка первой части серийника и формирование дворда. Сначала приведу кусок кода, потом объясню
что за проверки и как все происходит.
0034CB6F 8A40 08 MOV AL,BYTE PTR DS:[EAX+8] ; al = символ ключа
0034CB72 E8 89FEFFFF CALL cerberus.0034CA00 ; проверка и преобразование символа
0034CB77 85C0 TEST EAX,EAX
0034CB79 0F8C D8010000 JL cerberus.0034CD57
0034CB7F C1E6 04 SHL ESI,4 ; esi = формируемый дворд
0034CB82 0BF0 OR ESI,EAX ; добавляем преобразованный символ к дворду
Если кто еще не догадался, ключ в памяти выглядит так: "XXXX-XXXX-XXXXXXXX". Первая часть - это "XXXX-XXXX". Суть
работы такая: берется символ из этой части (начиная с _КОНЦА_!) и вызывается функция cerberus.0034CA00, которая
преобразует символ. Что есть преобразование? Все очень просто. Имеется массив, состоящий из 16 элементов. Функция ищет
переданный ей символ в данном массиве и возвращает порядковый номер в нем, который в последствии и добавляется к
дворду. Если переданного символа в нем нет - это значит, что введенные данные уже не верны. Массив для iAlbum.exe
выглядит так: "23456789ARTFGNSM", причем от программы к программе он остается постоянным.
Шаг номер 3 - получение хэша на основе имени. Тут все совсем просто. Имеется единая функция генерации, которая
используется во всей процедуре проверки. И если присмотреться повнимательнее к коду, то... это есть ни что иное, как
алгоритм CRC32! В edx ей передается указатель на тот участок памяти, с которого хотят получить crc; в ecx - количество
байт. А выглядит эта функция так:
0034B968 09D2 OR EDX,EDX
0034B96A 74 18 JE SHORT cerberus.0034B984
0034B96C E3 16 JECXZ SHORT cerberus.0034B984
0034B96E 53 PUSH EBX
0034B96F 31DB XOR EBX,EBX
0034B971 88C3 MOV BL,AL
0034B973 321A XOR BL,BYTE PTR DS:[EDX]
0034B975 C1E8 08 SHR EAX,8
0034B978 33049D A45D3500 XOR EAX,DWORD PTR DS:[EBX*4Ю]
0034B97F 42 INC EDX
0034B980 49 DEC ECX
0034B981 ^75 EC JNZ SHORT cerberus.0034B96F
0034B983 5B POP EBX
0034B984 C3 RETN
Я думаю все понятно без дополнительных объяснений. СтОит наверное только отметить то, что инструкция по адресу
0x0034B978 ксорит значение в eax с числом, которое берется из массива. Этот массив состоит из 256 элементов и размер
каждого из них, как вы наверное могли уже заметить, есть дворд.
И шаг номер 4 завершает первый кусок алгоритма инструкцией:
0034CC46 3BF0 CMP ESI,EAX
В которой просто сравнивается полученный дворд из серийника и хэш имени. Если чуть подумать и прокрутить алгоритм в
обратном направлении, то в голове уже рождается первая часть кейгена. Нам нужно только подсчитать CRC введенного имени
и преобразовать его в строчку, используя упоминавшийся массив из 16 элементов. Проще по-моему некуда.
Вторая часть алгоритма чуть более сложная. Объясню почему. Вас должно смущать слово "константа" в шаге номер 8.
Константа есть числовое значение. Нам же нужно вводить текст, чтобы функция смогла ее преобразовать и сравнить. Отсюда
делаем вывод, что автор программы знает эту строчку и прописывает ее заранее. И т.к. он врятли подскажет ее нам в
интимной переписке по мылу, придется все это угадывать самим. А здесь поможет только брутфорс...
Но обо всем по порядку. Следующий шаг - это формирование дворда из второй части ключа. Здесь все так же как и с
частью номер раз. Опять же, алгоритм начинается с последнего символа и движется к началу. Получившийся дворд находится
в edx.
Получение хэша на основе дворда - это то же самое, что и получение хэша имени. Функции подсчета CRC32 передается
указатель на память (в данном конкретном случае - это указатель на получившийся дворд). Количество байтов равно 4.
0034CD30 8955 F0 MOV DWORD PTR SS:[EBP-10],EDX ; в edx наш дворд
0034CD33 8D55 F0 LEA EDX,DWORD PTR SS:[EBP-10] ; edx = указатель на память
0034CD36 B9 04000000 MOV ECX,4 ; кол-во байт
0034CD3B 33C0 XOR EAX,EAX
0034CD3D E8 26ECFFFF CALL cerberus.0034B968 ; CRC32
0034CD42 8B15 B4613500 MOV EDX,DWORD PTR DS:[3561B4]
0034CD48 3B42 14 CMP EAX,DWORD PTR DS:[EDX+14] ; [edx+14] = дворд-константа
Последняя из вышеприведенных инструкций сравнивает результат работы функции с тем самым константным двордом,
"строковое" значение которого мы в скором времени получим. В iAlbum.exe он равен 0x6E3DF5BB. И это значение во всех
программах РАЗНОЕ!
Вот в общем-то и все. Вторая часть будущего кейгена ничего толком не делает. Только выводит "строковое" значение
константы.
Алгоритм работы брутфорса по-моему должен быть понятен всем.Нет?! Ну ладно, объясняю :) Все, что нам нужно сделать -
это найти правильное числовое значение для второй части ключа. Вторая часть выглядит так: "XXXXXXXX", т.е. 8 символов.
Все было бы хорошо, если бы эти символы были последовательны, т.е., например, "ABCDE..." и т.д. Но ведь это не так. В
нашем случае символы берутся из массива, о котором я говорил выше. И вот там символы совсем уж непоследовательны...
Поэтому стандартный брутфорс немного усложняется.
Суть же его работы такая:
1) Сформировать дворд из сточки (читай - куска ключа)
2) Подсчитать CRC32 этого дворда
3) Сравнить его с забитой в программе константой
4) Если они равны - ура, мы нашли то, что искали.
5) Если нет, то мы должны заменить символ(ы) в нашей строке и перейти к шагу 1.
Проблему тут может представлять только шаг под номером 5.Я выбрал такой способ (ниже идет собственно код брутфорса):
;---------------------------------------------------------------------------------------------------------------------
.386
.model flat, stdcall
option casemap :none
include c:\masm32\include\windows.inc
include c:\masm32\include\kernel32.inc
include c:\masm32\include\user32.inc
include c:\masm32\include\shlwapi.inc
includelib c:\masm32\lib\kernel32.lib
includelib c:\masm32\lib\user32.lib
includelib c:\masm32\lib\shlwapi.lib
.data
o_str db '23456789ARTFGNSM', 0 ; Наш исходный массив с символами
f_str db '22222222', 0 ; Строка, на основе которой считаем дворд
str_ptr dd 7 ; Указатель на символы в предыдущей строке
array dd 8 dup (0) ; Массив содержит текущее "состояние" строки,
; т.е. каждый элемент есть указатель на символ
; в o_str.
arr_ptr dd ? ; Указатель на элементы в предыдущем массиве
num dd ? ; Наш дворд
notfound db 'No combinations found....wtf?!', 0
; А это та самая таблица из 256 значений, которая
; используется при получении CRC32
tabl dd 0, 77073096h, 0EE0E612Ch, 990951BAh, 76DC419h, 706AF48Fh
dd 0E963A535h, 9E6495A3h, 0EDB8832h, 79DCB8A4h, 0E0D5E91Eh
dd 97D2D988h, 9B64C2Bh, 7EB17CBDh, 0E7B82D07h, 90BF1D91h
dd 1DB71064h, 6AB020F2h, 0F3B97148h, 84BE41DEh, 1ADAD47Dh
dd 6DDDE4EBh, 0F4D4B551h, 83D385C7h, 136C9856h, 646BA8C0h
dd 0FD62F97Ah, 8A65C9ECh, 14015C4Fh, 63066CD9h, 0FA0F3D63h
dd 8D080DF5h, 3B6E20C8h, 4C69105Eh, 0D56041E4h, 0A2677172h
dd 3C03E4D1h, 4B04D447h, 0D20D85FDh, 0A50AB56Bh, 35B5A8FAh
dd 42B2986Ch, 0DBBBC9D6h, 0ACBCF940h, 32D86CE3h, 45DF5C75h
dd 0DCD60DCFh, 0ABD13D59h, 26D930ACh, 51DE003Ah, 0C8D75180h
dd 0BFD06116h, 21B4F4B5h, 56B3C423h, 0CFBA9599h, 0B8BDA50Fh
dd 2802B89Eh, 5F058808h, 0C60CD9B2h, 0B10BE924h, 2F6F7C87h
dd 58684C11h, 0C1611DABh, 0B6662D3Dh, 76DC4190h, 1DB7106h
dd 98D220BCh, 0EFD5102Ah, 71B18589h, 6B6B51Fh, 9FBFE4A5h
dd 0E8B8D433h, 7807C9A2h, 0F00F934h, 9609A88Eh, 0E10E9818h
dd 7F6A0DBBh, 86D3D2Dh, 91646C97h, 0E6635C01h, 6B6B51F4h
dd 1C6C6162h, 856530D8h, 0F262004Eh, 6C0695EDh, 1B01A57Bh
dd 8208F4C1h, 0F50FC457h, 65B0D9C6h, 12B7E950h, 8BBEB8EAh
dd 0FCB9887Ch, 62DD1DDFh, 15DA2D49h, 8CD37CF3h, 0FBD44C65h
dd 4DB26158h, 3AB551CEh, 0A3BC0074h, 0D4BB30E2h, 4ADFA541h
dd 3DD895D7h, 0A4D1C46Dh, 0D3D6F4FBh, 4369E96Ah, 346ED9FCh
dd 0AD678846h, 0DA60B8D0h, 44042D73h, 33031DE5h, 0AA0A4C5Fh
dd 0DD0D7CC9h, 5005713Ch, 270241AAh, 0BE0B1010h, 0C90C2086h
dd 5768B525h, 206F85B3h, 0B966D409h, 0CE61E49Fh, 5EDEF90Eh
dd 29D9C998h, 0B0D09822h, 0C7D7A8B4h, 59B33D17h, 2EB40D81h
dd 0B7BD5C3Bh, 0C0BA6CADh, 0EDB88320h, 9ABFB3B6h, 3B6E20Ch
dd 74B1D29Ah, 0EAD54739h, 9DD277AFh, 4DB2615h, 73DC1683h
dd 0E3630B12h, 94643B84h, 0D6D6A3Eh, 7A6A5AA8h, 0E40ECF0Bh
dd 9309FF9Dh, 0A00AE27h, 7D079EB1h, 0F00F9344h, 8708A3D2h
dd 1E01F268h, 6906C2FEh, 0F762575Dh, 806567CBh, 196C3671h
dd 6E6B06E7h, 0FED41B76h, 89D32BE0h, 10DA7A5Ah, 67DD4ACCh
dd 0F9B9DF6Fh, 8EBEEFF9h, 17B7BE43h, 60B08ED5h, 0D6D6A3E8h
dd 0A1D1937Eh, 38D8C2C4h, 4FDFF252h, 0D1BB67F1h, 0A6BC5767h
dd 3FB506DDh, 48B2364Bh, 0D80D2BDAh, 0AF0A1B4Ch, 36034AF6h
dd 41047A60h, 0DF60EFC3h, 0A867DF55h, 316E8EEFh, 4669BE79h
dd 0CB61B38Ch, 0BC66831Ah, 256FD2A0h, 5268E236h, 0CC0C7795h
dd 0BB0B4703h, 220216B9h, 5505262Fh, 0C5BA3BBEh, 0B2BD0B28h
dd 2BB45A92h, 5CB36A04h, 0C2D7FFA7h, 0B5D0CF31h, 2CD99E8Bh
dd 5BDEAE1Dh, 9B64C2B0h, 0EC63F226h, 756AA39Ch, 26D930Ah
dd 9C0906A9h, 0EB0E363Fh, 72076785h, 5005713h, 95BF4A82h
dd 0E2B87A14h, 7BB12BAEh, 0CB61B38h, 92D28E9Bh, 0E5D5BE0Dh
dd 7CDCEFB7h, 0BDBDF21h, 86D3D2D4h, 0F1D4E242h, 68DDB3F8h
dd 1FDA836Eh, 81BE16CDh, 0F6B9265Bh, 6FB077E1h, 18B74777h
dd 88085AE6h, 0FF0F6A70h, 66063BCAh, 11010B5Ch, 8F659EFFh
dd 0F862AE69h, 616BFFD3h, 166CCF45h, 0A00AE278h, 0D70DD2EEh
dd 4E048354h, 3903B3C2h, 0A7672661h, 0D06016F7h, 4969474Dh
dd 3E6E77DBh, 0AED16A4Ah, 0D9D65ADCh, 40DF0B66h, 37D83BF0h
dd 0A9BCAE53h, 0DEBB9EC5h, 47B2CF7Fh, 30B5FFE9h, 0BDBDF21Ch
dd 0CABAC28Ah, 53B39330h, 24B4A3A6h, 0BAD03605h, 0CDD70693h
dd 54DE5729h, 23D967BFh, 0B3667A2Eh, 0C4614AB8h, 5D681B02h
dd 2A6F2B94h, 0B40BBE37h, 0C30C8EA1h, 5A05DF1Bh, 2D02EF8Dh
.code
start:
push offset arr_ptr-4 ; Инициализация
pop arr_ptr
mov str_ptr, 7
mov ecx, 8
mov ebx, ecx
dec ebx
xor edx, edx
_GetNum: ; Получение дворда
mov eax, array[ebx*4]
shl edx, 4
or edx, eax
dec ebx
loop _GetNum
mov num, edx
lea edx, num
mov ecx, 4
xor eax, eax
CRC32: ; Генерация хэша
xor ebx, ebx
mov bl, al
xor bl, byte ptr [edx]
shr eax, 8
xor eax, dword ptr [ebx*4+tabl]
inc edx
dec ecx
jnz CRC32
cmp eax, 6e3df5bbh ; Сравнение с константой
jz YEAH
cmp num, -1 ; Если ВСЕ комбинации проверены
je NotFound
; Изменяемый символ последний в массиве?
mov edi, arr_ptr
inc dword ptr [edi]
mov esi, [edi]
cmp esi, 15
jg FixArray ; Да, фиксим строчку
movzx eax, byte ptr o_str[esi] ; Нет, просто заменяем на следующий
mov [f_str+7], al
jmp start
FixArray:
mov ecx, 8
FixArray_loop: ; В цикле фиксим всю строку
and dword ptr [edi], 0
mov [f_str+ecx-1], '2'
sub edi, 4
inc dword ptr [edi]
cmp dword ptr [edi], 16
je FixNum
mov esi, [edi]
movzx eax, byte ptr o_str[esi]
mov ebx, str_ptr
dec ebx
mov f_str[ebx], al
jmp start
FixNum:
dec str_ptr
sub arr_ptr, 4
loop FixArray_loop
YEAH:
invoke MessageBox, NULL, offset f_str, NULL, 0
jmp _exit
NotFound:
invoke MessageBox, NULL, offset notfound, NULL, 0
_exit:
invoke ExitProcess, 0
end start
end
;---------------------------------------------------------------------------------------------------------------------
Вот такой вот может быть и не совсем оптимизированный способ. Но это первое, что пришло мне в голову. А если брать
в расчет то, что количество комбинаций не такое уж большое, то можно и немного подождать, раз уж на то пошло ;)
Запускаем наше творение и по истечении нескольких минут получаем желанное - "SS7RGRN2". В радости прыгаем по комнате
и открываем новую бутылку пива.
Ну и самое приятное - написание кейгена. Я думаю по его алгоритму никаких вопросов остаться не должно.Поэтому только
покажу как может выглядеть сам код (точнее - его основные куски):
;---------------------------------------------------------------------------------------------------------------------
szKey db KEYSIZE dup(0)
defname db "dzen",0
bigName db "Your name is very long...", 0
szString db "23456789ARTFGNSM", 0
szFormat db "%s-%s", 0
szSrcKey db "SS7RGRN2", 0
szBuf db 10 dup (0)
tabl dd 0, 77073096h, ...
KeyGen proc hWin: HWND
LOCAL szName[97]:BYTE
pusha
invoke GetDlgItem, hWin, 101
invoke GetWindowTextLength, eax
.if eax > 96
lea edx, bigName
jmp errorC
.elseif eax == 0
ret
.endif
invoke GetDlgItemText, hWin, 101, addr szName, 96
xchg eax, ecx
lea edx, szName
xor eax, eax
call CRC32
mov ecx, 8
xor edx, edx
lea esi, szBuf
Format:
movzx edx, al
and dl, 0fh
mov dl, [szStringЬ]
mov [esi], dl
.if ecx == 5
inc esi
mov byte ptr [esi], 2dh
.endif
inc esi
ror eax, 4
loop Format
invoke wsprintf, offset szKey, offset szFormat, offset szBuf, offset szSrcKey
lea edx, szKey
errorC:
invoke SetDlgItemText, hWin, 102, edx
popa
xor eax, eax
ret
CRC32:
push ebx
loc_41B96F:
xor ebx, ebx
mov bl, al
xor bl, [edx]
shr eax, 8
xor eax, dword ptr [ebx*4ж]
inc edx
dec ecx
jnz short loc_41B96F
pop ebx
retn
KeyGen endp
;---------------------------------------------------------------------------------------------------------------------
Если чего забыл - извиняюсь. Кусок выдирал из реального кейгена, поэтому все может быть. Но я думаю должно быть
понятно.
3. Cerberus DE 1.1
~~~~~~~~~~~~~~~~~~
Здесь порядок наших действий не отличается от предыдущих исследований. Поиск процедуры проверки точно такой же.
Различия только в адресах и смещениях. При просмотре образа стека нужно искать такую строчку:
0012EB04 |00567FEB RETURN to boostspe.00567FEB from boostspe.00576558
Т.е. адрес, который нам нужен - это 0x00567FEB. Переходим по нему и находим нужное:
00567F55 50 PUSH EAX
00567F56 E8 155FF9FF CALL boostspe.004FDE70 ; JMP to cerberus.regVerifyKey
00567F5B 84C0 TEST AL,AL
00567F5D 74 6B JE SHORT boostspe.00567FCA
Дальнейшие действия абсолютно аналогичны вышеописанным вплоть до шага номер 5. Здесь талантливые разработчики
применили недюжие усилия и улучшили процедуру проверки аж целым одним ксором!
0034CD14 8955 F0 MOV DWORD PTR SS:[EBP-10],EDX ; edx = сформированный дворд
0034CD17 3175 F0 XOR DWORD PTR SS:[EBP-10],ESI ; !!!
0034CD1A 8D55 F0 LEA EDX,DWORD PTR SS:[EBP-10]
0034CD1D B9 04000000 MOV ECX,4
0034CD22 33C0 XOR EAX,EAX
0034CD24 E8 FFEBFFFF CALL cerberus.0034B928 ; CRC32
Полет мысли отмечен тремя восклицательными знаками. В esi содержится хэш имени пользователя. Кейген также меняется
незначительно. Все что нам нужно сделать в дополнение к проделанному уже - это добавить точно такой же ксор. Правда
есть одна мелочь. Т.к. вторая часть ключа у нас уже не константа, то придется результат ксора перевести в строчку,
используя 16-символьный массив. Вот измененная процедура кейгена:
;---------------------------------------------------------------------------------------------------------------------
szKey db KEYSIZE dup(0)
defname db "dzen",0
bigName db "Your name is very long...", 0
szString db "23456789ARTFGNSM", 0
szBuf db 32 dup (0)
dwKey dd ?
tabl dd 0, 77073096h, ...
KeyGen proc hWin: HWND
LOCAL szName[97]:BYTE
pusha
invoke GetDlgItem, hWin, 101
invoke GetWindowTextLength, eax
.if eax > 96
lea edx, bigName
jmp errorC
.elseif eax == 0
ret
.endif
invoke GetDlgItemText, hWin, 101, addr szName, 96
xchg eax, ecx
lea edx, szName
xor eax, eax
call CRC32
mov dwKey, 4ada4492h ; дворд-константа в программе
xor dwKey, eax ; нужный ксор
mov ecx, 8
push ecx
xor edx, edx
lea esi, szBuf
xor edi, edi
call Format ; переводим в строку первую часть ключа
mov byte ptr [esi], '-'
inc esi
pop ecx
xor edx, edx
mov eax, dwKey
inc edi
call Format ; и вторую часть ключа
lea edx, szBuf
errorC:
invoke SetDlgItemText, hWin, 102, edx
popa
xor eax, eax
ret
;______
Format:
movzx edx, al
and dl, 0fh
mov dl, [szStringЬ]
mov [esi], dl
.if ecx == 5
.if edi == 0
inc esi
mov byte ptr [esi], 2dh
.endif
.endif
inc esi
ror eax, 4
loop Format
retn
;______
CRC32:
push ebx
loc_41B96F:
xor ebx, ebx
mov bl, al
xor bl, [edx]
shr eax, 8
xor eax, dword ptr [ebx*4ж]
inc edx
dec ecx
jnz short loc_41B96F
pop ebx
;locret_41B984:
retn
KeyGen endp
;---------------------------------------------------------------------------------------------------------------------
4. Cerberus 2
~~~~~~~~~~~~~
Итак, основная тема данной статьи. Для начала расскажу о различиях данной версии защиты и двух предыдущих. Первое,
что бросается в глаза - это присутствие в корневом каталоге программы еще одного файла Cerberus`а - cerberus.ini.
Если заглянуть внутрь, то можно обнаружить различные настройки как самой программы, так и защиты в целом.Единственное,
что нам может понадобится здесь - это опция под названием "Hash" и значением "0EEF" (в самом конце файла). Не буду
пока что подробно останавливаться на этом, всему свое время.
Конечно же, сразу возникает желание поменять строчки конфига на что-то свое. Но делать этого не советую, т.к.
программа при запуске проверяет данный файл на изменения и отказывается запускаться, если таковые обнаружатся. Причем
если вернуть все на место, то это тоже не поможет =) Так что прежде чем экспериментировать, сохраните cerberus.ini
где-нибудь в сухом и прохладном месте.
Изменения номер два - это файл cerberus.dll. Теперь он ничем не запакован (и это в принципе нам на руку, т.к. не
нужно лишний раз запускать UPX). Также, если посмотреть на экспорты, то там мы не обнаружим большинство функций
предыдущей версии. Теперь их всего семь штук.
Принцип работы защиты так же изменился в значительной степени, хотя сама логика осталась прежней. Разработчики
постарались усложнить жизнь возможным исследователям (т.е. нам)и теперь добиться своего будет не так легко как раньше.
Например, чтобы найти процедуры защиты - мало поставить бряк на MessageBoxA. Но нас это в общем-то не должно
останавливать :)
Но начнем мы, как ни странно, все тем же путем, т.е. отлавливая вызов MessageBox`а. Для этого откроем cuckoo.exe в
Оле и после того как загрузятся все нужные библиотеки, перейдем непосредственно к cerberus.dll (давим правую
кнопку -> View -> Module 'CERBERUS') и поставим бряк на MessageBoxA. Запустим программу по F9, введем левые данные
(заметьте, что внешний вид изменился - теперь это диалоговое окно; также обратите внимание на измененную структуру
ключа) и нажмем "Next". Оля приземлится здесь:
10002263 50 PUSH EAX
10002264 6A 00 PUSH 0
10002266 FF15 94810010 CALL DWORD PTR DS:[<&USER32.MessageBoxA>>; USER32.MessageBoxA ; <-- мы тут
И здесь нас ждет первое разочарование. Вблизи вызова MessageBox`а нет ничего такого, что напоминало бы проверку на
правильно введенный ключ. Так же если побродить по стеку и посмотреть на порядок вызова процедур, то ничего толкового
тоже не обнаружится. А все дело в том, что, как я уже и говорил, внешний вид защиты превратился в диалог, который
порождается в функцией StartTryWizard. Можно в общем-то ставить бряки на нее и дальше трейсить до нужного момента, но
как мне показалось - это не лучший вариант, т.к. придется пройти кучу ненужного кода. Так же ничего не выйдет и с
отловом всевозможных GetDlgItemTextA и GetWindowTextA, т.к. их нет (точнее есть, но к делу они не относятся). Текст из
полей ввода берется посредством SendMessage.
Я покажу вам тот способ, которым пользовался сам. Возможно и не самый элегантный вариант, но зато действенный и
проверенный. В данный момент (если конечно вы ничего больше не делали) мы находимся на вызове MessageBox, который
отображает текст о неудачно введенной регистрационной информации.Если прокрутить листинг чуть вверх, то в комментариях
(а Оля любовно проставляет их для вас) можно увидеть введенный ключ в текстовом виде:
10002220 F3:A4 REP MOVS BYTE PTR ES:[EDI],BYTE PTR DS:[ESI]
10002222 BF 8CAD0010 MOV EDI,CERBERUS.1000AD8C ; ASCII "2222-2222-2222-2222-2222"
10002227 83C9 FF OR ECX,FFFFFFFF
1000222A F2:AE REPNE SCAS BYTE PTR ES:[EDI]
Сделаем следующее: перейдем по адресу 0x1000AD8C и, как ни странно, увидим там наш ключ. Выделим его и поставим бряк
на запись в память по этому адресу (жмем правой кнопкой на выделенном тексте -> Breakpoint -> Memory, on write).
Отпустим программу и снова нажмем "Next". Теперь Оля приземлится тут:
10001D87 6A 0D PUSH 0D
10001D89 C1E9 02 SHR ECX,2
10001D8C F3:A5 REP MOVS DWORD PTR ES:[EDI],DWORD PTR DS:[ESI] ; <-- мы здесь
10001D8E 8BC8 MOV ECX,EAX
На первый взгляд может показаться, что ни к чему толковому это не привело, но скажу вам по секрету, что это и есть
нужная нам процедура ;) Удалим поставленный бряк ('md' в командной строке) и начнем разбираться в том, куда нас
занесло.
Начиная с адреса 0x10001D4F и вплоть до 0x10001F27 считываются текстовые поля ввода и формируется ключ в памяти в
формате "XXXX-XXXX-XXXX-XXXX-XXXX". Далее, с 0x10001F30 по 0x10001F78 проверяется принадлежность символов ключа
промежутку '0..9', 'A..F', т.е. легко сделать вывод о том, что каждая четырехзначная часть ключа будет в последствии
переводиться программой в word и использоваться.
Все последующие действия завязаны друг на друге, поэтому чтобы не запутать себя и читателя, я приведу алгоритм того,
что происходит внутри исследуемой программы:
1) Получение хэша на основе введенного имени
2) Получение хэша части ключа (без первого "XXXX-")
3) xor полученных на первом и втором шаге значений
4) Перевод первой части ключа ("XXXX-" без '-') в ворд
5) Изменение конечной части ключа (все без того же "XXXX-") на основе хэша имени
6) Получение хэша измененной части ключа
7) Сравнение значения шага 7 с константой и установка флага
8) Сравнение значений шага номер 3 и 4
9) Проверка установленного флага на шаге номер 7
Шаг номер один начинается по адресу 0x10001F89 и выглядит следующим образом:
10001F89 51 PUSH ECX ; длина
10001F8A 50 PUSH EAX
10001F8B 68 A8AD0010 PUSH CERBERUS.1000ADA8 ; имя
10001F90 E8 6B1D0000 CALL CERBERUS.10003D00
Как и в предыдущих версиях Cerberus, код получения хэша един для всей процедуры проверки регистрационных данных.
Единственное большое отличие здесь в том, что хэш есть НЕ дворд как раньше, а только лишь ворд и используемый алгоритм
не CRC32, а CRC16. Выглядит он так:
10003D00 56 PUSH ESI
10003D01 8B7424 10 MOV ESI,DWORD PTR SS:[ESP+10] ; кол-во байт
10003D05 33C9 XOR ECX,ECX
10003D07 85F6 TEST ESI,ESI
10003D09 7E 31 JLE SHORT CERBERUS.10003D3C
10003D0B 8B4424 0C MOV EAX,DWORD PTR SS:[ESP+C] ; начальное значение
10003D0F 53 PUSH EBX
10003D10 57 PUSH EDI
10003D11 8B7C24 10 MOV EDI,DWORD PTR SS:[ESP+10] ; с чего получать хэш
10003D15 33D2 XOR EDX,EDX
10003D17 8BD8 MOV EBX,EAX
10003D19 8A1439 MOV DL,BYTE PTR DS:[ECX+EDI]
10003D1C 81E3 FF000000 AND EBX,0FF
10003D22 33D3 XOR EDX,EBX
10003D24 33DB XOR EBX,EBX
10003D26 8ADC MOV BL,AH
10003D28 66:8B0455 BCA10010 MOV AX,WORD PTR DS:[EDX*2+1000A1BC] ; таблица
10003D30 66:33C3 XOR AX,BX
10003D33 41 INC ECX
10003D34 3BCE CMP ECX,ESI
10003D36 ^7C DD JL SHORT CERBERUS.10003D15
10003D38 5F POP EDI
10003D39 5B POP EBX
10003D3A 5E POP ESI
10003D3B C3 RETN ; результат в ax
10003D3C 66:8B4424 0C MOV AX,WORD PTR SS:[ESP+C]
10003D41 5E POP ESI
10003D42 C3 RETN
Размер используемого массива как и раньше состоит из 256 элементов,только вот размер каждого элемента сменился с 4-х
байт на 2. Да и вообще, весь алгоритм проверки и генерации больше оперирует вордами, нежели двордами. Но это не суть
важно.
Шаг номер 2 и 3 подробно описывать не буду, т.к. там и так все довольно банально. Программа передвигает указатель в
ключе на нужное место (вторая его часть) и вызывает всю ту же процедуру по адресу 0x10003D00. Ну и затем ксорит
предыдущие вычисления, оставляя результат в ebx. Перевод части ключа в ворд также занятие не очень сложное. Весь
процесс состоит из четырех вызовов функции, которая преобразует символ в число, и сохранение результата в регистре
(в esi в данном случае).
И вот, быстро рассмотрев частности, переходим к сути :) Самое интересное начинается здесь:
1000200E E8 BDFAFFFF CALL CERBERUS.10001AD0
Данная процедура реализует шаги с 5 по 7. На основе вновь подсчитанного хэша имени она изменяет вторую часть
введенного ключа и работает дальше с ней. Преобразование - это просто замена символа на новый. Причем стоит отметить,
что данная функция обратна сама себе,т.е. например если ввести ключ "2222-2222-2222-2222-2222", то последняя его часть
будет преобразована в "7777-7777-BBBB-6666". Но если в качестве ключа использовать полученную строку, т.е.
"2222-7777-7777-BBBB-6666", то в результате получим исходное, т.е. "2222-2222-2222-2222". Такие вот фокусы :) И если
посмотреть дальше и найти строки,где полученный хэш ключа сравнивается с константой (шаг 7), то все становится на свои
места. Для непонятливых разъясняю: в программу исходно зашит хэш некой строки, которую выдумал автор. Кстати, именно
это значение и присутствует в конфиге cerberus.ini ("0EEF"). Обрабатывая вторую часть ключа, защита должна привести ее
(часть) в изначальный вид, т.е в строку, хэш которой равен 0x0EEF. Да да, и снова нам потребуется написание брутфорса
чтобы эту самую строчку обнаружить и использовать в кейгене.Такой перебор, по сравнению с предыдущими двумя, процедура
долгая. Но авторы и здесь позаботились о нас. Почему? Читайте дальше ;)
Чтобы больше не возвращаться к этому, опишу оставшиеся действия программы. Код шага номер 5 я приводить не буду, его
(правда слегка в измененном виде) вы увидите в брутфорсе. Получение хэша преобразованного ключа в точности
соответствует получению хэша имени (процедура по адресу 0x10003D00). И шаг 7 - собственно само сравнение результатов
и установка флага. Выглядит все это так:
10001BF1 66:3B05 40C10010 CMP AX,WORD PTR DS:[1000C140] ; сравнение с константой
[...]
10001BFA 0F94C1 SETE CL ; установка флага
10001BFD 5D POP EBP
10001BFE 8AC1 MOV AL,CL ; результат (флаг) в al
[...]
10001C02 C3 RETN
И сразу после возвращения из функции проверяются результаты всей проделанной работы:
10002013 3BDE CMP EBX,ESI ; шаг 3 и 4
10002015 A2 84C70010 MOV BYTE PTR DS:[1000C784],AL
1000201A 0F85 2F020000 JNZ CERBERUS.1000224F
10002020 84C0 TEST AL,AL ; шаг 7
10002022 0F84 27020000 JE CERBERUS.1000224F
Привожу код брутфорса, который писался поздней ночью (а точнее ранним утром). Оптимизировать его у меня не было ни
сил, ни желания, поэтому за стиль прошу не кидать в меня тапками. И в данной ситуации это не очень критично, т.к.
нужная нам строка находится практически сразу же.
;---------------------------------------------------------------------------------------------------------------------
.386
.model flat, stdcall
option casemap :none
include c:\masm32\include\windows.inc
include c:\masm32\include\kernel32.inc
include c:\masm32\include\user32.inc
includelib c:\masm32\lib\kernel32.lib
includelib c:\masm32\lib\user32.lib
.data
o_fmt db '%.4X-%.4X-%.4X-%.4X', 0 ; формат ключа для wsprintf
n1 dw 0
n2 dw 0
n3 dw 0
n4 dw 0
buf db 24 dup (0)
; массив из 256 полиномов
tabl dw 0, 0C0C1h, 0C181h, 140h, 0C301h, 3C0h, 280h, 0C241h
dw 0C601h, 6C0h, 780h, 0C741h, 500h, 0C5C1h, 0C481h, 440h
dw 0CC01h, 0CC0h, 0D80h, 0CD41h, 0F00h, 0CFC1h, 0CE81h
dw 0E40h, 0A00h, 0CAC1h, 0CB81h, 0B40h, 0C901h, 9C0h, 880h
dw 0C841h, 0D801h, 18C0h, 1980h, 0D941h, 1B00h, 0DBC1h
dw 0DA81h, 1A40h, 1E00h, 0DEC1h, 0DF81h, 1F40h, 0DD01h
dw 1DC0h, 1C80h, 0DC41h, 1400h, 0D4C1h, 0D581h, 1540h
dw 0D701h, 17C0h, 1680h, 0D641h, 0D201h, 12C0h, 1380h
dw 0D341h, 1100h, 0D1C1h, 0D081h, 1040h, 0F001h, 30C0h
dw 3180h, 0F141h, 3300h, 0F3C1h, 0F281h, 3240h, 3600h
dw 0F6C1h, 0F781h, 3740h, 0F501h, 35C0h, 3480h, 0F441h
dw 3C00h, 0FCC1h, 0FD81h, 3D40h, 0FF01h, 3FC0h, 3E80h
dw 0FE41h, 0FA01h, 3AC0h, 3B80h, 0FB41h, 3900h, 0F9C1h
dw 0F881h, 3840h, 2800h, 0E8C1h, 0E981h, 2940h, 0EB01h
dw 2BC0h, 2A80h, 0EA41h, 0EE01h, 2EC0h, 2F80h, 0EF41h
dw 2D00h, 0EDC1h, 0EC81h, 2C40h, 0E401h, 24C0h, 2580h
dw 0E541h, 2700h, 0E7C1h, 0E681h, 2640h, 2200h, 0E2C1h
dw 0E381h, 2340h, 0E101h, 21C0h, 2080h, 0E041h, 0A001h
dw 60C0h, 6180h, 0A141h, 6300h, 0A3C1h, 0A281h, 6240h
dw 6600h, 0A6C1h, 0A781h, 6740h, 0A501h, 65C0h, 6480h
dw 0A441h, 6C00h, 0ACC1h, 0AD81h, 6D40h, 0AF01h, 6FC0h
dw 6E80h, 0AE41h, 0AA01h, 6AC0h, 6B80h, 0AB41h, 6900h
dw 0A9C1h, 0A881h, 6840h, 7800h, 0B8C1h, 0B981h, 7940h
dw 0BB01h, 7BC0h, 7A80h, 0BA41h, 0BE01h, 7EC0h, 7F80h
dw 0BF41h, 7D00h, 0BDC1h, 0BC81h, 7C40h, 0B401h, 74C0h
dw 7580h, 0B541h, 7700h, 0B7C1h, 0B681h, 7640h, 7200h
dw 0B2C1h, 0B381h, 7340h, 0B101h, 71C0h, 7080h, 0B041h
dw 5000h, 90C1h, 9181h, 5140h, 9301h, 53C0h, 5280h, 9241h
dw 9601h, 56C0h, 5780h, 9741h, 5500h, 95C1h, 9481h, 5440h
dw 9C01h, 5CC0h, 5D80h, 9D41h, 5F00h, 9FC1h, 9E81h, 5E40h
dw 5A00h, 9AC1h, 9B81h, 5B40h, 9901h, 59C0h, 5880h, 9841h
dw 8801h, 48C0h, 4980h, 8941h, 4B00h, 8BC1h, 8A81h, 4A40h
dw 4E00h, 8EC1h, 8F81h, 4F40h, 8D01h, 4DC0h, 4C80h, 8C41h
dw 4400h, 84C1h, 8581h, 4540h, 8701h, 47C0h, 4680h, 8641h
dw 8201h, 42C0h, 4380h, 8341h, 4100h, 81C1h, 8081h, 4040h
.code
start:
movzx eax, n4
push eax
movzx eax, n3
push eax
movzx eax, n2
push eax
movzx eax, n2
push eax
push offset o_fmt
push offset buf
call wsprintf
add esp, 18h
inc n4
.if n4 == 0
inc n3
.if n3 == 0
inc n2
.if n2 == 0
inc n1
.if n1 == 0
jmp _exit
.endif
.endif
.endif
.endif
push 13h
push 0
push offset buf
call CRC16
add esp, 0ch
cmp ax, 0eefh
jz YEAH
jmp start
YEAH:
invoke MessageBox, NULL, offset buf, NULL, 0
_exit:
invoke ExitProcess, 0
; Генерация хэша
CRC16:
push esi
mov esi, [esp+10h]
xor ecx, ecx
test esi, esi
jle loc_10003D3C
mov eax, [esp+0ch]
push ebx
push edi
mov edi, [esp+10h]
loc_10003D15: ; CODE XREF: sub_10003D00+36j
xor edx, edx
mov ebx, eax
mov dl, byte ptr [ecxШ]
and ebx, 0FFh
xor edx, ebx
xor ebx, ebx
mov bl, ah
mov ax, word ptr [edx*2ж]
xor ax, bx
inc ecx
cmp ecx, esi
jl short loc_10003D15
pop edi
pop ebx
pop esi
retn
loc_10003D3C:
mov ax, word ptr [espИ]
pop esi
ret
end start
end
;---------------------------------------------------------------------------------------------------------------------
После старта желанный messagebox появляется через секунду (у меня) со строкой "0000-0000-000B-1049". Ну вот... и
теперь действительно все =) Данную защиту можно считать пройденной и изученной.Дальнейшие похожие вещи не должны будут
останавливать ваше победное шествие. Так. Нужно завязывать, а то что-то меня уже понесло.
Если вы читали внимательно и вникали в суть дела, то написание кейгена не должно представлять каких-либо проблем.
Единственное его отличие от алгоритма защиты - это первая часть ключа. Ее вы должны сформировать сами на основе имени
пользователя. Изменение же второй части в точности похоже на описанное,т.к., как я уже и говорил, функция обратна сама
себе. Вот кусок, выдранный из реального кейгена:
;---------------------------------------------------------------------------------------------------------------------
defname db "dzen",0
bigName db "Your name is very long...", 0
shortName db "Name must be at least 4 characters", 0
_srcSN db "0000-0000-000B-1049", 0
srcSN db 22 dup (0)
szFormat db "%.04X-%s", 0
srcSN_1 equ srcSN
srcSN_2 equ srcSN+5
srcSN_3 equ srcSN+10
srcSN_4 equ srcSN+15
bHZ db 0
tabl dw 0, ...
KeyGen proc hWin: HWND
LOCAL szName[97]:BYTE
LOCAL dwNameLen:DWORD
LOCAL dwNameHash:DWORD
pusha
invoke GetDlgItem, hWin, 101
invoke GetWindowTextLength, eax
.if eax > 96
mov edx, offset bigName
jmp errorC
.elseif eax < 4
mov edx, offset shortName
jmp errorC
.endif
invoke GetDlgItemText, hWin, 101, addr szName, 96
mov dwNameLen, eax
invoke lstrcpy, offset srcSN, offset _srcSN ; восстанавливаем исходные данные, если
; процедура вызывается не в первый раз
push dwNameLen
push 0
lea eax, szName
push eax
call CRC16
add esp, 0ch
movzx ecx, ax
mov dwNameHash, ecx
; нижележащий код в основном рипнут из
; cerberus.dll
mov bx, ax
mov dl, al
xor ecx, ecx
and al, 0Fh
mov cl, ah
mov bHZ, al
and cl, 0fh
shr dl, 4
shr bx, 0Ch
and dl, 0Fh
push ebp
movsx edi, bl
xor esi, esi
movsx ebx, cl
movsx ebp, dl
loc_10001B58: ; CODE XREF: sub_10001AD0+108j
mov cl, byte ptr [esi+srcSN]
push ecx ; в винде тоже есть функция, которая переводит
; символ в число... :) это девелоперам защиты
call ChrToInt
movsx edx, al
xor edx, edi
mov al, [tabl2+edx]
mov [esi+srcSN_1], al ; сохраняем промежуточные значения, чтобы потом
; все склеить
mov cl, [esi·_2]
push ecx
call ChrToInt
movsx edx, al
xor edx, ebx
mov al, [tabl2Ь]
mov [esi·_2], al
mov cl, [esi·_3]
push ecx
call ChrToInt
movsx edx, al
xor edx, ebp
mov al, [tabl2Ь]
mov [esi·_3], al
mov cl, [esi·_4]
push ecx
call ChrToInt
movsx edx, al
movsx eax, bHZ
xor edx, eax
inc esi
mov al, [tabl2Ь]
cmp esi, 4
mov [esi·_4-1], al
jl loc_10001B58
pop ebp
push 13h
push 0
push offset srcSN
call CRC16
add esp, 0ch
movzx eax, ax
mov ebx, dwNameHash
xor ebx, eax ; ксорим хэш имени с хэшем второй части
; ключа (шаг 3)
invoke wsprintf, offset szKey, offset szFormat, ebx, offset srcSN ; склеиваем все вместе
lea edx, szKey
errorC:
invoke SetDlgItemText, hWin, 102, edx
popa
xor eax, eax
ret
CRC16:
push esi
mov esi, [espH]
xor ecx, ecx
test esi, esi
jle loc_10003D3C
mov eax, [espИ]
push ebx
push edi
mov edi, [espH]
loc_10003D15: ; CODE XREF: sub_10003D00+36j
xor edx, edx
mov ebx, eax
mov dl, byte ptr [ecxШ]
and ebx, 0FFh
xor edx, ebx
xor ebx, ebx
mov bl, ah
mov ax, word ptr [edx*2ж]
xor ax, bx
inc ecx
cmp ecx, esi
jl short loc_10003D15
pop edi
pop ebx
pop esi
retn
loc_10003D3C:
mov ax, word ptr [espИ]
pop esi
ret
KeyGen endp
;---------------------------------------------------------------------------------------------------------------------
Такие вот дела. Я бы не сказал, что очень уж сложная защита, но лично мне было интересно в ней копаться. И я все же
настаиваю вам последовать совету, который давал в начале статьи. Поковырять ее самим. Но раз уж вы дочитали до этого
места, то толку от этого будет 0. Хотя кто знает ;)
happy reversing!
ЗЫ. Все версии Cerberus`а хранят правильно введенные регистрационные данные тут: HKEY_CURRENT_USER\Software\Cerberus.
ЗЫ2. big thx to GL#0M & dMNt.
dzen, 03.03.05-05.03.05.