ObQueryNameString蓝屏问题分析

The ObQueryNameString routine supplies the name, if there is one, of a given object to which the caller has a pointer.

NTSTATUS ObQueryNameString(
  _In_      PVOID                    Object,
  _Out_opt_ POBJECT_NAME_INFORMATION ObjectNameInfo,
  _In_      ULONG                    Length,
  _Out_     PULONG                   ReturnLength
);

Obeject是指向对象的指针
ObjectNameInfo是一个用于存放查询名称的缓冲区,其结构如下:

typedef struct _OBJECT_NAME_INFORMATION {
  UNICODE_STRING Name;
} OBJECT_NAME_INFORMATION, *POBJECT_NAME_INFORMATION;

Length描述了缓冲区的大小
ReturnLength描述返回的字节数
从上面的描述中可以看到ObQueryNameString的参数POBJECT_NAME_INFORMATION在指定的情况下是用于返回查询数据。根据_OBJECT_NAME_INFORMATION的结构看出,其内部就是一个UNICODE_STRING的结构 ,所以有人就直接把一个UNICODE_STRING结构的指针直接转换成了_OBJECT_NAME_INFORMATION作为参数传入,结果可想而知,当程序运行到这个位置就无情的死掉了。

这是一段很普通的查询对象名称的代码:

BOOLEAN QueryObjectName(PVOID pAnyObject, PWCHAR *OutputBufferFreeByCaller)
{
        NTSTATUS status;
        ULONG returnedLength;
        BOOLEAN foundCompleteName = FALSE;
        PUNICODE_STRING pObjectName = NULL;
        status = ObQueryNameString(pAnyObject, NULL, 0, &returnedLength);
        if(status == STATUS_INFO_LENGTH_MISMATCH)
        {
                pObjectName = ExAllocatePool(PagedPool, returnedLength);
                status = ObQueryNameString(pAnyObject, (POBJECT_NAME_INFORMATION)pObjectName, returnedLength, &returnedLength);
                if(NT_SUCCESS(status))
                {
                        *OutputBufferFreeByCaller = ExAllocatePool(PagedPool,pObjectName->Length+2);
                        RtlZeroMemory(*OutputBufferFreeByCaller,pObjectName->Length+2);
                        memcpy(*OutputBufferFreeByCaller,pObjectName->Buffer,pObjectName->Length);
                        foundCompleteName = TRUE;
                }
                ExFreePool(pObjectName);
        }
        return foundCompleteName;
}

查询FILE_OBJECT时有机会蓝屏(代码是BAD_POOL_CALLER)

1.是因为ObQueryNameString例程设置的是unicode字符串,并且它假定unicode字符串的缓冲区紧接在OBJECT_NAME_INFORMATION结构之后。 "ObQueryNameString并没有利用参数ObjectNameInfo.Name.Buffer来指出缓冲区的地址,而是简单地认为名称紧跟着OBJECT_NAME_INFORMATION结构。
解决办法(好像还是会蓝屏)

{
ULONG ReturnLength;
UCHAR Buffer[sizeof(OBJECT_NAME_INFORMATION)+(256*sizeof(WCHAR))];
POBJECT_NAME_INFORMATION ObjectNameInfo = (POBJECT_NAME_INFORMATION)Buffer;


ObQueryNameString(pDeviceObject, ObjectNameInfo, sizeof(Buffer), &ReturnLength);
}
  1. 经过翻阅WRK的代码查阅到了ObQueryNameString的实现部分,也弄清楚了为什么不能直接传入UNICODE_STRING的指针。如下代码所示,是WRK中对于传入的_OBJECT_NAME_INFORMATION指针进行的处理。
//其中ObjectNameInfo 也就是传入的_OBJECT_NAME_INFORMATION 缓冲区指针
t = (PWCHAR)(ObjectNameInfo + 1);
//.......此处省略中间的代码,此部分代码位于WRK \base\ntos\config的123行
//.....
//这一步是将Name的buffer直接指向了t
ObjectNameInfo->Name.Buffer = t

看到这里原因已经很清晰了,传入的缓冲区应当是一个连续的缓冲区,且头部是一个UNICODE_STRING类型,UNICODE_STRING的Buffer直接指向了缓冲区下面的部分,所以有很多人直接传入了一个UNICODE_STRING却错误的原因,下面给出一段通过注册表Object获取路径代码。

//必须要在DISPATCH_LEVEL 之下
if (KeGetCurrentIrql() >= DISPATCH_LEVEL) {
    return FALSE;
}
//创建一个连续的缓冲区,UNICODE_STRING头部外加512个WCHAR字符
PUNICODE_STRING fileNameInfo = (PUNICODE_STRING)ExAllocatePool(PagedPool, 1024 + sizeof(UNICODE_STRING));
if (fileNameInfo == NULL) {
    return FALSE;
}
__try {
    ULONG uReturnLength = 0;
    if (!NT_SUCCESS(ObQueryNameString(pKeyObject, (POBJECT_NAME_INFORMATION)fileNameInfo, 1024 + sizeof(UNICODE_STRING),
 &uReturnLength)))
    {
        KdPrint(("错误\n"));
        ExFreePool(fileNameInfo);
        return FALSE;
    }
    else {
               //成功直接输入测试
        KdPrint(("---->%08X,%S\n", (ULONG)pKeyObject, fileNameInfo->Buffer));
        ExFreePool(fileNameInfo);
    }
}
__except (1) {
    KdPrint(("失败"));
    ExFreePool(fileNameInfo);
       return FALSE;
}
return TRUE;

ObQueryNameString源码解读

ObQueryNameString返回指定内核对象的名称。该函数只返回命名对象名称和拥有查询函数的对象名称,其他所有的对象都返回空结构。
第一步.先检查内核对象是否有专门的名称查询函数QueryNameProcedure函数,有则调用此函数并返回。WIXP中只有KEY和FILE对象有专门的名称查询函数。
第二步.检查是否为非命名对象或者命名对象的名称为NULL, 若是检查缓冲区大小后则返回空结构(ObjectNameInfo.Name为{0,0,0})。
第三步.对于命名对象,追根溯源直到根目录,获得需要的缓冲区的大小。
第四步.若用户提供的缓冲区大小不够,则返回STATUS_INFO_LENGTH_MISMATCH,并返回实际需要的缓冲区大小。
第五步.再次追根溯源到根目录,将名称拷贝至用户缓冲区中。

/*++


Routine description:
    This routine processes a query of an object's name information
Arguments:


    Object - Supplies the object being queried,内核对象的指针,该值不能为NULL.


    ObjectNameInfo - Supplies the buffer to store the name string nformation
        一个由用户提供的存放返回值得缓冲区,若不知大小则可以为NULL,由ReturnLength返回需要的缓冲区大小。


    Length - Specifies the length, in bytes, of the original object name info buffer.
        缓冲区的字节数.该值必须包括OBJECT_NAME_INFORMATION结构和对象名称的长度。根据DDK上推荐该值为1024。
       
    ReturnLength - Contains the number of bytes already used up in the object name info. On return this receives an updated byte count. 返回的数据的大小。此值包括OBJECT_NAME_INFORMATION结构和对象名称的长度。


        (Length minus ReturnLength) is really now many bytes are left in the output buffer.  The buffer supplied to this call may actually be offset within the original users buffer


Return Value:
    An appropriate status value
--*/
{
    NTSTATUS Status;
    POBJECT_HEADER ObjectHeader;
    POBJECT_HEADER_NAME_INFO NameInfo;
    POBJECT_HEADER ObjectDirectoryHeader;
    POBJECT_DIRECTORY ObjectDirectory;
    ULONG NameInfoSize;
    PUNICODE_STRING String;
    PWCH StringBuffer;
    ULONG NameSize;


    PAGED_CODE();


    //
    //  Get the object header and name info record if it exists
    //
    ObjectHeader = OBJECT_TO_OBJECT_HEADER( Object );      //获得Object的头部objectHeader
    NameInfo = OBJECT_HEADER_TO_NAME_INFO( ObjectHeader ); //若ObjectHeader是命名对象,则取对象头部的名称;


                                                                                                         // 否则返回NULL.


    //
    //  If the object type has a query name callback routine then that is how we get the name
    //  第一步.若对象有专门的名称查询函数,则调用该函数并返回结果。


    if (ObjectHeader->Type->TypeInfo.QueryNameProcedure != NULL) {
        try {
            KIRQL SaveIrql;


            ObpBeginTypeSpecificCallOut( SaveIrql );
            ObpEndTypeSpecificCallOut( SaveIrql, "Query", ObjectHeader->Type, Object );


            Status = (*ObjectHeader->Type->TypeInfo.QueryNameProcedure)( Object,
                                                                         (BOOLEAN)((NameInfo != NULL) && (NameInfo->Name.Length != 0)),
                                                                         ObjectNameInfo,
                                                                         Length,
                                                                         ReturnLength );
        } except( EXCEPTION_EXECUTE_HANDLER ) {
            Status = GetExceptionCode();
        }
        return( Status );
    }


    //
    //  Otherwise, the object type does not specify a query name procedure so we get to do the work.  The first thing
    //  to check is if the object doesn't even have a name.  If object doesn't have a name then we'll return an empty    


   //name info structure.  第二步.若没有名称查询函数,则查看不是命名对象或者名称为空则ObQueryNameString返回空。
   //
    if ((NameInfo == NULL) || (NameInfo->Name.Buffer == NULL)) {
        //
        //  Compute the length of our return buffer, set the output if necessary and make sure the supplied buffer is large enough
        //


        NameInfoSize = sizeof( OBJECT_NAME_INFORMATION );
        try {
            *ReturnLength = NameInfoSize;
        } except( EXCEPTION_EXECUTE_HANDLER ) {
            return( GetExceptionCode() );
        }


        if (Length < NameInfoSize) {
            return( STATUS_INFO_LENGTH_MISMATCH );
        }


        //
        //  Initialize the output buffer to be an empty string and then return to our caller
        //  这就是空结构。


        try {


            ObjectNameInfo->Name.Length = 0;
            ObjectNameInfo->Name.MaximumLength = 0;
            ObjectNameInfo->Name.Buffer = NULL;


        } except( EXCEPTION_EXECUTE_HANDLER ) {


            //
            //  Fall through, since we cannot undo what we have done.
            //
            //  **** This should probably get the exception code and return
            //  that value
            //
        }


        return( STATUS_SUCCESS );
    }


    //
    //  First lock the directory mutex to stop before we chase stuff down
    //  第三步.对象为有值得命名对象,则先获取整个名称的长度,即需要的内存大小。


    ObpEnterRootDirectoryMutex();


    try {


        //
        //  The object does have a name but now see if this is
        //  just the root directory object in which case the name size
        //  is only the "/" character
        //  先判断对象是否就是根目录对象,则名称的长度为sizeof( OBJ_NAME_PATH_SEPARATOR )


        if (Object == ObpRootDirectoryObject) {
            NameSize = sizeof( OBJ_NAME_PATH_SEPARATOR );
        } else {
            //
            //  The named object is not the root so for every directory working out way up we'll add its size to the name keeping track
            // of "/" characters inbetween each component.  We first start with the object name itself and then move on to the directories
            //  若不是根目录对象,则追根溯源该对象所有的目录对象直到根目录,此过程中取得对象总的长度。
           


            ObjectDirectory = NameInfo->Directory;
            NameSize = sizeof( OBJ_NAME_PATH_SEPARATOR ) + NameInfo->Name.Length;
            //
            //  While we are not at the root we'll keep moving up
            //


            while ((ObjectDirectory != ObpRootDirectoryObject) && (ObjectDirectory)) {
                //
                //  Get the name information for this directory
                //
                ObjectDirectoryHeader = OBJECT_TO_OBJECT_HEADER( ObjectDirectory );
                NameInfo = OBJECT_HEADER_TO_NAME_INFO( ObjectDirectoryHeader );


                if ((NameInfo != NULL) && (NameInfo->Directory != NULL)) {
                    //
                    //  This directory has a name so add it to the accomulated size and move up the tree
                    //
                    NameSize += sizeof( OBJ_NAME_PATH_SEPARATOR ) + NameInfo->Name.Length;
                    ObjectDirectory = NameInfo->Directory;
                } else {
                    //
                    //  This directory does not have a name so we'll give it the "..." name and stop the loop
                    //  若目录对象没有名称,则设它的名称为"..."并终止循环。


                    NameSize += sizeof( OBJ_NAME_PATH_SEPARATOR ) + OBP_MISSING_NAME_LITERAL_SIZE;
                    break;
                }
            }
        }


        //
        //  At this point NameSize is the number of bytes we need to store the name of the object from the root down.  The total buffer


        // size we are going to need will include this size, plus object name information structure, plus an ending null character
        //  现在知道对象名称的长度,由此知道需要缓冲区的大小了。


        NameInfoSize = NameSize + sizeof( OBJECT_NAME_INFORMATION ) + sizeof( UNICODE_NULL );


        //
        //  Set the output size and make sure the supplied buffer is large enough to hold the information
        //第四步. 置返回长度的大小,若用户提供的缓冲区的大小小于此值,则返回错误STATUS_INFO_LENGTH_MISMATCH。
        //  注意了,用户提供的缓冲区的大小是有函数参数Length指定的,
        //  而不是有参数ObjectNameInfo.Name.MaximumLength
        //  或者ObjectNameInfo.Name.Length指定的。(其实后面两个值大小无所谓)
        try {
            *ReturnLength = NameInfoSize;
        } except( EXCEPTION_EXECUTE_HANDLER ) {
            Status = GetExceptionCode();
            leave;
        }


        if (Length < NameInfoSize) {
            Status = STATUS_INFO_LENGTH_MISMATCH;
            leave;
        }
        //
        //  **** the following IF isn't necessary because name info size is
        //  already guaranteed to be nonzero from about 23 lines above
        // 


        if (NameInfoSize != 0) {


            //
            //  Set the String buffer to point to the byte right after the last byte in the output string.  This following logic actually
            //  fills in the buffer backwards working from the name back to the root
            //  第五步.再次追根溯源直到根目录,将对象名称拷贝到用户缓冲区中。


            StringBuffer = (PWCH)ObjectNameInfo;
            StringBuffer = (PWCH)((PCH)StringBuffer + NameInfoSize);
            //睁大眼睛:ObQueryNameString并没有利用参数ObjectNameInfo.Name.Buffer来指出
            //缓冲区的地址,而是简单地认为名称紧跟着OBJECT_NAME_INFORMATION结构。
            //这个问题我在博文中已经指出了。
           


            NameInfo = OBJECT_HEADER_TO_NAME_INFO( ObjectHeader );
            try {
                //
                //  Terminate the string with a null and backup one unicode character
                //


 


                *--StringBuffer = UNICODE_NULL;


 


                //
                //  If the object in question is not the root directory then we are going to put its name in the string buffer
                //  When we finally reach the root directory we'll append on the final "/"
                //
                if (Object != ObpRootDirectoryObject) {
                    //
                    //  Add in the objects name
                    //


 


                    String = &NameInfo->Name;
                    StringBuffer = (PWCH)((PCH)StringBuffer - String->Length);
                    RtlMoveMemory( StringBuffer, String->Buffer, String->Length );


                    //
                    //  While we are not at the root directory we'll keep moving up
                    //
                    ObjectDirectory = NameInfo->Directory;


                    while ((ObjectDirectory != ObpRootDirectoryObject) && (ObjectDirectory)) {
                        //
                        //  Get the name information for this directory
                        //
                        ObjectDirectoryHeader = OBJECT_TO_OBJECT_HEADER( ObjectDirectory );
                        NameInfo = OBJECT_HEADER_TO_NAME_INFO( ObjectDirectoryHeader );


                        //
                        //  Tack on the "/" between the last name we added and this new name
                        //
                        *--StringBuffer = OBJ_NAME_PATH_SEPARATOR;
                        //
                        //  Preappend the directory name, if it has one, and move up to the next directory.
                        //


                        if ((NameInfo != NULL) && (NameInfo->Directory != NULL)) {
                            String = &NameInfo->Name;
                            StringBuffer = (PWCH)((PCH)StringBuffer - String->Length);
                            RtlMoveMemory( StringBuffer, String->Buffer, String->Length );
                            ObjectDirectory = NameInfo->Directory;


                        } else {
                            //
                            //  The directory is nameless so use the "..." for its name and break out of the loop
                            //
                            StringBuffer = (PWCH)((PCH)StringBuffer - OBP_MISSING_NAME_LITERAL_SIZE);
                            RtlMoveMemory( StringBuffer, OBP_MISSING_NAME_LITERAL, OBP_MISSING_NAME_LITERAL_SIZE );
                            break;
                        }
                    }
                }


                //
                //  Tack on the "/" for the root directory and then set the output unicode string variable to have the right size
                //  and point to the right spot.
                //  置上根目录


                *--StringBuffer = OBJ_NAME_PATH_SEPARATOR;
                ObjectNameInfo->Name.Length = (USHORT)NameSize;
                ObjectNameInfo->Name.MaximumLength = (USHORT)(NameSize+sizeof( UNICODE_NULL ));
                ObjectNameInfo->Name.Buffer = StringBuffer;




            } except( EXCEPTION_EXECUTE_HANDLER ) {
                //
                // Fall through, since we cannot undo what we have done.
                //
                //  **** This should probably get the exception code and return that value
                //
            }
        }




        Status = STATUS_SUCCESS;
    } finally {


 


        ObpLeaveRootDirectoryMutex();


 


    }
    return Status;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值