这个是武汉科锐 第三阶段 的分析项目,每个同学负责不同的模块。在此我将我所分析的模块发出来。
声明:由于本人能力有限,文中必然有错漏之处,恳请读者不吝赐教。
因为这个函数比较简单,都是直线下来的,所以没有画流程图!
ObCreateObject它的作用是创建指定类型(OBJECT_TYPE)的对象示例。
堆是系统数据动态申请及释放的地方,WinNT把堆叫做pool。根据是否可以换出,分为PagedPool和NonPagedPool。
如果系统频繁的申请和释放内存,开销很大.于是WinNT利用LookasideList做缓冲,内存释放时并不是直接到pool,而是放到LookasideList中,等申请内存时,先检查表中是否有合适大小的快,若有就直接使用.系统定时检查表项的数目,以保持pool有足够的可用内存。
可以将Lookaside对象想像成一个内存容器。在初始的时候,它先向Windows申请了一块比较大的内存。以后程序员每次申请内存的时候,不是直接向Windows申请内存,而是向Lookaside对象申请内存。Lookaside对象会智能地避免产生内存“空洞”。如果Lookaside对象内部的内存不够用时,它会向操作系统申请更多的内存。当Lookaside对象内部有大量的末使用的内存时,它会自动让Windows回收一部分内存。总之,Lookaside是一个自动的内存分配容器,通过对Lookaside对象申请内存,效率要高于直接向Windows申请内存。
看实现代码之前我先大概的描述一下这个函数的工作流程吧!
ObCreateObject内部通过调用 ObpAllocateObject为对象申请空间。在调用ObpAllocateObject调用之前ObCreateObject函数做一些相关信息的收集以及判断,ObpAllocateObject函数返回通过参数返回其起始地址(即&Header->Body),同时函数里面还做一些对象本身的初始化工作,申请成功后,如果要求对象成为“永久对象”,即具有OB_FLAG_PERMANENT_OBJECT标志的话,再检查是否为系统的安全机制所允许,如果不允话的话,还要把刚刚申请的空间释放掉,同时返回一个错误代码STATUS_PRIVILEGE_NOT_HELD。
整个函数就是完成一个目标对象的创建工作。
下面先来看ObCreateObject函数
这个函数有9个参数,但是有一个没有使用,所以IDA里面没有标识出来。参数如下:
ProbeMode = byte ptr 8 ObjectType = dword ptr 0Ch ObjectAttributes= dword ptr 10h ObjectHeader = byte ptr 14h ObjectBodySize = dword ptr 1Ch PagedPoolCharge = dword ptr 20h NonPagedPoolCharge= dword ptr 24h arg_Object = dword ptr 28h
凡是创建对像的系统调用,都要提供至少二个输入参数。其中这定是DesiredAccess,说明所创建对象的访问模式,例如"只读"等。另一个是ObjectAttributes,这是一个OBJECT_ATTRIBUTES结构指针,OBJECT_ATTRIBUTES是一个很重要的数据结构。
0:000> dt _OBJECT_ATTRIBUTES ntdll!_OBJECT_ATTRIBUTES +0x000 Length : Uint4B +0x004 RootDirectory : Ptr32 Void +0x008 ObjectName : Ptr32 _UNICODE_STRING +0x00c Attributes : Uint4B +0x010 SecurityDescriptor : Ptr32 Void +0x014 SecurityQualityOfService : Ptr32 Void
另一个字段Attributes是一些表示各种属性的标志位,这些标志位连同这里别的字段最后都进入目标对像的OBJECT_CREATE_INFORMATION数据结构。
继续回到ObCreateObject函数上来。
mov ebx, large fs:20h ; 取得_KPRCB地址 push esi mov esi, [ebx+5F8h] ; 取得_KPRCB的PPLookasideList[3]中指向后一个节点的值;_PP_LOOKASIDE_LIST inc [esi+GENERAL_LOOKASIDE.TotalAllocates] ; _GENERAL_LOOKASIDE.TotalAllocates push edi ; 保存环镜,还不是传参 mov ecx, esi ; 通过寄存器ecx传参 call @ExInterlockedPopEntrySList@8 ; 删除单向链表互锁操作 mov edi, eax ; eax的值为指向previously _GENERAL_LOOKASIDE test edi, edi jnz short FillObjectCreateInformation inc [esi+GENERAL_LOOKASIDE.anonymous_0.AllocateHits] mov esi, [ebx+5FCh] ; 取得_KPRCB的PPLookasideList[3]中指向前一个节点的值;_PP_LOOKASIDE_LIST inc [esi+GENERAL_LOOKASIDE.TotalAllocates] mov ecx, esi call @ExInterlockedPopEntrySList@8 ; 删除单向链表互锁操作 mov edi, eax test edi, edi jnz short FillObjectCreateInformation push [esi+GENERAL_LOOKASIDE.Tag] ; ULONG inc dword ptr [esi+GENERAL_LOOKASIDE.anonymous_0] push [esi+GENERAL_LOOKASIDE.Size] ; SIZE_T push [esi+GENERAL_LOOKASIDE.Type] ; POOL_TYPE call [esi+GENERAL_LOOKASIDE.Allocate] ;通常放 ExAllocatePoolWithTag ; 还有一个Free释放函数,通常放 ExFreePool mov edi, eax ; 这里判断有没有申请空间成功 test edi, edi jnz short FillObjectCreateInformation mov ebx, 0C000009Ah ; STATUS_INVALID_PARAMETER ; 如果没有申请成功,跳到函数末 ; 尾部有个将ebx给eax的操作,ebx为返回值 jmp ObCreateObjectEnd
上段反汇编识别来自ObpAllocateObjectCreateInfoBuffer,但是在编译之后已经内联进来了。
如果申请空间成功的话,这里再通过调用ObpCaptureObjectCreateInformation函数,做一个填充结构体的工作。
mov esi, [ebp+ObjectType] push 0 push edi ; ObjectCreateInfo lea eax, [ebp+CapturedObjectName] push eax push [ebp+ObjectAttributes] push dword ptr [ebp+ObjectHeader] push dword ptr [ebp+ProbeMode] push esi ; ObjectType call ObpCaptureObjectCreateInformation
ObpCaptureObjectCreateInformation有7个参数,但是第一个忽略了,所以IDA中略过了。
ProbeMode = byte ptr 0Ch CreatorMode = byte ptr 10h ObjectAttributes= dword ptr 14h CapturedObjectName= dword ptr 18h ObjectCreateInfo= dword ptr 1Ch arg_18 = dword ptr 20h
xor eax, eax mov ebx, [ebp+ObjectCreateInfo] mov edi, ebx rep stosd ; 将ObjectCreateInformation结构体清零
mov eax, [ebp+ObjectAttributes] cmp eax, esi
后面还有判断ObjectName参数是否为空,不为空的话,调用ObpCaptureObjectName函数给相关成员赋值,否则直接赋空值。
在ObpCaptureObjectName中,有一系列的判断,比如Unicode字符串的长度不是偶数倍数或者不是以零结束,最后再通过ObpAllocateObjectNameBuffer分配空间,申请成功再调用RtlCopyMemory将字符串拷备过来。
ObpAllocateObjectNameBuffer函数的第二个参数给申请空间一些建议,如果值为真,首先从Lookaside列表中申请,如果这里申请不成功,再去非分页中申请,如果这个参数为假,或者是申请的长度过大,则直接通过ExAllocatePoolWithTag函数去非分页中申请。
好了,继续回ObCreateObject函数上面来。
当上面的二个操作成功之后,就到了我们真正为对象分配空间的时候了。
首先前面是一些检查。
mov eax, [esi+OBJECT_TYPE.TypeInfo.InvalidAttributes] test [edi+_OBJECT_CREATE_INFORMATION.Attributes], eax
此外,还需要在ObjectCreateInfo中补充收集一些附加的信息,其实只是目标对像对于内存的消耗,包括PagedPoolCharge和NonPagedPoolCharge两项。前者是对于可换出页面的消耗,后者是对于不可换出页面的消耗。
mov ecx, [ebp+PagedPoolCharge] ;属性没有冲突,就填写Charge test ecx, ecx ; 判断是否为0 jnz short FillNonPagedPoolCharge mov ecx, [esi+OBJECT_TYPE.TypeInfo.DefaultPagedPoolCharge] ; FillNonPagedPoolCharge: mov eax, [ebp+NonPagedPoolCharge] test eax, eax jnz short AllocObjectCreateInformation mov eax, [esi+OBJECT_TYPE.TypeInfo.DefaultNonPagedPoolCharge] AllocObjectCreateInformation: mov [edi+_OBJECT_CREATE_INFORMATION.NonPagedPoolCharge], eax
接着调用ObpAllocateObjec为对象真正的分配空间。这里我们先跳过这个函数将着往后面看。
调用完ObpAllocateObjec之后,判断一下是否申请对象成功。
call ObpAllocateObject mov ebx, eax test ebx, ebx jl ReleaseObjectCreateInformation
目标对象的数据结构分配成功以后,通过参数Object返回其起始地址,即&Header->Body。最后如果要求目标对象成为“永久对象”,即Header->Flags中的标志位OB_FLAG_PERMANENT_OBJECT为1,则还要通过SeSinglePrivilegeCheck()检查是否为系统的安全机制所允许,如果不允许的话就要”退赔”,就是把刚分配的数据结构释放掉。并返回STATUS_PRIVILEGE_NOT_HELD错误代码。
mov edi, dword ptr [ebp+ObjectHeader] mov esi, [ebp+ arg_Object] lea eax, [edi+_OBJECT_CREATE_INFORMATION.SecurityDescriptorCharge] mov [esi], eax ;将申请的空间地址给传出参数 test byte ptr [edi+(_OBJECT_CREATE_INFORMATION+0Fh)], 10h ; ProbeMode是一个char的宽度 jz short CheckPrivilege push dword ptr [ebp+ProbeMode] ; push ds:SeCreatePermanentPrivilege.HighPart push ds:SeCreatePermanentPrivilege.LowPart ; PrivilegeValue call SeSinglePrivilegeCheck ; 如果固定的对像已经创建,则检查是否为系统的安全机制所允许 test al, al jnz short CheckPrivilege mov ecx, [esi] call ObpFreeObject mov ebx, 0C0000061h ; 060000061h == STATUS_PRIVILEGE_NOT_HELD
cmp ObpTraceEnabled, 0 jz short ObCreateObjectEnd test ebx, ebx jl short ObCreateObjectEnd push edi ; NewIrql call ObpRegisterObject ; 如果上面的那个条件都成功,执行下面的二个函数 push 1 push edi call ObpPushStackInfo jmp short ObCreateObjectEnd
整个函数的功能都比较简单。就是单纯的分配空间,然后就是目标对象本身的初始化,就完成目标对象的创建。
我们先来看看对象的内存结构:
好了,现在我们来看ObpAllocateObject函数的实现吧!
这个函数有 6个参数,参数列表如下:
POBJECT_CREATE_INFORMATION ObjectCreateInfo, KPROCESSOR_MODE OwnershipMode, POBJECT_TYPE ObjectType OPTIONAL, PUNICODE_STRING ObjectName, ULONG ObjectBodySize, POBJECT_HEADER *ReturnedObjectHeader
下面我们来看函数的实现:
mov eax, [ebp+arg_ObjectCreateInfo] push ebx push esi mov esi, [ebp+arg_HandleInfoSize] xor ecx, ecx cmp eax, ecx ; 这里判断一下ObjectCreateInfo参数是否为空 ; 空的话使用默认值,空的话也说明是一个类型对像 ; 默认有OBJECT_HEADER_NAME_INFO、OBJECT_HEADER_NAME_INFO可选头部 push edi mov edi, [ebp+CreatorInfoSize] ; 这里原来是ObjectName参数,用来编译器优化,用于它用了,后来用来做变量了 jnz short cmpDefaultQuota push 10h pop ebx ; CreatorInfoSize等于10 mov [ebp+var_QuotaInfoSize], ecx mov [ebp+arg_HandleInfoSize], ecx mov [ebp+CreatorInfoSize], ebx jmp short ComputerHeaderSize:
INFO可选头部。
如果不为空的话,我们继续往下看:
mov edx, [eax+_OBJECT_CREATE_INFORMATION.PagedPoolCharge] ; cmp edx, [esi+OBJECT_TYPE.TypeInfo.DefaultPagedPoolCharge] jnz short cmpPsInitialSystemProcess mov edx, [eax+_OBJECT_CREATE_INFORMATION.NonPagedPoolCharge] cmp edx, [esi+OBJECT_TYPE.TypeInfo.DefaultNonPagedPoolCharge] jnz short cmpPsInitialSystemProcess cmp [eax+_OBJECT_CREATE_INFORMATION.SecurityDescriptorCharge], 800h ; SE_DEFAULT_SECURITY_QUOTA jbe short loc_525BD5 ; OBJ_EXCLUSIVE ; 判断是否允许别的进程打开 cmpPsInitialSystemProcess: ; mov edx, large fs:124h mov edx, [edx+38h] cmp edx, PsInitialSystemProcess ; 判断是不是系统初始化进程 jnz short HaveQuotaInfo ; 给QuotaInfo留空间 loc_525BD5: test byte ptr [eax+_OBJECT_CREATE_INFORMATION.Attributes], 20h ; 判断是否允许别的进程打开 mov [ebp+var_QuotaInfoSize], ecx ; ecx 为零值 jz short loc_525BE4 HaveQuotaInfo: mov [ebp+var_QuotaInfoSize], 10h ; 给QuotaInfo留空间
这个判断是不是系统初始化进程,是的话不成立,即不用为QuotaInfoSize留空间。
mov al, [esi+OBJECT_TYPE.TypeInfo.MaintainHandleCount] ; 对象的类型决定了是否需要OBJECT_HEADER_HANDLE_INFO结构 mov ebx, [edi+UNICODE_STRING.Buffer] neg al sbb eax, eax and eax, 8 neg ebx mov [ebp+arg_HandleInfoSize], eax mov al, [esi+OBJECT_TYPE.TypeInfo.MaintainTypeList] sbb ebx, ebx and ebx, 10h neg al sbb eax, eax and eax, 10h mov [ebp+CreatorInfoSize], eax
这些有一些很有意思的编译器优化。
mov ebx, [edi+UNICODE_STRING.Buffer] neg ebx sbb ebx, ebx and ebx, 10h
好了,我们继续回到代码上面来,现在到了计算头大小的时候了。
mov eax, [ebp+CreatorInfoSize] mov ecx, [ebp+var_QuotaInfoSize] add eax, ebx ; 这里开始对头的大小进么计算 ; ebx为NameInfoSize add eax, [ebp+arg_HandleInfoSize] xor edx, edx cmp esi, edx lea ecx, [eax+ecx+18h]
接着上面的代码,我们继续分析。
jz short loc_525C26 cmp [esi+OBJECT_TYPE.TypeInfo.PoolType], edx ; NonPagedPool jz short loc_525C26 inc edx ; edx为分页类型 loc_525C26 test esi, esi mov eax, 546A624Fh ; 字符串TjbO jz short loc_525C35 ; (ObjectType==NULL?'TjbO':ObjectType->Key)|PROTECTED_POOL mov eax, [esi+OBJECT_TYPE.Key] loc_525C35: or eax, 80000000h ; (ObjectType==NULL?'TjbO':ObjectType->Key)|PROTECTED_POOL push eax ; Tag mov eax, [ebp+arg_ObjectBodySize] add ecx, eax ; HeaderSize + ObjectBodySize push ecx ; NumberOfBytes push edx ; PoolType call ExAllocatePoolWithTag
接着下面又再次判断ObjectType是否为空,其实我觉得这里可以优化一下的,因为刚刚才比较过的,如果为空的话把Tag”TjbO”赋给eax,否则将OBJECT_TYPE.Key值给eax,最后再把eax和80000000h(PROTECTED_POOL)做个与的运算。
然后先最后一个参数压进栈之后,再计算一个申请空间的大小,因为我们刚刚计算的头大小中,并没有算上Body的,所以这里他做了一个加法,最后将其它的二个参数压进栈,完成函数的调用。
继续往下看代码:
test eax, eax ; 这里判断有没有申请成功 jnz short AllocObjectSuccess mov eax, 0C000009Ah ; STATUS_INSUFFICIENT_RESOURCES jmp FunctionEnd
成功的代码比较长,但是比较简单,都是一些相关结构体内成员的赋值。
mov ecx, [ebp+var_QuotaInfoSize] test ecx, ecx jz short cmpHandleInfoSize mov edx, [ebp+arg_ObjectCreateInfo] mov edx, [edx+_OBJECT_CREATE_INFORMATION.PagedPoolCharge] mov [eax+_OBJECT_HEADER_QUOTA_INFO.PagedPoolCharge], edx mov edx, [ebp+arg_ObjectCreateInfo] mov edx, [edx+_OBJECT_CREATE_INFORMATION.NonPagedPoolCharge] mov [eax+_OBJECT_HEADER_QUOTA_INFO.NonPagedPoolCharge], edx mov edx, [ebp+arg_ObjectCreateInfo] mov edx, [edx+_OBJECT_CREATE_INFORMATION.SecurityDescriptorCharge] and dword ptr [eax+0Ch], 0 ; QuotaInfo->ExclusiveProcess mov [eax+_OBJECT_HEADER_QUOTA_INFO.SecurityDescriptorCharge], edx add eax, 10h
注意后面的add eax, 10h。这有助于我们了解他的内存结构。
cmp [ebp+arg_HandleInfoSize], 0 jz short cmpNameInfoSize and [eax+_OBJECT_HEADER_HANDLE_INFO.anonymous_0.SingleEntry.HandleCount], 0 add eax, 8
这里后面是加8了,说明OBJECT_HEADER_HANDLE_INFO的大小为8,排列在_OBJECT_HEADER_QUOTA_INFO头部后面。
test ebx, ebx ; ebx为NameInfoSize,上面一直没有改过 jz short cmpCreatorInfoSize mov edx, [edi] ; edi指向ObjectName mov dword ptr [eax+_OBJECT_HEADER_NAME_INFO.Name.Length], ed mov edx, [edi+_UNICODE_STRING.Buffer] and [eax+_OBJECT_HEADER_NAME_INFO.Directory], 0 cmp [ebp+arg_OwnershipMode], 0 ;这里判断是不是内核模式 mov edi, [ebp+arg_ObjectCreateInfo] mov [eax+_OBJECT_HEADER_NAME_INFO.Name.Buffer], edx mov [eax+_OBJECT_HEADER_NAME_INFO.QueryReferences], 1 jnz short loc_525CBD test edi, edi jz short loc_525CBD test byte ptr [edi+_UNICODE_STRING.MaximumLength], 1 jz short loc_525CBD mov [eax+_OBJECT_HEADER_NAME_INFO.QueryReferences], 40000001h loc_525CBD: add eax, 10h jmp short cmp_CreatorInfoSize
PROTECTED)做个或运算,最后eax再加10h。
mov edi, [ebp+arg_ObjectCreateInfo] cmp_CreatorInfoSize: cmp [ebp+CreatorInfoSize], 0 jz short loc_525CEB ; ecx等于QuotaInfoSize的值 and word ptr [eax+0Ch], 0; CreatorBackTraceIndex mov edx, large fs:124h mov edx, [edx+38h];PsGetCurrentProcess函数实现 mov edx, [edx+94h]; UniqueProcessId mov [eax+_OBJECT_HEADER_CREATOR_INFO.CreatorUniqueProcess], edx mov [eax+_OBJECT_HEADER_CREATOR_INFO.TypeList.Blink], eax mov [eax+_OBJECT_HEADER_CREATOR_INFO.TypeList.Flink], eax add eax, 10h
InitializeListHead(),这里将这个函数内联进来了。
接着eax再加10h。
这里可选头的赋值就结束了,下面是开始计算可选头的偏移了。
OBJECT_HEADER中的NameInfoOffset、HandleInfoOffset、QuotaInfoOffset是相应可选头结构距OBJECT_HEADER的偏移。
OBJECT_HEADER的地址减去相应偏移就可以找到对应结构体。
若偏移为0则说明对应结构不存在。
test ecx, ecx ; ecx等于QuotaInfoSize的值 jz short setQuotaInfoOffsetZero add cl, byte ptr [ebp+arg_HandleInfoSize] add cl, bl add cl, byte ptr [ebp+CreatorInfoSize] mov [eax+0Eh], cl ; SetQuotaInfoOffset jmp short loc_525D00 setQuotaInfoOffsetZero: mov [eax+OBJECT_HEADER.QuotaInfoOffset], 0
后面依次是计算另二个可选头的偏移(OBJECT_HEADER_CREATE_INFO可不是记偏移的哦)。
跳上那部分计算偏移的代码,我们继续往下看:
cmp [ebp+CreatorInfoSize], 0 mov [eax+OBJECT_HEADER.Flags], 1 ; OB_FLAG_NEW_OBJECT jz short loc_525D35 mov [eax+OBJECT_HEADER.Flags], 5 ; OB_FLAG_CREATOR_INFO
cmp [ebp+arg_HandleInfoSize], 0 jz short loc_525D3F or [eax+OBJECT_HEADER.Flags], 40h ; OB_FLAG_SINGLE_HANDLE_ENTRY
and [eax+OBJECT_HEADER.anonymous_0.HandleCount], 0 xor edx, edx inc edx cmp [ebp+arg_OwnershipMode], 0 mov [eax+OBJECT_HEADER.PointerCount], edx mov [eax+OBJECT_HEADER.Type], esi ; esi指向objectType
jnz short loc_525D55 ; 这里判断是不是内核模式 or [eax+OBJECT_HEADER.Flags], 2 ;
test edi, edi ; 判断ObjectCreateInfo是否为空 jz short ObjectCreateInfoIsZero test byte ptr [edi+_OBJECT_CREATE_INFORMATION.Attributes], 10h jz short loc_525D62 ; or byte ptr [eax+OBJECT_HEADER.Flags], 10h loc_525D62: test byte ptr [edi+_OBJECT_CREATE_INFORMATION.Attributes], 20h jz short ObjectCreateInfoIsZero or [eax+OBJECT_HEADER.Flags], 8
接着下一个是判断是否不允许别的进程同时打开,是的话再属性(Attributes)域中增加OB_FLAG_EXCLUSIVE_OBJECT属性。
and [eax+OBJECT_HEADER.SecurityDescriptor], 0 test esi, esi ; 判断ObjectType是否等于0 mov [eax+OBJECT_HEADER.anonymous_1.ObjectCreateInfo], edi jz short loc_525D87 lea ecx, [esi+OBJECT_TYPE.TotalNumberOfObjects] lock xadd [ecx], edx mov ecx, [ecx] cmp ecx, [esi+OBJECT_TYPE.HighWaterNumberOfObjects] jbe short loc_525D87 mov [esi+OBJECT_TYPE.HighWaterNumberOfObjects], ecx loc_525D87: j mov ecx, [ebp+arg_ObjectHeader] mov [ecx], eax ; 这里将申请的对像地址返回 xor eax, eax
接着再判断TotalNumberOfObjects的值是否大于HighWaterNumberOfObjects,是的话将TotalNumberOfObjects的值给HighWaterNumberOfObjects。
最后将申请到的地址通过参数传出。
之前提到”质检”不合格的话再将装申请的空间释放。现在我们来看ObFreeObject函数。他跟ObCreateObject很多操作都是对应的。下面我们来看代码吧。
先来看ObCreateObject是如何调用他的,下面这代码还是来自ObCreateObject:
push dword ptr [ebp+ProbeMode] ; PreviousMode push ds:SeCreatePermanentPrivilege.HighPart push ds:SeCreatePermanentPrivilege.LowPart ; PrivilegeValue call SeSinglePrivilegeCheck ;检查是否为系统的安全机制所允许 test al, al jnz short CheckPrivilege mov ecx, [esi] call ObpFreeObject
接着我们进到ObpFreeObject函数里面。
push ebp mov ebp, esp sub esp, 0Ch push esi lea esi, [ecx-18h] ; 通过ecx传参, ecx-18就得到了OBJECT_HEADER
test [esi+OBJECT_HEADER.Flags], 4 这里通过Flags来判断有没有OBJECT_CREATE_INFO,因为这个头一定在OBJECT_HEADER上面,所以没有记录偏移,而是置了一个标志位。 mov ecx, [esi+OBJECT_HEADER.Type] mov [ebp+var_ObjectType], ecx ; 取得ObjectType mov [ebp+P], esi ; 现在esi指向已知的空间最顶处
jz short NoObjectCreateInfo lea eax, [esi-10h] ; 减去ObjectCreateInfo结构的长度 jmp short loc_525DBA NoObjectCreateInfo: xor eax, eax。 loc_525DBA: test eax, eax ; 判断有没有ObjectCreateInfo头 jz short loc_525DC1 mov [ebp+P], eax ;再次将已知的起始地址保存起来。 loc_525DC1: mov al, [esi+OBJECT_HEADER.NameInfoOffset] test al, al ; 判断有没有OBJECT_HEADER_NAME_INFO头
movzx edx, al mov eax, esi sub eax, edx ; 减去OBJECT_HEADER_NAME_INFO的长度 mov [ebp+var_NameInfo], eax。 jmp short loc_525DD8 NoOBJECT_HEADER_NAME_INFO: and [ebp+var_NameInfo], 0 ; 将var_NameInfo置0 loc_525DD8: mov eax, [ebp+var_NameInfo] test eax, eax ; 判断是否有OBJECT_HEADER_NAME_INFO头 jz short loc_525DE2 mov [ebp+P], eax loc_525DE2: mov al, [esi+OBJECT_HEADER.HandleInfoOffset] test al, al ; 判断有没有OBJECT_HEADER_HANDLE_INFO头 push ebx jz short loc_525DF3 movzx eax, al mov ebx, esi sub ebx, eax
LE_INFO头偏移,得到HANDLE_INFO的起始地址。同时也是已知的数据结构的起始地址。
xor ebx, ebx
test ebx, ebx jz short loc_525DFC mov [ebp+P], ebx loc_525DFC:j mov al, [esi+OBJECT_HEADER.QuotaInfoOffset] test al, al
这里基本完成了计算了,因为这个头是在最上面的了!
test dl, 1
jz short NotNewObject mov eax, [esi+OBJECT_HEADER.anonymous_1.ObjectCreateInfo] test eax, eax jz loc_525ECB mov ecx, [eax+_OBJECT_CREATE_INFORMATION.SecurityDescriptor] test ecx, ecx jz short loc_525E4E movsx eax, [eax+_OBJECT_CREATE_INFORMATION.ProbeMode] push 1 ; char push eax ; char push ecx ; P call SeReleaseSecurityDescriptor ; 将SecurityDescriptor释放
mov eax, [esi+OBJECT_HEADER.anonymous_1.ObjectCreateInfo] and [eax+_OBJECT_CREATE_INFORMATION.SecurityDescriptor], 0
mov eax, large fs:20h ; 取得_KPRCB地址 ; 这里的这些操作和ObCreateObect相对应 mov ecx, [eax+5F8h] ; 取得_KPRCB的PPLookasideList[3]中指向后一个节点的值;_PP_LOOKASIDE_LIST mov edx, [esi+OBJECT_HEADER.anonymous_1.ObjectCreateInfo] mov di, [ecx+GENERAL_LOOKASIDE.ListHead.anonymous_0.Depth] inc [ecx+GENERAL_LOOKASIDE.TotalFrees] cmp di, [ecx+GENERAL_LOOKASIDE.Depth] jb short loc_525E89 inc [ecx+_PAGED_LOOKASIDE_LIST.L.anonymous_1.FreeHits] mov ecx, [eax+5FCh] mov ax, [ecx+GENERAL_LOOKASIDE.ListHead.anonymous_0.Depth] inc [ecx+GENERAL_LOOKASIDE.TotalFrees] cmp ax, [ecx+GENERAL_LOOKASIDE.Depth] jb short loc_525E89 inc [ecx+_PAGED_LOOKASIDE_LIST.L.anonymous_1.FreeHits] push edx ; PVOID call [ecx+_PAGED_LOOKASIDE_LIST.L.Free] jmp short loc_525EC7 loc_525E89: call @InterlockedPushEntrySList@8 jmp short loc_525EC7 NotNewObject: mov edi, [esi+OBJECT_HEADER.anonymous_1.QuotaBlockCharged]
这下面都是在为PsReturnSharedPoolQuota准备参数的一些判断。
test edi, edi ;判断一下QuotaBlockCharged是否有值, jz short loc_525ECB test eax, eax jz short loc_525EA5 mov ecx, [eax+_OBJECT_HEADER_QUOTA_INFO.SecurityDescriptorCharge] add ecx, [eax+_OBJECT_HEADER_QUOTA_INFO.PagedPoolCharge] mov eax, [eax+_OBJECT_HEADER_QUOTA_INFO.NonPagedPoolCharge] jmp short loc_525EBF loc_525EA5: test dl, 20h ; dl是flags;OB_FLAG_DEFAULT_SECURITY_QUOTA mov ecx, [ecx+OBJECT_TYPE.TypeInfo.DefaultPagedPoolCharge] ; ecx指向ObjectType jz short loc_525EB6 add ecx, 800h loc_525EB6: mov eax, [ebp+var_ObjectType] mov eax, [eax+OBJECT_TYPE.TypeInfo.DefaultNonPagedPoolCharge] loc_525EBF: push eax ; int push ecx ; int push edi ; P call PsReturnSharedPoolQuota ;
and [esi+OBJECT_HEADER.anonymous_1.ObjectCreateInfo], 0 loc_525ECB: test ebx, ebx ; ebx指向OBJECT_HEADER_HANDLE_INFO pop edi jz short loc_525EE2 test [esi+OBJECT_HEADER.Flags], 40h ; OB_FLAG_SINGLE_HANDLE_ENTRY jnz short loc_525EE2 push 0 ; Tag push [ebx+_OBJECT_HEADER_HANDLE_INFO.anonymous_0.HandleCountDataBase] call ExFreePoolWithTag
And [ebx+_OBJECT_HEADER_HANDLE_INFO.anonymous_0.HandleCountDataBase], 0 loc_525EE2: j mov eax, [ebp+var_NameInfo] test eax, eax pop ebx jz short loc_525F00 mov eax, [eax+_OBJECT_HEADER_NAME_INFO.Name.Buffer]
test eax, eax jz short loc_525F00 push 0 ; Tag push eax ; P call ExFreePoolWithTag mov eax, [ebp+var_NameInfo] and [eax+_OBJECT_HEADER_NAME_INFO.Name.Buffer], 0 loc_525F00: mov eax, [ebp+var_ObjectType] ; 看下有没有OBJECT_YTPE结构。 test eax, eax mov [esi+OBJECT_HEADER.Type], 0BAD0B0B0h
pop esi jnz short loc_525F16 mov eax, 546A624Fh jmp short loc_525F1C loc_525F16: mov eax, [eax+OBJECT_TYPE.Key]
or eax, 80000000h
push eax ; Tag push [ebp+P] ; P call ExFreePoolWithTag
平台环镜:
Windows XP SP2 + VM6.5 + WRK1.2 + Windbg + IDA5.5
参考文献
[1] 毛德操 著 《Windows 内核情景分析--采用开源代码ReactOS》 [M]电子工业出版社 2009 年5月
[2] 张帆 史彩成 著《Windows驱动开发技术详解》 [M]电子工业出版社 2008年7月
还有几天就要毕业了,在此多谢钱方师,方老师,岳老师,赵老师,在这将近一年的时间里,对我们的教导,不仅仅是技术上,还有为人处事等等,同时还有多谢科锐的同学,是你们的陪伴,让我在这期间过得很充实,同时还有在专业学习上的交流以及指导。非常感谢。