
Скриншот программы ExifCleaner
ExifCleaner - программа для удаления
EXIF-данных из цифровых фотографий и других графических файлов. Это позволяет уменьшить размер файлов при пересылке их по электронной почте или при публикации на сайтах. Существует немало бесплатных аналогов ExifCleaner, так что ограничивать себя в выборе инструмента для этих целей не придется. А вот скоротать часок-другой, ковыряясь в коде, будет интересно.
Скачиваем
дистрибутив, устанавливаем, смотрим. Главный исполняемый файл упакован UPX, который снимается им же самим через
upx -d. Тут никаких сюрпризов от автора нет. Запускаем распакованный файл и видим наг-окно с предложением отстегнуть бабла или ввести код регистрации. На ввод левых данных программа реагирует достаточно интересно:

Окно регистрации программы
То есть регистрационное имя должно содержать строчку типа "
(Unlimited License)", "(Single User License)" или что-то около того. Теперь, если в регистрационное имя добавить строку "License", например, "ManHunter (License to Kill)", то сообщение об ошибке изменится на "
The Licensee and Registration Code strings are not valid. Please verify that you inputted them correctly" (орфография автора сохранена). Вот так, не открывая ни отладчика, ни дизассемблера, только благодаря подсказке от разработчика, мы знаем формат регистрационного имени. Спасибо, чо.

Строка сообщения в ресурсах
Строка сообщения находится в ресурсах под номером 65205 или 0FEB5h в шестнадцатеричной системе счисления. Поищем эту строчку в листинге дизассемблера. Стандартный дельфийский бутерброд из последовательных указателей:
.text:004E8354 off_4E8354 dd offset hModule
.text:004E8358 dd 0FEB5h
.data:00656A8C off_656A8C dd offset off_4E8354
Третьей перекрестной ссылкой попадаем на код вывода на экран искомого сообщения об ошибке
.text:0061CD8D push 30h ; uType
.text:0061CD8F lea edx, [ebp+var_2C]
.text:0061CD92 mov eax, off_656AD8
.text:0061CD97 call @System@LoadResString
; System::LoadResString(System::TResStringRec *)
.text:0061CD9C mov eax, [ebp+var_2C]
.text:0061CD9F call @System@@LStrToPChar
; System::__linkproc__ LStrToPChar(System::AnsiString)
.text:0061CDA4 push eax ; lpCaption
.text:0061CDA5 lea edx, [ebp+var_30]
; Загрузить строку сообщения из ресурсов
.text:0061CDA8 mov eax, off_656A8C
.text:0061CDAD call @System@LoadResString
; System::LoadResString(System::TResStringRec *)
.text:0061CDB2 mov eax, [ebp+var_30]
.text:0061CDB5 call @System@@LStrToPChar
; System::__linkproc__ LStrToPChar(System::AnsiString)
.text:0061CDBA push eax ; lpText
.text:0061CDBB push ebx ; hWnd
; Вывести сообщение на экран
.text:0061CDBC call MessageBoxA
Чуть выше находится код, с которого выполняется условный переход на сообщение об ошибке:
; Вызвать какую-то функцию проверки
.text:0061CD4A call sub_61C38C
; Если она вернула EAX!=9, то вывести сообщение об ошибке
.text:0061CD4F cmp eax, 9
.text:0061CD52 jnz short loc_61CD8D
; Иначе поблагодарить за регистрацию
Сперва некрасивый, но эффективный вариант решения. В начало функции проверки по адресу 0061C38C записываем команды MOV EAX,9 / RET и сохраняем изменения. После этого программа работает вообще без регистрации. Все настройки программы хранятся в файле %APPDATA%\SuperUtils.com\ExifCleaner\ExifClnr.ini и если его перенести в папку с установленной программой, то очень похоже, что она становится портативной. Также, если сперва ввести корректное с точки зрения программы регистрационное имя и неправильный серийный номер, то он все равно будет сохранен в этом файле. Поэтому если вы хотите наблюдать свое имя в окне "О программе", то сперва "зарегистрируйте" ее, а потом пропатчьте. Или сперва пропатчьте, а потом добавьте в конец файла ExifClnr.ini строчки типа:
Licensee=ManHunter (License to Kill)
RegCode=123123123123
Теперь разберем алгоритм проверки серийного номера. Адрес функции проверки мы знаем, это 0061C38C, пройдем его под отладчиком в пошаговом режиме. Параллельно с этим я буду комментировать процесс.
; Часть кода пропущена для удобства восприятия
...
.text:0061C3C3 push 0
.text:0061C3C5 lea eax, [ebp+var_4]
.text:0061C3C8 push eax
; Прочитать из ini-файла регистрационный номер и имя
.text:0061C3C9 mov ecx, offset _str_Licensee.Text
.text:0061C3CE mov edx, offset _str_Preferences_5.Text
.text:0061C3D3 mov eax, ebx
.text:0061C3D5 mov esi, [eax]
.text:0061C3D7 call dword ptr [esi]
.text:0061C3D9 push 0
.text:0061C3DB lea eax, [ebp+var_8]
.text:0061C3DE push eax
.text:0061C3DF mov edx, offset _str_Preferences_5.Text
.text:0061C3E4 mov ecx, offset _str_RegCode.Text
.text:0061C3E9 mov eax, ebx
.text:0061C3EB mov esi, [eax]
.text:0061C3ED call dword ptr [esi]
.text:0061C3EF mov eax, ebx
.text:0061C3F1 call @System@TObject@Free$qqrv
.text:0061C3F6 lea edx, [ebp+var_C]
; Указатель на строку регистрационного имени
.text:0061C3F9 mov eax, [ebp+var_4]
; Посчитать хеш MD5 от строки регистрационного имени
.text:0061C3FC call sub_61C250
.text:0061C401 xor eax, eax
.text:0061C403 mov [ebp+var_10], eax
.text:0061C406 mov eax, [ebp+var_8]
.text:0061C409 test eax, eax
; Длина серийного номера
.text:0061C40B jz short loc_61C412
.text:0061C40D sub eax, 4
.text:0061C410 mov eax, [eax]
.text:0061C412 loc_61C412:
; Первая проверка - длина должна быть 12 символов
.text:0061C412 cmp eax, 0Ch
.text:0061C415 jnz short loc_61C48F
.text:0061C417 push ebp
; Проверка хеша регистрационного имени на его нахождение в "черном списке"
.text:0061C418 call sub_61C2A0
.text:0061C41D pop ecx
.text:0061C41E test al, al
.text:0061C420 jnz short loc_61C48F
Первый этап проверки. Из ini-файла читаются серийный номер и регистрационное имя, после чего от имени считается хеш и переводится в верхний регистр. Что это за хеш, нам любезно подскажет KANAL (плагин для анализатора PEiD):

Результат проверки криптоанализатора
Это самый обычный MD5. На этом же этапе проверяется длина серийного номера (она должна быть 12 символов, 3 группы по 4 символа без учета разделителей) и наличие регистрационного имени во внутреннем "черном списке". Дальше идут два цикла проверки:
; Первый цикл проверки
.text:0061C422 mov edx, 1
.text:0061C427 mov eax, offset unk_6567C0
.text:0061C42C loc_61C42C:
.text:0061C42C mov ecx, [eax]
.text:0061C42E test ecx, ecx
.text:0061C430 jz short loc_61C446
.text:0061C432 mov ebx, [ebp+var_8]
.text:0061C435 movzx ebx, byte ptr [ebx+edx-1]
.text:0061C43A mov esi, [ebp+var_C]
; Сравнить символ серийника из BL с символом из хеша
.text:0061C43D cmp bl, [esi+ecx-1]
.text:0061C441 jnz short loc_61C446
.text:0061C443 inc [ebp+var_10]
.text:0061C446 loc_61C446:
.text:0061C446 inc edx
.text:0061C447 add eax, 4
.text:0061C44A cmp edx, 6
.text:0061C44D jnz short loc_61C42C
...
; Второй цикл проверки
.text:0061C463 mov edx, 6
.text:0061C468 mov eax, offset unk_6567D4
.text:0061C46D loc_61C46D:
.text:0061C46D cmp dword ptr [eax], 0
.text:0061C470 jz short loc_61C488
.text:0061C472 mov ebx, [ebp+var_8]
.text:0061C475 movzx ebx, byte ptr [ebx+edx-1]
.text:0061C47A mov esi, [ebp+var_C]
.text:0061C47D mov edi, [eax]
; Сравнить символ серийника из BL с символом из хеша
.text:0061C47F cmp bl, [esi+edi-1]
.text:0061C483 jnz short loc_61C488
; Увеличить счетчик совпавших символов
.text:0061C485 inc [ebp+var_10]
.text:0061C488 loc_61C488:
.text:0061C488 inc edx
.text:0061C489 add eax, 4
.text:0061C48C dec ecx
.text:0061C48D jnz short loc_61C46D
...
; Записать в EAX количество совпавших символов
.text:0061C4B1 mov eax, [ebp+var_10]
.text:0061C4B4 pop edi
.text:0061C4B5 pop esi
.text:0061C4B6 pop ebx
.text:0061C4B7 mov esp, ebp
.text:0061C4B9 pop ebp
.text:0061C4BA retn
В циклах проверки делается следующее. Сперва обнуляется некий счетчик, затем в каждом цикле последовательно проверяется совпадение символов из серийного номера и хеша. Если совпадают, то счетчик увеличивается. На выходе функция возвращает количество совпавших символов. Если помните, то оно сравнивается с 9, то есть из 12 символов серийного номера значение имеют только 9, а остальные могут быть любыми. Осталось выяснить какие символы проверяются, это легко сделать под отладчиком, места сравнений каждого цикла я прокомментировал. В итоге получается такая таблица соответствия (нумерация символов в обеих строках начинается с 0):
serial[00h] = md5[03h]
serial[01h] = md5[06h]
serial[02h] = md5[01h]
serial[03h] = любой
serial[04h] = md5[0Dh]
serial[05h] = md5[12h]
serial[06h] = md5[02h]
serial[07h] = md5[19h]
serial[08h] = любой
serial[09h] = любой
serial[0Ah] = md5[18h]
serial[0Bh] = md5[06h]
Так, для строки хеша "C2DF1E7DF12519AB4DF0D7C8D7183DCD", то есть для MD5 от строки "ManHunter (License to Kill)", по приведенной выше таблице соответствия серийный номер будет "F72P 9FD7 CLD7". Попробуем с его помощью зарегистрировать программу:

Программа успешно зарегистрирована
Регистрация прошла успешно, все ограничения сняты. Алгоритм оказался несложным, и рабочий кейген теперь вы можете написать самостоятельно.