Защищаем пароль в TeamViewer Суббота, 27. Октябрь 2012 автор: Kaimi http://kaimi.ru/2012/10/protecting-teamviewer-stored-password/ http://kaimi.ru/ TeamViewer, как и большинство программных продуктов, обладает опцией сохранения пароля от своего профиля (профиль используется для упорядоченного хранения перечня идентификаторов удаленных компьютеров с реквизитами доступа к ним). При включенной опции сохранения, пароль прозаично сохраняется в реестре по адресу HKCU\Software\TeamViewer\Version* в переменной BuddyLoginPWAES. Как видно из названия переменной, да и непосредственно из реестра, перед сохранением пароль шифруется, однако это не является проблемой, т.к. ключи шифрования легко получить, а также никто не мешает просто скопировать содержимое переменной из реестра, перенести на другой компьютер и там успешно авторизоваться. Отсюда возникает некоторое опасение, так как всегда существует вероятность запустить очередной password stealer, который всё благополучно утащит. Попробуем решить эту проблему костыльно-велосипедным способом. Для этого напишем небольшую библиотеку, которая будет перехватывать обращения к реестру и осуществлять дополнительное шифрование пароля, а также лаунчер для тимвьювера, который будет запускать его и заодно подгружать нашу библиотеку. Библиотеку будем писать на MASM, ибо C и Detours быстро надоедают. Начнем как всегда сначала. Code: .486 .model flat, stdcall option casemap :none include \masm32\include\windows.inc include \masm32\macros\macros.asm include \masm32\macros\windows.asm include \masm32\macros\inject.asm uselib kernel32, user32, masm32 validate proto value_name:DWORD, entry_type:DWORD, entry_buffer:DWORD JUMPNEAR STRUCT 1 opcd BYTE ? reladdr DWORD ? JUMPNEAR ENDS Стандартные неинтересные инклюды, за исключением inject.asm, старая структура, которая вместе с inject.asm была описана в этой статье, и прототип функции для проверки некоторых аргументов у перехватываемых в дальнейшем функций (RegSetValueExW, RegQueryValueExW). Перейдем к секции данных Code: .data ;Ключ шифрования ☺ enc_key db "I7JFTZgcZdk" ;Размер ключа enc_key_sz dd sizeof enc_key ;Имя ключа в реестре, который нас интересует (в Unicode) reg_key_name db 'B',0,'u',0,'d',0,'d',0,'y',0,'L',0,'o',0,'g',0,'i',0,'n',0,'P',0,'W',0,'A',0,'E',0,'S',0,0,0 ;Переменная для хранения адреса RegSetValueExW reg_set_value dd 0 Тут всё ещё более тривиально, поэтому перейдем к чуть менее тривиальному коду, который будет состоять из нескольких функций. Code: .code MyRegQueryValueExW: push ebp mov ebp, esp ;Вызовем оригинальную функцию HOOK_ORIGINAL_CALL RegQueryValueExW, 6 ;Сохраним результат выполнения push eax ;Проверим параметры invoke validate, [ebp + 4 + 4 * 2], [ebp + 4 + 4 * 4], [ebp + 4 + 4 * 5] ;Если меняется целевой параметр, то расшифруем его .if eax == 1 mov eax, [ebp + 4 + 4 * 6] mov eax, [eax] invoke XorData, [ebp + 4 + 4 * 5], eax, offset enc_key, enc_key_sz .endif pop eax pop ebp retn 4 * 6 MyRegSetValueExW: ;Буфер для пролога оригинальной функции stolen_bytes db 7 dup(90h) push ebp mov ebp, esp ;Компенсируем 2 x push из оригинального пролога add ebp, 4 * 2 ;Получаем указатель на 4-й аргумент на стеке mov eax, ebp add eax, 4 + 4 * 4 ;Проверим параметры invoke validate, [ebp + 4 + 4 * 2], eax, [ebp + 4 + 4 * 5] ;Если меняется целевой параметр, то зашифруем его .if eax == 1 mov eax, [ebp + 4 + 4 * 6] invoke XorData, [ebp + 4 + 4 * 5], eax, offset enc_key, enc_key_sz .endif pop ebp ;Вернемся в оригинальную функцию push reg_set_value add dword ptr[esp], 7 retn validate proc value_name:DWORD, entry_type:DWORD, entry_buffer:DWORD ;Имя параметра, тип и буфер для результата не должны быть нулевыми .if value_name == NULL || entry_type == NULL || entry_buffer == NULL xor eax, eax ret .endif ;Тип параметра должен быть REG_BINARY mov eax, entry_type mov eax, [eax] .if eax != REG_BINARY xor eax, eax ret .endif ;Имя параметра должно соответствовать ожидаемому invoke lstrcmpW, value_name, offset reg_key_name .if eax != 0 xor eax, eax ret .endif mov eax, 1 ret validate endp Как видно из вышеописанного кода, мы используем тривиальный XOR для шифрования, его вполне достаточно для наших целей, вдобавок не меняется размер данных после шифрования-дешифрования. Также кому-то может быть не сразу понятно, что из себя представляют аргументы вида [ebp + 4 + 4 * x], тут всё просто - это обращение к N-ому аргументу stdcall функции (например, [ebp + 4 + 4 * 4] - это четвертый по счету аргумент функции RegQueryValueExW, то есть lpType), чуть подробнее всё это описывалось в вышеупомянутой статье про инжектор. Теперь рассмотрим LibMain: Code: LibMain proc instance:DWORD,reason:DWORD,reserved:DWORD local pr : dword local h : dword .if reason == DLL_PROCESS_ATTACH ;Ставим хук на RegQueryValueExW SET_HOOK advapi32.dll, RegQueryValueExW, MyRegQueryValueExW ;У RegSetValueExW нестандартный пролог (который не поддерживается макросами dx'а для инжекта), поэтому установим хук вручную ;Получим адрес функции mov h, FUNC(GetModuleHandle, chr$("Advapi32.dll")) mov reg_set_value, FUNC(GetProcAddress, h, chr$("RegSetValueExW")) ;Скопируем 7 байт нестандартного пролога и поместим их в нашу функцию invoke VirtualProtect, MyRegSetValueExW, sizeof stolen_bytes, PAGE_READWRITE, addr pr invoke MemCopy, reg_set_value, MyRegSetValueExW, sizeof stolen_bytes invoke VirtualProtect, MyRegSetValueExW, sizeof stolen_bytes, pr, addr pr invoke VirtualProtect, reg_set_value, sizeof JUMPNEAR, PAGE_READWRITE, addr pr ;Сформируем jmp на нашу функцию в начале RegSetValueExW mov eax, reg_set_value assume eax: ptr JUMPNEAR mov [eax].opcd, 0E9h mov ecx, offset MyRegSetValueExW sub ecx, reg_set_value sub ecx, 5 mov [eax].reladdr, ecx assume eax: nothing ;Занопим байты, оставшиеся от старых инструкций add eax, sizeof JUMPNEAR mov byte ptr[eax], 90h mov byte ptr[eax + 1], 90h invoke VirtualProtect, reg_set_value, sizeof JUMPNEAR, pr, addr pr mov eax, 1 .elseif reason == DLL_PROCESS_DETACH REMOVE_HOOK RegQueryValueExW mov eax, 1 .endif ret LibMain endp end LibMain В функции наблюдается смесь из макросов, которые можно использовать для Winapi-функций с обычным прологом и костыля для нестандартной RegSetValueExW. Подход не является хорошей практикой, т.к. корректнее было бы прикрутить дизассемблер длин и сделать всё как надо, но кому это нужно в данном контексте? С библиотекой закончили, осталось её скомпилировать, для этого можно воспользоваться, например, этим набором, либо взять всё с "официального сайта. Перейдем к элементарному лаунчеру. Тут я позволю себе воспользоваться ранее написанным классом. С его использованием код сокращается до пары десятков строк. Вот он: Code: #include <Windows.h> #include "injector.hpp" int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { STARTUPINFO si = {0}; PROCESS_INFORMATION pi = {0}; // Запустим процесс в приостановленном состоянии if(!CreateProcess(L"TeamViewer.exe", NULL, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi)) { MessageBox(HWND_DESKTOP, L"Failed to start TeamViewer", L"Error", MB_OK); return -1; } // Создадим инстанс класса-инжектора injector inj; inj.set_blocking(false); // Подгрузим библиотеку в процесс try { inj.inject(pi.dwProcessId, L"lego.dll"); } catch(const injector_exception &e) { MessageBox(HWND_DESKTOP, e.msg(), L"Error", MB_OK); } // Возобновим выполнение процесса и закроем ненужные хендлы ResumeThread(pi.hThread); CloseHandle(pi.hThread); CloseHandle(pi.hProcess); return 0; } Остается только скопировать полученную библиотеку вместе с лаунчером в папку TeamViewer'a и наслаждаться результатом. Описанная методика применима для любой программы, которая хранит свои пароли в реестре. Исходный код: скачать