Издательский дом ООО "Гейм Лэнд"ЖУРНАЛ ХАКЕР #91, ИЮЛЬ 2006 г.

Zend Guard под хакерским прицелом

Paxan [tPORt] (paxan@tport.be)

Хакер, номер #091, стр. 091-090-5


В нашем случае функций всего 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 (http://derickrethans.nl/vld.php). Есть небольшое «но»: оптимайзер для версии 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 можно свободно скачать с официального сайта (http://www.php.net). Например, у меня эта задача заняла около 3-х месяцев неспешной писанины в свободное от учебы и работы время. Сразу хочу сказать, что конструкции типа (INIT_FCALL_BY_NAME, SEND_VAL, DO_FCALL_BY_NAME) очень легко сворачиваются в вызов функции, а (FETCH, ASSIGN) — в присваивании переменной значения. В общем, в получении исходного кода ничего сложного нет.

Назад на стр. 091-090-4  Содержание  Вперед на стр. 091-090-6