如何判断一个进程的权限是否存在及其enabled/disabled?

How to determine whether a process's privilege exists and its enabled/disabled?

场景


我想确定指定的进程是否启用了特定的privilege

为了让这个问题更简单,示例目标进程将是当前进程,我将检查关闭本地系统的权限(之前使用 AdjustTokenPrivileges 函数)。

然后,我发现PrivilegeCheck函数似乎可以确定目标进程的访问令牌中是否启用了一组指定的权限。

更新

我认为我的关注方向错误,因为PrivilegeCheck功能似乎需要模拟,所以现在我面临着另一个无休止的试错 阶段尝试 GetTokenInformation 函数,这似乎是实现此任务的正确函数。

问题


我遇到的问题是,当我尝试使用 PrivilegeCheck 函数时,它总是 returns False (错误),并且权限的引用数组没有具有预期值(因为函数失败)。

更新

GetTokenInformation 函数也因 False 值而失败,返回此 win32 错误代码:122(HRESULT:-2147467259) 消息:

Data Area Passed to a System Call Is Too Small

问题


我应该如何修复我在代码中遇到的错误,以便能够检查进程的权限是否存在,然后检查该权限是启用还是禁用?

使用 PrivilegeCheckGetTokenInformation 函数,或者任何其他可以确定特权状态的该死的函数。

源代码


这是一个完整的可复制示例(连同下面的 p/invokes),我将在其中演示如何测试 PrivilegeCheckGetTokenInformation 方法,两者失败。

Dim pHandle As IntPtr = Process.GetCurrentProcess().Handle
Dim privilegeName As String = "SeShutdownPrivilege"
Dim tokenAccess As TokenAccess = (TokenAccess.AdjustPrivileges Or TokenAccess.Query Or TokenAccess.Duplicate)
Dim hToken As IntPtr
Dim hTokenDup As IntPtr

Try
    ' ****************************************************************************
    ' 1st Step: Enable the "SeShutdownPrivilege" privilege in the current process.
    ' ****************************************************************************

    Dim win32Err As Integer

    ' Get the process token.
    NativeMethods.OpenProcessToken(pHandle, tokenAccess, hToken)

    ' Set up a LuidAndAttributes structure containing the privilege to enable,
    ' getting the LUID that corresponds to the privilege.
    Dim luAttr As New LuidAndAttributes
    luAttr.Attributes = TokenPrivilegeAttributes.PrivilegeEnabled
    NativeMethods.LookupPrivilegeValue(Nothing, privilegeName, luAttr.Luid)

    ' Set up a TokenPrivileges structure containing only the source privilege.
    Dim newState As New TokenPrivileges
    newState.PrivilegeCount = 1
    newState.Privileges = New LuidAndAttributes() {luAttr}

    ' Set up a TokenPrivileges structure for the previous (modified) privileges.
    Dim prevState As New TokenPrivileges
    prevState = New TokenPrivileges
    ReDim prevState.Privileges(CInt(newState.PrivilegeCount))

    ' Apply the TokenPrivileges structure to the source process token.
    Dim bufferLength As Integer = Marshal.SizeOf(prevState)
    Dim returnLength As IntPtr
    If Not NativeMethods.AdjustTokenPrivileges(hToken, False, newState, bufferLength, prevState, returnLength) Then
        win32Err = Marshal.GetLastWin32Error
        MessageBox.Show("AdjustTokenPrivileges failed.")
        Throw New Win32Exception(win32Err)
    End If

    ' *********************************************************************
    ' Everything OK at this point, 
    ' as AdjustTokenPrivileges dididn't failed, I assume the privilege Is enabled in the process.
    '
    ' 2n Step: Check whether the privilege is enabled or not...
    ' *********************************************************************

    ' Set up a new one LuidAndAttributes structure containing the privilege to check,
    ' getting the LUID that corresponds to the privilege.
    luAttr = New LuidAndAttributes
    NativeMethods.LookupPrivilegeValue(Nothing, privilegeName, luAttr.Luid)

    ' *********************************************************************
    ' Trying PrivilegeCheck and Duplicatetoken methodology...
    ' *********************************************************************

    NativeMethods.DuplicateToken(hToken, SecurityImpersonationLevel.SecurityImpersonation, hTokenDup)
    win32Err = Marshal.GetLastWin32Error

    If (hTokenDup <> IntPtr.Zero) Then
        Dim result As Boolean
        Dim pSet As New PrivilegeSet
        pSet.Control = 0
        pSet.PrivilegeCount = 1
        pSet.Privileges = New LuidAndAttributes() {luAttr}

        If Not NativeMethods.PrivilegeCheck(hToken, pSet, result) Then
            win32Err = Marshal.GetLastWin32Error
            MessageBox.Show("PrivilegeCheck using original access-token failed.")
            ' Ignore exception, to continue with the GetTokenInformation methodology.
            ' Throw New Win32Exception(win32Err)

        Else
            MessageBox.Show(String.Format("{0} (original token) state is: {1}",
                                              privilegeName, pSet.Privileges(0).Attributes.ToString()))

            If Not NativeMethods.PrivilegeCheck(hTokenDup, pSet, result) Then
                win32Err = Marshal.GetLastWin32Error
                MessageBox.Show("PrivilegeCheck using impersonated access-token failed.")
                ' Ignore exception, to continue with the GetTokenInformation methodology.
                ' Throw New Win32Exception(win32Err)
            Else
                MessageBox.Show(String.Format("{0} (impersonated token) state is: {1}",
                                              privilegeName, pSet.Privileges(0).Attributes.ToString()))

            End If

        End If

    Else
        MessageBox.Show("DuplicateToken failed.")
        ' Ignore exception, to continue with the GetTokenInformation methodology.
        ' Throw New Win32Exception(win32Err)

    End If

    ' *********************************************************************
    ' Trying GetTokenInformation methodology...
    ' *********************************************************************

    Dim tkp As New TokenPrivileges
    Dim tkpHandle As IntPtr
    Dim tkInfoLength As Integer = 0

    tkpHandle = Marshal.AllocHGlobal(Marshal.SizeOf(tkpHandle))
    Marshal.StructureToPtr(tkp, tkpHandle, False)

    NativeMethods.GetTokenInformation(hToken, TokenInformationClass.TokenPrivileges, IntPtr.Zero, tkInfoLength, tkInfoLength)
    win32Err = Marshal.GetLastWin32Error
    ' If I understood, It is supposed to return 122,
    ' so I should ignore that error code?:
    If (win32Err <> 122) Then 
        MessageBox.Show("GetTokenInformation failed in the attempt to get the TokenPrivileges's size.")
        Throw New Win32Exception(win32Err)

    Else
        If Not NativeMethods.GetTokenInformation(hToken, TokenInformationClass.TokenPrivileges, tkpHandle, tkInfoLength, tkInfoLength) Then
            win32Err = Marshal.GetLastWin32Error
            MessageBox.Show("GetTokenInformation failed in the attempt to get the TokenPrivileges.")
            Throw New Win32Exception(win32Err)

        Else
            Dim privilegeAttr As TokenPrivilegeAttributes = tkp.Privileges(0).Attributes
            MessageBox.Show(String.Format("{0} state is: {1}", privilegeName, privilegeAttr.ToString()))

        End If

    End If

Catch ex As Win32Exception
    MessageBox.Show(ex.NativeErrorCode & " " & ex.Message)

Catch ex As Exception
    MessageBox.Show(ex.Message)

Finally
    If (hTokenDup <> IntPtr.Zero) Then
        NativeMethods.CloseHandle(hTokenDup)
    End If

    If (hToken <> IntPtr.Zero) Then
        NativeMethods.CloseHandle(hToken)
    End If

End Try

这些是相关的 winapi 定义(注意感兴趣的评论 MSDN url):

' http://msdn.microsoft.com/en-us/library/windows/desktop/aa379295%28v=vs.85%29.aspx
<DllImport("advapi32.dll", SetLastError:=True)>
Public Shared Function OpenProcessToken(ByVal processHandle As IntPtr,
                                        ByVal desiredAccess As TokenAccess,
                                        ByRef tokenHandle As IntPtr
) As <MarshalAs(UnmanagedType.Bool)> Boolean
End Function

' http://msdn.microsoft.com/en-us/library/windows/desktop/aa379180%28v=vs.85%29.aspx
<DllImport("Advapi32.dll", SetLastError:=True, CharSet:=CharSet.Auto, BestFitMapping:=False, ThrowOnUnmappableChar:=True)>
Public Shared Function LookupPrivilegeValue(ByVal lpSystemName As String,
                                            ByVal lpName As String,
                                            ByRef lpLuid As Luid
) As <MarshalAs(UnmanagedType.Bool)> Boolean
End Function

' http://msdn.microsoft.com/es-es/library/windows/desktop/aa375202%28v=vs.85%29.aspx
<DllImport("Advapi32.dll", SetLastError:=True)>
Public Shared Function AdjustTokenPrivileges(ByVal tokenHandle As IntPtr,
                                             ByVal disableAllPrivileges As Boolean,
                                             ByRef newState As TokenPrivileges,
                                             ByVal bufferLength As Integer,
                                             ByRef refPreviousState As TokenPrivileges,
                                             ByRef refReturnLength As IntPtr
) As <MarshalAs(UnmanagedType.Bool)> Boolean
End Function

' https://msdn.microsoft.com/en-us/library/windows/desktop/aa379304%28v=vs.85%29.aspx
<DllImport("Advapi32.dll", SetLastError:=True)>
Public Shared Function PrivilegeCheck(ByVal token As IntPtr,
                          <[In], Out> ByRef privileges As PrivilegeSet,
                                      ByRef refResult As Boolean
) As <MarshalAs(UnmanagedType.Bool)> Boolean
End Function

' https://msdn.microsoft.com/en-us/library/windows/desktop/aa446616%28v=vs.85%29.aspx
<DllImport("advapi32.dll", SetLastError:=True)>
Public Shared Function DuplicateToken(ByVal tokenHandle As IntPtr,
                                      ByVal impersonationLevel As SecurityImpersonationLevel,
                                      ByRef duplicateTokenHandle As IntPtr
) As <MarshalAs(UnmanagedType.Bool)> Boolean
End Function

' https://msdn.microsoft.com/en-us/library/windows/desktop/aa446671%28v=vs.85%29.aspx
<DllImport("Advapi32.dll", SetLastError:=True)>
Public Shared Function GetTokenInformation(ByVal tokenHandle As IntPtr,
                                           ByVal tokenInformationClass As TokenInformationClass,
                                           ByVal tokenInformation As IntPtr,
                                           ByVal tokenInformationLength As Integer,
                                           ByRef refReturnLength As Integer
) As <MarshalAs(UnmanagedType.Bool)> Boolean
End Function

' http://msdn.microsoft.com/en-us/library/windows/desktop/aa374905%28v=vs.85%29.aspx
<Flags>
Public Enum TokenAccess As UInteger
    ' THIS ENUMERATION IS PARTIALLY DEFINED.
    ' **************************************
    TokenAdjustPrivileges = &H20UI
    TokenQuery = &H8UI
End Enum

' https://msdn.microsoft.com/en-us/library/windows/desktop/aa379630%28v=vs.85%29.aspx
<Flags>
Public Enum TokenPrivilegeAttributes As UInteger
    PrivilegeDisabled = &H0UI
    PrivilegeEnabledByDefault = &H1UI
    PrivilegeEnabled = &H2UI
    PrivilegeRemoved = &H4UI
    PrivilegeUsedForAccess = &H80000000UI
End Enum

' https://msdn.microsoft.com/en-us/library/windows/desktop/aa379572(v=vs.85).aspx
Public Enum SecurityImpersonationLevel As Integer
    SecurityAnonymous = 0
    SecurityIdentification = 1
    SecurityImpersonation = 2
    SecurityDelegation = 3
End Enum

' http://msdn.microsoft.com/en-us/library/windows/desktop/aa379261%28v=vs.85%29.aspx
<StructLayout(LayoutKind.Sequential)>
Public Structure Luid
    Public LowPart As UInteger
    Public HighPart As Integer
End Structure

' http://msdn.microsoft.com/en-us/library/windows/desktop/aa379263%28v=vs.85%29.aspx
<StructLayout(LayoutKind.Sequential)>
Public Structure LuidAndAttributes
    Public Luid As Luid
    Public Attributes As TokenPrivilegeAttributes
End Structure

' https://msdn.microsoft.com/en-us/library/windows/desktop/aa379630%28v=vs.85%29.aspx
<StructLayout(LayoutKind.Sequential)>
Public Structure TokenPrivileges
    Public PrivilegeCount As UInteger

    <MarshalAs(UnmanagedType.ByValArray, SizeConst:=1)>
    Public Privileges As LuidAndAttributes()
End Structure

' https://msdn.microsoft.com/en-us/library/windows/desktop/aa379307%28v=vs.85%29.aspx
<StructLayout(LayoutKind.Sequential)>
Public Structure PrivilegeSet
    Public PrivilegeCount As UInteger
    Public Control As UInteger

    <MarshalAs(UnmanagedType.ByValArray, SizeConst:=1)>
    Public Privileges As LuidAndAttributes()
End Structure

我现在上传了我用来测试我的答案的完整代码,以防其他人需要一个工作示例。特别是那里的样板文件并不完美(也不是有意的),请不要以此来评判我。看到这个贴子:http://pastebin.com/gdxwWHRb


您错误地使用了非托管内存块 tkpHandle 和结构 tkp。此外,您只是检查 GetTokenInformation (tkp.Privileges(0)) 返回的第一个权限的属性 - 相反,您必须检查所有权限并找到正确的权限。

如果我像这样更改您的代码,它对我有用:

' *********************************************************************
' Trying GetTokenInformation methodology...
' *********************************************************************

Dim tkp As New TokenPrivileges
Dim tkpHandle As IntPtr = IntPtr.Zero ' <<< will be set later
Dim tkInfoLength As Integer = 0

' Here we call GetTokenInformation the first time to receive the length of the data it would like to store.
NativeMethods.GetTokenInformation(hToken, TokenInformationClass.TokenPrivileges, IntPtr.Zero, tkInfoLength, tkInfoLength)
win32Err = Marshal.GetLastWin32Error

' Since the "current" length we pass is 0, we'll always get "error" 122, which is fine. We also get the required length returned.
If (win32Err <> 122) Then
    MessageBox.Show("GetTokenInformation failed in the attempt to get the TokenPrivileges's size.")
    Throw New Win32Exception(win32Err)
Else
    Try
        ' Here we allocate memory for receiving the actual data. By now, tkInfoLength contains the size of the memory block we need to allocate.
        tkpHandle = Marshal.AllocHGlobal(tkInfoLength)

        ' This time, we shouldn't get an error 122, because this time we already have set the correct buffer size. GetTokenInformation should now write the data into the memory block we just allocated.
        If Not NativeMethods.GetTokenInformation(hToken, TokenInformationClass.TokenPrivileges, tkpHandle, tkInfoLength, tkInfoLength) Then
            win32Err = Marshal.GetLastWin32Error
            MessageBox.Show("GetTokenInformation failed in the attempt to get the TokenPrivileges.")
            Throw New Win32Exception(win32Err)

        Else
            ' We will now ask PtrToStructure to read the raw data out of the memory block and convert it to a managed structure of type TokenPrivileges which we can use in our code. That's it!
            tkp = Marshal.PtrToStructure(tkpHandle, GetType(TokenPrivileges))

            ' We have to iterate over all privileges listed in the TokenPrivileges structure to find the one we are looking for
            Dim found As Boolean = False
            For i As Integer = 0 To tkp.PrivilegeCount - 1
                ' There is a problem: Marshal.PtrToStructure can't marshal variable-length structures, but the array TokenPrivileges::Privileges has
                ' a variable length determined by the value of TokenPrivileges::PrivilegeCount! Since we don't know the size at compile time, the
                ' size of the array was hardcoded to 1, which means that we would only be able to access the first element of the array.
                ' To work around this, we calculate the raw memory offset pointing to the array element we need and load it separately into a
                ' LuidAndAttributes variable.
                ' The way this works is: The contents of the TokenPrivilege structure or stored in memory one after another, like this:
                '   PrivilegeCount (type: UInteger)
                '   Privileges(0) (type: LuidAndAttributes)
                '   Privileges(1) (type: LuidAndAttributes) << these and all further we normally can't access
                '   Privileges(2) (type: LuidAndAttributes)
                ' ...and so on.
                ' We are now calculating the offset into the structure for a specific array element. Let's use Privileges(2) as example:
                ' To get to it, we need to take the pointer to the beginning of the structure and add the sizes of all previous elements,
                ' which would be once the size of PrivilegeCount and then 2 times the size of a LuidAndAttributes structure.
                Dim directPointer As New IntPtr(tkpHandle.ToInt64() + Len(tkp.PrivilegeCount) + i * Marshal.SizeOf(GetType(LuidAndAttributes)))
                Dim luidAndAttributes As LuidAndAttributes = Marshal.PtrToStructure(directPointer, GetType(LuidAndAttributes))

                ' Get the privilege name. We first call LookupPrivilegeName with a zero size to get the real size we need, then reserve space, then get the actual data
                ' NOTE: The part below isn't actually necessary as commented Mark Hurd pointed out, because you already have the right privilege's LUID in luAttr.Luid.
                ' But I'll leave it here anyway in case somebody uses this piece of code without the part which sets the privilege.
                ' Another solution in this case would also be to run LookupPrivilegeValue to get the LUID to compare to (which is what led to luAttr.Luid as well).
                Dim privNameLen As Integer = 0
                Dim sb As New System.Text.StringBuilder()
                NativeMethods.LookupPrivilegeName(Nothing, luidAndAttributes.Luid, sb, privNameLen)
                sb.EnsureCapacity(privNameLen + 1)
                If Not NativeMethods.LookupPrivilegeName(Nothing, luidAndAttributes.Luid, sb, privNameLen) Then
                    win32Err = Marshal.GetLastWin32Error
                    MessageBox.Show("LookupPrivilegeName failed.")
                    Throw New Win32Exception(win32Err)
                End If

                ' Now that we have the name, we can check if it's the one we are looking for.
                ' NOTE: Refering to me comments above, this could be just: If luidAndAttributes.Luid = luAttr.Luid
                If sb.ToString() = privilegeName Then
                    ' Found! So we can finally get the status of the privilege!
                    found = True
                    MessageBox.Show(String.Format("{0} state is: {1}", privilegeName, luidAndAttributes.Attributes.ToString()))
                    Exit For
                End If
            Next

            If Not found Then MessageBox.Show(String.Format("{0} not found in list of privileges!", privilegeName))
        End If
    Finally
        ' Make sure the memory block is freed again (even when an error occured)
        If tkpHandle Then Marshal.FreeHGlobal(tkpHandle)
    End Try

End If

查看我在代码中的注释。基本上,流程是:

  • 使用空指针和零大小调用 GetTokenInformation 以接收实际需要的缓冲区大小(因此错误 122 在这里是正常的)。
  • 根据我们收到的缓冲区大小分配内存。
  • 再次调用 GetTokenInformation,使用指向内存块的实际指针和实际大小,以便接收实际数据。
  • 使用 Marshal.PtrToStructure(不是 Marshal.StructureToPtr)将原始数据转换为 .NET 结构。
  • 找到合适的权限并检查其状态。这有点棘手,请参阅下面的解释。
  • 释放内存块。 (我添加了一个 Try/Finally 块以确保内存永远不会泄漏。)

这里还有一个棘手的部分是如何找到正确的权限来检查GetTokenInformation返回的权限列表:

  • Marshal.PtrToStructure 无法像 TokenInformation 结构中的那样编组可变长度数组。为了能够访问我们代码中第一个元素之后的元素,我们必须玩一些技巧并进行指针运算以计算这些数组元素的内存偏移量,然后对每个元素使用 Marshal.PtrToStructure个别地。详情见我在代码中的注释。
  • 为了知道哪个特权是哪个,我们需要使用LookupPrivilegeName,这基本上与LookupPrivilegeValue相反。请注意,我们首先使用零长度调用它以获取实际长度,然后保留 space,然后再次调用它。 - 注意:正如我在 EDIT2 中所写,如果您正在寻找特定权限,这实际上不是必需的。您只需要使用 LookupPrivilegeValue 获得正确的 LUID,然后将 LUID 与已知的 LUID 进行比较。你已经在你的代码中做了这件事(在 luAttr.Luid 中)。请阅读我在代码中的其他评论!

我这样导入 LookupPrivilegeName

<DllImport("Advapi32.dll", SetLastError:=True, CharSet:=CharSet.Auto, BestFitMapping:=False, ThrowOnUnmappableChar:=True)>
Public Shared Function LookupPrivilegeName(lpSystemName As String, ByRef lpLuid As Luid, lpName As System.Text.StringBuilder, ByRef cchName As Integer) As <MarshalAs(UnmanagedType.Bool)> Boolean

现在代码成功运行并显示消息框:

SeShutdownPrivilege state is: PrivilegeEnabled

编辑:我忽略了 tkp.Privileges(0).Attributes 是硬编码的问题,而不是遍历所有权限以找到正确的权限。我在答案中添加了正确的解决方案。

EDIT2: 由于正确,其实不需要每次都调用LookupPrivilegeName。在这个问题的原始代码中,权限的 LUID 是已知的,因为它之前使用 LookupPrivilegeValue 查找过。但无论如何我都会在代码中保留名称查找,以防万一有人需要枚举其他启用的权限 - 我只是添加了关于它的评论。