0x01 前言

  之前研究RootKit技术,发现了对象钩子这个概念,一直不知道是什么,然后在网上搜,最先找到的是sudami的一篇文章,于是跟着大牛的脚步研究,其中也参考<内核情景分析>,这本书真是每次看每次有收获。下面记录一下学习过程。

  0x02 OBJECT_HEADER结构

  这是对象的数据结构的形态,其中OBJECT_HEADER的结构如下

typedef struct _OBJECT_HEADER
{
LONBG PointerCount;
union
{
LONG HandleCount;
volatile PVOID NextToFree;
}
POBJECT_TYPE Type;
UCHAR NameInfoOffset;
UCHAR HandleInfoOffset;
UCHAR QuotaInfoOffset;
UCHAR Flags;
union
{
POBJECT_CREATE_INFORMNATION ObjectCreateInfo;
PVOID QuotaBlockCharged;
}
PSECURITY_DESCRIPTOR SecurityDescriptor;
QUAD Body;
}OBJECT_HEADER,*POBJECT_HEADER;

  对象头中保存着Body的地址,所以可以用下面的宏得到对象头

#define OBJECT_TO_OBJECT_HEADER(o)\
CONTAINING_RECORD((o),OBJECT_HEADER,Body)
#define CONTAINING_RECORD(address,type,field)\
((type*)(((ULONG_PTR)Address)-(ULONG_PTR)(&(((type*))->field))))

  实际硬偏移就是Body - 0x18 = Header  (XP下)

  0x03 OBJECT_TYPE

  在I/O子系统初始化的时候,系统就创建了Adapter,Controller,Device,Driver,IoCompletion,File等对象类型。

  其中创建文件对象类型的部分如下所示

IoInitSystem()-->IopCreateObjectTypes()
...
RtlInitUnicodeString( &nameString, L"File" );
objectTypeInitializer.DefaultPagedPoolCharge = IO_FILE_OBJECT_PAGED_POOL_CHARGE;
objectTypeInitializer.DefaultNonPagedPoolCharge = IO_FILE_OBJECT_NON_PAGED_POOL_CHARGE +
sizeof( FILE_OBJECT );
objectTypeInitializer.InvalidAttributes = OBJ_PERMANENT | OBJ_EXCLUSIVE | OBJ_OPENLINK;
objectTypeInitializer.GenericMapping = IopFileMapping;
objectTypeInitializer.ValidAccessMask = FILE_ALL_ACCESS;
objectTypeInitializer.MaintainHandleCount = TRUE;
objectTypeInitializer.CloseProcedure = IopCloseFile;
objectTypeInitializer.DeleteProcedure = IopDeleteFile;
objectTypeInitializer.ParseProcedure = IopParseFile;
objectTypeInitializer.SecurityProcedure = IopGetSetSecurityObject;
objectTypeInitializer.QueryNameProcedure = IopQueryName;
objectTypeInitializer.UseDefaultObject = FALSE; PERFINFO_MUNG_FILE_OBJECT_TYPE_INITIALIZER(objectTypeInitializer); if (!NT_SUCCESS( ObCreateObjectType( &nameString, // 对象类型名称
&objectTypeInitializer,//重要结构
(PSECURITY_DESCRIPTOR) NULL,//保留
&IoFileObjectType ))) { //对象类型
return FALSE;
}

  这里就要说说OBJECT_TYPE的结构了

typedef struct _OBJECT_TYPE {
ERESOURCE Mutex;
LIST_ENTRY TypeList;
UNICODE_STRING Name; // Copy from object header for convenience
PVOID DefaultObject;
ULONG Index;
ULONG TotalNumberOfObjects;
ULONG TotalNumberOfHandles;
ULONG HighWaterNumberOfObjects;
ULONG HighWaterNumberOfHandles;
OBJECT_TYPE_INITIALIZER TypeInfo;
ULONG Key;
ERESOURCE ObjectLocks[];
} OBJECT_TYPE, *POBJECT_TYPE;

  OBJECT_TYPE_INITIALIZER就是在IopCreateOBjectTypes()中初始化的那个结构

typedef struct _OBJECT_TYPE_INITIALIZER {
USHORT Length;
BOOLEAN UseDefaultObject;
BOOLEAN CaseInsensitive;
ULONG InvalidAttributes;
GENERIC_MAPPING GenericMapping;
ULONG ValidAccessMask;
BOOLEAN SecurityRequired;
BOOLEAN MaintainHandleCount;
BOOLEAN MaintainTypeList;
POOL_TYPE PoolType;
ULONG DefaultPagedPoolCharge;
ULONG DefaultNonPagedPoolCharge;
PVOID DumpProcedure;
PVOID OpenProcedure;
PVOID CloseProcedure;
PVOID DeleteProcedure;
PVOID ParseProcedure; //一般对象钩子 Hook 的函数
PVOID SecurityProcedure;
PVOID QueryNameProcedure;
PVOID OkayToCloseProcedure; //sudami要hook的函数
} OBJECT_TYPE_INITIALIZER, *POBJECT_TYPE_INITIALIZER;

  在这个重要的结构中就有我们Object Hook的目标函数了。在很多操作对象的函数中调用了很多的这些函数,这里我们可以Hook这个点,来达到保护的目的。

  在系统中有很多的对象,文件对象,注册表对象,进程对象,线程对象等等,每个对象在全局都有一个OBJECT_TYPE结构,而且所有对象类型一样的对象的OBJECT_TYPE都是一样的。

  这是进程对象的,其名称是Process,类型为IoProcessObjectType

  下面是文件对象的对象类型,类型名为File,对象类型为IoFileObjectType,一般进程的绝对路径都是遍历其句柄表,获得文件对象,然后根据IoQueryFileDosDeviceName函数获得绝对地址,这里深入讨论这个。

  总结上面的就是OBJECT_HEADER 中有两个重要结构一个是OBJECT_TYPE,一个是Body

  我们可以根据Body->OBJECT_HEADER->OBJECT_TYPE->TypeInfo 就可以替换里面的函数达到ObjectHook了

  0x04 ObjectHook

  我们看一下一般常见的ObjectHook,下面是进程对象的Object Hook

VOID HookProcessObjectType()
{
ULONG pid;
pid = ; //这里为要传入保护的进程ID PsLookupProcessByProcessId(pid,&eProcess);//得到进程对象
_asm{
push eax
mov eax,eProcess
mov eax,[eax-0x10]//这里eProcess-0x18 得到ObjectHeader
                  //ObjectHeader+0x8 得到OBJECT_TYPE
mov pEprocesstType,eax//得到ObjectType
pop eax
}
OldParseProcess = pEprocesstType->TypeInfo.ParseProcedure;
if (!MmIsAddressValid(OldParseProcess))
{
return ;
}
pEprocesstType->TypeInfo.ParseProcedure = fakeParseProcess; //替换其中的ParseProcedure
return ; }
//我们的fake函数
NTSTATUS fakeParseProcess(PVOID Object)
{
PEPROCESS kProcess;
NTSTATUS status;
kProcess = (PEPROCESS)Object;
//和我们要保护的进程名称比较,如果要操作的进程为要保护的进程
//则直接返回
if (strstr((char*)((PUCHAR)kProcess+0x174),(char*)((PUCHAR)eProcess +0x174))==)
{
return ;
}
//调用原来的函数
_asm
{
push eax
push Object
call OldParseProcess
mov status,eax
pop eax
}
return status; }

  下面是注册表对象的Object Hook

VOID HookRegObjectType()
{
HANDLE RegKeyHandle ;
OBJECT_ATTRIBUTES oba;
UNICODE_STRING RegPath;
NTSTATUS status;
PVOID KeyObject; RegKeyHandle = ; RtlInitUnicodeString(&RegPath,L"\\Registry\\Machine\\System"); InitializeObjectAttributes(&oba,&RegPath,OBJ_CASE_INSENSITIVE|OBJ_KERNEL_HANDLE,,); status = ZwOpenKey(&RegKeyHandle,KEY_QUERY_VALUE,&oba); if (!NT_SUCCESS(status))
{
DbgPrint("ZwOpenKey Error!\n");
return ;
}
//通过键值句柄得到对象
status = ObReferenceObjectByHandle(RegKeyHandle,GENERIC_READ,
NULL,
KernelMode,
&KeyObject, );
if (!NT_SUCCESS(status))
{
DbgPrint("ObReferenceObjectByHandle Error!\n");
ObDereferenceObject(KeyObject);
NtClose(RegKeyHandle);
return ;
} //和上面一样获得对象类型
__asm
{
push eax
mov eax,KeyObject
mov eax,[eax-0x10]
mov CmpKeyObjectType,eax
pop eax
}
//保存原始的函数
OldParseKey = CmpKeyObjectType->TypeInfo.ParseProcedure; if (!MmIsAddressValid(OldParseKey))
{
ObDereferenceObject(KeyObject);
ZwClose(RegKeyHandle);
return ;
}
//替换成我们的函数
CmpKeyObjectType->TypeInfo.ParseProcedure = (ULONG)FakeParseKey; ObDereferenceObject(KeyObject);
ZwClose(RegKeyHandle);
return ; }
//fake函数
NTSTATUS FakeParseKey(POBJECT_DIRECTORY RootDirectory,
POBJECT_TYPE ObjectType,
PACCESS_STATE AccessState,
KPROCESSOR_MODE AccessCheckMode,
ULONG Attributes,
PUNICODE_STRING ObjectName,
PUNICODE_STRING RemainingName,
PVOID ParseContext ,
PSECURITY_QUALITY_OF_SERVICE SecurityQos ,
PVOID *Object)
{
NTSTATUS stat ;
WCHAR Name[];
RtlCopyMemory(Name , ObjectName->Buffer , ObjectName->MaximumLength );
_wcsupr(Name);
if (wcsstr(Name , L"RUN"))
{
//检查是不是要保护的注册表键
return STATUS_OBJECT_NAME_NOT_FOUND ;
}
DbgPrint("Key"); //这里我打印一句话
//在调试的过程中,基本上没走一个函数都会打印很多次,说明注册表对象的操作特别频繁
//调用原来的函数
__asm
{
push eax
push Object
push SecurityQos
push ParseContext
push RemainingName
push ObjectName
push Attributes
movzx eax, AccessCheckMode
push eax
push AccessState
push ObjectType
push RootDirectory
call OldParseKey
mov stat, eax
pop eax
}
return stat ;
}

  下面是文件对象的Object Hook

VOID HookFileObjectType()
{
OBJECT_ATTRIBUTES oba;
NTSTATUS status;
UNICODE_STRING filePath;
HANDLE hFile;
IO_STATUS_BLOCK iostatus;
PVOID FileObject; RtlInitUnicodeString(&filePath,L"\\??\\C:"); InitializeObjectAttributes(&oba,&filePath,OBJ_CASE_INSENSITIVE|OBJ_KERNEL_HANDLE,,); ZwOpenFile(&hFile,GENERIC_ALL,&oba,&iostatus,FILE_SHARE_READ | FILE_SHARE_WRITE,FILE_SYNCHRONOUS_IO_NONALERT); status = ObReferenceObjectByHandle(hFile,GENERIC_READ,NULL,KernelMode,&FileObject,); if (!NT_SUCCESS(status))
{
ObDereferenceObject(FileObject);
ZwClose(hFile);
return;
} __asm{
push eax;
mov eax,FileObject
mov eax,[eax - 0x10]
mov pFileType,eax
pop eax
} OldParseFile = pFileType->TypeInfo.ParseProcedure;
if (!MmIsAddressValid(OldParseFile))
{
ObDereferenceObject(FileObject);
ZwClose(hFile);
return ;
} pFileType->TypeInfo.ParseProcedure = (ULONG)fakeFileParseProcedure; ObDereferenceObject(FileObject); ZwClose(hFile);
return;
} NTSTATUS fakeFileParseProcedure( IN PVOID ParseObject,
IN PVOID ObjectType,
IN PACCESS_STATE AccessState,
IN KPROCESSOR_MODE AccessMode,
IN ULONG Attributes,
IN OUT PUNICODE_STRING CompleteName,
IN OUT PUNICODE_STRING RemainingName,
IN OUT PVOID Context OPTIONAL,
IN PSECURITY_QUALITY_OF_SERVICE SecurityQos OPTIONAL,
OUT PVOID *Object)
{
NTSTATUS ntStatus ;
PVOID NamePool;
ntStatus = STATUS_SUCCESS;
if (RemainingName->Buffer)
{
NamePool = ExAllocatePool(NonPagedPool,RemainingName->Length + );
if (NamePool)
{
RtlCopyMemory(NamePool,RemainingName->Buffer,
RemainingName->Length + );
_wcsupr((wchar_t*)NamePool);
if (wcsstr((wchar_t*)NamePool,L"TEST.TXT"))
{
ExFreePool(NamePool);
return STATUS_ACCESS_DENIED;
}
}
}
__asm{
push eax
push Object
push SecurityQos
push Context
push RemainingName
push CompleteName
push Attributes
push AccessMode
push AccessState
push ObjectType
push ParseObject
call OldParseFile
mov ntStatus,eax
pop eax
}
return ntStatus;
}

  自己Hook的要还原就是将保存的原始函数地址 替换回去就行了。

  至于对于别人的Object Hook的检测与恢复我目前想到的就是通过IopCreateObjectTypes()函数在赋值的时候,将其赋值的函数地址获得,经过比较看是否被替换了。至于刚开始没有赋值的函数,我们可以通过调试的时候找出函数原型,然后获得地址比较。这部分我还没时间做。

  0x05 sudami大牛对NtClose函数中对ObjectHook的探索

  下面是sudami对于NtClose()函数中对Object Hook的探索过程,我只是个学习者,虽然这个方法最后没成功,因为要Hook的那个函数是没有初始化的,只有用到的时候才会赋值。

  首先用IDA分析NtClose函数

  这里直接进入ObpCloseHandle函数

  这里就判断是否是内核句柄,内核句柄就是系统进程创建的对象的句柄,应该去除句柄的KERNEL_HANDLE_FLAG标志,然后Attach到System进程,在其句柄表中查找

  附着到system进程,然后返回ObpCloseHandle函数

  下面就是通过ExMapHandleToPointer函数获得系统句柄表,
  在通过ObpCloseHandleEntry函数关闭句柄

  我们进入ObpCloseHandleEntry函数首先根据您对象体,获得对象头,然后偏移8的地方是对象类型
  对象体的低三位要去掉,才是真正的对象体,减去偏移就是对象头

  这里判断了对象类型结构体里面的OkayToCloseProcedure函数是否存在

   这里就调用OkayCloseProcedure函数

  我们hook了OkayCloseProcedure函数就可以让其直接走到 loc_51D1F6 的地方

  这里就是对于NtClose中Object Hook的思路。

  我们的函数原型是

 typedef NTSTATUS
(NTAPI *OB_OKAYTOCLOSE_METHOD)(
IN PEPROCESS Process OPTIONAL,
IN PVOID Object,
IN HANDLE Handle,
IN KPROCESSOR_MODE AccessMode      );

  下面是安装Hook过程

void InstallHook()
{
NTSTATUS status;
PVOID CureentProcess; CureentProcess = (PVOID)PsGetCurrentProcess();
__asm
{
push eax
mov eax,CureentProcess
mov eax,[eax-0x10]
mov EprocessObjectType,eax
pop eax
} DbgPrint("Eprocess Object Type :%08x \n" , EprocessObjectType ); Old_OkayToCloseProcedure = EprocessObjectType->TypeInfo.OkayToCloseProcedure; DbgPrint("Eprocess OkayToCloseProcedure routine :%08x \n ", Old_OkayToCloseProcedure );
DbgPrint("DeleteProcedure routine :%08x \n ",
EprocessObjectType->TypeInfo.DeleteProcedure); if (!MmIsAddressValid(Old_OkayToCloseProcedure)) {
DbgPrint("!MmIsAddressValid");
return ;
} EprocessObjectType->TypeInfo.OkayToCloseProcedure = fake_OkayToCloseProcedure;
g_bObjectHook = TRUE; return ;
} NTSTATUS
fake_OkayToCloseProcedure(
PEPROCESS Process OPTIONAL,
PVOID Object,
HANDLE Handle,
KPROCESSOR_MODE AccessCheckMode
)
{
NTSTATUS stat ;
PVOID ProcessObject; stat = ObReferenceObjectByHandle(Handle,
GENERIC_READ,
NULL,
KernelMode,
&ProcessObject,
); if (!NT_SUCCESS( stat )) {
dbg("ObReferenceObjectByHandle failed!\n");
goto _orig_;
} // 若操作的对象是我们关心的进程,且是其他进程在操作
// 拒绝之
if ( (DWORD)g_target_eprocess == (DWORD)ProcessObject &&
(DWORD)g_target_eprocess != (DWORD)Process ) { DbgPrint("%d :denny it \n", (DWORD)Process);
return ;
} _orig_:
__asm
{
push eax
movzx eax, AccessCheckMode
push eax
push Handle
push Object
push Process
call Old_OkayToCloseProcedure mov stat, eax
pop eax
} return stat ;
}

  这里理论上都是对的,可是系统对于这个函数是没有初始化的。

  可以看到这个函数是空的。

ObjectHeader、ObjectType和ObjectHook的学习的更多相关文章

  1. [4]Windows内核情景分析---内核对象

    写过Windows应用程序的朋友都常常听说"内核对象"."句柄"等术语却无从得知他们的内核实现到底是怎样的, 本篇文章就揭开这些技术的神秘面纱. 常见的内核对象 ...

  2. 《Django By Example》第六章 中文 翻译 (个人学习,渣翻)

    书籍出处:https://www.packtpub.com/web-development/django-example 原作者:Antonio Melé (译者注:无他,祝大家年会都中奖!) 第六章 ...

  3. iOS学习笔记(01) - 泛型

    决定新开一坑,在不断学习的同时分享自己的学习历程给大家,既是对自己学习的记录,又希望能对大家提供些微的帮助. 这一篇文章主要来介绍泛型的意义.使用与声明方法等. 1.泛型:限制类型 1.1.泛型使用场 ...

  4. Redis学习笔记(二)-key相关命令【转载】

    转自 Redis学习笔记(二)-key相关命令 - 点解 - 博客园http://www.cnblogs.com/leny/p/5638764.html Redis支持的各种数据类型包括string, ...

  5. Redis 学习(一) —— 安装、通用key操作命令

    一.Redis介绍 1.介绍 通常,在系统中,我们会把数据交由数据库来存储,但传统的数据库增删查改的性能较差,且比较复杂.根据 80/20 法则,百分之八十的业务访问集中在百分之二十的数据上.是否可以 ...

  6. NetSNMP开源代码学习——小试牛刀

    原创作品,转载请注明出处,严禁非法转载.如有错误,请留言! email:40879506@qq.com 题外话:技术越是古董级的东西,越是值得学习. 一. 配置 参考: http://www.cnbl ...

  7. fabric私密数据学习笔记

    fabric私密数据学习笔记 私密数据分为两部分 一个是真正的key,value,它被存在 peer的私密数据库(private state)中. 另一部分为公共数据,它是真实的私密数据key,val ...

  8. SNMP学习笔记之SNMP 原理与实战详解

    原文地址:http://freeloda.blog.51cto.com/2033581/1306743 原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法 ...

  9. SNMP学习笔记之SNMP介绍,OID及MIB库

    1.1.    SNMP概览 SNMP的基本知识介绍简单网络管理协议(SNMP-Simple Network Management Protocol)是一个与网络设备交互的简单方法.该规范是由IETF ...

随机推荐

  1. hbuilder h5 原生socket

    在网上搜索了很多资料都不行,要么就是不能发送数据,要么就不能接收数据,使用如下的方法可以接收数据,一个一个字节接收: 有部分限制是需要明确知道要接收多少个字节,否则容易出现接收异常.. var tes ...

  2. javascript js自执行函数

    javascript  自执行函数 一.自执行函数几种写法: 写法一: ( function(){ //代码 } )(); 写法二: ( function(){ //代码 }()); 二.作用: 隔离 ...

  3. 个人JS体系整理(三)

    一. 严格模式 JavaScript 严格模式(strict mode)即在严格的条件下运行.首先声明,严格模式是ES5中提出来的,准确来说就是一句指令Use strict,它的目的是指定代码在严格条 ...

  4. mysql 时间戳转换为日期

    from_unixtime(time); from_unixtime(time,'%Y%m%d'); from_unixtime(time,'%Y年%m月%d日');

  5. day3学python 字典+列表集合+文件读取

    字典+列表集合+文件读取 字典示例 ************************ 各地食品的三级菜单************************* 1.使用字典嵌套字典 2.采用死循环思路 3 ...

  6. ClickOnce发布包含某文件

    第一步.在文件上右键选择“属性”,“复制到输出目录”选择“始终复制”: 第二步.“生成操作”选择“选择”: 第三步.通过 项目右键属性-发布-应用程序文件 查看想要包含的文件是否包含进来了. 注:可以 ...

  7. linux 常用端口【转载】

    原文地址: http://blog.csdn.net/u013943420/article/details/65938696 一个计算机最多有65535个端口,端口不能重复.这65536个端口被分为两 ...

  8. Django 实现网站注册用户邮箱验证功能

    我们在很多网站上都可以看到用户注册使用电子邮件激活或启用的方式.也就是说,用户在注册后填写正确的电子邮件地址,接着网站会发送一封启用电子邮件到用户设置的电子邮件的邮箱中,并在邮件中提供一个激活或启用的 ...

  9. cf555e

    cf555e(缩点) 给一个 n 个点 m 条边的图,以及 q 对点 (s,t),让你给 m 条边定向.问是否存在一种方案,使每对点的 s 能走到 t. \(n,m,q≤ 2×10^5\). 首先,在 ...

  10. 【python】collections模块(有序字典,计数器,双向队列)

    collections模块基本介绍 我们都知道,Python拥有一些内置的数据类型,比如str, int, list, tuple, dict等, collections模块在这些内置数据类型的基础上 ...