ArgumentTable成员,指向我们刚刚分配的空间。Windows系统是多线程的系统,线程每时每刻都在Windows内核调度器的管理下协调地切换CPU运行着,对于多核CPU更是如此,因为我们改变的是整下Windows系统的唯一的系统调用表,因此我们在改变的同时,别的线程很有可能会访问这两个系统服务表,从而可能会导致错误。对于单核CPU我们可以紧紧提高中断请求级(IRQL)就行了,比如我们调用KeRaiseIrql(DISPATH_LEVEL)把IRQL提高到这个请求级之后,Windows调度程序就不会得到执行了,别的线程也就没有机会得到运行。但是对于多核CPU ,情况就不是这样了,虽然你在一个CPU上提高了IRQL,但是别的CPU照样可以运行别的线程,所以一定要保证在改变的同时还要保证别的CPU也不能运行别的线程,Windows系统专门提供了自旋锁机制,我们可以利用Windows系统内核自己使用的自旋锁LockQueueDispatcherLock ,它专门锁调度程序的,只要我们锁了这个锁,线程调度程序就一定会得不到执行,所以别的线程也一定会得不到运行的。我们调用KeAcquireQueuedSpinLockRaiseToSynch(LockQueueDispatcherLock);来提高IRQL,同时又锁定了别的CPU的调度,所以此时我们再改写Windows系统内核下这两个共享的系统调用表时会很安全的。改写后再调用KeReleaseQueuedSpinLock(LockQueueDispatcherLock,oldirql);来降低IRQL和释放Windows系统的自旋锁,此时Windows系统就会切换别的线程在其中的CPU上去运行。最后我们返回我们自己的系统调用在Windows内核的开始服务ID,方便应用程序OD插件好改变系统调用的流程,具体代码如下: NTSTATUS AddServices() {
ULONG NumberOfServices; KIRQL oldirql;
DbgPrint(\开始执行\\n\
NumberOfServices=sizeof(ServiceTableBase)/sizeof(ServiceTableBase[0]); // 将要增加的服务函数的个数
//KeServiceDescriptorTableShadow=(PSERVICE_DESCRIPTOR_TABLE)GetAddrssofShadowTable(); //获取 Shadow SSDT 地址
if (KeServiceDescriptorTableShadow==NULL) { DbgPrint(\没有找到TableShadow\\n\ return STATUS_UNSUCCESSFUL; }
DbgPrint(\KeServiceDescriptorTableShadow);
NewNumberOfServices=KeServiceDescriptorTable->TableSize+NumberOfServices; //增加后服务数目
StartingServiceId=KeServiceDescriptorTable->TableSize;
25
//得到后来增加服务中第一个服务的序号
DbgPrint(\增加的第一个服务号是StartingServiceId %x \\n\StartingServiceId); /* Allocate suffcient memory to hold the exising services as well as
the services you want to add */
NewServiceTableBase=(unsigned int *) ExAllocatePool(NonPagedPool, NewNumberOfServices*sizeof(unsigned int)); if (NewServiceTableBase==NULL) {
return STATUS_INSUFFICIENT_RESOURCES; }
NewParamTableBase=(unsigned char *) ExAllocatePool(NonPagedPool, NewNumberOfServices);
if (NewParamTableBase==NULL) {
ExFreePool(NewServiceTableBase);
return STATUS_INSUFFICIENT_RESOURCES; }
/* Backup the exising SSDT and SSPT */
memcpy(NewServiceTableBase, KeServiceDescriptorTable->ServiceTable, //原来的SSDT服务地址表拷贝到分配的NewServiceTableBase内存中去 KeServiceDescriptorTable->TableSize*sizeof(unsigned int));
memcpy(NewParamTableBase, KeServiceDescriptorTable->ArgumentTable, //原来的SSDT参数表拷贝到新分配的NewParamTableBase内存中去 KeServiceDescriptorTable->TableSize);
/*对地址表和参数表追加*/
memcpy(NewServiceTableBase+KeServiceDescriptorTable->TableSize, ServiceTableBase, sizeof(ServiceTableBase));
memcpy(NewParamTableBase+KeServiceDescriptorTable->TableSize, ParamTableBase, sizeof(ParamTableBase));
/*更新KeServiceDescriptorTableEntry的SSDT和 SSPT*/ OldServiceTable[0].oldServiceBase=
(DWORD)KeServiceDescriptorTable->ServiceTable;
OldServiceTable[0].oldServiceParamBase=(DWORD)KeServiceDescriptorTable->ArgumentTable;
OldServiceTable[0].oldServiceSize=
(DWORD)KeServiceDescriptorTable->TableSize;
//这里输出新的SSDT 服务函数的地址
/* 更新KeServiceDescriptorTableShadow的SSDT和 SSPT*/ OldServiceTable[1].oldServiceBase=
26
(DWORD)KeServiceDescriptorTableShadow->ServiceTable;
OldServiceTable[1].oldServiceParamBase=(DWORD)KeServiceDescriptorTableShadow->ArgumentTable;
OldServiceTable[1].oldServiceSize=
(DWORD)KeServiceDescriptorTableShadow->TableSize;
//下面改内核服务表时提IRQL,防止内核调度程序调度,自旋锁锁定CPU,只让这一个CPU访问
oldirql=KeAcquireQueuedSpinLockRaiseToSynch(LockQueueDispatcherLock);
KeServiceDescriptorTable->ServiceTable=(PVOID)NewServiceTableBase; KeServiceDescriptorTable->ArgumentTable=NewParamTableBase; KeServiceDescriptorTable->TableSize=NewNumberOfServices;
KeServiceDescriptorTableShadow->ServiceTable=(PVOID)NewServiceTableBase;
KeServiceDescriptorTableShadow->ArgumentTable=NewParamTableBase;
KeServiceDescriptorTableShadow->TableSize=NewNumberOfServices; KeReleaseQueuedSpinLock(LockQueueDispatcherLock,oldirql);
DbgPrint(\
KeServiceDescriptorTable->ServiceTableBase = %x\\n\KeServiceDescriptorTable->ServiceTable); return StartingServiceId; }
5.5 实现我们自己的系统调用函数
对常见的几个系统调用我们一定要自己实现,因为它们很重要,有几下这些函数,NtOpenProcess(),NtReadVirtualMemory(),NtWriteVirtualMemory() NtProtectVirtualMemory(),KeStackAttachProcess(),KiAttachProcess(),等等这几个函数是操作游戏进程的关键函数。还有NtCreateDebugObject(),NtDebugActiveProcess(),NtDebugContinue(),NtWaitForDebugEvent(),NtSetInformationDebugObject()等等这些函数是与调试有关的关键函数。
这些函数代码大多是参考WRK写的,因为WRK是Windows Server 2003的内核,对于Windows XP变化不是很大,有些函数是用IDA逆向ntkrnlpa.exe内核文件得到的,比如KeStackAttachProcess和KiAttachProcess 2003的变化和XP的变化很大。贴个关键函数的代码。 NTSTATUS WINAPI HxNtOpenProcess (
__out PHANDLE ProcessHandle, __in ACCESS_MASK DesiredAccess,
27
__in POBJECT_ATTRIBUTES ObjectAttributes, __in_opt PCLIENT_ID ClientId ) {
HANDLE Handle;
KPROCESSOR_MODE PreviousMode; NTSTATUS Status; PEPROCESS Process; PETHREAD Thread;
CLIENT_ID CapturedCid={0}; BOOLEAN ObjectNamePresent; BOOLEAN ClientIdPresent; ACCESS_STATE AccessState; AUX_ACCESS_DATA AuxData; ULONG Attributes; KIRQL CurrentIrql; PAGED_CODE();
CurrentIrql = KeGetCurrentIrql(); if(CurrentIrql>PASSIVE_LEVEL) {
KeLowerIrql(PASSIVE_LEVEL); }
//DbgPrint(\ DbgPrint(\已经调用过我们的HxNtOpenProcess ()函数 \\n\ PreviousMode = KeGetPreviousMode(); if (PreviousMode != KernelMode) { try {
ProbeForWriteHandle (ProcessHandle);
ProbeForReadSmallStructure (ObjectAttributes,
sizeof(OBJECT_ATTRIBUTES), sizeof(ULONG)); ObjectNamePresent = (BOOLEAN)ARGUMENT_PRESENT (ObjectAttributes->ObjectName); //Attributes = ObSanitizeHandleAttributes (ObjectAttributes->Attributes, UserMode);
Attributes=ObjectAttributes->Attributes; if (ARGUMENT_PRESENT (ClientId)) {
ProbeForReadSmallStructure (ClientId, sizeof (CLIENT_ID), sizeof (ULONG));
CapturedCid = *ClientId; ClientIdPresent = TRUE; } else {
ClientIdPresent = FALSE; }
28
} except (EXCEPTION_EXECUTE_HANDLER) { return GetExceptionCode(); } } else {
ObjectNamePresent = (BOOLEAN)ARGUMENT_PRESENT (ObjectAttributes->ObjectName); //Attributes = ObSanitizeHandleAttributes (ObjectAttributes->Attributes, KernelMode); Attributes=ObjectAttributes->Attributes; if (ARGUMENT_PRESENT (ClientId)) { CapturedCid = *ClientId; ClientIdPresent = TRUE; } else {
ClientIdPresent = FALSE; } }
if (ObjectNamePresent && ClientIdPresent) { return STATUS_INVALID_PARAMETER_MIX; }
Status = SeCreateAccessState( &AccessState, &AuxData,
DesiredAccess,
&(*PsProcessType)->TypeInfo.GenericMapping );
if ( !NT_SUCCESS(Status) ) { return Status; }
if (SeSinglePrivilegeCheck( SeDebugPrivilege, PreviousMode )) { if ( AccessState.RemainingDesiredAccess & MAXIMUM_ALLOWED ) { AccessState.PreviouslyGrantedAccess |= PROCESS_ALL_ACCESS; } else {
AccessState.PreviouslyGrantedAccess |= ( AccessState.RemainingDesiredAccess ); }
AccessState.RemainingDesiredAccess = 0; }
if (ObjectNamePresent) {
Status = ObOpenObjectByName( ObjectAttributes, *PsProcessType, PreviousMode, &AccessState, 0,
29