Новости из Блогов Защищаем пароль в TeamViewer

Discussion in 'Мировые новости. Обсуждения.' started by Suicide, 27 Oct 2012.

  1. Suicide

    Suicide Super Moderator
    Staff Member

    Joined:
    24 Apr 2009
    Messages:
    2,373
    Likes Received:
    6,619
    Reputations:
    693
    Защищаем пароль в TeamViewer
    Суббота, 27. Октябрь 2012
    автор: Kaimi
    http://kaimi.ru/2012/10/protecting-teamviewer-stored-password/
    http://kaimi.ru/



    TeamViewer, как и большинство программных продуктов, обладает опцией сохранения пароля от своего профиля (профиль используется для упорядоченного хранения перечня идентификаторов удаленных компьютеров с реквизитами доступа к ним). При включенной опции сохранения, пароль прозаично сохраняется в реестре по адресу HKCU\Software\TeamViewer\Version* в переменной BuddyLoginPWAES.

    [​IMG]

    Как видно из названия переменной, да и непосредственно из реестра, перед сохранением пароль шифруется, однако это не является проблемой, т.к. ключи шифрования легко получить, а также никто не мешает просто скопировать содержимое переменной из реестра, перенести на другой компьютер и там успешно авторизоваться. Отсюда возникает некоторое опасение, так как всегда существует вероятность запустить очередной 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 и наслаждаться результатом. Описанная методика применима для любой программы, которая хранит свои пароли в реестре.

    Исходный код: скачать