Blog. Just Blog

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

Версия для печати Добавить в Избранное Отправить на E-Mail | Категория: Образ мышления: Assembler | Автор: ManHunter
Перехват буфера обмена на Ассемблере
Перехват буфера обмена на Ассемблере

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

Для того, чтобы ваше приложение узнавало об изменении содержимого буфера обмена, оно должно встроиться в цепочку обработчиков буфера обмена ("наблюдателей"). Существует два основных способа это сделать. Первый способ поддерживается операционными системами от Windows 2000 и выше, он основан на использовании функции SetClipboardViewer.
  1. wm_init:
  2.         ; Добавить наше окно в список наблюдателей
  3.         invoke  SetClipboardViewer, [hwnddlg]
  4.         mov     [hNextW], eax
  5.         ...
  6.  
  7. wm_close:
  8.         ; Убрать наше окно из списка наблюдателей
  9.         invoke  ChangeClipboardChain, [hwnddlg], [hNextW]
  10.         ...
При инициализации диалогового окна-перехватчика вызывается функция SetClipboardViewer, которая добавляет его в цепочку наблюдателей за буфером обмена. После этого при любом изменении буфера обмена, окну будет посылаться сообщение WM_DRAWCLIPBOARD. Но тут есть один важный момент. Сразу после того, как наше окно было встроено в цепочку наблюдателей, возвращается хэндл окна, следующего в цепочке. Когда наше приложение заканчивает работу с буфером обмена, оно обязано послать сообщение WM_DRAWCLIPBOARD следующему в цепочке окну.
  1.         ; Отправить сообщение следующему окну
  2.         invoke  SendMessage, [hNextW], WM_DRAWCLIPBOARD, 0, 0
А при закрытии окна-перехватчика, надо обязательно уведомить об этом систему, сообщив ей через функцию ChangeClipboardChain хэндл закрываемого окна и хэндл окна, следующего по цепочке наблюдателей. Точно так же должны поступать и остальные перехватчики. При любом изменении цепочки, всем ее членам системой отправляется сообщение WM_CHANGECBCHAIN. Наше приложение также должно его обработать примерно следующим образом:
  1.         ; Удаляется известное нам окно?
  2.         mov     eax,[wparam]
  3.         cmp     eax,[hNextW]
  4.         jne     unknown_handle
  5.  
  6.         ; Запомнить новый хэндл следующего обработчика
  7.         mov     eax,[lparam]
  8.         mov     [hNextW],eax
Если удаляется обработчик, который мы знали в качестве следующего в цепочке, то нам надо запомнить у себя новый хэндл окна, которое теперь будет являться следующим. Если удаляемое окно нам неизвестно, то просто оповещаем об изменении цепочки следующее окно, отправив ему сообщение WM_CHANGECBCHAIN с теми же параметрами, что были получены.
  1. unknown_handle:
  2.         ; Передать сообщение следующему окну
  3.         invoke  SendMessage, [hNextW], WM_CHANGECBCHAIN, [wparam], [lparam]
Этот способ универсальный, но несколько сложен в реализации. Также слабым звеном тут является предположение, что все программы-перехватчики, встроенные в цепочку наблюдения за буфером обмена, всегда работают корректно и всегда отсылают другим участникам правильные сообщения. На деле это может быть совсем не так. Операционная система, конечно, пытается корректировать такие ситуации, но это не всегда получается. Если в исходнике из приложения к статье закомментировать строчку с отправкой сообщения WM_DRAWCLIPBOARD и запустить две копии программы, то перехватывать буфер обмена сможет только одна из них.

Второй, более современный способ, доступен в операционных системах, начиная с Windows Vista. Здесь используется функция AddClipboardFormatListener.
  1. wm_init:
  2.         ; Добавить наше окно в список наблюдателей
  3.         invoke  AddClipboardFormatListener,[hwnddlg]
  4.         ...
  5.  
  6. wm_close:
  7.         ; Убрать наше окно из списка наблюдателей
  8.         invoke  RemoveClipboardFormatListener,[hwnddlg]
  9.         ...
Немного похоже на первый способ, но гораздо проще в реализации. После добавления окна в список наблюдателей, при каждом изменении буфера обмена, окну будет приходить сообщение WM_CLIPBOARDUPDATE. Все просто, никаких цепочек, никого не надо ни о чем уведомлять, достаточно только убрать окно из списка наблюдателей при его закрытии при помощи функции RemoveClipboardFormatListener.

Третий способ основан на использовании функции GetClipboardSequenceNumber. Она возвращает текущий номер буфера обмена. В документации он почему-то назван "серийным номером", но при чем тут "серийный" я не совсем понял. При любом изменении буфера обмена, а также при его очистке, этот номер увеличивается на единичку. Соответственно, отслеживая изменения этого номера, можно получать информацию, что содержимое буфера обмена изменилось и его можно запросить. Опрос производится по таймеру:
  1.         cmp     [msg],WM_TIMER
  2.         je      wm_timer
  3.         ...
  4.  
  5. wm_timer:
  6.         ; Получить текущее значение счетчика
  7.         invoke  GetClipboardSequenceNumber
  8.         cmp     eax,[clip]
  9.         ; Значение не изменилось
  10.         je      processed
  11.         ; Сохранить новое значение
  12.         mov     [clip],eax
  13.         ...
  14.  
  15. wm_init:
  16.         ; Получить текущий номер буфера обмена
  17.         invoke  GetClipboardSequenceNumber
  18.         mov     [clip],eax
  19.         ; Установить таймер на интервал 1 мс
  20.         invoke  SetTimer,[hwnddlg],1,1,NULL
  21.         ...
  22.  
  23. wm_close:
  24.         ; Убрать таймер
  25.         invoke  KillTimer,[hwnddlg],1
  26.         ...
Минус этого способа в том, что теоретически между двумя последовательными срабатываниями таймера может произойти более одного изменения содержимого буфера обмена. Даже если использовать высокоточный мультимедийный таймер, такая вероятность хоть и становится ничтожно малой, но все равно сохраняется. В остальном способ отличный, не требует никаких очередей, никаких перехватчиков, никак не зависит от остальных приложений.

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

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

Пример программы с исходным текстом (FASM)Пример программы с исходным текстом (FASM)

Clipboard.Hook.Demo.zip (6,756 bytes)


Поделиться ссылкой ВКонтакте
Просмотров: 7169 | Комментариев: 5

Внимание! Статья опубликована больше года назад, информация могла устареть!

Комментарии

Отзывы посетителей сайта о статье
ManHunter (05.03.2021 в 11:53):
Добавил третий способ отслеживания через GetClipboardSequenceNumber. Статью дополнил, архив обновлен.
Марат (24.08.2013 в 12:52):
...Спасибо Вам...
brute (23.08.2013 в 20:04):
да стебусь я по случаю пятницы) Давно хотел на любимом PB переводчик в стиле QDictionary запилить(самодельные словари есть), поэтому статья весьма полезна!
ManHunter (23.08.2013 в 19:46):
А зачем копировать текст из окна самой программы? Можно добавить проверку владельца окна, но это уже выходит за рамки примера.
brute (23.08.2013 в 19:01):
первый пример не работает, если копировать текст из самого окна примера. Если запустить две копии первой программы и копировать текст в одной из них, то корректно отображает его только одна.. жаль, что второй пример на XP не запускается..:)

Добавить комментарий

Заполните форму для добавления комментария
Имя*:
Текст комментария (не более 2000 символов)*:

*Все поля обязательны для заполнения.
Комментарии, содержащие рекламу, ненормативную лексику, оскорбления и т.п., а также флуд и сообщения не по теме, будут удаляться. Нарушителям может быть заблокирован доступ к сайту.
Наверх
Powered by PCL's Speckled Band Engine 0.2 RC3
© ManHunter / PCL, 2008-2024
При использовании материалов ссылка на сайт обязательна
Время генерации: 0.1 сек. / MySQL: 2 (0.0069 сек.) / Память: 4.5 Mb
Наверх