逆核系列No.9--Windows进程提权

本节内容

解决上节关于提升Windows权限的遗留问题,在上节内容卸载DLL中,利用了EjectDll.exe从notepad.exe的加载模块中进行指定模块的卸载。这个过程中涉及了跨内存操作,都知道Windows系统需要保证进程之间互不干扰,相互独立。在Windows下编程有些涉及到硬件或者跨内存的API会发现失效,这就需要在执行某些跨内存的操作前提升Windows的权限。

这部分的具体实例是EjectDll -> SetPrivilege(SE_DEBUG_NAME, TRUE),函数定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
BOOL SetPrivilege(LPCTSTR lpszPrivilege, BOOL bEnablePrivilege) 
{
TOKEN_PRIVILEGES tp;
HANDLE hToken;
LUID luid;

if( !OpenProcessToken(GetCurrentProcess(),
TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,
&hToken) )
{
_tprintf(L"OpenProcessToken error: %u\n", GetLastError());
return FALSE;
}

if( !LookupPrivilegeValue(NULL, // lookup privilege on local system
lpszPrivilege, // privilege to lookup
&luid) ) // receives LUID of privilege
{
_tprintf(L"LookupPrivilegeValue error: %u\n", GetLastError() );
return FALSE;
}

tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = luid;
if( bEnablePrivilege )
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
else
tp.Privileges[0].Attributes = 0;

// Enable the privilege or disable all privileges.
if( !AdjustTokenPrivileges(hToken,
FALSE,
&tp,
sizeof(TOKEN_PRIVILEGES),
(PTOKEN_PRIVILEGES) NULL,
(PDWORD) NULL) )
{
_tprintf(L"AdjustTokenPrivileges error: %u\n", GetLastError() );
return FALSE;
}

if( GetLastError() == ERROR_NOT_ALL_ASSIGNED )
{
_tprintf(L"The token does not have the specified privilege. \n");
return FALSE;
}

return TRUE;
}

SetPrivilege -> OpenProcessToken

1
2
3
4
5
BOOL OpenProcessToken(
[in] HANDLE ProcessHandle,
[in] DWORD DesiredAccess,
[out] PHANDLE TokenHandle
);

Access token

学习这个API之前有必要了解access token

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

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

因此在涉及跨进程内存的操作需要了解是否具备相应的权限,没有的话则需要先进行权限提升。

回到OpenProcessToken的分析

关于OpenProcessToken API的官方文档中对于API的描述有这样的说法:The OpenProcessToken function opens the access token associated with a process.

因此OpenProcessToken 是用来返回具备权限(DesiredAccess)目标进程(notepad.exe)的访问令牌(access token)。

第一个参数传递的是想要获取的access token对应的进程句柄。

第二个参数需要指出将对打开的access token期望得到的权限

第三个参数则是操作成功后返回的access token

这里列举第二个参数可能的取值,来源

Value Meaning
TOKEN_ADJUST_DEFAULT Required to change the default owner, primary group, or DACL of an access token.
TOKEN_ADJUST_GROUPS Required to adjust the attributes of the groups in an access token.
TOKEN_ADJUST_PRIVILEGES Required to enable or disable the privileges in an access token.
TOKEN_ADJUST_SESSIONID Required to adjust the session ID of an access token. The SE_TCB_NAME privilege is required.
TOKEN_ASSIGN_PRIMARY Required to attach a primary token to a process. The SE_ASSIGNPRIMARYTOKEN_NAME privilege is also required to accomplish this task.
TOKEN_DUPLICATE Required to duplicate an access token.
TOKEN_EXECUTE Same as STANDARD_RIGHTS_EXECUTE.
TOKEN_IMPERSONATE Required to attach an impersonation access token to a process.
TOKEN_QUERY Required to query an access token.
TOKEN_QUERY_SOURCE Required to query the source of an access token.
TOKEN_READ Combines STANDARD_RIGHTS_READ and TOKEN_QUERY.
TOKEN_WRITE Combines STANDARD_RIGHTS_WRITE, TOKEN_ADJUST_PRIVILEGES, TOKEN_ADJUST_GROUPS, and TOKEN_ADJUST_DEFAULT.
TOKEN_ALL_ACCESS Combines all possible access rights for a token.

源码中第二个参数被设置为:TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY。表明对于打开的access token我们需要能够访问access token并作启用或禁用access token的某些特权。

PS:需要具备TOKEN_ADJUST_PRIVILEGES权限的access token才有提升权限的能力

如期执行的话将返回目标进程(notepad.exe)的access token(具备TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY的权限)

SetPrivilege -> LookupPrivilegeValue

1
2
3
4
5
BOOL LookupPrivilegeValueA(
[in, optional] LPCSTR lpSystemName,
[in] LPCSTR lpName,
[out] PLUID
); //返回LUID(系统中具备特权(lpName中描述)的LUID标识)

官方文档中有描述:The LookupPrivilegeValue function retrieves the locally unique identifier (LUID) used on a specified system to locally represent the specified privilege name.指出该API用于取回用于描述指定系统所所具备权限的LUID结构,大致作用是查看系统上所允许的令牌权限

第一个参数是一个以NULL结尾的字符串,指出想查看的系统。当第一个参数设置为NULL表明查询的是本地系统localhost

第二个参数指定特权名,马上提到

第三个参数用于接收指定权限的相关信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
//特权名,来源:winnt.h
////////////////////////////////////////////////////////////////////////
// //
// NT Defined Privileges //
// //
////////////////////////////////////////////////////////////////////////

#define SE_CREATE_TOKEN_NAME TEXT("SeCreateTokenPrivilege")
#define SE_ASSIGNPRIMARYTOKEN_NAME TEXT("SeAssignPrimaryTokenPrivilege")
#define SE_LOCK_MEMORY_NAME TEXT("SeLockMemoryPrivilege")
#define SE_INCREASE_QUOTA_NAME TEXT("SeIncreaseQuotaPrivilege")
#define SE_UNSOLICITED_INPUT_NAME TEXT("SeUnsolicitedInputPrivilege")
#define SE_MACHINE_ACCOUNT_NAME TEXT("SeMachineAccountPrivilege")
#define SE_TCB_NAME TEXT("SeTcbPrivilege")
#define SE_SECURITY_NAME TEXT("SeSecurityPrivilege")
#define SE_TAKE_OWNERSHIP_NAME TEXT("SeTakeOwnershipPrivilege")
#define SE_LOAD_DRIVER_NAME TEXT("SeLoadDriverPrivilege")
#define SE_SYSTEM_PROFILE_NAME TEXT("SeSystemProfilePrivilege")
#define SE_SYSTEMTIME_NAME TEXT("SeSystemtimePrivilege")
#define SE_PROF_SINGLE_PROCESS_NAME TEXT("SeProfileSingleProcessPrivilege")
#define SE_INC_BASE_PRIORITY_NAME TEXT("SeIncreaseBasePriorityPrivilege")
#define SE_CREATE_PAGEFILE_NAME TEXT("SeCreatePagefilePrivilege")
#define SE_CREATE_PERMANENT_NAME TEXT("SeCreatePermanentPrivilege")
#define SE_BACKUP_NAME TEXT("SeBackupPrivilege")
#define SE_RESTORE_NAME TEXT("SeRestorePrivilege")
#define SE_SHUTDOWN_NAME TEXT("SeShutdownPrivilege")
#define SE_DEBUG_NAME TEXT("SeDebugPrivilege")
#define SE_AUDIT_NAME TEXT("SeAuditPrivilege")
#define SE_SYSTEM_ENVIRONMENT_NAME TEXT("SeSystemEnvironmentPrivilege")
#define SE_CHANGE_NOTIFY_NAME TEXT("SeChangeNotifyPrivilege")
#define SE_REMOTE_SHUTDOWN_NAME TEXT("SeRemoteShutdownPrivilege")
#define SE_UNDOCK_NAME TEXT("SeUndockPrivilege")
#define SE_SYNC_AGENT_NAME TEXT("SeSyncAgentPrivilege")
#define SE_ENABLE_DELEGATION_NAME TEXT("SeEnableDelegationPrivilege")
#define SE_MANAGE_VOLUME_NAME TEXT("SeManageVolumePrivilege")
#define SE_IMPERSONATE_NAME TEXT("SeImpersonatePrivilege")
#define SE_CREATE_GLOBAL_NAME TEXT("SeCreateGlobalPrivilege")
#define SE_TRUSTED_CREDMAN_ACCESS_NAME TEXT("SeTrustedCredManAccessPrivilege")
#define SE_RELABEL_NAME TEXT("SeRelabelPrivilege")
#define SE_INC_WORKING_SET_NAME TEXT("SeIncreaseWorkingSetPrivilege")
#define SE_TIME_ZONE_NAME TEXT("SeTimeZonePrivilege")
#define SE_CREATE_SYMBOLIC_LINK_NAME TEXT("SeCreateSymbolicLinkPrivilege")

例子中传给SetPrivilege -> LookupPrivilegeValue的参数是LookupPrivilegeValue(NULL,,lpszPrivilege,&luid) )其中第二个参数是传递进来的 SE_DEBUG_NAME。所以完成的是查看当前系统是否具备SE_DEBUG_NAME的权限。LookupPrivilegeValue成功查询到,则返回非0值,并且保存 LUID在变量luid中,否则返回0,说明不具备相应权限。

关于权限SE_DEBUG_NAME:

If the debugging process has the SE_DEBUG_NAME privilege granted and enabled, it can debug any process.

具备权限SE_DEBUG_NAME并且处于enabled状态,则能够调试任何进程

结构体TOKEN_PRIVILEGES

这里需要补充的是一个结构体:TOKEN_PRIVILEGES

1
2
3
4
typedef struct _TOKEN_PRIVILEGES {
DWORD PrivilegeCount;
LUID_AND_ATTRIBUTES Privileges[ANYSIZE_ARRAY];
} TOKEN_PRIVILEGES, *PTOKEN_PRIVILEGES;

官方描述中给出:The TOKEN_PRIVILEGES structure contains information about a set of privileges for an access token.大致是说结构体TOKEN_PRIVILEGES包含给定的access token所具备的权限信息

第一个成员PrivilegeCount,标识access token所具备的权限条目数量,是必须要有的

第二个成员是指向结构体LUID_AND_ATTRIBUTES 的数组,每一个数组成员包含LUID 以及特权的属性

LUID_AND_ATTRIBUTES结构体

1
2
3
4
typedef struct _LUID_AND_ATTRIBUTES {
LUID Luid;
DWORD Attributes;
} LUID_AND_ATTRIBUTES, *PLUID_AND_ATTRIBUTES;

特权属性

Value Meaning
SE_PRIVILEGE_ENABLED The privilege is enabled.
SE_PRIVILEGE_ENABLED_BY_DEFAULT The privilege is enabled by default.
SE_PRIVILEGE_REMOVED Used to remove a privilege. For details, see AdjustTokenPrivileges.
SE_PRIVILEGE_USED_FOR_ACCESS The privilege was used to gain access to an object or service. This flag is used to identify the relevant privileges in a set passed by a client application that may contain unnecessary privileges.

接着分析

后续对声明的结构体TOKEN_PRIVILEGES tp赋值:

1
2
tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = luid; //这里luid是LookupPrivilegeValue查询具备权限SE_DEBUG_NAME权限的LUID

随后

1
2
3
4
if( true )
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
else
tp.Privileges[0].Attributes = 0;

完成的将luid中具备的SE_DEBUG_NAME的权限打开(enable)

SetPrivilege -> AdjustTokenPrivileges

1
2
3
4
5
6
7
8
BOOL AdjustTokenPrivileges(
[in] HANDLE TokenHandle,
[in] BOOL DisableAllPrivileges,
[in, optional] PTOKEN_PRIVILEGES NewState,
[in] DWORD BufferLength,
[out, optional] PTOKEN_PRIVILEGES PreviousState,
[out, optional] PDWORD ReturnLength
);

官方描述:The AdjustTokenPrivileges function enables or disables privileges in the specified access token. Enabling or disabling privileges in an access token requires TOKEN_ADJUST_PRIVILEGES access.

用于激活或禁止access token的相关特权,禁止或激活特权需要access token具备TOKEN_ADJUST_PRIVILEGES特权

第一个参数指向即将要修改的access token,这里是由前面OpenProcessToken中返回的access token并且是具备TOKEN_ADJUST_PRIVILEGES权限的

第二个参数用于指示是否关闭access token的全部权限,显然这里与我们目的相悖,因此为false

第三个参数的指针指向结构体TOKEN_PRIVILEGES,当第二个参数设置为false则AdjustTokenPrivileges根据这个结构体的内容关闭或禁止其中设置的权限,可以取值:SE_PRIVILEGE_ENABLED 或者 SE_PRIVILEGE_REMOVED 或者NONE(关闭),总的来说就是新特权的指针

第四个参数为字节为单位的PTOKEN_PRIVILEGES NewStateS所占的大小,如果NewState为空,该参数应为NULL

第五个参数也是一个指向 TOKEN_PRIVILEGES结构的指针,存放修改前的访问权限的信息,可空;

最后一个参数为实际PreviousState结构返回的大小,可空

1
2
AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), 
(PTOKEN_PRIVILEGES) NULL, (PDWORD) NULL)

如期执行函数则可以提升OpenProcessToken返回的access token的相关权限

回顾

  • 利用OpenProcessToken,返回一个能访问目标进程并且具备提升权限的access token
  • LookupPrivilegeValue,返回一个具备指定权限的LUID(local unique identifier)
  • 设置结构体TOKEN_PRIVILEGES
  • AdjustTokenPrivileges,利用上述步骤完成的参数铺垫设置,完成权限提升
Author: Victory+
Link: https://cvjark.github.io/2022/05/04/逆核No-9-Windows进程提权/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.