Про установку ловушек (hook-ов) говорилось уже очень много, но как правило, применение этого процесса крайне однобоко - как ты наверно догадываешься, речь идет о клавиатурных шпионах.
Но ведь кроме установки ловушки типа WH_KEYBOARD есть еще несколько типов.
Один из них - WH_GETMESSAGE. Как следует из названия, он перехватывает все сообщения, посылаемые программой.
Возникает резонный вопрос - для чего это может быть нужно?
Практическое применение этой функции может быть очень широким - это и лог перехвата сообщений меню, зная который легко можно восстановить в чем заключалась работа юзера с прогой, и блокировка определенных пунктов меню, и многое другое.
Для начала немного теории. Как известно, чтобы хук был поставлен на все процессы в системе
(что нам собственно и нужно), его необходимо загружать в отдельном процессе, для этого мы выносим все процедуры, отвечающие за установку хука, в отдельную dll, которая загружается из основной программы. В dll-ке создаются "экспортные" функции, которые могут быть загружены из основной проги, и конечно же сама процедура по перехвату сообщений.
В основной программе эта dll-ка загружается, затем получаем указатель на необходимую процедуру, которая собственно и установит хук, и запускаем ее.
Для того чтобы работать с нашей dll нужно создать интерфейсную часть. Для этого создадим новый проект и поместим на него 2 кнопки - одна из них будет запускать процедуру перехвата, а другая, соответственно, останавливать.
Примерный код на дельфе будет выглядеть так:
program MsgHook;
var
Hdll : HWND;
// дескриптор загружаемой
dll
type
HkProc = procedure ; stdcall;
На месте же самих процедур кнопок напишем следующее:
procedure TForm1.Button1Click(Sender: TObject);
var
UnHook,hook: HkProc;
begin
@hook:= nil; // инициализируем переменную hook
@Unhook:= nil; // инициализируем переменную hook
Hdll:= LoadLibrary(PChar('hook1.dll'));
// загружаем DLL
if Hdll > HINSTANCE_ERROR then
// Если нет ошибок то...
begin
@hook:=GetProcAddress(Hdll, 'hook');
// Указатель на необходимую процедуру
@Unhook:=GetProcAddress(Hdll, 'Unhook');
// указатель на вторую процедуру
hook;
// собственно устанавливаем хук.
end
else
ShowMessage('Ошибка при загрузке DLL !');
end;
procedure TForm1.Button2Click(Sender: TObject);
var
UnHook,hook: HkProc;
begin
@hook:= nil; // инициализируем переменную hook
@Unhook:= nil; // инициализируем переменную hook
if Hdll > HINSTANCE_ERROR then
begin
// если всё без ошибок, то идем дальше
@hook:=GetProcAddress(Hdll, 'hook');
// @hook: указатель на необходимую процедуру
@UnHook:=GetProcAddress(Hdll, 'Unhook');
// аналогично
Unhook;
// вызываем нужную процедуру - убираем хук.
end;
end;
А теперь самое интересное - собственно dll в которой и происходит работа с хуками.
library DLL_with_hook;
uses
Windows,
Messages;
var
SysHook : HHook = 0; // ошибка
Wnd : Hwnd = 0;
// это и есть основная функция проги, эта процедура вызывается
// каждый раз когда окну подается сообщение.
function SysMsgProc(code : integer; wParam : word; lParam : longint) : longint; stdcall;
var
hw,hw1:HWND;
begin
if code = HC_ACTION then
begin
// TMsg(Pointer(lParam)^) - собственно и есть сообщение
// которое нам нужно. Далее работаем с его параметрами.
Wnd:=TMsg(Pointer(lParam)^).hwnd;
// окно, которому принадлежит данное сообщение
// Дальше смотрим, с какими сообщениями будем работать.
// В данном случае это сообщения, посылаемые из меню:
// в таком случае параметр wParam будет отражать собственно ID
// пункта меню, а lParam должен быть равен 0.
if (TMsg(Pointer(lParam)^).message = WM_COMMAND) then
begin
if (TMsg(Pointer(lParam)^).wParam=Nomer_1 then
(TMsg(Pointer(lParam)^).wParam:=Nomer_2;
// теперь при выборе одного пункта меню будет
выполнен совсем
// другой, Nomer_1 и Nomer_2 - соответственно изменяемый и
// замещающий его ID пунктов меню
end;
end;
// передаем мессагу дальше - следующим хукам
Result:= CallNextHookEx(SysHook, Code, wParam,
lParam);
end;
// собственно процедура снятия хука:
procedure Unhook() export; stdcall;
begin
// смотрим результат снятия хука
if UnhookWindowsHookEx(SysHook) then
// если все нормально тогда..
MessageBox(0, 'Unhook - OK!', 'HookMsg', 0)
else
MessageBox(0, 'Unhook - error!', 'HookMsg', 0)
SysHook := 0;
end;
// а это процедура установки хука.
procedure hook() export; stdcall;
begin
SysHook := SetWindowsHookEx(WH_GETMESSAGE, @SysMsgProc, HInstance, 0);
// WH_GETMESSAGE - тип хука, в данном случае - перехват
// сообщений, @SysMsgProc - указатель на процедуру обработки,
// и последний интересующий нас параметр - 0, т.е. хук
// действует во всех потоках
if SysHook <> 0 then
// если результат установки отличен от нуля (результат
// собственно и есть идентификатор хука - то все ок
MessageBox(0, 'Hook set - OK!', 'HookMsg', 0)
else
MessageBox(0, 'Hook set - Error!', 'HookMsg', 0)
end;
// экспортируемые функции, которые будут использованы в
// основной программе
exports hook;
exports Unhook;
begin
end.
Разумеется, у каждого приложения своя структура меню, свои Menu ID. Поэтому нам нужно сделать так, чтобы наши действия различались в зависимости от приложения, сообщение которого было перехвачено.
Для этого рассмотрим, как по HWND определить приложение.
Здесь не все так просто, как может показаться на первый взгляд. Ведь для точного определения приложения нужно взглянуть на тип и/или название основного окна программы!
Хотя часто это бывает не нужно, т.к. сообщения от пунктов основного меню принадлежат главному окну, но вот другие диалоги, открываемые через основною меню, имеют свои окна.
Причем такое может происходить не один раз - то есть новые диалоговые окна могут быть открыты и из "дочернего" диалога.
В этом случае нам нужно определять родительские окна до тех пор пока такое окно не станет окном верхнего уровня.
Для определения родительского окна служит функция типа
hw2:=GetWindow(hw1,GW_OWNER);
hw1 - наше окно, hw2 - родительское, GW_OWNER показывает, что
нам необходимо получить именно родительское окно.
Аналогично действует функция GetParent:
hw2:=GetParent(hw1);
Теперь сделаем это в циле до тех пор пока найденное окно не будет окном верхнего уровня.
hw2:=GetParent(hw1);
while hw2<>0 do begin
hw1:=hw2;
// окно из родительского делаем тем, для которого нам
// собственно и нужно найти следующее родительское
hw2:=GetParent(hw1);
end;
Когда hw2 будет равно 0, то hw1 будет являться идентификатором окна верхнего уровня.
Далее поступаем стандартно - смотрим либо типа окна:
GetClassName(hw1,w_class,128);
s - типа PAnsiChar; 128-максимальная длина строки
Или смотрим заголовок окна:
GetWindowText(hw1,w_text,128);
Теперь наряду с данными о том окне, которому принадлежало сообщение, мы можем узнать какому приложению оно принадлежит.
Но опять таки этот случай не дает 100% гарантию. Некоторые из приложений
(достаточно малое количество, явно меньше 10%), например та же Дельфя ведет работу с сообщениями через окно, которое само по себе является окном верхнего уровня - это окно с пустым заголовком и типом TUtilWindow, причем таких окон там не одно и не два. Так что путь поиска родительского окна отпадает, поэтому мы будем определять приложение через имя exe файла.
ThreadId := GetWindowThreadProcessId(Handl, ProcID);
ThreadID - идентификатор нити,
ProcID - идентификатор процесса.
Эти переменные типа DWORD;
Далее, получив ID процесса, будем перебирать все процессы в системе, просматривая имя нужного exe файла перебираемого процесса. Эта технология ( с созданием снапшотов) уже неоднократно рассматривалась, поэтому остановимся лишь
на практической стороне дела.
han := CreateToolhelp32Snapshot( TH32CS_SNAPALL, 0 );
if han = 0 then exit;
// если не получилось создать - выходим
ProcStruct.dwSize := sizeof( PROCESSENTRY32 );
if Process32First( han, ProcStruct ) then
begin
repeat
EXe_name := ExtractFileName( ProcStruct.szExeFile);
P_ID:=ProcStruct.th32ProcessID;
if P_ID = procID then begin
// делаем нужные действия с сообщением.
// ...
// Exe_name - имя исполняемого файла
end;
// если нет следующего процесса - выходим
until not Process32Next( han, ProcStruct );
end;
CloseHandle( han );
end;
Типы используемых переменных:
han : THandle;
ProcStruct : PROCESSENTRY32;
Exe_Name : string;
P_ID:Cardinal;
Также для выполнения действий со снапшотами необходимо подключить tlhelp32.
В общем, надо сказать, что кроме сообщений окна могут так же перехватываться сообщения о нажатии кнопок мыши:
WM_RBUTTONDOWN (правая кнопка - Right Button)
WM_LBUTTONDOWN (левая кнопка - Left Button
WM_MBUTTONDOWN ( как ни странно - средняя - middle :)) )
И от нажатия кнопок на клаве:
WM_KEYDOWN и WM_SYSKEYDOWN
Так что зная, к какому приложению относится данное окно, можно производить действия не только над сообщениями, посылаемыми из меню, но и над многими многими другими :))
Дело лишь в твоей фантазии :))
PS: Если ты еще не понял - вышеописанный материал можно использовать и как кейлоггер, не трогая ловушку типа
WH_KEYBOARD.
PPS: Материал дан исключительно в образовательных целях. Благо тема эта для изучения более чем интересна 🙂