系统环境:WIN732位
重载内容:两张系统服务调度表 ,传说的SSDT和ShadowSSDT
作用:通过重载内核,可以饶过各大著名驱动保护的HOOK.极少数例外.
重载方式:挂勾系统三环和零环的主要通道KiFastCallEntry,然后改变自己的进程,通过新内核.
实现环境:NT式驱动环境
核心步骤;一,以PE文件在内存中的对齐方式将内核文件和WIN32K.sys读取到内存中.
步骤二;以原始内核文件为依据,修复基址表重定位表.
步骤三;重新构建自己的SSDT表
步骤四;定位KiFastCallEntry 里最佳HOOK点,
步骤五;实现自己的过滤函数.
步骤六;重载WIN32K.sys的时候,必须以GUI线程的方式运行,如此最好在用户层实现一个与驱动通信的程序.
原理是,驱动程序中的派遣函数其实全都是运行在应用程序的上下文环境中.用户层调用驱动的派遣例程就能成功访问WIN32K.sys里的内容
//核心代码开始/
//变量区头文件内容
typedef struct ServiceDescriptorEntry {
unsigned int *ServiceTableBase;
unsigned int *ServiceCounterTableBase; //仅适用于checked build版本
unsigned int NumberOfServices;
unsigned char *ParamTableBase;
} ServiceDescriptorTableEntry_t, *PServiceDescriptorTableEntry_t;
//全局变量区
__declspec(dllimport) ServiceDescriptorTableEntry_t KeServiceDescriptorTable;
PServiceDescriptorTableEntry_t KeServiceDescriptorTableShadow=NULL ;
PServiceDescriptorTableEntry_t PNewSSDT; //新SSDT表变量
PServiceDescriptorTableEntry_t PNewShadowSSDT; //新ShadowSSDT表变量
ULONG kerAddress; //枚举驱动后保存原始内核模块用的变量
ULONG win32kAddress;//枚举驱动后保存原始WIN32K.sys内核模块用的变量
PVOID NewKernalVirualAddress = NULL;//内核模块重载的新地址。
PVOID NewWin32kAddress = NULL;//shadowSSDT模块重载的新地址。
UCHAR *Faddress;//KiFastCall hook点
UCHAR backHookCode[5] = { 0 }; //KiFastCallEntry Hook 还原代码
ULONG HookbackAddress;//KiFastCallEntry Hook 返回跳转地址
ULONG ShadowBitAddr = 0;
char protect1[128] = { 0 };//要饶过旧内核的进程1,此变量用来接受用户层传进来的进程名,此进程将走新内核
char protect2[128] = { 0 };//要饶过旧内核的进程2,此变量用来接受用户层传进来的进程名,此进程将走新内核
char protect3[128] = { 0 };//要饶过旧内核的进程3,此变量用来接受用户层传进来的进程名,此进程将走新内核
void PageProtectOn()//恢复内存保护
{
__asm{
mov eax, cr0
or eax, 10000h
mov cr0, eax
}
}
void PageProtectOff()//去掉内存保护
{
__asm{
mov eax, cr0
and eax, not 10000h
mov cr0, eax
}
}
///重载内核主要头文件内容///
#ifndef NTh
#define NTh
#include <ntddk.h>
#include <EveryHead.h>
#endif
#include <ntimage.h>
/*此头文件实现重载内核功能,主要功能模块有如下;将磁盘中的内核文件按PE对齐值装载到内存,
修复需要重定位的数据,构建新SSDT表,枚举内核驱动模块,HOOK KiFastCallEntry,构造自己在KiFastCallEntry中的过滤函数
*/
///枚举内核驱动模块Start
typedef struct _LDR_DATA_TABLE_ENTRY {
LIST_ENTRY InLoadOrderLinks;
LIST_ENTRY InMemoryOrderLinks;
LIST_ENTRY InInitializationOrderLinks;
PVOID DllBase;
PVOID EntryPoint;
ULONG SizeOfImage;
UNICODE_STRING FullDllName;
UNICODE_STRING BaseDllName;
ULONG Flags;
USHORT LoadCount;
USHORT TlsIndex;
union {
LIST_ENTRY HashLinks;
struct {
PVOID SectionPointer;
ULONG CheckSum;
};
};
union {
struct {
ULONG TimeDateStamp;
};
struct {
PVOID LoadedImports;
};
};
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;
/*函数原形;ULONG EnumDriver(PDRIVER_OBJECT pDriverObject)
功能:枚举内核驱动,并返回指定名称的驱动模块地址
参数1:当前驱动对象的指针
返回是内核驱动模块L"ntoskrnl.exe"的地址
*/
ULONG EnumDriver(PDRIVER_OBJECT pDriverObject,PWCHAR oriAddress)
{
UNICODE_STRING findName;
RtlInitUnicodeString(&findName, oriAddress);
PLDR_DATA_TABLE_ENTRY DTableEntry, TempTable;
DTableEntry = pDriverObject->DriverSection;
PLIST_ENTRY plist = DTableEntry->InLoadOrderLinks.Flink;
while (plist != DTableEntry)
{
TempTable = (PLDR_DATA_TABLE_ENTRY)plist;
KdPrint(("%wZ--%x", &TempTable->BaseDllName, TempTable->DllBase));
if (RtlCompareUnicodeString(&TempTable->BaseDllName, &findName, FALSE) == 0)
{
KdPrint(("find DLLbase is %x", TempTable->DllBase));
return TempTable->DllBase;
}
plist = plist->Flink;
}
return 0;
}
///Read File To Memory Start
/*ReadFileToMemory(wchar_t *FileName, PVOID *OutAddress)
第一个参数是要打开的文件名,包含全路径,
第二个参数是磁盘文件读取到内存后的地址,是一个传出的参数。
*/
#define __Max(a,b) a>b?a:b
NTSTATUS ReadFileToMemory(WCHAR *FileName, PVOID *OutAddress)
{
NTSTATUS status;
if (!MmIsAddressValid(FileName))
{
KdPrint(("file name valid"));
return STATUS_UNSUCCESSFUL;
}
//第一步打开要读到内存的文件,获得文件对象的句柄
//ZwCreateFile 整个调用如下步骤
HANDLE hFile;
OBJECT_ATTRIBUTES ObjAttrutes;
IO_STATUS_BLOCK ioStatusBlock;
UNICODE_STRING unicodeFileName;
//初始化ZwCreateFile的参数
RtlInitUnicodeString(&unicodeFileName, FileName);
InitializeObjectAttributes(&ObjAttrutes, &unicodeFileName, OBJ_CASE_INSENSITIVE, NULL, NULL);
status = ZwCreateFile(&hFile,FILE_ALL_ACCESS,&ObjAttrutes,&ioStatusBlock,NULL,
FILE_ATTRIBUTE_NORMAL,FILE_SHARE_READ|FILE_SHARE_WRITE,FILE_OPEN,
FILE_NON_DIRECTORY_FILE,
NULL,0);
//ZwCreateFile调用结束,以上步骤是固定模式,以后可以直接复制这段。
if (!NT_SUCCESS(status))
{
KdPrint(("ZwCreateFile failed error code is %x",status));
return status;
}
//第二步,获得文件句柄之后,开始读取PE文件的DOS头,NT头和区块表到内存
IMAGE_DOS_HEADER ImageDosHeader;
LARGE_INTEGER FileOffset;
//读取DOS头
FileOffset.QuadPart = 0;
status = ZwReadFile(hFile, NULL, NULL, NULL, &ioStatusBlock,&ImageDosHeader,
sizeof(IMAGE_DOS_HEADER),&FileOffset,NULL);
if (!NT_SUCCESS(status))
{
KdPrint(("read image dos header failed code is%x\n", status));
ZwClose(hFile);
return status;
}
//读取NT头
IMAGE_NT_HEADERS ImageNTHeader;
FileOffset.QuadPart = ImageDosHeader.e_lfanew;
status = ZwReadFile(hFile, NULL, NULL, NULL, &ioStatusBlock, &ImageNTHeader, sizeof(IMAGE_NT_HEADERS),
&FileOffset, NULL);
if (!NT_SUCCESS(status))
{
KdPrint(("read nt header failed error code is %x\n", status));
ZwClose(hFile);
return status;
}
//读取区块表
FileOffset.QuadPart = ImageDosHeader.e_lfanew + sizeof(IMAGE_NT_HEADERS); //设置文件指针
IMAGE_SECTION_HEADER *pImageSectionHeader;
//申请一块和区块表大小相同的内存,然后将区块表复制到这块内存中
pImageSectionHeader = ExAllocatePool(NonPagedPool,
sizeof(IMAGE_SECTION_HEADER)*ImageNTHeader.FileHeader.NumberOfSections);
if (pImageSectionHeader==NULL)
{
KdPrint(("section exallocate pool failed"));
ZwClose(hFile);
return STATUS_UNSUCCESSFUL;
}
status = ZwReadFile(hFile, NULL, NULL, NULL, &ioStatusBlock, pImageSectionHeader,
sizeof(IMAGE_SECTION_HEADER)*ImageNTHeader.FileHeader.NumberOfSections, &FileOffset,NULL);
if (!NT_SUCCESS(status))
{
KdPrint(("read sections failed,error coed %x\n",status));
ZwClose(hFile);
ExFreePool(pImageSectionHeader);
return STATUS_UNSUCCESSFUL;
}
//第三步,重新申请一块内存,将之前的DOS头,NT头,区块表和真正的区块组织成一个连续整体。
//DOS头,NT头和区块表在磁盘文件中的对齐和内存的对齐是没有区别的,直接复制。
PVOID FullVirtualAddress = ExAllocatePool(NonPagedPool, ImageNTHeader.OptionalHeader.SizeOfImage);
if (FullVirtualAddress==0)
{
KdPrint(("allocate memory failed"));
ZwClose(hFile);
ExFreePool(pImageSectionHeader);
return STATUS_UNSUCCESSFUL;
}
memset(FullVirtualAddress, 0, ImageNTHeader.OptionalHeader.SizeOfImage);//清零新申请的内存
//申请到内存开始复制
RtlCopyMemory(FullVirtualAddress,&ImageDosHeader,sizeof(IMAGE_DOS_HEADER));//复制DOS头
RtlCopyMemory((PVOID)((ULONG)FullVirtualAddress+ImageDosHeader.e_lfanew),&ImageNTHeader,sizeof(IMAGE_NT_HEADERS));//复制NT头
//复制区块表
RtlCopyMemory((PVOID)((ULONG)FullVirtualAddress + ImageDosHeader.e_lfanew+sizeof(IMAGE_NT_HEADERS)),
pImageSectionHeader,sizeof(IMAGE_SECTION_HEADER)*ImageNTHeader.FileHeader.NumberOfSections);
//用FOR循环复制真正的区块表,真正的区块表一定要按内存对齐值读取的内存。
ULONG SecVirualAddress;
ULONG dataRaw;
for (ULONG i = 0; i < ImageNTHeader.FileHeader.NumberOfSections; i++)
{
SecVirualAddress = pImageSectionHeader .VirtualAddress+(ULONG)FullVirtualAddress;
dataRaw = max(pImageSectionHeader .SizeOfRawData, pImageSectionHeader .Misc.VirtualSize);
FileOffset.QuadPart = pImageSectionHeader .PointerToRawData;
status = ZwReadFile(hFile, NULL, NULL, NULL, &ioStatusBlock, (PVOID)SecVirualAddress,dataRaw,&FileOffset,NULL);
if (!NT_SUCCESS(status))
{
KdPrint(("read file failed in section[%d]\n", i));
ExFreePool(FullVirtualAddress);
ExFreePool(pImageSectionHeader);
ZwClose(hFile);
return status;
}
}
KdPrint(("read file to memory success address is%x\n", FullVirtualAddress));
*OutAddress = FullVirtualAddress;
}
/修复基址重定位表中的数据和构建新SSDT表开始///
/*函数原型 VOID RelocationModule(PVOID PNewImageAddres,PVOID POriginalAddress)
功能;修复重定位表里的数据
参数一,需要修复重定位数据的模块地址
参数二,原始内核模块在内存中的地址,因为新内核不能完全替代原始内核,许多原始内核数据在初始化时已
决定,新内核必须以原始内核基址为依据进行重定位,所以这里第二个参数传入原始内核地址。
*/
VOID RelocationModuleAndBuildSSDT(PVOID PNewImageAddres,PVOID POriginalAddress)
{
PIMAGE_DOS_HEADER pImageDosHeader;
PIMAGE_NT_HEADERS pImageNTHeader;
IMAGE_DATA_DIRECTORY imagedatadectory;
PIMAGE_BASE_RELOCATION pimagebaserelocation;
USHORT typevalue;
ULONG RelocationAddress;
pImageDosHeader = PNewImageAddres;
pImageNTHeader = (ULONG)PNewImageAddres + pImageDosHeader->e_lfanew;
ULONG RelocOffset = (ULONG)POriginalAddress - pImageNTHeader->OptionalHeader.ImageBase; //重定位数据偏移量计算
imagedatadectory =pImageNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];
pimagebaserelocation = (ULONG)PNewImageAddres + imagedatadectory.VirtualAddress;
/*重定位块PIMAGE_BASE_RELOCATION的.VirtualAddres成员不等于零时进行循环修复每一页中的重定位数据。
重定位块是一个连续存放的数组,每一个重定位块的大小是不相同的,但却是已知的。一个重定位块只能修复一页内存中的重定位数据项,
重定位块的数量是不确定的,最后以一个内容为零的重定位块作为结束标志,
每一页内存中有多少个数据需要重定位也是不相等的,但是却是可以通过PIMAGE_BASE_RELOCATION的.SizeOfBlock- 8) / 2计算出来,
用(SizeOfBlock-8)/2就可以得到每一页内存中有多少需要修复的数据,每一个重定位项占2字节,所以除2,减8是因为重定位块有两个成员
PIMAGE_BASE_RELOCATION->VirtualAddress和PIMAGE_BASE_RELOCATION->SizeOfBlock各占四字节。
重点内容是;第X+1重定位块的地址就等于第X重定位块的地址加上X.SizeOfBlock,依此类推就能得到所有重定位块的地址,
*/
ULONG ab = 0;//计算有多少个需要修复的重定位块用的变量,每个重定位块有若干需要重定位的项
while (pimagebaserelocation->VirtualAddress)
{
ULONG VirtualAddress = pimagebaserelocation->VirtualAddress+ (ULONG)PNewImageAddres;
ULONG NumberOfReloc = (pimagebaserelocation->SizeOfBlock- 8) / 2;
ab = ab + 1;
for (ULONG index = 0; index < NumberOfReloc;index++)
{
typevalue = pimagebaserelocation->TypeOffset[index];
if (typevalue>>12==IMAGE_REL_BASED_HIGHLOW)
{
RelocationAddress = (typevalue&0xfff) + pimagebaserelocation->VirtualAddress + (ULONG)PNewImageAddres;
if (!MmIsAddressValid(RelocationAddress))
{
continue;
}
*(ULONG*)RelocationAddress += RelocOffset;
}
}
pimagebaserelocation = (ULONG)pimagebaserelocation +pimagebaserelocation->SizeOfBlock;
//(ULONG)pimagebaserelocation +pimagebaserelocation->SizeOfBlock; 这句前面一定要叫个(ULONG)转换
//我一开始没加,就一直出错。
KdPrint(("修复第%d个重定位块\n",ab));
}
KdPrint(("Relocation base end"));
}
///Build New SSDT start构造新SSDT表开始/
VOID buildSSDT(PVOID PNewImageAddres, PVOID POriginalAddress)
{
ULONG RelocOffset;
PNewSSDT= ((ULONG)&KeServiceDescriptorTable - (ULONG)POriginalAddress) + (ULONG)PNewImageAddres;//获得新SSDT结构地址
if (!MmIsAddressValid(PNewSSDT))
{
KdPrint(("new ssdt address is invalid"));
return;
}
RelocOffset = (ULONG)PNewImageAddres - (ULONG)POriginalAddress;//定位偏移
PNewSSDT->NumberOfServices = KeServiceDescriptorTable.NumberOfServices;//设置新SSDT表个数
PNewSSDT->ServiceTableBase = ((ULONG)KeServiceDescriptorTable.ServiceTableBase - (ULONG)POriginalAddress) + (ULONG)PNewImageAddres;
PNewSSDT->ParamTableBase = ((ULONG)KeServiceDescriptorTable.ParamTableBase - (ULONG)POriginalAddress) + (ULONG)PNewImageAddres;
for (ULONG index = 0; index < KeServiceDescriptorTable.NumberOfServices; index++)//构建新SSDT表的TableBase
{
PNewSSDT->ServiceTableBase[index] += RelocOffset;
}
RtlCopyMemory(PNewSSDT->ParamTableBase, KeServiceDescriptorTable.ParamTableBase, KeServiceDescriptorTable.NumberOfServices*sizeof(char));
KdPrint(("new ssdt build success address is%x\n", PNewSSDT));
}
///Build New ShadowSSDT start构造新ShadowSSDT表开始/
/*函数原型:buildShadowSSDT(PVOID PNewImageAddres, PVOID POriginalAddress)
参数一;新WIN32K.SYS的基地址。
参数二;原始ntkrnlpa.exe文件的基地址,切记,这里不能传入原始WIN32K.sys地址,因为KeServiceDescriptorTableShadow表依然在ntkrnlpa.exe内核文件中
参数三;原始WIN32K.sys模块基地址*/
VOID buildShadowSSDT(PVOID PNewImageAddres, PVOID POriginalAddress,PVOID origWin32kAddress)
{
ULONG RelocOffset;
KeServiceDescriptorTableShadow =(ULONG)&KeServiceDescriptorTable+0x50;//WIN732位系统中KeServiceDescriptorTableShadow可以这样定位。
PNewShadowSSDT = ((ULONG)KeServiceDescriptorTableShadow - (ULONG)POriginalAddress) + (ULONG)PNewImageAddres;//获得新SSDT结构地址
if (!MmIsAddressValid(PNewSSDT))
{
KdPrint(("new ssdt address is invalid"));
return;
}
RelocOffset = (ULONG)PNewImageAddres - (ULONG)origWin32kAddress;//定位偏移
PNewShadowSSDT->NumberOfServices = KeServiceDescriptorTableShadow->NumberOfServices;//设置新SSDT表个数
/*注意;KeServiceDescriptorTableShadow结构变量在(ULONG)&KeServiceDescriptorTable+0x50的地方,但是KeServiceDescriptorTableShadow->ServiceTableBase 却是在
WIN32K.sys模块中。
*/
PNewShadowSSDT->ServiceTableBase = ((ULONG)KeServiceDescriptorTableShadow->ServiceTableBase - (ULONG)origWin32kAddress) + (ULONG)PNewImageAddres;
PNewShadowSSDT->ParamTableBase = ((ULONG)KeServiceDescriptorTableShadow->ParamTableBase - (ULONG)origWin32kAddress) + (ULONG)PNewImageAddres;
for (ULONG index = 0; index < KeServiceDescriptorTableShadow->NumberOfServices; index++)//构建新SSDT表的TableBase
{
PNewShadowSSDT->ServiceTableBase[index] += RelocOffset;
}
RtlCopyMemory(PNewShadowSSDT->ParamTableBase, KeServiceDescriptorTableShadow->ParamTableBase, KeServiceDescriptorTableShadow->NumberOfServices*sizeof(char));
KdPrint(("new shadow ssdt build success address is%x\n", PNewShadowSSDT));
}
/*开始构造自己在KiFastCallEntry中的过滤函数,这种过滤函数需要两个,第一个是裸函数,实现内容必须是汇编,第二个是功能函数,是将被第一个调用的
为什么这种过滤函数要用两个呢?因为第一个调用的是裸函数,裸函数的好处是不会破坏堆栈,不会产生额外的代码,但是它的坏处是只能用汇编实现内部代码,
不合适用高级语言接受参数或返回值等等,如果我们用汇编在这种裸函数内部实现过滤功能就太麻烦了,因此在第二个过滤函数中用高级语言实现我们的功能。
*/
VOID filterKiFastCall2(ULONG tablebase, ULONG indexs, ULONG OrigFuncAddress)
{
if (tablebase == KeServiceDescriptorTable.ServiceTableBase)
{
//DbgBreakPoint();
//KdPrint(("entry be hook fastcall\n"));
if (strstr((char *)PsGetCurrentProcess() + 0x16c, protect1) != 0 || \
strstr((char*)PsGetCurrentProcess() + 0x16c, protect2) != 0 || \
strstr((char*)PsGetCurrentProcess() + 0x16c, protect3) != 0)
{
//KdPrint(("ssdt find be protect process is %s\n",(char*)PsGetCurrentProcess()+0x16c));
return PNewSSDT->ServiceTableBase[indexs];
}
}
if (tablebase == KeServiceDescriptorTableShadow->ServiceTableBase) //过滤shadowssdt
{
//KdPrint(("entry be hook fastcall\n"));
if (strstr((char *)PsGetCurrentProcess() + 0x16c, protect1) != 0 || \
strstr((char*)PsGetCurrentProcess() + 0x16c, protect2) != 0 || \
strstr((char*)PsGetCurrentProcess() + 0x16c, protect3) != 0)
{
//KdPrint(("shadow ssdt find be protect process is %s\n", (char*)PsGetCurrentProcess() + 0x16c));
return PNewShadowSSDT->ServiceTableBase[indexs];
}
}
return OrigFuncAddress;
}
__declspec(naked)
VOID filterKiFastCall1() //第一个过滤函数简单调用第二个,用汇编传入参数。
{
__asm{
pushad //切记,一定要pushad保存寄存器环境
pushfd
push edx
push eax //系统调用的下标
push edi //SSDT数组地址
call filterKiFastCall2 //调用此函数会自动平衡堆栈,不需要有平衡堆栈的动作
mov [esp+0x18],eax
popfd
popad
sub esp, ecx //恢复被HOOK指令改变的五个字节
shr ecx, 2
jmp HookbackAddress //跳转到HOOK点加五节字的地方
}
}
/Hook KiFastCallEntry Start/
VOID HookKiFastCallEntry()
{
/*首先获得KiFastCallEntry函数的地址,梦无极的教程是用栈回溯的方式获得此地址的,这里将用另一种更简单的方式
CPU为了支持快速系统调用,另外增加了三个寄存器,其中MSR为0x176的寄存器就专门存放了KiFastCallEntry的地址,只要读取这个寄存器就可以
获得KiFastCallEntry的地址了。切记是0x176,十六进制的MSR地址。
*/
KdPrint(("start hook protect1=%s,protect2=%s,protect3=%s\n", protect1, protect2, protect3));
ULONG KiFastCallAddress=0;
__asm{
mov ecx,0x176
rdmsr
mov KiFastCallAddress,eax //这三句汇编代码已获得KiFastCallEntry的地址。
}
//开始特征码定位KiFastCallEntry内部最佳HOOK点
/*
83e5419e 8a0c10 mov cl,byte ptr [eax+edx]
83e541a1 8b1487 mov edx,dword ptr [edi+eax*4]
83e541a4 2be1 sub esp,ecx
83e541a6 c1e902 shr ecx,2
83e541a9 8bfc mov edi,esp
*/
if (KiFastCallAddress==0)
{
KdPrint(("get kifastcall failed"));
return;
}
Faddress=(UCHAR*)KiFastCallAddress;
for (ULONG index = 0; index < 0x300;index++)
{
if (*Faddress == 0x2B &&*(Faddress + 1) == 0xE1 &&*(Faddress + 2) == 0xC1 &&
*(Faddress + 3) == 0xE9 &&*(Faddress + 4) == 0x02)
{
KdPrint(("find best point address is%x\n", Faddress));
HookbackAddress = Faddress + 5;
break;
}
Faddress++;
}
/*特征码定位到最佳HOOK点之后,开始对它进行HOOK,让它跳转到我们自己的过滤函数中*/
UCHAR hookCode[5] = { 0 };
hookCode[0] = 0xe9;//JMP机器码是0xE9,CALL的机器码是0xe8
*(ULONG*)&hookCode[1] = ((ULONG)filterKiFastCall1 - (ULONG)Faddress) - 5;
PageProtectOff();
RtlCopyMemory(backHookCode, Faddress, 5);
RtlCopyMemory(Faddress, hookCode, 5);
PageProtectOn();
}
VOID UnHookKiFastCall()//取消重载内核,释放新模块内存
{
PageProtectOff();
RtlCopyMemory(Faddress,backHookCode, 5);
PageProtectOn();
ExFreePool(NewKernalVirualAddress);
ExFreePool(NewWin32kAddress);
}
驱动主文件内容,主要是派遣例程写法/
#include <FileOp.h>
#include "SSDTHook.h"
#include <ReLoadKernal.h>
#define stringBuffe CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) //缓冲模式交互控制码
#define stringNeither CTL_CODE(FILE_DEVICE_UNKNOWN,0x801,METHOD_NEITHER,FILE_ANY_ACCESS) //其它模式交互控制码
#define stringDirect CTL_CODE(FILE_DEVICE_UNKNOWN,0x802,METHOD_IN_DIRECT,FILE_ANY_ACCESS)//直接模式交互控制码
PDEVICE_OBJECT DeviceObejct;
PWCHAR inputBuffer;
PCHAR protectBuffer;
NTSTATUS UnloadDriver(PDRIVER_OBJECT pDriverObject)
{
NTSTATUS status;
UNICODE_STRING SymbolicName;
RtlInitUnicodeString(&SymbolicName, L"\\??\\AssociateDriver");
status = IoDeleteSymbolicLink(&SymbolicName);
if (pDriverObject->DeviceObject!=NULL)
{
IoDeleteDevice(pDriverObject->DeviceObject);
}
if (!NT_SUCCESS(status))
{
KdPrint(("unload driver failed"));
return status;
}
//UnHookNtOpenProcess();//SSDTHook
//UninlineHook();//inLineSSDTHook
UnHookKiFastCall();
KdPrint(("unload driver success"));
return STATUS_SUCCESS;
}
//创建设备
NTSTATUS CreateDevice(PDRIVER_OBJECT pDriverObject)
{
NTSTATUS status;
UNICODE_STRING DeviceName;
UNICODE_STRING SymbolicName;
RtlInitUnicodeString(&DeviceName, L"\\Device\\AssociateDevice");
status = IoCreateDevice(pDriverObject, 0, &DeviceName, FILE_DEVICE_UNKNOWN, 0, FALSE, &DeviceObejct);
if (!NT_SUCCESS(status))
{
KdPrint(("Create Device failed"));
}
DeviceObejct->Flags |= DO_BUFFERED_IO;
pDriverObject->Flags |= DO_BUFFERED_IO;
RtlInitUnicodeString(&SymbolicName, L"\\??\\AssociateDriver");
status=IoCreateSymbolicLink(&SymbolicName, &DeviceName);
if (!NT_SUCCESS(status))
{
KdPrint(("create symbolic link failed"));
IoDeleteDevice(DeviceObejct);
return status;
}
return STATUS_SUCCESS;
}
NTSTATUS DispatchMajor(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
PWCHAR buffer = L"来自驱动,缓冲模式";
PWCHAR direct = L"来自驱动, 直接模式";
PWCHAR neither = L"来自驱动, 其它模式";
ULONG wr = 0;
//得到当前栈指针
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);
ULONG mf = stack->MajorFunction;//得到当前IRP栈中的派遣函数,不是派遣函数的数组.
KdPrint(("entry device dispatch"));
switch (mf)
{
case IRP_MJ_DEVICE_CONTROL:
KdPrint(("entry device control"));
//得到输入缓冲区大小
ULONG cin = stack->Parameters.DeviceIoControl.InputBufferLength;
//得到输出缓冲区大小
ULONG cout = stack->Parameters.DeviceIoControl.OutputBufferLength;
//得到IOCTL码
ULONG CTLcode = stack->Parameters.DeviceIoControl.IoControlCode;
switch (CTLcode)
{
case stringBuffe://缓冲通信模式.
KdPrint(("buffer mode\n"));
/*获取缓冲区数据,在缓冲交互模式中,系统输入缓冲和系统输出缓冲是同一个,但用户层的缓冲是两个.
输入时,从用户输入缓冲复制数据到系统缓冲,操作完IRP之后再从系统缓冲复制数据到用户层的输出缓冲.*/
protectBuffer = (CHAR*)Irp->AssociatedIrp.SystemBuffer;
KdPrint(("buffer mode view use string is:%s", protectBuffer));
//RtlCopyMemory(protectall, protectBuffer, 384);
char *p = NULL, *s=NULL;
s = strtok_s(protectBuffer, ",",&p); //将用户层传进来的多个进程名字符串进行分割
if (s!=NULL)
{
strcpy(protect1, s); //将分割出来的第一个进程名字复制给内核字符数组
}else { strcpy(protect1, "Cheat"); }
s = NULL;
s = strtok_s(NULL, ",",&p);
if (s!=NULL)
{
strcpy(protect2, s);//将分割出来的第2个进程名字复制给内核字符数组
}else {strcpy(protect2, protect1);}
s = NULL;
s = strtok_s(NULL, ",",&p);
if (s!=NULL)
{
strcpy(protect3, s);//将分割出来的第3个进程名字复制给内核字符数组
}else {strcpy(protect3, protect1);}
KdPrint(("protect1=%s,protect2=%s,protect3=%s\n", protect1, protect2, protect3));
DbgBreakPoint();
hookShadowBitBlt();
ReadFileToMemory(L"\\??\\C:\\Windows\\System32\\ntkrnlpa.exe", &NewKernalVirualAddress);
ReadFileToMemory(L"\\??\\C:\\Windows\\System32\\win32k.sys", &NewWin32kAddress);
KdPrint(("new ssdt address is %x\n origkeraddress is%x\n", NewKernalVirualAddress,kerAddress));
KdPrint(("new shadowssdt address is %x\n origkeraddress is%x\n", NewWin32kAddress, win32kAddress));
RelocationModuleAndBuildSSDT(NewKernalVirualAddress, (PVOID)kerAddress);
RelocationModuleAndBuildSSDT(NewWin32kAddress, (PVOID)win32kAddress);
buildSSDT(NewKernalVirualAddress, kerAddress);
buildShadowSSDT(NewWin32kAddress, kerAddress, win32kAddress);
HookKiFastCallEntry();
/* inputBuffer = L"from driver",原先我用的这句,其实是个严重的错误,inputBuffer是个指针,
此指针的指向的地址是输入输出缓冲,我这样操作的作用是将此指针的指向的地址变成了字符串的地址,而原先的输入输出
缓冲依然未被操作,正确的作法如下*/
wr = 0;
break;
case stringDirect://直接通信模式
KdPrint(("direct mode"));
//直接通信模式获取用户层输入的数据与缓冲通信模式相同
inputBuffer = (PWCHAR)Irp->AssociatedIrp.SystemBuffer;
KdPrint(("direct mode view use string is %S", inputBuffer));
//返回给用户层数据时用的缓冲区就和缓冲通信模式不同了,这种模式是直接叫用户空间输出地址映射进内核.
PWCHAR outBuffer = MmGetSystemAddressForMdlSafe(Irp->MdlAddress, NormalPagePriority);
RtlCopyMemory(outBuffer, direct, wcslen(direct) * 2);
wr = wcslen(direct) * 2;//设置实际操作的字节数
UnHookShadow();//ShadowSSDT的Hook要在这个用户层传递进来的IRP例程中卸载,不能在驱动卸载例程里卸载。
break;
case stringNeither:
__try{
KdPrint(("neither mode"));
//其它通信模式获取用户层输入的数据如下,这种模式是直接取用户空间的缓冲地址,相对不安全.
inputBuffer = stack->Parameters.DeviceIoControl.Type3InputBuffer;
ProbeForRead(inputBuffer, cin, 4);
KdPrint(("neither mode view use string is %S", inputBuffer));
//其它通信模式返回用户数据操作如下
PWCHAR outBuffer = (PWCHAR)Irp->UserBuffer;
ProbeForWrite(outBuffer, cout, 4);//第三个参数是对齐值,第二个参数由上面获得.
memcpy(outBuffer, neither, wcslen(neither) * 2 + 2);
wr = wcslen(neither) * 2 + 2;
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
KdPrint(("exception for neither"));
}
break;
default:
break;
}
case IRP_MJ_CLOSE:
default:
break;
}
Irp->IoStatus.Information = wr;//操作了多少字节数,宽字符用wcslen取字节数乘2
Irp->IoStatus.Status = STATUS_SUCCESS;//返回成功
IoCompleteRequest(Irp, IO_NO_INCREMENT);//指示完成此IRP
KdPrint(("leave dispatch function\n"));
return STATUS_SUCCESS;//返回成功
}
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegistryPath)
{
NTSTATUS status;
KdPrint(("entry driver registry path is %wZ\n",pRegistryPath));
pDriverObject->DriverUnload = UnloadDriver;
status = CreateDevice(pDriverObject);
pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DispatchMajor;
//妈的,一定要把这个IRP_MJ_CREATE派遣函数替换成我们自己的,要不然用户层打不开符号链接
pDriverObject->MajorFunction[IRP_MJ_CREATE] = DispatchMajor;
pDriverObject->MajorFunction[IRP_MJ_CLOSE] = DispatchMajor;
pDriverObject->MajorFunction[IRP_MJ_READ] = DispatchMajor;
pDriverObject->MajorFunction[IRP_MJ_WRITE] = DispatchMajor;
//MyCreateFile();//文件操作
//HookNtOpenProcess();//SSDTHook
//DbgBreakPoint();//设试断点
//inlineHookOpenProcess();NtOpenProcess的inlineHook测试
kerAddress = EnumDriver(pDriverObject, L"ntoskrnl.exe");//遍历驱动模块.
win32kAddress = EnumDriver(pDriverObject,L"win32k.sys");
KeServiceDescriptorTableShadow =(ULONG) &KeServiceDescriptorTable + 0x50;
KdPrint(("KeServiceDescriptorTable is %x\n KeServiceDescriptorTableshadow is %x", &KeServiceDescriptorTable, KeServiceDescriptorTableShadow));
return STATUS_SUCCESS;
}
//最后是用户层通信代码
#include <winioctl.h>//与驱动交互一定要包含此头文件
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
#define stringBuffe CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) //缓冲模式交互控制码
#define stringNeither CTL_CODE(FILE_DEVICE_UNKNOWN,0x801,METHOD_NEITHER,FILE_ANY_ACCESS) //其它模式交互控制码
#define stringDirect CTL_CODE(FILE_DEVICE_UNKNOWN,0x802,METHOD_IN_DIRECT,FILE_ANY_ACCESS)//直接模式交互控制码
HANDLE OpenSymbolic();
//打开符号链接
HANDLE OpenSymbolic()
{
HANDLE hfile= CreateFile(L"\\??\\AssociateDriver", FILE_ALL_ACCESS, 0,
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hfile == INVALID_HANDLE_VALUE)
{
AfxMessageBox(L"open symbolic failed");
TRACE("open symbolic failed error code is %x\n", GetLastError());
}else
{
AfxMessageBox(L"open symbolic success");
}
return hfile;
}
void CAssociateExeDlg::OnBnClickedBAssocibuffer()
{
CString outbuffer;
UpdateData(TRUE);//控件的值刷新到控件变量
ULONG dwWrite;
HANDLE HFile = OpenSymbolic();
memset(outbuffer.GetBuffer(256), 0, 256 * 2);//输出缓冲区清零,否则会出现乱码.
//宽字符转换多字节集代码开始///
char *ansiname = new char[384];//为char字符指针分配一块大小512的空间,定义的是指针就得分配内存.
memset(ansiname, 0, sizeof(char)* 384); //初始化新分配的内存空间,将其全部填充0
WideCharToMultiByte(CP_ACP, 0, View.GetBuffer(), -1, ansiname, 384, NULL, NULL);
//宽字符转换多字节集代码结束,就三行///
DeviceIoControl(HFile, stringBuffe, ansiname, 384, outbuffer.GetBuffer(256), 256 * 2 + 2, &dwWrite, NULL);
}
重载内容:两张系统服务调度表 ,传说的SSDT和ShadowSSDT
作用:通过重载内核,可以饶过各大著名驱动保护的HOOK.极少数例外.
重载方式:挂勾系统三环和零环的主要通道KiFastCallEntry,然后改变自己的进程,通过新内核.
实现环境:NT式驱动环境
核心步骤;一,以PE文件在内存中的对齐方式将内核文件和WIN32K.sys读取到内存中.
步骤二;以原始内核文件为依据,修复基址表重定位表.
步骤三;重新构建自己的SSDT表
步骤四;定位KiFastCallEntry 里最佳HOOK点,
步骤五;实现自己的过滤函数.
步骤六;重载WIN32K.sys的时候,必须以GUI线程的方式运行,如此最好在用户层实现一个与驱动通信的程序.
原理是,驱动程序中的派遣函数其实全都是运行在应用程序的上下文环境中.用户层调用驱动的派遣例程就能成功访问WIN32K.sys里的内容
//核心代码开始/
//变量区头文件内容
typedef struct ServiceDescriptorEntry {
unsigned int *ServiceTableBase;
unsigned int *ServiceCounterTableBase; //仅适用于checked build版本
unsigned int NumberOfServices;
unsigned char *ParamTableBase;
} ServiceDescriptorTableEntry_t, *PServiceDescriptorTableEntry_t;
//全局变量区
__declspec(dllimport) ServiceDescriptorTableEntry_t KeServiceDescriptorTable;
PServiceDescriptorTableEntry_t KeServiceDescriptorTableShadow=NULL ;
PServiceDescriptorTableEntry_t PNewSSDT; //新SSDT表变量
PServiceDescriptorTableEntry_t PNewShadowSSDT; //新ShadowSSDT表变量
ULONG kerAddress; //枚举驱动后保存原始内核模块用的变量
ULONG win32kAddress;//枚举驱动后保存原始WIN32K.sys内核模块用的变量
PVOID NewKernalVirualAddress = NULL;//内核模块重载的新地址。
PVOID NewWin32kAddress = NULL;//shadowSSDT模块重载的新地址。
UCHAR *Faddress;//KiFastCall hook点
UCHAR backHookCode[5] = { 0 }; //KiFastCallEntry Hook 还原代码
ULONG HookbackAddress;//KiFastCallEntry Hook 返回跳转地址
ULONG ShadowBitAddr = 0;
char protect1[128] = { 0 };//要饶过旧内核的进程1,此变量用来接受用户层传进来的进程名,此进程将走新内核
char protect2[128] = { 0 };//要饶过旧内核的进程2,此变量用来接受用户层传进来的进程名,此进程将走新内核
char protect3[128] = { 0 };//要饶过旧内核的进程3,此变量用来接受用户层传进来的进程名,此进程将走新内核
void PageProtectOn()//恢复内存保护
{
__asm{
mov eax, cr0
or eax, 10000h
mov cr0, eax
}
}
void PageProtectOff()//去掉内存保护
{
__asm{
mov eax, cr0
and eax, not 10000h
mov cr0, eax
}
}
///重载内核主要头文件内容///
#ifndef NTh
#define NTh
#include <ntddk.h>
#include <EveryHead.h>
#endif
#include <ntimage.h>
/*此头文件实现重载内核功能,主要功能模块有如下;将磁盘中的内核文件按PE对齐值装载到内存,
修复需要重定位的数据,构建新SSDT表,枚举内核驱动模块,HOOK KiFastCallEntry,构造自己在KiFastCallEntry中的过滤函数
*/
///枚举内核驱动模块Start
typedef struct _LDR_DATA_TABLE_ENTRY {
LIST_ENTRY InLoadOrderLinks;
LIST_ENTRY InMemoryOrderLinks;
LIST_ENTRY InInitializationOrderLinks;
PVOID DllBase;
PVOID EntryPoint;
ULONG SizeOfImage;
UNICODE_STRING FullDllName;
UNICODE_STRING BaseDllName;
ULONG Flags;
USHORT LoadCount;
USHORT TlsIndex;
union {
LIST_ENTRY HashLinks;
struct {
PVOID SectionPointer;
ULONG CheckSum;
};
};
union {
struct {
ULONG TimeDateStamp;
};
struct {
PVOID LoadedImports;
};
};
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;
/*函数原形;ULONG EnumDriver(PDRIVER_OBJECT pDriverObject)
功能:枚举内核驱动,并返回指定名称的驱动模块地址
参数1:当前驱动对象的指针
返回是内核驱动模块L"ntoskrnl.exe"的地址
*/
ULONG EnumDriver(PDRIVER_OBJECT pDriverObject,PWCHAR oriAddress)
{
UNICODE_STRING findName;
RtlInitUnicodeString(&findName, oriAddress);
PLDR_DATA_TABLE_ENTRY DTableEntry, TempTable;
DTableEntry = pDriverObject->DriverSection;
PLIST_ENTRY plist = DTableEntry->InLoadOrderLinks.Flink;
while (plist != DTableEntry)
{
TempTable = (PLDR_DATA_TABLE_ENTRY)plist;
KdPrint(("%wZ--%x", &TempTable->BaseDllName, TempTable->DllBase));
if (RtlCompareUnicodeString(&TempTable->BaseDllName, &findName, FALSE) == 0)
{
KdPrint(("find DLLbase is %x", TempTable->DllBase));
return TempTable->DllBase;
}
plist = plist->Flink;
}
return 0;
}
///Read File To Memory Start
/*ReadFileToMemory(wchar_t *FileName, PVOID *OutAddress)
第一个参数是要打开的文件名,包含全路径,
第二个参数是磁盘文件读取到内存后的地址,是一个传出的参数。
*/
#define __Max(a,b) a>b?a:b
NTSTATUS ReadFileToMemory(WCHAR *FileName, PVOID *OutAddress)
{
NTSTATUS status;
if (!MmIsAddressValid(FileName))
{
KdPrint(("file name valid"));
return STATUS_UNSUCCESSFUL;
}
//第一步打开要读到内存的文件,获得文件对象的句柄
//ZwCreateFile 整个调用如下步骤
HANDLE hFile;
OBJECT_ATTRIBUTES ObjAttrutes;
IO_STATUS_BLOCK ioStatusBlock;
UNICODE_STRING unicodeFileName;
//初始化ZwCreateFile的参数
RtlInitUnicodeString(&unicodeFileName, FileName);
InitializeObjectAttributes(&ObjAttrutes, &unicodeFileName, OBJ_CASE_INSENSITIVE, NULL, NULL);
status = ZwCreateFile(&hFile,FILE_ALL_ACCESS,&ObjAttrutes,&ioStatusBlock,NULL,
FILE_ATTRIBUTE_NORMAL,FILE_SHARE_READ|FILE_SHARE_WRITE,FILE_OPEN,
FILE_NON_DIRECTORY_FILE,
NULL,0);
//ZwCreateFile调用结束,以上步骤是固定模式,以后可以直接复制这段。
if (!NT_SUCCESS(status))
{
KdPrint(("ZwCreateFile failed error code is %x",status));
return status;
}
//第二步,获得文件句柄之后,开始读取PE文件的DOS头,NT头和区块表到内存
IMAGE_DOS_HEADER ImageDosHeader;
LARGE_INTEGER FileOffset;
//读取DOS头
FileOffset.QuadPart = 0;
status = ZwReadFile(hFile, NULL, NULL, NULL, &ioStatusBlock,&ImageDosHeader,
sizeof(IMAGE_DOS_HEADER),&FileOffset,NULL);
if (!NT_SUCCESS(status))
{
KdPrint(("read image dos header failed code is%x\n", status));
ZwClose(hFile);
return status;
}
//读取NT头
IMAGE_NT_HEADERS ImageNTHeader;
FileOffset.QuadPart = ImageDosHeader.e_lfanew;
status = ZwReadFile(hFile, NULL, NULL, NULL, &ioStatusBlock, &ImageNTHeader, sizeof(IMAGE_NT_HEADERS),
&FileOffset, NULL);
if (!NT_SUCCESS(status))
{
KdPrint(("read nt header failed error code is %x\n", status));
ZwClose(hFile);
return status;
}
//读取区块表
FileOffset.QuadPart = ImageDosHeader.e_lfanew + sizeof(IMAGE_NT_HEADERS); //设置文件指针
IMAGE_SECTION_HEADER *pImageSectionHeader;
//申请一块和区块表大小相同的内存,然后将区块表复制到这块内存中
pImageSectionHeader = ExAllocatePool(NonPagedPool,
sizeof(IMAGE_SECTION_HEADER)*ImageNTHeader.FileHeader.NumberOfSections);
if (pImageSectionHeader==NULL)
{
KdPrint(("section exallocate pool failed"));
ZwClose(hFile);
return STATUS_UNSUCCESSFUL;
}
status = ZwReadFile(hFile, NULL, NULL, NULL, &ioStatusBlock, pImageSectionHeader,
sizeof(IMAGE_SECTION_HEADER)*ImageNTHeader.FileHeader.NumberOfSections, &FileOffset,NULL);
if (!NT_SUCCESS(status))
{
KdPrint(("read sections failed,error coed %x\n",status));
ZwClose(hFile);
ExFreePool(pImageSectionHeader);
return STATUS_UNSUCCESSFUL;
}
//第三步,重新申请一块内存,将之前的DOS头,NT头,区块表和真正的区块组织成一个连续整体。
//DOS头,NT头和区块表在磁盘文件中的对齐和内存的对齐是没有区别的,直接复制。
PVOID FullVirtualAddress = ExAllocatePool(NonPagedPool, ImageNTHeader.OptionalHeader.SizeOfImage);
if (FullVirtualAddress==0)
{
KdPrint(("allocate memory failed"));
ZwClose(hFile);
ExFreePool(pImageSectionHeader);
return STATUS_UNSUCCESSFUL;
}
memset(FullVirtualAddress, 0, ImageNTHeader.OptionalHeader.SizeOfImage);//清零新申请的内存
//申请到内存开始复制
RtlCopyMemory(FullVirtualAddress,&ImageDosHeader,sizeof(IMAGE_DOS_HEADER));//复制DOS头
RtlCopyMemory((PVOID)((ULONG)FullVirtualAddress+ImageDosHeader.e_lfanew),&ImageNTHeader,sizeof(IMAGE_NT_HEADERS));//复制NT头
//复制区块表
RtlCopyMemory((PVOID)((ULONG)FullVirtualAddress + ImageDosHeader.e_lfanew+sizeof(IMAGE_NT_HEADERS)),
pImageSectionHeader,sizeof(IMAGE_SECTION_HEADER)*ImageNTHeader.FileHeader.NumberOfSections);
//用FOR循环复制真正的区块表,真正的区块表一定要按内存对齐值读取的内存。
ULONG SecVirualAddress;
ULONG dataRaw;
for (ULONG i = 0; i < ImageNTHeader.FileHeader.NumberOfSections; i++)
{
SecVirualAddress = pImageSectionHeader .VirtualAddress+(ULONG)FullVirtualAddress;
dataRaw = max(pImageSectionHeader .SizeOfRawData, pImageSectionHeader .Misc.VirtualSize);
FileOffset.QuadPart = pImageSectionHeader .PointerToRawData;
status = ZwReadFile(hFile, NULL, NULL, NULL, &ioStatusBlock, (PVOID)SecVirualAddress,dataRaw,&FileOffset,NULL);
if (!NT_SUCCESS(status))
{
KdPrint(("read file failed in section[%d]\n", i));
ExFreePool(FullVirtualAddress);
ExFreePool(pImageSectionHeader);
ZwClose(hFile);
return status;
}
}
KdPrint(("read file to memory success address is%x\n", FullVirtualAddress));
*OutAddress = FullVirtualAddress;
}
/修复基址重定位表中的数据和构建新SSDT表开始///
/*函数原型 VOID RelocationModule(PVOID PNewImageAddres,PVOID POriginalAddress)
功能;修复重定位表里的数据
参数一,需要修复重定位数据的模块地址
参数二,原始内核模块在内存中的地址,因为新内核不能完全替代原始内核,许多原始内核数据在初始化时已
决定,新内核必须以原始内核基址为依据进行重定位,所以这里第二个参数传入原始内核地址。
*/
VOID RelocationModuleAndBuildSSDT(PVOID PNewImageAddres,PVOID POriginalAddress)
{
PIMAGE_DOS_HEADER pImageDosHeader;
PIMAGE_NT_HEADERS pImageNTHeader;
IMAGE_DATA_DIRECTORY imagedatadectory;
PIMAGE_BASE_RELOCATION pimagebaserelocation;
USHORT typevalue;
ULONG RelocationAddress;
pImageDosHeader = PNewImageAddres;
pImageNTHeader = (ULONG)PNewImageAddres + pImageDosHeader->e_lfanew;
ULONG RelocOffset = (ULONG)POriginalAddress - pImageNTHeader->OptionalHeader.ImageBase; //重定位数据偏移量计算
imagedatadectory =pImageNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];
pimagebaserelocation = (ULONG)PNewImageAddres + imagedatadectory.VirtualAddress;
/*重定位块PIMAGE_BASE_RELOCATION的.VirtualAddres成员不等于零时进行循环修复每一页中的重定位数据。
重定位块是一个连续存放的数组,每一个重定位块的大小是不相同的,但却是已知的。一个重定位块只能修复一页内存中的重定位数据项,
重定位块的数量是不确定的,最后以一个内容为零的重定位块作为结束标志,
每一页内存中有多少个数据需要重定位也是不相等的,但是却是可以通过PIMAGE_BASE_RELOCATION的.SizeOfBlock- 8) / 2计算出来,
用(SizeOfBlock-8)/2就可以得到每一页内存中有多少需要修复的数据,每一个重定位项占2字节,所以除2,减8是因为重定位块有两个成员
PIMAGE_BASE_RELOCATION->VirtualAddress和PIMAGE_BASE_RELOCATION->SizeOfBlock各占四字节。
重点内容是;第X+1重定位块的地址就等于第X重定位块的地址加上X.SizeOfBlock,依此类推就能得到所有重定位块的地址,
*/
ULONG ab = 0;//计算有多少个需要修复的重定位块用的变量,每个重定位块有若干需要重定位的项
while (pimagebaserelocation->VirtualAddress)
{
ULONG VirtualAddress = pimagebaserelocation->VirtualAddress+ (ULONG)PNewImageAddres;
ULONG NumberOfReloc = (pimagebaserelocation->SizeOfBlock- 8) / 2;
ab = ab + 1;
for (ULONG index = 0; index < NumberOfReloc;index++)
{
typevalue = pimagebaserelocation->TypeOffset[index];
if (typevalue>>12==IMAGE_REL_BASED_HIGHLOW)
{
RelocationAddress = (typevalue&0xfff) + pimagebaserelocation->VirtualAddress + (ULONG)PNewImageAddres;
if (!MmIsAddressValid(RelocationAddress))
{
continue;
}
*(ULONG*)RelocationAddress += RelocOffset;
}
}
pimagebaserelocation = (ULONG)pimagebaserelocation +pimagebaserelocation->SizeOfBlock;
//(ULONG)pimagebaserelocation +pimagebaserelocation->SizeOfBlock; 这句前面一定要叫个(ULONG)转换
//我一开始没加,就一直出错。
KdPrint(("修复第%d个重定位块\n",ab));
}
KdPrint(("Relocation base end"));
}
///Build New SSDT start构造新SSDT表开始/
VOID buildSSDT(PVOID PNewImageAddres, PVOID POriginalAddress)
{
ULONG RelocOffset;
PNewSSDT= ((ULONG)&KeServiceDescriptorTable - (ULONG)POriginalAddress) + (ULONG)PNewImageAddres;//获得新SSDT结构地址
if (!MmIsAddressValid(PNewSSDT))
{
KdPrint(("new ssdt address is invalid"));
return;
}
RelocOffset = (ULONG)PNewImageAddres - (ULONG)POriginalAddress;//定位偏移
PNewSSDT->NumberOfServices = KeServiceDescriptorTable.NumberOfServices;//设置新SSDT表个数
PNewSSDT->ServiceTableBase = ((ULONG)KeServiceDescriptorTable.ServiceTableBase - (ULONG)POriginalAddress) + (ULONG)PNewImageAddres;
PNewSSDT->ParamTableBase = ((ULONG)KeServiceDescriptorTable.ParamTableBase - (ULONG)POriginalAddress) + (ULONG)PNewImageAddres;
for (ULONG index = 0; index < KeServiceDescriptorTable.NumberOfServices; index++)//构建新SSDT表的TableBase
{
PNewSSDT->ServiceTableBase[index] += RelocOffset;
}
RtlCopyMemory(PNewSSDT->ParamTableBase, KeServiceDescriptorTable.ParamTableBase, KeServiceDescriptorTable.NumberOfServices*sizeof(char));
KdPrint(("new ssdt build success address is%x\n", PNewSSDT));
}
///Build New ShadowSSDT start构造新ShadowSSDT表开始/
/*函数原型:buildShadowSSDT(PVOID PNewImageAddres, PVOID POriginalAddress)
参数一;新WIN32K.SYS的基地址。
参数二;原始ntkrnlpa.exe文件的基地址,切记,这里不能传入原始WIN32K.sys地址,因为KeServiceDescriptorTableShadow表依然在ntkrnlpa.exe内核文件中
参数三;原始WIN32K.sys模块基地址*/
VOID buildShadowSSDT(PVOID PNewImageAddres, PVOID POriginalAddress,PVOID origWin32kAddress)
{
ULONG RelocOffset;
KeServiceDescriptorTableShadow =(ULONG)&KeServiceDescriptorTable+0x50;//WIN732位系统中KeServiceDescriptorTableShadow可以这样定位。
PNewShadowSSDT = ((ULONG)KeServiceDescriptorTableShadow - (ULONG)POriginalAddress) + (ULONG)PNewImageAddres;//获得新SSDT结构地址
if (!MmIsAddressValid(PNewSSDT))
{
KdPrint(("new ssdt address is invalid"));
return;
}
RelocOffset = (ULONG)PNewImageAddres - (ULONG)origWin32kAddress;//定位偏移
PNewShadowSSDT->NumberOfServices = KeServiceDescriptorTableShadow->NumberOfServices;//设置新SSDT表个数
/*注意;KeServiceDescriptorTableShadow结构变量在(ULONG)&KeServiceDescriptorTable+0x50的地方,但是KeServiceDescriptorTableShadow->ServiceTableBase 却是在
WIN32K.sys模块中。
*/
PNewShadowSSDT->ServiceTableBase = ((ULONG)KeServiceDescriptorTableShadow->ServiceTableBase - (ULONG)origWin32kAddress) + (ULONG)PNewImageAddres;
PNewShadowSSDT->ParamTableBase = ((ULONG)KeServiceDescriptorTableShadow->ParamTableBase - (ULONG)origWin32kAddress) + (ULONG)PNewImageAddres;
for (ULONG index = 0; index < KeServiceDescriptorTableShadow->NumberOfServices; index++)//构建新SSDT表的TableBase
{
PNewShadowSSDT->ServiceTableBase[index] += RelocOffset;
}
RtlCopyMemory(PNewShadowSSDT->ParamTableBase, KeServiceDescriptorTableShadow->ParamTableBase, KeServiceDescriptorTableShadow->NumberOfServices*sizeof(char));
KdPrint(("new shadow ssdt build success address is%x\n", PNewShadowSSDT));
}
/*开始构造自己在KiFastCallEntry中的过滤函数,这种过滤函数需要两个,第一个是裸函数,实现内容必须是汇编,第二个是功能函数,是将被第一个调用的
为什么这种过滤函数要用两个呢?因为第一个调用的是裸函数,裸函数的好处是不会破坏堆栈,不会产生额外的代码,但是它的坏处是只能用汇编实现内部代码,
不合适用高级语言接受参数或返回值等等,如果我们用汇编在这种裸函数内部实现过滤功能就太麻烦了,因此在第二个过滤函数中用高级语言实现我们的功能。
*/
VOID filterKiFastCall2(ULONG tablebase, ULONG indexs, ULONG OrigFuncAddress)
{
if (tablebase == KeServiceDescriptorTable.ServiceTableBase)
{
//DbgBreakPoint();
//KdPrint(("entry be hook fastcall\n"));
if (strstr((char *)PsGetCurrentProcess() + 0x16c, protect1) != 0 || \
strstr((char*)PsGetCurrentProcess() + 0x16c, protect2) != 0 || \
strstr((char*)PsGetCurrentProcess() + 0x16c, protect3) != 0)
{
//KdPrint(("ssdt find be protect process is %s\n",(char*)PsGetCurrentProcess()+0x16c));
return PNewSSDT->ServiceTableBase[indexs];
}
}
if (tablebase == KeServiceDescriptorTableShadow->ServiceTableBase) //过滤shadowssdt
{
//KdPrint(("entry be hook fastcall\n"));
if (strstr((char *)PsGetCurrentProcess() + 0x16c, protect1) != 0 || \
strstr((char*)PsGetCurrentProcess() + 0x16c, protect2) != 0 || \
strstr((char*)PsGetCurrentProcess() + 0x16c, protect3) != 0)
{
//KdPrint(("shadow ssdt find be protect process is %s\n", (char*)PsGetCurrentProcess() + 0x16c));
return PNewShadowSSDT->ServiceTableBase[indexs];
}
}
return OrigFuncAddress;
}
__declspec(naked)
VOID filterKiFastCall1() //第一个过滤函数简单调用第二个,用汇编传入参数。
{
__asm{
pushad //切记,一定要pushad保存寄存器环境
pushfd
push edx
push eax //系统调用的下标
push edi //SSDT数组地址
call filterKiFastCall2 //调用此函数会自动平衡堆栈,不需要有平衡堆栈的动作
mov [esp+0x18],eax
popfd
popad
sub esp, ecx //恢复被HOOK指令改变的五个字节
shr ecx, 2
jmp HookbackAddress //跳转到HOOK点加五节字的地方
}
}
/Hook KiFastCallEntry Start/
VOID HookKiFastCallEntry()
{
/*首先获得KiFastCallEntry函数的地址,梦无极的教程是用栈回溯的方式获得此地址的,这里将用另一种更简单的方式
CPU为了支持快速系统调用,另外增加了三个寄存器,其中MSR为0x176的寄存器就专门存放了KiFastCallEntry的地址,只要读取这个寄存器就可以
获得KiFastCallEntry的地址了。切记是0x176,十六进制的MSR地址。
*/
KdPrint(("start hook protect1=%s,protect2=%s,protect3=%s\n", protect1, protect2, protect3));
ULONG KiFastCallAddress=0;
__asm{
mov ecx,0x176
rdmsr
mov KiFastCallAddress,eax //这三句汇编代码已获得KiFastCallEntry的地址。
}
//开始特征码定位KiFastCallEntry内部最佳HOOK点
/*
83e5419e 8a0c10 mov cl,byte ptr [eax+edx]
83e541a1 8b1487 mov edx,dword ptr [edi+eax*4]
83e541a4 2be1 sub esp,ecx
83e541a6 c1e902 shr ecx,2
83e541a9 8bfc mov edi,esp
*/
if (KiFastCallAddress==0)
{
KdPrint(("get kifastcall failed"));
return;
}
Faddress=(UCHAR*)KiFastCallAddress;
for (ULONG index = 0; index < 0x300;index++)
{
if (*Faddress == 0x2B &&*(Faddress + 1) == 0xE1 &&*(Faddress + 2) == 0xC1 &&
*(Faddress + 3) == 0xE9 &&*(Faddress + 4) == 0x02)
{
KdPrint(("find best point address is%x\n", Faddress));
HookbackAddress = Faddress + 5;
break;
}
Faddress++;
}
/*特征码定位到最佳HOOK点之后,开始对它进行HOOK,让它跳转到我们自己的过滤函数中*/
UCHAR hookCode[5] = { 0 };
hookCode[0] = 0xe9;//JMP机器码是0xE9,CALL的机器码是0xe8
*(ULONG*)&hookCode[1] = ((ULONG)filterKiFastCall1 - (ULONG)Faddress) - 5;
PageProtectOff();
RtlCopyMemory(backHookCode, Faddress, 5);
RtlCopyMemory(Faddress, hookCode, 5);
PageProtectOn();
}
VOID UnHookKiFastCall()//取消重载内核,释放新模块内存
{
PageProtectOff();
RtlCopyMemory(Faddress,backHookCode, 5);
PageProtectOn();
ExFreePool(NewKernalVirualAddress);
ExFreePool(NewWin32kAddress);
}
驱动主文件内容,主要是派遣例程写法/
#include <FileOp.h>
#include "SSDTHook.h"
#include <ReLoadKernal.h>
#define stringBuffe CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) //缓冲模式交互控制码
#define stringNeither CTL_CODE(FILE_DEVICE_UNKNOWN,0x801,METHOD_NEITHER,FILE_ANY_ACCESS) //其它模式交互控制码
#define stringDirect CTL_CODE(FILE_DEVICE_UNKNOWN,0x802,METHOD_IN_DIRECT,FILE_ANY_ACCESS)//直接模式交互控制码
PDEVICE_OBJECT DeviceObejct;
PWCHAR inputBuffer;
PCHAR protectBuffer;
NTSTATUS UnloadDriver(PDRIVER_OBJECT pDriverObject)
{
NTSTATUS status;
UNICODE_STRING SymbolicName;
RtlInitUnicodeString(&SymbolicName, L"\\??\\AssociateDriver");
status = IoDeleteSymbolicLink(&SymbolicName);
if (pDriverObject->DeviceObject!=NULL)
{
IoDeleteDevice(pDriverObject->DeviceObject);
}
if (!NT_SUCCESS(status))
{
KdPrint(("unload driver failed"));
return status;
}
//UnHookNtOpenProcess();//SSDTHook
//UninlineHook();//inLineSSDTHook
UnHookKiFastCall();
KdPrint(("unload driver success"));
return STATUS_SUCCESS;
}
//创建设备
NTSTATUS CreateDevice(PDRIVER_OBJECT pDriverObject)
{
NTSTATUS status;
UNICODE_STRING DeviceName;
UNICODE_STRING SymbolicName;
RtlInitUnicodeString(&DeviceName, L"\\Device\\AssociateDevice");
status = IoCreateDevice(pDriverObject, 0, &DeviceName, FILE_DEVICE_UNKNOWN, 0, FALSE, &DeviceObejct);
if (!NT_SUCCESS(status))
{
KdPrint(("Create Device failed"));
}
DeviceObejct->Flags |= DO_BUFFERED_IO;
pDriverObject->Flags |= DO_BUFFERED_IO;
RtlInitUnicodeString(&SymbolicName, L"\\??\\AssociateDriver");
status=IoCreateSymbolicLink(&SymbolicName, &DeviceName);
if (!NT_SUCCESS(status))
{
KdPrint(("create symbolic link failed"));
IoDeleteDevice(DeviceObejct);
return status;
}
return STATUS_SUCCESS;
}
NTSTATUS DispatchMajor(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
PWCHAR buffer = L"来自驱动,缓冲模式";
PWCHAR direct = L"来自驱动, 直接模式";
PWCHAR neither = L"来自驱动, 其它模式";
ULONG wr = 0;
//得到当前栈指针
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);
ULONG mf = stack->MajorFunction;//得到当前IRP栈中的派遣函数,不是派遣函数的数组.
KdPrint(("entry device dispatch"));
switch (mf)
{
case IRP_MJ_DEVICE_CONTROL:
KdPrint(("entry device control"));
//得到输入缓冲区大小
ULONG cin = stack->Parameters.DeviceIoControl.InputBufferLength;
//得到输出缓冲区大小
ULONG cout = stack->Parameters.DeviceIoControl.OutputBufferLength;
//得到IOCTL码
ULONG CTLcode = stack->Parameters.DeviceIoControl.IoControlCode;
switch (CTLcode)
{
case stringBuffe://缓冲通信模式.
KdPrint(("buffer mode\n"));
/*获取缓冲区数据,在缓冲交互模式中,系统输入缓冲和系统输出缓冲是同一个,但用户层的缓冲是两个.
输入时,从用户输入缓冲复制数据到系统缓冲,操作完IRP之后再从系统缓冲复制数据到用户层的输出缓冲.*/
protectBuffer = (CHAR*)Irp->AssociatedIrp.SystemBuffer;
KdPrint(("buffer mode view use string is:%s", protectBuffer));
//RtlCopyMemory(protectall, protectBuffer, 384);
char *p = NULL, *s=NULL;
s = strtok_s(protectBuffer, ",",&p); //将用户层传进来的多个进程名字符串进行分割
if (s!=NULL)
{
strcpy(protect1, s); //将分割出来的第一个进程名字复制给内核字符数组
}else { strcpy(protect1, "Cheat"); }
s = NULL;
s = strtok_s(NULL, ",",&p);
if (s!=NULL)
{
strcpy(protect2, s);//将分割出来的第2个进程名字复制给内核字符数组
}else {strcpy(protect2, protect1);}
s = NULL;
s = strtok_s(NULL, ",",&p);
if (s!=NULL)
{
strcpy(protect3, s);//将分割出来的第3个进程名字复制给内核字符数组
}else {strcpy(protect3, protect1);}
KdPrint(("protect1=%s,protect2=%s,protect3=%s\n", protect1, protect2, protect3));
DbgBreakPoint();
hookShadowBitBlt();
ReadFileToMemory(L"\\??\\C:\\Windows\\System32\\ntkrnlpa.exe", &NewKernalVirualAddress);
ReadFileToMemory(L"\\??\\C:\\Windows\\System32\\win32k.sys", &NewWin32kAddress);
KdPrint(("new ssdt address is %x\n origkeraddress is%x\n", NewKernalVirualAddress,kerAddress));
KdPrint(("new shadowssdt address is %x\n origkeraddress is%x\n", NewWin32kAddress, win32kAddress));
RelocationModuleAndBuildSSDT(NewKernalVirualAddress, (PVOID)kerAddress);
RelocationModuleAndBuildSSDT(NewWin32kAddress, (PVOID)win32kAddress);
buildSSDT(NewKernalVirualAddress, kerAddress);
buildShadowSSDT(NewWin32kAddress, kerAddress, win32kAddress);
HookKiFastCallEntry();
/* inputBuffer = L"from driver",原先我用的这句,其实是个严重的错误,inputBuffer是个指针,
此指针的指向的地址是输入输出缓冲,我这样操作的作用是将此指针的指向的地址变成了字符串的地址,而原先的输入输出
缓冲依然未被操作,正确的作法如下*/
wr = 0;
break;
case stringDirect://直接通信模式
KdPrint(("direct mode"));
//直接通信模式获取用户层输入的数据与缓冲通信模式相同
inputBuffer = (PWCHAR)Irp->AssociatedIrp.SystemBuffer;
KdPrint(("direct mode view use string is %S", inputBuffer));
//返回给用户层数据时用的缓冲区就和缓冲通信模式不同了,这种模式是直接叫用户空间输出地址映射进内核.
PWCHAR outBuffer = MmGetSystemAddressForMdlSafe(Irp->MdlAddress, NormalPagePriority);
RtlCopyMemory(outBuffer, direct, wcslen(direct) * 2);
wr = wcslen(direct) * 2;//设置实际操作的字节数
UnHookShadow();//ShadowSSDT的Hook要在这个用户层传递进来的IRP例程中卸载,不能在驱动卸载例程里卸载。
break;
case stringNeither:
__try{
KdPrint(("neither mode"));
//其它通信模式获取用户层输入的数据如下,这种模式是直接取用户空间的缓冲地址,相对不安全.
inputBuffer = stack->Parameters.DeviceIoControl.Type3InputBuffer;
ProbeForRead(inputBuffer, cin, 4);
KdPrint(("neither mode view use string is %S", inputBuffer));
//其它通信模式返回用户数据操作如下
PWCHAR outBuffer = (PWCHAR)Irp->UserBuffer;
ProbeForWrite(outBuffer, cout, 4);//第三个参数是对齐值,第二个参数由上面获得.
memcpy(outBuffer, neither, wcslen(neither) * 2 + 2);
wr = wcslen(neither) * 2 + 2;
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
KdPrint(("exception for neither"));
}
break;
default:
break;
}
case IRP_MJ_CLOSE:
default:
break;
}
Irp->IoStatus.Information = wr;//操作了多少字节数,宽字符用wcslen取字节数乘2
Irp->IoStatus.Status = STATUS_SUCCESS;//返回成功
IoCompleteRequest(Irp, IO_NO_INCREMENT);//指示完成此IRP
KdPrint(("leave dispatch function\n"));
return STATUS_SUCCESS;//返回成功
}
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegistryPath)
{
NTSTATUS status;
KdPrint(("entry driver registry path is %wZ\n",pRegistryPath));
pDriverObject->DriverUnload = UnloadDriver;
status = CreateDevice(pDriverObject);
pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DispatchMajor;
//妈的,一定要把这个IRP_MJ_CREATE派遣函数替换成我们自己的,要不然用户层打不开符号链接
pDriverObject->MajorFunction[IRP_MJ_CREATE] = DispatchMajor;
pDriverObject->MajorFunction[IRP_MJ_CLOSE] = DispatchMajor;
pDriverObject->MajorFunction[IRP_MJ_READ] = DispatchMajor;
pDriverObject->MajorFunction[IRP_MJ_WRITE] = DispatchMajor;
//MyCreateFile();//文件操作
//HookNtOpenProcess();//SSDTHook
//DbgBreakPoint();//设试断点
//inlineHookOpenProcess();NtOpenProcess的inlineHook测试
kerAddress = EnumDriver(pDriverObject, L"ntoskrnl.exe");//遍历驱动模块.
win32kAddress = EnumDriver(pDriverObject,L"win32k.sys");
KeServiceDescriptorTableShadow =(ULONG) &KeServiceDescriptorTable + 0x50;
KdPrint(("KeServiceDescriptorTable is %x\n KeServiceDescriptorTableshadow is %x", &KeServiceDescriptorTable, KeServiceDescriptorTableShadow));
return STATUS_SUCCESS;
}
//最后是用户层通信代码
#include <winioctl.h>//与驱动交互一定要包含此头文件
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
#define stringBuffe CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) //缓冲模式交互控制码
#define stringNeither CTL_CODE(FILE_DEVICE_UNKNOWN,0x801,METHOD_NEITHER,FILE_ANY_ACCESS) //其它模式交互控制码
#define stringDirect CTL_CODE(FILE_DEVICE_UNKNOWN,0x802,METHOD_IN_DIRECT,FILE_ANY_ACCESS)//直接模式交互控制码
HANDLE OpenSymbolic();
//打开符号链接
HANDLE OpenSymbolic()
{
HANDLE hfile= CreateFile(L"\\??\\AssociateDriver", FILE_ALL_ACCESS, 0,
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hfile == INVALID_HANDLE_VALUE)
{
AfxMessageBox(L"open symbolic failed");
TRACE("open symbolic failed error code is %x\n", GetLastError());
}else
{
AfxMessageBox(L"open symbolic success");
}
return hfile;
}
void CAssociateExeDlg::OnBnClickedBAssocibuffer()
{
CString outbuffer;
UpdateData(TRUE);//控件的值刷新到控件变量
ULONG dwWrite;
HANDLE HFile = OpenSymbolic();
memset(outbuffer.GetBuffer(256), 0, 256 * 2);//输出缓冲区清零,否则会出现乱码.
//宽字符转换多字节集代码开始///
char *ansiname = new char[384];//为char字符指针分配一块大小512的空间,定义的是指针就得分配内存.
memset(ansiname, 0, sizeof(char)* 384); //初始化新分配的内存空间,将其全部填充0
WideCharToMultiByte(CP_ACP, 0, View.GetBuffer(), -1, ansiname, 384, NULL, NULL);
//宽字符转换多字节集代码结束,就三行///
DeviceIoControl(HFile, stringBuffe, ansiname, 384, outbuffer.GetBuffer(256), 256 * 2 + 2, &dwWrite, NULL);
}