
Скриншот программы Smart Photo Import
Программа
Smart Photo Import помогает переносить цифровые фотоснимки из камеры или с flash-карточек разного формата. При этом Smart Photo Import автоматически переименовывает файлы на основании EXIF-данных или по установленным пользователем правилам. Также может автоматически раскладывать скопированные файлы по каталогам. Польза от программы сомнительная, тем более от платной версии, но как объект исследования может и пригодится.
Сразу скажу, что программа обновляется пару раз в месяц, поэтому некоторые данные из статьи, такие как адреса кода в исполняемом файле, могут оказаться иными. Впрочем, это касается и любой другой программы. Скачиваем
дистрибутив, устанавливаем, запускаем. После старта незарегистрированной программы сразу появляется триальное окно с таймером и предложением купить или зарегистрировать ее. На ввод заведомо неправильных данных программа отреагирует вот таким сообщением:

Сообщение о неправильной регистрации
Поищем эту строчку. Файл не упакован, и искомая строка найдется в ресурсах под индексом 64798 в десятичной системе счисления или 0FD1Eh в шестнадцатеричной.

Строка в ресурсах
Отправляем файл в дизассемблер, ждем окончания его работы и теперь можно спокойно поискать, где в коде используется значение этого индекса. Шестнадцатеричного, естественно. Найдется типичный указатель на указатель.
CODE:007AAE50 off_7AAE50 dd offset hModule
CODE:007AAE54 dd 0FD1Eh
Если вы ранее сталкивались с реверсом программ на Delphi, то такая конструкция у вас не вызовет никаких недоумений. Достаточно посмотреть на код, который ссылается на первый DWORD конструкции.
...
CODE:007AAEBE call sub_40C22C
CODE:007AAEC3 mov eax, [ebp+var_4]
; Получить длину регистрационного имени
CODE:007AAEC6 call sub_40445C
; Имя не может быть меньше 8 символов
CODE:007AAECB cmp eax, 8
CODE:007AAECE jl loc_7AAF62
; Преобразовать имя в регистрационный ключ
CODE:007AAED4 lea edx, [ebp+var_14]
CODE:007AAED7 mov eax, [ebp+var_4]
CODE:007AAEDA call sub_5DABDC
; Команда "cmpstr" - сравнение двух строк
CODE:007AAEDF mov edx, [ebp+var_14]
CODE:007AAEE2 mov eax, [ebp+var_8]
CODE:007AAEE5 call sub_40456C
; Если строчки не равны, то переход
CODE:007AAEEA jnz short loc_7AAF40
; Сохранить куда-то регистрационные данные
CODE:007AAEEC lea edx, [ebp+var_18]
CODE:007AAEEF mov eax, [ebx+2E0h]
CODE:007AAEF5 call sub_43A4B4
CODE:007AAEFA mov ecx, [ebp+var_18]
CODE:007AAEFD mov edx, offset aUser_1 ; "user"
CODE:007AAF02 mov eax, offset aRegistration_1
; "registration"
CODE:007AAF07 call sub_5DBA48
CODE:007AAF0C mov eax, offset aRegistration_1
; "registration"
CODE:007AAF11 mov ecx, [ebp+var_8]
CODE:007AAF14 mov edx, offset aRegcode_1 ; "regcode"
CODE:007AAF19 call sub_5DBA48
CODE:007AAF1E push 0
CODE:007AAF20 lea edx, [ebp+var_1C]
CODE:007AAF23 mov eax, offset off_7AAE48
CODE:007AAF28 call sub_4076B8
CODE:007AAF2D mov eax, [ebp+var_1C]
CODE:007AAF30 mov cx, word_7AB024
CODE:007AAF37 mov dl, 2
CODE:007AAF39 call sub_460448
CODE:007AAF3E jmp short loc_7AAF82
CODE:007AAF40 ; ---------------------------------------------------
; Вывод сообщения о неправильной регистрации
CODE:007AAF40 loc_7AAF40:
CODE:007AAF40 push 0
CODE:007AAF42 lea edx, [ebp+var_20]
CODE:007AAF45 mov eax, offset off_7AAE50
CODE:007AAF4A call sub_4076B8
CODE:007AAF4F mov eax, [ebp+var_20]
CODE:007AAF52 mov cx, word_7AB024
CODE:007AAF59 mov dl, 2
CODE:007AAF5B call sub_460448
Этот участок кода нагляднее всего пройти под отладчиком. Что мы тут видим? Сперва вызывается функция вычисления длины регистрационного имени. Помните строку сообщения в ресурсах о том, что регистрационное имя не может быть меньше 8 символов? Так вот это оно и есть. Далее вычисляется какая-то строка, очевидно, что на основании регистрационного имени. Ну а потом вызывается функция сравнения двух строчек. Почему я в этом уверен? Обычно, если проверка кустарная и выполняется внутри какой-то функции, то ее результат возвращается в регистре EAX, а затем он сравнивается с нулем или единицей. В нашем случае в функцию передаются указатели на две строки в регистрах EAX и EDX, а условный переход выполняется на основании значения флага нуля ZF. Это типичное поведение функции проверки равенства строк. Перед проверкой регистры указывают на интересные значения:

Ссылки на строчки в регистрах отладчика
EAX указывает на введенный нами заведомо неправильный серийный номер, EDX указывает на строку, которая очень похожа на серийный номер для имени "ManHunter/ PCL". Сохраним значение строчки из регистра EDX, перезапустим программу и попробуем зарегистрировать ее с найденной парой "имя-серийник".

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