Finding image load and process notification callbacks

For a while now, Windows has supported image and process load notification callbacks. These callbacks are established by a driver by calling either PsSetImageLoadNotifyRoutine or PsSetCreateProcessNotifyRoutine{Ex}, respectively.

Given this, do you wonder what drivers are running on your system that are monitoring your image and process loads? Up until now there has been no easy way to view the current list of registered drivers. I was finally curious enough on my own system to toss together a WinDBG script that will walk both lists. It’s long, though I’ve tried to comment it enough that anyone can follow it. To run it, just copy the contents of the script to a file pscallbacks.wbs and execute the following command:

$$>a<c:\dumps\pscallbacks.wbs

The command also takes an option parameter to show just the image load or process load callbacks:

$$>a<c:\dumps\pscallbacks.wbs i

$$>a<c:\dumps\pscallbacks.wbs p

If everything goes right, you should see the following output:

0: kd> $$>a<c:\dumps\pscallbacks.wbs
************************************************
* This command brought to you by Analyze-v.com *
************************************************
************************************
* Printing image load callbacks... *
************************************
nt!EtwpTraceLoadImage:
fffff800`02de4ecc 48895c2420      mov     qword ptr [rsp+20h],rbx
--------------------------------------------
MpFilter!MpLoadImageNotifyRoutine:
fffff880`02a78220 4c8bdc          mov     r11,rsp
--------------------------------------------
**********************************************
* Printing process notification callbacks... *
**********************************************
nt!ViCreateProcessCallback:
fffff800`02a90688 48895c2408      mov     qword ptr [rsp+8],rbx
--------------------------------------------
ksecdd!KsecCreateProcessNotifyRoutine:
fffff880`01211330 4883ec28        sub     rsp,28h
--------------------------------------------
cng!CngCreateProcessNotifyRoutine:
fffff880`01158910 4883ec38        sub     rsp,38h
--------------------------------------------
tcpip+0x4e020:
fffff880`0164f020 4883ec28        sub     rsp,28h
--------------------------------------------
CI!I_PEProcessNotify:
fffff880`00c9db94 4584c0          test    r8b,r8b
--------------------------------------------
MpFilter!MpCreateProcessNotifyRoutineEx:
fffff880`02a77cd8 488bc4          mov     rax,rsp
--------------------------------------------
vmci!VMCIEvent_Unsubscribe+0x21ac:
fffff880`036b12dc 4584c0          test    r8b,r8b
--------------------------------------------
peauth+0x18d2c:
fffff880`03c18d2c 48895c2408      mov     qword ptr [rsp+8],rbx
--------------------------------------------

Here is the script in its entirety, enjoy!

Update: Just posted a new version that should work across all Windows platforms, so no special support needed to execute on XP.

Update: You can download a text file version of the script here, much easier than trying to fix the formatting yourself.

$$ pscallbacks.wbs
$$
$$ Version 2
$$
$$  - Updated script to work unchanged across all platforms
$$
$$ Scott Noone - analyze-v.com
$$ snoone@analyze-v.com
$$
$$ This script walks the list of registered image load and process load notification
$$ routines.
$$
$$ Currently tested platforms:
$$
$$  Win7 x64
$$
$$  WinXP 32bit
$$
$$
.echo ************************************************
.echo * This command brought to you by Analyze-v.com *
.echo ************************************************
.echo
$$ Start off by creating some aliases that will make this script more readable
$$ Globals
aS BuildNumber    "low(dwo(nt!NtBuildNumber))";
aS ImageLoadCount "dwo(nt!PspLoadImageNotifyRoutineCount)";
aS ImageLoadBase  "nt!PspLoadImageNotifyRoutine";
$$
$$ Include the Ex callbacks where available
$$
.block
{
    .if (${BuildNumber} <= 0n3790)
    {
        aS ProcessNotifyCount "dwo(nt!PspCreateProcessNotifyRoutineCount)";
    }
    .else
    {
        aS ProcessNotifyCount "dwo(nt!PspCreateProcessNotifyRoutineCount) + dwo(nt!PspCreateProcessNotifyRoutineExCount)";
    }
}
aS ProcessNotifyBase  "nt!PspCreateProcessNotifyRoutine";
$$ Our options
aS DISPLAY_IMAGE_NOTIFY "1";
aS DISPLAY_PROCESS_NOTIFY "2";
aS options "@$t0";
$$ variables
aS i "@$t1";
aS imageEntry "@$t2";
aS processEntry "@$t3";
aS functionPtr "@$t4";
$$ The .block is necessary to make sure that all of the above aliases are evaluated
.block
{
    r ${options} = 0;
    $$ Check the parameters. Valid values are:
    $$
    $$ i - Image load callbacks only
    $$ p - Process notification callbacks only
    $$ * - All callbacks
    $$
    .if (${/d:$arg1} == 1)
    {
        .if ('${$arg1}' == 'i')
        {
            r ${options} = ${DISPLAY_IMAGE_NOTIFY};
        }
        .elsif ('${$arg1}' == 'p')
        {
            r ${options} = ${DISPLAY_PROCESS_NOTIFY};
        }
        .elsif ('${$arg1}' == '*')
        {
            r ${options} = ${DISPLAY_IMAGE_NOTIFY} | ${DISPLAY_PROCESS_NOTIFY};
        }
        .else
        {
            .echo Error! Valid parameters are i,p, and *.
        }
    }
    .else
    {
        $$ Default to showing everything.
        r ${options} = ${DISPLAY_IMAGE_NOTIFY} | ${DISPLAY_PROCESS_NOTIFY};
    }
    .if ((${options} & ${DISPLAY_IMAGE_NOTIFY}) != 0)
    {
        .echo ************************************;
        .echo * Printing image load callbacks... *;
        .echo ************************************;
     �
        $$ Walk the image load notify routines
        r ${imageEntry} = ${ImageLoadBase};
        .for (r ${i} = 0; ${i} < ${ImageLoadCount}; r ${i} = ${i} + 1)
        {
            $$ This points to a function, though the bottom bits are control info.
            $$ So, mask those off
            r ${functionPtr} = (poi(${imageEntry}) & -8);
            $$ Unassemble the first instruction
            .if (@$ptrsize == 4)
            {
                $$ 32bit systems seem to have more control info ahead of the function
                $$ pointer, so skip that.
                u poi(${functionPtr} + 4) l1;
            }
            .else
            {
                u poi(${functionPtr}) l1;
            }
            $$ Walk to the next entry in the array
            r ${imageEntry} = ${imageEntry} + @$ptrsize;
     .echo --------------------------------------------;
        }
    }
    .if ((${options} & ${DISPLAY_PROCESS_NOTIFY}) != 0)
    {
        .echo **********************************************;
        .echo * Printing process notification callbacks... *;
        .echo **********************************************;
        $$ Walk the process notification routines
        r ${processEntry} = ${ProcessNotifyBase};
        .for (r ${i} = 0; ${i} < ${ProcessNotifyCount}; r ${i} = ${i} + 1)
        {
            $$ This points to a function, though the bottom bits are control info.
            $$ So, mask those off
            r ${functionPtr} = (poi(${processEntry}) & -8);
            $$ Unassemble the first instruction
            .if (@$ptrsize == 4)
            {
                $$ 32bit systems seem to have more control info ahead of the function
                $$ pointer, so skip that.
                u poi(${functionPtr} + 4) l1;
            }
            .else
            {
                u poi(${functionPtr}) l1;
            }
            $$ Walk to the next entry in the array
            r ${processEntry} = ${processEntry} + @$ptrsize;
     .echo --------------------------------------------;
        }
    }
}
$$ Clean up our aliases
ad ${/v:DISPLAY_IMAGE_NOTIFY};
ad ${/v:DISPLAY_PROCESS_NOTIFY};
ad ${/v:options};
ad ${/v:i};
ad ${/v:imageEntry};
ad ${/v:processEntry};
ad ${/v:functionPtr};
ad ${/v:BuildNumber};
ad ${/v:ImageLoadCount};
ad ${/v:ImageLoadBase};
ad ${/v:ProcessNotifyCount};
ad ${/v:ProcessNotifyBase};

One Response to “Finding image load and process notification callbacks”

  1. akap says:

    thanks ! it is very useful