TeamViewer, как и большинство программных продуктов, обладает опцией сохранения пароля от своего профиля (профиль используется для упорядоченного хранения перечня идентификаторов удаленных компьютеров с реквизитами доступа к ним). При включенной опции сохранения, пароль прозаично сохраняется в реестре по адресу HKCU\Software\TeamViewer\Version* в переменной BuddyLoginPWAES.
Как видно из названия переменной, да и непосредственно из реестра, перед сохранением пароль шифруется, однако это не является проблемой, т.к. ключи шифрования легко получить, а также никто не мешает просто скопировать содержимое переменной из реестра, перенести на другой компьютер и там успешно авторизоваться. Отсюда возникает некоторое опасение, так как всегда существует вероятность запустить очередной password stealer, который всё благополучно утащит.
Попробуем решить эту проблему костыльно-велосипедным способом. Для этого напишем небольшую библиотеку, которая будет перехватывать обращения к реестру и осуществлять дополнительное шифрование пароля, а также лаунчер для тимвьювера, который будет запускать его и заодно подгружать нашу библиотеку.
Библиотеку будем писать на MASM, ибо C и Detours быстро надоедают. Начнем как всегда сначала.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
.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).
Перейдем к секции данных
1 2 3 4 5 6 7 8 9 |
.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 |
Тут всё ещё более тривиально, поэтому перейдем к чуть менее тривиальному коду, который будет состоять из нескольких функций.
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 |
.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:
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 |
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. Подход не является хорошей практикой, т.к. корректнее было бы прикрутить дизассемблер длин и сделать всё как надо, но кому это нужно в данном контексте?
С библиотекой закончили, осталось её скомпилировать, для этого можно воспользоваться, например, этим набором, либо взять всё с "официального сайта.
Перейдем к элементарному лаунчеру. Тут я позволю себе воспользоваться ранее написанным классом. С его использованием код сокращается до пары десятков строк. Вот он:
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 |
#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 и наслаждаться результатом. Описанная методика применима для любой программы, которая хранит свои пароли в реестре.
Исходный код: скачать
TeamViewer мне не особо интересен, но за примеры работы на Масме с перехватом функций спасибо.
Такое применимо к любым программам, которые где-то хранят пароли.
Спасибо за статью.
Применимо, но только минус в том, что для каждой проги нужно писать отдельный код и кроме того могут быть проблемы с разными версиями одной и той самой проги. Немного более универсальный способ (но в некотором смысле и опасный) - внедрять библиотеку в программу, которую нужно защитить, при первом же ее запуске, и шифровать абсолютно все ключи реестра, которые пишет и модифицирует эта программа. Может быть незначительное падение быстродействия (незаметное) и будут проблемы в том случае, если программа модифицирует ключ, к которому обращаются другие программы.
Никто не мешает убрать фильтры и добавить проверку, чтобы отсечь обращения системных библиотек
Спасибо.
Но по моему надо поправить ...
>Стандартные неинтересные инклюды, за исключением inject.asm, старая >структура, которая вместе с inject.asm была описана в этой статье
... в "этой статье" она называлась mem.asm.
Или я что-то путаю?
Идея появилась.
Все, что описано в статье очень актуально для браузеров. То есть шифровать все сохраненные пароли в браузере и не бояться, что они могут быть скачаны кем нибудь по вашей неосторожности. Попробую реализовать как нибудь.
Фаерфокс и так их шифрует все на главном пользовательском пароле. Про другие не знаю.
Как всегда, ничего особо умного не скажу, но поблагодарю за статью - прекрасный сниппет и идея, спасибо.