Hook之前要干掉PG:http://www.m5home.com/bbs/thread-5893-1-1.html
上篇文章知道了寻找SSDT表的方法,这篇记录一下如何实现SSDT表的Hook。
下面以Hook NtOpenProcess为例,之前我查SSDT表发现NtOpenProcess函数的标号为35,用XT等工具也能查看。
废话不多说,上代码。
首先感谢老大(Tesla.Angela)对我的帮助
//相关声明
__int64 __readmsr(int register);
unsigned __int64 __readcr0(void);
void __writecr0(
unsigned __int64 Data
);
void _disable(void);
void _enable(void);
//_SYSTEM_SERVICE_TABLE结构声明
typedef struct _SYSTEM_SERVICE_TABLE{
PVOID ServiceTableBase;
PVOID ServiceCounterTableBase;
ULONGLONG NumberOfServices;
PVOID ParamTableBase;
} SYSTEM_SERVICE_TABLE, *PSYSTEM_SERVICE_TABLE;
//_SERVICE_DESCRIPTOR_TABLE结构声明
typedef struct _SERVICE_DESCRIPTOR_TABLE{
SYSTEM_SERVICE_TABLE ntoskrnl; // ntoskrnl.exe (native api)
SYSTEM_SERVICE_TABLE win32k; // win32k.sys (gdi/user)
SYSTEM_SERVICE_TABLE Table3; // not used
SYSTEM_SERVICE_TABLE Table4; // not used
}SERVICE_DESCRIPTOR_TABLE,*PSERVICE_DESCRIPTOR_TABLE;
//声明要寻找进程名用的函数
NTKERNELAPI UCHAR * PsGetProcessImageFileName(PEPROCESS Process);
//定义NTOPENPROCESS
typedef NTSTATUS (__stdcall *NTOPENPROCESS)(OUT PHANDLE ProcessHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes,
IN OPTIONAL PCLIENT_ID ClientId);
NTOPENPROCESS OldOpenProcess = NULL;
ULONG OldTpVal;
//定义自己的NtOpenProcess
NTSTATUS __stdcall Fake_NtOpenProcess(OUT PHANDLE ProcessHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes,
IN OPTIONAL PCLIENT_ID ClientId)
{
PEPROCESS process = NULL;
NTSTATUS st = ObReferenceObjectByHandle(ClientId->processid,0,*PsProcessType,KernelMode,&process,NULL);
DbgPrint("进入HOOK函数.\n");
if (NT_SUCCESS(st))
{
if (!_stricmp((char*)PsGetProcessImageFileName(process),"CrackMe3.exe"))
{
return STATUS_ACCESS_DENIED;
}
else
{
return OldOpenProcess(ProcessHandle,DesiredAccess,ObjectAttributes,ClientId);
}
}
else
{
return STATUS_ACCESS_DENIED;
}
}
//关闭页面保护
KIRQL WPOFFx64()
{
KIRQL irql=KeRaiseIrqlToDpcLevel();
UINT64 cr0=__readcr0();
cr0 &= 0xfffffffffffeffff;
__writecr0(cr0);
_disable();
return irql;
}
//开启页面保护
void WPONx64(KIRQL irql)
{
UINT64 cr0=__readcr0();
cr0 |= 0x10000;
_enable();
__writecr0(cr0);
KeLowerIrql(irql);
}
//老外定位KeServiceDescriptorTable的方法
ULONGLONG GetKeServiceDescriptorTable64()
{
char KiSystemServiceStart_pattern[] = "\x8B\xF8\xC1\xEF\x07\x83\xE7\x20\x25\xFF\x0F\x00\x00"; //特征码
ULONGLONG CodeScanStart = (ULONGLONG)&_strnicmp;
ULONGLONG CodeScanEnd = (ULONGLONG)&KdDebuggerNotPresent;
UNICODE_STRING Symbol;
ULONGLONG i, tbl_address, b;
for (i = 0; i < CodeScanEnd - CodeScanStart; i++)
{
if (!memcmp((char*)(ULONGLONG)CodeScanStart +i, (char*)KiSystemServiceStart_pattern,13))
{
for (b = 0; b < 50; b++)
{
tbl_address = ((ULONGLONG)CodeScanStart+i+b);
if (*(USHORT*) ((ULONGLONG)tbl_address ) == (USHORT)0x8d4c)
return ((LONGLONG)tbl_address +7) + *(LONG*)(tbl_address +3);
}
}
}
return 0;
}
//根据KeServiceDescriptorTable找到SSDT基址
PULONG GetSSDTBaseAddress()
{
PULONG addr = NULL;
PSYSTEM_SERVICE_TABLE ssdt = (PSYSTEM_SERVICE_TABLE)GetKeServiceDescriptorTable64();
addr = (PULONG)(ssdt->ServiceTableBase);
return addr;
}
//根据标号找到SSDT表中函数的地址
ULONGLONG GetFuncAddr(ULONG id)
{
LONG dwtmp = 0;
ULONGLONG addr = 0;
PULONG stb = NULL;
stb = GetSSDTBaseAddress();
dwtmp = stb[id];
dwtmp = dwtmp >> 4;
addr = (LONGLONG)dwtmp + (ULONGLONG)stb;
DbgPrint("SSDT TABLE BASEADDRESS:%llx",addr);
return addr;
}
//设置函数的偏移地址,注意其中参数的处理。低四位放了参数个数减4个参数。如果参数小于等于4的时候为0
#define SETBIT(x,y) x|=(1<<y) //将X的第Y位置1
#define CLRBIT(x,y) x&=~(1<<y) //将X的第Y位清0
#define GETBIT(x,y) (x & (1 << y)) //取X的第Y位,返回0或非0
ULONG GetOffsetAddress(ULONGLONG FuncAddr, CHAR paramCount)
{
LONG dwtmp = 0,i;
CHAR b = 0, bits[4] = {0};
PULONG stb = NULL;
stb = GetSSDTBaseAddress();
dwtmp = (LONG)(FuncAddr - (ULONGLONG)stb);
dwtmp = dwtmp << 4;
if (paramCount>4)
{
paramCount = paramCount - 4;
}
else
{
paramCount = 0;
}
memcpy(&b,&dwtmp,1);
for (i=0;i<4;i++)
{
bits[i] = GETBIT(paramCount,i);
if (bits[i])
{
SETBIT(b,i);
}
else
{
CLRBIT(b,i);
}
}
memcpy(&dwtmp,&b,1);
return dwtmp;
}
//内核中用不到的方法,二次跳转用(自己的NtOpenProcess跳到KeBugCheckEx函数,然后再KeBugCheckEx函数跳到要Hook的NtOpenProcess)
VOID FuckKeBugCheckEx()
{
KIRQL irql;
ULONGLONG myfun;
UCHAR jmp_code[]="\xFF\x25\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF";
myfun=(ULONGLONG)Fake_NtOpenProcess;
memcpy(jmp_code+6,&myfun,8);
irql=WPOFFx64();
memset(KeBugCheckEx,0x90,15);
memcpy(KeBugCheckEx,jmp_code,14);
WPONx64(irql);
}
//Hook ssdt
VOID HookSSDT()
{
KIRQL irql;
LONG dwtmp = 0;
PULONG stb = NULL;
//1.get old address
OldOpenProcess = (NTOPENPROCESS)GetFuncAddr(35);
DbgPrint("Old_NtOpenProcess:%llx",(ULONGLONG)OldOpenProcess);
//2.show new address
stb = GetSSDTBaseAddress();
//3.get offset value
dwtmp = GetOffsetAddress((ULONGLONG)KeBugCheckEx,4);
//set kebugcheckex
FuckKeBugCheckEx();
//4.record old offset value
OldTpVal = stb[35];
irql = WPOFFx64();
stb[35] = GetOffsetAddress((ULONGLONG)KeBugCheckEx,2);
WPONx64(irql);
DbgPrint("KeBugCheckEx:%llx",(ULONGLONG)KeBugCheckEx);
DbgPrint("New_NtOpenProcess:%llx",GetFuncAddr(35));
}
//UN hook
VOID UnhookSSDT()
{
KIRQL irql;
PULONG stb=NULL;
stb = GetSSDTBaseAddress();
//老函数的地址复制回来
irql=WPOFFx64();
stb[35]=OldTpVal;
WPONx64(irql);
}
相关解释:
1.为什么要二次跳转?
WIN64内核里的每个驱动都不在同一个4GB里,4字节的整数只能表示4GB的范围,所以不管怎么修改这个4字节都不会跳到你的代理函数,因为你的驱动不可能跟NTOSKRNL在同一个4GB里面。
2.参数的处理:
函数地址的低四位存放了函数参数个数减4的数字,如果参数为5,那么低四位的数字为1,如果参数个数小于等于4个,低四位的数位0,可以再WINDBG里面查看到。