Перехват функций MAPI

([internals] Internals.com newsletter #1) from 10/8/2001. Оригинал здесь.

Дорогой подписчик Internals.com,

Я должен принести Вам свои извинения. Как Вы, вероятно, знаете, я почти год не мог запустить этот бюллетень. Это был лихорадочный период, в течение которого у меня не было времени для развития Internals.com. Теперь я ручаюсь, что и Internals.com и этот бюллетень будут обновляться более регулярно и часто. Благодарю Вас за Ваше терпение, я надеюсь, что Вы получите удовольствие от сегодняшнего выпуска бюллетеня от Internals.com.



Перехват функций MAPI

Пока продолжалась работа над MailControl, моим персональным почтовым брандмауэром, я наткнулся на очень простой и элегантный способ перехвата (hooking) функций MAPI. В общем случае, перехват функций API – не легкая задача, особенно в масштабах всей системы. Тем не менее, в некоторых случаях с помощью нескольких программистских трюков можно легко осуществить вызов перехваченной функции. Один из этих случаев - MAPI, который является стандартным Windows API для передачи и приема email сообщений.

MAPI в настоящее время включает в себя больше одного API. Он предлагает три различных пути для работы с email сообщениями. Как следует из названия, Simple MAPI - это самый простой из трех, предлагающий ограниченный набор из 12 функций, позволяющих приложениям отправлять и принимать email сообщения. Common Messaging Calls (CMC) API это другая форма MAPI, которая предназначена для кросс-платформенного использования в языках высокого уровня, таких как C и C++. И для тех, кто предпочитает скриптовые языки, такие как JavaScript и VBScript, известна библиотека CDO (Collaboration Data Objects), позволяющая легко манипулировать с email сообщениями через исчерпывающее множество COM интерфейсов.

Справедливости ради, существует четвертое API (известное как Extended MAPI), но так как это просто расширение Simple MAPI, мы будем рассматривать оба этих API как один. Эти три (или четыре) набора MAPI действительно различны, но одно у них общее – все они находятся в одной DLL, которая называетсяMAPI32. Это выглядит незначительным, но этот факт является восхитительной новостью для API spy author. Почему, спросите Вы? Потому что, когда вызовы API происходят из одного места, то существует простейший способ перехватить их вдоль их пути.

Если мы заменим MAPI32.DLL специальной DLL, которая экспортирует тот же набор функций, что и MAPI32.DLL, мы сможем перехватывать все вызовы, сделанные к этим функциям. Фактически, это не новая идея (смотри мою статью о перехвате API). Единственная проблема с этой технологией в том, что это обычно раздражает Microsoft, когда приложения третьих фирм насильно заменяют системные DLL. Это создает разного рода проблемы, связанные с обратной совместимостью и приводящие к краху DLL. Можно представить, сколько будет радости, когда два различных приложения пытаются перезаписать MAPI32.DLL своими собственными версиями этой DLL – ничего кроме тотального вреда.

В попытке предотвратить использование этой технологии сторонними разработчиками с MAPI32, Microsoft придумала способ для перехвата вызовов MAPI к указанной DLL без необходимости замены оригинала. Когда MAPI32.DLL загружается приложением, то сначала производится чтение ключа из реестра, который показывает, требуется ли вызывающему приложению специальная версия MAPI32 DLL. Если да, MAPI32 загружает эту DLL в память и превращает себя в диспетчер вызовов функций. Это означает, что MAPI32.DLL направляет каждый вызов MAPI от вызывающего приложения к этой специальной DLL вместо того, чтобы обрабатывать его самостоятельно. К счастю для нас, мы можем извлечь пользу из этого механизма для своих целей. Но прежде чем мы продолжим, посмотрим на ключ в регистре, в зависимости от которого MAPI32.DLL выполняет эти действия.

Значение, используемое MAPI32 для определения необходимости загрузки специальной DLL, расположено в ветви HKLM\Software\Microsoft\Windows Messaging Subsystem\MSMapiApps. Рисунок 1 показывает содержимое этой ветви на моем компьютере.


Рисунок 1

Рисунок 1

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

Но постойте, как MAPI32 ищет эту DLL? Мы могли ожидать увидеть путь к специальной DLL указанный как значение при соответствующем имени, но вместо этого мы видим или пустую строку или “Microsoft Outlook”. Ответ прост. Если это пустая строка, MAPI32 знает, что не нужно загружать никакую специальную DLL, а нужно обработать все вызовы MAPI самостоятельно. В противном случае, MAPI32 использует окольные пути. Для поиска пути к специальной DLL, MAPI32 использует строку (например “Microsoft Outlook”), добавляет ее к HKLM\Software\Clients\Mail (получая в нашем случае HKLM\Software\Clients\Mail\Microsoft Outlook) и читает значения одного из параметров DllPath или DllPathEx, которые находятся в этой ветви реестра. Разница между ними заключается в типе вызовов MAPI исходящих от приложения – вызовы Simple MAPI отправляются DLL указанной в значении параметра DllPath, а вызовы Extended MAPI отправляются DLL указанной в значении параметра DllPathEx. Фактически, этот процесс более разработан более тщательно, включая дополнительные ключи реестра и дополнительную логику, но для наших целей мы можем игнорировать эти детали.

Итак, теперь мы знаем, как MAPI32 находит и загружает специальную DLL, но как мы можем использовать эту информацию для создания перехватчика MAPI? Наша цель состоит в том, чтобы как-нибудь заставить MAPI32 загружать нашу DLL - перехватчик в каждый процесс, использующий функции MAPI. Хорошо обдумайте этот момент, и Вы поймете, что ключи из ветви MSMapiApps содержат ответ. Вспомним, как MAPI32 определяет необходимость загрузки специальной DLL? Это происходит с помощью списка имен модулей расположенных в ветви MSMapiApps реестра, путем поиска модуля который расположен в адресном пространстве вызывающего приложения. Когда он найден, MAPI32 его значение для поиска и загрузки специальной DLL (или в нашем случае, перехватывающей DLL). Итак, если мы сможем найти имя модуля, которое для каждого процесса сможет пройти проверку, устраиваемую MAPI32, то мы будем на правильном пути. К счастью для нас такой модуль существует, и он называется - kernel32.dll.

Да, kernel32.dll – это ответ. Он существует в адресном пространстве каждого запущенного процесса и всегда сможет пройти проверку, устраиваемую MAPI32. Добавиви kernel32.dll в список модулей указанных в ветви MSMapiApps, мы сможем заставить MAPI32 загружать нашу DLL в каждый процесс, использующий функции MAPI. Нам только необходимо обеспечить, чтобы наша DLL экспортировала все необходимые функции MAPI и вуаля – мы имеем полнофункциональный перехватчик функций MAPI!

Осталась одна вещь, которую мы должны сделать. Мы должны делать вызовы исходных функций MAPI после того, как наш перехватывающий код сделает свою работу. Однако, так как этот бюллетень уже раздулся больше, чем я расчитывал, то я пропущу обычные, но важные детали о том, как это сделать. Если Вам необходимо узнать, как это сделать, получите пример кода, сопровождающий статью. Он доступен для скачивания по ссылке http://www.internals.com/newsletter/MAPIHook.zip. Распакуйте файл в корневой каталог диска C, иначе пример не будет правильно работать.

Вот и все на сегодня. Увидимся в следующий раз.

Internals.com Favorite Link

Этот сайт предлагает некоторые замечательные системные утилиты бесплатно! Проверьте это на: http://www.geocities.com/smidgeonsoft/

Hosted by uCoz