或许大家在刚刚接触到Windows7、Windows8或者Vista的时候,会发现它们比XP“麻烦”了不少,其中一个“麻烦”就是UAC,什么是UAC呢?UAC就是User Account Control(用户帐户控制),他对我们来说最直观的一个地方就是在你进行某些操作或者运行某些图标上有小盾牌图标的程序的时候,突然屏幕一黑,然后蹦个窗口出来,要询问你“允许该程序对你的电脑进行操作吗”,如果你点了否那么该程序就无法运行。
这个操作过程看似只是一个请求权限的小窗口而已,其实不然,这个窗口并不是在你当前的桌面上生成的,而是在一个叫做安全桌面(secure desktop)的地方生成的,这个桌面没有壁纸,没有图标,什么都没有。那么这个“安全桌面”是用来做什么的呢?到底有什么用呢?
什么是安全桌面?
我们写过程序的人可能知道,如果我们调用系统的某些API(SetWindowsHookEx,WH_KEYBOARD_LL笑而不语),就可以利用钩子监视键盘,你输入的每一个字符全都被一览无余……
据我所知,Windows一共有3种不同的桌面,1是winlogon,2是屏幕保护(没错它也是个桌面),3才是我们平时用到的用户桌面。安全桌面是由“local system”用户所持有并通过winlogon运行,它几乎不响应大部分的其他程序的操作,所以在用户桌面上的键盘钩子,也不会捕获到你在安全桌面上面的按键,在这里看来安全桌面是相对安全的。
如何实现安全桌面?
大体来说,我们需要进行以下几个步骤(这几个函数可以在user32.dll里面找到):
- CreateDesktop,创建安全桌面
- SwitchDesktop,切换到安全桌面
- SetThreadDesktop,将安全新桌面关联到当前线程,然后进行操作
- SwitchDesktop,切换回原桌面
- CloseDesktop,关闭安全桌面
既然知道了要怎么做,那我们就来自己动手做一下(当然是使用我们最熟悉的C#):
声明上面的几个函数,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
[Flags] public enum ACCESS_MASK : uint { DELETE = 0x00010000, READ_CONTROL = 0x00020000, WRITE_DAC = 0x00040000, WRITE_OWNER = 0x00080000, SYNCHRONIZE = 0x00100000, STANDARD_RIGHTS_REQUIRED = 0x000F0000, STANDARD_RIGHTS_READ = 0x00020000, STANDARD_RIGHTS_WRITE = 0x00020000, STANDARD_RIGHTS_EXECUTE = 0x00020000, STANDARD_RIGHTS_ALL = 0x001F0000, SPECIFIC_RIGHTS_ALL = 0x0000FFFF, ACCESS_SYSTEM_SECURITY = 0x01000000, MAXIMUM_ALLOWED = 0x02000000, GENERIC_READ = 0x80000000, GENERIC_WRITE = 0x40000000, GENERIC_EXECUTE = 0x20000000, GENERIC_ALL = 0x10000000, DESKTOP_READOBJECTS = 0x00000001, DESKTOP_CREATEWINDOW = 0x00000002, DESKTOP_CREATEMENU = 0x00000004, DESKTOP_HOOKCONTROL = 0x00000008, DESKTOP_JOURNALRECORD = 0x00000010, DESKTOP_JOURNALPLAYBACK = 0x00000020, DESKTOP_ENUMERATE = 0x00000040, DESKTOP_WRITEOBJECTS = 0x00000080, DESKTOP_SWITCHDESKTOP = 0x00000100, WINSTA_ENUMDESKTOPS = 0x00000001, WINSTA_READATTRIBUTES = 0x00000002, WINSTA_ACCESSCLIPBOARD = 0x00000004, WINSTA_CREATEDESKTOP = 0x00000008, WINSTA_WRITEATTRIBUTES = 0x00000010, WINSTA_ACCESSGLOBALATOMS = 0x00000020, WINSTA_EXITWINDOWS = 0x00000040, WINSTA_ENUMERATE = 0x00000100, WINSTA_READSCREEN = 0x00000200, WINSTA_ALL_ACCESS = 0x0000037F } [StructLayout(LayoutKind.Sequential)] public struct SECURITY_ATTRIBUTES { public int nLength; public IntPtr lpSecurityDescriptor; public int bInheritHandle; } [DllImport("kernel32.dll")] private static extern uint GetCurrentThreadId(); [DllImport("user32.dll", EntryPoint = "CreateDesktop", CharSet = CharSet.Unicode, SetLastError = true)] public static extern IntPtr CreateDesktop( [MarshalAs(UnmanagedType.LPWStr)] string desktopName, [MarshalAs(UnmanagedType.LPWStr)] string device, // must be null. [MarshalAs(UnmanagedType.LPWStr)] string deviceMode, // must be null, [MarshalAs(UnmanagedType.U4)] int flags, // use 0 [MarshalAs(UnmanagedType.U4)] ACCESS_MASK accessMask, [MarshalAs(UnmanagedType.LPStruct)] SECURITY_ATTRIBUTES attributes);//这里的SECURITY_ATTRIBUTES是一个代表权限的结构体,如果不知道这是什么,请直接将这个值的类型改为IntPtr,然后传入IntPtr.Zero [DllImport("user32.dll", EntryPoint = "CloseDesktop", CharSet = CharSet.Unicode, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool CloseDesktop(IntPtr handle); [DllImport("user32.dll")] public static extern bool SwitchDesktop(IntPtr hDesktop); [DllImport("user32.dll", SetLastError = true)] public static extern bool SetThreadDesktop(IntPtr hDesktop); [DllImport("user32.dll", SetLastError = true)] public static extern IntPtr GetThreadDesktop(uint dwThreadId); |
然后是桌面操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
//获取原桌面的句柄 IntPtr hOldDesktop = GetThreadDesktop(GetCurrentThreadId()); //创建桌面并获得句柄 IntPtr hNewDesktop = CreateDesktop("HelloMyNewDesktop",null,null, 0,ACCESS_MASK.GENERIC_ALL, IntPtr.Zero); //切换到新桌面 SwitchDesktop(hNewDesktop); //返回值 bool returnvalue = false; //开始一个新的线程并且开始消息循环 System.Threading.Tasks.Task.Factory.StartNew(() => { //将新桌面绑定到当前线程 SetThreadDesktop(hNewDesktop); var login = new frmLogon(); login.FormClosing += (s, c) => { returnvalue = login.IsSuccess; }; Application.Run(login); }).Wait(); //切换回原来的桌面 SwitchDesktop(hOldDesktop); //关闭新桌面 CloseDesktop(hNewDesktop); if (returnvalue) MessageBox.Show("登录成功!"); else MessageBox.Show("登录失败!"); |
如果运行不起来,可以看看 源代码 。
总结:
当然了,时代在进步,有了安全桌面也不一定能保证你的系统或是程序是100%安全的,
作为一个用户:你需要做的就是养成一个良好的使用习惯,平时不知道的程序不要随意打开,把windows的文件后缀名显示出来,exe文件要尤其小心。
作为一个开发者:你做的软件可能会需要登录,将登陆事务放到安全桌面上虽然可以防止一些钩子,可是如果别人直接跑去黑你的数据库了呢?或者直接把你的程序反编译了呢?所以我们开发者,必须时时刻刻提高自己,多学习新知识,不要一直停滞不前,不然终究会被淘汰。
PS:我在使用的过程中发现在某些Win7系统中(Win8.1和Win10经测试没有发现这个问题),打开一次桌面就会打开一个输入法管理器进程(CtfMon.exe),而桌面关闭了以后它不会关闭,这样可能会出现进程中有好几个甚至N多CtfMon,要怎么解决这个问题呢?这个我们就到这篇文章里面去讨论吧。
1条评论. Leave new
[…] 前几天当我还沉浸在学会怎样产生安全桌面并在里面放置登录事务的时候,在一次例行测试过程中我发现,如果在纯净版(使用官方镜像安装,没有打过补丁。之所以强调纯净版,是因为不排除微软会发布补丁修复这个问题的可能性)的Windows7上面生成安全桌面,会随之产生一个CtfMon.exe进程,但是当我们关闭了安全桌面的时候,这个进程不会被关闭,我测试了8次程序,进程列表里就出现了9个CtfMon(原本用户桌面上的那个当然也要算在内了),这怎么能行?要怎样解决这个问题? […]