[分享]新手学内核第二篇 Shadow SSDT
今天再来一篇,再次记录自己学习过程,同时也感慨一下自学的辛苦。
以下内容都是自己自学所理解,如有不对,还望指点。
接上篇:http://bbs.pediy.com/showthread.php?p=1283055#post1283055
上篇主要讲SSDT 的相关内容,这篇说一下Shadow SSDT 相关内容。
对于像我一样,并且想自学的新手来说,Shadow SSDT 学起来要比 SSDT 稍微难一些,有很多东西需要注意,此篇希望对我们新手有所帮助。
Shadow SSDT 是由win32k.sys 未导出的一些函数,所以在微软公开的wrk中查不到对应的代码,学习起来也相对的复杂一些。既然是未导出,那么想要得到KeServiceDescriptorTableShadow 地址,就要麻烦一些。
KeServiceDescriptorTableShadow 跟 KeServiceDescriptorTable 拥有相同的结构。
虽然结构相同,但是 KeServiceDescriptorTableShadow 中 的第一张表存放的依然是SSDT 的函数首地址和个数,想要获取Shadow SSDT 的函数首地址和个数就要 将KeServiceDescriptorTableShadow + 结构体大小,或者 KeServiceDescriptorTableShadow [1].ServiceTableBase和KeServiceDescriptorTableShadow [1].NumberOfServices
一般,想获取KeServiceDescriptorTableShadow 的话,看到前辈们通常是硬编码或者使用特征码定位的方式。
本人也是使用特征码定位,根据SSDT导出的KeAddSystemServiceTable函数,来定位KeServiceDescriptorTableShadow。
具体看码:
第一:枚举Shadow SSDT 表
/***************************************枚举Shadow 表 START****************************************/
PKeServiceDescriptorTable KeServiceDescriptorTableShadow;
NTSTATUS PsLookupProcessByName(char * processName,OUT PEPROCESS &_processRet);
//通过导出函数KeAddSystemServiceTable 来搜索特征码来定位
extern "C" NTKERNELAPI
VOID
KeAttachProcess (
__inout PRKPROCESS Process
);
extern "C" NTKERNELAPI
VOID
KeDetachProcess (
VOID
);
extern "C" NTKERNELAPI
BOOLEAN
KeAddSystemServiceTable(
IN PULONG_PTR Base,
IN PULONG Count OPTIONAL,
IN ULONG Limit,
IN PUCHAR Number,
IN ULONG Index
);
ULONG GetKeServiceDescriptorTableShadow()
{
ULONG ServiceTableShadow=NULL;
char *p=(char*)KeAddSystemServiceTable;
for (int i=0;i<0x1024;i++,p++)//转换成char * 的原因是字符串指针每次加1 都是一字节,其他的像int float 每次加1都加4字节
{
__try
{
ServiceTableShadow=*(ULONG *)p;
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
return NULL;
}
if(MmIsAddressValid((PVOID)ServiceTableShadow))
{
if(memcmp((PVOID)ServiceTableShadow,KeServiceDescriptorTable,16)==0)
{
if(ServiceTableShadow!=(ULONG)KeServiceDescriptorTable)
{
return ServiceTableShadow;
break;
}
}
}
}
return ServiceTableShadow;
}
NTSTATUS ShadowSSDTEnum()
{
KeServiceDescriptorTableShadow=(PKeServiceDescriptorTable)GetKeServiceDescriptorTableShadow();
if(KeServiceDescriptorTableShadow!=NULL)
{
DbgPrint("KeServiceDescriptorTableShadow=%x \r\n",KeServiceDescriptorTableShadow);
DbgPrint("Count=%x \r\n",KeServiceDescriptorTableShadow[1].NumberOfServices);
DbgPrint("ServiceTableBase=%x \r\n",KeServiceDescriptorTableShadow[1].ServiceTableBase);
PEPROCESS keAttactProcess=NULL;
PsLookupProcessByName("explorer.exe",keAttactProcess);
KeAttachProcess((PRKPROCESS)keAttactProcess);
for (int i=0;i<KeServiceDescriptorTableShadow[1].NumberOfServices;i++)
{
DbgPrint("索引:%d,地址:%x \r\n",i,KeServiceDescriptorTableShadow[1].ServiceTableBase[i]);
}
KeDetachProcess();
}else
{
DbgPrint("定位失败!\r\n");
}
return STATUS_SUCCESS;
}
NTSTATUS PsLookupProcessByName(char * processName,OUT PEPROCESS &_processRet)
{
NTSTATUS statusret=0;
PEPROCESS _eprocess=PsGetCurrentProcess();
LIST_ENTRY * CurrentLink;
LIST_ENTRY * BLink;
CurrentLink=(LIST_ENTRY *)((ULONG)_eprocess+0x88);
BLink=CurrentLink->Blink;
do
{
_eprocess=(PEPROCESS)((ULONG)BLink-0x88);
char *currentName=(char *)PsGetProcessImageFileName(_eprocess);
DbgPrint("进程:%s",currentName);
if(strcmp(currentName,processName)==0)
{
_processRet=_eprocess;
return STATUS_SUCCESS;
break;
}
CurrentLink=(LIST_ENTRY *)((ULONG)_eprocess+0x88);
BLink=CurrentLink->Blink;
} while (BLink);
return statusret;
}
/***************************************枚举Shadow 表 END****************************************/
此处有几个需要说明的。
1.KeServiceDescriptorTableShadow想要使用的话,必须在具有GUI 线程上下文的环境中。具体做法是,查找一个GUI线程上下文环境的进程,附加(KeAttachProcess)
然后,调用KeServiceDescriptorTableShadow,最后退出附加(KeDetachProcess)
2.由于本人很菜,在写PsLookupProcessByName根据进程名获取进程PEPROCESS 时花了不少时间。故在此多写几句,留着以后看。
PEPROCESS 结构体我就不贴了,在windbg下轻松看到。
PEPROCESS +0x88 出的 LIST_ENTRY 活动链。可以获取下一个进程的LIST_ENTRY
再根据LIST_ENTRY - 0x88 来定位下一个进程的 PEPROCESS ,根据进程名来判断是否是自己想要的。PsGetProcessImageFileName(_eprocess);
此函数往上的很多,感觉都不是很明白,干脆在网上copy下来自己改了一下。然后觉得清晰很多。
第二:Shadow SSDT HOOK NtUserPostThreadMessage
这个直接上码,写的时候,也是很痛苦。
/*************************************Shadow SSDT HOOK NtUserPostThreadMessage Start****************************/
ULONG OldNtUserPostThreadMessageAddr;
PEPROCESS CurrentkeAttactProcess=NULL;
//函数原型
typedef BOOL
(*_NtUserPostThreadMessage)(DWORD
idThread,UINT
Msg,WPARAM
wParam,LPARAM lParam);
extern "C" KIRQL
FORCEINLINE
KeGetCurrentIrql (
VOID
);
extern "C" NTKERNELAPI
KPROCESSOR_MODE
ExGetPreviousMode(
VOID
);
extern "C" PEPROCESS
IoThreadToProcess(
IN PETHREAD Thread
);
extern "C" NTKERNELAPI
NTSTATUS
PsLookupThreadByThreadId(
__in HANDLE ThreadId,
__deref_out PETHREAD *Thread
);
_NtUserPostThreadMessage NewUserPostThreadMessage;
/*
* 自定义线程消息函数
*/
NTSTATUS
MyNtUserPostThreadMessage(DWORD
idThread,UINT
Msg,WPARAM
wParam,LPARAM lParam)
{
PEPROCESS _eprocess;
PETHREAD _thread;
//1.判断自旋锁等级 MyNtUserPostThreadMessage 只能在PASSIVE_LEVEL级别上执行
if(KeGetCurrentIrql()==PASSIVE_LEVEL)
{
//2.判读操作者是否是本身,不是本身操作的就要验证一下
if(PsGetCurrentProcess()!=CurrentkeAttactProcess)
{
//3.判断调用模式,是用户模式还是内核模式
if(ExGetPreviousMode()!=KernelMode)//用户模式下需要校验是否可读
{
__try
{
ProbeForRead((PVOID)idThread,sizeof(HANDLE),sizeof(DWORD));
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
goto _END;
}
}
//通过线程ID 获取PETHREAD
if(PsLookupThreadByThreadId((HANDLE)idThread,&_thread)==STATUS_SUCCESS)
{
//通过PETHREAD 获取PEPROCESS 获取的是被操作者的PEPROCESS
_eprocess=IoThreadToProcess(_thread);
if(_eprocess!=NULL)
{
ObDereferenceObject(_thread);
if(_eprocess==CurrentkeAttactProcess)
{
return FALSE;
}
}else
{
ObDereferenceObject(_thread);
goto _END;
}
}
}
}
_END:
return NewUserPostThreadMessage(idThread,Msg,wParam,lParam);
}
NTSTATUS ShadowSSDTHook()
{
//1.先获取KeServiceDescriptorTableShadow 地址
KeServiceDescriptorTableShadow=(PKeServiceDescriptorTable)GetKeServiceDescriptorTableShadow();
if(KeServiceDescriptorTableShadow!=NULL)
{
DbgPrint("KeServiceDescriptorTableShadow=%x \r\n",KeServiceDescriptorTableShadow);
DbgPrint("Count=%x \r\n",KeServiceDescriptorTableShadow[1].NumberOfServices);
DbgPrint("ServiceTableBase=%x \r\n",KeServiceDescriptorTableShadow[1].ServiceTableBase);
PsLookupProcessByName("explorer.exe",CurrentkeAttactProcess);//explorer可以换成自己要保护的进程
if(CurrentkeAttactProcess)
{
KeAttachProcess((PRKPROCESS)CurrentkeAttactProcess);
//2.获取要hook 函数的首地址
OldNtUserPostThreadMessageAddr=KeServiceDescriptorTableShadow[1].ServiceTableBase[476];
NewUserPostThreadMessage=(_NtUserPostThreadMessage)OldNtUserPostThreadMessageAddr;
PageClose();
KeServiceDescriptorTableShadow[1].ServiceTableBase[476]=(ULONG)MyNtUserPostThreadMessage;
PageOpen();
KeDetachProcess();
}else
{
return 0;
}
}else
{
DbgPrint("KeServiceDescriptorTableShadow定位失败!\r\n");
return 0;
}
return STATUS_SUCCESS;
}
NTSTATUS ShadowSSDTUnHook()
{
if(KeServiceDescriptorTableShadow!=NULL)
{
PsLookupProcessByName("explorer.exe",CurrentkeAttactProcess);//explorer可以换成自己要保护的进程
if(CurrentkeAttactProcess)
{
KeAttachProcess((PRKPROCESS)CurrentkeAttactProcess);
PageClose();
KeServiceDescriptorTableShadow[1].ServiceTableBase[476]=OldNtUserPostThreadMessageAddr;
PageOpen();
KeDetachProcess();
}else
{
return 0;
}
}else
{
DbgPrint("KeServiceDescriptorTableShadow定位失败!\r\n");
return 0;
}
return STATUS_SUCCESS;
}
/*************************************Shadow SSDT HOOK NtUserPostThreadMessage End******************************/
注意:在hook 时,也是在有GUI线程环境上下文才能HOOK成功,如不然,蓝屏。
学习时碰到的问题:
1.不知道IRQL ,没注意判断,蓝屏十几次,终于无法忍受,查阅资料并改之。
if(KeGetCurrentIrql()==PASSIVE_LEVEL){}
2.不知道如何过滤消息,查百度如何过滤,大概的做法,判断当前操作者和被操作者。
像一些KeGetCurrentIrql、ExGetPreviousMode、IoThreadToProcess、PsLookupThreadByThreadId都可以在WRK 中获得。需要自己声明一下。
