使用AdjustTokenPrivileges函数提升进程访问令牌的权限

Leftme

发布日期: 2019-01-12 11:57:43 浏览量: 766
评分:
star star star star star star star star star star_border
*转载请注明来自write-bug.com

背景

在我们编程实现一些系统操作的时候,往往要求我们执行操作的进程拥有足够的权限方可成功操作。比如,我们使用 ExitWindows 函数实现关机或重启操作的时候,就要求我们的进程要有 SE_SHUTDOWN_NAME 的权限,否则,会忽视不执行操作。这时,我们唯一能够做的,就是按照要求,提升我们进程的权限。

本文要讲解的就是这样一个程序,编程实现提升进程访问令牌的权限。主要涉及到 3 个WIN32 API函数的使用:OpenProcessToken、LookupPrivilegeValue 以及 AdjustTokenPrivileges 函数。

现在,我就把提取权限的实现过程整理成文档,分享给大家。

函数介绍

OpenProcessToken 函数

打开与进程关联的访问令牌。

函数声明

  1. BOOL WINAPI OpenProcessToken(
  2. _In_ HANDLE ProcessHandle,
  3. _In_ DWORD DesiredAccess,
  4. _Out_ PHANDLE TokenHandle
  5. );

参数

  • ProcessHandle [in]
    要打开访问令牌的进程的句柄。 该进程必须具有PROCESS_QUERY_INFORMATION访问权限。
  • DesiredAccess [in]
    指定一个访问掩码,指定访问令牌的请求类型。 这些请求的访问类型与令牌的自由访问控制列表(DACL)进行比较,以确定哪些访问被授予或拒绝。
  • TokenHandle [出]
    指向一个句柄的指针,用于标识当函数返回时新打开的访问令牌。

返回值

  • 如果函数成功,则返回值不为零。
  • 如果函数失败,返回值为零。 要获取扩展错误信息,请调用GetLastError。

LookupPrivilegeValue 函数

查看系统权限的特权值,返回信息到一个LUID结构体里。

函数声明

  1. BOOL WINAPI LookupPrivilegeValue(
  2. _In_opt_ LPCTSTR lpSystemName,
  3. _In_ LPCTSTR lpName,
  4. _Out_ PLUID lpLuid
  5. );

参数

  • lpSystemName [in]
    指向以NULL结尾的字符串的指针,该字符串是指向要获取特权值的系统名称。 如果指定了空字符串,则该函数尝试在本地系统上查找特权名称。
  • lpName [in]
    指向空终止字符串的指针,指定特权的名称,在Winnt.h头文件中定义。 例如,该参数可以指定常量SE_SECURITY_NAME或其相应的字符串“SeSecurityPrivilege”。
  • lpLuid [out]
    指向LUID变量的指针,该变量接收由lpSystemName参数指定的系统上已知权限的LUID。

返回值

  • 如果函数成功,函数将返回非零值。
  • 如果函数失败,则返回零。 要获取扩展错误信息,请调用GetLastError。

AdjustTokenPrivileges 函数

启用或禁用指定的访问令牌中的权限。 在访问令牌中启用或禁用权限需要TOKEN_ADJUST_PRIVILEGES访问。

函数声明

  1. BOOL WINAPI AdjustTokenPrivileges(
  2. _In_ HANDLE TokenHandle,
  3. _In_ BOOL DisableAllPrivileges,
  4. _In_opt_ PTOKEN_PRIVILEGES NewState,
  5. _In_ DWORD BufferLength,
  6. _Out_opt_ PTOKEN_PRIVILEGES PreviousState,
  7. _Out_opt_ PDWORD ReturnLength
  8. );

参数

  • TokenHandle [in]
    访问令牌的句柄,其中包含要修改的权限。 句柄必须有TOKEN_ADJUST_PRIVILE GES访问令牌。 如果PreviousState参数不为NULL,则该句柄还必须具有TOKEN_Q UERY访问权限。

  • DisableAllPrivileges [in]
    指定该功能是否禁用所有令牌的权限。 如果此值为TRUE,该函数将禁用所有权限,并忽略NewState参数。 如果为FALSE,则该函数将根据NewState参数指向的信息修改权限。

  • NewState [in]
    指向TOKEN_PRIVILEGES结构的指针,该结构指定特权数组及其属性。 如果DisableAllPrivileges参数为FALSE,则AdjustTokenPrivileges函数将启用,禁用或删除令牌的这些权限。 下表描述了基于特权属性的AdjustTokenPrivileges函数执行的操作:

VALUE MEANING
SE_PRIVILEGE_ENABLED 启用此特权
SE_PRIVILEGE_REMOVED 特权从令牌中的特权列表中删除
None 禁用此特权
  • BufferLength [in]
    指定由PreviousState参数指向的缓冲区的大小(以字节为单位)。如果PreviousState参数为NULL,则此参数可以为 0。

  • PreviousState[out]
    指向缓冲区的指针,该函数使用包含函数修改的任何特权的先前状态的TOKEN_PRIVILEGES结构填充。也就是说,如果此功能已修改特权,则该特权及其先前状态将包含在由PreviousState引用的TOKEN_PRIVILEGES结构中。如果TOKEN_PRIVILEGES的PrivilegeCount成员为零,则此功能不会更改任何权限。此参数可以为NULL。
    如果指定的缓冲区太小,无法接收修改权限的完整列表,则该函数将失败,并且不调整任何权限。在这种情况下,该函数将ReturnLength参数指向的变量设置为保存已修改权限的完整列表所需的字节数。

  • ReturnLength [out]
    指向一个变量的指针,该变量接收由PreviousState参数指向的缓冲区所需的大小(以字节为单位)。如果PreviousState为NULL,则此参数可以为NULL。

返回值

  • 如果函数成功,则返回值不为零。 要确定该函数是否调整了所有指定的权限,请调用GetLastError,该函数在函数成功时返回以下值之一:
VALUE MEANING
ERROR_SUCCESS 该函数调整所有指定的权限
ERROR_NOT_ALL_ASSIGNED 该标记没有在NewState参数中指定的一个或多个特权。 即使没有调整任何权限,该功能也可能会以此错误值成功。 PreviousState参数指示已调整的权限。

  • 如果函数失败,返回值为零。 要获取扩展错误信息,请调用GetLastError。

实现过程

首先,我们需要调用 OpenProcessToken 函数打开指定进程令牌,并获取 TOKEN_ADJUST_PRIVILEGES 权限的令牌句柄。之所以要获取进程令牌权限为 TOKEN_ADJUST_PRIVILEGES,是因为 AdjustTokenPrivileges 函数,要求要有此权限,方可修改进程令牌的访问权限。

其中,第 1 个参数表示要打开进程令牌的进程句柄;第 2 个参数表示我们对进程令牌具有的权限,TOKEN_ADJUST_PRIVILEGES就表示,我们有修改进程令牌的权限;第 3 个参数表示返回的进程令牌句柄。

  1. //打开进程令牌并获取具有 TOKEN_ADJUST_PRIVILEGES 权限的进程令牌句柄
  2. bRet = ::OpenProcessToken(hProcess, TOKEN_ADJUST_PRIVILEGES, &hToken);
  3. if (FALSE == bRet)
  4. {
  5. ShowError("OpenProcessToken");
  6. return FALSE;
  7. }

然后,我们调用 LookupPrivilegeValue 函数,获取本地系统指定特权名称的LUID值,这个LUID值就相当于该特权的身份标号。

其中,第 1 个参数表示系统,NULL表示本地系统,即要获取本地系统的指定特权的LUID值;第 2 个参数表示特权名称;第 3 个参数表示获取到的LUID返回值。

  1. // 获取本地系统的 pszPrivilegesName 特权的LUID值
  2. bRet = ::LookupPrivilegeValue(NULL, pszPrivilegesName, &luidValue);
  3. if (FALSE == bRet)
  4. {
  5. ShowError("LookupPrivilegeValue");
  6. return FALSE;
  7. }

接着,我们就开始对 TOKEN_PRIVILEGES 进程令牌特权结构体进行赋值设置,设置设置新特权的数量、特权对应的LUID值以及特权的属性状态。其中,tokenPrivileges.PrivilegeCount表示设置新特权的特权数量;tokenPrivileges.Privileges[i].Luid表示第 i 个特权对应的LUID值;tokenPrivileges.Privileges[0].Attributes表示特权的属性;SE_PRIVILEGE_ENABLED就表示启用该特权。

  1. // 设置提升权限信息
  2. tokenPrivileges.PrivilegeCount = 1;
  3. tokenPrivileges.Privileges[0].Luid = luidValue;
  4. tokenPrivileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;

最后,我们调用 AdjustTokenPrivileges 函数对进程令牌的特权进行修改,将上面设置好的新特权设置到进程令牌中。

其中,第 1个参数表示进程令牌;第 2 个参数表示能是否禁用所有令牌的权限,FALSE则不禁用;第 3个参数是新设置的特权,指向设置好的令牌特权结构体;第 4 个参数表示返回上一个特权数据缓冲区的大小,不获取,则可以设为 0;第 5 个参数表示返回上一个特权数据缓冲区,不接收返回数据,可以设为 NULL;第 6 个参数表示接收返回上一个特权数据缓冲区应该有的大小。

  1. // 提升进程令牌访问权限
  2. bRet = ::AdjustTokenPrivileges(hToken, FALSE, &tokenPrivileges, 0, NULL, NULL);
  3. if (FALSE == bRet)
  4. {
  5. ShowError("AdjustTokenPrivileges");
  6. return FALSE;
  7. }

但是,需要注意的是,AdjustTokenPrivileges 返回 TRUE,并不代表特权就设置成功,还需要使用 GetLastError 来判断错误吗返回值。若错误码返回值为ERROR_SUCCESS,则所有特权设置成功;若为 ERROR_NOT_ALL_ASSIGNED,则表示并不是所有特权都设置成功。

  1. dwRet = ::GetLastError();
  2. if (ERROR_SUCCESS == dwRet)
  3. {
  4. return TRUE;
  5. }
  6. else if (ERROR_NOT_ALL_ASSIGNED == dwRet)
  7. {
  8. ShowError("ERROR_NOT_ALL_ASSIGNED");
  9. return FALSE;
  10. }

换句话说,如果你只提升了一个特权,且错误码为ERROR_NOT_ALL_ASSIGNED,那么这就是说明提升失败了。如果程序运行在 Win7 或者 Win7 以上版本的操作系统,可以试着以管理员身份运行程序,这样就可以成功提升进程令牌的访问权限。

编码实现

  1. BOOL EnbalePrivileges(HANDLE hProcess, char *pszPrivilegesName)
  2. {
  3. HANDLE hToken = NULL;
  4. LUID luidValue = {0};
  5. TOKEN_PRIVILEGES tokenPrivileges = {0};
  6. BOOL bRet = FALSE;
  7. DWORD dwRet = 0;
  8. // 打开进程令牌并获取进程令牌句柄
  9. bRet = ::OpenProcessToken(hProcess, TOKEN_ADJUST_PRIVILEGES, &hToken);
  10. if (FALSE == bRet)
  11. {
  12. ShowError("OpenProcessToken");
  13. return FALSE;
  14. }
  15. // 获取本地系统的 pszPrivilegesName 特权的LUID值
  16. bRet = ::LookupPrivilegeValue(NULL, pszPrivilegesName, &luidValue);
  17. if (FALSE == bRet)
  18. {
  19. ShowError("LookupPrivilegeValue");
  20. return FALSE;
  21. }
  22. // 设置提升权限信息
  23. tokenPrivileges.PrivilegeCount = 1;
  24. tokenPrivileges.Privileges[0].Luid = luidValue;
  25. tokenPrivileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
  26. // 提升进程令牌访问权限
  27. bRet = ::AdjustTokenPrivileges(hToken, FALSE, &tokenPrivileges, 0, NULL, NULL);
  28. if (FALSE == bRet)
  29. {
  30. ShowError("AdjustTokenPrivileges");
  31. return FALSE;
  32. }
  33. else
  34. {
  35. // 根据错误码判断是否特权都设置成功
  36. dwRet = ::GetLastError();
  37. if (ERROR_SUCCESS == dwRet)
  38. {
  39. return TRUE;
  40. }
  41. else if (ERROR_NOT_ALL_ASSIGNED == dwRet)
  42. {
  43. ShowError("ERROR_NOT_ALL_ASSIGNED");
  44. return FALSE;
  45. }
  46. }
  47. return FALSE;
  48. }

总结

要特别注意一点,如果 AdjustTokenPrivileges 函数执行的返回值为 TRUE,但是使用 GetLastError 获取错误码却是 ERROR_NOT_ALL_ASSIGNED ,则表示并没有将所有设置的特权全部提升成功。换句话说,如果你只提升了一个特权,那么这就是说明提升失败了。如果程序运行在 Windows7 或者 Windows7 以上版本的操作系统,可以试着以管理员身份运行程序,这样就可以成功提升进程令牌的访问权限了。

参考

参考自《Windows黑客编程技术详解》一书

上传的附件 cloud_download AdjustTokenPrivileges_Test.7z ( 144.58kb, 2次下载 )

发送私信

告别错的,方可遇见对的

16
文章数
17
评论数
最近文章
eject