赞
踩
ark相关技术是关于进程检测,杀进程,线程检测,杀线程,怎么检测模块,隐藏dll等。还有驱动模块怎么检测,怎么卸载别人的驱动。SSDT,shadowSSDT,FSD相关。
CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS)/Process32First/Process32Next,建一个快照,然后去遍历
ZwQuerySystemInformation(SystemProcessAndThreadsInformation),ntdll.dll里需要getprocdress导入然后用SystemProcessAndThreadsInformation功能号去查,返回系统中所有的进程和线程。如果是32位程序跑在64位系统可能存在截断指针,8->4的情况。
EnumWindows/GetWindowThreadProcessId,EnumWindows枚举窗口,得到句柄,GetWindowThreadProcessId然后得到窗口由哪个线程,进程创建。存在问题程序没有窗口。
for(;;)OpenProcess,通过循环for(int nPid=0;nPid<=2^26;nPid+=4)内部OpenProcess(nPid,....)。
第一种方法PspCidTable
查找PspCidTable
ExEnumHandleTable
解析PspCidTable,
HANDLE_TABLE
HANDLE_TABLE_ENTRY
KPCP.KdVersionBlock->PspCidTable(wdbgexts.h)
PspCidTable这是个想树一样的结构,在Win2000中,句柄表使用的是固定的3层索引模式,而在WinXp,2003以及之后的系统中使用的动态的可扩展的3层索引句柄表,存放的是系统中所有的进程和线程对象,其索引也就是进程ID(PID)或线程ID(TID)。我们每创建一个进程,系统就把我们的进程线程信息保存在这里。我们就遍历这个结构,找出所有的进程线程。
参考资料
https://blog.csdn.net/jiangfuqiang/article/details/4567625
PspCidTable使用了handle_table句柄表这种结构但是有所区别。因为windows对于这个结构是动态分配和确定层数,另外如何确定层数,就是如下,因为句柄表肯定都是按页或者对齐的。拿32位举例,其中低二位 TagBits 位被操作系统完全忽略掉,因此可以被应用程序自由改写。高 6 位,作为 Tag 来使用,其中最高位等于 1 ,代表是系统 HandleTable 中的 Handle。windows就是由最低两位空闲的位置来保存具体有几层。windows有很多这种用法。
下面用windbg观察下结构
- 1: kd> dq PspCidTable
- fffff800`04042bc8 fffff8a0`00004880 00000000`00000000
- fffff800`04042bd8 ffffffff`80000020 00000000`00000101
- fffff800`04042be8 ffffffff`800002e4 ffffffff`80000024
- fffff800`04042bf8 00000000`00000000 00000000`00000113
- fffff800`04042c08 00000000`00000000 00000000`00000000
- fffff800`04042c18 fffff800`03f9db40 00000000`00000000
- fffff800`04042c28 00000000`00000000 00000000`00000000
- fffff800`04042c38 00000000`00000000 00000000`00000008
- kd> dt _HANDLE_TABLE fffff8a0`00004880
- nt!_HANDLE_TABLE
- +0x000 TableCode : 0xfffff8a0`01254001
- +0x008 QuotaProcess : (null)
- +0x010 UniqueProcessId : (null)
- +0x018 HandleLock : _EX_PUSH_LOCK
- +0x020 HandleTableList : _LIST_ENTRY [ 0xfffff8a0`000048a0 - 0xfffff8a0`000048a0 ]
- +0x030 HandleContentionEvent : _EX_PUSH_LOCK
- +0x038 DebugInfo : (null)
- +0x040 ExtraInfoPages : 0n0
- +0x044 Flags : 1
- +0x044 StrictFIFO : 0y1
- +0x048 FirstFreeHandle : 0xe74
- +0x050 LastFreeHandleEntry : 0xfffff8a0`01aa21d0 _HANDLE_TABLE_ENTRY
- +0x058 HandleCount : 0x371
- +0x05c NextHandleNeedingPool : 0x1000
- +0x060 HandleCountHighWatermark : 0x372
得到TableCode 0xfffff8a0`01254001,最后一位是1,说明有2层。然后我们看一下第一层
- 1: kd> dq 0xfffff8a0`01254000
- fffff8a0`01254000 fffff8a0`00005000 fffff8a0`01255000
- fffff8a0`01254010 fffff8a0`01aa2000 fffff8a0`01df1000
- fffff8a0`01254020 00000000`00000000 00000000`00000000
- fffff8a0`01254030 00000000`00000000 00000000`00000000
- fffff8a0`01254040 00000000`00000000 00000000`00000000
因为windows内存按页4k对齐,每个里面每一项都是一个指针,指向下一层。这里4个,这里只用了32字节。下面再看下一层叶子节点,对应的结构是HANDLE_TABLE_ENTRY
- 1: kd> dq fffff8a0`00005000
- fffff8a0`00005000 00000000`00000000 48202454`fffffffe
- fffff8a0`00005010 fffffa80`018fe041 00000000`00000000
- fffff8a0`00005020 fffffa80`018feb61 00000000`00000000
- fffff8a0`00005030 fffffa80`0196f041 00000000`00000000
- fffff8a0`00005040 fffffa80`0198b041 00000000`00000000
- fffff8a0`00005050 fffffa80`01929b61 00000000`00000000
- fffff8a0`00005060 fffffa80`0197db61 fffff880`00000000
- fffff8a0`00005070 fffffa80`019769e1 fffff880`00000000
- 1: kd> dt _HANDLE_TABLE_ENTRY fffff8a0`00005000
- nt!_HANDLE_TABLE_ENTRY
- +0x000 Object : (null)
- +0x000 ObAttributes : 0
- +0x000 InfoTable : (null)
- +0x000 Value : 0
- +0x008 GrantedAccess : 0xfffffffe
- +0x008 GrantedAccessIndex : 0xfffe
- +0x00a CreatorBackTraceIndex : 0xffff
- +0x008 NextFreeTableEntry : 0xfffffffe
这个句柄对象是空值,可能是创建完释放掉了。HANDLE_TABLE_ENTRY每一项是64个字节。那我们看下这个页下一项。
- 1: kd> dt _HANDLE_TABLE_ENTRY fffff8a0`00005000+0x10
- nt!_HANDLE_TABLE_ENTRY
- +0x000 Object : 0xfffffa80`018fe041 Void
- +0x000 ObAttributes : 0x18fe041
- +0x000 InfoTable : 0xfffffa80`018fe041 _HANDLE_TABLE_ENTRY_INFO
- +0x000 Value : 0xfffffa80`018fe041
- +0x008 GrantedAccess : 0
- +0x008 GrantedAccessIndex : 0
- +0x00a CreatorBackTraceIndex : 0
- +0x008 NextFreeTableEntry : 0
我们要知道这个对象是进程还是线程,查询下,因为对象这些结构最后一位应该是0,所以查询时把末尾数字去掉
- 1: kd> !object 0xfffffa80`018fe040
- Object: fffffa80018fe040 Type: (fffffa80018fda00) Process
- ObjectHeader: fffffa80018fe010 (new version)
- HandleCount: 4 PointerCount: 157
发现是个process
- 1: kd> dt _eprocess 0xfffffa80`018fe040
- nt!_EPROCESS
- +0x000 Pcb : _KPROCESS
- +0x160 ProcessLock : _EX_PUSH_LOCK
- +0x168 CreateTime : _LARGE_INTEGER 0x1d53011`4551bed7
- +0x170 ExitTime : _LARGE_INTEGER 0x0
- +0x178 RundownProtect : _EX_RUNDOWN_REF
- +0x180 UniqueProcessId : 0x00000000`00000004 Void
- +0x188 ActiveProcessLinks : _LIST_ENTRY [ 0xfffffa80`0279b498 - 0xfffff800`04042b90 ]
- +0x198 ProcessQuotaUsage : [2] 0
- +0x1a8 ProcessQuotaPeak : [2] 0
- +0x1b8 CommitCharge : 0x29
- +0x1c0 QuotaBlock : 0xfffff800`04020c00 _EPROCESS_QUOTA_BLOCK
- +0x1c8 CpuQuotaBlock : (null)
- +0x1d0 PeakVirtualSize : 0xb24000
- +0x1d8 VirtualSize : 0x5ad000
- +0x1e0 SessionProcessLinks : _LIST_ENTRY [ 0x00000000`00000000 - 0x0 ]
- +0x1f0 DebugPort : (null)
- +0x1f8 ExceptionPortData : (null)
- +0x1f8 ExceptionPortValue : 0
- +0x1f8 ExceptionPortState : 0y000
- +0x200 ObjectTable : 0xfffff8a0`00001780 _HANDLE_TABLE
- +0x208 Token : _EX_FAST_REF
- +0x210 WorkingSetPage : 0
- +0x218 AddressCreationLock : _EX_PUSH_LOCK
- +0x220 RotateInProgress : (null)
- +0x228 ForkInProgress : (null)
- +0x230 HardwareTrigger : 0
- +0x238 PhysicalVadRoot : 0xfffffa80`01992500 _MM_AVL_TABLE
- +0x240 CloneRoot : (null)
- +0x248 NumberOfPrivatePages : 0xb
- +0x250 NumberOfLockedPages : 0x40
- +0x258 Win32Process : (null)
- +0x260 Job : (null)
- +0x268 SectionObject : (null)
- +0x270 SectionBaseAddress : (null)
- +0x278 Cookie : 0
- +0x27c UmsScheduledThreads : 0
- +0x280 WorkingSetWatch : (null)
- +0x288 Win32WindowStation : (null)
- +0x290 InheritedFromUniqueProcessId : (null)
- +0x298 LdtInformation : (null)
- +0x2a0 Spare : (null)
- +0x2a8 ConsoleHostProcess : 0
- +0x2b0 DeviceMap : 0xfffff8a0`00008bc0 Void
- +0x2b8 EtwDataSource : (null)
- +0x2c0 FreeTebHint : 0x000007ff`fffe0000 Void
- +0x2c8 FreeUmsTebHint : 0x00000000`772c9000 Void
- +0x2d0 PageDirectoryPte : _HARDWARE_PTE
- +0x2d0 Filler : 0
- +0x2d8 Session : (null)
- +0x2e0 ImageFileName : [15] "System"
- +0x2ef PriorityClass : 0x2 ''
- +0x2f0 JobLinks : _LIST_ENTRY [ 0x00000000`00000000 - 0x0 ]
- +0x300 LockedPagesList : (null)
- +0x308 ThreadListHead : _LIST_ENTRY [ 0xfffffa80`018fef80 - 0xfffffa80`03dea460 ]
- +0x318 SecurityPort : (null)
- +0x320 Wow64Process : (null)
- +0x328 ActiveThreads : 0x6b
- +0x32c ImagePathHash : 0
- +0x330 DefaultHardErrorProcessing : 5
- +0x334 LastThreadExitStatus : 0n0
- +0x338 Peb : (null)
- +0x340 PrefetchTrace : _EX_FAST_REF
- +0x348 ReadOperationCount : _LARGE_INTEGER 0xf
- +0x350 WriteOperationCount : _LARGE_INTEGER 0x0
- +0x358 OtherOperationCount : _LARGE_INTEGER 0x1ea
- +0x360 ReadTransferCount : _LARGE_INTEGER 0x2509758
- +0x368 WriteTransferCount : _LARGE_INTEGER 0x0
- +0x370 OtherTransferCount : _LARGE_INTEGER 0x130b
- +0x378 CommitChargeLimit : 0
- +0x380 CommitChargePeak : 0x50
- +0x388 AweInfo : (null)
- +0x390 SeAuditProcessCreationInfo : _SE_AUDIT_PROCESS_CREATION_INFO
- +0x398 Vm : _MMSUPPORT
- +0x420 MmProcessLinks : _LIST_ENTRY [ 0xfffffa80`0279b730 - 0xfffff800`0401c5e0 ]
- +0x430 HighestUserAddress : (null)
- +0x438 ModifiedPageCount : 0x24ba
- +0x43c Flags2 : 0x2d800
- +0x43c JobNotReallyActive : 0y0
- +0x43c AccountingFolded : 0y0
- +0x43c NewProcessReported : 0y0
- +0x43c ExitProcessReported : 0y0
- +0x43c ReportCommitChanges : 0y0
- +0x43c LastReportMemory : 0y0
- +0x43c ReportPhysicalPageChanges : 0y0
- +0x43c HandleTableRundown : 0y0
- +0x43c NeedsHandleRundown : 0y0
- +0x43c RefTraceEnabled : 0y0
- +0x43c NumaAware : 0y0
- +0x43c ProtectedProcess : 0y1
- +0x43c DefaultPagePriority : 0y101
- +0x43c PrimaryTokenFrozen : 0y1
- +0x43c ProcessVerifierTarget : 0y0
- +0x43c StackRandomizationDisabled : 0y1
- +0x43c AffinityPermanent : 0y0
- +0x43c AffinityUpdateEnable : 0y0
- +0x43c PropagateNode : 0y0
- +0x43c ExplicitAffinity : 0y0
- +0x440 Flags : 0x14040800
- +0x440 CreateReported : 0y0
- +0x440 NoDebugInherit : 0y0
- +0x440 ProcessExiting : 0y0
- +0x440 ProcessDelete : 0y0
- +0x440 Wow64SplitPages : 0y0
- +0x440 VmDeleted : 0y0
- +0x440 OutswapEnabled : 0y0
- +0x440 Outswapped : 0y0
- +0x440 ForkFailed : 0y0
- +0x440 Wow64VaSpace4Gb : 0y0
- +0x440 AddressSpaceInitialized : 0y10
- +0x440 SetTimerResolution : 0y0
- +0x440 BreakOnTermination : 0y0
- +0x440 DeprioritizeViews : 0y0
- +0x440 WriteWatch : 0y0
- +0x440 ProcessInSession : 0y0
- +0x440 OverrideAddressSpace : 0y0
- +0x440 HasAddressSpace : 0y1
- +0x440 LaunchPrefetched : 0y0
- +0x440 InjectInpageErrors : 0y0
- +0x440 VmTopDown : 0y0
- +0x440 ImageNotifyDone : 0y0
- +0x440 PdeUpdateNeeded : 0y0
- +0x440 VdmAllowed : 0y0
- +0x440 CrossSessionCreate : 0y0
- +0x440 ProcessInserted : 0y1
- +0x440 DefaultIoPriority : 0y010
- +0x440 ProcessSelfDelete : 0y0
- +0x440 SetTimerResolutionLink : 0y0
- +0x444 ExitStatus : 0n259
- +0x448 VadRoot : _MM_AVL_TABLE
- +0x488 AlpcContext : _ALPC_PROCESS_CONTEXT
- +0x4a8 TimerResolutionLink : _LIST_ENTRY [ 0x00000000`00000000 - 0x0 ]
- +0x4b8 RequestedTimerResolution : 0
- +0x4bc ActiveThreadsHighWatermark : 0x6f
- +0x4c0 SmallestTimerResolution : 0
- +0x4c8 TimerResolutionStackRecord : (null)
这样我们就看到了这个进程详细信息,然后继续解析,就能知道这个进程的详细信息了。这个代码实际可以去逆向的ExpLookupHandleTableEntry,
- /************************************************************************
- * Name : APEnumProcessInfoByIterateThirdLevelHandleTable
- * Param: TableCode
- * Param: EProcess
- * Param: pti
- * Param: ThreadCount
- * Ret : VOID
- * 遍历三级表
- ************************************************************************/
- VOID
- APEnumProcessInfoByIterateThirdLevelHandleTable(IN UINT_PTR TableCode,
- OUT PPROCESS_INFORMATION pi, IN UINT32 ProcessCount)
- {
- do
- {
- APEnumProcessInfoByIterateSecondLevelHandleTable(TableCode, pi, ProcessCount);
- TableCode += sizeof(UINT_PTR);
- } while (*(PUINT_PTR)TableCode != 0 && MmIsAddressValid((PVOID)*(PUINT_PTR)TableCode));
- }
- NTSTATUS
- APEnumProcessInfoByIteratePspCidTable(OUT PPROCESS_INFORMATION pi, IN UINT32 ProcessCount)
- {
- NTSTATUS Status = STATUS_UNSUCCESSFUL;
- // 保存之前的模式,转成KernelMode
- PETHREAD EThread = PsGetCurrentThread();
- UINT8 PreviousMode = APChangeThreadMode(EThread, KernelMode);
- UINT_PTR PspCidTable = APGetPspCidTableAddress();
- APChangeThreadMode(EThread, PreviousMode);
- pi->NumberOfProcesses = 1; // 先把0号成员预留出来,存放Idle
- // EnumHandleTable
- if (PspCidTable)
- {
- PHANDLE_TABLE HandleTable = NULL;
- HandleTable = (PHANDLE_TABLE)(*(PUINT_PTR)PspCidTable); // HandleTable = fffff8a0`00004910
- if (HandleTable && MmIsAddressValid((PVOID)HandleTable))
- {
- UINT8 TableLevel = 0; // 指示句柄表层数
- UINT_PTR TableCode = 0; // 地址存放句柄表首地址
- TableCode = HandleTable->TableCode & 0xFFFFFFFFFFFFFFFC; // TableCode = 0xfffff8a0`00fc5000
- TableLevel = HandleTable->TableCode & 0x03; // TableLevel = 0x01
- if (TableCode && MmIsAddressValid((PVOID)TableCode))
- {
- switch (TableLevel)
- {
- case 0:
- {
- // 一层表
- APEnumProcessInfoByIterateFirstLevelHandleTable(TableCode, pi, ProcessCount);
- break;
- }
- case 1:
- {
- // 二层表
- APEnumProcessInfoByIterateSecondLevelHandleTable(TableCode, pi, ProcessCount);
- break;
- }
- case 2:
- {
- // 三层表
- APEnumProcessInfoByIterateThirdLevelHandleTable(TableCode, pi, ProcessCount);
- break;
- }
- default:
- break;
- }
- }
- }
- }
- if (pi->NumberOfProcesses > 1)
- {
- // 填充Idle的信息
- pi->ProcessEntry[0].ProcessId = 0;
- pi->ProcessEntry[0].EProcess = (UINT_PTR)APGetPsIdleProcess(); // 全局导出
- pi->ProcessEntry[0].ParentProcessId = 0;
- Status = STATUS_SUCCESS;
- }
- DbgPrint("EnumProcessInfo by iterate PspCidTable\r\n");
- return Status;
- }
但是现在有个问题,如果在应用层打开文件,根据句柄,createFile/readFile,windows是不会让用户层看到这个FileObject,一般操作文件都是把句柄传进内核,SSDT存在大量句柄,内核层通过函数ObReferenceObjectByHandle(hFile,IoFileObjectType,&Fileobject),通过这个函数把用户层句柄值通过这个表找到对象转换成FileObject,然后用去操作文件。
那么这个句柄值被分成的字段,在32位如下
最低2位在句柄表示没用。(所以可以在其他地方用在标识有几层)
Low Level Index在叶子节点做索引,9位因为一页4K,一个handle_table_entry有8字节,所以范围是2^12/2^3=2^9。
Mid Level Index,是如果2,3层,是2层第一层或者3层中间层索引,10是因为一页4K,一个地址指针32位是4字节,2^12/2^2=2^10.
High Level Index,用来如果3层第一层索引,windows没有全部用掉高11位来标识句柄,只用了5位,最高6位它用。
所以这也是在应用层变量句柄for循环nPid<=2^26的原因,最高6位没用。另外循环附图+=4,是因为最后两位句柄表没用。
64位也跟这个一样,没有扩展,。因为64位指针变成8字节,然后handle_table_entry变成16字节,所以Low Level Index变成了8,Mid Level Index成了9,High Level Index变成了7,然后剩下的没用。
所以内核关于进程枚举步骤就是
查找PspCidTable(在ntoskrnl.exe中),未导出,有两种方法查找,1.反汇编PsLookupProcessByProcessId,这个函数就有对其引用。如下
- 1: kd> u PsLookupProcessByProcessId l 40
- nt!PsLookupProcessByProcessId:
- fffff800`0416e1fc 48895c2408 mov qword ptr [rsp+8],rbx
- fffff800`0416e201 48896c2410 mov qword ptr [rsp+10h],rbp
- fffff800`0416e206 4889742418 mov qword ptr [rsp+18h],rsi
- fffff800`0416e20b 57 push rdi
- fffff800`0416e20c 4154 push r12
- fffff800`0416e20e 4155 push r13
- fffff800`0416e210 4883ec20 sub rsp,20h
- fffff800`0416e214 65488b3c2588010000 mov rdi,qword ptr gs:[188h]
- fffff800`0416e21d 4533e4 xor r12d,r12d
- fffff800`0416e220 488bea mov rbp,rdx
- fffff800`0416e223 66ff8fc4010000 dec word ptr [rdi+1C4h]
- fffff800`0416e22a 498bdc mov rbx,r12
- fffff800`0416e22d 488bd1 mov rdx,rcx
- fffff800`0416e230 488b0d9149edff mov rcx,qword ptr [nt!PspCidTable (fffff800`04042bc8)]
- fffff800`0416e237 e834480200 call nt!ExMapHandleToPointer (fffff800`04192a70)
- fffff800`0416e23c 458d6c2401 lea r13d,[r12+1]
第二种方法就是KPCR.KdVersionBlock->PspCidTable(wdbgexts.h)。这种相对简单。
获取kpcr可以参考https://blog.csdn.net/hu3167343/article/details/7612595,
-
- NTSTATUS
- DriverEntry(IN PDRIVER_OBJECT pDriverObj, IN PUNICODE_STRING pRegistryString)
- {
- NTSTATUS status = STATUS_SUCCESS;
- ULONG FSAddr;
-
- pDriverObj->DriverUnload = DriverUnload;
-
- KeSetSystemAffinityThread(1); //使当前线程运行在第一个处理器上,之所以这样每一个cpu都有个KPCR,但是系统不会为每一个CPU设置KdVersionBlock,只会对第一个cpu设置,这个跟系统引导有关,ntosknrl这个也有个InitSyStem,会对KdVersionBlock初始化,可以理解这里就是单核,还在初始化,如果不设置,获取到其他cpu这里就是0
- __asm{
- push eax
- mov eax, fs:[0x34] ;得到KdVersionBlock的地址
- add eax,18h ;得到指向PsLoadedModuleList的地址
- mov eax,[eax] ;得到PsLoadedModuleList的地址
- mov eax,[eax] ;取出PsLoadedModuleList里面的内容, 即KLDR_DATA_TABLE_ENTRY结构
- mov eax,[eax+18h] ;取出DllBase, 即ntoskrnl.exe的基地址
- mov FSAddr, eax
- pop eax
- }
- KeRevertToUserAffinityThread();//恢复线程运行的处理器
-
- KdPrint(("0x%08X\n", FSAddr));
- return STATUS_SUCCESS;
- }
mov eax, fs:[0x34] ;得到KdVersionBlock的地址(),63位就变成了GS段,然后再去找头文件wdbgexts.h。
但是这里有个问题,a.exe创建,记录在这个表里,然后a进程又退出了。退出时候肯定会从PspCidTable摘掉这个进程。但是在期间b进程调用链OpenProcess获取了a进程。这时候会出现一个问题,a进程由于被b打开,所以计数器不为0,所以此时退出并不能销毁自己。所以会造成僵尸进程。
如果不想自己解析就调用windowsapi,ExEnumHandleTable,回调函数里也有。
windows句柄表分配算法分析可以参考下面网页
https://bbs.pediy.com/thread-84827.htm
以上急速通过PspCidTable的方法,此外还可以
通过PsActiveProcessHead
这是一个链表,系统每创建一个进程,都会把进程放到PsActiveProcessHead,对这个双向链表做循环ActiveProcessLinks.Blink,就能遍历枚举所有进程。或者KPCR的KdVersionBlock->PsActiveProcessHead里也有。
所以病毒之类的要想隐藏自己。就可以根据Pid去遍历找到PspCidTable.handle_table_entry.Object这一项,然后指向NULL,还有就是remove掉PsActiveProcessHead自己,但是由于x64有PG,所以可能会蓝屏。
第三种方法是KiWaitInListHead/KiWaitOutListHead/KiDispatcherReadyListHead,
这个只能枚举一部分,前2者是链表,通过ETRHEAD.WaitListEntry串联(这些个链表是系统阻塞队列,存放着系统的一些线程通过API,IoThreadToProcess就能得到所属进程)。后者是在非2000系统是LIST_ENTRY[32],也是通过ETHERA.WaitListEntry串联(这个是等待队列)。
关于这3个表的找法,前两个表xp下是反汇编KeDelayExcution Thread,后面版本系统通过KeGetCurrentPrcb()->KiWaitIniListHead,后一个表在xp下是通过KiReadyThread->KiDispatcherReadyListHead,后面版本KeGetCurrentPrcb()->kiDispatcherReadyListHead。参考资料
https://blog.csdn.net/iiprogram/article/details/672558
关于病毒这方面隐藏,工作量就比较大了。
关于内核枚举驱动进程的第四种方法是
搜索内存
因为不管前面再这么隐藏,不可能抹去进程对象。所以我们可以搜索内存,找特征码,这里的问题就是提取特诊码,还有搜索范围。
关于搜索范围,32位只会有4G寻址空间,所以搜索0x800000到0xFFFFFFFF,64位地址空间是2^47,就不能简单for循环了。
在64位,win7是写死的,比如0xFFFFFA8000000000,在win8是因为这种进程对象是放在非分页内存,而非分页内存范围是有关键字MmIsNoPagedSystemAddressValid和MmNoPagedPoolStart这两个字段之间搜索。
win10里面既没写死,也没有关键字了。但是因为这些Object会被集中放置。所以我们可以在写DriverEntry是有drvObj,或者IoCtldevice会有自己进程上下文。这些都是放一起的,所以我们就以这个点为基准上下扫描。
第五种方法是
CSRSS.EXE句柄
原理是系统每创建一个进程或者线程,都会给CSRSS发一个LPC消息,告诉其创建进程,CSRSS就会去保存进程线程句柄,所以我们就想着找到CSRSS然后遍历里面的句柄。比如拿到进程句柄后,NtQuerySystemInformation,会返回handle和Object(eprocess).
操作就是csrss.exe识别。这个进程肯定有id,然后NtQuerySystemInformation,收集句柄对象。然后用Csrss过滤,然后筛选感兴趣的句柄。哪些是进程句柄,这时有两种思路,1.NtQuerySystemInformation有句柄类型代号,7是进程。如果不想不硬编码7,就是先看自己进程句柄的类型,然后比较是否一样也行。
然后根据句柄对应的object,拿到进程对象。然后就能搜集起来了。
这里面的问题比如CSRSS.exe句柄怎么找,就要要知道他的进程id,参考资料
https://www.2cto.com/article/201205/131961.html
原理就是通过GetInfoTable获取进程所有句柄。对每一个句柄分析。如果不是自己进程句柄就打开文件,然后复制一份回来再query。然后硬编码发现名字是L"\\Windows\\ApiPort",就认为所在进程就是CSRSS进程。因为每创建一个进程就会跟他自己的CSRSS通信,让CSRSS记录一份,通过LPC,所以会有一个ALPCPORT,在通信时肯定要send和get之类的,这时需要名字,名字就是Windows\\ApiPort。只有csrss会占用他。所以就可以了。
- HANDLE GetCsrPid()
- {
- HANDLE Process, hObject;
- HANDLE CsrId = (HANDLE)0;
- OBJECT_ATTRIBUTES obj;
- CLIENT_ID cid;
- UCHAR Buff[0x100];
- POBJECT_NAME_INFORMATION ObjName = (PVOID)&Buff;
- PSYSTEM_HANDLE_INFORMATION_EX Handles;
- ULONG r;
-
- Handles = GetInfoTable(SystemHandleInformation);
-
- if (!Handles) return CsrId;
-
- for (r = 0; r < Handles->NumberOfHandles; r++)
- {
- if (Handles->Information[r].ObjectTypeNumber == 21) //Port object
- {
- InitializeObjectAttributes(&obj, NULL, OBJ_KERNEL_HANDLE, NULL, NULL);
-
- cid.UniqueProcess = (HANDLE)Handles->Information[r].ProcessId;
- cid.UniqueThread = 0;
-
- if (NT_SUCCESS(NtOpenProcess(&Process, PROCESS_DUP_HANDLE, &obj, &cid)))
- {
- if (NT_SUCCESS(ZwDuplicateObject(Process, (HANDLE)Handles->Information[r].Handle,NtCurrentProcess(), &hObject, 0, 0, DUPLICATE_SAME_ACCESS)))
- {
- if (NT_SUCCESS(ZwQueryObject(hObject, ObjectNameInformation, ObjName, 0x100, NULL)))
- {
- if (ObjName->Name.Buffer && !wcsncmp(L"\\Windows\\ApiPort", ObjName->Name.Buffer, 20))
- {
- CsrId = (HANDLE)Handles->Information[r].ProcessId;
- }
- }
-
- ZwClose(hObject);
- }
-
- ZwClose(Process);
- }
- }
- }
-
- ExFreePool(Handles);
- return CsrId;
- }
因为DriverEntry在的system进程并没有载入win32k.sys,所以,要访问shadowssdt表,必须KeStackAttackProces到一个有GUI线程的进程中,而csrss.exe就是这样的一个合适的进程,所以我们先attachProcess过去,切换cr3,最后再detach。然后在其中就能操作了。
那关于这个方法的anti就是让ARK工具看不见或者看见的不是实际的。在PTE做手脚,或者EPT hook,或者在ark工具的页表做pte hook,
其他方法还有交换上下文,因为进程在频繁切换,此时做inline hook收集。此时要注意寄存器,还有DPC级别。
RootKit在驱动层就是hookR3R0调用驱动检测的函数。
前面做内存搜索,那么关于搜索的特征码怎么确定呢?
- nt!_EPROCESS
- +0x000 Pcb : _KPROCESS
- +0x160 ProcessLock : _EX_PUSH_LOCK
- +0x168 CreateTime : _LARGE_INTEGER//创建时间
- +0x170 ExitTime : _LARGE_INTEGER//已经消亡了,僵尸进程这里是个点
- +0x178 RundownProtect : _EX_RUNDOWN_REF//进程保护,类似个锁,可以防止进程被结束掉,比如attach别人进程先加个锁,然后detach再释放
- +0x180 UniqueProcessId : Ptr64 Void//pid
- +0x188 ActiveProcessLinks : _LIST_ENTRY
- +0x198 ProcessQuotaUsage : [2] Uint8B
- +0x1a8 ProcessQuotaPeak : [2] Uint8B
- +0x1b8 CommitCharge : Uint8B
- +0x1c0 QuotaBlock : Ptr64 _EPROCESS_QUOTA_BLOCK
- +0x1c8 CpuQuotaBlock : Ptr64 _PS_CPU_QUOTA_BLOCK
- +0x1d0 PeakVirtualSize : Uint8B
- +0x1d8 VirtualSize : Uint8B
- +0x1e0 SessionProcessLinks : _LIST_ENTRY//进程检测相关
- +0x1f0 DebugPort : Ptr64 Void
- +0x1f8 ExceptionPortData : Ptr64 Void
- +0x1f8 ExceptionPortValue : Uint8B
- +0x1f8 ExceptionPortState : Pos 0, 3 Bits
- +0x200 ObjectTable : Ptr64 _HANDLE_TABLE//进程句柄链表
- +0x208 Token : _EX_FAST_REF//令牌权限相关
- +0x210 WorkingSetPage : Uint8B
- +0x218 AddressCreationLock : _EX_PUSH_LOCK
- +0x220 RotateInProgress : Ptr64 _ETHREAD
- +0x228 ForkInProgress : Ptr64 _ETHREAD
- +0x230 HardwareTrigger : Uint8B
- +0x238 PhysicalVadRoot : Ptr64 _MM_AVL_TABLE//占用的物理内存
- +0x240 CloneRoot : Ptr64 Void
- +0x248 NumberOfPrivatePages : Uint8B
- +0x250 NumberOfLockedPages : Uint8B
- +0x258 Win32Process : Ptr64 Void
- +0x260 Job : Ptr64 _EJOB
- +0x268 SectionObject : Ptr64 Void//这个和下面可以获得进程对应的模块路径
- +0x270 SectionBaseAddress : Ptr64 Void
- +0x278 Cookie : Uint4B
- +0x27c UmsScheduledThreads : Uint4B
- +0x280 WorkingSetWatch : Ptr64 _PAGEFAULT_HISTORY
- +0x288 Win32WindowStation : Ptr64 Void
- +0x290 InheritedFromUniqueProcessId : Ptr64 Void
- +0x298 LdtInformation : Ptr64 Void
- +0x2a0 Spare : Ptr64 Void
- +0x2a8 ConsoleHostProcess : Uint8B
- +0x2b0 DeviceMap : Ptr64 Void
- +0x2b8 EtwDataSource : Ptr64 Void
- +0x2c0 FreeTebHint : Ptr64 Void
- +0x2c8 FreeUmsTebHint : Ptr64 Void
- +0x2d0 PageDirectoryPte : _HARDWARE_PTE
- +0x2d0 Filler : Uint8B
- +0x2d8 Session : Ptr64 Void
- +0x2e0 ImageFileName : [15] UChar
- +0x2ef PriorityClass : UChar
- +0x2f0 JobLinks : _LIST_ENTRY
- +0x300 LockedPagesList : Ptr64 Void
- +0x308 ThreadListHead : _LIST_ENTRY
- +0x318 SecurityPort : Ptr64 Void//32位指向一个peb64为
- +0x320 Wow64Process : Ptr64 Void
- +0x328 ActiveThreads : Uint4B
- +0x32c ImagePathHash : Uint4B
- +0x330 DefaultHardErrorProcessing : Uint4B
- +0x334 LastThreadExitStatus : Int4B
- +0x338 Peb : Ptr64 _PEB
- +0x340 PrefetchTrace : _EX_FAST_REF
- +0x348 ReadOperationCount : _LARGE_INTEGER
- +0x350 WriteOperationCount : _LARGE_INTEGER
- +0x358 OtherOperationCount : _LARGE_INTEGER
- +0x360 ReadTransferCount : _LARGE_INTEGER
- +0x368 WriteTransferCount : _LARGE_INTEGER
- +0x370 OtherTransferCount : _LARGE_INTEGER
- +0x378 CommitChargeLimit : Uint8B
- +0x380 CommitChargePeak : Uint8B
- +0x388 AweInfo : Ptr64 Void
- +0x390 SeAuditProcessCreationInfo : _SE_AUDIT_PROCESS_CREATION_INFO
- +0x398 Vm : _MMSUPPORT
- +0x420 MmProcessLinks : _LIST_ENTRY
- +0x430 HighestUserAddress : Ptr64 Void
- +0x438 ModifiedPageCount : Uint4B
- +0x43c Flags2 : Uint4B
- +0x43c JobNotReallyActive : Pos 0, 1 Bit
- +0x43c AccountingFolded : Pos 1, 1 Bit
- +0x43c NewProcessReported : Pos 2, 1 Bit
- +0x43c ExitProcessReported : Pos 3, 1 Bit
- +0x43c ReportCommitChanges : Pos 4, 1 Bit
- +0x43c LastReportMemory : Pos 5, 1 Bit
- +0x43c ReportPhysicalPageChanges : Pos 6, 1 Bit
- +0x43c HandleTableRundown : Pos 7, 1 Bit
- +0x43c NeedsHandleRundown : Pos 8, 1 Bit
- +0x43c RefTraceEnabled : Pos 9, 1 Bit
- +0x43c NumaAware : Pos 10, 1 Bit
- +0x43c ProtectedProcess : Pos 11, 1 Bit
- +0x43c DefaultPagePriority : Pos 12, 3 Bits
- +0x43c PrimaryTokenFrozen : Pos 15, 1 Bit
- +0x43c ProcessVerifierTarget : Pos 16, 1 Bit
- +0x43c StackRandomizationDisabled : Pos 17, 1 Bit
- +0x43c AffinityPermanent : Pos 18, 1 Bit
- +0x43c AffinityUpdateEnable : Pos 19, 1 Bit
- +0x43c PropagateNode : Pos 20, 1 Bit
- +0x43c ExplicitAffinity : Pos 21, 1 Bit
- +0x440 Flags : Uint4B
- +0x440 CreateReported : Pos 0, 1 Bit
- +0x440 NoDebugInherit : Pos 1, 1 Bit
- +0x440 ProcessExiting : Pos 2, 1 Bit
- +0x440 ProcessDelete : Pos 3, 1 Bit
- +0x440 Wow64SplitPages : Pos 4, 1 Bit
- +0x440 VmDeleted : Pos 5, 1 Bit
- +0x440 OutswapEnabled : Pos 6, 1 Bit
- +0x440 Outswapped : Pos 7, 1 Bit
- +0x440 ForkFailed : Pos 8, 1 Bit
- +0x440 Wow64VaSpace4Gb : Pos 9, 1 Bit
- +0x440 AddressSpaceInitialized : Pos 10, 2 Bits
- +0x440 SetTimerResolution : Pos 12, 1 Bit
- +0x440 BreakOnTermination : Pos 13, 1 Bit
- +0x440 DeprioritizeViews : Pos 14, 1 Bit
- +0x440 WriteWatch : Pos 15, 1 Bit
- +0x440 ProcessInSession : Pos 16, 1 Bit
- +0x440 OverrideAddressSpace : Pos 17, 1 Bit
- +0x440 HasAddressSpace : Pos 18, 1 Bit
- +0x440 LaunchPrefetched : Pos 19, 1 Bit
- +0x440 InjectInpageErrors : Pos 20, 1 Bit
- +0x440 VmTopDown : Pos 21, 1 Bit
- +0x440 ImageNotifyDone : Pos 22, 1 Bit
- +0x440 PdeUpdateNeeded : Pos 23, 1 Bit
- +0x440 VdmAllowed : Pos 24, 1 Bit
- +0x440 CrossSessionCreate : Pos 25, 1 Bit
- +0x440 ProcessInserted : Pos 26, 1 Bit
- +0x440 DefaultIoPriority : Pos 27, 3 Bits
- +0x440 ProcessSelfDelete : Pos 30, 1 Bit
- +0x440 SetTimerResolutionLink : Pos 31, 1 Bit
- +0x444 ExitStatus : Int4B
- +0x448 VadRoot : _MM_AVL_TABLE
- +0x488 AlpcContext : _ALPC_PROCESS_CONTEXT
- +0x4a8 TimerResolutionLink : _LIST_ENTRY
- +0x4b8 RequestedTimerResolution : Uint4B
- +0x4bc ActiveThreadsHighWatermark : Uint4B
- +0x4c0 SmallestTimerResolution : Uint4B
- +0x4c8 TimerResolutionStackRecord : Ptr64 _PO_DIAG_STACK_RECORD
首先观察eprocess结构,找到一些固定的点。不能找可以随意改的地方比如进程UniqueProcessId 被篡改,那我们再想打开这个进程,还是传打开前的id,因为函数调用方面,OpenProcess(40)->NtOpenProcess(40)->PsLookupProcessByProcessId(40,eprocess)。
所以可以通过下面几个地方作为入口去提取特征。
ObjectType(Address,固定ID,随机固定值)
PEB
VADRoot,记录了进程占用了哪些虚拟内存块,通过VirtualAlloc申请的。是一颗平衡二叉树。
ObjectTable,是进程句柄表,类似一颗树。
几个ThreadListHead,以为进程肯定是有线程的。这个线程就是在PEB里。
结合ETHREDA信息。
其中ObjectType是对象的类型,可以通过_eprocess-sizeof(OBJECT_HEADER)
- 1: kd> dt _OBJECT_HEADER
- nt!_OBJECT_HEADER
- +0x000 PointerCount : Int8B
- +0x008 HandleCount : Int8B
- +0x008 NextToFree : Ptr64 Void
- +0x010 Lock : _EX_PUSH_LOCK
- +0x018 TypeIndex : UChar
- +0x019 TraceFlags : UChar
- +0x01a InfoMask : UChar
- +0x01b Flags : UChar
- +0x020 ObjectCreateInfo : Ptr64 _OBJECT_CREATE_INFORMATION
- +0x020 QuotaBlockCharged : Ptr64 Void
- +0x028 SecurityDescriptor : Ptr64 Void
- +0x030 Body : _QUAD
这个就是OBJECT_HEADER,Body就是根据不同对象而不同,这里就是eprocess。TypeIndex 就是对象类型,xp里面指向对象类型,什么PsProcessType的结构体。win7是硬编码,win10之后都把他编码了,动态变化的。过的话哦就是伪造对象类型。
结束进程可以通过
NtTerminateProcess
涂改内存(应用层,写成00或cc,就会出异常,关了)
卸载模块(FreeLibrary(ntdll.dll))
窗口攻击
CreateJobObject/AssignProcessToJobObject/TerminateJobObject或设置关闭时结束,就是把进程关联到工作集,然后结束工作集。
注入代码,ExitProcess
SetThreadContext,设置线程指向非法内存
调试器吸附,退出
对每个线程(GetNextProcessThread)PspTerminateThreadByPointer.
线程相关的操作跟进程一样,也是涉及,线程枚举,ETHREAD识别,TerminateThread
线程枚举总体来说有
PspCidTable
hook SwapContext
搜索内存
ThreadListEntry,因为枚举某个进程的线程,已经知道进程对象,可以遍历EPROCESS的ThreadListEntry,或者KPROCESS的ThreadLIstEntry
跟EPROCESS类似,可以通过
ObjectType
TEB(但是比如system线程内核态,没有)
ETHREAD.ThreadListHead
KTHREAD.ThreadListHead
定位EPROCESS,检测EPROCESS的一些信息
NtTerminateThread
PspTerminateThreadByPointer(未导出,需要反汇编上面的函数找到)
Insert APC杀线程
https://bbs.pediy.com/thread-59091-1.htm
https://www.cnblogs.com/dsky/archive/2012/02/23/2364503.html
就是进程会记录在,PEB里,有个LDR,系统给每个模块分配一个_LDR_DATA_TABLE_ENTRY,通过链表连在一起。其中结构入下
- 1: kd> dt _PEB
- nt!_PEB
- +0x000 InheritedAddressSpace : UChar
- +0x001 ReadImageFileExecOptions : UChar
- +0x002 BeingDebugged : UChar
- +0x003 BitField : UChar
- +0x003 ImageUsesLargePages : Pos 0, 1 Bit
- +0x003 IsProtectedProcess : Pos 1, 1 Bit
- +0x003 IsLegacyProcess : Pos 2, 1 Bit
- +0x003 IsImageDynamicallyRelocated : Pos 3, 1 Bit
- +0x003 SkipPatchingUser32Forwarders : Pos 4, 1 Bit
- +0x003 SpareBits : Pos 5, 3 Bits
- +0x008 Mutant : Ptr64 Void
- +0x010 ImageBaseAddress : Ptr64 Void
- +0x018 Ldr : Ptr64 _PEB_LDR_DATA
里面的LDR
- 1: kd> dt _PEB_LDR_DATA
- nt!_PEB_LDR_DATA
- +0x000 Length : Uint4B
- +0x004 Initialized : UChar
- +0x008 SsHandle : Ptr64 Void
- +0x010 InLoadOrderModuleList : _LIST_ENTRY
- +0x020 InMemoryOrderModuleList : _LIST_ENTRY
- +0x030 InInitializationOrderModuleList : _LIST_ENTRY
- +0x040 EntryInProgress : Ptr64 Void
- +0x048 ShutdownInProgress : UChar
- +0x050 ShutdownThreadId : Ptr64 Void
这里面InLoadOrderModuleList指向下面的InLoadOrderLinks,InMemoryOrderModuleList指向 下面的InMemoryOrderLinks,InMemoryOrderModuleList指向下面的InInitializationOrderLinks
- 1: kd> dt _LDR_DATA_TABLE_ENTRY
- nt!_LDR_DATA_TABLE_ENTRY
- +0x000 InLoadOrderLinks : _LIST_ENTRY
- +0x010 InMemoryOrderLinks : _LIST_ENTRY
- +0x020 InInitializationOrderLinks : _LIST_ENTRY
- +0x030 DllBase : Ptr64 Void
- +0x038 EntryPoint : Ptr64 Void
- +0x040 SizeOfImage : Uint4B
- +0x048 FullDllName : _UNICODE_STRING
- +0x058 BaseDllName : _UNICODE_STRING
- +0x068 Flags : Uint4B
- +0x06c LoadCount : Uint2B
- +0x06e TlsIndex : Uint2B
- +0x070 HashLinks : _LIST_ENTRY
- +0x070 SectionPointer : Ptr64 Void
- +0x078 CheckSum : Uint4B
- +0x080 TimeDateStamp : Uint4B
- +0x080 LoadedImports : Ptr64 Void
- +0x088 EntryPointActivationContext : Ptr64 _ACTIVATION_CONTEXT
- +0x090 PatchInformation : Ptr64 Void
- +0x098 ForwarderLinks : _LIST_ENTRY
- +0x0a8 ServiceTagLinks : _LIST_ENTRY
- +0x0b8 StaticLinks : _LIST_ENTRY
- +0x0c8 ContextInformation : Ptr64 Void
- +0x0d0 OriginalBase : Uint8B
- +0x0d8 LoadTime : _LARGE_INTEGER
64位下稍微复杂些
分两种情况
1.64位程序在64位系统运行,这些模块都会记录到PEB,可以通过EPROCESS里面。
2.32位程序运行在64位系统运行。会有两个ntdll.dll32位和64位模块。windows会把64模块保存在ProcessPEB,32位保存在EPROCESS::wow64process。
有个注意点,32位程序运行在64位OS下
32位peb(wow64process)的ntdll.dll在LDR_DTAT_TABLE_ENTRY里的全路径和64位在这里记录的一样,这里就有问题到底哪个是真的。实际上32位的需要重定位到syswow64(Wow64EnableWow64FsRedirection)
所以上面的隐藏方法就是把自己从上面3个链表摘掉。
这里也跟上面枚举PEB一样,也要分32位系统和64位运行32位和64位程序的情况。这个链表是_LDR_DATA_TABLE_ENTRY的 +0x070 HashLinks连在一起的。
ZwQueryVirtualMemory(MemorySectionName)
for(void *p=0;pM<0x7FFFF...;p+=0x1000){ZwQueryVirtualMemory(hprocess,p,MemorySectionName,ppName);}
https://bbs.pediy.com/thread-66886.htm
搜索范围
PE识别
远线程FreeLibrary
NtUnmapViewOfSection
进程快捷键枚举
PsLoadMuduleList
ObjectDirectory
PsLoadMuduleList
驱动模块加载,都会保存到一个全局链表,PsLoadMuduleList,非导出,通过LDR_DATA_TABLE_ENTRY的第一项InLoadOrderLinks链接在一起,然后可以遍历这个链表得到所有。
KPCR.KdVersionBlock->PsLoadModuleList.
另外在DriverEntry里DriverObject的DriverSection就是LDR_DATA_TABLE_ENTRY,PsLoadModuleList的加载顺序就是ntoskrnl然后其他。
ObjectDirectory,对象目录,驱动会创建对象,通过驱动对象或者设备对象得到驱动模块,所有驱动都会保存在对象树里。
- 1: kd> !object \\
- Object: fffff8a0000040d0 Type: (fffffa8001848240) Directory
- ObjectHeader: fffff8a0000040a0 (new version)
- HandleCount: 0 PointerCount: 42
- Directory Object: 00000000 Name: \
-
- Hash Address Type Name
- ---- ------- ---- ----
- 01 fffff8a0000068f0 Directory ObjectTypes
- 05 fffff8a0003f92d0 SymbolicLink SystemRoot
- 06 fffff8a00013fc50 Directory Sessions
- 07 fffffa80036c8090 ALPC Port MmcssApiPort
- 08 fffff8a00000c260 Directory ArcName
- 09 fffff8a000071d30 Directory NLS
- 10 fffffa80036e2c00 ALPC Port ThemeApiPort
- fffff8a0001321e0 Directory Windows
- fffffa80038c4660 Event LanmanServerAnnounceEvent
- fffff8a000008060 Directory GLOBAL??
- 11 fffff8a000139080 Directory RPC Control
- 13 fffffa8002f757a0 Event EFSInitEvent
- 14 fffffa8001d62480 Device clfs
- fffff8a0002edde0 SymbolicLink Dfs
- 15 fffffa800279cb80 ALPC Port SeRmCommandPort
- fffffa80026b9860 Event CsrSbSyncEvent
- 16 fffff8a000004370 SymbolicLink DosDevices
- 17 fffff8a005ebb2e0 Directory KnownDlls32
- 18 fffff8a00001aca0 Key \REGISTRY
- 19 fffff8a003261c90 Directory BaseNamedObjects
- 20 fffffa80018f0830 ALPC Port PowerPort
- 21 fffffa8003640bd0 ALPC Port SmSsWinStationApiPort
- fffffa800276af20 Event UniqueInteractiveSessionIdEvent
- fffff8a00006f390 Directory UMDFCommunicationPorts
- 22 fffff8a00396cbd0 Directory KnownDlls
- fffffa80018f0140 ALPC Port PowerMonitorPort
- 23 fffff8a000006eb0 Directory KernelObjects
- fffff8a00006f060 Directory FileSystem
- fffffa8001d5c520 Device Ntfs
- 26 fffff8a000006060 Directory Callback
- fffffa8003579090 ALPC Port SeLsaCommandPort
- 28 fffff8a00000b060 Directory Security
- 29 fffffa800371d6a0 ALPC Port UxSmsApiPort
- 30 fffff8a000010920 Directory Device
- 32 fffffa80035a7260 Event DSYSDBG.Debug.Trace.Memory.218
- 34 fffffa800279f610 ALPC Port SmApiPort
- fffff8a000f87080 Section LsaPerformance
- fffffa80027a4510 Event UniqueSessionIdEvent
- 36 fffff8a000071b80 Directory Driver
- fffffa800359b2b0 Event SAM_SERVICE_STARTED
我们要找到就是\driver或者\device,如果是驱动就可以找到驱动对象,如果是设备,可以找到属于哪个驱动,然后去找driversection。
这里面有个特殊的_object_directory
利用IoDriver(Device)ObjectType::TypeList
就是每个object_head里有个objectType的TypeIndex,里面有个Typelist,当标志位致1就会插入到这个表,但是系统没用,或者在object_head的前面还有一个结构。
暴力搜索DriverObject
https://bbs.pediy.com/thread-109819.htm
常规是调用ZwUnloadDriver。必须要有服务名。如果没有服务名就要自己实现。就是想办法直接传driverobject去系统空间调用DriverUnload。
PE loader
我们检测比如ssdt有没有被inlinehook,就需要比对文件,要把检测模块从当前内存dump出来,我们要跟
文件的作对比。我们要把文件中的这份加载到内存比较。所以需要PE loader
但要注意比较的时候可能不同,比如一些数据全局变量,加壳的都是动态的,所以这些都要注意。比如如果是全局变量引起的不同,可以通过节比较,不整个比较,节里面比如不可写的节一个一个字节比较是可以,但如果节可写,就要注意了。
所以我们可以检测PE头,如果变化过多说明可能误报。
还有如果代码自修改。也是需要注意,所以一定要合理设计检测策略。
首先导出函数名收集可以从ntdll里面导出表里有。序号可以从导出表导出函数函数地址指向的第二个操作数获取(32位),64位是第二个函数第二个操作数,但是有个函数ZWQuerySystemTime没法收集需要单独硬编码。原因是,在内核中有个数据结构
_KUSER_SHARED_DATA的SystemTime在应用层就能读取,所以64位改了实现。
获取干净的ssdt表,可以从ntdll.dll的重定位表里收集。或者4字节扫看哪个不在这个模块。
获取正在使用的ssdt表,通过X86下KeServiceDescriptorTable,x64(SSDT为了节省空间还是一个4字节,距离ssdt表头的距离,在左移4位,低4位是参数个数)是在
函数名收集因为没有导出,通过win32.sys加载ida去找,然后硬编码了。一般都在.data开始,或者第四个字节都是BF。
干净的Shadow SSDT表获取,x86data段。x64导出了,通过W32pServiceTable。
https://www.cnblogs.com/Jesses/articles/1647674.html
驱动派发函数的检测,分为
函数名
当前函数
原始函数
比对
当前函数对象名获取通过打开当前对象。原始函数地址获取通过反汇编引擎。或者自己load这个ntfs.sys,load之后重定向,对某些函数进行导入(导入个假的之类的让堆栈平衡为了绕过检测代码)或者挂钩子,之后创建一个线程,自己调start函数,传的就是自己伪造的DriverObject,然后比对是否hook了。
判断虚拟地址是否合法。解析都是cpu硬件完成的。
32位模式下,PDE和PTE,如果非PAE,非扩展分页,就是通过cr3,然后101012去找。如果申请4M内存,会有1024个页面。会在第二级pte里面有1024个。这样是4K,所以intel为了省内存,使用只用一级PDE,没有二级,然后里面有一位进行标志LARGEPAGE,标志只有2级寻址。就是扩展分页模式。这个大页面是2^10*2^12=4M
关于PAE物理地址扩展模式就是变成29912分页。扩展分页模式。这个大页面是2^9*2^12=2m
64位模式下,使用4个页表,PML4,PDPE,PDE,PTE,9,9,9,9,12
页就时4k,大页2M大小。AMD大页面是把后面在合一个页表,大页就是1G。
高清这个原理可以自己实现一个MmIsAddressValid
- int PTESize;
- UINT_PTR PAGE_SIZE_LARGE;
- UINT_PTR MAX_PDE_POS;
- UINT_PTR MAX_PTE_POS;
-
- struct PTEStruct
- {
- unsigned P : 1; // present (1 = present)是否存在
- unsigned RW : 1; // read/write
- unsigned US : 1; // user/supervisor
- unsigned PWT : 1; // page-level write-through
- unsigned PCD : 1; // page-level cache disabled
- unsigned A : 1; // accessed
- unsigned Reserved : 1; // dirty
- unsigned PS : 1; // page size (0 = 4-KB page)
- unsigned G : 1; // global page
- unsigned A1 : 1; // available 1 aka copy-on-write
- unsigned A2 : 1; // available 2/ is 1 when paged to disk
- unsigned A3 : 1; // available 3
- unsigned PFN : 20; // page-frame number
- };
-
- void InitMemSafe()
- {
- #ifndef AMD64
- ULONG cr4reg;
- //determine if PAE is used
- cr4reg=(ULONG)__readcr4();
- if ((cr4reg & 0x20)==0x20)
- {
- PTESize=8; //pae
- PAGE_SIZE_LARGE=0x200000;
- MAX_PDE_POS=0xC0604000;
- MAX_PTE_POS=0xC07FFFF8;
- }
- else
- {
- PTESize=4;
- PAGE_SIZE_LARGE=0x400000;
- MAX_PDE_POS=0xC0301000;
- MAX_PTE_POS=0xC03FFFFC;
- }
- #else
- PTESize=8; //pae
- PAGE_SIZE_LARGE=0x200000;
- MAX_PTE_POS=0xFFFFF6FFFFFFFFF8ULL;
- MAX_PDE_POS=0xFFFFF6FB7FFFFFF8ULL;
- #endif
- }
-
- BOOLEAN IsAddressSafe(UINT_PTR StartAddress)
- {
- #ifdef AMD64
- //cannonical check. Bits 48 to 63 must match bit 47
- UINT_PTR toppart=(StartAddress >> 47);
- if (toppart & 1)
- {
- //toppart must be 0x1ffff
- if (toppart != 0x1ffff)
- return FALSE;
- }
- else
- {
- //toppart must be 0
- if (toppart != 0)
- return FALSE;
-
- }
- #endif
- //PDT+PTE judge
- {
- #ifdef AMD64
- UINT_PTR kernelbase=0x7fffffffffffffffULL;
- if (StartAddress<kernelbase)
- {
- return TRUE;
- }
- else
- {
- PHYSICAL_ADDRESS physical;
- physical.QuadPart=0;
- physical=MmGetPhysicalAddress((PVOID)StartAddress);
- return (physical.QuadPart!=0);
- }
- return TRUE; //for now untill I ave figure out the win 4 paging scheme
- #else
- ULONG kernelbase=0x7ffe0000;
- UINT_PTR PTE,PDE;
- struct PTEStruct *x;
- if (StartAddress<kernelbase)
- {
- return TRUE;
- }
- PTE=(UINT_PTR)StartAddress;
- PTE=PTE/0x1000*PTESize+0xc0000000;
- //now check if the address in PTE is valid by checking the page table directory at 0xc0300000 (same location as CR3 btw)
- PDE=PTE/0x1000*PTESize+0xc0000000; //same formula
- x=(struct PTEStruct *)PDE;
- if ((x->P==0) && (x->A2==0))
- {
- //Not present or paged, and since paging in this area isn't such a smart thing to do just skip it
- //perhaps this is only for the 4 mb pages, but those should never be paged out, so it should be 1
- //bah, I've got no idea what this is used for
- return FALSE;
- }
- if (x->PS==1)
- {
- //This is a 4 MB page (no pte list)
- //so, (startaddress/0x400000*0x400000) till ((startaddress/0x400000*0x400000)+(0x400000-1) ) ) is specified by this page
- }
- else //if it's not a 4 MB page then check the PTE
- {
- //still here so the page table directory agreed that it is a usable page table entry
- x=(PVOID)PTE;
- if ((x->P==0) && (x->A2==0))
- return FALSE; //see for explenation the part of the PDE
- }
- return TRUE;
- #endif
- }
- }
比如创建进程。线程这些x86可以通过HOOK,64位通过PsSetCreateProcessNorityRountine()
{nPid,NtOpenProcess() NtTerminateProcess}
禁止创建进程,
FltRegisterFilter
通过hook那些修改的函数。
Win7 X64 ShadowSSDT hook
;知道shadow ssdt serviceTable[] ULONG
自己函数放不进去,不是一个模块,所以先去win32k.sys找间隙(nop),要有八个字节,放我们hook函数地址,再要5个字节,进行jmp到我们放地址的地方。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。