突破Session0隔离在系统服务程序中创建用户桌面进程

catastrophe

发布日期: 2019-01-10 14:11:25 浏览量: 1851
评分:
star star star star star star star star star star_border
*转载请注明来自write-bug.com

背景

病毒木马通常会把自己注入系统服务进程或是伪装成系统服务进程,运行在SESSION 0会话中。但是,处于SESSION 0会话的系统服务进程,无法与普通的用户进程通讯,不能通过Windows消息机制进行通信,更不能创建普通的用户进程,这是由于SESSION 0会话隔离导致的。

在Windows XP、Windows Server 2003,以及更老版本的Windows操作系统中,服务和应用程序使用相同的会话(SESSION)运行,而这个会话是由第一个登录到控制台的用户启动的,该会话就叫做SESSION 0。

将服务和用户应用程序一起在SESSION 0中运行会导致安全风险,因为服务会使用提升后的权限运行,而用户应用程序使用用户特权(大部分都是非管理员用户)运行,这会使得恶意软件以某个服务为攻击目标,通过“劫持”该服务,达到提升自己权限级别的目的。
从Windows Vista开始,只有服务可以托管到SESSION 0中,用户应用程序和服务之间会被隔离,并需要运行在用户登录到系统时创建的后续会话中。例如第一个登录的用户创建 Session 1,第二个登录的用户创建Session 2,以此类推。

使用不同会话运行的实体(应用程序或服务)如果不将自己明确标注为全局命名空间,并提供相应的访问控制设置,将无法互相发送消息,共享UI元素,或共享内核对象。

虽然Windows 7以及上版本的SESSION 0给服务层和应用层的通信造成了很大的难度,但并不代表没有办法实现服务层与应用层的通信与交互。微软提供了一系列WTS( Windows Terminal Service Windows终端服务)开头的函数,来完成服务层与应用层的交互。

接下来,本文将介绍突破SESSION 0会话隔离,在服务程序中创建用户桌面进程。

函数介绍

WTSGetActiveConsoleSessionId 函数

检索控制台会话的会话标识符Session Id。 控制台会话是当前连接到物理控制台的会话。 请注意,远程桌面服务不需要运行,以使此功能成功。

函数声明

  1. DWORD WTSGetActiveConsoleSessionId(void);

参数

  • 无参数

返回值

  • 执行成功,则返回连接到物理控制台的会话的会话标识符。
  • 如果没有连接到物理控制台的会话(例如,如果物理控制台会话正在附加或分离),则此函数返回0xFFFFFFFF。

WTSQueryUserToken 函数

获取由会话ID(Session Id)指定的登录用户的主访问令牌。 要成功调用此功能,调用应用程序必须在LocalSystem帐户的上下文中运行,并具有SE_TCB_NAME特权。

函数声明

  1. BOOL WTSQueryUserToken(
  2. _In_ ULONG SessionId,
  3. _Out_ PHANDLE phToken
  4. );

参数

  • SessionId [in]
    远程桌面服务会话标识符。 在服务上下文中运行的任何程序将具有一个会话标识符为零(0)。 您可以使用WTSEnumerateSessions函数来检索指定的RD会话主机服务器上所有会话的标识符。
    为了能够查询另一个用户会话的信息,您需要具有“查询信息”权限。 有关详细信息,请参阅远程桌面服务权限。 要修改会话的权限,请使用“远程桌面服务配置”管理工具。
  • phToken [out]
    如果该功能成功,则会收到一个指向登录用户的令牌句柄的指针。 请注意,您必须调用CloseHandle函数才能关闭该句柄。

返回值

  • 如果函数成功,则返回值为非零值,phToken参数指向用户的主令牌。
  • 如果函数失败,返回值为零。 要获取扩展错误信息,请调用GetLastError。

DuplicateTokenEx 函数

创建一个新的访问令牌,它与现有令牌重复。 此功能可以创建主令牌或模拟令牌。

函数声明

  1. BOOL WINAPI DuplicateTokenEx(
  2. _In_ HANDLE hExistingToken,
  3. _In_ DWORD dwDesiredAccess,
  4. _In_opt_ LPSECURITY_ATTRIBUTES lpTokenAttributes,
  5. _In_ SECURITY_IMPERSONATION_LEVEL ImpersonationLevel,
  6. _In_ TOKEN_TYPE TokenType,
  7. _Out_ PHANDLE phNewToken
  8. );

参数

  • hExistingToken [in]
    使用TOKEN_DUPLICATE访问权限打开访问令牌的句柄。

  • dwDesiredAccess [in]
    指定新令牌的请求访问权限。 DuplicateTokenEx函数将请求的访问权限与现有令牌的自由访问控制列表(DACL)进行比较,以确定哪些权限被授予或拒绝。要请求与现有令牌相同的访问权限,请指定零。要请求对呼叫者有效的所有访问权限,请指定MAXIMUM_ALLOWED。有关访问令牌的访问权限列表,请参阅访问令牌对象的访问权限。

  • lpTokenAttributes [in,optional]
    指向SECURITYATTRIBUTES结构的指针,该结构指定新令牌的安全描述符,并确定子进程是否可以继承令牌。如果lpTokenAttributes为NULL,则令牌获取默认安全描述符,并且该句柄不能被继承。如果安全描述符包含系统访问控制列表(SACL),即使在dwDesiredAccess中没有请求,令牌将获取ACCESS_SYSTEM SECURITY访问权限。要将所有者设置为新令牌的安全描述符,呼叫者的进程令牌必须设置SE_RESTORE_NAME权限。

  • ImpersonationLevel [in]
    指定SECURITY_IMPERSONATION_LEVEL枚举中指示新令牌的模拟级别的值。

  • TokenType [in]
    从TOKEN_TYPE枚举中指定以下值之一。

VALUE MEANING
TokenPrimary 新令牌是可以在CreateProcessAsUser函数中使用的主令牌
TokenImpersonation 新令牌是一个模拟令牌
  • phnewToken [out]
    指向接收新令牌的HANDLE变量的指针。完成使用新令牌后,调用CloseHandle函数来关闭令牌句柄。

返回值

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

CreateEnvironmentBlock 函数

检索指定用户的环境变量。 然后可以将此块传递给CreateProcessAsUser函数。

函数声明

  1. BOOL WINAPI CreateEnvironmentBlock(
  2. _Out_ LPVOID *lpEnvironment,
  3. _In_opt_ HANDLE hToken,
  4. _In_ BOOL bInherit
  5. );

参数

  • lpEnvironment [out]
    当该函数返回时,接收到指向新的环境块的指针。 环境块是一个以NULL结尾的Unicode字符串的数组。 列表以两个空值(\ 0 \ 0)结尾。
  • hToken [in]
    Logon为用户,从LogonUser函数返回。 如果这是主令牌,则令牌必须具有TOKEN_QUERY和TOKEN_DUPLICATE访问权限。 如果令牌是模拟令牌,则必须具有TOKEN_QUERY权限。 有关详细信息,请参阅访问令牌对象的访问权限。
    如果此参数为NULL,则返回的环境块仅包含系统变量。
  • bInherit[in]
    指定是否继承当前进程的环境。 如果该值为TRUE,则该进程将继承当前进程的环境。 如果此值为FALSE,则该进程不会继承当前进程的环境。

返回值

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

CreateProcessAsUser 函数

创建一个新进程及其主要线程,新进程在由指定令牌表示的用户的安全上下文中运行。

函数声明

  1. BOOL WINAPI CreateProcessAsUser(
  2. _In_opt_ HANDLE hToken,
  3. _In_opt_ LPCTSTR lpApplicationName,
  4. _Inout_opt_ LPTSTR lpCommandLine,
  5. _In_opt_ LPSECURITY_ATTRIBUTES lpProcessAttributes,
  6. _In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,
  7. _In_ BOOL bInheritHandles,
  8. _In_ DWORD dwCreationFlags,
  9. _In_opt_ LPVOID lpEnvironment,
  10. _In_opt_ LPCTSTR lpCurrentDirectory,
  11. _In_ LPSTARTUPINFO lpStartupInfo,
  12. _Out_ LPPROCESS_INFORMATION lpProcessInformation
  13. );

参数

  • hToken [in]
    表示用户的主令牌的句柄。句柄必须具有TOKEN_QUERY,TOKEN_DUPLICATE和TOKEN_ASSIGN_PRIMARY访问权限。
  • lpApplicationName [in]
    要执行的模块的名称。该模块可以是基于Windows的应用程序。
  • lpCommandLine [in,out]
    要执行的命令行。 该字符串的最大长度为32K个字符。 如果lpApplicationName为NULL,则lpCommandLine的模块名称部分限制为MAX_PATH个字符。
  • lpProcessAttributes [in]
    指向SECURITY_ATTRIBUTES结构的指针,该结构指定新进程对象的安全描述符,并确定子进程是否可以继承返回的进程的句柄。如果lpProcessAttributes为NULL或lpSecurityDescriptor为NULL,则该进程将获得默认安全描述符,并且该句柄不能被继承。
  • lpThreadAttributes [in]
    指向SECURITY_ATTRIBUTES结构的指针,该结构指定新线程对象的安全描述符,并确定子进程是否可以继承线程返回的句柄。如果lpThreadAttributes为NULL或lpSecurityDescriptor为NULL,则线程将获取默认安全描述符,并且该句柄不能被继承。
  • bInheritHandles [in]
    如果此参数为TRUE,则调用进程中的每个可继承句柄都由新进程继承。如果参数为FALSE,则不会继承手柄。请注意,继承的句柄具有与原始句柄相同的值和访问权限。
  • dwCreationFlags [in]
    控制优先级的标志和进程的创建。有关值的列表,请参阅过程创建标志。
    该参数还控制新进程的优先级,用于确定进程线程的调度优先级。
  • lpEnvironment [in]
    指向新进程的环境块的指针。如果此参数为NULL,则新进程将使用调用进程的环境。
  • lpCurrentDirectory [in]
    进程当前目录的完整路径。 字符串也可以指定一个UNC路径。
    如果此参数为NULL,则新进程将具有与调用进程相同的当前驱动器和目录。
  • lpStartupInfo [in]
    指向STARTUPINFO或STARTUPINFOEX结构的指针。用户必须具有对指定的窗口站和桌面的完全访问权限。
  • lpProcessInformation [out]
    指向一个PROCESS_INFORMATION结构的指针,用于接收关于新进程的标识信息。PROCESS_INFORMATION中的句柄必须在不再需要时使用CloseHandle才能关闭。

返回值

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

实现原理

由于SESSION 0会话隔离,使得在系统服务进程内不能通过直接调用CreateProcess等函数创建进程,而是通过过CreateProcessAsUser函数来创建。这样,创建的进程才会显示UI界面,与用户进行交互。

那么,在SESSION 0会话中创建用户桌面进程具体的实现流程如下所示。

首先,调用WTSGetActiveConsoleSessionId函数来获取当前程序的活动会话ID,即Session Id。该函数的调用不需要任何参数,直接返回Session Id。根据Session Id继续调用WTSQueryUserToken函数来检索用户令牌,并获取对应的用户令牌句柄。在不需要使用用户令牌句柄之后,可以调用CloseHandle函数来释放句柄。

然后,使用DuplicateTokenEx函数创建一个一个新令牌,并复制上述获取的用户令牌。设置新令牌的访问权限问MAXIMUM_ALLOWED,表示获取所有令牌权限。新访问令牌的模拟级别为SecurityIdentification,而且令牌类型为TokenPrimary,表示新令牌是可以在CreateProcessAsUser函数中使用的主令牌。

最后,根据新令牌调用CreateEnvironmentBlock函数创建一个环境块,用来传递给CreateProcessAsUser使用。在不需要使用进程环境块后,可以通过调用DestroyEnvironmentBlock函数进行释放。获取环境块之后,就可以调用CreateProcessAsUser来创建用户桌面进程了。CreateProcessAsUser函数的用法以及参数的含义与CreateProcess函数的用法和参数的含义类似。新令牌句柄作为用户主令牌的句柄,指定创建进程的路径,设置优先级和创建标志,设置STARTUPINFO结构信息,获取PROCESS_INFORMATION结构信息。

经过上述的操作,就完成了用户桌面进程的创建。但是,要注意的是,上述介绍的方法创建的用户桌面进程并没有继承服务程序的SYSTEM权限,而只是普通权限而已。要想创建一个SYSTEM权限的的子进程,可以通过设置进程访问令牌的安全描述符来实现,具体的实现步骤在此就不详细介绍了。

编码实现

创建用户桌面进程

  1. // 突破SESSION 0隔离创建用户进程
  2. BOOL CreateUserProcess(char *lpszFileName)
  3. {
  4. BOOL bRet = TRUE;
  5. DWORD dwSessionID = 0;
  6. HANDLE hToken = NULL;
  7. HANDLE hDuplicatedToken = NULL;
  8. LPVOID lpEnvironment = NULL;
  9. STARTUPINFO si = { 0 };
  10. PROCESS_INFORMATION pi = { 0 };
  11. si.cb = sizeof(si);
  12. do
  13. {
  14. // 获得当前Session ID
  15. dwSessionID = ::WTSGetActiveConsoleSessionId();
  16. // 获得当前Session的用户令牌
  17. if (FALSE == ::WTSQueryUserToken(dwSessionID, &hToken))
  18. {
  19. ShowMessage("WTSQueryUserToken", "ERROR");
  20. bRet = FALSE;
  21. break;
  22. }
  23. // 复制令牌
  24. if (FALSE == ::DuplicateTokenEx(hToken, MAXIMUM_ALLOWED, NULL,
  25. SecurityIdentification, TokenPrimary, &hDuplicatedToken))
  26. {
  27. ShowMessage("DuplicateTokenEx", "ERROR");
  28. bRet = FALSE;
  29. break;
  30. }
  31. // 创建用户Session环境
  32. if (FALSE == ::CreateEnvironmentBlock(&lpEnvironment,
  33. hDuplicatedToken, FALSE))
  34. {
  35. ShowMessage("CreateEnvironmentBlock", "ERROR");
  36. bRet = FALSE;
  37. break;
  38. }
  39. // 在复制的用户Session下执行应用程序,创建进程
  40. if (FALSE == ::CreateProcessAsUser(hDuplicatedToken,
  41. lpszFileName, NULL, NULL, NULL, FALSE,
  42. NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE | CREATE_UNICODE_ENVIRONMENT,
  43. lpEnvironment, NULL, &si, &pi))
  44. {
  45. ShowMessage("CreateProcessAsUser", "ERROR");
  46. bRet = FALSE;
  47. break;
  48. }
  49. } while (FALSE);
  50. // 关闭句柄, 释放资源
  51. if (lpEnvironment)
  52. {
  53. ::DestroyEnvironmentBlock(lpEnvironment);
  54. }
  55. if (hDuplicatedToken)
  56. {
  57. ::CloseHandle(hDuplicatedToken);
  58. }
  59. if (hToken)
  60. {
  61. ::CloseHandle(hToken);
  62. }
  63. return bRet;
  64. }

程序测试

因为程序要实现的是突破SESSION 0会话隔离,在系统服务程序中创建用户桌面进程。程序必须要注册成为一个系统服务进程,这样才处于SESSION 0会话中。服务程序的入口点与普通程序的入口点不同,需要通过调用函数StartServiceCtrlDispatcher来设置服务入口点函数。对于创建服务程序的知识本文没有进行具体讲解,读者可以阅读配套的示例代码来理解该部分内容。同时,本文还开发了一个服务加载器ServiceLoader.exe(该加载器的源码可以在相应章节的配套示例代码中找到),用来将测试程序加载为服务进程。

在 main 函数中,设置服务入口点函数,成为服务程序,并在服务程序中调用上述封装好的函数进行测试。首先,以管理员身份运行服务加载器ServiceLoader.exe,这样服务加载器会将CreateProcessAsUser_Test.exe程序加载为服务进程,便会执行上述创建用户进程的代码。服务加载器提示创建和启动服务成功后,立即成功显示对话框和启动“520.exe”程序,而且窗口界面也成功显示,如图4-2所示。

然后,使用进程查看器ProcessExplorer.exe查看CreateProcessAsUser_Test.exe进程以及520.exe进程的SESSION值,如图4-3所示,CreateProcessAsUser_Test.exe进程处于SESSION 0,而520.exe处于SESSION 1。

总结

突破SESSION 0会话隔离创建用户进程,要求程序处于SESSION 0会发才会有效。创建服务程序,需要在 main 中设置服务程序入口点函数,这样才能成功为程序创建系统服务。该程序的实现,关键是调用CreateProcessAsUser函数。需要程序创建并复制一个新的访问令牌,并获取访问令牌的进程环境块信息。

本文介绍的方法并没有对进程访问令牌进行设置,所以导致创建出来的用户桌面进程是用户默认权限而已,并没有继承SYSTEM权限。

参考

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

上传的附件 cloud_download CreateProcessAsUser_Test.7z ( 193.54kb, 29次下载 )

发送私信

如果你改变不了沙漠,又没本事离开沙漠,那你只能把自己变成仙人掌

8
文章数
13
评论数
最近文章
eject