
Скриншот программы STOIK Panorama Maker
STOIK Panorama Maker - очень удобная программа для создания панорамных снимков из нескольких отдельных кадров, причем можно делать как горизонтальные, так и вертикальные панорамы. Все операции выполняются буквально за несколько кликов мышкой, а результат получается потрясающий. Более простой и удобной программы для создания панорам я пока не встречал. А любой хороший софт просто обязан быть (или стать) бесплатным.
Скачиваем
дистрибутив, устанавливаем, запускаем. Сразу же после запуска программа предлагает нам резко метнуться в кассу, ввести регистрационный ключ или продолжить работу в триальном режиме.

Окно регистрации программы
Аффтары пишут, что программа в течение 15-дневного триального срока полнофункциональная. На самом деле это не так. В бесплатном режиме результат невозможно сохранить, и, как вы уже догадались, сейчас мы будем обходить все эти ограничения. На ввод неправильных регистрационных данных программа реагирует сообщением
"Wrong activation key!", поищем его в исполняемом файле.

Строка сообщения в ресурсах
Строка сообщения в ресурсах файла имеет индекс 346 или
15Ah в шестнадцатеричной системе счисления. С похожими ситуациями мы уже неоднократно сталкивались, поэтому как и в прошлые разы поищем это значение в листинге дизассемблера.
.text:004AD136 mov ecx, esp
.text:004AD138 mov [ebp+108h+var_120], esp
.text:004AD13B push edx
.text:004AD13C call ds:mfc90_300
.text:004AD142 lea eax, [ebp+108h+var_120]
.text:004AD145 push eax
; Вызвать функцию проверки
.text:004AD146 call loc_4A1D80
.text:004AD14B add esp, 8
; По умолчанию будем считать, что программа не зарегистрирована (BL=1)
.text:004AD14E mov ebx, 1
.text:004AD153 push offset aOk ; "Ok"
.text:004AD158 mov ecx, eax
.text:004AD15A mov byte ptr [ebp+108h+var_10C], bl
.text:004AD15D mov [ebp+108h+var_134], ebx
; Сравнить результат функции проверки со строкой 'Ok'
.text:004AD160 call ds:mfc90_1603
.text:004AD166 test eax, eax
.text:004AD168 jnz short loc_4AD188
; Если совпадает, то программа действительно зарегистрирована
.text:004AD16A push ecx
.text:004AD16B lea edx, [ebp+108h+var_11C]
.text:004AD16E mov ecx, esp
.text:004AD170 mov [ebp+108h+var_12C], esp
.text:004AD173 push edx
.text:004AD174 call ds:mfc90_300
.text:004AD17A call sub_4ACDC0
.text:004AD17F add esp, 4
.text:004AD182 cmp eax, esi
.text:004AD184 jnz short loc_4AD188
; После успешного прохождения всех проверок обнулить регистр BL
.text:004AD186 xor bl, bl
.text:004AD188 loc_4AD188:
.text:004AD188 lea ecx, [ebp+108h+var_120] ; void *
.text:004AD18B mov [ebp+108h+var_10C], esi
.text:004AD18E call ds:mfc90_601
; Программа зарегистрирована (BL=0) ?
.text:004AD194 test bl, bl
.text:004AD196 jz short loc_4AD1AA
.text:004AD198 loc_4AD198:
; Вывести сообщение "Wrong activation key!"
.text:004AD198 push 0FFFFFFFFh
.text:004AD19A push esi
.text:004AD19B push 15Ah
.text:004AD1A0 call mfc90_1182
.text:004AD1A5 jmp loc_4AD2DA
Теперь посмотрим на вызываемую функцию проверки. Мы помним, что она должна вернуть строчку "Ok", поэтому поищем все, что так или иначе связано с этой строкой. Найдется вот такой кусочек кода:
; Выполнить сравнение
.text:004A1EAE cmp edx, eax
; Если не равно, то переход на возврат значения "Error"
.text:004A1EB0 jnz short loc_4A1F15
.text:004A1EB2 inc ecx
.text:004A1EB3 cmp ecx, 6
.text:004A1EB6 jl short loc_4A1E73
.text:004A1EB8 add ebp, 7
.text:004A1EBB cmp ebp, 1Ch
.text:004A1EBE jl short loc_4A1E6D
; Вернуть значение "Ok"
.text:004A1EC0 mov esi, [esp+18h]
.text:004A1EC4 push offset aOk ; "Ok"
.text:004A1EC9 mov ecx, esi
.text:004A1ECB call ds:mfc90_310
...
; Часть второстепенного кода опущена
...
.text:004A1F15 loc_4A1F15:
.text:004A1F15 mov esi, [esp+18h]
; Вернуть значение "Error"
.text:004A1F19 push offset aError_0 ; "Error"
.text:004A1F1E mov ecx, esi
...
Простыми патчами на возврат EAX=1 тут не обойтись, но достаточно забить командами NOP единственный условный переход по адресу
004A1EB0, ведущий к возврату ошибки. После этого программа примет любой регистрационный ключ как родной. Код около места патча достаточно "приметный", так что можно легко выделить сигнатуру для поиска и сделать универсальный патч для всех последующих версий программы.
При анализе ассемблерного листинга в районе вызова проверок серийного номера я несколько раз натыкался на файл под названием "stpnm2.key". Поискав на диске этот файл я обнаружил, что он находится в папке %ProgramData%\stpnm2.key и в него в текстовом виде записывается введенный регистрационный ключ плюс первым байтом длину ключа. То есть мало того, что программа гадит в реестре, так еще и распихивает свои файлы куда ни попадя. Причем при деинсталляции файл ключа не удаляется. В который раз убеждаюсь, что хорошие манеры программирования сейчас большая редкость. Но может быть эта информация будет полезной для любителей делать портативные сборки разного софта.
С патчами все понятно, теперь попробуем разобрать алгоритм проверки и, соответственно, алгоритм генерации правильного серийного номера. Для этого поставим точку останова на начало уже знакомой нам функции проверки по адресу 004A1D80 и пройдем ее под отладчиком, введя какой-нибудь неправильный серийный номер. Еще в листинге дизассемблера бросается в глаза интересная конструкция, в которой из строки получаются значения по определенной маске.
...
.text:004A1E03 push edx
.text:004A1E04 push offset a6c6c6c6c6c6c6c
; "%6c-%6c-%6c-%6c-%6c-%6c-%6c-%6c"
.text:004A1E09 lea ecx, [esp+0A4h]
.text:004A1E10 call ds:mfc90_910
.text:004A1E16 push eax
.text:004A1E17 call ds:sscanf
...
Теперь мы знаем формат серийного номера - 8 групп по 6 символов, разделенных черточками. Что интересно, полученные после этой операции данные нигде не используются и даже не проверяются на валидность. Ладно, зная формат, придумаем какой-нибудь серийный номер заведомо неправильный, но подходящий по формату, например, "AAAAAA-BBBBBB-CCCCCC-DDDDDD-EEEEEE-FFFFFF-GGGGGG-HHHHHH". Стартуем программу под отладчиком, вводим его в окно регистрации и в пошаговом режиме доходим до уже знакомого кода, но на этот раз уже с целью добиться понимания алгоритма проверки:
...
; Взять следующий символ из серийного номера
.text:004A1E73 movsx eax, byte ptr [esi+ecx]
; Умножить на 343FDh
.text:004A1E77 imul eax, 343FDh
; Прибавить 269EC3h
.text:004A1E7D add eax, 269EC3h
.text:004A1E82 mov dword_504F84, eax
; Двоичный сдвиг
.text:004A1E87 shr eax, 10h
; Сброс всех битов кроме первых 8
.text:004A1E8A and eax, 7FFFh
; Обнулиить EDX
.text:004A1E8F cdq
; Поделить на 24h (значение видно под отладчиком)
.text:004A1E90 idiv edi
; Если результат вышел за некие границы, то серийник неправильный
.text:004A1E92 test edx, edx
.text:004A1E94 jl loc_4A1F35
.text:004A1E9A cmp edx, edi
.text:004A1E9C jge loc_4A1F35
; Взять символ из проверочного массива и сравнить его с символом из серийника
; Проверочный массив - строка "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
; Индекс элемента - значение регистра EDX
.text:004A1EA2 movzx edx, byte ptr [ebx+edx]
.text:004A1EA6 lea eax, [ecx+ebp]
.text:004A1EA9 movsx eax, byte ptr [esp+eax+4Ch]
.text:004A1EAE cmp edx, eax
; Не совпало - серийный номер неправильный
.text:004A1EB0 jnz short loc_4A1F15
; Следующий символ
.text:004A1EB2 inc ecx
.text:004A1EB3 cmp ecx, 6
; Группа символов обработана?
.text:004A1EB6 jl short loc_4A1E73
; Следующая группа
.text:004A1EB8 add ebp, 7
.text:004A1EBB cmp ebp, 1Ch
.text:004A1EBE jl short loc_4A1E6D
; Все символы соответствуют, серийный номер правильный
.text:004A1EC0 mov esi, [esp+18h]
.text:004A1EC4 push offset aOk ; "Ok"
.text:004A1EC9 mov ecx, esi
...
Что получается? Серийный номер условно разделяется на две половины по 4 группы символов. При этом каждый символ из первой половины преобразуется в определенное значение (индекс элемента массива) по описанному выше алгоритму. Затем по этому индексу берется символ (элемент) из проверочного массива "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" и он сравнивается с соответствующим символом из той же позиции второй половины серийника. В нашем примере для латинской буквы "A" из первой половины серийника в проверочном массиве будет соответствовать символ "8", это хорошо видно в отладчике. Контрольное значение "8" сравнивается с введенным символом "E" из второй половины серийника:

Сравнение символа со значением из массива
Формула получения индекса в более привычной записи будет выглядеть примерно так:
index = ((serial[i] * 343FDh + 269EC3h) >> 10h) & 7FFFh) mod 24h
Выяснив одно единственное совпадение, можно сразу же узнать один из валидных серийный номеров, в котором будет первая и вторая половина будут состоять из одинаковых символов. Например, правильным серийным номером для регистрации будет
"AAAAAA-AAAAAA-AAAAAA-AAAAAA-888888-888888-888888-888888". Попробуем зарегистрировать программу с этим ключом. Никаких сообщений о правильной регистрации не появится, однако надпись "TRIAL" в заголовке программы пропадет. Если теперь попробовать сделать панораму и сохранить результат, то и тут никаких ограничений не будет. Окно регистрации при запуске программы также пропало. То есть регистрационный ключ подобран правильно. Рабочий кейген теперь вы можете написать самостоятельно. Подобным образом обходится защита и других программ от STOIK. Новых побед вам в реверсинге и творческих успехов в фототворчестве!