Исследование защиты программы PHP LockIt!


Скриншот программы 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! также не составит большого труда.
Теги:
защита, взлом
Добавлено: 20 Сентября 2013 01:45:41 Добавил: Андрей Ковальчук Нравится 0
Добавить
Комментарии:
Нету комментариев для вывода...