Оконная подсистема Win хранит в себе много тайн и загадок, некоторые из которых мутируют в огромные дыры безопасности. Её можно сравнить с "Марианской впадиной", которая начиная с пользовательского пространства в лице библиотеки User32.dll, и уходит глубоко в ядро к драйверу win32k.sys. На системах WinXP передачей окон юзеру занималась подсистема клиент-сервера csrss.exe, а начиная с Win7 появился диспетчер "Desktop Window Manager" dwm.ехе, для реализации графического интерфейса "Windows Aero" с такими фишками как прозрачность, различные 3D-эффекты и прочее. В данной статье мы рассмотрим методы управления чужими окнами - по сути ничего новаторского, но полезно знать.
1. Модель оконных сообщений
2. Практика - дефейс калькулятора
3. Проблемы сообщений таймера
4. Постскриптум
1. Модель оконных сообщений
GUI составляющая Win полностью построена на оконных сообщениях "Window Message", которые отождествляются константами с префиксом WM_xx. У любой программы с граф.интерфейсом имеется собственная процедура обратного вызова
В результате различных событий в системе генерируются сотни сообщений (в доках зарезервированы первые 400h констант), а из этого пула приложение юзера выбирает и обрабатывает только нужные себе. Но архитектура окон построена так, что если есть сообщение, оно обязательно должно быть кем-то обработано, иначе хаос и бардак. Чтобы гарантировать обработку буквально всех поступающих сообщений, Win предоставляет дефолтную свою процедуру
Разрабатывая подсистему управления окнами майки преследовали основную цель - надёжность подсистемы, чтобы ни один поток не мог нарушить работу других потоков. В дефолте передача сообщения в окно всегда осуществляется синхронно чз
Каждому потоку с графическим интерфейсом система выделяет свою собственную очередь сообщений "Message Queue", которая независит от других потоков. Как результат потоки выполняются в такой среде, где они считают себя единственными. Изначально, создавая какой-либо поток система предполагает, что он не будет работать с графическим интерфейсом - это позволяет уменьшить объём выделяемых ему системных ресурсов. Но, как только поток обратится к той или иной GUI-функции (например создание окна), система на автомате выделит ему нужные ресурсы для поддержки оконных сообщений. Эти ресурсы заворачиваются в структуру
Если поток вызывает синхронную
Во-первых, переданное сообщение присоединяется к очереди приёмника, и для него устанавливается флаг
Поскольку Win обрабатывает межпоточные мессаги описанным выше образом, в ожидании ответа наш поток может заснуть навсегда. Задумаемся, что произойдёт с вызвавшим
2. Практика - дефейс калькулятора
Чтобы дефейснуть любую форточку мастдая много ума не надо, хотя на системах Win7+ имеется одно ограничение - наш уровень доверенности "Integrity Level" должен быть не меньше жертвы. Узнать свои полномочия можно в программе "Process Hacker", а для их повышения достаточно зайти в систему под админом. На скрине ниже я атакую текстовый редактор "AkelPad" своим софтом WinDeface.exe, и как видно в столбце "Integrity" наши уровни совпадают, хотя до System я уже не дотянусь:
Рассмотрим такой пример, где я нахожу окно калькулятора функцией
Здесь нужно задействовать функции из либы gdi32.dll, чтобы сначала рассчитать позицию для вывода в клиентскую область окна
Если изменить аргумент
3. Проблемы сообщений таймера
Среди всех оконных сообщений притаилось в засаде одно из интересных (и в то-же время опасных) мессаг - это
Только вот всю малину портит то, что начиная с Висты инженеры ограничили возможности этого сообщения - теперь его можно посылать исключительно своему окну, и функция терпит крах, если мы отправляем мессагу таймера чужой форточке. При хороших обстоятельствах
Запреты такого рода только разжигают интерес хакеров, и конечно-же они нашли выход из этого положения. По сути запреты и создаются, чтобы их нарушать. Если мыслить логически, то раз уж есть ограничение, значит где-то должна быть проверка типа сообщения, и если обнаружится, что это таймер, то секьюрити тут-же должен проводить нас на выход. И такая проверка действительно существует, причём ни где-то в нёдрах системы, а прямо у нас под носом в библиотеке user32.dll, которая проецируется системой в адресное пространство нашего процесса.
Как уже упоминалось выше, все мессаги из оконной процедуры
4. Постскриптум
Подсистема графических окон Win дырява как сито, и что особенно важно, инженеры ничего не могут с этим поделать. Для ускорения отрисовки окон, компонент ядра в лице драйвера win32k.sys отображает большую часть своих структур в пространство пользователя, от куда мы можем их без проблем читать, но не модифицировать. Однако для разведки и этого достаточно, а дальше уже по обстоятельствам. В промапленной в наш процесс либе user32.dll есть куча уязвимых мест, и нам остаётся лишь использовать их в своих корыстных (и не очень) целях. В скрепку кладу исполняемый файл для тестов (запускать при активном калькуляторе), всех с наступающим, пока!
1. Модель оконных сообщений
2. Практика - дефейс калькулятора
3. Проблемы сообщений таймера
4. Постскриптум
1. Модель оконных сообщений
GUI составляющая Win полностью построена на оконных сообщениях "Window Message", которые отождествляются константами с префиксом WM_xx. У любой программы с граф.интерфейсом имеется собственная процедура обратного вызова
WindowProc() - она отвечает за обработку всех сообщений, отправляемых системой окну. Процедура в бесконечном цикле должна идентифицировать поступающие мессаги, и выполнять с ними все необходимые действия. Прототип WindowProc() выглядит так (4 указанных ниже аргумента передаёт графическая подсистема):
C-подобный:
CALLBACK WindowProc(
HWND hWnd,
UINT uMsg,
WPARAM wParam,
LPARAM lParam
)
---------------------
hWnd = дескриптор окна, кому адресовано сообщение
uMsg = сама мессага WM_xx
wParam = основной параметр сообщения
lParam = доп.параметр (зависит от типа WM_xx)
В результате различных событий в системе генерируются сотни сообщений (в доках зарезервированы первые 400h констант), а из этого пула приложение юзера выбирает и обрабатывает только нужные себе. Но архитектура окон построена так, что если есть сообщение, оно обязательно должно быть кем-то обработано, иначе хаос и бардак. Чтобы гарантировать обработку буквально всех поступающих сообщений, Win предоставляет дефолтную свою процедуру
DefWindowProc() для обработки мессаг, которые наш юзерский колбек пропустил между ног. Как правило в системную процедуру мы передаём управление в конце своей WindowProc(), чтобы необработанные сообщения могли быть переданы в процедуру по умолчанию.Разрабатывая подсистему управления окнами майки преследовали основную цель - надёжность подсистемы, чтобы ни один поток не мог нарушить работу других потоков. В дефолте передача сообщения в окно всегда осуществляется синхронно чз
SendMessage(): отправитель ждёт, пока окно получателя не обработает его сообщение и не вернёт ответ. Но если на обработку потребуется длительное время (или окно адресата вообще зависнет), напрочь заморозится и отправитель, а значит такая ОС не вправе называться надёжной. Так появились асинхронные сообщения PostMessage(), когда отправитель послав мессагу не дожидается ответа, и сразу продолжает заниматься своими делами.Каждому потоку с графическим интерфейсом система выделяет свою собственную очередь сообщений "Message Queue", которая независит от других потоков. Как результат потоки выполняются в такой среде, где они считают себя единственными. Изначально, создавая какой-либо поток система предполагает, что он не будет работать с графическим интерфейсом - это позволяет уменьшить объём выделяемых ему системных ресурсов. Но, как только поток обратится к той или иной GUI-функции (например создание окна), система на автомате выделит ему нужные ресурсы для поддержки оконных сообщений. Эти ресурсы заворачиваются в структуру
THREADINFO драйвера win32k.sys, которая сопоставляется с данным потоком.THREADINFO - это фундамент всей подсистемы передачи сообщений. Каждый поток имеет не одну, а целых три очереди - указатели на них прописываются именно в данной структуре. Первая - это очередь синхронных Sent-мессаг, куда выстроившись в ряд оседают сообщения от функции SendMessage(). Вторая очередь для асинхронных PostMessage(), и третья для ответных сообщений "Reply-Message Queue" (реализована как ReceiveList). Помимо того в структуре имеется и переменная под флаги пробуждения потока "Wake Flags". Фрагмент этой структуры с перечисленными выше полями представлен ниже:
Код:
0: kd> dt win32k!tagTHREADINFO
.....
+0x198 TIF_flags : Uint4B
+0x1a0 pstrAppName : Ptr64 _UNICODE_STRING
+0x1a8 psmsSent : Ptr64 tagSMS
+0x1b0 psmsCurrent : Ptr64 tagSMS
+0x1b8 psmsReceiveList : Ptr64 tagSMS
+0x1d0 exitCode : Int4B
.....
Если поток вызывает синхронную
SendMessage() для посылки сообщения своему окну, то функция просто обращается к своей оконной процедуре WindowProc(), и в ответ получает некое значение (зависит от типа мессаги). Но если поток посылает сообщение чужому окну, всё значительно усложняется.Во-первых, переданное сообщение присоединяется к очереди приёмника, и для него устанавливается флаг
QS_SENDMESSAGE. Во-вторых, если поток приёмника в данный момент чем-то занят, система не прервёт его работу для немедленной обработки поступившего сообщения. Если-же поток свободен и сообщений в его очереди больше нет, флаг QS_SENDMESSAGE сбрасывается. Пока приёмник обрабатывает мессагу, отправивший SendMessage() поток простаивает, ожидая ответа в своей очереди ответных сообщений Reply. С этого момента поток-отправитель просыпается и возобновляет работу в обычном режиме.Поскольку Win обрабатывает межпоточные мессаги описанным выше образом, в ожидании ответа наш поток может заснуть навсегда. Задумаемся, что произойдёт с вызвавшим
SendMessage() потоком, если по каким-либо причинам получатель войдёт в бесконечный цикл? Значит-ли это, что ошибка в одном приложении уронит другое? Ответ - да, и это является дырой в подсистеме безопасности! Выход из этой непростой ситуации один - использовать безопасные функции типа SendMessageTimeout(), последний аргумент которой ограничивает время ожидания ответа. Можно использовать асинхронную PostMessage(), но тогда мы не узнаем, обработал получатель наш запрос или нет, что не всегда удобно.2. Практика - дефейс калькулятора
Чтобы дефейснуть любую форточку мастдая много ума не надо, хотя на системах Win7+ имеется одно ограничение - наш уровень доверенности "Integrity Level" должен быть не меньше жертвы. Узнать свои полномочия можно в программе "Process Hacker", а для их повышения достаточно зайти в систему под админом. На скрине ниже я атакую текстовый редактор "AkelPad" своим софтом WinDeface.exe, и как видно в столбце "Integrity" наши уровни совпадают, хотя до System я уже не дотянусь:
Рассмотрим такой пример, где я нахожу окно калькулятора функцией
FindWindow(), и на всю его рабочую зону вывожу произвольную надпись, не забыв изменить и заголовок окна с "Калькулятор" на "Happy New Year!". Если для смены заголовка окна достаточно через SendMessage() отправить потоку калькулятора сообщение WM_SETTEXT, то с выводом самой надписи не всё так просто.Здесь нужно задействовать функции из либы gdi32.dll, чтобы сначала рассчитать позицию для вывода в клиентскую область окна
GetClientRect(), далее создать шрифт требуемого размера CreateFont(), активировать его посредством SelectObject(), задать цвет и атрибут прозрачности SetTextColor() + SetBkMode(), и только потом напечатать в окно TextOut(). Более того, нужно предварительно захватить контекст устройства вывода "Device Context" через GetDC() и на выходе освободить его ReleaseDC(). Вот код и что в итоге из этого получилось:
C-подобный:
format pe64 console
include 'win64ax.inc'
entry start
;//----------
.data
rect RECT
hWnd dd 0
dc dd 0
font dq 0
;//sTxt db 'Hello World!',0
;//sTxt db 'Hackerlab!',0
sTxt db 'Codeby.net',0
txtLen = $ - sTxt
;//----------
section '.code' code readable executable
start: push rbp
invoke FindWindow,0,<'Калькулятор',0>
or eax,eax
jnz @ok
cinvoke printf,<10,' Error! Calc not fond.',0>
jmp @exit
@ok: mov [hWnd],eax
invoke GetClientRect,eax,rect
invoke GetDC,[hWnd]
mov [dc],eax
;// Рассчитываем размер шрифта по размеру окна
mov ecx,[rect.bottom]
shr ecx,1
mov eax,[rect.right]
mov ebx,txtLen
xor edx,edx
div ebx
xchg eax,edx
invoke CreateFont,rcx,rdx,0,0,600,0,0,0,\
ANSI_CHARSET,0,0,0,\
FF_MODERN,<'Comic Sans MS',0>
mov [font],rax
invoke SelectObject,[dc],[font]
invoke SetBkMode,[dc],TRANSPARENT
;// Цвет текста будем выбирать рандомом
rdtsc
and eax,0xffffff
invoke SetTextColor,[dc],eax
mov eax,[rect.bottom]
shr eax,3
invoke TextOut,[dc],0,eax,sTxt,txtLen
;// Сменить заголовок окна, и освободить контекст девайса
invoke SendMessage,[hWnd],WM_SETTEXT,0,<'Happy New Year!',0>
invoke ReleaseDC,[hWnd],[dc]
cinvoke printf,<10,' Window found: 0x%08x',0>,[hWnd]
@exit: cinvoke _getch
cinvoke exit,0
;//----------
section '.idata' import data readable
library msvcrt,'msvcrt.dll', kernel32,'kernel32.dll',\
user32,'user32.dll', gdi32,'gdi32.dll'
include 'api\msvcrt.inc'
include 'api\kernel32.inc'
include 'api\user32.inc'
include 'api\gdi32.inc'
Если изменить аргумент
FindWindow(), можно аналогичным образом дефейснуть любое окно, как в примере ниже "AkelPad". Отметим, что это просто безобидный мод активного на данный момент окна, чтобы навести на юзера жути - при следующем перезапуске "Calc" и "AkelPad" всё восстанавливается в дефолт, и никаких надписей уже не будет. Это не фотошоп, и всё реально выглядит так:3. Проблемы сообщений таймера
Среди всех оконных сообщений притаилось в засаде одно из интересных (и в то-же время опасных) мессаг - это
WM_TIMER=0113h с его собратом WM_SYSTIMER=0118h. Согласно описанию на MSDN, они имеют привязанные функции обратного вызова "Callback". То-есть мы определяем в своём коде процедуру с любым содержимым (например шелл-код), и по истечении указанного нами времени в таймере, она получает управление. Красота!Только вот всю малину портит то, что начиная с Висты инженеры ограничили возможности этого сообщения - теперь его можно посылать исключительно своему окну, и функция терпит крах, если мы отправляем мессагу таймера чужой форточке. При хороших обстоятельствах
WM_TIMER могла-бы послужить отличной альтернативой традиционным способам внедрения шелл-кодов в чужой процесс, через давно уже палённую CreateRemoteThread() с последующим WriteProcessMemoryEx(). Например такой бесхитростный код наглым образом забирает управление у "Калькулятора", и передает его по адресу 0x004060Е8.
C-подобный:
invoke FindWindow,0,<'Калькулятор',0>
invoke SendMessage,eax,WM_TIMER,0,0x4060E8
Запреты такого рода только разжигают интерес хакеров, и конечно-же они нашли выход из этого положения. По сути запреты и создаются, чтобы их нарушать. Если мыслить логически, то раз уж есть ограничение, значит где-то должна быть проверка типа сообщения, и если обнаружится, что это таймер, то секьюрити тут-же должен проводить нас на выход. И такая проверка действительно существует, причём ни где-то в нёдрах системы, а прямо у нас под носом в библиотеке user32.dll, которая проецируется системой в адресное пространство нашего процесса.
Как уже упоминалось выше, все мессаги из оконной процедуры
WindowProc() диспетчеризуются в user32.dll, где и происходит их фактическая обработка внутренней функцией DispatchMessageWorker(). На скрине ниже фрагмент из листинга этой функции, с тестом мессаги на WM_TIMER. Здесь достаточно изменить адрес перехода с 0x7DC6792D на адрес чуть ниже 0x7DC67740 (у вас он может быть другим) и всё.. таймер "пойдёт танцевать в пьяную", захватывая в свои объятия абсолютно любые окна:4. Постскриптум
Подсистема графических окон Win дырява как сито, и что особенно важно, инженеры ничего не могут с этим поделать. Для ускорения отрисовки окон, компонент ядра в лице драйвера win32k.sys отображает большую часть своих структур в пространство пользователя, от куда мы можем их без проблем читать, но не модифицировать. Однако для разведки и этого достаточно, а дальше уже по обстоятельствам. В промапленной в наш процесс либе user32.dll есть куча уязвимых мест, и нам остаётся лишь использовать их в своих корыстных (и не очень) целях. В скрепку кладу исполняемый файл для тестов (запускать при активном калькуляторе), всех с наступающим, пока!