最近这三个星期,参加了学校组织的实训,做的全是Windows下的东西,实际编码时间就是一周左右。哎,略扯。虽然自己是搞Linux的,但是其实有时候了解一下windows也算是不错了。毕竟了解多了不算坏事。不费话了,下面我们避开前期扯淡的一大堆文档。直接开始实现策略。
LOGO First
界面 Second:
【一】.软件功能
实现对局域网内客户机的进程管理,以及硬件信息、操作版本和网络流量信息查阅,客户端具有隐秘、放查杀保护和开机自启的特点,实现单一服务端管理局域网内多客户端的系统进程,对非法进程有手动查杀和黑名单自动查杀的功能。
【二】.软件的设计架构
整体的软件设计分为两部分,即Client与Server端。下面放出一张设计架构的贴图:
软件的整体设计框架就如这张图,整体采用标准的TCP/IP
通信协议。在策略上仿照FTP的双端通信的模型,即服务端开启两个通信端口,一个端口是用来接受客户端采集的数据(进程信息、网络流量信息,OS信息、CPU、内存、网卡等等一系列的硬件信息),另一个端口是用来做客户端等待远程指令的,默认在这一条处理通道中第一次会传输黑名单列表的信息。大体上就是这个样子。下面来说说具体的服务端与客户端的实现。
【三】.服务端实现
服务端的实现完全是采用windows下MFC封装的Csocket通信类(不是基础的socketAPI,windows下的socket API与Linux基本都是差不多的)。它在处理多客户端的连接时不用自己考虑处理模型的问题,在实现机制上其实MFC本身也是通过windows的消息响应机制,通过父窗体指针Cwnd*与子窗体指针的转化实现对不同客户端数据的处理,并且在内部实现上其实也是为每一个连接的客户端建立一个线程,但是MFC本身有自己的线程管理机制。在这点上就不用自己去管理每一个客户端的线程了。但缺点是正是由于自己不用管理,在服务端性能上感觉略扯,这种算是以线程数来抗并发连接,绝对不是好的服务端的解决策略。但是由于这次是简单的局域网管理,也ok了(实训要求必须用MFC才是。。。。。。省略)。
然后,MFC这种封装机制实现了接收数据的统一性,每个客户端的数据请求都会有一个Onrecieve()回调函数,每一个客户端的数据都会触发他,然后后台处理这边都会根据他已有的IP标识来判断是属于那个客户端的数据。在服务端我们把每个客户端的数据都组织为一个大的struct,然后塞进Clist<>统一管理。
对于每一个客户端的数据包都会存在一个数据包的标识,服务端不会管收到什么数据,它判断数据包是什么信息都是通过这个数据包的其实标识信息。(数据包,这里简单来说就是一个struct,我们通过内存拷贝到缓冲区,发送,然后再通过内存拷贝回原来的结构)。
在服务端的网路流速信息这里我们选用了TeeChart来进行数据的动态显示,但是老是崩溃,还是不太稳定。,。。。。。其他信息都基本还算正常,服务端当然可以通过手动查杀的方式来结束客户机的进程,比如QQ 、LOL、什么的。只要不是系统进程。都ok。客户端接受的黑名单都是通过服务端发送的,默认在指令的那一个通道正常连接后,服务端发送一次黑名单信息,然后以后每一次服务端修改黑名单都会触发一个消息到客户端,准备再次更新黑名单信息。
【四】.客户端实现
客户端这边由于自己对socket有一定的了解,所以学习winsock会很快,winsock的一个流程基本和Linux下socket差不多。由于windows和Linux两个平台都是继承BSD的socket通信机制。所以,都是一样的。在客户端的设计策略上先贴一张图:
之前在服务端那边说过采用开放双端口的模型,所以在客户端这边会有对应的两条线路进行与服务端的通信,即上图的双线程模块处理,两条线程处理的是不同的数据逻辑,当然这里的两个线程只是两条主线程,我还有一条线程跑的是一个循环检测系统进程模块,这个线程会根据服务端的黑名单与本机的进程列表进行比对,找到的话调用windows系统API进行查杀。这样就做到了客户端进行自比对查杀的功能。当然那条主线程会一直在监听来自服务端的远程指令,做到远程的进程管理。
客户端其实只是有一个总控类,一切的功能都其实是在这个总控类中实现的,包括客户端自动重新连接功能,其实就是把客户端当前的对象进行析构,再new出一个新的对象,重新初始化一切东西,但是这里有一个问题就是当C++遭遇多线程时,当多个线程都在引用这个对象,什么时候在哪个线程进行析构就是个很头疼的问题,里面涉及了很复杂的时序逻辑问题。推荐看看《muduo》那本书。里面有讲到。下面是我客户端的总控类:
class CClient_Control{
private:
OS_INFO *ms; //系统硬件信息Gather数据包结构
Final_Info *ps; //系统进程信息列表数据包结构
NetFlux *net; //系统网络流速信息数据包结构
CRITICAL_SECTION mylock; //防止多线程同时访问同一变量的临界区锁
public:
CClient_Control(){ //总控类初始化构造函数
this->ms=(OS_INFO*)malloc(sizeof(OS_INFO));
this->ps=(Final_Info*)malloc(sizeof(Final_Info));
this->net=(NetFlux*)malloc(sizeof(NetFlux));
InitializeCriticalSection(&(this->mylock));
memset(this->m_Black_List,'\0',Black_Num*name_length);
}
~CClient_Control(){ //总控类析构函数
free(this->ms);
this->ms=NULL;
free(this->ps);
this->ps=NULL;
free(this->net);
this->net=NULL;
DeleteCriticalSection(&(this->mylock));
}
void Init_MainMessage_Part(); //主初始化函数,启动双端口通信
static void Begin_Kill_Part(void *); //子线程初始化指令等待模块
void Network_Adapter(); //计算机网卡信息获取函数
void GetNetSpeed(float *, float *); //获取网络流速函数
void GetHardwareInfo(); //系统CPU信息获取函数
void os_version(); //获取操作系统版本API
void Gethostname(); //获取主机名称
void Getmemory_stat(); //内存状态获取
intProcess_Info(); //给系统进程创建快照函数
void Collect_Info();
void send_message(SOCKET ); //硬件信息发送函数
void Process_And_NetFlux( SOCKET); //进程与流量信息发送函数
BOOL KillProcess(DWORD); //调用系统API查杀进程
void Find_Process_ID(char *); //进程列表查找对应进程PID
void wait_kill_command(SOCKET); //子线程等待远程查杀指令函数
static void Check_Process_With_BlackList(void *); //单线程循环检测,根据本地黑名单本地查杀模块
protected:
char m_Black_List[Black_Num][name_length]; //系统黑名单存取结构
};
注:里面的临界区变量,其实就是相当于Linux下的互斥锁那种概念。
在客户端的信息采集这边,我们全部调用的是windows系统的底层API,并未涉及任何的第三方库。所以在采集信息这块有一定的学习时间。下面是一些对应的数据包结构以及他们的填充函数(篇幅问题,只给出API):
【系统数据包】(服务端只接受一次,因为这部分信息长时间都不会改变):
typedef struct Mess_Info{
int flag;
char m_stProcessorType[name_length]; //CPU架构类型
char m_stwProcessorArchitecture[name_length]; //CPU设计架构
DWORD m_dwOemId; //OEMID :sysInfo.dwOemId
DWORD m_dwProcessorType; //处理器的类型
DWORD dwNumberOfProcessors; //处理器个数
DWORD dwMaxClockSpeed; //CPU主频
char vername[50]; //操作系统名称
DWORD dwMajorVersion; //系统主版本号
DWORD dwMinorVersion; //系统副版本号
TCHAR szCSDVersion[128]; //系统server pack版本
DWORD dwBuildNumber; //系统构建版本号
DWORD dwPlatformId; //系统支持的架构平台
char ComputerName[MAX_COMPUTERNAME_LENGTH]; //计算机主机名称
int memory_already_use;
long memory_total; //系统总共的物理内存大小
long memory_for_free;
char m_Netowrk_Adapter[2048]; //网卡信息(组织打包了,直接是一个字符数组)
}OS_INFO;
【进程列表数据包】:
typedef struct infor{
int flag;
PROCESS_INFO m_pop[MAX_PROCESS];
}Final_Info;
【网路流速数据包】:
typedef struct Net{
int flag;
float m_Final_Send;
float m_Final_Recv;
}NetFlux;
对应获取的API省略,只给出关键结构:
【网卡信息】
PIP_ADAPTER_INFO pAdapterInfo;
PIP_ADAPTER_INFO pAdapter = NULL;
ULONGulOutBufLen = sizeof(IP_ADAPTER_INFO);
pAdapterInfo= (PIP_ADAPTER_INFO)malloc(ulOutBufLen);
DWORDdwRetVal = GetAdaptersInfo( pAdapterInfo, &ulOutBufLen);
【网络流速】:
MIB_IFTABLE*pIfTable=(MIB_IFTABLE*)malloc(sizeof(MIB_IFTABLE));
GetIfTable(pIfTable, &dwSize, FALSE);
【CPU信息比对】:
给个完成整的,懒得截了:
void CClient_Control::GetCpuInfo(){
SYSTEM_INFO sysInfo;
CRegKey regkey;
LONGl Result;
DWORD dwValue;
std::stringstrPath="HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0";
lResult=regkey.Open(HKEY_LOCAL_MACHINE,LPCTSTR(strPath.c_str()),KEY_ALL_ACCESS);
GetSystemInfo(&sysInfo);
memset(this->ms->m_stProcessorType,'\0',name_length);
memset(this->ms->m_stwProcessorArchitecture,'\0',name_length);
if(sysInfo.dwProcessorType ==PROCESSOR_INTEL_386) {
strcpy(this->ms->m_stProcessorType,"Intel386");
}
elseif (sysInfo.dwProcessorType ==PROCESSOR_INTEL_486) {
strcpy(this->ms->m_stProcessorType,"Intel486");
}
elseif (sysInfo.dwProcessorType ==PROCESSOR_INTEL_PENTIUM) {
strcpy(this->ms->m_stProcessorType,"IntelPentium架构");
}
else{
strcpy(this->ms->m_stProcessorType,"Unknown");
}
if(sysInfo.wProcessorArchitecture==9){
strcpy(this->ms->m_stwProcessorArchitecture,"x64(AMD or Intel)");
}elseif(sysInfo.wProcessorArchitecture==PROCESSOR_ARCHITECTURE_IA64){
strcpy(this->ms->m_stwProcessorArchitecture,"IntelItanium Processor Family (IPF)");
}elseif(sysInfo.wProcessorArchitecture==PROCESSOR_ARCHITECTURE_INTEL){
strcpy(this->ms->m_stwProcessorArchitecture,"x86");
}elseif(sysInfo.wProcessorArchitecture==PROCESSOR_ARCHITECTURE_UNKNOWN){
strcpy(this->ms->m_stwProcessorArchitecture,"Unknownprocessor");
}
this->ms->m_dwOemId=sysInfo.dwOemId;
this->ms->dwNumberOfProcessors=sysInfo.dwNumberOfProcessors;
this->ms->m_dwProcessorType=sysInfo.dwProcessorType;
if(ERROR_SUCCESS == regkey.QueryValue(dwValue,"MHz")){
this->ms->dwMaxClockSpeed = dwValue;
}
regkey.Close();
}
【操作系统】:
void CClient_Control::os_version(){
OSVERSIONINFOEX osVersionInfoEx;
osVersionInfoEx.dwOSVersionInfoSize=sizeof(osVersionInfoEx);
if(!GetVersionEx((LPOSVERSIONINFO)&osVersionInfoEx)){
strcpy(this->ms->vername,"GetVersionEx(LPOSVERSIONINFOosVersionInfoEx) return FALSE");
std::cout<<this->ms->vername<<std::endl;
}
else{
strcpy(this->ms->vername,"操作系统信息:");
switch(osVersionInfoEx.dwPlatformId){
case VER_PLATFORM_WIN32_NT:
if(osVersionInfoEx.dwMajorVersion==6&& osVersionInfoEx.dwMinorVersion==0)
strcat(this->ms->vername,"MicrosoftWindows Vista");
if(osVersionInfoEx.dwMajorVersion==6&& osVersionInfoEx.dwMinorVersion==1)
strcat(this->ms->vername,"MicrosoftWindows 7");
elseif(osVersionInfoEx.dwMajorVersion==5 &&osVersionInfoEx.dwMinorVersion==0)
strcat(this->ms->vername,"MicrosoftWindows 2000");
elseif(osVersionInfoEx.dwMajorVersion==5 &&osVersionInfoEx.dwMinorVersion==1)
strcat(this->ms->vername,"MicrosoftWindows XP");
elseif(osVersionInfoEx.dwMajorVersion==4 &&osVersionInfoEx.dwMinorVersion==0)
strcat(this->ms->vername,"MicrosoftWindows NT");
break;
case VER_PLATFORM_WIN32_WINDOWS:
if(osVersionInfoEx.dwMajorVersion==4&& osVersionInfoEx.dwMinorVersion==10)
strcat(this->ms->vername,"MicrosoftWindows 98");
elseif(osVersionInfoEx.dwMajorVersion==4 && osVersionInfoEx.dwMinorVersion==90)
strcat(this->ms->vername,"MicrosoftWindows ME");
else
strcat(this->ms->vername,"MicrosoftWindows 95");
break;
default:
break;
}
}
strcpy(this->ms->szCSDVersion,osVersionInfoEx.szCSDVersion);
this->ms->dwMajorVersion=osVersionInfoEx.dwMajorVersion;
this->ms->dwMinorVersion=osVersionInfoEx.dwMinorVersion;
this->ms->dwBuildNumber=osVersionInfoEx.dwBuildNumber;
this->ms->dwPlatformId=osVersionInfoEx.dwPlatformId;
}
【主机名】:
GetComputerName(this->ms->ComputerName,&MaxComputerlenth);;
【内存状态】:
MEMORYSTATUS stat;
GlobalMemoryStatus(&stat);
【系统运行进程快照】:
int CClient_Control::Process_Info(){
PROCESSENTRY32pe32;
Final_Info*pi=this->ps;
inti=0;
pi->flag=Process_Flag;
pe32.dwSize= sizeof(pe32);
HANDLEhProcessSnap = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);
if(hProcessSnap == INVALID_HANDLE_VALUE){
printf("CreateToolhelp32Snapshot调用失败.\n");
return-1;
}
BOOLbMore = ::Process32First(hProcessSnap,&pe32);
while(bMore){
(pi->m_pop[i]).PROCESS_ID=pe32.th32ProcessID;
strcpy((pi->m_pop[i]).PROCESS_NAME,pe32.szExeFile);
i++;
bMore= ::Process32Next(hProcessSnap,&pe32);
}
pi=NULL;
::CloseHandle(hProcessSnap);
return0;
}
【针对进程PID查杀指定进程】:
BOOL CClient_Control::KillProcess(DWORD ProcessId){
HANDLE hProcess=OpenProcess(PROCESS_TERMINATE,FALSE,ProcessId);
if(hProcess==NULL)
return FALSE;
if(!TerminateProcess(hProcess,0))
return FALSE;
return TRUE;
}
以上是一些API,由于篇幅限制,就这样吧。
【五】客户端的双进程保护
这部分就是一个对于客户端的防查杀功能,目的是方式用户直接将client干掉,那句没得控制了。所以才做了这一部分,其实逻辑结构很简单:
进程一:检测进程二与客户端client进程,其中任何一个没有启动就立刻调用API启动那个exe。
进程二:检测进程一,若是没有则启动。
一开始就只需要启动进程2就是这样一个简单的逻辑,但是你会发现,你关闭掉进程1,进程2,和client三个中任意一个都会发现又重启了。你可以从这个逻辑入手判断。
并且这三个exe都是系统创建的,不属于同一个进程树,所以你没有办法通过在任务管理器中通过结束进程树的方式结束他们。(当然我们的三个进程都会隐藏的)。
一切的东西只能说个大概,其实就当是总结了。扯淡over !