您的位置:首页 > 其它

VNC源码研究(十)XP、Win7实现模拟发送ATL+CTRL+DEL

2015-08-19 17:53 309 查看
1、vnc-4.0-winsrc 版本中实现模拟发送ATL+CTRL+DEL

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

在Service.h中有

[cpp] view
plaincopy

// -=- Routines used by the SInput Keyboard class to emulate Ctrl-Alt-Del

// Returns false under Win9x

bool emulateCtrlAltDel();

它的实现是:

[cpp] view
plaincopy

bool

rfb::win32::emulateCtrlAltDel() {

if (!osVersion.isPlatformNT)

return false;

CADThread* cad_thread = new CADThread();

vlog.debug("emulate Ctrl-Alt-Del");

if (cad_thread) {

cad_thread->start();

cad_thread->join();

bool result = cad_thread->result;

delete cad_thread;

return result;

}

return false;

}

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

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

[cpp] view
plaincopy

namespace rfb {

namespace win32 {

extern struct OSVersionInfo : OSVERSIONINFO {

OSVersionInfo();

// Is the OS one of the NT family (NT 3.51, NT4.0, 2K, XP, etc.)?

bool isPlatformNT;

// Is one of the Windows family?

bool isPlatformWindows;

// Is this OS one of those that blue-screens when grabbing another desktop (NT4 pre SP3)?

bool cannotSwitchDesktop;

} osVersion;

};

};

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

[cpp] view
plaincopy

OSVersionInfo::OSVersionInfo() {

// Get OS Version Info

ZeroMemory(static_cast<OSVERSIONINFO*>(this), sizeof(this));

dwOSVersionInfoSize = sizeof(OSVERSIONINFO);

if (!GetVersionEx(this))

throw rdr::SystemException("unable to get system version info", GetLastError());

// Set the special extra flags

isPlatformNT = dwPlatformId == VER_PLATFORM_WIN32_NT;

isPlatformWindows = dwPlatformId == VER_PLATFORM_WIN32_WINDOWS;

cannotSwitchDesktop = isPlatformNT && (dwMajorVersion==4) &&

((_tcscmp(szCSDVersion, _T("")) == 0) ||

(_tcscmp(szCSDVersion, _T("Service Pack 1")) == 0) ||

(_tcscmp(szCSDVersion, _T("Service Pack 2")) == 0));

}

(2)创建CADThread对象

[cpp] view
plaincopy

CADThread* cad_thread = new CADThread();

CADThread类继续自Thread

Thread的设计是值得我们学习的。

[cpp] view
plaincopy

class Thread {

public:

Thread(const char* name_=0);

virtual ~Thread();

virtual void run();

virtual void start();

virtual Thread* join();

const char* getName() const;

ThreadState getState() const;

// Determines whether the thread should delete itself when run() returns

// If you set this, you must NEVER call join()!

void setDeleteAfterRun() {deleteAfterRun = true;};

unsigned long getThreadId() const;

static Thread* self();

friend class Condition;

protected:

Thread(HANDLE thread_, DWORD thread_id_);

static DWORD WINAPI threadProc(LPVOID lpParameter);

HANDLE thread;

DWORD thread_id;

char* name;

ThreadState state;

Condition* sig;

Mutex mutex;

HANDLE cond_event;

Thread* cond_next;

bool deleteAfterRun;

};

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

最后把桌面还原。

[cpp] view
plaincopy

class CADThread : public Thread {

public:

CADThread() : Thread("CtrlAltDel Emulator"), result(false) {}

virtual void run() {

HDESK old_desktop = GetThreadDesktop(GetCurrentThreadId());

if (switchToDesktop(OpenDesktop(_T("Winlogon"), 0, FALSE, DESKTOP_CREATEMENU | DESKTOP_CREATEWINDOW |

DESKTOP_ENUMERATE | DESKTOP_HOOKCONTROL |

DESKTOP_WRITEOBJECTS | DESKTOP_READOBJECTS |

DESKTOP_SWITCHDESKTOP | GENERIC_WRITE))) {

PostMessage(HWND_BROADCAST, WM_HOTKEY, 0, MAKELONG(MOD_ALT | MOD_CONTROL, VK_DELETE));

switchToDesktop(old_desktop);

result = true;

}

}

bool result;

};

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

[cpp] view
plaincopy

cad_thread->start();

cad_thread->join();

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

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

[cpp] view
plaincopy

Thread::Thread(const char* name_) : sig(0), deleteAfterRun(false) {

sig = new Condition(mutex);

cond_event = CreateEvent(NULL, TRUE, FALSE, NULL);

if (!name_)

name_ = "Unnamed";

name = strDup(name_);

thread = CreateThread(NULL, 0, threadProc, this, CREATE_SUSPENDED, &thread_id);

state = ThreadCreated;

logAction(this, "created");

}

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



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

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

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

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

[cpp] view
plaincopy

#ifndef _ENVIRONMENT_H_

#define _ENVIRONMENT_H_

#include "util/StringStorage.h"

#include <winnt.h>

class Environment

{

public:

static const int APPLICATION_DATA_SPECIAL_FOLDER = 0x0;

static const int COMMON_APPLICATION_DATA_SPECIAL_FOLDER = 0x1;

public:

Environment();

~Environment();

static void getErrStr(StringStorage *out);

static void getErrStr(const TCHAR *specification, StringStorage *out);

static bool getSpecialFolderPath(int specialFolderId, StringStorage *out);

static bool getCurrentModulePath(StringStorage *out);

static bool getCurrentModuleFolderPath(StringStorage *out);

static bool getCurrentUserName(StringStorage *out);

static bool getComputerName(StringStorage *out);

static void restoreWallpaper();

static void disableWallpaper();

static bool isWinNTFamily();

static bool isWinXP();

static bool isWin2003Server();

static bool isVistaOrLater();

static void simulateCtrlAltDel();

private:

static void init();

static OSVERSIONINFO m_osVerInfo;

};

#endif

其中有 static void simulateCtrlAltDel();接口

[cpp] view
plaincopy

void Environment::simulateCtrlAltDel()

{

if (isWinNTFamily()) {

CtrlAltDelSimulator cadSim;

cadSim.wait();

}

}

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

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

[cpp] view
plaincopy

Thread::Thread()

: m_terminated(false), m_active(false), m_hDesk(0)

{

m_hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) threadProc,

(LPVOID) this, CREATE_SUSPENDED, (LPDWORD) &m_threadID);

m_hDesk = DesktopSelector::getInputDesktop();

}

线程里会去调用execute方法:

[cpp] view
plaincopy

DWORD WINAPI Thread::threadProc(LPVOID pThread)

{

Thread *_this = ((Thread *)pThread);

try {

DesktopSelector::setDesktopToCurrentThread(_this->m_hDesk);

DesktopSelector::closeDesktop(_this->m_hDesk);

_this->m_hDesk = 0;

_this->execute();

} catch (Exception &e) {

Log::error(_T("Abnormal thread termination.")

_T(" ThreadId = %x, message = \"%s\" \n"),

_this->m_threadID, e.getMessage());

}

_this->m_active = false;

return 0;

}

CtrlAltDelSimulator类重载了execute方法:

[cpp] view
plaincopy

void CtrlAltDelSimulator::execute()

{

if (DesktopSelector::selectDesktop(&StringStorage(_T("Winlogon")))) {

HWND hwndCtrlAltDel = FindWindow(_T("SAS window class"), _T("SAS window"));

if (hwndCtrlAltDel == NULL) {

hwndCtrlAltDel = HWND_BROADCAST;

}

PostMessage(hwndCtrlAltDel, WM_HOTKEY, 0, MAKELONG(MOD_ALT | MOD_CONTROL, VK_DELETE));

}

}

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

[cpp] view
plaincopy

bool DesktopSelector::selectDesktop(const StringStorage *name)

{

HDESK desktop;

if (name) {

desktop = getDesktop(name);

} else {

desktop = getInputDesktop();

}

bool result = setDesktopToCurrentThread(desktop) != 0;

closeDesktop(desktop);

return result;

}

[cpp] view
plaincopy

HDESK DesktopSelector::getDesktop(const StringStorage *name)

{

return OpenDesktop(name->getString(), 0, TRUE,

DESKTOP_CREATEMENU |

DESKTOP_CREATEWINDOW |

DESKTOP_ENUMERATE |

DESKTOP_HOOKCONTROL |

DESKTOP_WRITEOBJECTS |

DESKTOP_READOBJECTS |

DESKTOP_SWITCHDESKTOP |

GENERIC_WRITE);

}

[cpp] view
plaincopy

bool DesktopSelector::setDesktopToCurrentThread(HDESK newDesktop)

{

return SetThreadDesktop(newDesktop) != 0;

}



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

3、1 在Vista版本前

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

3、2 在Vista版本后

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

[plain] view
plaincopy

static bool isWinNTFamily();

static bool isWinXP();

static bool isWin2003Server();

static bool isVistaOrLater();

static void simulateCtrlAltDel();

static void simulateCtrlAltDelUnderVista();

win7下的函数就是:simulateCtrlAltDelUnderVista

[cpp] view
plaincopy

void Environment::simulateCtrlAltDelUnderVista()

{

Log::info(_T("Requested Ctrl+Alt+Del simulation under Vista or later"));

try {

DynamicLibrary sasLib(_T("sas.dll"));

SendSas sendSas = (SendSas)sasLib.getProcAddress("SendSAS");

if (sendSas == 0) {

throw Exception(_T("The SendSAS function has not been found"));

}

sendSas(FALSE);

} catch (Exception &e) {

Log::error(_T("The simulateCtrlAltDelUnderVista() function failed: %s"),

e.getMessage());

}

}

实现思路:

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

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

使用注意事项:

(1)


SendSAS function

Simulates a secure attention sequence (SAS).

[cpp] view
plaincopy

VOID WINAPI SendSAS(

_In_ BOOL AsUser

);


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.





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

reg add

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

SoftwareSASGeneration /t REG_DWORD /d 1 /f

即增加以下注册表:

[plain] view
plaincopy

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System]

"SoftwareSASGeneration"=dword:00000003

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

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

(3)

那么simulateCtrlAltDelUnderVista在哪里被调用呢?

[cpp] view
plaincopy

void SasUserInput::setKeyboardEvent(UINT32 keySym, bool down)

{

bool delPressed = false;

if (m_underVista) {

switch (keySym) {

case XK_Alt_L:

case XK_Alt_R:

m_altPressed = down;

break;

case XK_Control_L:

case XK_Control_R:

m_ctrlPressed = down;

break;

case XK_Delete:

delPressed = down;

}

}

if (m_ctrlPressed && m_altPressed && delPressed && m_underVista) {

Environment::simulateCtrlAltDelUnderVista();

} else {

m_client->setKeyboardEvent(keySym, down);

}

}

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

(1)

[cpp] view
plaincopy

void UserInputServer::applyKeyEvent(BlockingGate *backGate)

{

UINT32 keySym;

bool down;

readKeyEvent(&keySym, &down, backGate);

m_userInput->setKeyboardEvent(keySym, down);

}

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

applyKeyEvent的实现过程:

[cpp] view
plaincopy

void DesktopServerProto::readKeyEvent(UINT32 *keySym, bool *down,

BlockingGate *gate)

{

*keySym = gate->readUInt32();

*down = gate->readUInt8() != 0;

}

(2)

[cpp] view
plaincopy

void RfbClient::onKeyboardEvent(UINT32 keySym, bool down)

{

m_desktop->setKeyboardEvent(keySym, down);

}

(3)

[cpp] view
plaincopy

void WinDesktop::setKeyboardEvent(UINT32 keySym, bool down)

{

Log::info(_T("set keyboard event (keySym = %u, down = %d)"), keySym, (int)down);

try {

if (isRemoteInputAllowed()) {

m_userInput->setKeyboardEvent(keySym, down);

}

} catch (Exception &e) {

Log::error(_T("setKeyboardEvent() crashed: %s"), e.getMessage());

m_extDeskTermListener->onAbnormalDesktopTerminate();

}

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: