Print This Post PHP Generic Eval Unpacker

Четверг, 26. Апрель 2012
Раздел: C/C++, PHP, Windows, автор:

В предыдущей статье dx рассказывал о ручной методике снятия типовой и довольно распространенной защиты PHP-скрипта. Если проанализировать наиболее часто встречающиеся типы защиты (например, в разделе запросов на расшифровку на Античате), то можно заметить, что в большинстве случаев защита построена на максимальном сохранении исходного кода скрипта и использовании функции eval в конечном счете. Снимать такую защиту очень просто, но слегка занудно, поэтому я решил написать примитивную программу, которая осуществляет сие действо автоматически.
Чтобы пост не был унылым, я кратенько опишу, что из себя представляет анпакер. Итак, из-за своей лени я решил использовать php-cli, расширение для php (которое перехватывает eval) и сделать к этому простой GUI. Результирующая программа выглядит следующим образом:



Начнем с расширения. Процесс создания расширения и используемая техника перехвата довольно примитивны и описаны здесь и здесь. Код, описанный в последней ссылке, я слегка изменил под себя. Приведу его одним куском:

#define PHP_WIN32
#define ZEND_WIN32
#define ZTS 1
#define ZEND_DEBUG 0

#pragma comment(lib, "php5ts.lib")

#include "zend_config.w32.h"
#include "php.h"

 
PHP_MINIT_FUNCTION(evalhook);
PHP_MSHUTDOWN_FUNCTION(evalhook);
PHP_MINFO_FUNCTION(evalhook);
 
 
zend_module_entry evalhook_ext_module_entry = {
    STANDARD_MODULE_HEADER,
    "Eval Hook",
    NULL,
    PHP_MINIT(evalhook),
    PHP_MSHUTDOWN(evalhook),
    NULL, NULL, NULL,
    "1.0",
    STANDARD_MODULE_PROPERTIES
};
 
ZEND_GET_MODULE(evalhook_ext);
 
static zend_op_array *(*orig_compile_string)(zval *source_string, char *filename TSRMLS_DC);
static zend_bool evalhook_hooked = 0;
 
static zend_op_array *evalhook_compile_string(zval * input, char *filename TSRMLS_DC)
{
    /* Разделитель */
    const unsigned char delim[] = {0xDE, 0xAD, 0xBE, 0xEF};
 
    if (Z_TYPE_P(input) != IS_STRING)
    {
        return orig_compile_string(input, filename TSRMLS_CC);
    }
 
    /* Записываем содержимое, переданное в eval, в stdout */
    fwrite(input->value.str.val, 1, input->value.str.len, stdout);
    /* Добавляем разделитель, чтобы была возможность разделения кода, относящегося к разным eval'ам */
    fwrite(delim, 1, sizeof(delim), stdout);
 
 
    return orig_compile_string(input, filename TSRMLS_CC);
}
 
/* Функция, вызываемая при загрузке расширения */
PHP_MINIT_FUNCTION(evalhook)
{
    /* Отключаем буферизацию stdout */
    setvbuf(stdout, NULL, _IONBF, 0);
 
    if (evalhook_hooked == 0)
    {
        evalhook_hooked = 1;
        orig_compile_string = zend_compile_string;
        zend_compile_string = evalhook_compile_string;
    }
    return SUCCESS;
}
 
/* Функция, вызываемая при выгрузке расширения */
PHP_MSHUTDOWN_FUNCTION(evalhook)
{
    if (evalhook_hooked == 1)
    {
        evalhook_hooked = 0;
        zend_compile_string = orig_compile_string;
    }
 
    return SUCCESS;
}

Как видно из приведенного выше кода, перехват осуществляется довольно просто, если знать как. Никаких грязных методов, трамплинов и прочей лабуды. Теперь рассмотрим не менее простой GUI к этому делу, который написан на адовой смеси C/C++ и является примером того, как не следует писать программы. Код GUI приведу частями. Начнем с инклюдов и глобальных переменных:

#include <vector>
#include <string>
#include <algorithm>
#include <iterator>
 
#include <Windows.h>
#include <Shlwapi.h>
#include <process.h>
#include <tchar.h>
 
#include "resource.h"
 
#pragma comment (lib, "Shlwapi")
 
/* Хендл основного окна */
HWND ghWnd;
/* Вектор для хранения данных eval'ов */
std::vector<std::wstring> eval_results;
/* Хендлы, используемые для перенаправления stdout дочернего процесса */
HANDLE child_read = NULL, child_write = NULL;
/* Сигнатура для разделения кода, относящегося к разным eval'ам */
const unsigned char signature[] = {0xDE, 0xAD, 0xBE, 0xEF};

Несколько вспомогательных функций:

/* Функция очевидного преобразования */
std::wstring str2wstr(const std::string& s)
{
    std::wstring result;
    size_t len, slength = s.length() + 1;
 
    len = MultiByteToWideChar(CP_ACP, 0, s.c_str(), slength, 0, 0); 
    result.resize(len);
 
    MultiByteToWideChar(CP_ACP, 0, s.c_str(), slength, &result[0], len);
 
    return result;
}
/* Мой любимый Structured Exception Handler */
LONG WINAPI SEH(struct _EXCEPTION_POINTERS *lpTopLevelExceptionFilter)
{
    FatalAppExit(0, TEXT("Необрабатываемое исключение"));
    return 0L;
}
/* Функция для включения/отключения элементов управления на основной форме */
void enable_gui_controls(BOOL enable)
{
    EnableWindow(GetDlgItem(ghWnd, IDC_LIST), enable);
    EnableWindow(GetDlgItem(ghWnd, IDC_UNPACK), enable);
    EnableWindow(GetDlgItem(ghWnd, IDC_CLEAR), enable);
}
/* Функция, отвечающая за диалог выбора файла */
DWORD GetOpenName(TCHAR * outbuf, const TCHAR * filter, const TCHAR * title)
{
    OPENFILENAME ofn = {0};
    TCHAR buf[MAX_PATH + 2];
 
    GetModuleFileName(NULL, buf, MAX_PATH);
 
    TCHAR * tmp = StrRChr(buf, NULL, L'\\');
    if(tmp != 0)
    {
        *tmp = 0;
        ofn.lpstrInitialDir = buf;
    }
 
    ofn.hInstance = GetModuleHandle(NULL);
    ofn.hwndOwner = ghWnd;
    ofn.lStructSize = sizeof(OPENFILENAME);
    ofn.lpstrFilter = filter;
    ofn.nFilterIndex = 1;
    ofn.lpstrFile = outbuf;
    ofn.lpstrFile[0] = 0;
    ofn.lpstrFile[1] = 0;
    ofn.nMaxFile = MAX_PATH;
    ofn.lpstrTitle = title;
    ofn.Flags = OFN_EXPLORER | OFN_DONTADDTORECENT | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY | OFN_LONGNAMES | OFN_NONETWORKBUTTON | OFN_PATHMUSTEXIST;
 
    return GetOpenFileName(&ofn);
}

И, наконец, основные функции в порядке убывания "важности":

unsigned __stdcall process_pipe(void * arg)
{
    BYTE buffer[1024];
    DWORD bytes_read = 0;
    std::wstring temporary;
    std::vector<unsigned char> data;
    std::vector<unsigned char>::iterator begin, end;
    std::vector<std::wstring>::const_iterator it;
 
 
    for (;;) 
    {
        /* Проверяем, есть ли данные в пайпе */
        if(!PeekNamedPipe(child_read, NULL, 0, NULL, &bytes_read, NULL) && bytes_read == 0)
        {
 
            begin = data.begin();
 
            /* Разделяем содержимое data на составляющие, */
            /* попутно занося результаты в глобальный вектор eval_results */
            /* и добавляя перечень в форму */
            while(1)
            {
                end = std::search(begin, data.end(), signature, signature + sizeof(signature));
 
                /* Преобразуем в wide-string для адекватного отображения многобайтовых кодировок */
                temporary = str2wstr(std::string(begin, end));
                eval_results.push_back(temporary);
                /* Добавляем в ListBox урезанных вариант содержимого */
                temporary = temporary.substr(0, 12) + L"...";
                SendDlgItemMessage(ghWnd, IDC_LIST, LB_ADDSTRING, NULL, reinterpret_cast<LPARAM>(temporary.c_str()));
 
                if(end == data.end())
                {
                    break;
                }
                begin = end + sizeof(signature);
            }
            /* Активируем элементы управления на форме */
            enable_gui_controls(TRUE);
 
            break;
        }
 
        /* Читаем данные из пайпа */
        ReadFile(child_read, buffer, min(bytes_read, sizeof(buffer)), &bytes_read, NULL);
        if(bytes_read != 0)
        {
            data.insert(data.end(), buffer, buffer + bytes_read);
        }
    }
 
    CloseHandle(child_read);
 
    return 0;
}

В общем-то основную функцию мы рассмотрели, теперь остался всеми любимый DlgProc и WinMain:

int DlgProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    static HICON ico;
    unsigned int selection_index;
    static TCHAR file_path[MAX_PATH], cmd[MAX_PATH * 2];
    TCHAR * tmp;
 
    STARTUPINFO si;
    PROCESS_INFORMATION pi;
    SECURITY_ATTRIBUTES sa;
 
    ghWnd = hWnd;
 
    switch(uMsg)
    {
        case WM_INITDIALOG:
            /* Устанавливаем иконку для основного окна */
            ico = LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_ICON));
            SendMessage(hWnd, WM_SETICON, ICON_SMALL, (LPARAM)ico);
        break;
 
        case WM_COMMAND:
            switch(LOWORD(wParam))
            {
                case IDC_LIST:
                    switch(HIWORD(wParam))
                    {
                        case LBN_DBLCLK:
                        case LBN_SELCHANGE:
                            selection_index = SendDlgItemMessage(hWnd, IDC_LIST, LB_GETCURSEL, 0, 0);
                            if(selection_index < eval_results.size())
                            {
                                /* Выводим в IDC_DATA содержимое в соответствии с выбранным элементом из IDC_LIST */
                                SetDlgItemText(hWnd, IDC_DATA, eval_results[selection_index].c_str());
                            }
                        break;
                    }
                break;
 
                case IDC_CLEAR:
                    /* Очищаем вектор и сопутствующие элементы на форме */
                    eval_results.clear();
 
                    SetDlgItemText(hWnd, IDC_DATA, L"");
                    SendDlgItemMessage(hWnd, IDC_LIST, LB_RESETCONTENT, 0, 0);
                break;
 
                case IDC_BROWSE:
                    /* Вызываем диалог выбора файла, результат записываем в IDC_PATH */
                    if(GetOpenName(file_path, TEXT("PHP (*.php)\0*.php\0Все файлы (*.*)\0*.*\0\0"), TEXT("Открыть...")))
                    {
                        SetDlgItemText(hWnd, IDC_PATH, file_path);
                    }
                break;
 
                case IDC_UNPACK:
                    if(GetDlgItemText(hWnd, IDC_PATH, file_path, sizeof(file_path)))
                    {
                        /* Деактивируем некоторые элементы интерфейса */
                        /* А то будет ататат из-за потоков */
                        enable_gui_controls(FALSE);
 
                        /* Формируем аргумент командной строки для последующего запуска процесса */
                        _stprintf_s
                        (
                            cmd,
                            sizeof(cmd)/sizeof(cmd[0]),
                            TEXT("-f \"%s\""),
                            file_path
                        );
 
                        memset(&si, 0, sizeof(STARTUPINFO));
                        memset(&pi, 0, sizeof(PROCESS_INFORMATION));
                        memset(&sa, 0, sizeof(SECURITY_ATTRIBUTES));
 
                        /* Включаем наследование дескрипторов дочерним процессом */
                        sa.nLength = sizeof(SECURITY_ATTRIBUTES);
                        sa.bInheritHandle = TRUE;
                        sa.lpSecurityDescriptor = NULL;
                        /* Создаем пайп для перенаправления stdout */
                        CreatePipe(&child_read, &child_write, &sa, 0);
                        SetHandleInformation(child_read, HANDLE_FLAG_INHERIT, 0);
 
                        /* Устанавливаем хендл, куда будет перенаправлен stdout дочернего процесса */
                        /* и флаги для скрытия консольного окна дочернего процесса */
                        si.cb = sizeof(STARTUPINFO);
                        si.wShowWindow = SW_HIDE;
                        si.hStdOutput = child_write;
                        si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
 
                        GetModuleFileName(NULL, file_path, MAX_PATH);
 
                        tmp = StrRChr(file_path, NULL, L'\\');
                        if(tmp != 0)
                        {
                            *tmp = 0;
                        }
                        /* Путь к интерпретатору PHP */
                        _tcscat_s(file_path, MAX_PATH, TEXT("\\php-5.3.3\\php.exe"));
 
                        if
                        (
                            CreateProcess
                            (
                                file_path,
                                cmd,
                                NULL,
                                NULL,
                                TRUE,
                                0,
                                NULL,
                                NULL,
                                &si,
                                &pi
                            ) == NULL
                        )
                        {
                            MessageBox(hWnd, TEXT("Ошибка создания процесса"), TEXT("Ошибка"), MB_OK | MB_ICONERROR);
                            break;
                        }
 
                        /* Закрываем ненужные хендлы */
                        CloseHandle(pi.hProcess);
                        CloseHandle(pi.hThread);
                        CloseHandle(child_write);
 
                        _beginthreadex(NULL, 0, &process_pipe, NULL, 0, NULL);
                    }
                    else
                    {
                        MessageBox(hWnd, TEXT("Укажите путь к файлу"), TEXT("Ошибка"), MB_OK | MB_ICONERROR);
                    }
                break;
            }
        break;
 
        case WM_CLOSE:
            if(child_write)
            {
                CloseHandle(child_write);
            }
 
            DestroyIcon(ico);
            EndDialog(hWnd, 0);
        break;
 
        default:
            return 0;
    }
 
    return 1;
}
 
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    SetUnhandledExceptionFilter(SEH);
 
    DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_MAIN), 0, (DLGPROC) DlgProc, 0);
 
    return 0;
}

Вот и все. Приведенный выше код является ужасно примитивным и нелепым, но позволяет сэкономить немного времени при распаковке очередного PHP-скрипта. Но какие же скрипты может распаковать данная программа? Ну, например, подобные этим:

http://pastebin.com/rLhMLui2

http://pastebin.com/r8m1Vj2b

Обратите внимание на то, что при распаковке скрипт исполняется, а также скрипт может до конца не распаковаться, если в нем существует, например, привязка к домену.

Бинарник с PHP: скачать
Исходный код: скачать

 Обсудить на форуме


Получать обновления на почту:     

Метки: , , , .

Комментариев: 9 к “PHP Generic Eval Unpacker”

  1. спасибо, полезная вещь

    [Ответить]


  2. AsseR :

    Господа, напишите пожалуйста криптософт. Сорцев мало достойных
    К примеру алго PAQ а поверх RSA 4096.
    Криптография довольно инстересная штука, и мало реализаций хороших.
    Архиватор Ваш видел, но там банально: ZLib+AES, а вышепредложенный алгоритм повзрослее чтоли будет.
    С уважением.

    [Ответить]

    dx:

    Да вы параноик, сэр.

    [Ответить]

    AsseR:

    Да, сэр, Вы правы.

    [Ответить]

    Kaimi:

    Гуглишь реализацию PAQ, добавляешь OpenSSL и получается готовая реализация.
    Смысл? Вся практическая сложность сведется к интерфейсу и мелочам.
    С нуля, опираясь на математические описания алгоритмов, писать все это дело - ну, как минимум результат будет медленнее работать, да и косяки могут быть.

    [Ответить]

    AsseR:

    Ок, я понял, что никому из Вас это неинтересно.
    Спасибо за совет.

    [Ответить]

    Kaimi:

    Вопрос скорее в целесообразности и смысле. Лично я не вижу конечного смысла затеи.
    Уникальной криптостойкости не будет, реализация по готовым выкладкам, юзабилити под вопросом...
    Эта задача скорее напоминает то, что обычно просят на форумах в качестве курсовой или диплома, а security through obscurity за счет кастомного формата, хрен знает, например, rar-архив с 32 символьным паролем люди как-то не вскрывают, так почему бы не его...

  3. Привет админ! Не смог найти контакты. Отпишись мне пожалуйста. Хочу разместить рекламу в твоем блоге.

    [Ответить]

    Anon:

    1. Купи очки (или линзы) и сразу же начни пользоваться ими.
    2. Нажми на кнопке "О блоге" под шапкой сайта.
    3. Сделай усилия, и попробуй там отыскать контакты админа.

    [Ответить]


Оставьте ваш комментарий