学习内核编程,祝早日能编写POC程序!
SSDT HOOK NtOpenProcess
typedef NTSTATUS(*pfnNtOpenProcess)(
PHANDLE,
ACCESS_MASK,
POBJECT_ATTRIBUTES,
PCLIENT_ID);
pfnNtOpenProcess OldNtOpenProcess;
定义一个指向API的函数指针定义方法。作用是定义API函数指针备份原来的函数地址。
typedef struct _SERVICE_DESCRIPTOR_TABLE {
PVOID ServiceTableBase;//System Service Dispatch Table 的基地址
PVOID ServiceCounterTableBase;//包含着 SSDT 中每个服务被调用次数的计数器。这个计数器一般由sysenter 更新
ULONG NumberOfServices;//由 ServiceTableBase 描述的服务的数目
PUCHAR ParamTableBase;//包含每个系统服务参数字节数表的基地址-系统服务参数表
}SERVICE_DESCRIPTOR_TABLE, *PSERVICE_DESCRIPTOR_TABLE;
定义SERVICE_DESCRIPTOR_TABLE结构类型,SERVICE_DESCRIPTOR_TABLE = struct _SERVICE_DESCRIPTOR_TABLE,SERVICE_DESCRIPTOR_TABLE实际上就是_SERVICE_DESCRIPTOR_TABLE的别名。C语言小基础。
VOID TestAddr(PCWSTR funcname)
{
UNICODE_STRING strToFind;
RtlInitUnicodeString(&strToFind, funcname);
PVOID AddrToFind = MmGetSystemRoutineAddress(&strToFind);
if (NULL != AddrToFind) {
DbgPrint("Hook之前调用MmGetSystemRoutineAddress: 0x%X\n", AddrToFind);
DbgPrint("Hook之前直接用NtOpenProcess名 %X\n", NtOpenProcess);
//直接用函数名获得的NtOpenProcess的地址和用MmGetSystemRoutineAddress获得的地址一样
OldNtOpenProcess = (pfnNtOpenProcess)HookSSDTFunction(NtOpenProcess, MyNtOpenProcess);//传入原始函数地址和自定义的函数地址,返回指向原始函数地址的函数指针
}
}
可以用MmGetSystemRoutineAddress获取未导出的函数地址方法并转换为函数指针调用
for (ULONG i = 0; i < KeServiceDescriptorTable.NumberOfServices; i++)
{
if ((LONG)pMdlLocked[i] == (LONG)OldFunction)
{
InterlockedExchange(&pMdlLocked[i], (LONG)HookFunction);
break;
}
}
遍历KeServiceDescriptorTable找到原来的NtOpenProcess函数地址,并使用InterlockedExchange函数实现多线程下的循环锁功能: 所谓循环锁,就是在线程1中如果要对变量进行操作,要先查看这个变量(或资源)有没有被其它线程用到,如果是,则一直循环,则到其它线程放弃对该变量(或资源)的控制。如果否,直接可以对该变量(或资源)进行操作。
EProcess中的ActiveProcessLinks双向循环链表断链
ListEntry = (ULONG_PTR)EProcess + x86_EPROCESS_OFFSET;
v1 = (ULONG_PTR)ListEntry->Flink - x86_EPROCESS_OFFSET;
while (v1 != EProcess)
{
//v1 0x87e95d40 struct _KPROCESS *
//ImageFileName 0x87e95eac "calc.exe"
ImageFileName = (ULONG_PTR)v1 + x86_IMAGEFILENAME_OFFSET;
if (strcmp(ImageFileName,"System")==0)
{
__HeadEntry = (ULONG_PTR)v1+ x86_EPROCESS_OFFSET;
}
ListEntry = (ULONG_PTR)v1 + x86_EPROCESS_OFFSET;
if (strcmp(ImageFileName, ProcessName) == 0)
{
if (ListEntry != NULL)
{
__ListEntry = ListEntry;
RemoveEntryList(ListEntry);//调用此API移除链表
Status = STATUS_SUCCESS;
break;
}
}
v1 = (ULONG_PTR)(ListEntry->Flink) - x86_EPROCESS_OFFSET;
这里要特别注意EPROCESS的ImageFileName是16个字节。[15] UChar。
搜索PsLookupProcessByProcessId函数中的PspCidTable结构并获取地址
RtlInitUnicodeString (&pslookup, L"PsLookupProcessByProcessId");
addr=(PUCHAR)MmGetSystemRoutineAddress(&pslookup);
KdPrint(("PsLookupProcessByProcessId addr=0x%x\r\n", addr));
for (p=addr;p<addr+PAGE_SIZE;p++) //搜索PsLookupProcessByProcessId函数
{
if((*(PUSHORT)p==0x35ff)&&(*(p+6)==0xe8))// 注意这句
{
cid=*(PULONG)(p+2);
return cid;
//break;
}
}
首先windows平台的VS编译器无论32位还是64位,Int一定是四字节。USHORT是两字节。ULONG也是四字节。
((PUSHORT)p==0x35ff)&&((p+6)==0xe8)即是指FF35XXXXXXXXe8字节码。
看到函数的反汇编就知道了。
诸如push offset,call xxx此类的函数调用方式都可以使用这种搜索机器码的方式进行跟踪。
取的时候cid=*(PULONG)(p+2);注意位置起始是p+2,取的大小是四字节。所以就是push的操作数,60b25588。此相对偏移+指令当前地址+指令长度=PspCidTable表的真实内存地址。
参考
HOOK SSDT NtOpenProcess 保护进程
CR0方式读写内存与MDL方式读写内存
进程的攻与“防” ---- 进程隐藏(Win7 x32 绕过PC Hunter)