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

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

Информация о лицензии
Как много всего! Тут и лицензии для студентов, и образовательные, и еще какие-то ограниченные по времени. Посмотрим, как и на основании чего они подразделяются:
...
; Если DWORD по адресу B74CE0 равен 92h, то лицензия "NFR"
.text:0049EE82 cmp dword_B74CE0, 92h
.text:0049EE8C jnz loc_49EF16
.text:0049EE92 push 0
.text:0049EE94 lea ecx, [esp+0C8h+var_50]
.text:0049EE98 push offset aNfr ; "NFR"
.text:0049EE9D push ecx
.text:0049EE9E call sub_494220
...
...
; Если DWORD по адресу B74CE0 равен 93h, то лицензия "Student"
.text:0049EF16 cmp dword_B74CE0, 93h
.text:0049EF20 jnz loc_49EFAA
.text:0049EF26 push 0
.text:0049EF28 lea eax, [esp+0C8h+var_50]
.text:0049EF2C push offset aStudent ; "Student"
.text:0049EF31 push eax
.text:0049EF32 call sub_494220
...
На основании этих фрагментов кода мы выяснили, что за тип лицензии отвечает DWORD по адресу
00B74CE0. Посмотрим по перекрестным ссылкам, где же инициализируется это значение.

Перекрестные ссылки на DWORD
Как видите, все изменения значения типа лицензии происходят в процедуре по адресу
004849D0. Она большая, я ее тут не буду приводить. Первая мысль - пропатчить все "неправильные" значения инициализации, например, на "студенческую" лицензию, то есть 93h. Логично, но в данном случае абсолютно неэффективно. Программа в этом случае все равно работает в триальном режиме. Похоже, что если вообще никакого ключа у программы нет, то при проверке типа лицензии срабатывают какие-то иные ветки алгоритма. Надо искать другой путь. Посмотрим еще раз на перекрестные ссылки. Кроме инициализации в коде выполняется куча проверок типа лицензии. Так почему бы нам не заменить одну из проверок на инициализацию типа лицензии нужным нам значением? Тем более, что раз код инициализации уже отработал, то ничего наше предустановленное значение больше не изменит.

Ставим точку останова на доступ к памяти
Поставим в отладчике точку останова на адрес памяти
00B74CE0. Теперь при любом обращении программы к этой ячейке памяти мы об этом сразу же узнаем. Запускаем программу. Первая остановка будет на коде инициализации:
...
.text:004856AC mov ecx, offset dword_B74CF8
.text:004856B1 mov dword_B74CE0, 1
.text:004856BB call ds:??4QString@@QAEAAV0@PBD@Z
...
Пропускаем ее и снова запускаем программу. Вторая остановка гораздо интереснее:
...
.text:0049EBE6 mov ecx, [esi+88h]
.text:0049EBEC lea edx, [esp+0C4h+var_78]
.text:0049EBF0 push edx
.text:0049EBF1 call ds:?setText@QLabel@@QAEXABVQString@@@Z
; Если DWORD по адресу B74CE0 меньше 80h, то лицензии нет или она
; ограничена по времени
.text:0049EBF7 cmp dword_B74CE0, 80h
.text:0049EC01 jl loc_49F1F2
.text:0049EC07 push 0
.text:0049EC09 lea eax, [esp+0C8h+var_68]
.text:0049EC0D push offset aLicense ; "License:"
.text:0049EC12 push eax
.text:0049EC13 call sub_494220
.text:0049EC18 add esp, 0Ch
.text:0049EC1B mov eax, ds:?shared_null@QString@@0UData@1@A
.text:0049EC20 mov [esp+0C4h+var_7C], eax
.text:0049EC24 mov ecx, 1
.text:0049EC29 lock xadd [eax], ecx
; Если байт по адресу B74CE4 не равен 0, то лицензия "Pro"
.text:0049EC2D cmp byte_B74CE4, 0
.text:0049EC34 mov byte ptr [esp+0C4h+var_4], 7Bh
.text:0049EC3C jz loc_49ECC6
.text:0049EC42 push 0
.text:0049EC44 lea edx, [esp+0C8h+var_50]
.text:0049EC48 push offset aPro ; "pro"
.text:0049EC4D push edx
.text:0049EC4E call sub_494220
.text:0049EC53 push offset unk_6D4C50
...
Ну просто подарок на блюдечке. Тут нам и проверка типа лицензии, и проверка на версию "Pro", которая, оказывается, хранится в байте по адресу 00B74CE4. Приступаем к патчу.

Патчим проверки
Заменяем две проверки на команды MOV и нужные нам значения, а чтобы не нарушалась логика работы, также забиваем последующие команды условных переходов на NOP. Сохраняем изменения. Запускаем программу и видим окно с расширенными функциями и греющую душу надпись в правом нижнем углу:

"Студенческая лицензия Pro"
Запускаем, проверяем, все расширенные функции программы работают. Можно на этом остановиться, но есть еще одно маленькое "но". У нас получилась программа, зарегистрированная на именную персональную лицензию, информация о которой будет добавляться в контекстное меню всех создаваемых Flash-роликов, причем эта информация до кучи является ссылкой на сайт программы. Оно вам надо? Вот и мне не надо. Поэтому сейчас будем расширять нашу лицензию до максимально возможной, то есть "unbranded". Никаких прямых упоминаний об этом типе в коде программы нет, поэтому надо подумать и снова призвать на помощь логику. Раз этот пункт меню добавляется в панораму, значит он где-то в программе должен обрабатываться. Поищем что-то подобное в листинге дизассемблера, используя в качестве сигнатуры уже известную нам строчку "Student".
...
.text:004732F1 push offset dword_B74CF8
; jumptable 004731D9 case 3
.text:004732F6 lea eax, [esp+4Ch+var_18]
.text:004732FA push offset aStudentLicense
; "Student License: "
.text:004732FF push eax
.text:00473300 call sub_40AAE0
.text:00473305 add esp, 0Ch
.text:00473308 push eax
.text:00473309 lea ecx, [esp+4Ch+var_2C]
.text:0047330D mov byte ptr [esp+4Ch+var_4], 0Eh
.text:00473312 call ds:??4QString@@QAEAAV0@ABV0@@Z
; QString::operator=(QString const &)
.text:00473318 mov ecx, [esp+48h+var_18]
.text:0047331C mov byte ptr [esp+48h+var_4], 0Ah
.text:00473321 or edx, 0FFFFFFFFh
.text:00473324 lock xadd [ecx], edx
.text:00473328 jnz short loc_473338
.text:0047332A mov eax, [esp+48h+var_18]
.text:0047332E push eax
.text:0047332F call ds:?free@QString@@CAXPAUData@1@@Z
.text:00473335 add esp, 4
.text:00473338 loc_473338:
.text:00473338 push offset aHttpGardengn_5
; "http://gardengnomesoftware.com/pano2vr?"...
.text:0047333D lea ecx, [esp+4Ch+var_28]
.text:00473341 call ds:??4QString@@QAEAAV0@PBD@Z
...
Первое же совпадение и сразу удача. Тут сразу же видны и манипуляции со строкой лицензии, и ссылку на сайт, которая добавляется в контекстное меню, в общем, копать надо здесь. Смотрим на код, с которого выполняется переход:
...
.text:004731C1 mov eax, dword_B74CE0
.text:004731C6 add eax, 0FFFFFF70h
.text:004731CB mov byte ptr [esp+48h+var_4], 0Ah
.text:004731D0 cmp eax, 3 ; switch 4 cases
.text:004731D3 ja loc_473349 ; default
.text:004731D9 jmp ds:off_473610[eax*4] ; switch jump
...
Код короткий, но все-таки требует пояснения. Сперва в регистр EAX записывается значение типа лицензии, затем к нему прибавляется константа 0FFFFFF70h. Если полученный результат больше 3, то выполняется прыжок в обход всех вариантов лицензии (unbranded-версия), иначе в зависимости от полученного значения в меню добавляется информация о лицензии. Теперь чуток математики: 93h + 0FFFFFF70h = 3h. Значит, чтобы получить значение больше 3, надо тип лицензии увеличить как минимум на единицу, то есть в коде инициализации вписывать не 93h, а 94h. Проверим нашу догадку на практике.

Финальный патч
Сохраняем изменения, запускаем программу. Вот теперь действительно полный порядок. У нас получилась unbranded-версия программы со всем доступными опциями режима Pro. Я даже боюсь представить, сколько это стоило бы в евро, если вдруг кто-то захотел бы ее купить.

"Лицензия Unbranded Pro"
В принципе, можно даже выделить сигнатуру для универсального патча, чтобы потом не заморачиваться с обновлениями. Автору спасибо за классную программу и интересную защиту, а всем вам спасибо за внимание и успехов в творчестве.