远程检测客户机上的系统补丁安装情况与端口开放状态

时间:2022-10-11 15:24:08
上周末WannaCrypt(永恒之蓝)横扫全球不仅用户受害,也坑了大量了机构的信息管理员成为背锅侠。为什么呢?MS17-010 漏洞微软于早在3月份已经提供了补丁程序(KB4012212),作为一个修复严重漏洞的补丁是强列推荐用户安装的,但是在一些公共开放场所,想让用户自觉的安装他们也没有这样习惯。而且多数学校会采用还原卡(或其他系统还原技术)来保护系统,想为系统打补丁必须得先关闭还原,然后在做系统同传。更一个机房可能就需要几个时间(使用云桌面技术除外)。在这种情况下信息管理员可能就赖于为每个机房去打补丁,或者部分机房部分机器已经补过,而部分机器没有补。所以一旦出现针对具体漏洞发起攻击的大事件下就问题严重了。
那么如何从一个管理端上详细的了解每一台客户机的补丁安装情况,是否安装了指定的补丁,已经安装了哪些?某些指定的端口是否处于开启状态或关闭状态。市面上已有一些成熟的安全产品具备这样的功能。但今天我们要讨论的是从程序开发的角度来分析用最简方案自己用程序代码来实现一套“程序检测工具”;
谈到远程序检测或远程控制,这里我想用白话来胡说一下,所谓的远程控制远程检测其实是建立在管理端与客户端之间的网络通讯基础之上的一种指令呼叫方式。比如说我想从管理端探测客户机上是否已经安装了某个补丁,或者我想远程关闭掉远程的客户机。这是如何完成的呢?
其实这是一切客户机上面它自己干的。没错就好比你的老板打电话命令你做一件事情,你接到命令后就把活给干了。回到我们要做的程序上来,我们想从管理端了解客户端上装了多少个补丁、装了哪些补丁。就先得让客户机上有一个程序先把这些信息扫描出来,然后把需要的结果传回给管理端。管理端就知道结果了,接下来就能做进一步的处理。
管理端与客户端之间只需要传递指令就可以了,在最简情况下指令甚至可以只是一个整数,双方约定好指令码即可。比如你老板和你之间有一个约定,当你听到他喊 1 的时候你就座下,他喊 2 的时候你就站起来…  总之我们想让管理端了解客户端的信息 ,必须客户端自己知道自己的信息。由于篇幅原因这个部分我会分段连载,今天我们先看一下如何从客户端获取相关的信息,后一步再讨论如何把这些信息传递给管理端。
 
废话不多说,直接跳过我走的弯路贡献代码先,从我目前查询的资料没有找微软Windows API 中获取已安装HotFix 信息的Function ,曾尝试直接从注册表中轮询出来,但是发现在Windows 7 以上的系统中原注册表
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WindowsNT\CurrentVersion\HotFix主键已经不存在,在XP 或 2003 系统中倒是可以从这个位置把已经补丁列表全部读出;不得以更换方案。其实Windows 中提供了一条命令行工具 SystemInfo  用它就可以详细的读出全部系统信息,当中就包括你已经安装过的补丁。                      


 “修补程序” 下列出的以KB 开头的记录就是已安装补丁的官方统一标识(编号),为了节省精力我决定直接调用这个工具来生成我所需要信息文件,再解析文件从中提取需要的内容。注意Systeminfo 不是一条MSDOS内部命令而是一个程序是一个可执行exe 文件。不过这个文件在XP 及以上的系统中都已经集成了。
在VC 项目通过调用Windows API 中CreateProcess 函数,开启一个进程在命令行中调用systeminfo 然后将其输出重定向到一个文本文件里面。


以下代码为 VC++ 12
CString tstsGetSystemInfo()
{
 
         TCHARszDir[MAX_PATH];
         GetCurrentDirectory(MAX_PATH,szDir);
 
         CStringsaveInfoFile;
         saveInfoFile.Format(L"%s\\systeminfo.csv", szDir);
 
         CStringmParameters;
         mParameters.Format(L"/c systeminfo > %s ", saveInfoFile );
 
         STARTUPINFOsi = { sizeof(si) };
         PROCESS_INFORMATIONpi;
 
         si.dwFlags= STARTF_USESHOWWINDOW;
         si.wShowWindow= SW_HIDE;
 
         BOOLbRet = ::CreateProcess(
                   _T("c:/windows/system32/cmd.exe"),
                   mParameters.GetBuffer(),
                   NULL,
                   NULL,
                   FALSE,
                   CREATE_NEW_CONSOLE,
                   NULL,
                   NULL,
                   &si,
                   &pi);
 
         mParameters.ReleaseBuffer();
 
         interror = GetLastError();
 
         if( bRet )
         {
                   //printf("新进程的进程ID号:%d /n", pi.dwProcessId);
                   //printf("新进程的主线程ID号:%d /n", pi.dwThreadId);
 
                   WaitForSingleObject(pi.hProcess ,INFINITE);
 
                   ::CloseHandle(pi.hThread);
                   ::CloseHandle(pi.hProcess);
        
         }
         else
         {
                   //printf("errorcode:%d/n", error);
         }
 
 
         returnsaveInfoFile;
}


 
执行成功后会返回生成的文本文件,我这里指定的格式是.csv
接下来用程序解读文件,这里用到了标准C++  中的ifstream 和 getline, csv 格式每一行的结尾都有换行符,因此这里只需要逐行读取就可以了,当读到"修补程序" 时,就知道接下来多少行是已安装的修补程序,用一个LIST 存下来就OK 了


ifstream Fsysinfo( infoFile );
         stringsline;
         inth = 0;
         intr = 0;
         boolfindhotfix = false;
 
         while( getline( Fsysinfo , sline) )
         {
                  
                   stringsb = sline.substr( 0, 8 );
 
                   if(  !findhotfix  && sb == "修补程序")
                   {
                            findhotfix= true;
 
                            //安装了 0 个修补程序
                            stringqz = "了";
                            stringhz = "个";
                            intl_i = sline.rfind( qz );
                            intr_i = sline.find_first_of( hz );
                            stringk = sline.substr(l_i + qz.length(), r_i - hz.length() - l_i );
                            h  = atoi( k.c_str() );                     
                            continue;
                   }       
 
                   if( findhotfix &&  r < h )
                   {
                            sline.erase(0,sline.find_first_not_of(" "));
                            sline.erase(sline.find_last_not_of("") + 1);
 
                            //网卡:             安装了 5 个 NIC。
                            if(sline.find("网卡") != string::npos )
                            {
                                     break;
                            }
           CString tmpPtint( sline.c_str() );
                       m_ListBox1.AddString(tmpPtint);
 
                            hotfixList.AddTail(tmpPtint);
 
                            r++;
                   }
         }


 
列表已经取到了余下只要和需要补丁名称做一个字符串比较就可以了,就是从列表中查找一个字符串,找到了就说明已经在,没有找到就说明没有安装。看是否需要做强制处理就看后面的处理,今天暂不讨论。
 
接下来是判断客户机上一些危险的TCP 端口是打开了,这次WannaCrypt 就利用了445 端口,同样也是让客户机先自我检查自己的指定端口是不是开放的。很简单WINSOCKET 中为我们提供了GetTcpTable 函数,我们只要通过一个循环轮一下就知道端口的启用的详细,必要的时候你可以把从0 – 65535 都测试一次,今天这里不必要。


bool testTCPPort(ULONG uPort)
{
         MIB_TCPTABLETcpTable[100];
         DWORDnSize = sizeof(TcpTable);
         if( NO_ERROR == GetTcpTable(&TcpTable[0], &nSize, TRUE) )
         {
                   DWORDnCount = TcpTable[0].dwNumEntries;
                   if(nCount > 0)
                   {
                            for(DWORD i = 0; i<nCount; i++)
                            {
                                     MIB_TCPROWTcpRow = TcpTable[0].table[i];
                                     DWORDtemp1 = TcpRow.dwLocalPort;
                                     inttemp2 = temp1 / 256 + (temp1 % 256) * 256;
                                     if(temp2 == uPort)
                                     {
                                               returnTRUE;
                                     }
                            }
                   }
                   returnFALSE;
         }
         returnFALSE;
}
 


自己写一个方法,返回 false 或 true 交给上层来处理。

通过自己写的这两个方法,就基本可以实现一个补丁自扫和端口自扫的单机版工具。基于这个基础之上,后期加上网络通讯和管理端的对接就能可以实现“远程”探测了。