Hook之前要干掉PG:http://www.m5home.com/bbs/thread-5893-1-1.html
上篇文章知道了寻找SSDT表的方法,这篇记录一下如何实现SSDT表的Hook。
下面以Hook NtOpenProcess为例,之前我查SSDT表发现NtOpenProcess函数的标号为35,用XT等工具也能查看。
废话不多说,上代码。
[cpp] view plain copy 首先感谢老大(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(<span style="font-family: Arial, Helvetica, sans-serif;">ClientId->processid</span> ,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里面查看到。