Оконная подсистема 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. У любой программы с граф.интерфейсом имеется собственная процедура обратного вызова
- она отвечает за обработку всех сообщений, отправляемых системой окну. Процедура в бесконечном цикле должна идентифицировать поступающие мессаги, и выполнять с ними все необходимые действия. Прототип
выглядит так
(4 указанных ниже аргумента передаёт графическая подсистема):
C-подобный:
Код:
CALLBACK
WindowProc
(
HWND hWnd
,
UINT uMsg
,
WPARAM wParam
,
LPARAM lParam
)
--
--
--
--
--
--
--
--
--
--
-
hWnd
=
дескриптор окна
,
кому адресовано сообщение
uMsg
=
сама мессага WM_xx
wParam
=
основной параметр сообщения
lParam
=
доп
.
параметр
(
зависит от типа WM_xx
)
В результате различных событий в системе генерируются сотни сообщений
(в доках зарезервированы первые 400h констант), а из этого пула приложение юзера выбирает и обрабатывает только нужные себе. Но архитектура окон построена так, что если есть сообщение, оно обязательно должно быть кем-то обработано, иначе хаос и бардак. Чтобы гарантировать обработку буквально всех поступающих сообщений, Win предоставляет дефолтную свою процедуру
для обработки мессаг, которые наш юзерский колбек пропустил между ног. Как правило в системную процедуру мы передаём управление в конце своей
, чтобы необработанные сообщения могли быть переданы в процедуру по умолчанию.
Разрабатывая подсистему управления окнами майки преследовали основную цель - надёжность подсистемы, чтобы ни один поток не мог нарушить работу других потоков. В дефолте передача сообщения в окно всегда осуществляется
синхронно чз
: отправитель ждёт, пока окно получателя не обработает его сообщение и не вернёт ответ. Но если на обработку потребуется длительное время
(или окно адресата вообще зависнет), напрочь заморозится и отправитель, а значит такая ОС не вправе называться надёжной. Так появились
асинхронные сообщения
, когда отправитель послав мессагу не дожидается ответа, и сразу продолжает заниматься своими делами.
Каждому потоку с графическим интерфейсом система выделяет свою собственную очередь сообщений "Message Queue", которая независит от других потоков. Как результат потоки выполняются в такой среде, где они считают себя единственными. Изначально, создавая какой-либо поток система предполагает, что он не будет работать с графическим интерфейсом - это позволяет уменьшить объём выделяемых ему системных ресурсов. Но, как только поток обратится к той или иной GUI-функции
(например создание окна), система на автомате выделит ему нужные ресурсы для поддержки оконных сообщений. Эти ресурсы заворачиваются в структуру
драйвера win32k.sys, которая сопоставляется с данным потоком.
- это фундамент всей подсистемы передачи сообщений. Каждый поток имеет не одну, а целых три очереди - указатели на них прописываются именно в данной структуре. Первая - это очередь синхронных Sent-мессаг, куда выстроившись в ряд оседают сообщения от функции
. Вторая очередь для асинхронных
, и третья для ответных сообщений "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
.....
Если поток вызывает синхронную
для посылки сообщения
своему окну, то функция просто обращается к своей оконной процедуре
, и в ответ получает некое значение
(зависит от типа мессаги). Но если поток посылает сообщение
чужому окну, всё значительно усложняется.
Во-первых, переданное сообщение присоединяется к очереди приёмника, и для него устанавливается флаг
. Во-вторых, если поток приёмника в данный момент чем-то занят, система не прервёт его работу для немедленной обработки поступившего сообщения. Если-же поток свободен и сообщений в его очереди больше нет, флаг
сбрасывается. Пока приёмник обрабатывает мессагу, отправивший
поток простаивает, ожидая ответа в своей очереди ответных сообщений Reply. С этого момента поток-отправитель просыпается и возобновляет работу в обычном режиме.
Поскольку Win обрабатывает межпоточные мессаги описанным выше образом, в ожидании ответа наш поток может заснуть навсегда. Задумаемся, что произойдёт с вызвавшим
потоком, если по каким-либо причинам получатель войдёт в бесконечный цикл? Значит-ли это, что ошибка в одном приложении уронит другое? Ответ - да, и это является дырой в подсистеме безопасности! Выход из этой непростой ситуации один - использовать безопасные функции типа
Код:
SendMessageTimeout()
, последний аргумент которой ограничивает время ожидания ответа. Можно использовать асинхронную
, но тогда мы не узнаем, обработал получатель наш запрос или нет, что не всегда удобно.
2. Практика - дефейс калькулятора
Чтобы дефейснуть любую форточку мастдая много ума не надо, хотя на системах Win7+ имеется одно ограничение - наш уровень доверенности "Integrity Level" должен быть не меньше жертвы. Узнать свои полномочия можно в программе "Process Hacker", а для их повышения достаточно зайти в систему под админом. На скрине ниже я атакую текстовый редактор "AkelPad" своим софтом WinDeface.exe, и как видно в столбце "Integrity" наши уровни совпадают, хотя до System я уже не дотянусь:
Рассмотрим такой пример, где я нахожу окно калькулятора функцией
, и на всю его рабочую зону вывожу произвольную надпись, не забыв изменить и заголовок окна с "Калькулятор" на "Happy New Year!". Если для смены заголовка окна достаточно через
отправить потоку калькулятора сообщение
, то с выводом самой надписи не всё так просто.
Здесь нужно задействовать функции из либы gdi32.dll, чтобы сначала рассчитать позицию для вывода в клиентскую область окна
, далее создать шрифт требуемого размера
, активировать его посредством
, задать цвет и атрибут прозрачности
+
, и только потом напечатать в окно
. Более того, нужно предварительно захватить контекст устройства вывода "Device Context" через
и на выходе освободить его
. Вот код и что в итоге из этого получилось:
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
,
or eax
,
eax
jnz @ok
cinvoke printf
,
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
,
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
,
invoke ReleaseDC
,
[
hWnd
]
,
[
dc
]
cinvoke printf
,
,
[
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'

Если изменить аргумент
, можно аналогичным образом дефейснуть любое окно, как в примере ниже "AkelPad". Отметим, что это просто безобидный мод активного на данный момент окна, чтобы навести на юзера жути - при следующем перезапуске "Calc" и "AkelPad" всё восстанавливается в дефолт, и никаких надписей уже не будет. Это не фотошоп, и всё реально выглядит так:
3. Проблемы сообщений таймера
Среди всех оконных сообщений притаилось в засаде одно из интересных
(и в то-же время опасных) мессаг - это
с его собратом
. Согласно описанию на MSDN, они имеют привязанные функции обратного вызова "Callback". То-есть мы определяем в своём коде процедуру с любым содержимым
(например шелл-код), и по истечении указанного нами времени в таймере, она получает управление. Красота!
Только вот всю малину портит то, что начиная с Висты инженеры ограничили возможности этого сообщения - теперь его можно посылать исключительно своему окну, и функция терпит крах, если мы отправляем мессагу таймера чужой форточке. При хороших обстоятельствах
могла-бы послужить отличной альтернативой традиционным способам внедрения шелл-кодов в чужой процесс, через давно уже палённую
Код:
CreateRemoteThread()
с последующим
Код:
WriteProcessMemoryEx()
. Например такой бесхитростный код наглым образом забирает управление у "Калькулятора", и передает его по адресу
.
C-подобный:
Код:
invoke FindWindow
,
0
,
invoke SendMessage
,
eax
,
WM_TIMER
,
0
,
0x4060E8
Запреты такого рода только разжигают интерес хакеров, и конечно-же они нашли выход из этого положения. По сути запреты и создаются, чтобы их нарушать. Если мыслить логически, то раз уж есть ограничение, значит где-то должна быть проверка типа сообщения, и если обнаружится, что это таймер, то секьюрити тут-же должен проводить нас на выход. И такая проверка действительно существует, причём ни где-то в нёдрах системы, а прямо у нас под носом в библиотеке user32.dll, которая проецируется системой в адресное пространство нашего процесса.
Как уже упоминалось выше, все мессаги из оконной процедуры
диспетчеризуются в user32.dll, где и происходит их фактическая обработка внутренней функцией
Код:
DispatchMessageWorker()
. На скрине ниже фрагмент из листинга этой функции, с тестом мессаги на
. Здесь достаточно изменить адрес перехода с
на адрес чуть ниже
(у вас он может быть другим) и всё.. таймер "пойдёт танцевать в пьяную", захватывая в свои объятия абсолютно любые окна:
4. Постскриптум
Подсистема графических окон Win дырява как сито, и что особенно важно, инженеры ничего не могут с этим поделать. Для ускорения отрисовки окон, компонент ядра в лице драйвера win32k.sys отображает большую часть своих структур в пространство пользователя, от куда мы можем их без проблем читать, но не модифицировать. Однако для разведки и этого достаточно, а дальше уже по обстоятельствам. В промапленной в наш процесс либе user32.dll есть куча уязвимых мест, и нам остаётся лишь использовать их в своих корыстных
(и не очень) целях. В скрепку кладу исполняемый файл для тестов
(запускать при активном калькуляторе), всех с наступающим, пока!