PDA

Просмотр полной версии : Дефейс окон в Windows


Marylin
30.12.2025, 13:24
Оконная подсистема 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. У любой программы с граф.интерфейсом имеется собственная процедура обратного вызова

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 я уже не дотянусь:

https://forum.antichat.xyz/attachments/4950173/img_7c309417da.png
Рассмотрим такой пример, где я нахожу окно калькулятора функцией

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
,

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'


https://forum.antichat.xyz/attachments/4950173/img_1b745c5d39.png
Если изменить аргумент

FindWindow()

, можно аналогичным образом дефейснуть любое окно, как в примере ниже "AkelPad". Отметим, что это просто безобидный мод активного на данный момент окна, чтобы навести на юзера жути - при следующем перезапуске "Calc" и "AkelPad" всё восстанавливается в дефолт, и никаких надписей уже не будет. Это не фотошоп, и всё реально выглядит так:

https://forum.antichat.xyz/attachments/4950173/img_0ea11f3fd4.png
3. Проблемы сообщений таймера

Среди всех оконных сообщений притаилось в засаде одно из интересных (и в то-же время опасных) мессаг - это

WM_TIMER=0113h

с его собратом

WM_SYSTIMER=0118h

. Согласно описанию на MSDN, они имеют привязанные функции обратного вызова "Callback". То-есть мы определяем в своём коде процедуру с любым содержимым (например шелл-код), и по истечении указанного нами времени в таймере, она получает управление. Красота!

Только вот всю малину портит то, что начиная с Висты инженеры ограничили возможности этого сообщения - теперь его можно посылать исключительно своему окну, и функция терпит крах, если мы отправляем мессагу таймера чужой форточке. При хороших обстоятельствах

WM_TIMER

могла-бы послужить отличной альтернативой традиционным способам внедрения шелл-кодов в чужой процесс, через давно уже палённую

CreateRemoteThread()

с последующим

WriteProcessMemoryEx()

. Например такой бесхитростный код наглым образом забирает управление у "Калькулятора", и передает его по адресу

0x004060Е8

.

C-подобный:



invoke FindWindow
,
0
,

invoke SendMessage
,
eax
,
WM_TIMER
,
0
,
0x4060E8


Запреты такого рода только разжигают интерес хакеров, и конечно-же они нашли выход из этого положения. По сути запреты и создаются, чтобы их нарушать. Если мыслить логически, то раз уж есть ограничение, значит где-то должна быть проверка типа сообщения, и если обнаружится, что это таймер, то секьюрити тут-же должен проводить нас на выход. И такая проверка действительно существует, причём ни где-то в нёдрах системы, а прямо у нас под носом в библиотеке user32.dll, которая проецируется системой в адресное пространство нашего процесса.

Как уже упоминалось выше, все мессаги из оконной процедуры

WindowProc()

диспетчеризуются в user32.dll, где и происходит их фактическая обработка внутренней функцией

DispatchMessageWorker()

. На скрине ниже фрагмент из листинга этой функции, с тестом мессаги на

WM_TIMER

. Здесь достаточно изменить адрес перехода с

0x7DC6792D

на адрес чуть ниже

0x7DC67740

(у вас он может быть другим) и всё.. таймер "пойдёт танцевать в пьяную", захватывая в свои объятия абсолютно любые окна:

https://forum.antichat.xyz/attachments/4950173/img_0fc86c180d.png

4. Постскриптум

Подсистема графических окон Win дырява как сито, и что особенно важно, инженеры ничего не могут с этим поделать. Для ускорения отрисовки окон, компонент ядра в лице драйвера win32k.sys отображает большую часть своих структур в пространство пользователя, от куда мы можем их без проблем читать, но не модифицировать. Однако для разведки и этого достаточно, а дальше уже по обстоятельствам. В промапленной в наш процесс либе user32.dll есть куча уязвимых мест, и нам остаётся лишь использовать их в своих корыстных (и не очень) целях. В скрепку кладу исполняемый файл для тестов (запускать при активном калькуляторе), всех с наступающим, пока!