статейка про dezend

Статус
В этой теме нельзя размещать новые ответы.

someone

сисадмин сервера 0ed
Команда форума
Администратор
Регистрация
3 Апр 2006
Сообщения
554
Реакции
896
  • Автор темы
  • Модер.
  • #1
Кто бы мог подумать что на "хацкере" публикуют не только про взлом пхпбб ;)

В общем после недавнего флема вот малость более конкретной информации.



Zend Guard под хакерским прицелом
Paxan [tPORt] (paxan@tport.be)
Хакер, номер #091, стр. 091-090-1
Для просмотра ссылки Войди или Зарегистрируйся


Исследование и взлом закодированных скриптов

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

Все манипуляции будут рассматриваться для PHP 5.1.4 и Zend Optimizer 3.0.0. Существует несколько альтернатив сторонних разработчиков: Source Guardian, ionCube, phpCipher (их ты можешь найти на нашем DVD). Порождения воспаленного мозга типа SourceCop или CodeLock за защиту можно не считать.

Кодовая защита

Каждый кодированный протектором файл начинается с одной из сигнатур:

1. <?php @Zend;

2. Zend

В первом случае имеется некий заголовок, который будет показан в том случае, если не установлен Zend Optimizer. Во втором же случае все бинарное мясо вывалится в браузер.

Важно знать, что все строки в закодированном сценарии за что-то отвечают. Первая строка - это сигнатура. Вторая - это файловое смещение относительно начала файла в восьмеричной системе счисления.

Далее следуют четыре строковых параметра.

1. Это номер формата или номер Zend API - кому как нравится. Руководствуясь этим номером, Optimizer будет парсить файл, поэтому, если ты правишь запакованный скрипт, всегда возвращай оригинальный номер.

2. Версия интерпретатора, для которого предназначен кодированный файл. Для PHP4 - 1, PHP5 - 2. Если установлен PHP4, а файл для пятерки, то оптимизатор выдаст ошибку.

3. Длина запакованных данных (тех данных, что идут сразу после последнего, четвертого, параметра).

4. Длина распакованных данных.

Теперь нам необходимо распаковать оставшиеся данные. Создатели Zend не стали придумывать свои алгоритмы, а воспользовались deflate со словарем. Данный алгоритм реализован в библиотеке zlib (Для просмотра ссылки Войди или Зарегистрируйся. Этой библиотекой, не долго думая, и воспользовались в Zend. Покопавшись в примерах из zlib, можно быстро написать себе программу для распаковки. Словарь надо взять из самого оптимайзера. Например, для версии PHP 5.1.x он лежит на смещении 0x000503D0 и имеет длину 3296 байт. Также необходимо написать запаковщик. Принцип, алгоритм и словарь точно такие же.

Распаковываем любой, нами же запакованный, скрипт - и <брюки превращаются> во что-нибудь подобное:

Что бросается в глаза на первый взгляд? Это имена используемых функций, переменных, классов, методов класса, строки. Какую же выгоду ты можешь извлечь из этого? Во-первых, есть список используемых функций. Во-вторых, в коде отчетливо видны некоторые строки, которые ты можешь легко изменить, не забывая при этом исправить длину, которая указана перед началом строки. Это можно сотворить с элементами дизайна, встроенными в скрипт, или примитивными защитами наподобие:

if ($_SERVER["HTTP_HOST"] != "mysupersite.ru") exit;

Рассмотрим реальный пример: продукт Для просмотра ссылки Войди или Зарегистрируйся - парсер Excel, который отказывается работать через 15 дней. В нем есть файл license.php. Распаковав его, сразу бросаются в глаза названия типа expiration_date и purchase_date, а также даты, которые можно подправить. Правим на 2030 год, запаковываем - и скрипт больше нас не беспокоит по поводу окончания даты использования.




Это лишний пример для разработчиков как самая неправильная защита. Впрочем, очень часто в платных скриптах есть <стучалка> на сайт разработчика, и скрипт может содержать бэкдор, поэтому надо быть осторожным. Далее мы рассмотрим, что представляет собой лицензирование и за что Zend просит такие дикие деньги.

Лицензирование в Zend

Закодируем простейший скрипт:

<?php

set_time_limit(0);

printf("Script start\n");

$a = 5;

$b = $a + 6;

echo $b;

?>

Распакуем и получим:

Для начала поясню два принципа десериализации:

1. Для строк: сначала идет длина, которая является числом. Полученное число является длиной идущей далее строки (это хорошо видно в самом начале скрипта, так как первым параметром является строка).

2. Для чисел: вначале идет длина строки. Потом само число, которое записано как строка. Для примера: 02 31 00. Длина - 2, Строка - 31 00. Само число - единица.

Первым параметром является имя владельца кодировщика, который кодировал скрипт. В нашем случае кодировщик зарегистрирован на <Zend Encoder trial>. Второй параметр - всегда единица. Если он отличен от единицы, то загрузчик прекращает выполнение. Далее следует параметр, обозначающий, какую версию кодировщика мы использовали: зарегистрированную или нет. В нашем случае - единица, то есть незарегистрированная. Четвертый - всегда единица (назначение его я так и не понял). Пятый параметр отвечает за тип лицензии:

0 - Не требует лицензии (No License Support)

1 - Требует лицензию (Require Valid License)

2 - Поддерживает лицензию (Support License).

Процитирую документацию: <This allows you to generate encoded files that can operate within a limited scope, such as a limited trial version that requires a valid license in order to be fully functional>. А именно функция zend_loader_file_licensed(), с помощью которой мы можем ограничить полную функциональность скрипта при отсутствии лицензии. Шестой параметр отвечает за ограничение по времени на запуск скрипта. Если текущая дата выше указанной, то получим ошибку <Fatal error: This file has expired>. Дата отображена в unix timestamp (количество секунд, прошедших с 1970-го года).

Если параметр равен нулю, то ограничения по времени нет. Рассмотрим пример:

Здесь мы видим число 1159714573, что соответствует 01.10.2006. Значит, данный скрипт перестанет работать после этой даты. Но ничего страшного. Меняем число на 0 (то есть записываем 30 00), правим длину 0B на 02 - и данное ограничение больше нас не беспокоит.

Следующий параметр очень важен. Это - контрольная сумма. По ней проверяются лицензионные данные и регистрационное имя (первый параметр). По ней также генерируется таблица, по которой мы получаем реальный номер опкодов PHP, поэтому с этим параметром надо вести себя осторожно.


Восьмой параметр (визуально неприметен:(

В кодировщике есть параметр Work only with other encoded files. Думаю, название говорит само за себя. Варианты: 00 и 01, то есть <нет> и <да>.

И, наконец, последний параметр - <dummy.addr>. Он не всегда такой, бывает странная строка, что-нибудь типа: <-Tudsb3T$)>. Смысл в нем не совсем ясен. Загрузчик при его чтении выделяет, читает и сразу же освобождает память (скорее всего, что-нибудь для обратной совместимости). Уфф. С параметрами мы разобрались. Теперь самое время приступить к разбору наших имен, регистраций и лицензий.

Типы лицензий:

Первый тип лицензии - это <No License Support>. В нем участвуют всего две сущности: имя (первый параметр) и контрольная сумма:

По ней мы определяем имя владельца кодировщика. По этому имени генерируется контрольная сумма. Функция, используемая для этого, называется adler32. Приведем листинг ниже:

unsigned int adler32_hash(char *data, int len) {

unsigned int i1 = 0, i2 = 0;

for (int i=0;i<len;i++) {

i1 += data;

i2 += i1;

}

i1 %= 65521;

i2 %= 65521;

i2 <<= 16;

return i2 | i1;

}

Вызвав функцию adler32_hash (<Zend Encoder trial>,18), мы на выходе получим число: 1036256941. Если мы в скрипте изменим имя, то при вычислениях оптимайзер получит другую контрольную сумму и сравнит с той, что указана в скрипте. Если они не равны, то прекратит выполнение. А если мы изменим вместе с именем и контрольную сумму, то будет сгенерированная неправильная таблица преобразования опкодов - и оптимайзер упадет.

Второй тип лицензии - это <Require Valid License>. Закодируем наш скрипт с этой опцией. Попробуем запустить и получим: <Warning: License check failed!>. Выполнением на этом прерывается. Распакуем и увидим что-нибудь типа:

Сколько сразу интересного добавилось. Для начала разберем тип лицензии (обозначено желтой полоской). Он равен единице, то есть это - <Require Valid License>. Далее следует блок данных, относящийся к этой лицензии. Опять мы видим имя владельца кодировщика (назовем его regname). Далее следует название продукта (обозначено зеленой линией, назовем его productname), которое мы указываем в параметре -license-product кодировщика. Потом идет первый блок данных, длиной 548 байт (назовем этот блок data1), после него расположен второй блок данных, длиной 808 байт (назовем этот блок data2, заканчивается он красной вертикальной полоской). И далее, как всегда, идет параметр ограничения по времени и контрольная сумма. В данном случае контрольная сумма вычисляется таким образом:

int checkhash = adler32_hash(regname) ^ adler32_hash(productname) ^ adler32_hash(data1) ^ adler32_hash(data2);

То есть в данном случае имеет место операция XOR над всеми контрольными суммами.

Изменим параметр лицензии с <1> на <0> и вырежем все блоки (regname, productname, data1, data2). Однако, запаковав данный скрипт, мы все равно не сможем его запустить, так как контрольная сумма от <Zend Encoder trial> равна 1036256941, а у нас - 457376076. Менять контрольную сумму мы не в силах, зато запросто можем поменять имя. То есть задача сводится к тому, чтобы подобрать набор байт. Обсуждение и решение этой проблемы есть на форуме crackl@b: Для просмотра ссылки Войди или Зарегистрируйся. Найдя необходимые байты и подставив это в скрипт, получим:




Пакуем, пробуем запускать, и видим:

Script start

11

О, чудо! Скрипт запустился! Как видите, вырезание требования лицензии - задача тривиальная и очень легко поддается автоматизации.

И, наконец, третий тип лицензии - это <Support License>. Он служит для ограничения возможностей скрипта при отсутствии лицензии.

Тут совсем все легко. Для примера напишем скрипт:

<?php

$licinfo = zend_loader_file_licensed();

if ($licinfo === FALSE) {

echo "Demo Version";

} else {

echo "Full Version, license data:";

print_r($licinfo);

}

?>

Закодируем c <Support License>, запустим и, естественно, получим <Demo Version>. Распакуем. Сразу бросается в глаза, что контрольная сумма у нас зависит только от имени, то есть adler32_hash(<Zend Encoder trial>,18) = 1036256941.

Поэтому весь лицензионный <мусор> мы можем смело вырезать. А имя функции изменим на zend_loader_file_licen"z"ed.

Пакуем под названием script.php и пишем небольшой сценарий:

<?

function zend_loader_file_licen"z"ed() {

return array("Product-Name" => "MyProduct"); // ну и остальные лицензионные параметры не забываем вернуть

// подробнее, какие параметры должна возвращать

// zend_loader_file_licensed(), можно посмотреть в документации

}

include("script.php");

?>

Запускаем его и видим:

Full Version, license data:Array

(

[Product-Name] => MyProduct

)

Это, конечно, варварский метод, ведь для каждого скрипта придется писать соответствующий над ним скрипт и при разных уровнях оптимизации придется править несколько мест. Но это первое, что пришло мне в голову :).

Хочу отметить, почему нельзя было менять контрольную сумму. Дело в том, что номеров опкодов PHP в скрипте явно нет, но есть смещения, по которым мы получим оригинальные опкоды. Примерно так:

char opcode = table[offset] - (opcodenum & 7)

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

Резюмируя данную главу, отмечу: вся лицензионная защита скрипта обходится буквально за 5 минут. Есть и другой вариант обхода - править сам Optimizer, но это не универсальный вариант, так как на чужом хостинге никто не даст этого сделать.

Получение дампа скрипта

Хочу заметить, что у кодировщика существует несколько уровней оптимизации: full, minimal, none. Напоминаю, что мы остановились на последнем параметре <dummy.addr>. При оптимизации уровня full кодировщик сразу записывает после <dummy.addr> количество функций, для имен которых предварительно рассчитан хэш.



В нашем случае функций всего 2 (в оптимизации уровня minimal и none предварительно рассчитанные хэши для функций отсутствуют). Это - set_time_limit и printf. Хэш для них вычисляется нехитрой функцией из Zend\zend_hash.h исходников PHP. Чтобы понять дальнейшее описание, советую ознакомиться с виртуальной машиной Zend, а в частности, с такими ключевыми структурами, как zend_op_array и zend_opcode. Структура zend_op_array - описатель функции, в которой содержится указатель на массив опкодов zend_opcode. Каждый скрипт начинает выполняться с функции main. Далее в нашем скрипте идут данные, которые заполняют структуру zend_op_array для функции main. Есть два варианта получения дампа:

1. Разобрать полностью Zend Optimizer, посмотреть, какие данные он кладет в какие структуры и т.д. Ну и написать программу парсинга.

2. Вежливо попросить сам оптимайзер сделать это для нас.

Скажу сразу: вначале я пошел по первому пути. Через день я понял, что это задача не для первого класса :). И тогда на ум пришел второй вариант. В движке Zend есть две стадии: компиляция скрипта (парсинг, заполнения всех структур для виртуальной машины) и запуск скрипта. За первое отвечает функция, на которую указывает указатель zend_compile_file, за второе - указатель zend_execute. При старте движка php загружаются все его расширения, в том числе и оптимайзер, который изменяет указатель вместо стандартной функции компиляции на свою.

То есть принцип действия примерно такой:

php_module_startup(&cli_sapi_module, NULL, 0); // инициализируем движок

...

file_handle->handle.fp = VCWD_FOPEN(script_file, "rb"); // открываем файл

file_handle.type = ZEND_HANDLE_FP;

file_handle.opened_path = NULL;

file_handle.free_filename = 0;

...

php_request_startup(TSRMLS_C); // запускаем движок

main_op_array = zend_compile_file(&file_handle, ZEND_EVAL TSRMLS_CC); // вызываем функцию для получения опкодов

...

Все, теперь у нас в памяти есть полностью готовые структуры на выполнение. Для получения текстового дампа можно воспользоваться утилитой Vulcan Logic Disassembler (Для просмотра ссылки Войди или Зарегистрируйся. Есть небольшое <но>: оптимайзер для версии PHP 5.1.x выставляет адреса-обработчики опкодов, при этом затирая номера опкодов.

Чтобы получить нормальный дамп, надо попросить оптимайзер не делать этой глупости. Например, вот так:

0000D26B: C6 90

0000D26C: 44 90

0000D26D: 28 90

0000D26E: 58 90

0000D26F: 00 90

Теперь можно свободно дампить, используя vld:

vld_dump_oparray(new_op_array);

zend_hash_apply (CG(function_table), (apply_func_t) vld_dump_fe TSRMLS_CC);

zend_hash_apply (CG(class_table), (apply_func_t) vld_dump_cle TSRMLS_CC);

Например, для скрипта:

<?php

set_time_limit(0);

printf("Script start\n");

$a = 5;

$b = $a + 6;

echo $b;

?>

закодированного без оптимизации, это будет выглядеть так:

Естественно, vld далек от идеала, и в процессе написания программы восстановления исходного кода он был почти полностью переписан. Восстановить исходный код - это задача скорее на усидчивость. Необходимо разобрать всю виртуальную машину, понять для чего используется каждый опкод. Исходники PHP можно свободно скачать с официального сайта (Для просмотра ссылки Войди или Зарегистрируйся. Например, у меня эта задача заняла около 3-х месяцев неспешной писанины в свободное от учебы и работы время. Сразу хочу сказать, что конструкции типа (INIT_FCALL_BY_NAME, SEND_VAL, DO_FCALL_BY_NAME) очень легко сворачиваются в вызов функции, а (FETCH, ASSIGN) - в присваивании переменной значения. В общем, в получении исходного кода ничего сложного нет.



Вместо заключения

Общая уязвимость протекторов исходного кода PHP такого вида состоит в том, что необходимо всегда передавать массив опкодов в виртуальную машину. Это место всегда можно отловить и сделать дамп/получить исходный код. Что касается самого Zend Optimizer, отмечу, что все читаемые из скрипта данные он принимает за чистую монету. Во время экспериментов очень часто падал оптимайзер или сам php, причем иногда интерпретатор оказывался в совершенно левых участках памяти, в которых он, по сути, находиться не должен. Это еще одно поле для поиска уязвимостей. А если веб-сервер работает с правами рута и стоит mod_php... Но это уже совсем другая история.

CD

На нашем DVD ты найдешь самые популярные протекторы исходного кода PHP, а также их описание.

INFO

Казалось бы, все просто, но Zend вставляет палки в колеса, добавляя в последний Zend Guard обфускатор. Относительно его могу сказать, что он портит имена функций, классов, методов, но оставляет нетронутыми имена переменных внутри функций и классов. Использовать его надо осторожно: на тестовых примерах часть скриптов просто отказалась работать.

WWW

Для просмотра ссылки Войди или Зарегистрируйся

Для просмотра ссылки Войди или Зарегистрируйся

DANGER

Все описанные в этой статье действия носят исключительно исследовательский характер и направлены, в первую очередь, на исследование слабых мест в защите интеллектуальной собственности. Уважайте труд программистов!
 
собственно статья подтверждает то, что я писал уже в каком-то топике - довольно несложно получить набор опкодов, но потом придется по опкодам восстанавливать пхп синтаксис, учитывая что опкодов около 150 штук, то та еще работа. так что всем охотникам за дезендом вперед качать сорцы и чекать виртуальную машину зенда :)
 
4. Длина распакованных данных.

Теперь нам необходимо распаковать оставшиеся данные. Создатели Zend не стали придумывать свои алгоритмы, а воспользовались deflate со словарем. Данный алгоритм реализован в библиотеке zlib (Для просмотра ссылки Войди или Зарегистрируйся. Этой библиотекой, не долго думая, и воспользовались в Zend. Покопавшись в примерах из zlib, можно быстро написать себе программу для распаковки. Словарь надо взять из самого оптимайзера. Например, для версии PHP 5.1.x он лежит на смещении 0x000503D0 и имеет длину 3296 байт. Также необходимо написать запаковщик. Принцип, алгоритм и словарь точно такие же.

Кто то пытался собрать такой распаковщик? Не поделитесь?
 
У меня он есть...
Написан и работает :)
Ну а так никто по статье так ничего и не сделал..

А автор статьи вроде как пропал :)
 
Я хотел бы, но си или делфи для меня далек. А ради этого изучать новый для меня язык это сейчас не посилам. А то что у вас он есть помню где то в фореме писали, я в личку по поводу этого постил. Может заказать чтоб написал кто?
 
Автор жив. Я ему писал - не дал, сказал сам пиши.
Штука интересная, но закодить не смог. Словарь зенда только нашел.
 
Я вообще тему дезенда отслеживаю достаточно давно. Иногда что то интересное всплывает, но быстро вся инфа исчезает. А вообще то что казается дезендера, я так понимаю, каждый сам за себя...
 
хорошая статья.... надо будет поколдовать
 
я на досуге делал прогу, словарь и расбор скрипта, фигня. Весь html и коментарии на выходе + оппкоды, но когда дело дошло до копания в исходниках Zend+PHP, тут мне что-то стало жалко своего времени. Пара тысяч файлов это знаете ...:) . Может позже и доделаю. Но могу сказать что в статье правда и дезенд реален, покрайней мере до 5 php.
 
Alex2006,
ковыряться в каждом файле исходников пхп не надо :)
можно вообще сделать просто:
1) ставим расширение типа

Для просмотра ссылки Войди или Зарегистрируйся

оно позволяет компилить строки с пхп кодом в опкоды

2) пишем простенькие скрипты с основными конструкциями и смотрим генерируемый набор опкодов,
таким образом понимаем назначение каждого опкода и его аргументов (список всех опкодов есть в одном из файлов исходника, найти его элементарно)

3) ну дальше понятно что :)
 
Статус
В этой теме нельзя размещать новые ответы.
Назад
Сверху