内核对象HOOK
程序员文章站
2022-03-30 12:32:18
...
内核对象HOOK
内核对象的结构
首先我们来了解一下内核对象的基本结构,每一个内核对象都是由对象头和对象体组成,对象头都是一样的,对象体 不同的内核对象是不一样的。该结构体请参考内核结构体。
//0x20 bytes (sizeof)
struct _OBJECT_HEADER
{
LONG PointerCount; //0x0 //对象的指针计数
union
{
LONG HandleCount; //0x4 //对象的引用计数
VOID* NextToFree; //0x4
};
struct _EX_PUSH_LOCK Lock; //0x8
UCHAR TypeIndex; //0xc //对象类型
UCHAR TraceFlags; //0xd
UCHAR InfoMask; //0xe
UCHAR Flags; //0xf
union
{
struct _OBJECT_CREATE_INFORMATION* ObjectCreateInfo; //0x10
VOID* QuotaBlockCharged; //0x10
};
VOID* SecurityDescriptor; //0x14
struct _QUAD Body; //0x18 //对象ti
};
内核对象的获取
获取ObTypeIndexTable首地址, 地址在ObGetObjectType函数内部中出现,因此, 取函数首地址, 加上一定偏移就能得到该表的首地址.在win7 32系统下, 该表在函数中的0xF偏移处。
kd> u ObGetObjectType
nt!ObGetObjectType:
84291a72 8bff mov edi,edi
84291a74 55 push ebp
84291a75 8bec mov ebp,esp
84291a77 8b4508 mov eax,dword ptr [ebp+8]
84291a7a 0fb640f4 movzx eax,byte ptr [eax-0Ch]
84291a7e 8b0485c0801784 mov eax,dword ptr nt!ObTypeIndexTable (841780c0)[eax*4]
84291a85 5d pop ebp
84291a86 c20400 ret 4
内核对象HOOK
测试环境:
> windows7 32位
HOOK步骤:
>找到内核对象原型数组
>遍历数组,得到指定类型的原型对象
>修改原型对象中的函数指针
首先构造进程内核对象和函数原型。这里以Hook进程内核对象的OpenProcessDure函数为例,以打开记事本展开实验。
//内核对象
//0x88 bytes (sizeof)
struct _OBJECT_TYPE
{
struct _LIST_ENTRY TypeList; //0x0
struct _UNICODE_STRING Name; //0x8
VOID* DefaultObject; //0x10
UCHAR Index; //0x14
ULONG TotalNumberOfObjects; //0x18
ULONG TotalNumberOfHandles; //0x1c
ULONG HighWaterNumberOfObjects; //0x20
ULONG HighWaterNumberOfHandles; //0x24
struct _OBJECT_TYPE_INITIALIZER TypeInfo; //0x28
struct _EX_PUSH_LOCK TypeLock; //0x78
ULONG Key; //0x7c
struct _LIST_ENTRY CallbackList; //0x80
};
//0x50 bytes (sizeof)
//内核对象的操作函数结构体
struct _OBJECT_TYPE_INITIALIZER
{
USHORT Length; //0x0
union
{
UCHAR ObjectTypeFlags; //0x2
struct
{
UCHAR CaseInsensitive:1; //0x2
UCHAR UnnamedObjectsOnly:1; //0x2
UCHAR UseDefaultObject:1; //0x2
UCHAR SecurityRequired:1; //0x2
UCHAR MaintainHandleCount:1; //0x2
UCHAR MaintainTypeList:1; //0x2
UCHAR SupportsObjectCallbacks:1; //0x2
};
};
ULONG ObjectTypeCode; //0x4
ULONG InvalidAttributes; //0x8
struct _GENERIC_MAPPING GenericMapping; //0xc
ULONG ValidAccessMask; //0x1c
ULONG RetainAccess; //0x20
enum _POOL_TYPE PoolType; //0x24
ULONG DefaultPagedPoolCharge; //0x28
ULONG DefaultNonPagedPoolCharge; //0x2c
VOID (*DumpProcedure)(VOID* arg1, struct _OBJECT_DUMP_CONTROL* arg2); //0x30
LONG (*OpenProcedure)(enum _OB_OPEN_REASON arg1, CHAR arg2, struct _EPROCESS* arg3, VOID* arg4, ULONG* arg5, ULONG arg6); //0x34
VOID (*CloseProcedure)(struct _EPROCESS* arg1, VOID* arg2, ULONG arg3, ULONG arg4); //0x38
VOID (*DeleteProcedure)(VOID* arg1); //0x3c
LONG (*ParseProcedure)(VOID* arg1, VOID* arg2, struct _ACCESS_STATE* arg3, CHAR arg4, ULONG arg5, struct _UNICODE_STRING* arg6, struct _UNICODE_STRING* arg7, VOID* arg8, struct _SECURITY_QUALITY_OF_SERVICE* arg9, VOID** arg10); //0x40
LONG (*SecurityProcedure)(VOID* arg1, enum _SECURITY_OPERATION_CODE arg2, ULONG* arg3, VOID* arg4, ULONG* arg5, VOID** arg6, enum _POOL_TYPE arg7, struct _GENERIC_MAPPING* arg8, CHAR arg9); //0x44
LONG (*QueryNameProcedure)(VOID* arg1, UCHAR arg2, struct _OBJECT_NAME_INFORMATION* arg3, ULONG arg4, ULONG* arg5, CHAR arg6); //0x48
UCHAR (*OkayToCloseProcedure)(struct _EPROCESS* arg1, VOID* arg2, VOID* arg3, CHAR arg4); //0x4c
};
//函数原型需用到该枚举变量
typedef enum _OB_OPEN_REASON {
ObCreateHandle,
ObOpenHandle,
ObDuplicateHandle,
ObInheritHandle,
ObMaxOpenReason
} OB_OPEN_REASON;
//OpenProcessDure函数原型
typedef NTSTATUS(*OB_OPEN_METHOD)(
IN ULONG Unknown,
IN OB_OPEN_REASON OpenReason,
IN PEPROCESS Process OPTIONAL,
IN PVOID Object,
IN ACCESS_MASK GrantedAccess,
IN ULONG HandleCount
);
// 声明函数 获取内核对象表地址
PULONG _declspec(dllimport) ObGetObjectType(PVOID);
//用来保存系统进程对象地址
POBJECT_TYPE g_pFileObjTypeProcess;
//用来保存原来系统打开进程内核对象的函数
OB_OPEN_METHOD g_oldObjectParseProcess;
//定义自己的打开进程内核对象函数
NTSTATUS MyIopParseProcess(
IN ULONG Unknown,
IN OB_OPEN_REASON OpenReason,
IN PEPROCESS Process OPTIONAL,
IN PVOID Object,
IN ACCESS_MASK GrantedAccess,
IN ULONG HandleCount)
{
//打开进程的名称保存的Object的偏移位0x16c位置,比较进程名
if (
RtlCompareMemory(((char*)Object + 0x16c), "notepad.exe", 12) == 12
)
{
//当打开记事本程序时返回失败
return STATUS_UNSUCCESSFUL;
}
//不是则调用系统打开进程的函数
return ((OB_OPEN_METHOD)g_oldObjectParseProcess)(0, OpenReason, Process, Object, GrantedAccess, HandleCount);
}
//进程内核对象HOOK
NTSTATUS OnDeviceCtrlObjProcessHook(DEVICE_OBJECT* pDevice, IRP* pIrp)
{
//获取typeIndexTable表的位置
ULONG addr = ObGetObjectType;
OBJECT_TYPE** typeIndexTable = (OBJECT_TYPE*)*(ULONG*)(addr + 0xF);
//在表中找到文件内核对象
ULONG i = 2;
UNICODE_STRING fileTypeName;
RtlInitUnicodeString(&fileTypeName, L"Process");
POBJECT_TYPE fileObjType = NULL;
// 遍历TypeIndex表,找到File对象的原型
while (typeIndexTable[i])
{
// 判断类型名是否一致
if (0 == RtlCompareUnicodeString(&typeIndexTable[i]->Name, &fileTypeName, TRUE))
{
//保存内核对象的di'zhi
g_pFileObjTypeProcess = typeIndexTable[i];
break;
}
++i;
}
//替换函数地址
if (g_pFileObjTypeProcess)
{
//保存老函数地址
g_oldObjectParseProcess = (OB_OPEN_METHOD)g_pFileObjTypeProcess->TypeInfo.OpenProcedure;
//替换新函数地址
g_pFileObjTypeProcess->TypeInfo.OpenProcedure = (PVOID)MyIopParseProcess;
}
}
//卸载进程内核对象HOOK
NTSTATUS OnDeviceCtrlUnloadProHook(DEVICE_OBJECT* pDevice, IRP* pIrp)
{
if (g_pFileObjTypeProcess)
{
_asm sti; // 屏蔽中断
// 将原始函数还原回去
g_pFileObjTypeProcess->TypeInfo.OpenProcedure = g_oldObjectParseProcess;
_asm cli; // 允许中断
}
}
实验测试
>新建文本文档,打开
>Hook 进程内核对象
>再次打开记事本失败
总结
此实验程序三环用MFC框架编写,利用设备IRP_MJ_DEVICE_CONTROL进行通讯,可以看成三环发送消息进内核,内核处理消息,这里就不多叙述了。
HOOK的关键在于找到内核对象原型数组,这里只介绍了32位win7环境下的方法。有兴趣的话可以查找其他方法。知道了以上过程,就可以对其他内核对象进行
HOOK了,方法大同小异。
上一篇: 调整内核printk的打印级别