GA的存储挂载其实在上周三外围工作基本已经完成了,但是在后续这几天开发工作中遇到了一个很大的问题,是关于GA的捕捉模式,很可能有违我们修改GA的初衷。这里具体碰到哪些问题就先不说了,还是先介绍完GA存储实现的方式,等到下一篇文章中我们的研究方向可能会有改变,不过GA的源码分析总会坚持下去的。
1. Windows session机制
2. 以模拟域帐号的身份启动应用进程
1> 模拟用户身份
2> 多字节到宽字节的转换
3. Powershell执行mount和dismount操作
4. GA服务器端添加存储模块
一、Windows session机制
为什么会提到windows的session机制呢?我们来分析一下,GamingAnywhere的服务端是运行在win8系统上的,该系统是以域帐号管理员的身份登录的,一旦服务端启动我们就不会手动去切换用户,基于视频流捕捉的机制,客户端连接过来后它所看到的窗口是在服务端实际运行的一个进程,而你所看到的以及你所拥有的权限都取决于运行在服务器上的该进程的权限。还记得我们分析GamingAnywhere的初衷吗?就是想利用它的这种方式来启动一个应用程序,比如:Unity,用户在启动后进行编辑势必要将自己编辑的内容保存下来,那么保存在哪?就是我们需要解决的问题,这必须面临着不同用户的权限控制。还记得以RDP登录的方式吧?它就是利用windows的session机制,每一个用户利用RDP登录后,实际上在windows上就会新建出一个session,windows就是利用它来完成不同用户的权限控制的(当然还要依赖其它方式的辅助)。那我们既要避开使用RDP这种方式,又要实现权限控制,如何实现呢?答案就是:以模拟用户的身份启动进程,那么改进程拥有的身份及权限就是那个模拟用户的。下面就先来了解一下Windows的session机制吧(先来一幅图):
现在我们知道,每一个用户登录windows系统后(利用域控可以允许多用户同时登录),都会产生一个标识他身份的session,它可以启动它自己的应用程序(进程),打开任意的窗口等。每个进程都唯一属于它的启动用户,同时也拥有一个session。也就是说,session是由代表单个用户登录会话的所有进程和系统对象组成的。其中的对象包括所有的窗口,桌面和windows stations。windows station基本上可以被描述为包含特定桌面和进程的安全边界,一个session可以包含多个windows station,而每个windows station又可拥有多个桌面。
在多个windows station中,只有被称为winsta0的windows station才被允许与用户交互。在winsta0下共被载入三个桌面:winlogon(登录界面),default(缺省,即用户桌面)和ScreenSaver(屏幕保护桌面)。它们三个都有各自的显存,这就是为什么当你锁定工作站时你的主桌面会消失。当你锁定工作站时,屏幕由用户界面切换到登录界面,并且两者之间并没有交互。
基于上面的原理,我们不会实际创建出一个session出来,在同一个session下模拟出另一个用户
二、以模拟域帐号的身份启动应用进程
通过模拟不同域帐号的身份启动不同的应用程序,就好像每个用户都登录了启动各自的应用程序,然后将创作的文件保存在各自的存储下一样,这里我们用的存储是Linux利用samba共享出来的用户目录,平台用户每注册一个,就自动生成该目录,并设置磁盘配额,这样既能限制用户最大磁盘使用量又可以保存在各自的目录下互不可见,从而完成权限控制。
1> 利用Windows API:CreateProcessWithLogonW模拟不同用户的身份
关于:CreateProcessWithLogonW函数,MSDN上是这样描述的:“Creates a new process and its primary thread. Then the new process runs the specified executable file in the security context of the specified credentials(user , domain , and password). It can optionally load the user profile for a specified user. This function is similar to the CreateProcessAsUser and CreateProcessWithTokenW functions , except that the caller does not need to call the LogonUser function to authenticate the user and get a token.”,这句话是说“该函数用来创建一个新的进程和它的主线程。新建的进程以指定凭证(用户名、密码、域)的安全上下文运行exe文件。它可以可选择地加载指定用户的配置文件。这个函数和CreateProcessAsUser和CreateProcessWithTokenW函数很像,除了调用者无需通过调用LogonUser函数来验证一个用户并获得一个标识。” , 它的基本语法为:
BOOL WINAPI CreateProcessWithLogonW( _In_ LPCWSTR lpUsername, _In_opt_ LPCWSTR lpDomain, _In_ LPCWSTR lpPassword, _In_ DWORD dwLogonFlags, _In_opt_ LPCWSTR lpApplicationName, _Inout_opt_ LPWSTR lpCommandLine, _In_ DWORD dwCreationFlags, _In_opt_ LPVOID lpEnvironment, _In_opt_ LPCWSTR lpCurrentDirectory, _In_ LPSTARTUPINFOW lpStartupInfo, _Out_ LPPROCESS_INFORMATION lpProcessInfo );
其中几个重要的参数为:lpUsername,为需要模拟的用户的名称;lpDomain,为需要模拟的用户所在的域;lpPassword,为需要模拟的用户的登录密码;lpApplicationName,为待执行模块的名称;lpCommandLine,为application后面需要跟的参数。lpStartupInfo,用来设置产生的窗口的一些属性;lpProcessInfo,是返回值,从里面可以获取到进程和主线程的信息,例如:进程/线程句柄(hProcess/hThread)以及进程/线程ID(dwProcessId/dwThreadId)。
这里有一点需要注意:挂载存储和启动应用程序都必须以模拟用户的身份执行才可以。我在第一次测试的时候只将启动应用程序来以模拟用户身份执行了,结果存储正常挂载了,以administrator的视角可以看到,在画图软件中保存的时候却看不到。这里我们执行挂载存储是在命令行下执行一条Powershell的命令,注意:这里执行使用命令行就可以了,很简单,不要使用Windows提供的API函数。如果只是执行一条命令,在使用这个函数的时候将lpApplicationName设为NULL, 命令都放在lpCommandLine里面就可以了。
2> 多字节到宽字节的转换
CreateProcessWithLogonW()其中的参数采用的是宽字节,何谓宽字节呢?就是Unicode编码,无论是英文字符还是中文字符都采用2个字节来表示,这种方式很适合将应用程序移植到不同的平台上而不会产生其它问题;多字节就是一个英文字符采用一个字节,一个汉字采用2个字节,这种方式影响应用程序的通用性,因此我们提倡要尽量采用Unicode编码。
多字节到宽字节的转换,相关参考链接:http://blog.csdn.net/thdxs/article/details/8501053 。
这里采用MultiByteToWideChar()函数完成多字节到宽字节的转换,先把该函数中的一个参数设为-1,函数返回转换后应占用的字节数,然后再进行转换,实现代码:
#include "StdAfx.h" #include "utilities.h" // added by houqd 2014/0304 convert multi-bytes to wide-bytes WCHAR * convert_multi_to_wideBytes(const char *str) { int str_len = MultiByteToWideChar(CP_ACP , 0 , str , -1 , NULL , 0 ); WCHAR * pwtr = new WCHAR[str_len]; int len2 = MultiByteToWideChar(CP_ACP , 0 , str , -1 , pwtr , str_len); return pwtr ; }
三、Powershell执行挂载(mount)和解挂载(dismount)操作
cmd下Powershell执行挂载的命令类似:char *mount_str = "cmd /c powershell -command \"net use Z: \\\\%s\\%s /user:%s\\%s %s\"";
cmd下Powershell接挂载的命令类似 :char *dis_mount_str = "cmd /c powershell -command \"net use Z: /del\"";
实现代码(注:这里也是以dll的方式提供do-mount和dis-mount操作):
MOUNTSTORAGE_API bool do_mount(char *username , char *passwd , char *domain , char *storage_ip , char *studio_id) { STARTUPINFO si ; PROCESS_INFORMATION pi ; char mount_str_str[1024] = {0}; ZeroMemory(&si , sizeof(si)); ZeroMemory(&pi , sizeof(pi)); si.cb = sizeof(si); si.wShowWindow = SW_HIDE ; // 1> joint powershell string char *mount_str = "cmd /c powershell -command \"net use Z: \\\\%s\\%s /user:%s\\%s %s\""; sprintf(mount_str_str , mount_str , storage_ip , studio_id , domain , username , passwd); // 2> convert char* to LPWSTR WCHAR *username2 = convert_multi_to_wideBytes(username); WCHAR *domain2 = convert_multi_to_wideBytes(domain); WCHAR *passwd2 = convert_multi_to_wideBytes(passwd); WCHAR *mount_str2 = convert_multi_to_wideBytes(mount_str_str); // 3> impersonate user identify to execute mount storage if(CreateProcessWithLogonW(username2 , domain2 , passwd2 , 0 , NULL , mount_str2 , NORMAL_PRIORITY_CLASS , NULL , NULL , &si , &pi)){ return true ; }else{ return false ; } } MOUNTSTORAGE_API bool dis_mount(char *username , char *passwd , char *domain) { STARTUPINFO si ; PROCESS_INFORMATION pi ; char dis_mount_str_str[1024] = {0} ; ZeroMemory(&si , sizeof(si)); ZeroMemory(&pi , sizeof(pi)); si.cb = sizeof(si); si.wShowWindow = SW_HIDE; //char *dis_mount_str = "cmd /c powershell -command \"net use \\\\%s\\%s /del\""; char *dis_mount_str = "cmd /c powershell -command \"net use Z: /del\""; //sprintf(dis_mount_str_str , dis_mount_str , storage_ip , studio_id); WCHAR *username2 = convert_multi_to_wideBytes(username); WCHAR *passwd2 = convert_multi_to_wideBytes(passwd); WCHAR *domain2 = convert_multi_to_wideBytes(domain); //WCHAR *dis_mount_str2 = convert_multi_to_wideBytes(dis_mount_str_str); WCHAR *dis_mount_str2 = convert_multi_to_wideBytes(dis_mount_str); if(CreateProcessWithLogonW(username2 , domain2 , passwd2 , 0 , NULL , dis_mount_str2 , NORMAL_PRIORITY_CLASS , NULL , NULL , &si , &pi)){ return true ; }else{ return false ; } }四、GA服务器端添加存储模块
和前面添加用户认证模块一样,挂载存储模块也采用单独的子项目,以dll的形式提供do-mount和dis-mount接口。
使用代码:
if (CreateProcess(app_exe, app_exe, NULL, NULL, TRUE, NORMAL_PRIORITY_CLASS, NULL, NULL, &startupInfo, &procInfo) == 0) { fprintf(stderr, "CreateProcess failed: 0x%08x\n", GetLastError()); return NULL; }else{ fprintf(stderr , "CreateProcess success and processId = %d.\n" , procInfo.dwProcessId); // added by houqd 2014/3/3 , impersonate user identity and mount storage mnt_res = do_mount(username , passwd , "vsochina" , "10.3.4.6" , "studio_1323"); if(mnt_res){ fprintf(stderr , "[mount-storage success] username = %s , passwd = %s and domain = vsochina and storage_ip = 10.3.4.6 and studio_id = studio_2373" , username , passwd); }else{ fprintf(stderr , "[mount-storage failed] username = %s , passwd = %s and domain = vsochina and storage_ip = 10.3.4.6 and studio_id = studio_2373" , username , passwd); } }
经过上面的步骤,就可以完成在administrator登录的情况下,模拟其它用户登录,启动应用程序并挂载存储,间接地完成对用户操作的权限控制,至于本地磁盘可以在组策略里面进行配置隐藏。
今天就说到这里,GA剩下还面临着很多工作,整体框架的改动:原始代码捕捉和利用Nvidia库进行捕捉的可配置切换;视频流传输优化等,其中这里面ffmpeg和live555也要详加分析才行。存储这里使我对windows权限控制有了更进一步的了解,这种解决问题的思路对我是很重要的。
加油,只要有进步,哪怕再小都是值得珍惜的。
【GamingAnywhere源码分析之知识补充五】Windows模拟用户session完成存储权限控制,布布扣,bubuko.com