65.9K
CodeProject 正在变化。 阅读更多。
Home

Target Eye 揭秘:第一部分 - Target Eye 独特的自动更新机制

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.99/5 (58投票s)

2012 年 1 月 4 日

CPOL

9分钟阅读

viewsIcon

132440

downloadIcon

1601

Target Eye 的自动更新机制如何实现静默更新应用程序,从识别新版本、下载它们并运行它们,而不是旧版本。

引言

自动更新机制不一定基于实际的版本号(保存在版本字符串中),也可以基于新版本相对于旧版本的最后修改日期戳。Target Eye 监控系统,开发于 2000 年至 2010 年,就拥有这样的机制。本系列的下一篇文章是关于 Target Eye 的屏幕捕获,第三篇是关于 购物清单机制。第四篇文章是关于 键盘捕获。第五篇文章是关于 封面故事机制第六篇文章 是关于 Target Eye 式的文件隐藏。

背景

Target Eye 监控系统,由我十二年前开发,是最早的用于捕获远程计算机活动的监控和监视工具之一。以下描述摘自该项目的原始商业计划。

Target Eye 监控系统

Target Eye 是一家初创公司,其使命是开发集成软件解决方案,用于实时监控远程 PC,这些解决方案基于公司正在申请专利的技术(60/204,084 和 60/203,832)。我们的主要产品 Target Eye 监控系统是一款软件产品,能够以用户无法察觉的方式,持续跟踪、记录、回放、分析和报告在一台或多台远程 PC 上执行的任何活动。该软件依赖于快速捕获、压缩的全屏图像流和连续的键盘按键捕获,以提供用户活动的全面准确的记录,包括不产生任何网络流量的本地活动。通过这种方式,该软件可以跟踪和记录依赖网络流量分析的系统无法检测到的活动。在被监控 PC 上运行的智能代理模块使用规则库向监控位置发送警报或执行预定义的本地操作。可以从多个位置进行监控。主要市场是执法部门。

Target Eye 监控系统是通过自动更新机制开发的。该机制允许平滑且静默地(无人值守)执行新版本,而不是当前版本。

一张历史图片:Target Eye 的第一个版本(2000 年 4 月)

一个较新的版本(Target Eye 2007)

Target Eye 的组成部分

有几个术语和构建块构成了 Target Eye 监控系统。

  • Target Eye Secret Agent - 该产品的隐蔽部分,运行在被监控的计算机上。
  • Target Eye 操作员 - 操作秘密代理的权威(例如:执法机构)。
  • 一个任务 - 执行操作的请求。有许多可能的动作(例如,如我的另一篇文章所述的屏幕捕获,或如本文所述的文件搜索和查找)。
  • Target Eye Compiler - Target Eye Operator 用于自定义 Secret Agent 以执行特定任务的工具。

关于本文

本文仅关注该产品的一个方面,即自动更新机制。每当服务器上有可用新版本时,此机制都用于静默更新 Target Eye 秘密代理。

创建和标记增量版本

为了能够确定比当前版本更新的版本,必须有一个机制来标记各个版本,递增版本号,并检查给定可执行文件的版本。

最常见的方法是使用“版本字符串”资源,并使用预构建的自动化工具在每次构建应用程序时提升此字符串。

我使用了另一种方法,该方法基于可执行文件的列表修改日期。这种方法有利有弊,但在大多数情况下都很有用,因为它允许您的最终用户获取比当前已安装版本更新的任何应用程序版本。

在我解释之前,提及两个在启动时用于构建的全局变量很重要。

strRegularLocation   

应用程序正常运行时(例如,c:\program files\your app name\your app.exe)的路径和完整路径,

以及……

strTemporaryLocation 

用于更新时下载的临时版本的另一个完整路径。

这样做的原因是,由于应用程序下载其新版本,因此新版本无法下载到其当前位置,也无法替换自身,因为在文件被使用(或应用程序正在运行时),该文件被锁定,无法移动、删除或重命名。

char strRegularLocation[256];
char strTemporaryLocation[256];

strRegularLocationstrTemporaryLocation 填充实际值。

strRegularLocation 直接从 __argv[0] 获取,前提是我们确定我们并非已从临时位置运行。我们通过使用名为“INSTALL”的参数来确保这一点,稍后将进行解释。

strTemporaryLocation 是使用通用文件夹和临时名称组合而成的。我们使用 GetSpecialFolder() 在运行应用程序的任何计算机上查找此文件夹的路径名。

获取当前版本的日期戳

为此,使用第一个构建块 TEGetVersion(),它返回一个 CString,其中包含给定文件的最后修改日期。

//
TEGetVersion returns the last modification date / time of a given file
CString
TEGetVersion (CString FileName) 
{  
         CFileStatus status1;
         CTime Time1;
         if( CFile::GetStatus( FileName, status1) )
         {        
                 Time1 = status1.m_mtime; 
                 return (Time1.Format("%d%m%M%S"));
         } 
         // Failed 
         return ((CString)"");
}
//

我的应用程序启动时,我会将它的日期/时间戳存储在某个地方。

TE_Options.Version=TEGetVersion((CString)__argv[0]);
// here we keep the date/time stamp of the current version

现在我们需要获取文件的在线日期/时间戳,最好是在不必先下载它的情况下,这样我们就只在需要更新到新版本时才下载。

HINTERNET FileHandle=NULL; 
       
WIN32_FIND_DATA ftpFileData; 
//find the file on the ftp server 
       
FileHandle= FtpFindFirstFile( m_ftpHandle, FTP_NEWVERSION, &ftpFileData,
INTERNET_FLAG_RELOAD, 0 );
if( NULL != FileHandle )
{
       
    // get the write time of the ftp file
       
    SYSTEMTIME ftpFileWriteTime, stUTC1;
       
    FILETIME ftp;
       
    FileTimeToSystemTime( &ftpFileData.ftLastWriteTime, &stUTC1 );
       
    SystemTimeToTzSpecificLocalTime( NULL, &stUTC1, &ftpFileWriteTime );
}

我们需要定义当前版本应该有多旧才能进行更新。同样,这种方法在某些情况下可能非常有用,在其他情况下则不太有用。例如,如果您的应用程序涉及数据库,您可能希望确保数据库始终是最新版本,并且不超过 3 天。

#define UPDATEEVERY  60*24*7 // 7 days
#define APP_EXE_NAME "TargetEyeTest.exe"
#define FTP_NEWVERSION "NewVersion.exe"
#define APP_REGULAR_FOLDER "\\TargetEye\\"

下一步是比较每个文件的日期/时间戳。

CFileStatus
statusOld;
CTime TimeOld;
if( CFile::GetStatus( FileHandle, statusOld ) )
{
	CTime ct,OldTime;
    	OldTime=statusOld.m_mtime;
	hFindFile.GetLastWriteTime(ct);
	LONG Diff;
	ver=ct.FormatGmt("%d %m %Y %H:%M %Z");
	oldver=OldTime.FormatGmt("%d %m %Y %H:%M %Z");
 	Diff = ((CTimeSpan)(ct-OldTime)).GetTotalMinutes();
 	hFindFile.Close();
 	if (Diff>UPDATEEVERY || resultSpecific)
 	{
        // download the newer version
 	}
} 

下载新版本

使用此处列出的 TE_DownladLoad() 执行下载新版本。我们尝试多次,以防出现临时中断或通信问题。

#define FTPRETRIES 5 // number of retries
BOOL TE_DownloadLoad(char *FtpFileName,char *LocalFileName)
{
     int DoTry=FTPRETRIES; 
     int result; 
     TryAgain:; 
     try 
     {
         result = MyConnection.m_FtpConn->GetFile(FtpFileName, LocalFileName, FALSE);
     }
     catch (CInternetException* pEx)
     {
        TCHAR sz[1024]; 
        pEx->GetErrorMessage(sz,1024);
        WriteToLog("Error %s\n", sz);
        if(TE_DEBUG) MessageBox(NULL,sz,"Error 6 - TE_Load",MB_OK);
        pEx->Delete(); 
     }
     if (!result)
     {
         if(DoTry-- >0) goto TryAgain;
         return(FALSE);
     }
     else     
     {
           return (TRUE);
     }
}

现在我们可以切换当前运行的版本(旧版本)和新版本了。

执行新版本

BOOL ExecuteNewVersion(char *ExeName,char *Param)
{
     STARTUPINFO sinfo;
     PROCESS_INFORMATION  pinfo;
     ZeroMemory(&sinfo, sizeof(sinfo));
     sinfo.cb = sizeof(sinfo); 
     sinfo.lpDesktop= "WinSta0\\Default";
     sinfo.dwFlags=STARTF_USESHOWWINDOW ;
     sinfo.wShowWindow=SW_SHOW;
     if(!CreateProcess(NULL,(char*)(LPCTSTR)((CString)(ExeName)+(CString)"
         "+(CString)(Param)), NULL, NULL,FALSE,NORMAL_PRIORITY_CLASS |
         CREATE_NEW_CONSOLE, NULL, NULL, &sinfo, &pinfo))
     {
           char s[256];
           sprintf(s,"Can't execute program: %s params %s",ExeName,Param);
           // ERROR LOG
           TELog.LogError("Execute New Version",s,0);
           return FALSE;
     }
     else
     {                                  
           return TRUE;
     }
} 

因此,如果我们把所有的代码放在一起,我们就会得到

CFileStatus statusOld;
CTime TimeOld;
if(CFile::GetStatus( FileHandle, statusOld ) )
{
     CTime ct,OldTime;
     OldTime=statusOld.m_mtime;
     hFindFile.GetLastWriteTime(ct);
     LONG Diff;
     ver=ct.FormatGmt("%d %m %Y %H:%M %Z");
     oldver=OldTime.FormatGmt("%d %m %Y %H:%M %Z");
     Diff = ((CTimeSpan)(ct-OldTime)).GetTotalMinutes();
     hFindFile.Close();        
     if (Diff>UPDATEEVERY || resultSpecific)
     { 
         // downloading the newer version
         if(TE_DownLoad((resultGeneric)?NEWEXESTR:NEWEXE,TEMPPLACE))
         {
              // We have successfully downloaded the newer version
              if(ExecuteNewVersion(TEMPPLACE,"INSTALL"))
              {
                  // We have successfully executed the
                  // newer version. Current version can now quit
              }
              else
              // Failed to execute new version
          }
         else
         {
            TELog.LogError("New Ftp version found","Can't download",0);
         }
     }
}

TE_Init() 函数

TE_Init() 用于确定应用程序执行时使用的参数(与 Target Eye Monitoring System 的完整版本不同,后者更复杂,在我们的示例中,有一个可选参数——“INSTALL”)。

if(__argc>1) 
{
     if(strcmp(__argv[1],"INSTALL")==0)
     {
        // TE_FirstTime(); -> here you can place code you wish to
        //be executed only during the first run 
     }
}
else
// No parameters
{
     // Delete a temporary version if exists at the temporary location
}

用新的替换旧的

为了正常退出,而不丢失任何我们希望在退出前完成的事情,主事件循环包含一个对 NeedToQuit 值的检查,该值通常为 FALSE

BOOL NeedToQuit=FALSE; 

NeedToQuit 变为 TRUE 时,应用程序将在退出前执行任何必需的例程(例如,保存未保存的工作)。例如

if(NeedToQuit)
{
     if(TE_DEBUG)
         MessageBox(NULL,"Terminating Targe Eye",
           "Target Eye Monitoring System",NULL);
        return FALSE;
} 

此外,应用程序预期要么带“INSTALL”参数作为命令行的一部分执行,要么不带。以下方案说明了将新版本安装到临时位置(在本例中为桌面文件夹)的流程,直到用于安装的临时文件被删除。这需要几个阶段。

Target Eye 周期

阶段 带参数运行 从位置运行 描述
1 常规 当前(旧)版本在可用新版本之前运行
2     新版本检查临时位置是否有其自身的临时副本,但没有
3     找到新版本
4     新版本下载到临时位置
5     新版本从临时位置运行,并带有“INSTALL”参数。
6     当前版本退出
7 安装 临时 新应用程序从临时位置运行,并将其自身复制到常规位置,替换已退出的旧版本(5)。
8     新版本运行常规位置的副本并退出。
9 常规 新版本检查临时位置是否有其自身的临时副本,并将其删除。

为本次演示选择 FTP 服务器

为了使用本文附带的源代码,有一个由 Chilkat Software, Inc. 公开提供的公共安全 FTP 服务器的预定义设置。

该服务器的详细信息是

安全 FTP 服务器详细信息

类型 FileZilla
地址 ftp.secureftp-test.com
登录 测试
密码 测试

那里有一个名为 hamlet.xml 的文件,可用于测试远程文件日期戳。

国际化

为了符合全球用户场景,同时我们希望在同一时刻向所有用户发布一个版本,无论他们当地时间如何,我们使用

FormatGmt 

GMT 是一个绝对时间参考,不会因季节或地点而改变。FormatGmt 的用法如下:

CTime t( 1999, 3, 19, 22, 15, 0 );
// 10:15 PM March 19, 1999
CString s = t.Format( "%A, %B %d, %Y" );
ATLASSERT( s == "Friday, March 19, 1999" );

限制为单个实例

本文介绍的机制只能在我们限制应用程序在任何给定时刻只运行一次的情况下工作。为此,可以使用几种方法,例如 CreateMutex()

请参阅 http://support.microsoft.com/kb/243953

Target Eye 监控系统使用一种不同的、有点“粗暴”的方法,将在另一篇文章中详细介绍。基本上,Target Eye 监控系统会搜索当前正在内存中运行的其他实例,找到它们时,会执行以下两个选项之一:

  1. 如果找到的实例更新,则当前正在运行的实例退出。
  2. 如果找到的实例较旧,则当前正在运行的实例将其杀死。

为了说明,让我们考虑以下场景。最终用户已将其软件的当前副本更新为新版本。一天后,该最终用户运行软件的原始 CD。位于原始 CD 上的版本将在 FTP 服务器上搜索新更新,这是不必要的。此外,如果应用程序持续运行(就像监控应用程序应该的那样),那么很可能当 CD 版本运行时,也有另一个更新的版本已经在运行。为了解决这种情况和其他情况,我们应该采取必要的措施来确保应用程序始终保持最新。

延伸阅读

历史

  • 2012 年 1 月 4 日:初始版本

©2000-2010 Target Eye LTD (UK)

本文包含的所有材料均受国际版权法保护,未经 Target Eye LTD (UK) 事先书面许可,不得使用、复制、分发、传输、展示、发布或广播。您不得更改或删除副本中的任何商标、版权或其他声明。

Michael Haephrati , CodeProject MVP 2013

© . All rights reserved.