关于进程访问令牌( access token )

An access token is an object that describes the security context of a process or thread. The information in a token includes the identity and privileges of the user account associated with the process or thread. When a user logs on, the system verifies the user's password by comparing it with information stored in a security database. If the password is authenticated, the system produces an access token. Every process executed on behalf of this user has a copy of this access token.

一个访问令牌是一个对象:用来描述一个进程或者是线程的安全上下文。在一个令牌里的信息包含与这一个进程或者线程相关的用户帐户的标识符(identity)和(访问)特权。当一个用户登陆的时候,系统会验证用户的密码通过与存储在一个安全数据库中的信息比较。如果密码通过验证,则系统会产生一个访问令牌。每一个代表该用户执行的进程有一份这个访问令牌的拷贝。

The system uses an access token to identify the user when a thread interacts with a securable object or tries to perform a system task that requires privileges. Access tokens contain the following information:

  • The security identifier (SID) for the user's account
  • SIDs for the groups of which the user is a member
  • logon SID that identifies the current logon session
  • A list of the privileges held by either the user or the user's groups
  • An owner SID
  • The SID for the primary group
  • The default DACL that the system uses when the user creates a securable object without specifying a security descriptor
  • The source of the access token
  • Whether the token is a primary or impersonation token
  • An optional list of restricting SIDs
  • Current impersonation levels
  • Other statistics

当一个线程与一个安全的对象相互作用或者要执行一个需要特权级的系统任务,此时系统会用访问令牌来验证用户。访问令牌包含以下的信息。

  1. 安全标识符对一个帐户
  2. 这一个用户所在的组的安全标识符
  3. 一个登陆安全标识符用来标识当前的登陆会话
  4. 一个用于存储用户或者是用户的组所拥有特权级的链表
  5. 一个拥有者sid(安全标识符)
  6. 默认的dacl,当用户创建一个安全对象时没有指定特定的安全描述符的时候系统用的
  7. 访问令牌的源
  8. 是否是一个主令牌还是模拟令牌。
  9. 一个可选的限制的SID链表
  10. 当前模拟(令牌)级
  11. 其它统计信息

very process has a primary token that describes the security context of the user account associated with the process. By default, the system uses the primary token when a thread of the process interacts with a securable object. Moreover, a thread can impersonate a client account. Impersonation allows the thread to interact with securable objects using the client's security context. A thread that is impersonating a client has both a primary token and an impersonation token.

Use the OpenProcessToken function to retrieve a handle to the primary token of a process. Use theOpenThreadToken function to retrieve a handle to the impersonation token of a thread. For more information, see Impersonation.

每一个进程有一个主要的令牌,它描述了与当前进程相关的用户帐户的安全上下文。默认情况下,系统用主令牌当一个进程的线程与一个安全对象相互作用时。而且(此外),一个线程可以模拟一个客户端帐户。模拟允许此线程与安全对象交互时用客户端的安全上下文。一个正模拟客户端的线程拥有一个主令牌和一个模拟令牌。

用函数:OpenProcessToken 来接收一个指向一个进程的主令牌句柄。用OpenThreadToken函数来得到一个指向线程模拟令牌的句柄。更多的信息可以看下面的客户端模拟。

你可以用下面的函数来操作访问令牌

Function

Description

AdjustTokenGroups Changes the group information in an access token.
AdjustTokenPrivileges Enables or disables the privileges in an access token. It does not grant new privileges or revoke existing ones.
CheckTokenMembership
Determines whether a specified SID is enabled in a specified access token.
CreateRestrictedToken Creates a new token that is a restricted version of an existing token. The restricted token can have disabled SIDs, deleted privileges, and a list of restricted SIDs.
DuplicateToken Creates a new impersonation token that duplicates an existing token.
DuplicateTokenEx Creates a new primary token or impersonation token that duplicates an existing token.
GetTokenInformation Retrieves information about a token.
IsTokenRestricted Determines whether a token has a list of restricting SIDs.
OpenProcessToken Retrieves a handle to the primary access token for a process.
OpenThreadToken Retrieves a handle to the impersonation access token for a thread.
SetThreadToken
Assigns or removes an impersonation token for a thread.
SetTokenInformation
Changes a token's owner, primary group, or default DACL.


The access token functions use the following structures to describe the parts of an access token.

Structure

Description

TOKEN_CONTROL Information that identifies an access token.
TOKEN_DEFAULT_DACL The default DACL that the system uses in the security descriptors of new objects created by a thread.
TOKEN_GROUPS Specifies the SIDs and attributes of the group SIDs in an access token.
TOKEN_OWNER The default owner SID for the security descriptors of new objects.
TOKEN_PRIMARY_GROUP The default primary group SID for the security descriptors of new objects.
TOKEN_PRIVILEGES The privileges associated with an access token. Also determines whether the privileges are enabled.
TOKEN_SOURCE The source of an access token.
TOKEN_STATISTICS Statistics associated with an access token.
TOKEN_USER The SID of the user associated with an access token.
typedef struct _TOKEN_CONTROL { 
LUID         TokenId; 
LUID         AuthenticationId; 
LUID         ModifiedId; 
TOKEN_SOURCE TokenSource; 
} TOKEN_CONTROL,*PTOKEN_CONTROL;
typedef struct _TOKEN_DEFAULT_DACL { 
PACL DefaultDacl; 
} TOKEN_DEFAULT_DACL, *PTOKEN_DEFAULT_DACL;
typedef struct _TOKEN_GROUPS { 
DWORD              GroupCount; SID_AND_ATTRIBUTES Groups[ANYSIZE_ARRAY]; 
} TOKEN_GROUPS, *PTOKEN_GROUPS;
typedef struct _TOKEN_OWNER { 
  PSID Owner;
} TOKEN_OWNER, *PTOKEN_OWNER;
typedef struct _TOKEN_PRIMARY_GROUP { 
PSID PrimaryGroup; 
} TOKEN_PRIMARY_GROUP, *PTOKEN_PRIMARY_GROUP;

typedef struct _TOKEN_PRIVILEGES { 

DWORD               PrivilegeCount; LUID_AND_ATTRIBUTES Privileges[ANYSIZE_ARRAY]; 
} TOKEN_PRIVILEGES, *PTOKEN_PRIVILEGES;

typedef struct _TOKEN_SOURCE { 

CHAR SourceName[TOKEN_SOURCE_LENGTH]; 
LUID SourceIdentifier; 
} TOKEN_SOURCE, *PTOKEN_SOURCE;
typedef struct _TOKEN_STATISTICS { 
LUID                         TokenId; 
LUID                         AuthenticationId; LARGE_INTEGER                ExpirationTime; TOKEN_TYPE                   TokenType; SECURITY_IMPERSONATION_LEVEL ImpersonationLevel; DWORD                        DynamicCharged; 
DWORD                        DynamicAvailable; 
DWORD                        GroupCount; 
DWORD                        PrivilegeCount; 
LUID                         ModifiedId; 
} TOKEN_STATISTICS, *PTOKEN_STATISTICS;
typedef struct _TOKEN_USER { 
SID_AND_ATTRIBUTES User; 
} TOKEN_USER, *PTOKEN_USER;
 

访问令牌函数用下面的枚举类型.

Enumeration type
Specifies
TOKEN_INFORMATION_CLASS Identifies the type of information being set or retrieved from an access token.
TOKEN_TYPE Identifies an access token as a primary or impersonation token.

 

Client Impersonation(客户端模拟)

Impersonation is the ability of a thread to execute using different security information than the process that owns the thread. Typically, a thread in a server application impersonates a client. This allows the server thread to act on behalf of that client to access objects on the server or validate access to the client's own objects.

模拟(令牌)是一个线程用不同安全信息执行的能力,而不是用拥有这一个线程的进程之安全信息。典型地,一个在一个服务程序中的线程模拟了一个客户端。这样允许这一个服务线程去活动代表那个客户端线程来访问在服务上的对象,或者是使对客户端自己的对象的访问生效。

微软的api提供了下面的函数来开始一个模拟。

The Microsoft Windows API provides the following functions to begin an impersonation:

  • A DDE server application can call the DdeImpersonateClient function to impersonate a client.
  • 一个DDE服务程序可以调用函数DdeImpersonateClient 来模拟一个客户端。
  • A named-pipe server can call the ImpersonateNamedPipeClient function.
  • 一个命名管道服务可以调用函数ImpersonateNamedPipeClient
  • You can call the ImpersonateLoggedOnUser function to impersonate the security context of a logged-on user's access token.
  • 你可以调用ImpersonateLoggedOnUser 函数来模拟一个登陆用户的访问令牌的安全上下文。
  • The ImpersonateSelf function enables a thread to generate a copy of its own access token. This is useful when an application needs to change the security context of a single thread. For example, sometimes only one thread of a process needs to enable a privilege.
  • 函数ImpersonateSelf 使一个线程能产生一个它自己的访问令牌的拷贝。这个是有用的当一个应用程序需要改变一个单线程的安全描述符的时候。例如:有时一个进程的一个线程需要全能一个特权(操作)。
  • You can call the SetThreadToken function to cause the target thread to run in the security context of a specified impersonation token.
  • 你可以调用SetThreadToken 函数来使一个目标线程运行在指定的模拟令牌的安全描述符中。
  • A Microsoft Remote Procedure Call (RPC) server application can call theRpcImpersonateClient function to impersonate a client.
  • 微软的远程过程调用服务应用程序可以调用函数RpcImpersonateClient 来模拟一个客户端。
  • security package or application server can call the ImpersonateSecurityContext function to impersonate a client.
  • 一个安全包或者是一个应用程序服务可以调用ImpersonateSecurityContext 函数来模拟一个客户端。

For most of these impersonations, the impersonating thread can revert to its own security context by calling the RevertToSelf function. The exception is the RPC impersonation, in which the RPC server application calls RpcRevertToSelf or RpcRevertToSelfEx to revert to its own security context.

对于大多数的模拟,这个模拟线程可以恢复到原来的安全描述符通过调用函数RevertToSelf 。一个例外是RPC模拟,在这一种情况下RPC服务程序调用函数RpcRevertToSelf or RpcRevertToSelfEx 来恢复到原来的安全描述符。


访问令牌是一个被保护的对象,包含了与用户帐户相关的辨识和特权信息。当用户登陆到一台windows计算机,登陆进程会验证用户的登陆凭据。成功后,登陆进程返回一个对应用户的SID和一个用户的安全组SID列表。计算机LSA使用这些信息创建一个访问令牌(主访问令牌)。该访问令牌包括了由登录进程返回的SIDs和一份由本地安全策略分发给用户以及用户安全组的特权列表。此后,这份访问令牌的拷贝会跟每个代表用户执行的线程和进程链接。

    如果拿到访问令牌,登录成功,要是访问某台本域内计算机的共享资源时,则必须出示访问令牌。从而来决定将拥有何种权限来访问。

    在使用OpenProcess()函数打开一个进程时,通常是没有权限的,这时就需要设置进程访问令牌,更改进程的权限。

 

    我们要修改一个进程的访问令牌,首先要获得进程访问令牌的句柄,这可以通过 OpenProcessToken得到,函数的原型如下:
BOOL OpenProcessToken(

           HANDLE ProcessHandle,
           DWORD DesiredAccess,
           PHANDLE TokenHandle
);
第一参数是要修改访问权限的进程句柄;第三个参数就是返回的访问令牌指针;第二个参数指定你要进行的操作类型,如要修改令牌我们要指定第二个参数为TOKEN_ADJUST_PRIVILEGES(其它一些参数可参考Platform SDK)。通过这个函数我们就可以得到当前进程的访问令牌的句柄(指定函数的第一个参数为GetCurrentProcess()就可以了)。

 

    要修改进程的什么权限?通过函数LookupPrivilegevalue()设定

BOOL LookupPrivilegevalue(
            LPCTSTR lpSystemName, // system name
            LPCTSTR lpName, // privilege name
            PLUID lpLuid // locally unique identifier
);

第一个参数是系统的名称,如果是本地系统只要指明为NULL就可以了,第三个参数就是返回LUID的指针,第二个参数就是指明了权限的名称,如“SeDebugPrivilege”(Winnt.h有详细定义),如果要对一个任意进程进行指定了写相关的访问权限,设为"SeDeDebug"权限就可以了。

 

typedef struct _TOKEN_PRIVILEGES {
         DWORD PrivilegeCount ;
         LUID_AND_ATTRIBUTES Privileges [ANYSIZE_ARRAY];

} TOKEN_PRIVILEGES

该结构包含一个数组,数据组的每个项指明了权限的类型和要进行的操作,PrivilegeCount指的数组元素的个数,接着是一个LUID_AND_ATTRIBUTES类型的数组,再来看一下LUID_AND_ATTRIBUTES这个结构的内容,声明如下:

typedef struct _LUID_AND_ATTRIBUTES {
          LUID Luid;
          DWORD Attributes;
} LUID_AND_ATTRIBUTES, *PLUID_AND_ATTRIBUTES

第一个参数就是指权限的类型,是一个LUID的值,LUID就是指locally unique identifier,保证局部唯一,就是指在系统的每一次运行期间保证是唯一的就可以了。这个就是LookupPrivilegevalue()返回的值。第二个参数就指明了我们要进行的操作类型,有3个可选项: SE_PRIVILEGE_ENABLED、SE_PRIVILEGE_ENABLED_BY_DEFAULT、SE_PRIVILEGE_USED_FOR_ACCESS。

 

最后,调用AdjustTokenPrivileges对这个访问令牌进行修改。AdjustTokenPrivileges的原型如下:
BOOL AdjustTokenPrivileges(
            HANDLE TokenHandle,  
            BOOL DisableAllPrivileges,  
            PTOKEN_PRIVILEGES NewState,  
            DWORD BufferLength,  
            PTOKEN_PRIVILEGES PreviousState,  
            PDWORD ReturnLength 
);
第一个参数是访问令牌的句柄(OpenProcessToken()返回的),第二个参数决定是进行权限修改还是Disable所有权限;第三个参数指明要修改的权限,是一个指向 TOKEN_PRIVILEGES结构的指针; 第四个参数是结构NewStateS的长度,如果NewState为空,该参数应为NULL;第五个参数也是一个指向 TOKEN_PRIVILEGES结构的指针,存放修改前的访问权限的信息,可空;最后一个参数为实际PreviousState结构返回的大小。

通过上述的调用,就可以提升OpenProcess()访问权限了。

 

举例说明,在OpenProcess()之前调用该函数。

bool enableDebugPriv()
{
    HANDLE hToken;
    LUID sedebugnameValue;
    TOKEN_PRIVILEGES tkp;

    if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
    {
        return false;
    }


    if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &sedebugnameValue))

    {
        CloseHandle(hToken);
        return false;
    }


    tkp.PrivilegeCount = 1;
    tkp.Privileges[0].Luid = sedebugnameValue;
    tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
    if (!AdjustTokenPrivileges(hToken, FALSE, &tkp, sizeof(tkp), NULL, NULL))

    {
        CloseHandle(hToken);
        return false;
    }
    return true;
}

 

PS :关于访问令牌的另一篇文章

From CTOBlog

LookupPrivilegeValue

 
  函数查看系统权限的特权值,返回信息到一个LUID结构体里。
 
  BOOL LookupPrivilegeValue(LPCTSTR lpSystemName,LPCTSTR lpName,PLUID lpLuid);
 
  第一个参数表示所要查看的系统,本地系统直接用NULL
 
  第二个参数表示所要查看的特权信息的名称,定义在winnt.h中,具体指请MSDN索引“windows nt privileges”
 
  第三个参数用来接收所返回的制定特权名称的信息。
 
  函数调用成功后,信息存入第三个类型为LUID的结构体中,并且函数返回非0。
 
  函数定义在winbase.h中,链接使用advapi32.lib库。
 
  示例:
 
  CString str;
 
  LUID Luid;   //LUID 就是LARGE_INTEGER的定义
 
  LookupPrivilegeValue(NULL,"SeDebugPrivilege",&Luid);
 
  str.Format("%d,%d\n",Luid.HighPart,Luid.LowPart);
 
  MessageBox(str);
 
   
AdjustTokenPrivileges这个函数启用或禁止 指定访问令牌的特权。
 
  启用或禁用特权一个有TOKEN_ADJUST_PRIVILEGES访问的访问令牌.
 
  BOOL AdjustTokenPrivileges(
 
  HANDLE TokenHandle//包含特权的句柄
 
  BOOL DisableAllPrivileges,
 
  //禁用所有权限标志
 
  PTOKEN_PRIVILEGES NewState,
 
  //新特权信息的指针(结构体)
 
  DWORD BufferLength//大小,以字节为单位的PreviousState的缓存区(sizeof)
 
  PTOKEN_PRIVILEGES PreviousState,
 
  //接收被改变特权当前状态的Buffer
 
  PDWORD ReturnLength//接收PreviousState缓存区要求的大小
 
  );
 
  参数
 
  TokenHandle
 
  包含要修改特权的访问令牌的标识(句柄).这个句柄必须有TOKEN_ADJUST_PRIVILEGES访问令牌.如果PreviousState不是NULL,这个句柄还必须有TOKEN_QUERY访问特权.
 
  DisableAllPrivileges
 
  标志这个函数是否禁用该令牌的所有特权.如果为TRUE,这个函数禁用所有特权,NewState参数无效.如果为假,以NewState参数指针的信息为基础来修改特权.
 
  NewState
 
  一个TOKEN_PRIVILEGES结构体的指针指定了一组特权和他们的属性.
 
  如果参数DisableAllPrivileges为FALSE,AdjustTokenPrivileges启用或禁用这些令牌的特权.
 
  如果你给一个特权设置了SE_PRIVILEGE_ENABLED的属性,这个函数将启动特权,否则禁用特权.
 
  如果DisableAllPrivileges为TRUE,这个参数无效.
 
  BufferLength
 
  标志参数PreviousState指针以字节大小缓存区(sizeof).
 
  如果参数PreviousState是NULL,这个参数可以为NULL.
 
  PreviousState
 
  这个函数填充一个TOKEN_PRIVILEGES结构体【指针】,它包括该函数修改之前任何特权状态.这个参数可以为NULL.
 
  如果指定的缓冲区太小,无法收到完整的修改权限列表,这个函数失败并不会修改任何特权.
 
  这个函数设置了一个 拥有修改权限完成列表【 参数ReturnLength 】的字节数 的指针变量.[结果的Buffer]
 
  ReturnLength
 
  接收 参数PreviousState的缓存区指针的 字节大小 的 变量指针(长度指针).
 
  如果PreviousState为NULL,这个参数可以为NULL.
 
  返回值
 
  如果这个函数成功,返回非0.为了确定这个函数是否修改了所有指定的特权,可以调用GetLastError函数,当这个函数返回下面的值之一时就代表函数成功:
 
 
 
OpenProcess 函数用来打开一个已存在的进程对象,并返回进程的句柄。
 
  1.函数原型
 
  HANDLE OpenProcess(
 
  DWORD dwDesiredAccess, // access flag
 
  BOOL bInheritHandle, // handle inheritance option
 
  DWORD dwProcessId// process identifier
 
  );
 
  2.返回值:
 
  如成功,返回值为指定进程的句柄。
 
  如失败,返回值为空,可调用GetLastError获得错误代码。
 
  -------------------------------------------------------------
 
  3.举例
 
  HANDLE hProcess = OpenProcess( PROCESS_ALL_ACCESS, FALSE, pID );
 
  ----------------------
 
  4.附:
 
  BOOL ReadProcessMemory( HANDLE hProcess, PVOID pvAddressRemote, PVOID pvBufferLocal, DWORD dwSize, PDWORD pdwNumBytesRead);
 
  参数
 
  hProcess //为远程进程的句柄
 
  pvAddressRemote //用于指明远程进程中的地址
 
  pvBufferLocal //是本地进程中的内存地址
 
  dwSize //是需要传送的字节数
 
  pdwNumBytesRead和pdwNumBytesWritten //用于指明实际传送的字节数.当函数返回时,可以查看这两个参数的值.
 
   
 说明:
 
  BOOL WINAPI DuplicateHandle(
 
  __in HANDLE hSourceProcessHandle,
 
  __in HANDLE hSourceHandle,
 
  __in HANDLE hTargetProcessHandle,
 
  __out LPHANDLE lpTargetHandle,
 
  __in DWORD dwDesiredAccess,
 
  __in BOOL bInheritHandle,
 
  __in DWORD dwOptions
 
  );
 
  hSourceProcessHandle:源进程内核句柄(即负责传递内核对象句柄的进程句柄)hSourceHandle:要传递的内核对象句柄hTargetProcessHandle:目标进程内核句柄lpTargetHandle:接收内核对象句柄的地址(先随便声明一个HANDLE)dwDesiredAccess:TargetHandle句柄使用何种访问掩码(这个掩码是在句柄表中的一项)bInheritHandle:是否拥有继承dwOptions:当设DUPLICATE_SAME_ACCESS时,表示于源的内核对象所有标志一样,此时wDesiredAccess可标志为0
 
  当设DUPLICATE_CLOSE_SOURCE时,传输完后,关闭源中的内核对象句柄此函数能否成功调用还要看你是否有足够的权限去操作目标进程通常目标进程的内核句柄是利用OpenProcess()得到的HANDLE WINAPI OpenProcess(
 
  __in DWORD dwDesiredAccess,
 
  __in BOOL bInheritHandle,
 
  __in DWORD dwProcessId
 
  );
 
  dwDesiredAccess:决定你拥有该进程的操作权限,如果要成功用到则要填PROCESS_ALL_ACCESS或PROCESS_DUP_HANDLE
 
  bInheritHandle:是否可继承
 
  dwProcessId:这个ID可在资源管理器中找到,当然,我不提倡在哪里得到,或者你可以通过进程间通信的方法把PID从目标进程传给源进程
 
  若DuplicateHandle()能成功执行,则利用进程通信把句柄值TargetHandle传给目标进程,让他知道利用该句柄使用内核对象
 
  注意:不要试图在源进程中利用CloseHandle()关闭TargetHandle,因为这个TargetHandle句柄值并不属于源进程的句柄表中的,若错误关闭了,会产生不可预料的结果

你可能感兴趣的:(thread,struct,Security,Access,token,attributes)