
Скриншот программы Visual Watermark
Программа
Visual Watermark предназначена для нанесения текстовых и графических водяных знаков или подписей на цифровые фотографии. Программа очень проста в использовании, имеет простой и дружественный интерфейс, поддерживает все популярные графические форматы, имеет функцию пакетной обработки изображений. Все это можно сделать совершенно бесплатно в любых графических программах, а можно выложить денежку и сделать в программе Visual Watermark. А можно и не выкладывать ничего, и точно так же сделать в программе Visual Watermark. Последний вариант мне нравится больше.
Скачиваем
дистрибутив, устанавливаем, запускаем. Исполняемый файл ничем не упакован, поэтому параллельно открываем его на анализ в дизассемблере. А пока посмотрим, как программа реагирует на неправильную регистрацию:

Сообщение о неправильной регистрации
Нужная строка легко находится в файле:

Строка сообщения найдена
Дальше в дизассемблере по перекрестным ссылкам находим код, где она используется и смотрим условия ее появления:
.text:00457360 sub_457360 proc near
.text:00457360 arg_0 = dword ptr 8
.text:00457360 push ebp
.text:00457361 mov ebp, esp
.text:00457363 mov eax, [ebp+arg_0]
.text:00457366 inc dword ptr [eax+30Ch]
.text:0045736C mov edx, [ebp+arg_0]
.text:0045736F cmp dword ptr [edx+30Ch], 3
.text:00457376 jge short loc_457394
.text:00457378 push 10h ; uType
.text:0045737A push offset aRegistration_1
; "Registration"
.text:0045737F push offset aTheKeyYouEnter
; "The key you entered is not valid. Pleas"...
.text:00457384 mov eax, [ebp+arg_0]
.text:00457387 call @Controls@TWinControl@GetHandle$qqrv
.text:0045738C push eax ; hWnd
.text:0045738D call MessageBoxA
.text:00457392 pop ebp
.text:00457393 retn
Вывод сообщения о неправильной регистрации оформлен в виде отдельной процедуры, которая, в свою очередь, вызывается отсюда:
; Вызвать процедуру проверки
.text:004572B5 call sub_42920C
.text:004572BA pop ecx
; ПоXORить результат функции с константой 35h
.text:004572BB xor eax, 35h
; Сохранить результат в ячейку памяти
.text:004572BE mov [ebp+var_44], eax
.text:004572C1 mov [ebp+var_28], 14h
; Сравнить результат с числом 24h
.text:004572C7 cmp [ebp+var_44], 24h
; Если не равно, то показать сообщение о неправильной регистрации
.text:004572CB jz short loc_4572D8
.text:004572CD push [ebp+var_3C]
; Сообщение о неправильной регистрации
.text:004572D0 call sub_457360
Вызывается процедура проверки по адресу
0042920C, если она вернула значение, которое после XORирования с константой 35h дает значение 24h, то регистрация правильная. Несложной математикой получаем нужное значение - 11h. Сама же функция проверки достаточно громоздкая, но тут нам на помощь приходит замечательный плагин для дизассемблера IDA - Hex-Rays. Он позволяет перевести ассемблерный листинг в C-подобный псевдокод. Скомпилировать его, конечно, не получится, но зато псевдокод в разы облегчает анализ файла. Вот что получается после преобразования функции проверки в псевдокод:
int __cdecl sub_42920C(char a1)
{
__InitExceptBlockLDTC();
System__AnsiString__AnsiString(&a1, &a1);
v12 = 2;
v11 = 8;
v9 = sub_42919C(a1);
if ( sub_429350(&unk_6D71F8, &unk_6E0E38, &v9) > (unsigned int)&unk_6E0E34
|| (v8 = sub_42919C(a1),
v2 = __rwstd____RW_array(&unk_732B08),
v3 = unknown_libname_34(&unk_732B08),
v4 = sub_429350(v3, v2, &v8),
v4 != __rwstd____RW_array(&unk_732B08))
|| System__AnsiString__Length(&a1, v1, v7) != 19
|| *(_BYTE *)System__AnsiString__operator__(&a1, 5) != 45
|| *(_BYTE *)System__AnsiString__operator__(&a1, 10) != 45
|| *(_BYTE *)System__AnsiString__operator__(&a1, 15) != 45 )
v10 = 6681;
else
v10 = 17;
v6 = v10;
--v12;
sub_6C8040(&a1, 2);
return v6;
}
Все как на ладони. Тут и проверка на длину серийника - 19 символов, и проверка на наличие разделителей (символов с кодами 45 в десятичной системе, это символы "-") в позициях 5, 10 и 15. А также еще одна дополнительная проверка, к которой мы вернемся чуть позже. А пока мы выяснили формат и длину серийного номера, это должно быть что-то типа "ABCD-EFGH-JKLM-NOPQ". Теперь нас интересуют два вызова - это функция sub_42919C и sub_429350. Первая представляет собой последовательность вызовов:
.text:0042919C sub_42919C proc near
.text:0042919C push ebp
.text:0042919D mov ebp, esp
.text:0042919F add esp, 0FFFFFFD4h
.text:004291A2 mov eax, offset stru_6E0F50
.text:004291A7 call @__InitExceptBlockLDTC
.text:004291AC mov [ebp+var_8], 1
.text:004291B3 lea edx, [ebp+arg_0]
.text:004291B6 lea eax, [ebp+arg_0]
.text:004291B9 call @System@AnsiString@AnsiString
.text:004291BE inc [ebp+var_8]
.text:004291C1 mov [ebp+var_14], 8
.text:004291C7 lea eax, [ebp+arg_0]
.text:004291CA call @System@AnsiString@c_str$xqqrv
.text:004291CF push eax
.text:004291D0 lea edx, [ebp+var_2C]
.text:004291D3 push edx
.text:004291D4 call sub_4291FC
.text:004291D9 add esp, 8
.text:004291DC push eax
.text:004291DD dec [ebp+var_8]
.text:004291E0 lea eax, [ebp+arg_0]
.text:004291E3 mov edx, 2
.text:004291E8 call sub_6C8040
.text:004291ED pop eax
.text:004291EE mov edx, [ebp+var_24]
.text:004291F1 mov large fs:0, edx
.text:004291F8 mov esp, ebp
.text:004291FA pop ebp
.text:004291FB retn
.text:004291FB sub_42919C endp
;-------------------------------------------------------
.text:004291FC sub_4291FC proc near
.text:004291FC push ebp
.text:004291FD mov ebp, esp
.text:004291FF push [ebp+arg_4]
.text:00429202 call sub_429FC0
.text:00429207 pop ecx
.text:00429208 pop ebp
.text:00429209 retn
.text:00429209 sub_4291FC endp
;-------------------------------------------------------
.text:00429FC0 push ebp
.text:00429FC1 mov ebp, esp
.text:00429FC3 push ecx
.text:00429FC4 xor eax, eax
.text:00429FC6 mov [ebp+var_4], eax
.text:00429FC9 jmp short loc_429FDF
.text:00429FCB ; ---------------------------------
.text:00429FCB loc_429FCB:
.text:00429FCB mov edx, [ebp+var_4]
.text:00429FCE lea edx, [edx+edx*4]
.text:00429FD1 mov ecx, [ebp+arg_0]
.text:00429FD4 movsx eax, byte ptr [ecx]
.text:00429FD7 add edx, eax
.text:00429FD9 mov [ebp+var_4], edx
.text:00429FDC inc [ebp+arg_0]
.text:00429FDF loc_429FDF:
.text:00429FDF mov edx, [ebp+arg_0]
.text:00429FE2 cmp byte ptr [edx], 0
.text:00429FE5 jnz short loc_429FCB
.text:00429FE7 mov eax, [ebp+var_4]
.text:00429FEA pop ecx
.text:00429FEB pop ebp
.text:00429FEC retn
При помощи того же плагина Hex-Rays этот код сворачивается в абсолютно понятный псевдокод:
int __cdecl sub_429FC0(int a1)
{
int v2; // [sp+0h] [bp-4h]@1
v2 = 0;
while ( *(_BYTE *)a1 )
v2 = *(_BYTE *)a1++ + 5 * v2;
return v2;
}
Под отладчиком эта процедура становится еще понятнее, в ней считается некая контрольная сумма от всего серийного номера, включая разделители. Алгоритм заключается в последовательном сложении и умножении на 5 всех символов строки серийного номера. На выходе получаем контрольное значение. Вторая функция - это проверка контрольного значения. Я приведу только ее часть, остальное лучше смотреть под отладчиком.
.text:00429350 sub_429350 proc near
.text:00429350 push ebp
.text:00429351 mov ebp, esp
.text:00429353 add esp, 0FFFFFFF8h
.text:00429356 lea eax, [ebp+var_8]
.text:00429359 push eax
.text:0042935A push [ebp+arg_8]
.text:0042935D push [ebp+arg_4]
.text:00429360 push [ebp+arg_0]
.text:00429363 call sub_429370
.text:00429368 add esp, 10h
.text:0042936B pop ecx
.text:0042936C pop ecx
.text:0042936D pop ebp
.text:0042936E retn
.text:0042936E sub_429350 endp
;-------------------------------------------------------
.text:00429370 sub_429370 proc near
.text:00429370 push ebp
.text:00429371 mov ebp, esp
.text:00429373 push ecx
.text:00429374 mov eax, [ebp+arg_4]
.text:00429377 sub eax, [ebp+arg_0]
.text:0042937A test eax, eax
.text:0042937C jns short loc_429381
.text:0042937E add eax, 3
.text:00429381 loc_429381:
.text:00429381 sar eax, 2
.text:00429384 sar eax, 2
.text:00429387 mov [ebp+var_4], eax
.text:0042938A cmp [ebp+var_4], 0
.text:0042938E jle short loc_4293F1
.text:00429390 loc_429390:
.text:00429390 mov edx, [ebp+arg_0]
.text:00429393 mov ecx, [edx]
.text:00429395 mov eax, [ebp+arg_8]
.text:00429398 cmp ecx, [eax]
.text:0042939A jnz short loc_4293A2
.text:0042939C mov eax, [ebp+arg_0]
.text:0042939F pop ecx
.text:004293A0 pop ebp
.text:004293A1 retn
.text:004293A2 ; ------------------------------------------
.text:004293A2 loc_4293A2:
.text:004293A2 add [ebp+arg_0], 4
.text:004293A6 mov edx, [ebp+arg_0]
.text:004293A9 mov ecx, [edx]
.text:004293AB mov eax, [ebp+arg_8]
.text:004293AE cmp ecx, [eax]
.text:004293B0 jnz short loc_4293B8
.text:004293B2 mov eax, [ebp+arg_0]
.text:004293B5 pop ecx
.text:004293B6 pop ebp
.text:004293B7 retn
.text:004293B8 ; ----------------------------------------------
.text:004293B8 loc_4293B8:
.text:004293B8 add [ebp+arg_0], 4
.text:004293BC mov edx, [ebp+arg_0]
.text:004293BF mov ecx, [edx]
.text:004293C1 mov eax, [ebp+arg_8]
.text:004293C4 cmp ecx, [eax]
.text:004293C6 jnz short loc_4293CE
.text:004293C8 mov eax, [ebp+arg_0]
.text:004293CB pop ecx
.text:004293CC pop ebp
.text:004293CD retn
...
В листинге код не очень понятен, а вот отладчик вносит ясность. В программе по адресу 006D71F8 содержится массив заранее определенных проверочных значений для серийных номеров в формате DWORD размером 10000 элементов:
.data:006D71F8 dword_6D71F8 dd 0C6DDDh
.data:006D71FC dd 1267C1h
.data:006D7200 dd 14EC36h
.data:006D7204 dd 15BFAFh
.data:006D7208 dd 1F2B5Bh
.data:006D720C dd 259E5Ch
.data:006D7210 dd 3F92A6h
.data:006D7214 dd 47A33Eh
...
Если рассчитанное проверочное значение совпадает с одним из значений их массива, то программа считается зарегистрированной. То есть какого-либо особого алгоритма подсчета серийного номера нет, все возможные значения заранее определены. Итак, мы знаем формат серийного номера, алгоритм подсчета контрольного значения и массив из 10000 проверочных значений. Кейген пишется на базе простейшего брутфорса.

Программа успешно зарегистрирована
Защиты такого типа, где серийный номер сравнивается с заранее подготовленным списком, встречаются не очень часто, но все-таки авторы упорно продолжают их использовать. И хорошо, если для хеширования используется что-то типа MD5 или более серьезные алгоритмы, а что если при слабом алгоритме хеширования ключ из списка будет подобран брутфорсом или реверсом алгоритма из исходного значения? Автор, по идее, должен забанить такие ключи сразу после релиза. Что же в этом случае делать легальным пользователям? Вопрос остается без ответа.