Перехват буфера обмена на Ассемблере


Перехват буфера обмена на Ассемблере

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

Для того, чтобы ваше приложение узнавало об изменении содержимого буфера обмена, оно должно встроиться в цепочку обработчиков буфера обмена ("наблюдателей"). Существует два основных способа это сделать. Первый способ поддерживается операционными системами от Windows 2000 и выше, он основан на использовании функции SetClipboardViewer.
wm_init:
        ; Добавить наше окно в список наблюдателей
        invoke  SetClipboardViewer, [hwnddlg]
        mov     [hNextW], eax
        ...
 
wm_close:
        ; Убрать наше окно из списка наблюдателей
        invoke  ChangeClipboardChain, [hwnddlg], [hNextW]
        ...

При инициализации диалогового окна-перехватчика вызывается функция SetClipboardViewer, которая добавляет его в цепочку наблюдателей за буфером обмена. После этого при любом изменении буфера обмена, окну будет посылаться сообщение WM_DRAWCLIPBOARD. Но тут есть один важный момент. Сразу после того, как наше окно было встроено в цепочку наблюдателей, возвращается хэндл окна, следующего в цепочке. Когда наше приложение заканчивает работу с буфером обмена, оно обязано послать сообщение WM_DRAWCLIPBOARD следующему в цепочке окну.
        ; Отправить сообщение следующему окну
        invoke  SendMessage, [hNextW], WM_DRAWCLIPBOARD, 0, 0

А при закрытии окна-перехватчика, надо обязательно уведомить об этом систему, сообщив ей через функцию ChangeClipboardChain хэндл закрываемого окна и хэндл окна, следующего по цепочке наблюдателей. Точно так же должны поступать и остальные перехватчики. При любом изменении цепочки, всем ее членам системой отправляется сообщение WM_CHANGECBCHAIN. Наше приложение также должно его обработать примерно следующим образом:
        ; Удаляется известное нам окно?
        mov     eax,[wparam]
        cmp     eax,[hNextW]
        jne     unknown_handle
 
        ; Запомнить новый хэндл следующего обработчика
        mov     eax,[lparam]
        mov     [hNextW],eax

Если удаляется обработчик, который мы знали в качестве следующего в цепочке, то нам надо запомнить у себя новый хэндл окна, которое теперь будет являться следующим. Если удаляемое окно нам неизвестно, то просто оповещаем об изменении цепочки следующее окно, отправив ему сообщение WM_CHANGECBCHAIN с теми же параметрами, что были получены.
unknown_handle:
        ; Передать сообщение следующему окну
        invoke  SendMessage, [hNextW], WM_CHANGECBCHAIN, [wparam], [lparam]

Этот способ универсальный, но несколько сложен в реализации. Также слабым звеном тут является предположение, что все программы-перехватчики, встроенные в цепочку наблюдения за буфером обмена, всегда работают корректно и всегда отсылают другим участникам правильные сообщения. На деле это может быть совсем не так. Операционная система, конечно, пытается корректировать такие ситуации, но это не всегда получается. Если в исходнике из приложения к статье закомментировать строчку с отправкой сообщения WM_DRAWCLIPBOARD и запустить две копии программы, то перехватывать буфер обмена сможет только одна из них.

Второй, более современный способ, доступен в операционных системах, начиная с Windows Vista. Здесь используется функция AddClipboardFormatListener.
wm_init:
        ; Добавить наше окно в список наблюдателей
        invoke  AddClipboardFormatListener,[hwnddlg]
        ...
 
wm_close:
        ; Убрать наше окно из списка наблюдателей
        invoke  RemoveClipboardFormatListener,[hwnddlg]
        ...

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

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

Осталось выяснить еще один вопрос. Как узнать, что именно содержится в буфере обмена? Например, нас интересуют только текстовые строчки, а картинки и всякие эксельные таблицы должны пролетать мимо. Для этого есть функция IsClipboardFormatAvailable.
        ; В буфере обмена находится текст?
        invoke  IsClipboardFormatAvailable,CF_TEXT
        or      eax,eax
        jz      not_text

В приложении пример двух программ, реализующих оба метода перехвата буфера обмена. Если в буфер обмена копируется текст, то он сразу же появится в окне перехватчика.
Добавлено: 11 Сентября 2013 10:46:56 Добавил: Андрей Ковальчук Нравится 0
Добавить
Комментарии:
Нету комментариев для вывода...