Перехват API-вызовов: пример
Внедрение DLL в адресное пространство процесса — замечательный способ узнать, что происходит в этом процессе. Однако простое внедрение DLL не дает достаточной информации, Зачастую надо точно знать, как потоки определенного процесса вызывают различные функции, а иногда и изменять поведение той или иной Windowsфункции.
Мне известна одна компания, которая выпустила DLL для своего приложения, работающего с базой данных. Эта DLL должна была расширить возможности основного продукта. При закрытии приложения DLL получала уведомление DLL_PROCESS_DETACH и только после этого проводила очистку ресурсов. При этом DLL должна была вызывать функции из других DLL для закрытия сокетов, файлов и других ресурсов, по к тому моменту другие DLL тоже получали уведомление DLL_PROCESS_DETACH, так что корректно завершить работу никак не удавалось.
Для решения этой проблемы компания наняла меня, и я предложил поставить ловушку на функцию ExitProcess. Как Вам известно, вызов ExitProcess заставляет систему посылать библиотекам уведомление DLL_PROCESS_DETACH, Перехватывая вызов ExitProcess, мы гарантируем своевременное уведомление внедренной DLL о вызове этой функции. Причем уведомление приходит до того, как аналогичные уведомления посылаются другим DLL. В этот момент внедренная DLL узнает о завершении процесса и успевает провести корректную очистку. Далее вызывается функция ExitProcess, что приводит к рассылке уведомлений DLL_PROCESS_DETACH остальным DLL, и они корректно завершаются. Это же уведомление получает и внедренная DLL, но ничего особенного она не делает, так как уже выполнила свою задачу.
В этом примере внедрение DLL происходило как бы само по себе, приложение было рассчитано на загрузку именно этой DLL, Оказываясь в адресном пространстве процесса, DLL должна была просканировать ЕХЕ-модуль и все загружаемые DLL-модули, найти все обращения ExitProcess и заменить их вызовами функции, находящейся во внедренной DLL. (Эта задача не так сложна, как кажется.) Подставная функция (функция ловушки), закончив свою работу, вызывала настоящую функцию ExitProcess из Kernel32.dll.
Данный пример иллюстрирует типичное применение перехвата API-вызовов, который позволил решить насущную проблему при минимуме дополнительного кода.