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

Сообщение демо-версии
Как пишут сами разработчики программы, ограничение по времени - это единственное различие демонстрационной и полной версии. По истечение отведенного триального срока окно меняется на сообщение, что время вышло, и PHP LockIt! перестает запускаться.

Демонстрационный период закончился
Ну раз других отличий нет, то попробуем убрать ограничение по времени и надоедливые сообщения, превратив тем самым демо-версию в некое подобие коммерческой. Тем более, что в демо-версии не предусмотрено никакой возможности регистрации, а коммерческую версию никто не видел :) Предварительный анализ главного исполняемого файла показывает, что он ничем не упакован, значит просто отдаем его на растерзание дизассемблеру. Параллельно поищем в ресурсах строки или сообщения демонстрационной версии. Найдется два диалога с характерными заголовками:

Триальное окно в ресурсах

Окно окончания демонстрационного периода
Текстовые строки в них заменены заглушками, так как они подставляются потом, перед выводом окон на экран. Видимо авторы задумывали поддержу многоязычности, но так и не реализовали эту функцию. Мы знаем индекс окна со счетчиком триального периода - это число 133 или 85h в шестнадцатеричной системе счисления. Посмотрим в дизассемблере, где он используется. В сегменте кода найдется только одно вхождение:
.text:0040546A push ebx
.text:0040546B push ebp
.text:0040546C push esi
.text:0040546D push edi
.text:0040546E mov esi, ecx
; Вызвать функцию открытия диалогового окна CDialog
.text:00405470 push eax ; На стек хэндл владельца
.text:00405471 push 85h ; На стек идентификатор ресурса
.text:00405476 mov [esp+28h+var_10], esi
.text:0040547A call ??0CDialog@@QAE@IPAVCWnd@@@Z
; CDialog::CDialog(uint,CWnd *)
.text:0040547F lea edi, [esi+5Ch]
.text:00405482 mov [esp+20h+var_4], 0
.text:0040548A mov ecx, edi
.text:0040548C call sub_4214F4
.text:00405491 mov dword ptr [edi], offset off_42CCA4
.text:00405497 mov ecx, off_44215C
.text:0040549D lea edi, [esi+98h]
Дизассемблер показывает, что для открытия диалогового окна вызывается функция CDialog, значит если ее вырезать, то окно не появится. У функции CDialog два параметра, то есть предварительно перед вызовом на стек кладутся два числа. Чтобы корректно удалить это открытие диалога, придется забить NOP'ами не только сам вызов функции по адресу 0040547A, но и две команды PUSH по адресам 00405470 и 00405471. Сохраняем изменения, запускаем. Триальное окно при старте пропало. Едем дальше. После окончания демонстрационного периода появляется уже другое сообщение, но просто удалять его по аналогии с триальным окном нельзя, надо полностью отключить ветку алгоритма, которая получает управление после 30 дней пользования программой. Поищем в дизассемблере условие срабатывания. Диалоговое окно с нужным нам сообщением появляется по адресу:
.text:00405790 sub_405790 proc near
.text:00405790
.text:00405790 var_10 = dword ptr -10h
.text:00405790 var_C = dword ptr -0Ch
.text:00405790 var_4 = dword ptr -4
.text:00405790 arg_0 = dword ptr 4
.text:00405790
.text:00405790 push 0FFFFFFFFh
.text:00405792 push offset SEH_405790
.text:00405797 mov eax, large fs:0
.text:0040579D push eax
.text:0040579E mov large fs:0, esp
.text:004057A5 push ecx
.text:004057A6 mov eax, [esp+10h+arg_0]
.text:004057AA push ebx
.text:004057AB push esi
.text:004057AC push edi
.text:004057AD mov esi, ecx
; Вызвать функцию открытия диалогового окна CDialog
.text:004057AF push eax ; На стек хэндл владельца
.text:004057B0 push 84h ; На стек идентификатор ресурса
.text:004057B5 mov [esp+24h+var_10], esi
.text:004057B9 call ??0CDialog@@QAE@IPAVCWnd@@@Z
; CDialog::CDialog(uint,CWnd *)
.text:004057BE mov ecx, off_44215C
.text:004057C4 lea edi, [esi+5Ch]
.text:004057C7 mov [esp+1Ch+var_4], 0
...
Эта процедура в свою очередь вызывается только из одного места:
.text:00409F27 mov ecx, dword ptr [esp+0BACh+cbData]
.text:00409F2B cmp ecx, eax
; Если одно значение больше другого, то триальный срок окончен
.text:00409F2D jg loc_40A0CC
.text:00409F33 lea edx, [eax-28DE80h]
.text:00409F39 cmp ecx, edx
; Если одно значение меньше другого, то триальный срок окончен
.text:00409F3B jl loc_40A0CC
.text:00409F41 loc_409F41:
; Триальная ветка алгоритма
.text:00409F41 sub eax, ecx
.text:00409F43 lea esi, [edi+0C0h]
.text:00409F49 mov ecx, eax
.text:00409F4B mov eax, 0C22E4507h
.text:00409F50 imul ecx
.text:00409F52 add edx, ecx
.text:00409F54 mov ecx, esi
.text:00409F56 sar edx, 10h
.text:00409F59 mov eax, edx
.text:00409F5B shr eax, 1Fh
; ............
; Часть кода пропущена для экономии места
; ............
.text:0040A0A6 call sub_424911
.text:0040A0AB mov byte ptr [esp+0BACh+var_4], 1Ch
.text:0040A0B3 lea ecx, [esp+0BACh+var_874]
.text:0040A0BA call sub_401590
.text:0040A0BF mov byte ptr [esp+0BACh+var_4], 1Bh
.text:0040A0C7 jmp loc_40A2E0
.text:0040A0CC ; -----------------------------------------------
; Нужная нам ветка алгоритма, в которой показывается сообщение об
; окончании триального периода, после чего программа завершает работу
.text:0040A0CC loc_40A0CC:
.text:0040A0CC push esi
.text:0040A0CD lea ecx, [esp+0BB0h+var_B8C]
; Вызвать функцию открытия диалогового окна
.text:0040A0D1 call sub_405790
.text:0040A0D6 lea ecx, [esp+0BACh+var_B8C]
.text:0040A0DA mov byte ptr [esp+0BACh+var_4], 4
.text:0040A0E2 call sub_420FF7
...
Получается, что если забить NOP'ами два условных перехода по адресам 00409F2D и 00409F3B, то программа будет всегда считать, что ее демонстрационный период не закончился. Сохраняем изменения, переводим часы на год вперед, запускаем. Все работает как надо, у нас получилась почти что коммерческая версия. Почему почти? Потому что мы не заплатили за нее ни копейки :)
Напоследок попробуем разобраться с ограничением по времени. Запустим первоначальный вариант программу под управлением монитора RegMon или
Process Monitor. В любом случае нас интересуют только обращения к реестру. Обращений не так много, в основном это стандартные вызовы. Но в списке присутствует один интересный ключ, к которому фиксируется обращение при старте программы и сразу перед появлением сообщения об истечении триального срока.
[HKEY_LOCAL_MACHINE\SOFTWARE\{A77633C1-E8C8-6A41-12CA-BADFE4FA2760}]
@="0x448ac29f"
Ключ вызывает интерес хотя бы даже потому, что ключей с таким именем в этой ветке реестра быть не должно. Видимо разработчики задумывали спрятать этот ключ где-то в дебрях ветки CLSID, но забыли или не смогли сделать это правильно. Если удалить этот ключ, то триальный срок снова сбрасывается на 30 дней. В принципе, можно обойтись и без вмешательства в исполняемый код, просто периодически удаляя этот ключ из реестра. В этом случае даже демо-версией можно будет пользоваться неограниченное время, если не обращать внимания на надоедливые наг-окна.
Разбирать снятие защиты с самих скриптов я не буду по личным соображениям. При наличии знаний языка PHP и свободного времени вы можете сделать это самостоятельно. Написать автоматический распаковщик для PHP LockIt! также не составит большого труда.