Quantcast
Channel: CodeSection,代码区,网络安全 - CodeSec
Viewing all articles
Browse latest Browse all 12749

【技术分享】如何枚举Windows中的进程、线程以及映像加载通知回调例程

0
0
【技术分享】如何枚举windows中的进程、线程以及映像加载通知回调例程

2017-09-22 16:41:07

阅读:48次
点赞(0)
收藏
来源: triplefault.io





【技术分享】如何枚举Windows中的进程、线程以及映像加载通知回调例程

作者:興趣使然的小胃





【技术分享】如何枚举Windows中的进程、线程以及映像加载通知回调例程

译者:興趣使然的小胃

预估稿费:200RMB

投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿


一、前言

大多数人都知道,Windows包含内核模式下的各种回调例程,驱动开发者可以使用这些例程接收各种事件通知。本文将介绍其中某些函数的工作机制。具体说来,我们会研究进程创建及终止回调例程(nt!PsSetCreateProcessNotifyRoutine、 nt!PsSetCreateProcessNotifyRoutineEx和nt!PsSetCreateProcessNotifyRoutineEx2)、线程创建及终止回调例程(nt!PsSetCreateThreadNotifyRoutine和nt!PsSetCreateThreadNotifyRoutineEx)以及映像加载通知回调例程(nt!PsSetLoadImageNotifyRoutine)的内部工作原理。此外,我们也会提供一个便于操作的WinDbg脚本,你可以利用这个脚本枚举这几种回调例程。

如果你想跟随本文的脚步,我建议你跟我一样使用Windows x64 10.0.15063(创造者更新版)中的系统文件。本文所使用的伪代码以及反汇编代码都是在这个发行版的基础上编写的。

如果你还没有搭建内核调试环境,不要担忧,我们给出了一个教程,一步一步教你如何使用WindDbg以及VMware搭建基本的内核调试环境。


二、回调例程的作用

在某些事件发生时,驱动开发者可以使用这些回调例程来接收事件的通知。比如,nt!PsSetCreateProcessNotifyRoutine这个基本的进程创建回调会注册一个用户定义的函数指针(“NotifyRoutine”),每当进程创建或者删除时,Windows都会调用这个函数指针。作为事件通知的一部分,用户提供的这个处理函数可以获得大量信息。在我们的演示案例中,这些信息包括父进程的PID(如果父进程存在的话)、实际进程的PID以及一个布尔值,标识进程处于创建还是终止状态。

安全软件可以利用这些回调来仔细检查主机上运行的代码。


三、深入分析

3.1 已公开的API

万事开头难,我们先得找个出发点。没有什么比官方文档中的函数更适合上手,我们选择的是nt!PsSetCreateProcessNotifyRoutine这个函数。MSDN称这个例程自Windows 2000起就一直存在。ReactOS似乎很早以前就已经实现了这个例程。接下来,我们会具体分析从Windows 2000到现在的这17年中,这个函数发生过什么变化(如果这么多年这些函数的确发生过变化的话)。

NTSTATUS__stdcallPsSetCreateProcessNotifyRoutine(PVOIDNotifyRoutine,BOOLEANRemove) { returnPspSetCreateProcessNotifyRoutine(NotifyRoutine,Remove!=0); }

这个函数似乎会调用一个nt!PspSetCreateProcessNotifyRoutine例程。实际上,其他类似的函数(nt!PsSetCreateProcessNotifyRoutineEx及nt!PsSetCreateProcessNotifyRoutineEx2)也会调用这个例程:


【技术分享】如何枚举Windows中的进程、线程以及映像加载通知回调例程

唯一的区别在于传递给nt!PspSetCreateProcessNotifyRoutine的第二个参数。这些参数属于有效的标识符。在基础函数(nt!PsSetCreateProcessNotifyRoutine)中,根据“Remove”参数的状态,这些标识的值为1或者0。如果“Remove”为TRUE,那么Flags=1。如果“Remove”为FALSE,那么Flags=0。在扩展函数(nt!PsSetCreateProcessNotifyRoutineEx)中,这些标识的值为2或者3。

NTSTATUS__fastcallPsSetCreateProcessNotifyRoutineEx(PVOIDNotifyRoutine,BOOLEANRemove) { returnPspSetCreateProcessNotifyRoutine(NotifyRoutine,(Remove!=0)+2); }

对于nt!PsSetCreateProcessNotifyRoutineEx2而言,这些标识的值为6或者7:

NTSTATUS__fastcallPsSetCreateProcessNotifyRoutineEx2(intNotifyType,PVOIDNotifyInformation,BOOLEANRemove) { NTSTATUSresult;//eax if(NotifyType)//OnlyPsCreateProcessNotifySubsystemsissupported. result=STATUS_INVALID_PARAMETER; else result=PspSetCreateProcessNotifyRoutine(NotifyInformation,(Remove!=0)+6); returnresult; }

因此,我们可以推测出传递给nt!PspSetCreateProcessNotifyRoutine函数的标识符具体定义如下:

#defineFLAG_REMOVE_FROM_ARRAY0x1 //ThisiswhyflagsfromPsSetCreateProcessNotifyRoutineExcanhaveavalueof2or3 //FLAG_IS_EXTENDED=0x2 //FLAG_IS_EXTENDED|FLAG_REMOVE_FROM_ARRAY=0x3 #defineFLAG_IS_EXTENDED0x2 //Thisiswhyflagsfromnt!PsSetCreateProcessNotifyRoutineEx2canhaveavalueof6or7 //FLAG_IS_EXTENDED2=0x6 //FLAG_IS_EXTENDED2|FLAG_REMOVE_FROM_ARRAY=0x7 #defineFLAG_IS_EXTENDED2(0x4|FLAG_IS_EXTENDED)

3.2 未公开文档

nt!PspSetCreateProcessNotifyRoutine稍微有点复杂,该函数的具体定义如下,我建议你还是在另一个窗口中好好阅读这段代码,便于理解。

NTSTATUS__fastcallPspSetCreateProcessNotifyRoutine(PVOIDNotifyRoutine,DWORDFlags) { BOOLbIsRemove; BOOLbIsExRoutine; DWORDUpperFlagBits; DWORDLdrDataTableEntryFlags; _EX_CALLBACK_ROUTINE_BLOCK*NewCallBackBlock; _ETHREAD*CurrentThread; size_tIndex; _EX_CALLBACK_ROUTINE_BLOCK*CallBackBlock; //CopyovereverythingtoUpperFlagBitsfromFlags,butbit0. UpperFlagBits=((DWORD)Flags&0xFFFFFFFE); //Checkifbit1isset.ThiswillonlybetrueifthecallerisPsSetCreateProcessNotifyRoutineEx //orPsSetCreateProcessNotifyRoutineEx2. bIsExRoutine=(Flags&2); //Bit0willbesetif"Remove"==TRUEfromthecaller. bIsRemove=(Flags&1); //Bit0isset.Wewanttoremoveacallback. if(bIsRemove) { //DisableAPCs. CurrentThread=(_ETHREAD*)KeGetCurrentThread(); --CurrentThread->Tcb.KernelApcDisable; Index=0; while(1) { CallBackBlock=ExReferenceCallBackBlock(&PspCreateProcessNotifyRoutine[Index]); if(CallBackBlock) { if(/*Isitthesameroutine?*/ ExGetCallBackBlockRoutine(CallBackBlock)==NotifyRoutine /*Isitthesametype?e.g.PsSetCreateProcessNotifyRoutineExvsPsSetCreateProcessNotifyRoutineEx2vsPsSetCreateProcessNotifyRoutine.*/ &&(_DWORD)ExGetCallBackBlockContext(CallBackBlock)==(_DWORD)UpperFlagBits /*DidwesuccessfullyNULLitout?*/ &&ExCompareExchangeCallBack(&PspCreateProcessNotifyRoutine[Index],NULL,CallBackBlock)) { //Decrementglobalcount. if(bIsExRoutine) _InterlockedDecrement(&PspCreateProcessNotifyRoutineExCount); else _InterlockedDecrement(&PspCreateProcessNotifyRoutineCount); ExDereferenceCallBackBlock(&PspCreateProcessNotifyRoutine[Index],CallBackBlock); KiLeaveCriticalRegionUnsafe(CurrentThread); ExWaitForCallBacks(CallBackBlock); ExFreePoolWithTag(CallBackBlock,0); returnSTATUS_SUCCESS; } ExDereferenceCallBackBlock(&PspCreateProcessNotifyRoutine[Index],CallBackBlock); } Index++; //Maximumcallbacks==64 if(Index>=0x40) { KiLeaveCriticalRegionUnsafe(CurrentThread); returnSTATUS_PROCEDURE_NOT_FOUND;//Couldnotfindentrytoremove. } } } else//Wewanttoaddacallback. { if(bIsExRoutine) LdrDataTableEntryFlags=0x20;//"Ex"routinemusthave_KLDR_DATA_TABLE_ENTRY.IntegrityCheckbitset. else LdrDataTableEntryFlags=0; if(!MmVerifyCallbackFunctionCheckFlags(NotifyRoutine,LdrDataTableEntryFlags)) returnSTATUS_ACCESS_DENIED; //Allocatenewdatastructure. NewCallBackBlock=ExAllocateCallBack(NotifyRoutine,UpperFlagBits); if(!NewCallBackBlock) returnSTATUS_INSUFFICIENT_RESOURCES; Index=0; while(!ExCompareExchangeCallBack(&PspCreateProcessNotifyRoutine[Index],NewCallBackBlock,NULL)) { Index++; if(Index>=0x40) { //Nospaceforcallbacks. ExFreePoolWithTag(NewCallBackBlock,0); returnSTATUS_INVALID_PARAMETER; } } //Incrementglobalcounters. if(bIsExRoutine) { _InterlockedIncrement(&PspCreateProcessNotifyRoutineExCount); if(!(PspNotifyEnableMask&4)) _interlockedbittestandset(&PspNotifyEnableMask,2u);//Have"Ex"callbacks. } else { _InterlockedIncrement(&PspCreateProcessNotifyRoutineCount); if(!(PspNotifyEnableMask&2)) _interlockedbittestandset(&PspNotifyEnableMask,1u);//Havebase-typeofcallbacks. } returnSTATUS_SUCCESS; } }

幸运的是,从Windows 2000起,与回调例程有关的许多内部数据结构并没有发生改动。ReactOS的前辈们已经给出过这些结构的定义,因此在可能的情况下,我们会使用这些结构定义,以避免重复劳动。

每个回调都对应一个全局数组,该数组最多可以包含64个元素。在我们的案例中,用于进程创建回调的数组的起始元素为nt!PspCreateProcessNotifyRoutine。数组中的每个元素均为EXCALLBACK类型。

//Source:https://doxygen.reactos.org/de/d22/ndk_2extypes_8h_source.html#l00545 // //InternalCallbackHandle // typedefstruct_EX_CALLBACK { EX_FAST_REFRoutineBlock; }EX_CALLBACK,*PEX_CALLBACK; 为了避免同步问题,系统使用了nt!ExReferenceCallBackBlock来安全获取底层回调对象的引用(EXCALLBACKROUTINEBLOCK,如下所示)。我们以非线程安全形式的代码重现这一过程:
/* nt!_EX_CALLBACK +0x000RoutineBlock:_EX_FAST_REF */ _EX_CALLBACK*CallBack=&PspCreateProcessNotifyRoutine[Index]; /* kd>dtnt!_EX_FAST_REF +0x000Object:Ptr64Void +0x000RefCnt:Pos0,4Bits +0x000Value:Uint8B */ _EX_FAST_REFReferenceObject=CallBack->RoutineBlock; //Weneedtofindthelocationoftheactual"Object"fromthe //_EX_FAST_REFstructure.Thisisaunion,wherethelower4bits //arethe"RefCnt".So,thismeanswe'reinterestedintheremaining //60bits. //Stripoffthe"RefCnt"bits. _EX_CALLBACK_ROUTINE_BLOCK*CallBackBlock=(_EX_CALLBACK_ROUTINE_BLOCK*)(ReferenceObject.Value&0xFFFFFFFFFFFFFFF0); //Source:https://doxygen.reactos.org/db/d49/xdk_2extypes_8h_source.html#l00179 typedefstruct_EX_RUNDOWN_REF{ _ANONYMOUS_UNIONunion{ volatileULONG_PTRCount; volatilePVOIDPtr; }DUMMYUNIONNAME; }EX_RUNDOWN_REF,*PEX_RUNDOWN_REF; //Source:https://doxygen.reactos.org/de/d22/ndk_2extypes_8h_source.html#l00535 // //InternalCallbackObject // typedefstruct_EX_CALLBACK_ROUTINE_BLOCK { EX_RUNDOWN_REFRundownProtect; PEX_CALLBACK_FUNCTIONFunction; PVOIDContext; }EX_CALLBACK_ROUTINE_BLOCK,*PEX_CALLBACK_ROUTINE_BLOCK;

如果我们想要删除某个回调对象(“Remove”为TRUE),我们要确保在数组中找到正确的EXCALLBACKROUTINEBLOCK。具体的方法是,首先使用nt!ExGetCallBackBlockRoutine来检查目标“NotifyRoutine”是否与当前的EXCALLBACK_ROUTINE相匹配:

PVOID__fastcallExGetCallBackBlockRoutine(_EX_CALLBACK_ROUTINE_BLOCK*CallBackBlock) { returnCallBackBlock->Function; } 随后,使用nt!ExGetCallBackBlockContext检查目标类型是否正确(即是否使用正确的nt!PsSetCreateProcessNotifyRoutine/Ex/Ex2创建而得):
PVOID__fastcallExGetCallBackBlockContext(_EX_CALLBACK_ROUTINE_BLOCK*CallBackBlock) { returnCallBackBlock->Context; }

此时,我们已经找到了数组中的那个元素。我们需要通过nt!ExCompareExchangeCallback将EXCALLBACK的值设置为NULL,减少其对应的全局计数值(为nt!PspCreateProcessNotifyRoutineExCount或者nt!PspCreateProcessNotifyRoutineCount),通过nt!ExDereferenceCallBackBlock解除EXCALLBACKROUTINEBLOCK的引用,等待使用EXCALLBACK的其他代码(nt!ExWaitForCallBacks),最后释放内存(nt!ExFreePoolWithTag)。从这个过程中,我们可知微软为了避免释放在用的回调对象做了许多工作。

如果遍历所有64个元素后,我们依然无法在nt!PspCreateProcessNotifyRoutine数组中找到需要删除的元素,那么系统就会返回STATUSPROCEDURENOT_FOUND错误信息。

另一方面,如果我们想要往回调数组中添加新的元素,过程会稍微简单点。nt!MmVerifyCallbackFunctionCheckFlags函数会执行完整性检查,以确保已加载模块中存在“NotifyRoutine”。这样一来,未链接的驱动(或shellcode)就无法收到回调事件。

BOOL__fastcallMmVerifyCallbackFunctionCheckFlags(PVOIDNotifyRoutine,DWORDFlags) { struct_KTHREAD*CurrentThread;//rbp BOOLbHasValidFlags;//ebx _KLDR_DATA_TABLE_ENTRY*Entry;//rax if(MiGetSystemRegionType(NotifyRoutine)==1) return0; CurrentThread=KeGetCurrentThread(); bHasValidFlags=0; --CurrentThread->KernelApcDisable; ExAcquireResourceSharedLite(&PsLoadedModuleResource,1u); Entry=MiLookupDataTableEntry(NotifyRoutine,1u); if(Entry&&(!Flags||Entry->Flags&Flags)) bHasValidFlags=1; ExReleaseResourceLite(&PsLoadedModuleResource); KeLeaveCriticalRegionThread(CurrentThread); returnbHasValidFlags; }

通过完整性检查后,系统会使用nt!ExAllocateCallBack来分配一个EXCALLBACKROUTINEBLOCK。这个函数可以用来确认EXCALLBACKROUTINEBLOCK结构的大小及布局:

_EX_CALLBACK_ROUTINE_BLOCK*__fastcallExAllocateCallBack(PEX_CALLBACK_FUNCTIONFunction,PVOIDContext) { _EX_CALLBACK_ROUTINE_BLOCK*CallbackBlock; CallbackBlock=(_EX_CALLBACK_ROUTINE_BLOCK*)ExAllocatePoolWithTag( NonPagedPoolNx, sizeof(_EX_CALLBACK_ROUTINE_BLOCK), 'brbC'); if(CallbackBlock) { CallbackBlock->Function=Function; CallbackBlock->Context=Context; ExInitializePushLock(&CallbackBlock->RundownProtect); } returnCallbackBlock; }

随后,系统会使用nt!ExCompareExchangeCallBack将新分配的EXCALLBACKROUTINEBLOCK添加到nt!PspCreateProcessNotifyRoutine数组中的空闲(NULL)位置(需要确保数组没有超过64个元素大小限制)。最后,对应的全局计数值会增加计数,nt!PspNotifyEnableMask中也会设置一个全局标识,表明系统中注册了用户指定类型的回调函数。

3.3 其他回调

幸运的是,线程以及映像创建回调与进程回调非常类似,它们使用了相同的底层数据结构。唯一的区别在于,线程创建/终止回调存储在nt!PspCreateThreadNotifyRoutine数组中,而映像加载通知回调存储在nt!PspLoadImageNotifyRoutine中。


四、实用脚本

现在我们可以将这些信息综合利用起来。我们可以使用WinDbg创建一个简单便捷的脚本来自动枚举进程、线程以及镜像回调例程。

我选择使用其他方法,而不去使用WinDbg自带的脚本引擎来完成这个任务。WinDbg中有个非常棒的第三方扩展PyKd,可以在WinDbg中运行python脚本。安装这个扩展的过程非常简单,你只需要使用正确版本的Python即可(如64位的WinDbg需要安装64位版)。

''' ModuleName: enum_win_callbacks.py Abstract: Iteratesoverthent!PspCreateProcessNotifyRoutine, nt!PspCreateThreadNotifyRoutine,andnt!PspLoadImageNotifyRoutine callbackarrays. Requirements: WinDbg:https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/ PyKd:https://pykd.codeplex.com/ Author: Nemanja(Nemi)Mulasmajic<nm@triplefault.io> http://triplefault.io ''' frompykdimport* importargparse #Discoversthesizeofapointerforthissystem. SIZE_OF_POINTER=(8if(is64bitSystem())else4) #Thenumberofpotentialcallbackobjectsinthearray. MAXIMUM_NUMBER_OF_CALLBACKS=0 defread_ptr(memory): ''' Readapointerofmemory. ''' try: return(ptrPtr(memory)) except: print"ERROR:Couldnotread{}bytesfromlocation{:#x}.".format(SIZE_OF_POINTER,memory) exit(-4) defread_dword(memory): ''' ReadaDWORDofmemory. ''' try: return(ptrDWord(memory)) except: print"ERROR:Couldnotread4bytesfromlocation{:#x}.".format(memory) exit(-5) defget_address_from_fastref(_EX_FAST_REF): ''' Givena_EX_FAST_REFstructure,thisfunctionwillextract thepointertotherawobject. ''' #kd>dtnt!_EX_FAST_REF #+0x000Object:Ptr64Void #+0x000RefCnt:Pos0,4Bits #+0x000Value:Uint8B #Removethelast4bitsofthepointer. return((_EX_FAST_REF>>4)<<4) defenumerate_over_callbacks(array): ''' Giventhebaseofacallbackarray,thisfunctionwill enumerateoverallvalidcallbackentries. ''' foriinxrange(MAXIMUM_NUMBER_OF_CALLBACKS): #Getthei'thentryinthearray. entry=(array+(i*SIZE_OF_POINTER)) entry=read_ptr(entry) #Notcurrentlyinuse;skipping. ifentry==0: continue #Extractjusttheobjectpointerfromthe_EX_FAST_REFstructure. callback_object=get_address_from_fastref(entry) print"{}:_EX_CALLBACK_ROUTINE_BLOCK{:#x}".format(i,callback_object) #_EX_CALLBACK_ROUTINE_BLOCK #+0x000RundownProtect:EX_RUNDOWN_REF #+0x008Function:PVOID #+0x010Context:PVOID rundown_protect=read_ptr(callback_object+(SIZE_OF_POINTER*0)) callback_function=read_ptr(callback_object+(SIZE_OF_POINTER*1)) context=read_ptr(callback_object+(SIZE_OF_POINTER*2)) print"\tRundownProtect:{:#x}".format(rundown_protect) print"\tFunction:{:#x}({})".format(callback_function,findSymbol(callback_function)) type="" ifcontext==0: type="(Normal)" elifcontext==2: type="(Extended)" elifcontext==6: type="(Extended#2)" else: type="(Unknown)" print"\tContext:{:#x}{}".format(context,type) defget_address_from_symbol(mod,name): ''' Attemptstolocatetheaddressofagivensymbolin amodule. ''' try: return(mod.offset(name)) except: print"ERROR:Failedtoretrievetheaddressof{}!{}.Aresymbolsloaded?".format(m.name(),name) exit(-3) parser=argparse.ArgumentParser(description='Iteratesoverthent!PspCreateProcessNotifyRoutine,nt!PspCreateThreadNotifyRoutine,andnt!PspLoadImageNotifyRoutinecallbackarrays.') parser.add_argument("-p",action="store_false",default=True,dest="process_callbacks",help="Exludesiterationoftheprocesscallbackarray(nt!PspCreateProcessNotifyRoutine).") parser.add_argument("-t",action="store_false",default=True,dest="thread_callbacks",help="Excludesiterationofthethreadcallbackarray(nt!PspCreateThreadNotifyRoutine).") parser.add_argument("-i",action="store_false",default=True,dest="image_callbacks",help="Excludesiterationoftheimageloadcallbackarray(nt!PspLoadImageNotifyRoutine).") args=parser.parse_args() #Mustbekerneldebuggingtousethis. ifnotisKernelDebugging()andnotisLocalKernelDebuggerEnabled(): print"ERROR:Thisscriptcanonlybeusedwhilekerneldebugging." exit(-1) try: mod=module("nt") except: print"ERROR:Couldnotfindthebaseaddressofntoskrnl.Aresymbolsloaded?" exit(-2) spacer="="*100 ver=getSystemVersion() #Vista+canstore64entriesinthecallbackarray. #OlderversionsofWindowscanonlystore8. MAXIMUM_NUMBER_OF_CALLBACKS=(64if(ver.buildNumber>=6000)else8) ##################################################################### #Forprocesscallbacks. ##################################################################### ifargs.process_callbacks: PspCreateProcessNotifyRoutine=get_address_from_symbol(mod,"PspCreateProcessNotifyRoutine") PspCreateProcessNotifyRoutineCount=read_dword(get_address_from_symbol(mod,"PspCreateProcessNotifyRoutineCount")) ifver.buildNumber>=6001:#VistaSP1+ PspCreateProcessNotifyRoutineExCount=read_dword(get_address_from_symbol(mod,"PspCreateProcessNotifyRoutineExCount")) else: PspCreateProcessNotifyRoutineExCount=0 print"\nIteratingoverthent!PspCreateProcessNotifyRoutinearrayat{:#x}.".format(PspCreateProcessNotifyRoutine) print"Expecting{}nt!PspCreateProcessNotifyRoutineCountand{}nt!PspCreateProcessNotifyRoutineExCountentries.".format(PspCreateProcessNotifyRoutineCount,PspCreateProcessNotifyRoutineExCount) printspacer enumerate_over_callbacks(PspCreateProcessNotifyRoutine) printspacer ##################################################################### #Forthreadcallbacks. ##################################################################### ifargs.thread_callbacks: PspCreateThreadNotifyRoutine=get_address_from_symbol(mod,"PspCreateThreadNotifyRoutine") PspCreateThreadNotifyRoutineCount=read_dword(get_address_from_symbol(mod,"PspCreateThreadNotifyRoutineCount")) ifver.buildNumber>=10240:#Windows10+ PspCreateThreadNotifyRoutineNonSystemCount=read_dword(get_address_from_symbol(mod,"PspCreateThreadNotifyRoutineNonSystemCount")) else: PspCreateThreadNotifyRoutineNonSystemCount=0 print"\nIteratingoverthent!PspCreateThreadNotifyRoutinearrayat{:#x}.".format(PspCreateThreadNotifyRoutine) print"Expecting{}nt!PspCreateThreadNotifyRoutineCountand{}nt!PspCreateThreadNotifyRoutineNonSystemCountentries.".format(PspCreateThreadNotifyRoutineCount,PspCreateThreadNotifyRoutineNonSystemCount) printspacer enumerate_over_callbacks(PspCreateThreadNotifyRoutine) printspacer ##################################################################### #Forimagecallbacks. ##################################################################### ifargs.image_callbacks: PspLoadImageNotifyRoutine=get_address_from_symbol(mod,"PspLoadImageNotifyRoutine") PspLoadImageNotifyRoutineCount=read_dword(get_address_from_symbol(mod,"PspLoadImageNotifyRoutineCount")) print"\nIteratingoverthent!PspLoadImageNotifyRoutinearrayat{:#x}.".format(PspLoadImageNotifyRoutine) print"Expecting{}nt!PspLoadImageNotifyRoutineCountentries.".format(PspLoadImageNotifyRoutineCount) printspacer enumerate_over_callbacks(PspLoadImageNotifyRoutine) printspacer

这个脚本读起来应该没什么困难,我已经尽量在代码中给出了足够多的注释。这段代码的兼容性也不错,可以兼容从XP以来的所有Windows系统(32位及64位都兼容)。

在WinDbg中使用“!py”命令运行这段脚本后,输出结果如下所示:

kd>!py"C:\Users\root\Desktop\WinDbgScripts\enumwin_callbacks.py" Iteratingoverthent!PspCreateProcessNotifyRoutinearrayat0xfffff8036e6042d0. Expecting6nt!PspCreateProcessNotifyRoutineCountand4nt!PspCreateProcessNotifyRoutineExCountentries. 0:EXCALLBACKROUTINEBLOCK0xffffc98b8c84b660RundownProtect:0x20Function:0xfffff8036e3979f0(nt!ViCreateProcessCallback)Context:0x0(Normal)1:EXCALLBACKROUTINEBLOCK0xffffc98b8c8f1410RundownProtect:0x20Function:0xfffff8099e9358a0(cng!CngCreateProcessNotifyRoutine)Context:0x0(Normal)2:EXCALLBACKROUTINEBLOCK0xffffc98b8dd607a0RundownProtect:0x20Function:0xfffff8099f33bcf0(WdFilter!MpCreateProcessNotifyRoutineEx)Context:0x6(Extended#2)3:EXCALLBACKROUTINEBLOCK0xffffc98b8dd6db60RundownProtect:0x20Function:0xfffff8099de7a0c0(ksecdd!KsecCreateProcessNotifyRoutine)Context:0x0(Normal)4:EXCALLBACKROUTINEBLOCK0xffffc98b8d7c9670RundownProtect:0x20Function:0xfffff8099ee88080(tcpip!CreateProcessNotifyRoutineEx)Context:0x6(Extended#2)5:EXCALLBACKROUTINEBLOCK0xffffc98b8e05b070RundownProtect:0x20Function:0xfffff8099f2ec860(iorate!IoRateProcessCreateNotify)Context:0x2(Extended)6:EXCALLBACKROUTINEBLOCK0xffffc98b8e0736a0RundownProtect:0x20Function:0xfffff8099e8c8b30(CI!IPEProcessNotify)Context:0x0(Normal)7:EXCALLBACKROUTINEBLOCK0xffffc98b8cb9e440RundownProtect:0x20Function:0xfffff8099f4e2e60(dxgkrnl!DxgkProcessNotify)Context:0x2(Extended)8:EXCALLBACKROUTINEBLOCK0xffffc98b8e3ed150RundownProtect:0x20Function:0xfffff809a0e13ecc(vm3dmp+13ecc)Context:0x0(Normal)9:EXCALLBACKROUTINE_BLOCK0xffffc98b8eec1a30RundownProtect:0x20Function:0xfffff809a07ebbe0(peauth+2bbe0) Context:0x0(Normal) Iteratingoverthent!PspCreateThreadNotifyRoutinearrayat0xfffff8036e6040d0. Expecting2nt!PspCreateThreadNotifyRoutineCountand0nt!PspCreateThreadNotifyRoutineNonSystemCountentries. 0:EXCALLBACKROUTINEBLOCK0xffffc98b8dd627a0RundownProtect:0x20Function:0xfffff8099f33c000(WdFilter!MpCreateThreadNotifyRoutine)Context:0x0(Normal)1:EXCALLBACKROUTINEBLOCK0xffffc98b8c8df4a0RundownProtect:0x20Function:0xfffff809a0721ae0(mmcss!CiThreadNotification) Context:0x0(Normal) Iteratingoverthent!PspLoadImageNotifyRoutinearrayat0xfffff8036e603ed0. Expecting2nt!PspLoadImageNotifyRoutineCountentries. 0:EXCALLBACKROUTINEBLOCK0xffffc98b8dd617a0RundownProtect:0x20Function:0xfffff8099f33fa50(WdFilter!MpLoadImageNotifyRoutine)Context:0x0(Normal)1:EXCALLBACKROUTINEBLOCK0xffffc98b8df671c0RundownProtect:0x20Function:0xfffff8099fb45d60(ahcache!CitmpLoadImageCallback) Context:0x0(Normal)

五、总结

理清Windows中系统回调函数的工作原理后,我们就可以做许多有趣的事情。如上所述,我们可以编个程序,遍历每个回调数组,探测已注册的所有回调。这对于安全取证来说非常重要。

此外,这些底层数组不受PatchGuard的保护。为了开发与64位系统上PatchGuard完美兼容的驱动,反病毒产品或多或少都需要注册回调函数,因此恶意软件可以动态禁用(或者替换)已注册的这些回调函数,以禁用安全防护服务。还有其他可能性能够实现相同目的。

非常感谢ReactOS精心准备的文档资料。需要着重感谢的是ReactOS的Alex Ionescu,我用到的大多数结构都是他识别出来的。此外,也顺便感谢PyKd的开发者,在我看来,这个工具比WinDbg原生脚本接口更加好用。

如果读者有需要的话,欢迎提出意见及建议。



【技术分享】如何枚举Windows中的进程、线程以及映像加载通知回调例程
【技术分享】如何枚举Windows中的进程、线程以及映像加载通知回调例程
本文由 安全客 翻译,转载请注明“转自安全客”,并附上链接。
原文链接:http://www.triplefault.io/2017/09/enumerating-process-thread-and-image.html

Viewing all articles
Browse latest Browse all 12749