邢海峰
(内蒙古财经大学计算机信息管理学院,内蒙古呼和浩特 010050)
随着计算机系统的普及,人们工作学习基本均可通过计算机系统的辅助完成,而大量的工作学习资料和成果将以虚拟数字资料的形式保存,这对提高人们的工作学习效率、节约工作学习成本有较大的帮助,但其也带来了诸多负面影响,如非法入侵、信息泄露、未授权使用计算机等,所以计算机安全成为数字化时代面临的严峻问题。
基于此,本文提出了一种基于Socket数据传输技术,用于实现个人计算机屏幕加解锁的无线控制程序设计方法,该程序是为了帮助提高个人计算机安全性,允许使用者通过个人手机来完成对个人计算机屏幕的锁屏及解锁控制。这不但有效地防止了外人对他人计算机的非法使用,保证个人信息安全,同时,由于计算机的主人在无线局域网覆盖的任何位置均可方便的控制计算机,所以计算机控制不受空间的约束,为使用者带来便利。
设计实现的控制程序主要利用Android平台作为控制端,通过WiFi方式接收信息并传送进入局域网,然后通过局域网将信号传递到受控端的PC机实现对个人计算机屏幕加锁及解锁控制。控制程序原理框架如图1所示。
图1 控制程序原理框架图
由控制程序原理框架图可知,该程序有两部分:移动客户端实现接收用户控制信息;被控端接收用户发送控制信息。被控信息传输过程为:移动终端发送控制信息到无线路由器,无线路由器接收控制信息并路由,控制信息通过局域网传送到被控计算机。
由于Android移动平台随处可得,常见的如用户手中的Android手机就是最佳选择,所以将用户的手机作为客户端即经济又不影响便携性,则可将客户端的控制程序部署到用户的手机中,而被控计算机端可以开发部署一个负责接受、解析客户端信息,然后根据解析信息来实现用户控制意图的服务端程序即可。因为用户控制计算机数量不一定是1台,有可能工作中需要两台或以上同时工作,为保证多台计算机同时得到控制,可采用基于UDP/IP协议的数据报方式的Socket技术来实现客户端与服务端的数据传输。控制程序业务流程如图2所示。
图2 控制程序业务流程
1.2.1 Socket套接字
Socket[1]也称为“套接字”,应用程序通过“套接字”向网络发出请求或者应答网络请求。Socket有两种实现方式,即流式Socket(SOCK_STREAM)和数据报式Socket(SOCK_DGRAM)。流式是一种面向连接的Socket,针对于面向连接的TCP服务应用,一般多用于实现可靠的点对点的数据传输;数据报式Socket是一种面向无连接的UDP服务应用,通常多用在实现具有广播功能的数据传输。
因在实际工作学习中可能出现一个用户同时使用多台计算机的情况,所以计算机屏幕加解锁控制程序要实现客户终端到被控设备的一对多数据传输,所以最佳的“套接字”传输方式应为非面向连接的数据报“套接字”。非面向连接的数据报“套接字”传输数据的工作过程无需建立可靠连接,因此数据传输过程要比面向连接少一个建立连接的环节,客户端仅需要向被控服务端直接发送信息即可。
由于服务端程序除了要负责接收客户端通过Socket发来的控制信息之外,更重要的是还需要根据控制信息实现对被控服务端的屏幕锁屏及解锁操作,而实现屏幕加解屏必须重写Windons系统的相应钩子函数来实现系统硬件层的屏蔽工作,则选择C语言实现服务端程序。在C语言中提供了数据报方式Socket[2]的接口函数及Socket数据存放结构,它们被定义在winsock.h或winsock2.h头文件中。
客户端是Android平台的手机,所以使用Java语言开发较方便。在Java中对两种方式的Socket[3]均提供了支持。Java中用于支持流式Socket有Socket和ServerSocket类,Socket类为客户端提供全部接口,ServerSocket类为服务端提供全部接口。Java用于支持数据报方式的Socket被封装在DatagramSocket类中,同时提供了DatagramPacket类来封装Socket数据。
需要注意的是,由于客户端和服务端实现Socket的语言不同,而不同语言在获取Socket数据的类型可能存在差异,所以当发送接收数据时需要对数据进行一些必要的类型处理。
1.2.2 屏幕加解锁原理
屏幕加锁是通过屏蔽系统标准输入外设的输入来实现。理论上有两种方法可以实现该项功能:(1)直接修改处理器内部负责管理输入中断的寄存器值,将管理外部输入设备的中断位屏蔽,使硬件系统不能得到外部中断,进而实现锁屏功能。(2)不修改任何硬件寄存器值,仅是通过一些软件技术,使操作系统忽略外设的输入中断请求,从而实现锁屏的功能。由于实现锁屏功能时,操作系统已经处于运行状态,其会通过自身定义的安全策略对底层的硬件加以保护,所以要采用直接修改硬件寄存器的方法来实现锁屏难度较大。再者,即使能够突破操作系统的保护直接修改硬件寄存器值,对系统本身的安全性也是一种潜在威胁,所以第2种是较好的选择方案。而解锁功能与加锁正好相反。
Windows系统在设计时就为高级用户预留了一项称为钩子[4-5]的功能,用户可通过调用钩子安装函数SetWindowsHookEx将自己实现的钩子挂到Windows系统中。正是通过自身定义的钩子函数,无论是系统的开发维护人员,还是系统高级外围程序员均可完成一些能够影响系统的行为工作。如本文要实现的加减锁控制程序便可使用Windows的钩子来实现。
Windows中钩子的实现有两种方式:内部钩子、全局钩子。内部钩子只能影响当前运行的进程行为,而全局钩子可影响所有运行进程,但全局钩子实现需要采用动态链接库方式,所以在本文所实现的服务端程序的核心锁屏及解锁功能要以动态连接库的方式连接到主程序中。
分析了所使用的加锁及解锁技术原理后,为了提高控制程序自身的安全性,防止非法用户通过使用用户手机来控制用户计算机,提出一种简单有效的方法来保证控制程序的安全性。
首先,服务端程序开始运行时需要用户输入一组密码,该密码的生存期从输入结束开始,到服务端程序结束终止;其次,当用户需要启动手机客户端程序控制计算机时,需要用户提供服务端密码。当用户提供了正确的密码,客户端程序会将密码和控制信号封装发送到服务端;服务端接收到数据后,按照数据封装顺序解析密码和控制信号,根据密码验证身份,身份正确后执行相应的屏幕控制行为。数据封装及解析过程如图3所示。
图3 数据封装及解析
1.2.3 Android应用开发原理
(1)Android应用程序组成。Android应用程序是由组件组成,而组件是可调用的基本功能模块。Android系统有4 个重要组件分别是[6]:Activity、Service、BroadcaseReceiver和ContentProvider;Activity是Android程序的呈现层,显示可视化的用户界面,并接收与用户交互所产生的界面事件;Service用于没有用户界面,但需要长时间在后台运行的应用;ContentProvider是Android系统提供的一种标准的共享数据的机制,应用程序可通过ContentProvider访问其他应用程序存储在文件系统中的文件,也可以是SQLite中数据库的私有数据;Broadcase-Receiver是用于接受并响应广播消息的组件,不包含任何用户界面,可通过启动Activity或Notification通知用户接收到重要信息。
(2)组件生命周期。所有Android组件都具有自己的生命周期[7],是从组件建立到组件销毁的整个过程。在生命周期中,组件会在可见、不可见、活动、非活动等状态中不断变化。介绍控制系统中使用的Activity生命周期,如图4所示。
图4 Activity生命周期
Activity生命周期指Activity从启动到销毁的过程,Activity表现为4种状态,分别是活动状态、暂停状态、停止状态和非活动状态。
活动状态:Activity在用户界面中处于最上层,用户可见,实现用户交互。
暂停状态:Activity在界面上被部分遮挡,该Activity不再处于用户界面的最上层,且不能够与用户进行交互。
停止状态:Activity在界面上完全不能被用户看到,也就是说这个Activity被其他Activity全部遮挡。
非活动状态不在以上3种状态中的Activity则处于非活动状态。
(3)组件的回调函数。Android系统的Activity组件提供了7个回调函数来控制该组件的创建、状态变化及销毁,分别为 onCreate、onStart、onRestart、onResume、onPause、onStop、onDestroy。
(4)Android应用程序间消息传递。Android系统中采用称为意图的Intent类来实现不同应用程序间的信息传递。Intent[8]可划分为显式意图和隐式意图两种。显式意图:调用Intent.setComponent()或Intent.setClass()方法指定组件名或类对象。隐式意图:Android系统会根据隐式意图中设置的动作(Action)、类别(Category)、数据(URI和数据类型)找到最合适的组件来处理这个意图。
客户端功能由Java语言封装的控制主类MainActivity来实现,主类继承了Android组件Activity。
客户端主控制类MainActivity中使用的MangicPackage类封装了数据报Socket的操作。
public class MangicPackage{
public boolean sendMessages(String str)throws IOException{
int port=9999;//端口号大于1024
String destIP=“255.255.255.255”;//广播地址
//检测地址,并将其转换为二进制
InetAddress destHost=null;
try{destHost=InetAddress.getByName(destIP);
}
catch(UnknownHostException e){e-.printStackTrace();}
//建立Socket数据包
byte[]magicBytes=str.getBytes();
DatagramPacket dp=null;
dp=new DatagramPacket(magicBytes,magicBytes.length,destHost,port);
DatagramSocket ds=new DatagramSocket();
ds.send(dp);//发送数据
ds.close();//关闭 Socket
return true;
}
}
服务端程序主要任务有两个,首先利用Socket来监听客户端的控制信息,然后解码信息,根据解码信息来实现锁屏或解屏操作。采用动态连接库方式实现全局钩子,钩子中完成锁屏及解屏。
HHOOK hKey=NULL;//保存键盘消息钩子句柄
HHOOK hMouse=NULL;//保存鼠标消息钩子句柄
HINSTANCE hInst=NULL;
BOOL WINAPI DllMain(HINSTANCE hInstance,DWORD reason,LPVOID lpVoid)
{hInst=hInstance;
return true;
}
//屏蔽鼠标的钩子
LRESULT CALLBACK MouseProc(int nCode,WPARAM wParam,LPARAM wParam)
{
if(nCode<0)//wParam和wParam中没有关于鼠标的消息
return CallNextHookEx(hMouse,nCode,wParam,lParam);
//屏蔽鼠标右键按下及弹起信息
if(wParam==WM_RBUTTONDOWN||wParam==WM_RBUTTONUP)
return 1;
//屏蔽鼠标移动
if(wParam==WM_MOUSEMOVE)
return 1
//屏蔽鼠标左键按下及弹起信息
if(wParam==WM_LBUTTONDOWN||wParam==WM_LBUTTONUP)
return 1;
else
return CallNextHookEx(hMouse,nCode,wParam,lParam);
}
//屏蔽键盘钩子
LRESULT CALLBACK KeyBoardProc(int nCode,WPARAM wParam,LPARAM lParam)
{
if(VK_F2==wParam && (lParam > >29&1)==1)
{
UnhookWindowsHookEx(hMouse);hMouse=NULL;
UnhookWindowsHookEx(hKey);hKey=NULL;
}
return 1;
}
//挂载钩子
EXPORT BOOL CALLBACK HookOn()
{
if(!hInst)
return false;
if(hKey||hMouse)
return false;
//函数的第四个参数为0,表示全局钩子
hMouse=SetWindowsHookEx(WH_MOUSE_LL,MouseProc,hInst,0);
hKey=SetWindowsHookEx(WH_KEYBOARD_LL,KeyBoardProc,hInst,0);
return hKey&& hMouse;
}
//卸载钩子
EXPORT BOOL CALLBACK HookOff()
{
BOOL bRetMouse=false,bRetKey=false;
if(hMouse)
bRetMouse = UnhookWindowsHookEx(hMouse);
if(hKey)
bRetKey=UnhookWindowsHookEx(hKey);
if(bRetKey&& bRetMouse)
{
hKey=hMouse=NULL;
return true;
}
return false;}
注意锁键盘钩子,本文特意留出解锁后门,防止因不可预料意外导致屏幕无法解锁,后门热键是“Alt+F2”,当然可设为任意。把动态链接库文件编译生成dll文件,然后拷贝到服务端主控程序的目录中,开始实现服务端主控程序,主控程序核心控制功能通过创建一个线程ControlThread来处理。
void ControlThread(HINSTANCE hInstance)
{TCHAR Tmp[256]=“”;
char ctrl;
WSADATA wWsadata;
WORD wWord;
wWord=MAKEWORD(1,1);
if(!WSAStartup(wWord,&wWsadata))//装载指定Socket
{//创建数据报SOCKET
SOCKET sock;
if((sock=socket(AF_INET,SOCK_DGRAM,0))!=INVALID_SOCKET)
{//本地地址与端口绑定
SOCKADDR_IN addr;
addr.sin_addr.s_addr=htonl(INADDR_ANY);//转换本地地址格式
addr.sin_family=AF_INET;
addr.sin_port=htons(9999);//端口号大于1024
//绑定
bind(sock,(SOCKADDR*)&addr,sizeof(SOCKADDR));
SOCKADDR_IN addrClient;//收到数据后,存储客户端的地址
int len=sizeof(SOCKADDR);
while(1)
{//接收数据开始,buf是定义的全局字符串变量
recvfrom(sock,buf,RECVLEN,0,(SOCKADDR*)&addrClient,&len);
int len=strlen(buf);
strcpy(Tmp,buf);
ctrl=Tmp[len-1];
switch(ctrl)
{case‘1’:if(屏幕没有锁)HookOn();
break;
case‘2’:if(屏幕被锁)HookOff();
break;
}
}
closesocket(sock);
WSACleanup();
}//卸载 Socket
}}
手机客户端:首先确定手机Android平台版本为2.3或以上,因客户端程序开发基于Android 2.3版本的开发包,然后下载该系统客户端生成的apk文件,直接点击安装即可。客户端程序安装及运行如图4所示。
图4 客户端界面
PC服务端:将C语言编写的主程序编译的可执行程序和主程序调用的全局钩子动态链接库dll文件一同复制到C盘根目录下即可。然后点击启动主程序,服务端程序处于接收客户端信号状态。服务端程序运行如图5所示。
图5 服务端程序界面
首先保证局域网当中的无线WiFi畅通,打开手机的无线连接且查验连接成功,然后启动PC端的服务程序,最后打开手机终端的控制程序,进入控制页面。该控制程序在Android 2.3及以上版本手机上进行测试,全部可以正常使用,具体测试结果如表1所示。
表1 测试结果
在数据报Socket数据传输技术支撑下,使用C和Java两种开发语言共同设计实现了一个无线控制的屏幕加解锁程序。该程序既能有效防止用户正在运行的计算机被非法使用,又可保证用户操作的方便性。其设计思想及实现方法将对逐步完善用户个人计算机安全性工作起到积极作用。
[1]Richard Stevens W.Advance programming in the UNIX environment[M].New York:Addison Wesley,1992.
[2]孙鑫.VC++深入详解[M].北京:电子工业出版社,2006.
[3]魔乐科技软件实训中心.Java从入门到精通[M].北京:人民邮电出版社,2010.
[4]金花.钩子函数在Windows键盘锁中的应用初探[J].福建电脑,2007(4):181-183.
[5]刘雄恩.捕捉和模拟Windows环境中鼠标和键盘操作的方法[J].福建农业大学学报:自然科学版,2003,32(2):256-260.
[6]董晓刚.浅析Android系统的四大基本组件[J].中国电子商务,2013(1):39.
[7]关晶鑫,李永全.Android中的Activity生命周期[J].电脑知识与技术,2013(11):2713-2715.
[8]Cowley N.The relevance of intent to human -android strategic interaction and artificial consciousness[J].Robot and Human Interactive Communication,2006(5):480 -485.