Перехват оконных сообщений в cbuilder


Содержание

Перехват оконных сообщений в cbuilder

Понятие перехвата используется во многих областях. Например, даже при создании диалогового окна открытия файла мы может установить перехват подробнее читайте «Шаг 140 — Cтруктура OPENFILENAME». Перехват является точкой в обработка сообщений Microsoft Windows, где приложение может установить подпрограмму, чтобы проверить трафик сообщения в системе и обработать определенные тип сообщений прежде, чем они достигнут процедуры окна в которую посланы. Перехваты имеют тенденцию замедлять систему из-за того, что они увеличивают суммарное время обработки, которую система должна выполнить для каждого сообщения. У нас есть утилита SPY подробнее читайте «Шаг 47 — Как пользоваться SPY», которая умеет следить за сообщениями Windows для конкретного окна. Разработчики использующие MFC имеют еще одну дополнительную утилиту MFC Tracer, читайте «Шаг 44 — Знакомимся с MFC Tracer», «Шаг 227 — afxTraceFlags», которая позволяет следить за сообщениями. Можно воспользоваться и сторонними утилитами, которые в основном нацелены на слежение за пользователями читайте «Шаг 139 — Программа слежения WinGuard».

А можно создать ее и самому. Вы должны устанавливать захват только когда необходимо, и удалять его как можно скорее. Для установки захвата используется функция.

А для отмены перехвата UnhookWindowsHookEx. Ну попробуем, что ли. MFC приложение Dialog Bases:

Заводим глобальную переменную:

А теперь процедуру обработки, запуск и остановка перехвата:

Все можно запускать. Теперь после запуска программы и нажатия на Start, где бы Вы не нажали клавишу тут Ваша программа получит сообщение. Вот я в WORD попытался нажать клавишу:

Перехват вызовов функций WinAPI

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

Какие бывают хуки

Ловушки (hook) могут быть режима пользователя (usermode) и режима ядра (kernelmode). Установка хуков режима пользователя сводится к методу сплайсинга и методу правки таблиц IAT. Ограничения этих методов очевидны: перехватить можно только userspace API, а вот до функций с префиксом Zw*, Ki* и прочих «ядерных» из режима пользователя дотянуться нельзя.

Установка хуков режима ядра позволяет менять любую информацию, которой оперирует Windows на самом низком уровне. Для перехватов подобного типа необходимо модифицировать таблицы SSDT/IDT либо менять само тело функции (kernel patch).

Надо сказать, что в Windows на архитектуре x64 ядро контролирует свою целостность при помощи механизма KPP (Kernel Patch Protection), который является частью PatchGuard и просто так подобные манипуляции с системными таблицами сделать не позволит.

Почему хуки работают?

Когда Windows запускает приложение, создается его процесс и потоки, загрузчик ОС ищет зависимости динамических библиотек, которые нужны для работы программы. Поиск ведется сначала в папке, где находится исполняемый файл, далее в нескольких системных папках.

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

Наша задача — загрузить в адресное пространство приложения нашу библиотеку и исправить таблицу зависимостей в программе таким образом, чтобы она думала, будто функции, которые ей нужны, находятся именно в нашей библиотеке, а не в той, где они были раньше.

Сплайсинг функций WinAPI

Пролог функций, трамплин и т.д

Функции WinAPI начинаются с пролога — это стандартный код, отвечающий за балансировку стека для корректного доступа к локальным переменным, которые использует функция. Обычно пролог выглядит таким образом:

В большинстве функций он одинаков, и поэтому на его место можно добавить инструкцию безусловного перехода jmp, которая передаст управление на наш код. Это называется «трамплин» — мы просто уводим поток выполнения функции в наш код, где делаем все, что хотим: можем подменить результат выполнения функции, можем вызвать какой-то другой код, запустить процесс — одним словом, массу всего.

Но чтобы грамотно реализовать перехватчик функций методом сплайсинга, нам нужен дизассемблер длин инструкций.

Зачем использовать дизассемблер длин, если мы и так знаем пролог функций?

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

В том случае, если мы «настроим» нашу функцию сплайсинга на стандартный пролог, а он окажется другим, то после реализации перехвата выполнение может пойти не с начала машинной команды, а с какой-то ее части.

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

Библиотеки для перехвата

Как вы уже поняли, для написания качественного универсального сплайсера функций нужно затратить много сил и времени, и даже крупные фирмы предпочитают для своих проектов покупать готовые библиотеки, реализующие перехваты функций.

Мы рассмотрим два популярных коммерческих решения: Detours производства непосредственно Microsoft и библиотеку madCodeHook. Почему именно эти две библиотеки? На них можно реализовать перехват с минимумом кода, что как нельзя лучше подходит для обучения. Полные версии обеих библиотек платные, но для обучения можно либо использовать ограниченные бесплатные версии, либо покупать полные, либо… ну, вы знаете.

С готовой библиотекой мы будем уверены, что

  • в ней встроен качественный дизассемблер длин, который не испугается разнообразных функций WinAPI;
  • встроен специальный корректор кода, способный работать вместе с функцией, реализующей трамплины;
  • при сборке проекта будут использованы блоки условной компиляции кода и нам не придется менять синтаксис перехватов при смене архитектур x86 и x64.

Одним словом, мы будем уверены, что в нашей DLL окажутся все необходимые функции.

Тестовое приложение

Для начала экспериментов с перехватами напишем тестовое приложение, назовем его test1.exe. Оно ничего не делает. Точнее, просто ждет 60 секунд, используя функцию WinAPI Sleep(), а потом закрывается. Я выбрал эту функцию специально, чтобы было понятно, что изначально наше приложение неспособно, например, создавать файлы.

Перехват оконных сообщений в cbuilder

Тут субклассинг не поможет. У нас создается автоматом объект Application класса TApplication, на TMyApplication я тип никак не поменяю.

TApplication::HookMainWindow решает проблему, вроде бы (файл forms.pas):

TApplication::HookMainWindow

Enables a non-VCL dialog box to receive messages sent to the application’s main window.

void __fastcall HookMainWindow(TWindowHook &Hook);

Use HookMainWindow to ensure that a non-VCL dialog box behaves correctly as a child of the application, not as a stand-alone window. For example, switching among applications with Alt+Tab treats the application as a single task after calling HookMainWindow, rather than treating the non-VCL dialog box as a separate task.

When the window identified by the Handle property receives relevant dialog messages, it passes them to the dialog procedure passed as the Hook parameter.

There is no problem with leaving a dialog box hooked into the main window, even for extended periods. However, should the dialog box close, call the UnhookMainWindow method to release the hook.

TWindowHook type

TWindowHook is the type for a dialog procedure.

typedef bool __fastcall (__closure *TWindowHook)(Messages::TMessage &Message);

TWindowHook is the type for the window procedure of a non-VCL dialog box. Like most window procedures, TWindowHook takes a message struct as an argument.

Перехват сообщений в Билдере??

Здравствуйте! Меня интересует вопрос о перехвате сообщий Винды. Ну предположим такая ситуация: например я хочу что бы окно при нажатии на крестик в правом верхнем углу не закрывалось, а предположим сворачивалось. Или вот еще пример: Есть RichEdit и я хочу чтобы при нажатии Enter не печатался перенос каретки(на нову строку) , а допустим выполнялась передача строки на сервер или какое либо другое действие!

Как это можно реализовать в Билдере?? Заранее спасибо!

Ловушки в Microsoft Windows

Когда то давно возникла у меня необходимость написать программку для создания скриншотов. А именно пользователь выделяет мышью необходимую ему область на экране, затем отпускает кнопку и получает скриншот. В то время я про ловушки еще не знал. Несколько дней я “бился” над поставленной задачей, но мои эксперименты так и ни к чему, ни привели. Почитав различную литературу и статьи, и узнав, что такое ловушки, и с чем их “едят”, я принялся экспериментировать дальше. А начал я с книги Михаила Фленова «Программирование в Delphi глазами хакера». На то время, все то, что я почерпал из его книги, мне показалось довольно легко, но только потом я понял (когда в этом деле поднабрался опыта), что сильно ошибся.

А обстояло все это дело так: я переписал его код, кое что добавил свое и в ожидании того, чего я ждал так долго, что это, наконец, и есть, то, что нужно. Но пару тестов и результат нулевой. Все я уже почти был готов сдаться. Но на этих пробах и ошибках мои знания только увеличивались. Но что толку, тогда думал я. Ведь я так и не написал, то, что было нужно. Все те статьи, которые я нашел в Интернете, тоже не помогали. А у большинства людей были те же проблемы, как и у меня. Почему перехват событий происходит только в моей программе? Как передать значения из ловушки в основную программу? Ведь я написал глобальную ловушку? Ведь ловушка размещена в библиотеке, а не в модуле. Ведь данные, полученные из ловушки нужно где то сохранять. Ладно, еще можно попытаться писать в текстовый файл (если это простенький клавиатурный шпион). Но писать в файл из библиотеки категорически не рекомендуется. Я на своих экспериментах в этом убедился. А что если писать скриншоты, это уже не текст и соответственно время записи уже не такое. Позже случилось так, что я забросил это дело, хотя и возвращался время от времени, все хотел понять, что я делаю не так.

Но произошло чудо. Попала ко мне в руки книга Юрия Ревича “Нестандартные приемы программирования на Delphi”. Полистав которую, я был немного шокирован, вот оно то, что мне нужно. А не получалось ранее, потому что нужно передавать значения из ловушки через MAP файлы (Memory Mapped Files) – отображение файла на память.

Ну что начинаем писать шпиона?

И так все постепенно…
В операционной системе Microsoft Windows ловушкой называется механизм перехвата особой функцией событий (таких как сообщения, ввод с мыши или клавиатуры) до того, как они дойдут до приложения. Эта функция может затем реагировать на события и, в некоторых случаях, изменять или отменять их. Функции, получающие уведомления о событиях, называются фильтрующими функциями и различаются по типам перехватываемых ими событий. Пример – фильтрующая функция для перехвата всех событий мыши или клавиатуры. Чтобы Windows смогла вызывать функцию фильтр, эта функция должна быть установлена, то есть, прикреплена к ловушке (например, к клавиатурной ловушке).

Все, хватит теории, начинаем писать. Мы напишем простой клавиатурный шпион. Почему простой? Да потому что шпион должен не только перехватывать нажатые клавиши, а также следить за приложениями, в которых эти клавиши нажимались. Еще можно записывать и время запуска ловушки, приложения которое имело на момент нажатия клавиш фокус ввода и т.д. Но это вы уже сможете реализовать сами.
Создаем новый проект. Бросаем TMemo и две кнопки:

Затем объявляем константу с пользовательским сообщением:

Теперь нам надо импортировать процедуры запуска и удаления ловушки. Хотя библиотека еще не написана, но это так, чтобы потом не возвращаться. Добавьте у себя такие вот строки:

На следующем шаге создадим, обработчики для кнопок и в них напишем следующий код:

В обработчике кнопки «Start» есть переменные, которые не были объявлены, эти переменные находятся в другом модуле, который мы рассмотрим позже.

Теперь нам необходимо написать обработчик для нашего пользовательского сообщения. Для этого поместите следующий прототип процедуры в область private:

Ну а сам обработчик настолько прост, что проще некуда.

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

Сама функция предназначена только для конвертирования клавиш по их коду. Поэтому на ней мы останавливаться не будем. Скажу только что, она распознает все символы от A до Z, от a до z, от А до Я и от а до я и т.д. То есть по регистру и по раскладке.

Теперь нам предстоит написать модуль для создания общей разделяемой области. Модуль у меня называется IniHook, с дополнительными секциями initialization и finalization. Создаем новый модуль и называем его IniHook. Подключаем два модуля: Windows и Messages.

Объявим указатель на переменную THookInfo
Затем напишем запись THookInfo.

Далее объявляем две переменные:

Через переменную DataArea мы будем обращаться к полям записи THookInfo, hMapArea будет содержать дескриптор объекта «проецируемого» файла.

Далее в разделе initialization вызовем функцию CreateFileMapping и присвоим ее возвращенное значение переменной hMapArea. Затем вызовем функцию MapViewOfFile и присвоим ее возвращенное значение переменной DataArea. Исходный код смотрите ниже:

Кратко рассмотрим использованные здесь функции.
Создание и использование объектов файлового отображения осуществляется посредством функций Windows API. Этих функций три:

  • CreateFileMapping;
  • MapViewOfFile;
  • UnMapViewOfFile.

Отображаемый файл создается операционной системой при вызове функции CreateFileMapping. Этот объект поддерживает соответствие между содержимым файла и адресным пространством процесса, использующего этот файл. Функция CreateFiieMapping имеет шесть параметров:

Следующая задача – спроецировать данные файла в адресное пространство нашего процесса. Этой цели служит функция MapviewOfFile. Функция MapViewOfFile имеет пять параметров:

Немного отвлеклись? Ну что продолжаем. Далее нам необходимо в разделе finalization написать код, который убирает файл из памяти.

Здесь только следует отметить, что функция UnMapViewOfFile должна вызываться перед функцией CloseHandle. То есть ни в коем случае этот порядок нарушать нельзя.

Ну что модуль мы написали, теперь его необходимо подключить к основной программе. Осталось нам написать саму ловушку, которая будет размещаться в библиотеке. А пока идем пить кофе. Попили? Продолжаем. Начните новый проект, только не для написания приложения, а для библиотеки. Для этого нужно выбрать команду File/New/Other, затем перед вами откроется следующее окно:

Найдите элемент DLL Wizard и дважды щелкните на нем. И Delphi создаст пустой проект динамической библиотеки. Не забудьте сразу сохраниться. Ниже представлен исходный код моей библиотеки:

Теперь обо всем по порядку. Для начала необходимо подключить три модуля: Windows, Messages, SysUtils и один наш модуль, а именно IniHook Чтобы мне не держать копии модуля в каталоге самой программы и в каталоге библиотеки я его вынес в общий каталог, в котором находятся каталоги основной программы и библиотеки. Но вы можете его подключить стандартным способом, то есть, объявив его со всеми модулями, только тогда вам придется положить этот модуль в каталог с библиотекой. Это уже дело вкуса.


Теперь, как и в основной программе, мы объявили константу WM_ReadWithHook = WM_USER + 120, для нашего пользовательского сообщения. Функция KeyboardProc – это обработчик нашей ловушки. Эта функция имеет три параметра. Сейчас мы и их рассмотрим более подробно. При установленном типе ловушки WH_KEYBOARD, эти параметры могут иметь следующие значения:

  • nCode: Определяет код использования процедуры ловушки, чтобы определить как обработать сообщение. Этот параметр может иметь несколько значений.
  • AC_ACTIONWParam и LParamпараметры содержат информацию относительно нажатой клавиши. Сообщения другого типа у нас нет необходимости обрабатывать.
    • WParam: Определяет код с виртуальным идентификатором клавиши, которая генерировала сообщение нажатия клавиши.
    • LParam: определяет повторный счет, скэн-код, флажок расширенной клавиши, контекстный код, предыдущий флажок состояния клавиши, и флажок переходного состояния. Этот параметр может иметь комбинацию определенных значений. Но мы их рассматривать не будем, поскольку они в нашем случае не предоставляют интереса.

Функция CallNextHookEx имеет четыре параметра:

  1. (hhk)– дескриптор ловушки, возвращенный функцией SetWindowsHookEx;
  2. (Code) – определяет код перехвата;
  3. (WParam) – определяет приходящую длину в процедуре по обработке ловушки. Его значение зависит от типа установленной ловушки;
  4. (LParam) – определяет приходящую длину в процедуре по обработке ловушки. Его значение зависит от типа установленной ловушки.

В данный момент нас интересует вот эта строка кода:

В этой строке мы передаем команду нашему приложению, в котором вызывается обработчик нашего пользовательского сообщения WM_ReadWithHook = WM_USER + 120 и параметр WPARAM, который содержит код клавиши.

И, на конец я вызываю опять функцию CallNextHookEx, возвращаемое значение которой я передаю в переменную. Я заметил, что так практически никто не делает. Оно то все работает. Но в игре, к примеру «Counter-Strike» при включенной ловушке, были зависания клавиш. А после добавления в конец нашего обработчика функции CallNextHookEx, пришло все в норму.
Процедура SetHook содержит всего лишь одну строку кода:

Здесь вызывается функция установки ловушки SetWindowsHookEx. У этой функции должно быть четыре параметра:

  1. Тип ловушки. Указан WH_KEYBOARD, эта ловушка контролирует сообщения нажатия клавиш;
  2. Идентификатор, содержащий процедуру ловушки;
  3. Указатель на приложение;
  4. Идентификатор потока. Если параметр равен нулю, то используется текущий.

Процедура DelHook также имеет всего одну строку кода:

Функция UnhookWindowsHookEx имеет всего один параметр и это дескриптор ловушки.
Процедуры SetHook и DelHook объявлены как экспортные.
Ну вот и все. Вы можете скачать demo версию или полный исходник проекта.

Образовательный блог — всё для учебы

Использование события OnMessage
Для обработки сообщений в приложении можно использовать событие Application.OnMessage. Как уже говорилось ранее, цикл обработки сообщений периодически просматривает содержимое поля FOnMessage. Фактически проверяется наличие обработчика у события OnMessage. С помощью этого обработчика можно перехватить или изменить обработку любых сообщений, кроме сообщений оперативной подсказки и (напомним, что сообщения, посланные функцией SendMessage, не попадают в цикл сообщений и также не могут быть перехвачены этим обработчиком этого события).

Поле FOnMessage и событие OnMessage имеют тип указателя на метод TMessageEvent. Он объявлен следующим образом

Procedure(Var Msg: TMsg; Var Handled: Boolean) Of Object;

Таким образом, обработчик этого события должен возвращать True через параметр Handled, если сообщение обработано, иначе обработка сообщения будет продолжена.

Тогда: положить компонент Application и в OnMessage написать эту функцию:

procedure TForm1.ApplicationEvents1Message(var Msg: tagMSG;
var Handled: Boolean);
var ch:char;
begin
if CheckBox2.Checked then
if Msg.message= WM_CHAR then begin
ch:=char(msg.WParam);
case ch of
‘r’,’R’: Panel1.Color:=clred;
‘b’,’B’: Panel1.Color:=clblue;
‘g’,’G’: Panel1.Color:=clgreen;
end;
end
end;

Можно использовать и для обработки сообщений от других приложений. Поскольку код обработчика сообщения Application.OnMessage, будет выполняться для каждого сообщения, то не рекомендуется предусматривать в нем длительную обработку сообщений, поскольку это может сказаться на быстродействии приложения.

Использование перехватчиков сообщений
а) Перехваты Delphi
Можно осуществить перехват сообщения, предназначенного для окна объекта Application, с помощью метода, имеющего следующий тип:

Туре TWindowHook=Function(Var Message: TMessage): Boolean Of Object;

Такой метод следует зарегистрировать в качестве перехватчика сообщений приложения с помощью следующего метода класса TApplication:
HookMainWindow(Hook: TWindowHook);

Этот метод — перехватчик сообщения добавляется перед методом WndProc. Подобных методов можно зарегистрировать несколько.

Если один из перехватчиков сообщения типа TWindowHook в цепочке методов обрабатывающих сообщение вернет True, то это будет означать, что это сообщение обработано. Когда надобность в перехвате сообщений отпадет, зарегистрированный перехватчик сообщения можно исключить из списка действующих с помощью метода класса TApplication:

UnhookMainWindow(Hook: TWindowHook);
Type TForm 1 =Class(TForm)
// Все иные необходимые объявления
Public
Function AppHook(Var Msg: TMessage): Boolean; // Объявление метода
End;
Function TForml.AppHookfVar Msg: TMessage): Boolean; Begin
Editl.Text:=’3ro Hook!’; // Вывод сообщения
Result:=True;
End;
Procedure TForml.ButtonlClick(Sender: TObject);
Begin
Application.HookMainWindow(AppHook); // Регистрация метода
End;
Procedure TForml.Button2Click(Sender: TObject);
Begin
Application.UnhookMainWindow(AppHook); // Отключение метода
End;
Procedure TForml.Button3Click(Sender: TObject);
Begin
SendMessage(Application.Handle,WM_NULL,0,0); // Посылка сообщения
End;

б) Перехваты Windows
В Windows предусмотрена функция SetWindowsHookEx, позволяющая установить функцию перехвата для доступа к сообщениям до того, как их получат другие приложения. Для этой функции определены перехваты Windows, приведенные ниже
WH_CALLWNDPROC — Позволяет контролировать сообщение до посылки сообщения конкретному окну функцией SendMessage

WH_CALLWNDPROCRET — Позволяет контролировать сообщение после того, как оно обработано процедурой окна.

WHGETMESSAGE — Позволяет контролировать сообщения, зарегистрированные в очереди сообщений

Перехватчики подобных системных сообщений должны располагаться в библиотеках Dll. Примеры подобных сообщений относительно большие и имеются в литературе.

Нестандартные сообщения

Помимо стандартных и системных сообщений Windows имеется еще три обширные категории сообщений.
а) Собственные сообщения компонентов
Целый ряд стандартных для Windows компонентов имеют набор собственных сообщений, идентификаторы которых начинаются на соответствующие буквы (ListBox — LB_**, Edit, Memo — MEM_**, ComboBox — CB_**, Button — BM_**, Scroll Bar — SBM_**).

С помощью подобных сообщений можно эмулировать активизацию компонентов, производить дополнительную настройку соответствующих компонентов, получать сведения об их параметрах. Посылка таких сообщений может производиться с помощью метода Perform или функции SendMessage.

SendMessage(Button1.Handle, BM_CLICK, 0,0); // Нажатие кнопки

б) Извещающие сообщения (notification message)
Представляют собой сообщения, посылаемые родительскому окну, когда в одном из его дочерних компонентов происходят действия, для которых предусмотрены эти сообщения. Эти сообщения генерируется только стандартными управляющими элементами Windows, такими как кнопки (BN_**), поля ввода (EN__**), списки (LBN_**) и т.п.

Можно обеспечить контроль этих сообщений, чтобы по факту их появления выполнить какие-нибудь специальные действия.

в) Сообщения устройств и компонентов Windows
В Windows предусмотрено еще около 40 групп сообщений, которые не поддерживаются Delphi. К ним можно сообщения от TrackBar (TBM_**), Tree View (TVM_**), UpDown (UDM_**), ряда других компонентов, различных элементов управления, устройств Windows, таких как видеодрайверов, панели управления и т.п. С подобными сообщениями можно познакомиться в справочной системе.

Отметим также, что в литературе имеются примеры сообщений Delphi, которые официально не декларированы в справочной системе.

Отсутствие сообщений
Когда приложение не занято обработкой поступающих к нему сообщений, можно организовать фоновую работу приложения. Фоновая работа может заключаться как в выводе заставки, сберегающей люминофор экрана монитора, так и в выполнении определенных действий.

Выделяют два основных варианта организации фоновых работ.
а) Использование события Onldle
Когда приложение не занято обработкой сообщений, то оно находится в режиме ожидания. В этот момент приложение, как уже говорилось ранее, проверяет содержимое поля FOnldle. Фактически проверяется наличие обработчика у события Onldle. Если обработчик есть, то он выполняется. Как и положено, поле FOnldle и событие Onldle имеют тип указателя на метод TIdleEvent. Он объявлен следующим образом.

Procedure(Sender: TObject; Var Done: Boolean) Of Object;

Обработчик получает логический параметр Done, по умолчанию равный True. Если установить его равным False, то обработчик будет запускаться во время ожидания сообщений. Для запуска фонового режима с определенной задержкой можно использовать компонент Timer 1, установив его свойство Enabled в True.

Положить компонент ApplicationEvents1 и сделать обработчик события для OnIdle

procedure TForm1.ApplicationEvents1Idle(Sender: TObject;
var Done: Boolean);
begin
If edit3.Text<>edit2.Text then edit3.Text:=edit2.Text;
Done:=false;
end;

Поскольку во время работы обработчика приложение не обрабатывает поступающие сообщения, следует делать его коротким. В долго работающие обработчики необходимо включать метод ProcessMessages, который запускает механизм обработки поступающих сообщений. Например, так:
Application.ProcessMessages;

б) Использование функции PostMessage
Менее распространенный способ реализации фоновых вычислений заключается в том, что приложение посылает самому себе пользовательское сообщение по окончании каждого шага фонового процесса:

PostMessage(Handle, WM_USER, 0,0);

Приложение через некоторое время получит это сообщение и сможет в качестве реакции на него выполнить следующий шаг фоновых вычислений. Подобный вариант особенно хорош тогда, когда выполняемые действия могут быть разбиты на отдельные непродолжительные этапы. Это может быть резервное копирование базы данных, обновление статистик в СУБД и т.п.

Как отключить сообщение WM_ONPAINT для моего окна в С++ Builder?

Я делаю рисование в другом потоке в моем окне, и когда моя форма получает WM_PAINT (или WM_ERASE. ), форма стирается, чтобы начать рисовать. Мне нужно остановить это сообщение от моего окна. Как это можно сделать?

Этот код просто рисует текущее время и день недели в строковом типе в основной форме, когда он получает сообщение WM_APP + 1.

Предполагая, что у вас есть приложение WIn32, тогда у вас будет аналогичный код: Ищите мои комментарии в коде.

Я не знаю CBuilder, но все приложения Win32 имеют обработчик сообщений Window. Вы должны найти, как это делается и где в вашей форме, и поймать эти два сообщения и ничего не делать.

Игнорирование проблем безопасности потоков для объекта холста С++ builder.

Вы всегда должны делать свою картину с помощью соответствующего метода WM_PAINT. Хотя диспетчер окон в Windows 7 может рисовать вне BeginPaint/EndPaint, он вызывает низкопроизводительные кодовые пути, которые существуют исключительно для обратной совместимости.

Кроме того, существует ряд асинхронных событий, которые пользователь может инициировать (путем перемещения окон и т.д.), которые недействительны для области окна, если окно не обновляется с помощью WM_PAINT, тогда перед окном будет видимая латентность перекрашены.

Диспетчер окон рабочего стола (который реализует Aero Glass) действительно требует, чтобы вы использовали правильный способ перерисовки анимированного окна: используйте таймер, который вызывает InvalidateRect, чтобы аннулировать область с меняющимся содержимым: и просто обрабатывайте WM_PAINT для обработки перерисовки нового состояния.

Как перехватить оконные сообщения чужого окна?

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

Суть задачи: Есть приложение А, после обработки события, результат приходит как сообщение TE_COMPLETE. Приложению Б, нужно считать-перехватить это оконные сообщения, и выполнить свою функцию.

Помогите хотя бы примером, дальше как всё работает, по примеру сам разберусь.

Введение в перехват API для системы команд x86

В данном посте представлены способы перехвата API для системы команд x86.

Краткий обзор

В последующих параграфах мы введем читателя в перехват API: то, что мы можем сделать с его помощью, то, почему он полезен, и в его основные формы. После представления простых методов перехвата API мы охватим некоторые менее известные и используемые (если вообще используемые) методы, которые однажды могут пригодиться, а также некоторые техники, которые стоит иметь ввиду при использовании любых методов перехвата.

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

Введение

В программировании термин перехват (hooking) охватывает ряд методов, используемых для изменения или дополнения поведения операционной системы, приложений или других программных компонентов путем перехвата вызовов функций, сообщений или событий, передаваемых между программными компонентами. Код, который обрабатывает подобные перехваченные вызовы функций, события или сообщения, называется перехватчиком (hook).

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

  • Профилирование: Насколько быстро выполняются определенные вызовы функций?
  • Мониторинг: Послали ли мы корректные параметры функции X?
  • .

Более полный список примеров использования перехвата функций можно найти здесь [1] [2] .

Данный список должен свидетельствовать о полезности перехвата функций. Теперь настало время перейти к перехвату функций как таковому.

Просим читателя заметить, что данный пост не охватывает точки останова, перехват IAT и т. д., так как эти темы заслуживают отдельного поста.

Базовый перехват API

Итак, если у нас есть две функции – A и B, как мы перенаправим выполнение с функции A в функцию B? Очевидно, что мы используем инструкцию перехода, поэтому осталось лишь вычислить правильное относительное смещение.


Предположим, что функция A расположена по адресу 0×401000, а функция B – по адресу 0×401800. Далее мы определим требуемое относительное смещение. Разница между адресами данных функций составляет 0×800 байт, и мы хотим перейти из функции A в функцию B, так что нам пока не нужно беспокоиться об отрицательных смещениях.

Дальше следует хитрый момент. Предположим, что мы уже записали нашу инструкцию перехода по адресу 0×401000 (функции A), и что данная инструкция выполняется. CPU при этом сделает следующее: сначала он добавит длину инструкции к Указателю на Инструкцию [3] (или Программному Счетчику). Длина инструкции перехода равна пяти байтам, как мы установили ранее. После этого к Указателю на Инструкцию добавляется относительное смещение. Другими словами, CPU вычисляет новое значение Указателя на Инструкцию следующим образом:

instruction_pointer = instruction_pointer + 5 + relative_offset;

Поэтому для вычисления относительного смещения нам нужно переписать формулу в следующем виде:

relative_offset = function_B — function_A — 5;

Мы вычитаем 5, поскольку это длина инструкции перехода, которую CPU добавляет при запуске данной инструкции, а function_A is вычитается из function_B, так как это относительный переход. Разница между адресами функций равна, как мы помним, 0×800 байтам. (Например, если мы забудем вычесть function_A, то CPU перейдет по адресу 0×401800 + 0×401000 + 5, что, очевидно, нежелательно).

На языке ассемблера перенаправление из функции A в функцию B будет выглядеть примерно так.

До внедрения перехватчика в начале функции можно видеть несколько исходных инструкций. После внедрения они перезаписываются инструкцией jmp. Первые три исходные инструкции занимают 6 байт вместе взятые (т. е. можно видеть, что инструкция push ebx расположена по адресу 0×401006). Наша инструкция перехода использует только пять байтов, что оставляет нам один дополнительный байт. Мы перезаписали этот байт инструкцией nop (инструкция, не делающая ничего).

Инструкции, которые мы перезаписали, мы называем украденными байтами, в следующем параграфе мы рассмотрим их более подробно.

Трамплины

Следующий фрагмент кода показывает простой пример использования трамплина для запуска исходной функции из перехваченной функции. В нем function_A_trampoline обозначает трамплин к функции A (функции, которая была перехвачена).

// this is the hooked function void function_A(int value, int value2);
// this is the Trampoline with which we can call
// function_A without executing the hook void (*function_A_trampoline)(int value, int value2);
// this is the hooking function which is executed
// when function_A is called void function_B(int value, int value2) <
// alter arguments and execute the original function function_A_trampoline(value + 1, value2 + 2); >

В примере перехвата, который мы только что обсудили, мы перезаписали первые пять байт функции A. Чтобы запустить исходную функцию без перехватчика, нам понадобится запустить байты, которые мы перезаписали при установке перехватчика, а затем перейти по адресу, на несколько байт большему, чем адрес функции A (чтобы пропустить код перехватчика). Это в точности то, что происходит в вышеуказанном фрагменте кода, но в коде на языке C вы этого не видите из-за относительно высокой абстракции. В любом случае, картинки говорят больше, чем тысяча слов, так что… вот изображение, которое показывает устройство трамплина.

На изображении вы видите следующий поток выполнения: запускается функция A, выполняется перехватчик, передавая тем самым управление функции B. Функция B выполняет некие действия, но на адресе 0×401820 она хочет выполнить исходную версию функции A (без перехватчика), где и приходит на помощь трамплин. Про трамплин можно написать множество слов, но одно изображение объясняет его полностью. Трамплин состоит из двух частей: исходных инструкций и перехода на ту часть функции A, которая следует за перехватчиком. Если вы вернетесь к изображению из раздела Базовый перехват API, вы увидите, что перезаписанные перехватчиком инструкции теперь располагаются в трамплине. Переход в трамплине вычисляется по указанной ранее формуле, однако, в данном случае адреса и смещения немного отличаются, так что формула принимает следующий вид:

relative_offset = (function_A_trampoline + 6) — (function_A + 6) — 5;

Отметим, что мы переходим с адреса 0×402006 (function_A_trampoline + 6) на 0×401006 (function_A + 6). Данные адреса можно проверить по рассмотренному ранее изображению. Ничего особенного, кроме того факта, что у нас получилось отрицательное смещение. Но это не доставит нам проблем (CPU сделает всю грязную работу по корректному представлению отрицательного относительного смещения).

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

Продвинутые методы перехвата

Обнаружение перехвата для примера, рассмотренного в разделе «Базовый перехват API», можно осуществить так:

Суть в том, что 0xe9 – опкод инструкции безусловного перехода с 32-битным относительным смещением. ПО, которое мы перехватываем, может обнаружить или не обнаружить подобный перехватчик. В любом случае, далее мы обсудим разные методы, которые пытаются обойти подобные алгоритмы обнаружения. (Заметим, что программы вроде GMER [4] обнаруживают все типы методов перехвата, так как сверяют виртуальную память с физическим образом на диске).

Метод I: вставка Nop в начало

По сути, вместо записи по адресу функции A, например, инструкции перехода, мы сначала запишем инструкцию nop (не делающую ничего), за которой уже последует функция перехода. Применяя данную технику, имейте в виду, что инструкция перехода теперь будет располагаться по адресу 0×401001 (function_A + 1), и это изменит относительное смещение на единицу.

Вот изображение, иллюстрирующую данную технику.

Поскольку первая инструкция функции A теперь – nop (а не jmp), чтобы обнаружить перехватчик, нам нужно переписать метод обнаружения подобным образом:

unsigned char *addr = function_A; while (*addr == 0x90) addr++; if(*addr == 0xe9) <
printf(«Hook detected in function A.\n»); >

По существу, он пропускает любое количество последовательных инструкций nop, имеющих опкод 0×90, и проверяет присутствие инструкции перехода после всей цепочки nop-ов.

Метод II: Push/Retn

Этот метод занимает 6 байт и выглядит следующим образом. Отметим, что инструкция push принимает абсолютный адрес, а не относительный.

Обнаружение данного метода может выглядеть так, как показано ниже. Однако, учтите, что помещение nop-инструкций в начало или между инструкциями push и retn не позволит данному коду обнаружить перехватчик.

Как вы наверное уже догадались, 0×68 – опкод инструкции push, а 0xc3 – опкод инструкции retn.

Метод III: Числа с плавающей запятой

Данный пример похож на метод push/retn: мы помещаем в стек фиктивное значение, которое затем перезаписываем настоящим адресом, после чего выполняем retn.

Примечательна же техника тем, что вместо хранения адреса перехода в виде 32-битного значения мы храним его в виде 64-битного числа с плавающей запятой. Далее мы считываем его инструкцией fld и преобразуем в 32-битное значение инструкцией fistp.

Следующее изображение демонстрирует данную технику. Перехватчик имеет размер 11 байт, что немного больше, чем для предыдущих методов, но это не так страшно. Также заметим, что floating_point – указатель на 64-битное число с плавающей запятой, которое равно адресу нашей перехватывающей функции (функции B).

Получить нужное значение с плавающей точкой довольно просто:

double floating_point_value = (double) function_B;

Функция B здесь, как и в других примерах,– наша перехватывающая функция.

Вызов исходной функции делается через трамплин, как и в других методах. (Трамплин в данном случае должен содержать как минимум инструкции, которые находятся в первых одиннадцати байтах функции).

Очевидно, что это только верхушка айсберга: существует множество операций с плавающей точкой, которые подойдут в данной ситуации. Например, вы можете вычислять адрес перехватывающей функции путем умножения значения на число π (которое можно получить с помощью инструкции fldpi).

Метод IV: MMX/SSE

Обе техники используют, как и метод с плавающей запятой, инструкции push/retn. Первый метод использует MMX-инструкции, в частности инструкцию movd. Она, как и инструкция fistp, позволяет прочитать значение из памяти и сохранить значение в стеке. Второй метод с SSE-инструкциями также использует инструкцию movd. Единственное различие между этими двумя методами в том, что MMX-инструкции оперируют 64-битными регистрами, тогда как SSE оперируют 128-битными регистрами. (Хотя в нашем случае это неважно, поскольку инструкция movd позволяет читать и записывать 32-битные значения).

Раз эти техники похожи на метод с плавающей запятой, за исключением используемых инструкций, то для них не будет отдельного рисунка (в статье и так их предостаточно).

Метод V: косвенный переход

Косвенный переход имеет длину 6 байтов, и пример перехвата выглядит так:

Заметим, что hook_func_ptr обозначает адрес, по которому находится адрес нашей перехватывающей функции (т. е. B).

Метод VI: инструкция Call

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

Данная техника работает как для прямого, так и для непрямого варианта инструкции call. Далее следует рисунок, иллюстрирующий рассмотренный метод.

Другие методы

Другие методы I: Hotpatching

Если функция допускает так называемый Hotpatch, она уже определенным образом подготовлена для перехвата. Первой инструкцией функции в этом случае будет mov edi, edi (два байта длиной), а перед собственно функцией расположены 5 nop-инструкций. Это позволяет разместить инструкцию близкого перехода (ту, что имеет длину два байта и принимает 8-битное относительное смещение) по адресу собственно функции (перезаписав инструкцию mov edi, edi) и обычную инструкцию перехода с 32-битным относительным смещением на место nop-инструкций.

Вот и все, что касается данной техники. Отметим, что вместо хотпатчинга подобной функции возможно также выполнить перехват одним из ранее рассмотренных методов, поместив перехватчик по адресу function+2, где два означает размер инструкции, вставленной компилятором для хотпатчинга (В этом случае остается возможность применения Hotpatch, несмотря на установку перехвата одним из наших излюбленных методов).

Вот изображение, иллюстрирующее хотпатчинг. Перехватываемой функцией здесь является MessageBoxA, а перехватывающей – hook_MessageBoxA (т. е. MessageBoxA = функция A, hook_MessageBoxA = функция B).

Другие методы II: Методы для классов C++

Эта техника относится к перехвату методов классов C++. Методы классов C++ используют так называемое соглашение вызова __thiscall [5] (по крайней мере в Windows).

Соглашение вызова __thiscall хранит указатель на информацию об объекте (к которому можно обратиться в методах класса через переменную this) в регистре ecx перед вызовом метода класса. Другими словами, если мы хотим перехватить метод класса, необходимо особое внимание.

В дальнейшем обсуждении мы будем использовать следующий фрагмент кода, определяющий функцию (function_A), которую мы хотим перехватить.

Так как мы хотим перехватывать функции C++ обычными функциями C, нам нужно, чтобы первым параметром перехватывающей функции был указатель this. Пример перехватывающей функции и трамплин (который мы обсудим позднее) выглядят следующим образом. Отметим, что вместо this используется имя переменной self из-за того, что this – зарезервированное в C++ ключевое слово.

void (*function_A_trampoline)(void *self, int value, int value2, int value3);
void function_B(void *self, int value, int value2, int value3)

Чтобы иметь возможность перехватывать методы классов C++ из обычных C-функций, нам придется изменить стек, поскольку нам нужно вставить в него указатель this. Следующий пример представляет разметку стека при вызове функции A (слева) и разметку, которую мы хотим иметь при вызове функции B (перехватывающей функции). Отметим, что ecx содержит значение указателя this, а вершина стека – это адрес, по которому расположен return_address.

К счастью для нас, мы можем вставить указатель this всего двумя инструкциями – довольно лаконично. Первый шаг – обмен значений указателя this (регистра ecx) и вершины стека (return_address). После данного обмена на вершине стека окажется указатель this, а в регистре ecx – return_address. Теперь мы можем просто положить значение регистра ecx на стек, и стек станет выглядеть в точности так, как мы хотели (см. изображение).

Вот ассемблерное представление перехвата метода класса C++.

Мы рассмотрели часть, касающуюся перехвата. Однако, нам также нужно адаптировать наш трамплин, поскольку трамплин принимает указатель this в качестве первого аргумента. Стек, который мы имеем и стек, к которому мы хотим прийти, выглядят следующим образом. (Очевидно, что значение this после всех манипуляций должно снова оказаться в регистре ecx).

Мы выполняем действия, в точности обратные тем, которые делали в ходе перехвата функции. Сначала мы выталкиваем из стека return_address, так что стек теперь указывает на this, а регистр ecx принимает значение return_address. Теперь мы обмениваем значение на вершине стека с регистром ecx, после чего стек принимает желаемый вид, а в регистр ecx загружается значение указателя this. Следующее изображение иллюстрирует трамплин, хотя инструкции из функции A на нем не показаны (т. е. данное изображение лишь показывает что особенного в трамплине к методу класса C++, а не то, что мы ранее обсуждали в разделе Трамплины).

Построение трамплинов

Вот основы построения трамплинов. У нас есть функция (функция A), которую мы хотим перехватить и метод перехвата. Сейчас мы используем самый простой метод – прямой безусловный переход, который имеет размер 5 байтов. Поскольку мы будем перезаписывать первые пять байтов функции A, нам нужно взять, по меньшей мере, те инструкции, которые расположены в первых пяти байтах данной функции. Однако, последняя инструкция в этих пяти байтах может оказаться довольно длинной, например, растянуться от третьего до шестого байта, что имеет место в использованном ранее примере (далее еще раз приведено иллюстрирующее его изображение).

Как вы можете видеть, третья инструкция использует шестой байт, из-за этого мы не можем просто скопировать первые пять байт, нам придется скопировать инструкцию целиком. Чтобы сделать это мы используем так называемый LDE (Length Disassembler Engine). LDE способен вычислять длину инструкции (обычно путем махинаций с предопределенной таблицей, содержащей информацию о каждом опкоде).

Поскольку LDE может вычислить для нас длину инструкции, мы можем просто продолжать получать длины инструкций, пока не найдем достаточно инструкций для покрытия первых пяти байт. На вышеуказанном рисунке видно, что нам достаточно трех инструкций, чтобы набрать длину в 6 байт, достаточную для трамплина.

Это была простая часть, поскольку инструкции, которые мы нашли, не содержали ветвлений. Однако, любые инструкции перехода, вызова и возврата также требуют особого внимания. Для начала, инструкции вызова и перехода должны указывать в трамплине на тот же адрес, на который они указывали в исходной функции. Впрочем, этого несложно добиться с помощью двух формул (одной для получения адреса инструкции вызова или перехода и другой для вычисления относительного смещения для инструкции вызова или перехода, размещенной в трамплине). Отметим также, что любые переходы, которые имеют 8-битное относительное смещение, должны быть преобразованы в инструкции перехода с 32-битными относительными смещениями (вряд ли относительное смещение между трамплином и исходной функцией будет в пределах, задаваемых 8-битным смещением).

Наконец, иногда бывает, что для нашего метода перехвата попросту не хватает места. Это случается при наличии инструкции безусловного перехода или инструкции возврата. Такое происходит, когда мы попадаем в конец функции (т. е. нам требуется переписать байты, находящиеся уже после инструкции возврата). После инструкции возврата, например, может располагаться совершенно другая функция, содержимое которой переписывать мы не собирались и в общем случае не можем. То же справедливо и для ситуации, в которой нужно переписать инструкцию безусловного перехода: функция не будет продолжать выполняться после инструкции перехода, так что перезапись участка памяти после данной инструкции может привести к неопределенным последствиям.

Многослойные перехваты

Предотвращение рекурсии перехвата

В целом, чтобы предотвратить данную проблему, нам нужно поддерживать счетчик перехватов. Всякий раз при запуске перехватчик будет производить проверку счетчика (который, очевидно будет увеличиваться при входе в перехватчик). Таким образом, мы сможем контролировать две вещи. Первая – то, что перехватчик не будет создавать вложенные перехваты (например, если мы перехватили функцию fwrite(), и в перехватывающей функции мы также вызываем fwrite(), перехватчик не отработает повторно). Вторая – максимальная вложенность рекурсии, например, не более трех вложенных перехватов. Впрочем, подобная функциональность обычно не нужна.

Отметим также, что подобный счетчик перехватов должен быть специфичным для потока. Для решения этой задачи, код, приведенный в разделе «Демонстрация», хранит счетчик в сегменте, на который указывает регистр fs [6] (данный раздел обозначает Блок Информации Потока, т. е. специфичную для потока информацию).

Демонстрация

Текущий исходный код можно найти тут, достаточно скоро он будет здесь. Реализацию описанных в данном посте методов можно посмотреть в файле hooking.c.

Ссылки

Подписывайтесь на каналы «SecurityLab» в Telegram и Яндекс.Дзен, чтобы первыми узнавать о новостях и эксклюзивных материалах по информационной безопасности.

Перехват сообщений в Билдере??

Здравствуйте! Меня интересует вопрос о перехвате сообщий Винды. Ну предположим такая ситуация: например я хочу что бы окно при нажатии на крестик в правом верхнем углу не закрывалось, а предположим сворачивалось. Или вот еще пример: Есть RichEdit и я хочу чтобы при нажатии Enter не печатался перенос каретки(на нову строку) , а допустим выполнялась передача строки на сервер или какое либо другое действие!

Как это можно реализовать в Билдере?? Заранее спасибо!

Илон Маск рекомендует:  Безопасность faq по php
Понравилась статья? Поделиться с друзьями:
Кодинг, CSS и SQL