这个函数的功能是设置一个回调钩子,该钩子会在进程创建和进程销毁时被调用,钩子由用户提供
API格式
NTSTATUS PsSetCreateProcessNotifyRoutineEx(
PCREATE_PROCESS_NOTIFY_ROUTINE_EX NotifyRoutine,
BOOLEAN Remove
);
NotifyRoutine 是IN,传入用户指定的回调钩子
Remove 是IN ,创建回调钩子时传FALSE,创建了肯定需要在某一个时刻销毁,销毁回调钩子时传TRUE
ps:使用该函数前同样需要过掉驱动签名认证,过掉的方式和ObRegisterCallbacks一样
回调钩子的格式
void PcreateProcessNotifyRoutineEx(
PEPROCESS Process,
HANDLE ProcessId,
PPS_CREATE_NOTIFY_INFO CreateInfo
)
{...}
Process 指向进程的EPROCESS的指针
ProcessId 该进程的PID
CreateInfo 是一个结构体
typedef struct _PS_CREATE_NOTIFY_INFO {
SIZE_T Size;
union {
ULONG Flags;
struct {
ULONG FileOpenNameAvailable : 1;
ULONG IsSubsystemProcess : 1;
ULONG Reserved : 30;
};
};
HANDLE ParentProcessId;
CLIENT_ID CreatingThreadId;
struct _FILE_OBJECT *FileObject;
PCUNICODE_STRING ImageFileName;
PCUNICODE_STRING CommandLine;
NTSTATUS CreationStatus;
} PS_CREATE_NOTIFY_INFO, *PPS_CREATE_NOTIFY_INFO;
Size
该结构的大小(以字节为单位)。
Flags
保留。 请改用FileOpenNameAvailable成员。
FileOpenNameAvailable
一个布尔值,指定ImageFileName成员是否包含用于打开进程可执行文件的确切文件名。
IsSubsystemProcess
指示进程子系统类型的布尔值是Win32以外的子系统。
Reserved
保留供系统使用。
ParentProcessId
新进程的父进程的进程ID。 请注意,父进程不一定与创建新进程的进程相同。 新进程可以继承父进程的某些属性,如句柄或共享内存。 (进程创建者的进程ID由CreatingThreadId-> UniqueProcess给出。)
CreatingThreadId
创建新进程的进程和线程的进程ID和线程ID。 CreatingThreadId-> UniqueProcess包含进程ID,而CreatingThreadId-> UniqueThread包含线程ID。
FileObject
指向进程可执行文件的文件对象的指针。如果IsSubsystemProcess为TRUE,则此值可能为NULL。
ImageFileName
指向保存可执行文件的文件名的UNICODE_STRING字符串的指针。 如果FileOpenNameAvailable成员为TRUE,则该字符串指定用于打开可执行文件的确切文件名。 如果FileOpenNameAvailable为FALSE,则操作系统可能仅提供部分名称。如果IsSubsystemProcess为TRUE,则此值可能为NULL。
CommandLine
指向UNICODE_STRING字符串的指针,该字符串保存用于执行该过程的命令。 如果命令不可用,CommandLine为NULL。如果IsSubsystemProcess为TRUE,则此值可能为NULL。
CreationStatus
用于进程创建操作返回的NTSTATUS值。 驱动程序可以将此值更改为错误代码,以防止创建进程。
反调试
创建保护进程时让一个全局变量加一,保护进程退出时让那个全局变量减一,设置一个范围,当变量超出那个范围时让进程创建失败
代码实现
void PcreateProcessNotifyRoutineEx(
PEPROCESS Process,
HANDLE ProcessId,
PPS_CREATE_NOTIFY_INFO CreateInfo
)
{
static int OpenCount = 0;
PUCHAR ExeName = PsGetProcessImageFileName(Process);
//DbgPrint("ExeName=%s\n", ExeName);
if (strcmp((PCCHAR)ExeName, "xxx.exe") == 0)
{
//目标进程创建时,且创建时已有此进程被创建
if (OpenCount >= 1 && CreateInfo)
{
OpenCount++;
//STATUS_UNSUCCESSFUL会在用户层弹出进程创建失败的提示框,而下面的参数不会
CreateInfo->CreationStatus = STATUS_ACCESS_DISABLED_NO_SAFER_UI_BY_POLICY;
}
//目标进程创建时,且创建时没有此进程被创建
else if (OpenCount == 0 && CreateInfo)
{
OpenCount++;
}
//目标进程退出时
else if (NULL==CreateInfo)
{
OpenCount--;
}
DbgPrint("ExeName=%s,OpenCount=%d\n", ExeName,OpenCount);
}
}
反反调试
系统有一个全局数组维护着所有注册的进程回调钩子,根据特征码搜索定位到该全局数组变量,遍历该数组,根据回调钩子的地址定位到所属模块,根据模块的名称判断是否是目标模块,然后卸载该模块下的所有进程回调钩子
代码实现
//==================================================================
//函数名: DeleteDriverProcessNotifyRoutine
//功能:删除指定驱动注册的进程回调钩子
//输入参数1:NotifyRoutineArray,进程回调钩子全局数组的基地址
//输入参数2:dllInfo,本驱动的pDriver->DriverSection
//输入参数3:DriverName,要删除的进程回调钩子所属的对象
//返回值:BOOLEAN,删除成功返回TRUE,删除失败返回FALSE
//==================================================================
BOOLEAN DeleteDriverProcessNotifyRoutine(PUINT64 NotifyRoutineArray, PLDR_DATA dllInfo, LPCWSTR DriverName)
{
PEX_CALLBACK_ROUTINE_BLOCK pCallbackRoutineBlock;
PEX_CALLBACK_FUNCTION pCallbackFunc;
UNICODE_STRING uniDriverName;
while (0 != *NotifyRoutineArray)
{
pCallbackRoutineBlock = (PEX_CALLBACK_ROUTINE_BLOCK)(*NotifyRoutineArray & (~0x000000000000000F));
//DbgPrint("pCallbackRoutineBlock=%p\n", pCallbackRoutineBlock);
pCallbackFunc = pCallbackRoutineBlock->Function;
//查找API所在的内核模块
PLDR_DATA TargetDllInfo = GetModuleByObCallbackAddr((PVOID)pCallbackFunc, dllInfo);
//DbgPrint("pCallbackFunc=%p,DriverName=%wZ\n", pCallbackFunc, TargetDllInfo->name);
//do something else ...
RtlInitUnicodeString(&uniDriverName, DriverName);
if (RtlCompareUnicodeString(&uniDriverName, &TargetDllInfo->name, TRUE) == 0)
{
DbgPrint("pCallbackFunc=%p,DriverName=%wZ\n", pCallbackFunc, TargetDllInfo->name);
PsSetCreateProcessNotifyRoutineEx(pCallbackFunc, TRUE);
return TRUE;
}
NotifyRoutineArray++;
}
return FALSE;
}
//==================================================================
//函数名: GetAddrByOffset
//功能:根据偏移获得地址
//输入参数1:Offset,偏移
//输入参数2:CodeStart,这条指令的开始地址
//输入参数3:CodeLength,这条指令的长度
//返回值:UINT64,地址
//==================================================================
UINT64 GetAddrByOffset(INT32 Offset,UINT64 CodeStart, UINT64 CodeLength)
{
return (UINT64)((INT64)CodeLength + (INT64)CodeStart + Offset);
}