1、vnc-4.0-winsrc  版本中实现模拟发送ATL+CTRL+DEL

在工程wrfb_win32m中找到模拟发送ATL+CTR_DEL 的代码

 

在Service.h中有

  1. // -=- Routines used by the SInput Keyboard class to emulate Ctrl-Alt-Del
  2. // Returns false under Win9x
  3. bool emulateCtrlAltDel();


它的实现是:

  1. bool
  2. rfb::win32::emulateCtrlAltDel() {
  3. if (!osVersion.isPlatformNT)
  4. return false;
  5. CADThread* cad_thread = new CADThread();
  6. vlog.debug("emulate Ctrl-Alt-Del");
  7. if (cad_thread) {
  8. cad_thread->start();
  9. cad_thread->join();
  10. bool result = cad_thread->result;
  11. delete cad_thread;
  12. return result;
  13. }
  14. return false;
  15. }

(1)首先判断是不是NT家族的操作系统

OSVersionInfo继续于系统的结构体OSVERSIONINFO(这样的设计是为了考虑到后面要调用系统API:GetVersionEx)

  1. namespace rfb {
  2. namespace win32 {
  3. extern struct OSVersionInfo : OSVERSIONINFO {
  4. OSVersionInfo();
  5. // Is the OS one of the NT family (NT 3.51, NT4.0, 2K, XP, etc.)?
  6. bool isPlatformNT;
  7. // Is one of the Windows family?
  8. bool isPlatformWindows;
  9. // Is this OS one of those that blue-screens when grabbing another desktop (NT4 pre SP3)?
  10. bool cannotSwitchDesktop;
  11. } osVersion;
  12. };
  13. };

它的系统信息在OSVersionInfo构造函数时获得:

  1. OSVersionInfo::OSVersionInfo() {
  2. // Get OS Version Info
  3. ZeroMemory(static_cast(this), sizeof(this));
  4. dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
  5. if (!GetVersionEx(this))
  6. throw rdr::SystemException("unable to get system version info", GetLastError());
  7. // Set the special extra flags
  8. isPlatformNT = dwPlatformId == VER_PLATFORM_WIN32_NT;
  9. isPlatformWindows = dwPlatformId == VER_PLATFORM_WIN32_WINDOWS;
  10. cannotSwitchDesktop = isPlatformNT && (dwMajorVersion==4) &&
  11. ((_tcscmp(szCSDVersion, _T("")) == 0) ||
  12. (_tcscmp(szCSDVersion, _T("Service Pack 1")) == 0) ||
  13. (_tcscmp(szCSDVersion, _T("Service Pack 2")) == 0));
  14. }

(2)创建CADThread对象

CADThread* cad_thread = new CADThread();

CADThread类继续自Thread
Thread的设计是值得我们学习的。

  1. class Thread {
  2. public:
  3. Thread(const char* name_=0);
  4. virtual ~Thread();
  5. virtual void run();
  6. virtual void start();
  7. virtual Thread* join();
  8. const char* getName() const;
  9. ThreadState getState() const;
  10. // Determines whether the thread should delete itself when run() returns
  11. // If you set this, you must NEVER call join()!
  12. void setDeleteAfterRun() {deleteAfterRun = true;};
  13. unsigned long getThreadId() const;
  14. static Thread* self();
  15. friend class Condition;
  16. protected:
  17. Thread(HANDLE thread_, DWORD thread_id_);
  18. static DWORD WINAPI threadProc(LPVOID lpParameter);
  19. HANDLE thread;
  20. DWORD thread_id;
  21. char* name;
  22. ThreadState state;
  23. Condition* sig;
  24. Mutex mutex;
  25. HANDLE cond_event;
  26. Thread* cond_next;
  27. bool deleteAfterRun;
  28. };


CADThread重载了Thread类的run函数,CADThread的run函数先保存原有的桌面,然后切换到Winlogon桌面,然后发送ATL_CTRL_DEL,

最后把桌面还原。

  1. class CADThread : public Thread {
  2. public:
  3. CADThread() : Thread("CtrlAltDel Emulator"), result(false) {}
  4. virtual void run() {
  5. HDESK old_desktop = GetThreadDesktop(GetCurrentThreadId());
  6. if (switchToDesktop(OpenDesktop(_T("Winlogon"), 0, FALSE, DESKTOP_CREATEMENU | DESKTOP_CREATEWINDOW |
  7. DESKTOP_ENUMERATE | DESKTOP_HOOKCONTROL |
  8. DESKTOP_WRITEOBJECTS | DESKTOP_READOBJECTS |
  9. DESKTOP_SWITCHDESKTOP | GENERIC_WRITE))) {
  10. PostMessage(HWND_BROADCAST, WM_HOTKEY, 0, MAKELONG(MOD_ALT | MOD_CONTROL, VK_DELETE));
  11. switchToDesktop(old_desktop);
  12. result = true;
  13. }
  14. }
  15. bool result;
  16. };


 

(3)使用(2)得到的对象调用功能

  1. cad_thread->start();
  2. cad_thread->join();


分别调用了start函数与join函数。有人可能会问,(2)定义的run接口怎么没调用到,其实是有的。

因为在Thread类的构造时就启动了此线程:

  1. Thread::Thread(const char* name_) : sig(0), deleteAfterRun(false) {
  2. sig = new Condition(mutex);
  3. cond_event = CreateEvent(NULL, TRUE, FALSE, NULL);
  4. if (!name_)
  5. name_ = "Unnamed";
  6. name = strDup(name_);
  7. thread = CreateThread(NULL, 0, threadProc, this, CREATE_SUSPENDED, &thread_id);
  8. state = ThreadCreated;
  9. logAction(this, "created");
  10. }


 

Thread::threadProc中就会调用到它:

Windows ctrl alt delete远程模拟-LMLPHP

 

2、tightvnc  客户端版本中实现模拟发送ATL+CTRL+DEL

在我的文章《XP、Wn7模拟发送ctrl+alt+delete组合键》中讲到在win7中实现模拟ATL+CTRL+DEL的方法,虽然我已经改在了会话1中发送窗口消息,可是还是没达到效果。

网友建议我去查看VNC代码,由于tightvnc代码是支持Win7 的,于是希望在这里找到解决方法。

在win-system工程中有Environment.h文件定义有类:

  1. #ifndef _ENVIRONMENT_H_
  2. #define _ENVIRONMENT_H_
  3. #include "util/StringStorage.h"
  4. #include
  5. class Environment
  6. {
  7. public:
  8. static const int APPLICATION_DATA_SPECIAL_FOLDER = 0x0;
  9. static const int COMMON_APPLICATION_DATA_SPECIAL_FOLDER = 0x1;
  10. public:
  11. Environment();
  12. ~Environment();
  13. static void getErrStr(StringStorage *out);
  14. static void getErrStr(const TCHAR *specification, StringStorage *out);
  15. static bool getSpecialFolderPath(int specialFolderId, StringStorage *out);
  16. static bool getCurrentModulePath(StringStorage *out);
  17. static bool getCurrentModuleFolderPath(StringStorage *out);
  18. static bool getCurrentUserName(StringStorage *out);
  19. static bool getComputerName(StringStorage *out);
  20. static void restoreWallpaper();
  21. static void disableWallpaper();
  22. static bool isWinNTFamily();
  23. static bool isWinXP();
  24. static bool isWin2003Server();
  25. static bool isVistaOrLater();
  26. static void simulateCtrlAltDel();
  27. private:
  28. static void init();
  29. static OSVERSIONINFO m_osVerInfo;
  30. };
  31. #endif


其中有 static void simulateCtrlAltDel();接口

  1. void Environment::simulateCtrlAltDel()
  2. {
  3. if (isWinNTFamily()) {
  4. CtrlAltDelSimulator cadSim;
  5. cadSim.wait();
  6. }
  7. }


vnc-4.0-winsrc  版本的CADThread类继承于Thread类类似,这里的CtrlAltDelSimulator类也继续Thread。

同样在thread类的构造时就启动了此线程:

  1. Thread::Thread()
  2. : m_terminated(false), m_active(false), m_hDesk(0)
  3. {
  4. m_hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) threadProc,
  5. (LPVOID) this, CREATE_SUSPENDED, (LPDWORD) &m_threadID);
  6. m_hDesk = DesktopSelector::getInputDesktop();
  7. }


线程里会去调用execute方法:

  1. DWORD WINAPI Thread::threadProc(LPVOID pThread)
  2. {
  3. Thread *_this = ((Thread *)pThread);
  4. try {
  5. DesktopSelector::setDesktopToCurrentThread(_this->m_hDesk);
  6. DesktopSelector::closeDesktop(_this->m_hDesk);
  7. _this->m_hDesk = 0;
  8. _this->execute();
  9. } catch (Exception &e) {
  10. Log::error(_T("Abnormal thread termination.")
  11. _T(" ThreadId = %x, message = \"%s\" \n"),
  12. _this->m_threadID, e.getMessage());
  13. }
  14. _this->m_active = false;
  15. return 0;
  16. }

CtrlAltDelSimulator类重载了execute方法:

  1. void CtrlAltDelSimulator::execute()
  2. {
  3. if (DesktopSelector::selectDesktop(&StringStorage(_T("Winlogon")))) {
  4. HWND hwndCtrlAltDel = FindWindow(_T("SAS window class"), _T("SAS window"));
  5. if (hwndCtrlAltDel == NULL) {
  6. hwndCtrlAltDel = HWND_BROADCAST;
  7. }
  8. PostMessage(hwndCtrlAltDel, WM_HOTKEY, 0, MAKELONG(MOD_ALT | MOD_CONTROL, VK_DELETE));
  9. }
  10. }

 

往窗口发送消息是与VNC4.0版本是类似的,这里只讲selectDesktop的不同。

 

  1. bool DesktopSelector::selectDesktop(const StringStorage *name)
  2. {
  3. HDESK desktop;
  4. if (name) {
  5. desktop = getDesktop(name);
  6. } else {
  7. desktop = getInputDesktop();
  8. }
  9. bool result = setDesktopToCurrentThread(desktop) != 0;
  10. closeDesktop(desktop);
  11. return result;
  12. }


 

  1. HDESK DesktopSelector::getDesktop(const StringStorage *name)
  2. {
  3. return OpenDesktop(name->getString(), 0, TRUE,
  4. DESKTOP_CREATEMENU |
  5. DESKTOP_CREATEWINDOW |
  6. DESKTOP_ENUMERATE |
  7. DESKTOP_HOOKCONTROL |
  8. DESKTOP_WRITEOBJECTS |
  9. DESKTOP_READOBJECTS |
  10. DESKTOP_SWITCHDESKTOP |
  11. GENERIC_WRITE);
  12. }


 

  1. bool DesktopSelector::setDesktopToCurrentThread(HDESK newDesktop)
  2. {
  3. return SetThreadDesktop(newDesktop) != 0;
  4. }


3、VNC服务端发送模拟CTRL+ATL+DEL的方法

3、1 在Vista版本前

 参考我的文章《XP、Wn7模拟发送ctrl+alt+delete组合键

 

3、2 在Vista版本后

同样是在Environment.h里的class Environment类中有:

 static bool isWinNTFamily(); static bool isWinXP(); static bool isWin2003Server(); static bool isVistaOrLater(); static void simulateCtrlAltDel(); static void simulateCtrlAltDelUnderVista();

win7下的函数就是:simulateCtrlAltDelUnderVista

  1. void Environment::simulateCtrlAltDelUnderVista()
  2. {
  3. Log::info(_T("Requested Ctrl+Alt+Del simulation under Vista or later"));
  4. try {
  5. DynamicLibrary sasLib(_T("sas.dll"));
  6. SendSas sendSas = (SendSas)sasLib.getProcAddress("SendSAS");
  7. if (sendSas == 0) {
  8. throw Exception(_T("The SendSAS function has not been found"));
  9. }
  10. sendSas(FALSE);
  11. } catch (Exception &e) {
  12. Log::error(_T("The simulateCtrlAltDelUnderVista() function failed: %s"),
  13. e.getMessage());
  14. }
  15. }


实现思路:

(1)从system32文件夹下的sas.dll里得到SendSAS接口,

(2)然后调用此接口sendSas(FALSE);

使用注意事项:

(1)

SendSAS function

Simulates a secure attention sequence (SAS).

  1. VOID WINAPI SendSAS(
  2. _In_ BOOL AsUser
  3. );


 

Parameters

AsUser [in]

TRUE if the caller is running as the current user; otherwise,FALSE.

Remarks

为了调用SendSAS成功,应用必然以服务方式运行或有uiAccess属性(用requestedExceptionLevel函数设置为true)。

如果不是以服务方式运行,那么得把User Account Control打开。

Important  Applications with the uiAccess attribute set to "true" must be signed by usingAuthenticode. In addition, the application must reside in a protected location in the file system. Currently, there are two allowable protected locations:

\Program Files\\windows\system32\

本地的安全策略必须配置为允许服务和应用程序模拟SAS。

为了配置策略,修改组策略编辑器(GPE)中的设置。

控制 委托的GPE是在下面的位置:

Computer Configuration | Administrative Templates | Windows Components | Windows Logon Options | Disable or enable software Secure Attention Sequence

A service can impersonate the token of another process that calls that service. In this case, a call to theSendSAS function by that service simulates a SAS on the session associated with the impersonated token.

Windows ctrl alt delete远程模拟-LMLPHP

Windows ctrl alt delete远程模拟-LMLPHP

以上策略对应的注册表是:

reg add

 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System /v

SoftwareSASGeneration /t REG_DWORD /d 1 /f

即增加以下注册表:

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System] "SoftwareSASGeneration"=dword:00000003


 

 

(2)我把它放在Win7类型于GINA的凭证COM里(且是以起一线程的方式调用),发现没有效果。

这是因为SendSAS只能在服务中才起作用,而在凭据中是不能起作用的。

(3) 

 

 那么simulateCtrlAltDelUnderVista在哪里被调用呢?

  1. void SasUserInput::setKeyboardEvent(UINT32 keySym, bool down)
  2. {
  3. bool delPressed = false;
  4. if (m_underVista) {
  5. switch (keySym) {
  6. case XK_Alt_L:
  7. case XK_Alt_R:
  8. m_altPressed = down;
  9. break;
  10. case XK_Control_L:
  11. case XK_Control_R:
  12. m_ctrlPressed = down;
  13. break;
  14. case XK_Delete:
  15. delPressed = down;
  16. }
  17. }
  18. if (m_ctrlPressed && m_altPressed && delPressed && m_underVista) {
  19. Environment::simulateCtrlAltDelUnderVista();
  20. } else {
  21. m_client->setKeyboardEvent(keySym, down);
  22. }
  23. }


我们再来看setKeyboardEvent在哪里被调用了:

(1)

  1. void UserInputServer::applyKeyEvent(BlockingGate *backGate)
  2. {
  3. UINT32 keySym;
  4. bool down;
  5. readKeyEvent(&keySym, &down, backGate);
  6. m_userInput->setKeyboardEvent(keySym, down);
  7. }

applyKeyEvent调用readKeyEvent读出键值和按下或弹上的标志,然后再调用setKeyboardEvent。

applyKeyEvent的实现过程:

  1. void DesktopServerProto::readKeyEvent(UINT32 *keySym, bool *down,
  2. BlockingGate *gate)
  3. {
  4. *keySym = gate->readUInt32();
  5. *down = gate->readUInt8() != 0;
  6. }



 

(2)

  1. void RfbClient::onKeyboardEvent(UINT32 keySym, bool down)
  2. {
  3. m_desktop->setKeyboardEvent(keySym, down);
  4. }


 

(3)

  1. void WinDesktop::setKeyboardEvent(UINT32 keySym, bool down)
  2. {
  3. Log::info(_T("set keyboard event (keySym = %u, down = %d)"), keySym, (int)down);
  4. try {
  5. if (isRemoteInputAllowed()) {
  6. m_userInput->setKeyboardEvent(keySym, down);
  7. }
  8. } catch (Exception &e) {
  9. Log::error(_T("setKeyboardEvent() crashed: %s"), e.getMessage());
  10. m_extDeskTermListener->onAbnormalDesktopTerminate();
  11. }
  12. }


 

(4)

 

10-03 18:22