В предыдущей статье dx рассказывал о ручной методике снятия типовой и довольно распространенной защиты PHP-скрипта. Если проанализировать наиболее часто встречающиеся типы защиты (например, в разделе запросов на расшифровку на Античате), то можно заметить, что в большинстве случаев защита построена на максимальном сохранении исходного кода скрипта и использовании функции eval в конечном счете. Снимать такую защиту очень просто, но слегка занудно, поэтому я решил написать примитивную программу, которая осуществляет сие действо автоматически.
Чтобы пост не был унылым, я кратенько опишу, что из себя представляет анпакер. Итак, из-за своей лени я решил использовать php-cli, расширение для php (которое перехватывает eval) и сделать к этому простой GUI. Результирующая программа выглядит следующим образом:
Начнем с расширения. Процесс создания расширения и используемая техника перехвата довольно примитивны и описаны здесь и здесь. Код, описанный в последней ссылке, я слегка изменил под себя. Приведу его одним куском:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
#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 приведу частями. Начнем с инклюдов и глобальных переменных:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
#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}; |
Несколько вспомогательных функций:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
/* Функция очевидного преобразования */ 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); } |
И, наконец, основные функции в порядке убывания "важности":
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 |
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: скачать
Исходный код: скачать
GitHub: php-eval-unpacker
спасибо, полезная вещь
Господа, напишите пожалуйста криптософт. Сорцев мало достойных
К примеру алго PAQ а поверх RSA 4096.
Криптография довольно инстересная штука, и мало реализаций хороших.
Архиватор Ваш видел, но там банально: ZLib+AES, а вышепредложенный алгоритм повзрослее чтоли будет.
С уважением.
Да вы параноик, сэр.
Да, сэр, Вы правы.
Гуглишь реализацию PAQ, добавляешь OpenSSL и получается готовая реализация.
Смысл? Вся практическая сложность сведется к интерфейсу и мелочам.
С нуля, опираясь на математические описания алгоритмов, писать все это дело - ну, как минимум результат будет медленнее работать, да и косяки могут быть.
Ок, я понял, что никому из Вас это неинтересно.
Спасибо за совет.
Вопрос скорее в целесообразности и смысле. Лично я не вижу конечного смысла затеи.
Уникальной криптостойкости не будет, реализация по готовым выкладкам, юзабилити под вопросом...
Эта задача скорее напоминает то, что обычно просят на форумах в качестве курсовой или диплома, а security through obscurity за счет кастомного формата, хрен знает, например, rar-архив с 32 символьным паролем люди как-то не вскрывают, так почему бы не его...
Привет админ! Не смог найти контакты. Отпишись мне пожалуйста. Хочу разместить рекламу в твоем блоге.
1. Купи очки (или линзы) и сразу же начни пользоваться ими.
2. Нажми на кнопке "О блоге" под шапкой сайта.
3. Сделай усилия, и попробуй там отыскать контакты админа.