PHP Generic Eval Unpacker В предыдущей статье dx рассказывал о ручной методике снятия типовой и довольно распространенной защиты PHP-скрипта. Если проанализировать наиболее часто встречающиеся типы защиты (например, в разделе запросов на расшифровку на Античате), то можно заметить, что в большинстве случаев защита построена на максимальном сохранении исходного кода скрипта и использовании функции eval в конечном счете. Снимать такую защиту очень просто, но слегка занудно, поэтому я решил написать примитивную программу, которая осуществляет сие действо автоматически. Чтобы пост не был унылым, я кратенько опишу, что из себя представляет анпакер. Итак, из-за своей лени я решил использовать php-cli, расширение для php (которое перехватывает eval) и сделать к этому простой GUI. Результирующая программа выглядит следующим образом: Начнем с расширения. Процесс создания расширения и используемая техника перехвата довольно примитивны и описаны здесь и здесь. Код, описанный в последней ссылке, я слегка изменил под себя. Приведу его одним куском: PHP: #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 приведу частями. Начнем с инклюдов и глобальных переменных: PHP: #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}; Несколько вспомогательных функций: PHP: /* Функция очевидного преобразования */ 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); } И, наконец, основные функции в порядке убывания "важности": PHP: 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: PHP: 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: скачать Исходный код: скачать автор: Kaimi http://kaimi.ru/ http://kaimi.ru/2012/04/php-generic-eval-unpacker/