分类归档: Programming

编程编程编程。。。

杀毒软件编程精华——特征码扫描技术

特征码扫描技术是反病毒技术中的常规武器,虽然特征码扫描技术有着一定的局限性,但是,改进的基于特征码扫描技术的广 谱特征串过滤技术却有着无比的优越性和广阔的应用前景。但是,无论是上面提到的哪种技术,最终都难以逃脱扫描算法的效率要求。而学过《数据结构》的读者应 该知道一种快速的字符串扫描算法叫做“模式匹配算法”,这种算法可以对字符串进行快速扫描。但是,深入进去就会发现,我们的特征码扫描算法中经常会用到通 配符,如:%和*(这里我令%匹配一个字符,*匹配32个字符),对于这样一些非常规的串,没有现成的算法可以引用。笔者在做防治木马技术的研究中,对常 规的模式匹配算法进行了改进,写出了如下的快速查找算法,暂且命名为“半回溯的模式匹配算法”,以与常规的“无回溯的模式匹配算法”区别。
这里,我之所以称之为“半回溯”,就是因为在对通配符进行匹配时要进行适当的回溯,以减少因为采用通配符所带来的过长推进,虽然这是一种带有回溯的算法, 但是实际上它的效率并不比“无回溯的模式匹配算法”低多少,其对算法的效率不能构成真正的威胁。这个算法比较抽象,如果没有基础的读者最好先回头看一下 《数据结构》的教材中的“无回溯的模式匹配算法”,会对您接下来对算法的理解产生很大的帮助。光说不练,不是好汉,下面我们就开始艰辛而又刺激的模式匹配 算法之旅。
对于“无回溯的模式匹配算法”,我不想做过多的说明,因为相关书籍上已经说得够明白了。下面,我们一起来看一下带通配符的半回溯的匹配算法(用%号匹配一个字符,用*匹配32个字符):
1.[初始化]
i=1; j=1;
counter1=0; counter2=0;
2.[利用next[i]反复进行比较,直到i等于m+1]
循环 当i<=m且j<=n时,反复执行
若(p[i]!=’*’且p[i]==t[j])
则i=i+1; j=j+1;
否则若p[i]==’%’
则 i=i+1; j=j+1; counter1=counter1+1;
否则若p[i]==’*’
则i=i+1; j=j+32; counter2=counter2+1;
否则若next[i]>0
则i=next[i] j=j – (counter1+counter2*32);counter1=0;counter2=0;
否则
i=1; j=j – (counter1+counter2*32);j++;counter1=0;counter2=0;
其中,counter1是用来统计%的个数,而counter2用来统计*的个数,其中m是子串的长度,n是被搜索串的长度。每次碰到子串中有%的时候就 自动将counter1加一,若是碰到counter2就自动将counter2加一。i用来指明子串的当前的比较的位置,而j用来指明被搜索串当前比较 的位置,如果一旦子串与被搜索串不能满足搜索条件,就要将被搜索串的比较位置进行回溯,以回到被通配符漏过的字符的个数,以便重新匹配。
这里,涉及到了与“无回溯的模式匹配算法”中相同的要使用的Next数组,其中,Next数组的计算方法如下:
1.[初始化]
j=0; i=1;
next[1]=0;
2.[反复比较计算next[i+1]
循环 当i<m时,反复执行
(1)[找出p1p2…pi中最大的相同的前缀和后缀,并将长度送j]
循环 当j>0且p[i]!=p[j] 且p[i]!=’%’且p[j]!=’%’时,反复执行
j=next[j]
(2)[计数器加1]
i=i+1; j=j+1;
(3)[计算next[i]]
若p[i]==p[j]或p[i]==’%’或p[j]==’%’
则next[i]=next[j];
否则next[i]=j;
Next数组的计算方法,与“无回溯匹配算法”稍有不同,因为%用来匹配一个字符,所以对于%的处理,可以看作比较的字符相等的情况来处理,而对*的比较,无论如何一个字符都不可能与一个32字符的字符串相等,所以对于*始终认为不相等。
这个算法的描述,无论我作何解释,读者总会感觉到有一些抽象,而难于真正理解。下面,笔者写了一个简单的程序,来对这个算法进行测试,希望对您会有所帮 助。这个程序是在VC6.0的控制台下运行的,非常简单,基本上就上将以上的算法翻译成了C++语言。源程序可以在附书光盘中找到,名称为“带通配符的无 回溯模式匹配算法源程序”。
在这个程序中,首先就是要建立一个子串和被搜索串,并确定算法中提到的m和n的值,如下所示。
//被搜索的串
char* t="nihaoaxishanchangshanchangaoyejiehhe*%dfisd%shanchashanchanghongioejw";
//子串
char* p="shanchang%*%hong";
//next数组中元素的个数等于子串的长度
int next[16];
int i,j;
//initialize variable
j=0;i=1;next[0]=0;
接下来,要计算next数组的值,对上面的算法进行翻译,得到如下代码。
//计算next数组的循环
while(i<16)
{
while(j>0&&p[i-1]!=p[j-1]&&p[i-1]!=’%’&&p[j-1]!=’%’)
j=next[j-1];
i++;
j++;
if(p[i-1]==p[j-1]||p[i-1]==’%’||p[j-1]==’%’)
next[i-1]=next[j-1];
else
next[i-1]=j;
}
有了至关重要的next数组之后,就要使用改进了的模式匹配算法,从被搜索串中搜索出子串,代码如下:
//模式匹配的循环
i=1;
j=1;
int counter1=0,counter2=0;
while(i<=16&&j<=69)
{
if(p[i-1]!=’*’&&p[i-1]==t[j-1])
{i++;j++;}
else if(p[i-1]==’%’)
{
i++;
j++;
counter1++;
}
else if (p[i-1]==’*’)
{
i++;
j+=32;
counter2++;
}
else if (next[i-1]>0)
{
i=next[i-1];
j-=(counter1+counter2*32);
counter1=0;
counter2=0;
}
else
{
i=1;
j-=(counter1+counter2*32);
j++;
counter1=0;
counter2=0;
}
//如果I等于17,说明对子串的搜索已经走到尽头,并搜索到了子串,跳出循环。
if(i==17) break;
}
如果您需要使用这个算法进行特征码的查找,可以直接将其中的计算next数组和模式匹配的部分进行移植。
到这里,基本上将要说的都说完了,可能您对这个算法还是有一些迷惑,如果您对其的正确性有疑问的话,可以使用上面的程序,对不同的子串和被搜索串进行操 作。另外,如果您对next数组不太了解,还是建议您找出教材来好好看一下。本算法的效率是比较高的,至于它的计算复杂度,我想在这里我就不能做过多的说 明了,有兴趣的读者,或者从事反病毒研究的读者,希望能对你们有一定的帮助,同时,如果有不对的地方,可以在黑防的论坛上面提一下,以期获得改进。
后记:对于一个真正的程序员来说,其任务决不应该仅仅只是机械地拖拖控件,动动鼠标。更重要的应该是编程开始前的软件的整体安排和设计,而相关算法设计也 应该是非常重要的一部分。对于一个扫描软件来说,其扫描算法的设计也定是其精华所在。本文专门对算法进行相关分析,希望能够对您有所启发。

Read: 953

CGI扫描器的原理和实现过程

有很多网站为了安全起见,在WEB Server前面架了防火墙,或者做了TCP/IP过滤,对外只开放TCP 80 端口。从入侵者角度来看,要入侵那 么从80上跑的CGI入手是比较可行的,当然也可以用别的办法,例如旁敲侧击,呵呵。从网管角度来看,一是要保证CGI的安全性,另外网络的整体安全性也 是很重要的。针对基于80端口入侵、防范而出的CGI扫描器数不胜数,但基本上原理都一样。
CGI扫描器原理说起来其实非常简单,可以用四句话来概括:<1>连接目标WEB SERVER;<2>发送一个特殊的请求;<3>接收目标服务器返回数据;<4>根据返回数据判断目标服务器是否有此CGI漏洞。
当管理的服务器达到一定数量的时候,手工检测自己的服务器是否存在各种各样的CGI漏洞,那就太消耗时间和精力了,所以一个网管手上有个比较好用的 CGI漏洞扫描器还是必要的。OK!今天我们就自己来动手用C写一个简单的CGI扫描器,帮助自己在日常工作中检测服务器:))

源代码如下,很多地方我都加了注释,别嫌我烦哦:))编译好的程序可以从http://eyas.3322.net/program/cgicheck.exe下载。

/*************************************************************************

说明:这是一个Console下多线程,带有进度显示的CGI扫描器的模板,更改一下szSign和SendBuff就可以扫描其他CGI漏洞,设置了连接、发送、接收超时,速度还可以哦。希望可以帮助到admins检测自己的服务器:))

*************************************************************************/

#include <stdio.h>

#include <winsock2.h>

#include <time.h>

#define iPort 80//目标Web Server端口

#define szSign "500 13rnServer: Microsoft-IIS/5.0"//根据此标志来检查目标是否有漏洞

#pragma comment(lib,"ws2_32.lib")

///////////////////////////////////////////////////////////////////////////

//

file://定义&初始化全局变量

//

char *SendBuff="GET /NULL.printern",//发送的请求buff

CurrentTarget[52]={0},//存放最后一个线程将扫描的目标

turn[4][2]={"-","\"," ","/"};//显示进度时的字符

int SendBuffLen=strlen(SendBuff),//发送的buff长度

iConnTimeout,//TCP Connect TimeOut

ii=0,//扫描进度

iTotal;//服务器总数

HANDLE hSemaphore=NULL,//信标内核对象句柄,用来控制线程数量

hStdout;//console标准输出句柄,做进度显示的时候用的

struct timeval timeout;//连接、发送和接收的超时值

DWORD SleepTime;//每个一个线程后等待的时间

/*

SleepTime值根据用户输入的线程数量[ThreadNum]和TCP ConnectTimeOut[CONNTIMEO]来计算。确保在 CONNTIMEO时间左右开    ThreadNum个线程。这样在CONNTIMEO时间后,所开的线程开始陆续超时退出,可以继续稳定的开线程, 可以有效的保证同时有    ThreadNum个线程在运行。

*/

///////////////////////////////////////////////////////////////////////////

void ShowError(char *);//显示出错信息函数,可以写完善一些,偶偷懒了:)

BOOL ResetCursor(void);//重置光标位置,线程输出的时候调用的

DWORD WINAPI ShowProInfo(LPVOID);//显示进度信息

DWORD WINAPI scan(LPVOID);//扫描函数

void usage(char *);//帮助函数

///////////////////////////////////////////////////////////////////////////

int main(int argc,char **argv)

{

HANDLE hThread=NULL;//线程句柄

DWORD dwThreadID;//线程ID

struct sockaddr_in sa;

int i,

MaxThread;//最大线程数量

WSADATA    wsd;

long PreviousCount;

clock_t start,end;//程序运行的起始和结束时间

double duration;

file://检查用户输入参数

if(argc!=5)

{

usage(argv[0]);

return 1;

}

file://get target range

int StartNet=inet_addr(argv[1]);

int StopNet=inet_addr(argv[2]);

int StartHost=ntohl(StartNet);

int StopHost=ntohl(StopNet);

file://取得线程数量

MaxThread=atoi(argv[3]);

file://取得conn超时时间

iConnTimeout=atoi(argv[4]);

file://检查参数合法性

if((iConnTimeout>6) (iConnTimeout<2) (MaxThread<1) (MaxThread>500) (StopHost<StartHost))

{

usage(argv[0]);

return 1;

}

file://计算时间

SleepTime=1000*iConnTimeout/MaxThread;

file://设置连接超时值

timeout.tv_sec = iConnTimeout;

timeout.tv_usec =0;

__try

{

file://开始计时

start=clock();

file://加载winsock库

if (WSAStartup(MAKEWORD(1,1), &wsd) != 0)

{

    ShowError("WSAStartup");

    __leave;

}

file://创建信标内核对象句柄

hSemaphore=CreateSemaphore(NULL,MaxThread,MaxThread,NULL);

if(hSemaphore==NULL)

{

    ShowError("CreateSemaphore");

    __leave;

}

file://取得console标准输出句柄

hStdout=GetStdHandle(STD_OUTPUT_HANDLE);

if(hStdout==INVALID_HANDLE_VALUE)

{

    ShowError("GetStdHandle");

    __leave;

}

file://设置目标总数

iTotal=StopHost-StartHost;

file://创建进度显示线程

hThread=CreateThread(NULL,0,ShowProInfo,NULL,0,&dwThreadID);

if(hThread==NULL)

{

    ShowError("1 CreateThread");

    __leave;

}

file://关闭句柄

CloseHandle(hThread);

file://循环创建扫描线程

for(i=StartHost;i<=StopHost;i++)

{

file://等待信标内核对象通知

    WaitForSingleObject(hSemaphore,INFINITE);

    file://create thread to scan

    hThread=CreateThread(NULL,0,scan,(LPVOID)i,0,&dwThreadID);

    if(hThread==NULL)

{

    ShowError("2 CreateThread");

break;

}

file://进度自加1

    ii++;

file://重设最后一个线程扫描的目标

    sa.sin_addr.s_addr=htonl(i);

    strncpy(CurrentTarget,inet_ntoa(sa.sin_addr),sizeof(CurrentTarget));

file://休息一会儿:))

    Sleep(SleepTime);

file://关闭线程句柄

    CloseHandle(hThread);

}

file://等待所有线程结束

while(1)

{

    WaitForSingleObject(hSemaphore,INFINITE);

    if(!ReleaseSemaphore(hSemaphore,1,&PreviousCount))

{

ShowError("main() ReleaseSemaphore");

Sleep(5000);

break;

}

    if(PreviousCount==(MaxThread-1))

    {

printf("nAll done.");

break;

}

    Sleep(500);

}

}//end of try

file://搞定,清场,收工

__finally

{

file://计时结束

end=clock();

file://转换时间格式

duration = (double)(end – start) / CLOCKS_PER_SEC;

file://显示所用时间

printf("nnComplete.Scan %d targets use %2.1f seconds.Speed %0.3g/sn",iTotal,duration,iTotal/duration);

file://关闭句柄

CloseHandle(hStdout);

CloseHandle(hSemaphore);

WSACleanup();

}

return 0;

}

///////////////////////////////////////////////////////////////////////////

//

file://回显错误信息函数

//

void ShowError(char *msg)

{

MessageBox(NULL,msg,"ERROR",0);

file://printf("n%s failed:%d",GetLastError());

}

//////////////////////////////////////////////////////////////////////////

//

file://重置光标位置函数,以便扫描线程输出结果

//

BOOL ResetCursor()

{

CONSOLE_SCREEN_BUFFER_INFO ConsoleScreenBufferInfo;

file://取得当前光标位置

if(!GetConsoleScreenBufferInfo(hStdout,&ConsoleScreenBufferInfo))

{

ShowError("GetConsoleScreenBufferInfo");

return FALSE;

}

file://设置光标X坐标为0

ConsoleScreenBufferInfo.dwCursorPosition.X=0;

file://设置当前光标位置

SetConsoleCursorPosition(hStdout,ConsoleScreenBufferInfo.dwCursorPosition);

return TRUE;

}

///////////////////////////////////////////////////////////////////////////

//

file://显示进度信息函数

//

DWORD WINAPI ShowProInfo(LPVOID lp)

{  

int j,k;

CONSOLE_SCREEN_BUFFER_INFO ConsoleScreenBufferInfo;

float m;

for(j=0;ii<iTotal;j++)

{

file://休息一会儿:)))

Sleep(SleepTime);

file://取得当前光标位置

if(!GetConsoleScreenBufferInfo(hStdout,&ConsoleScreenBufferInfo))

{

    ShowError("GetConsoleScreenBufferInfo");

    return 1;

}

file://设置百分比进度显示的X坐标

ConsoleScreenBufferInfo.dwCursorPosition.X=0;

file://设置当前光标位置

SetConsoleCursorPosition(hStdout,ConsoleScreenBufferInfo.dwCursorPosition);

file://已经完成的百分比

m=(ii+1)*100.00/iTotal;

file://显示进度

if(ii==iTotal)

{

    printf("******** 100%% Wait %d seconds to exit ********       n",iConnTimeout);

    break;

}

else

{

    k=j%4;

    printf("%-15s %s [%d/%d] %s %%%0.3g",CurrentTarget,turn[k],ii,iTotal,turn[k],m);

}

}//end of for

return 0;

}

///////////////////////////////////////////////////////////////////////////

//

file://扫描函数

//

DWORD WINAPI scan(LPVOID lp)

{

int i=(int)lp,iErr;

struct sockaddr_in server;

SOCKET s=INVALID_SOCKET;

char RecvBuff[1024]={0},*ptr;

int RecvBuffLen=sizeof(RecvBuff);

u_long ul=1;//初始化为为非0值

fd_set r,w;

file://create socket

s=socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

if(s==INVALID_SOCKET)

{

printf("nCreate socket failed:%d",GetLastError());

ExitProcess(1);

}

file://fill the addr struct

server.sin_family=AF_INET;

server.sin_port=htons(iPort);

server.sin_addr.S_un.S_addr=htonl(i);

__try

{

file://设置socket为非锁定模式,ul为0值的话,那么soocket将被设置为锁定模式

iErr=ioctlsocket(s,FIONBIO,(unsigned long*)&ul);

if(iErr==SOCKET_ERROR )

{

    ResetCursor();

    ShowError("ioctlsocket");

    ExitProcess(1);

}

file://printf("n%X ioctl ok.strat conn",i);

file://connect to target

connect(s,(struct sockaddr *)&server,sizeof(server));

file://printf("n%X conn return,start select w",i);

file://设置select参数

FD_ZERO(&w);

FD_SET(s, &w);

file://等待connect成功&socket可写

iErr=select(0, 0, &w, 0, &timeout);

file://printf("n%X select w return %d",i,iErr);

file://等待返回后,socket仍不可写则退出

if((iErr==SOCKET_ERROR) (iErr==0))

{

    file://printf("n%X select return w err,exit",i);

    __leave;

}

file://socket可写则继续

else

{

    file://send buff to target

    file://printf("n%X send",i);

    iErr=send(s,SendBuff,SendBuffLen,0);

    file://printf("n%X send return",i);

    if(iErr==SOCKET_ERROR)

__leave;

}

file://等待socket可读

FD_ZERO(&r);

FD_SET(s, &r);

file://printf("n%X start select r",i);

iErr=select(0, &r, 0, 0, &timeout);

file://printf("n%X select r return %d",i,iErr);

if((iErr==SOCKET_ERROR) (iErr==0))

{

    file://printf("n%X select r err,exit",i);

    __leave;

}

else

{

    file://recv buff from target

    file://printf("n%X start recv",i);

    iErr=recv(s,RecvBuff,RecvBuffLen,0);

    file://printf("n%X recv ret",i);

    if(iErr==SOCKET_ERROR)

__leave;

}

file://verify buff

ptr=strstr(RecvBuff,szSign);

if(ptr!=NULL)

{

file://线程输出前要先调用ResetCursor函数

    ResetCursor();

file://输出信息后务必加一个以上换行符号,输出前请别加换行符号,以免显示混乱

    printf("[%-15s] has .printer mapped.        n",inet_ntoa(server.sin_addr));

}

}

__finally

{

if(!ReleaseSemaphore(hSemaphore,1,NULL))

    ShowError("thread ReleaseSemaphore failed");

closesocket(s);

}

return 0;

}

///////////////////////////////////////////////////////////////////////////

void usage(char *proname)

{

printf("n%s v0.1 only can find IIS5 .Printer mapped"

"nPower by ey4s<ey4s@21cn.com> 2001.5.20"

"nhttp://www.patching.net"

"nnUsage:%s <StartIP> <EndIP> <ThreadNum> <CONNTIMEO>"

"nnNotice"

"n    StartIP StopIP ==>Don‘t forgot StopIP must large than StartIP "

"n    ThreadNum ==>Thread number,please input between 1-500"

"n    CONNTIMEO ==>TCP connect timeout,please input between 2-6"

"nnExample"

"n    %s 192.168.0.0 192.168.255.255 200 2",proname,proname,proname);

}

程序在VC++6.0上编译通过,在windows2000上运行良好:)

Read: 872

用VC++编写CGI程序

近年来,Internet与Intranet迅猛发展,企业纷纷建立自己的Intranet,以加强企业内部的联系,促进与外部世界的沟通,增强企业的竞 争能力。通过建立自己的Internet服务器,企业能在网络上发布广告,介绍本公司的情况与产品,收集顾客意见,以及建立虚拟电子商场等。大家都知道写 一个HTML超文本文件放在World Wide Web服务器上,即可通过各种浏览器访问你的主页,但是如何使你的静态主页能响应客户的动作,并与本地的数据库联系,将数据库的相应内容传送给远端的浏览 器呢?这就需要通过CGI接口进行联系。
CGI全名是Common Gateway Interface,它是WWW服务器调用外部程序的接口通过CGI,WW W服务器能完成一些本身所力不能及的工作。

在不同操作系统下CGI接口也不相同,可分为Standard CGI接口、DOS CGI接口与Win CG I接口等。Standard CGI接口用于Unix系统,它是通过环境变量来实现WWW Server与CGI应用程序之间的联系。DOS CGI接口与Standard CGI接口原理类同。Win CGI接口则与前两者大不相同。由于越来越多的WWW Server采用Windows系统,而Windows系统又不能有效地传递环境变量,由Bob Denny提出了一种Windows CGI标准,并得以不断推广。
Win CGI是通过.ini文件来进行联系的。在Windows环境下,许多应用程序都是利用.ini 文件,即profile文件来定义一些基本设定,而Win CGI程序与WWW Server之间也正是通过pro file文件来实现联系的。当WWW Client通过WWW Server触发CGI程序时,WWW Server先动态生成几个文件于临时目录下,分为.ini、.inp、.out等文件。在WWW Server生成CGI程序的进程时,通过命令行参数将.ini文件的绝对路径传给CGI进程。.ini文件中也包含了.inp、.ou t等文件路径信息。这样,CGI程序可读取.ini和.inp文件所提供的信息,了解所有来自WWW Server端和与WWW Client端的数据。然后,CGI程序可以调用外部程序,或由CGI直接执行外部程序的功能,并将要传给客户端的信息以HTML文本的形式写在.out 文件上。最后,WWW Server取.out文件的HTML文本,将信息传送给WWW Client端,并删除临时目录下的文件。那么,CGI的profile文件包含了什么数据呢?与Windows其它.ini文件一样,CGI profile 文件包含了许多session,每个session又包含一些key,以下是一个CGI profile的部份内容

[CGI]
Request Method=POST //传递方式,即HTML中的Form method有GET,POST,HEAD等。
Server Software=WebSite/1.1e
//Server的名称与版本。
Server Name=199.199.198.55
//Server的IP地址。
Server Port=80//Server的Socket Port。
Server Admin=tomy@vico.bme.zju.edu.cn//Server管理者的E-mail地址。
Referer=http://199.199.198.55/
//主页的URL路径。
Remote Address=199.199.198.89
//Client端的IP地址。
[System]
Output File=c:WebSitecgi-temp198ws.out//输出文件的路径名。
Content File=c:WebSitecgi-temp198ws.inp//输入文件的路径名。
[Form Literal]
type=B//接收到的Form data即client端动作的结果信息。

原则上可用来编写CGI程序的语言有:perl, AppleScript, Unix Shell, VB, C/C++等。但在Windows(NT、3x、95)环境下,还是必须借助成熟的Windows编程语言才能编写出精巧的Win CGI程序。C语言是一门深受广大程序员喜爱的语言,尤其是VCi++作为一门面向对象的语言,具备了Windows环境的许多基本功能,如OLE, ODBC等。通过ODBC这项功能,可以非常容易地连接FoxPro, Access, dBASE等多种数据库,而无需考虑各种数据库结构的差异。
以下是笔者用VC++4.0编写的一个用Win CGI连接数据库的范例。使用者可在屏幕上选择,以查询ISP服务商提供的internet服务价格表。系统环境采用Windows 95,数据库采用FoxP ro2.5, WWW Server采用Website。

1.建立数据库
本例的CGI程序是通过ODBC连接数据库,因此数据库建完后,应用32位ODBC驱动器加以登记。

2.编写HTML程序
使用者可在屏幕上选择一服务类型,再按"提交"键,即可查询这一服务类型的各价格栏目。其HTML文本描述如下:
<HTML>
<HEAD>{TITLE>维科公司internet服务收费标准</TITLE></HEAD>
<pre><H1>维科公司internet服务收费标准</H1>
</P>
<hr><form method="POST" action="cgi-win/wincgi.exe">
您要查询的类型是:
<UL><LI>A类用户<input name="usertype" type=radio value="A">
<LI>B类用户<input name="usertype" type=radio value="B">
<LI>C类用户<input name="usertype" type=radio value="C">
<LI>D类用户<input name="usertype" type=radio value="D">
</UL>
<center>
<input type=submit value="提交"> <input type=reset value="取消"></center>
</pre></form><hr>
</HTML>

3.编写VC++CGI程序

Website为C++程序员提供了一个CCGI类,可在website目录下找到。该类提供了一些方便的函数,无需程序员编写一些直接与CGI界面打 交道的底层工作,充分体现面向对象语言的优势。在VC++4.0中建立一个"Dialog based"工程框架。将CCGI类引入框架,即将cgi.h,cgi .cpp加入程序。然后用VC++的ClassWizard工具创建一个与数据库相对应的CUserSet类,该类继承MFC类库中的 CRecordSet类。
主要代码加在wincgi.cpp文件中。

//程序:wincgi.cpp
#include "stdafx.h"
#include "wincgi.h"
#include "cgi.h"
#include "userset.h"
……
void ProcessCGI(CCGI *cgi);
BOOL CBaseApp::InitInstance()
{
CCGI cgi(m-ipCmdLine); //实例化CCGI类,并用命令行参数初始化
if(cgi.CalledAsCGI())
{
try
{
ProcessCGI(&cgi);
//在此做所有真正的工作
}
catch(CException* e)
{
cgi.HandleException(e);
//异常处理
e->Delete();
}
}
return FALSE;
}
void ProcessCGI(CCGI *cgi)
{
int i;
CString strTemp, strWebmaster, strReferer;
strWebmaster=cgi->GetCGIVar("Server Admin");
//获得Server管理者的E-mail地址。
strReferer=cgi->GetCGIVar("Referer"); //获得主页的URL路径。
CStringArray csaFields;
cgi->EnumFormFieldNames(csaFields);
//取得所有变量名,放在数组中。
cgi->WriteHTMLHeader();
cgi->WriteHTMLBody("<HEAD><TITLE>VC++CGI test</TITLE></HEAD>1512");
cgi->WriteHTMLBody("<BODY>1512");
CString csaFieldVal;
csaFieldVal=cgi->GetFormField(csaFields[0]);
//得到第一个变量的值,即"usertype"变量的值。
cgi->WriteHTMLBody("<h1>internet服务收费标准查询结果</h1><hr>");
CUserSet m-user;
//实例化CUserSet类
char sfilter[80];
wsprintf(sfilter,"USER-TYPE=´%s´´´,csaFieldVal);
m-user.m-strFilter=sfilter;
//设置筛选器。
m-user.Open();
strTemp="<h2>"+m-user.m-USER-TYPE+"类用户"+"</h2>1512";
cgi->WriteHTMLBody(strTemp);
strTemp="<P>入网费:"+m-user-FARE-INI+".</P>1512";
cgi->WriteHTMLBody(strTemp);
……
strTemp="<P>通信费:"+m-user.m-FARE-COM+".</P>1512"
;
cgi->WriteHTMLBody(strTemp);
m-user.Close();
if(!strReferer.IsEmpty())
//设置回到主页的锚点。
{
cgi->WriteHTMLBody("<P>Go<A HREF="");
cgi->WriteHTMLBody(strReferer);
cgi->WriteHTMLBody("">Back</A>where you came from.</P>1512");
}
if(!strWebmaster.IsEmpty())
//设置Server管理者的E-mail地址。
{
cgi->WriteHTMLBody("<P>Please email comments to");
cgi->WriteHTMLBody("<A HREF="mailto:");
cgi->WriteHTMLBody(strWebmaster);
cgi->WriteHTMLBody("">");
cgi->WriteHTMLBody(strWebmaster);
cgi->WriteHTMLBody("</A>.</P>1512");
}
cgi->WriteHTMLBody("</BODY>1512");
cgi->WriteHTMLFooter();
}

4.结论
将程序编译后,把wincgi.exe拷入website/cgi-win/目录下,用浏览器查询结果。至此简单地阐述了用VC++编写Win CGI程序的整个过程,C程序员也可以用自己熟悉的语言编写CGI程序了。利用VC++的强大功能,你的WWW Server将无所不能。

Read: 899

Windows操作平台下CGI的实现

华中理工大学图像识别与人工智能研究所     龚建勇

实现WWW服务器与客户的交互可以有多种途径,如CGI、ISAPI、JAVA、ASP等,CGI是一种大家都比较熟悉的一种实现上述功能的有效途径,本文将就如何在Windows平台下实现CGI功能作一介绍。  

一、CGI概述
1.CGI概念
CGI即通用网关接口(Common Gateway Interface),它是一个WWW服务器主机对外服务的标准接口。一般来说,一个CGI接口的功能就是在超文本文件和服务器主机应用程序间传递信息。

2.CGI程序语言
事实上,任何一种程序语言,只要能在服务器主机上利用CGI接口来编写应用程序,都可以叫作CGI程序语言。目前最为流行的CGI程序语言有四种:C、Shell、Perl和Visual Basic,其它一些语言也有许多人在用,如TCL、Frotran及AppleScript等。下文我们将介绍用C语言编写CGI的方法。

3.CGI程序的执行
CGI程序一般是个可执行程序。编译好的CGI程序一般要集中放在一个目录下。具体存放的位置随操作系统的不同而不同,例如UNIX系统下是放在cgi-bin子目录下,而在Windows操作下(注意,这里不包括NT4.0)以Webstar或Website作WWW服务器,CGI程序都放在cgi-win下。C GI程序的执行一般有两种调用方式:一种是通过URL直接调用,如:"http://gjy.sic.o.ml.or g/cgi-win/cgi.exe",在Netscape的URL栏里直接写入上述描述就可以调用该程序:另一种方式,也是主要的方式是通过交互式主页里的FORM栏调用,通常都是用户在填完一张表后按确认按钮启动CGI程序。

4.CGI工作的主要流程
CGI工作的主要流程是:1.通过HTML获取用户输入的信息;2.将用户输入的信息传给服务器主机应用程序(如数据库查询);3.将服务器处理结果通过HTML文件返回给用户。
二、Windows平台下CGI的工作原理
CGI主要的功能不外乎在服务器应用程序和客户之间传递信息。我们知道,通常CGI应用在服务器和客户端传递信息是通过输入输出流来实现的,如UNIX平台下的CGI,通过读取输入流Scanf来获取用户输入,读取输出流printf向用户输出结果。而Windows系统下CGI实现这一功能是通过文件系统来实现,即通过一个特定的输入文件来获取用户输入的信息,通过一个输出文件向用户传递结果。
这里这个输入文件是由WWW服务器生成的一个临时文件,里面包含了各种环境信息及用户输入信息,以key=value的形式给出,类似于Windows里的INI文件。下面以一个典型的该文件为例说明主要层的含义。
[CGI]
Request Protocol=HTTP/1.0
Repuest Method=POST
Executable Path=/cgi-win/cgitest.exe
Logical Path=/CGI
Physical Path=c:httpdhtdocsCGI
Server Software=Website /1.1(Shareware non-commercial License))
Server Name=gjy.sic.o.ml.org
Server Port=80
Server Admin=gjy@gjy.sic.o.ml.org
CGI Version=CGI/1.1 WIN
Remote Adress=2.02.114.2.182
[system〗
Debug Mode=No
Output File=D:hs06f12d.out
*/*=Yes
image/gif=Yes
image/x-xbitmap=Yes
image/jpeg=Yes
[Extra Headers〗
User-Agent=Mozilla/3.0 (Windows;I;32bit)
[Fotra rm Literal〗
textl=hello world
radio1=2
[Form External〗
field300chars=D:hs06f12d.000 300
fieldwith l inebreaks=D:hs06f12d.001 43
[Frm Huge〗
fiel d230
K=d:s06f12d.002 276920

下面将主要各栏的意义解释如下:
1.CGI
这一栏包含大部分的CGI信息,如要求的协议Request Protocol,提取用户信息的方式Me thod,执行程序的路径Exectable,服务器软件(Server Software),服务器名(Server Name), 端口号(server Port)等。
2.Accept栏
这一栏包含客户端可以接受的文件类型。
3.System栏
这一栏包含一些Widnows CGI使用的特别项目如输出(Output File),Content File
4.Form Literal资料栏
如果用户端的要求是使用POST方式,用户输入的资料会被解码后以key=value的方式放在这一栏里,这里的key即输入栏的变量名,value是用户输入的信息。
5.Form External栏
如果解码后的字串长度超过254byte,或字串中含有控制字元,系统会把这些信息存在该栏指定的临时文件里并指出长度。
三、CGI应用的实现
1、开发平台
首先操作系统应为Windows操作系统。我们用的是Windows 95。其次需要建立WWW服务器 ,我们用的是Website1.1。安装website并设置各相应目录后,将主页放入主目录中。
2、交互主页的制作
交互主页就是用户可以在主页上输入信息,而不是单纯仅供浏览的主页。一般来说,用户输入信息的形式有填写编辑框,点选无线按钮等选择框及选择下拉菜单等。下面是一个简单的例子。
<HTML>
<HEAD>
<TITLT>Form Sample</TITLE>
<HEAD>
<BODY>
<FORM ACTION="http://gjy.sic.o.ml.org/cgi/win/sample.exe"METHOD=POST>
A Text Input:<INPUT TYPE="text"><BR>
A Radio Input:
1<INPUT TYPE="radio"NAME="radiol"VALUE=1>
2<input type="radio" NAME="radiol"VALUE=2>
3<INPUT TYPE="radio"NAME="radiol"VALUE=3><BR>
OK Input:<INPUT TYPE="submit"VALUE="OK">
<INPUT TYPE="reset" VALUE="reset">
</FORM>
</BODY>
</HTML>
将上述标记存成HTML文件,该主页作好后,将其放在website设定的主目录中。然后用浏览器观看,将会看到结果。
其中FORM:说明一个交互式输入组合框。
ACTION:指定该FORM对应的CGI程序的URL。
INPUT:输入的形式,text为编辑框,radio为无线按钮等。
INPUT里的第一个参数TYPE表明输入的类型,第二个参数NAME表示该输入框代表的变量名。用户在此输入的信息将存在这个变量里。CGI程序也是通过这些变量提取用户输入信息。
3.CGI程序编写
前面我们说过,原则上CGI程序可以用任何语言编写,但在windows平台下,一般只有 C和VB是适合的。因为CGI程序是后台执行的back-end程序,C和VB都可以方便地生成无界面的 back-end程序。考虑到效率,我们用C语言来作CGI程序的编写。
Web server会通过Winexec来执行back-end,命令格式如下:
back-end-exe cgi-data-file content-file outputfile url-args
back-end-exe是CGI程序的完整路径,cgi-data-file是服务器产生的CGI资料文件,值为完整的路径:content-file是客户端发出的请求,其信息放在临时目录下的文件里,值为完整的路径;output-file是程序处理的结果,值为完整路径。url-args为URL中?后的参数。
服务器生成的临时输入文件在客户请求结束后即自动消失,所以CGI程序的第一步就是截取该文件,然后将所需的信息提取出来并过滤,最后得到的就是用户输入,然后对其作相应处理,如写入留言板或转交给数据库应用程序作查询处理。
值得一提的是VC和VB都提供了专用的从INI文件中提取信息的函数,即GetPrivateprofi leString()。
4、服务器应用程序
这一部分程序的功能就是对CGI转来的用户信息进行处理,处理的结果交给CGI程序的ou tput-file,通过CGI将其转交给用户。典型的服务器应用程序以数据库查询为代表。   

Read: 835

病毒的本质

by Koms Bomb/CVC.GA

本文的目的,就是想说明写病毒其实是多么的容易。
一,病毒神话
我们先看看让大家“兴奋”、“激动”的病毒技术。
1,CIH,简直成了神话,CIH的作者也成为无数人的偶像。这神话,是AVer吹出来的,这偶像,只是中国人盲目崇拜的延续,外国人没这臭毛病。看看这个病毒里究竟有什么?1K多点的代码,再厉害能有什么?我觉得这个病毒唯一比较好的地方就是它的非常优化。这种优化,其实说白了也不难,只要稍微有些汇编编程经验,再加上一部指令手册(至少要有指令长度),再有足够的耐心,都可以做到。其它还有什么?进入Ring 0?早已有之,再说稍微懂些保护模式的人都知道利用GDT/IDT/LDT是可以进行特权转换的,常识问题。感染后文件大小不变?稍微了解PE格式的人都知道PE里有多少空地。使用VxD技术?Flash BIOS?只要能找到这些资料,有什么难的?VxD无非就是玩一些int 20h,Flash BIOS无非是写一些端口。调用中断,写端口,谁不会?但怎么调中断,写哪些端口,则需要知道相关的资料。我就写不出VxD为基础的病毒,我也不会Flash BIOS,因为我没有这方面的资料。
再看看时下“热门”的病毒技术。
2,跨平台病毒。跨Windows和Linux,同样是X86指令,有什么特殊的吗?基本上这种跨平台病毒,公用一部分变形之类的和OS无关的代码,而和OS有关的则是分开的。比如在Windows上就调用API,而在Linux上则用int 80h(没搞错中断号吧,我对Linux没什么研究)。其实就相当于两个病毒的合并。只要有了相关系统调用的资料,这种病毒显然很容易。其实写出针对不同CPU的病毒也不难,只要针对不同的文件感染不同的代码,然后把另外OS或CPU的代码做为数据不执行就行。顺便说一句,跨平台有什么用?有几个Linux机器上有PE文件的?又有几个Windows上有ELF文件的?
3,.net病毒。有什么新鲜吗?Java不是也有病毒吗?只要掌握MSIL汇编,写一个真正的.net病毒并不难。值得一提的是前一阵炒得火热的两个.net病毒其实根本难登大雅之堂。29A的那个是用X86汇编写的,稍微有点头脑的都知道,.net是架构在MSIL上的,是一种中间语言,X86汇编根本就和.net无关。我没仔细看这个病毒的代码,但我估计无非是对EPE格式有一些不同于PE的操作,总体来说,还是PE病毒。那个“17岁天才少女”的C#病毒,算是.net病毒吧,但值得一吹吗?你,说的就是你,去看看C#语法,再略微看一些.net framework,相信你也可以用C#写出一个可以把自己通过邮件发出去的程序,这就是时髦的.net病毒。
4,metamorphism。简直被AVer吹上天了。这东西说白了其实就是对自身代码进行重新编码。过程是,反汇编自己的代码,重新改写(比如mov eax,esi可以写成push esi;pop eax等等),插入垃圾代码。当然最好还要在下一代中删除垃圾代码。怎样判断垃圾代码?显然看到一条指令的目的操作数在以后没有被当作源操作数,就可以认为是垃圾。这东西玩弄的是机器代码技巧,只要在手边摆一部Intel的指令手册,再加上足够的耐心,想对代码重新编码,不难。但写这东西确实很繁琐,可以说是非常繁琐,写出的代码也很大,没有实用价值。以后有空我可能会重新考虑一个semi-metamorphism,也不难,但力求小,如果engine超过8K,我将放弃。

二,病毒的本质
大家之所以觉得病毒神秘,其实主要是因为大家没有对病毒没有了解,AVer又在大肆鼓吹(这种现象在中国尤为严重)。病毒到底是什么?
病毒是一段程序,它与普通程序的不同是它会复制自己,这是最主要的不同。复制有很多方法,直接copy,通过网络,插入别的可执行文件内部,等等,但这些方法的实现也是一段程序,并无特殊之处。
其实写病毒和写普通程序一样,不需要太高的智商。我为什么这么说?编程只是技术,这种技术所需要的基础知识是很浅的,可以说是非常浅,而且没有阶梯性。如果你要做物理学家,则一定要先学会中学物理,否则你就学不会大学物理。但如果你要做汇编程序员(听起来很高深的样子),你不必先学Basic,再学Pascal和C,最后学汇编,你完全可以从一开始就学汇编,完全没有问题。很多人以自己了解系统核心而自称高手,但这哪里高了?可能他花了三个月发现的一个系统核心的秘密,只是M$的一个程序员用一个小时写出来的。这一点也造就了很多“天才少年/少女”的神话,十几岁的小孩可以写出很好的程序或者病毒,让大家觉得真是天才。其实我敢肯定一个10岁的小孩对编程知识的理解能力不会比我差,甚至可能因为年龄小记忆力好而比我强,大家认为我智商有问题?也许吧,但小孩一样比你强。中国人对电脑界的“天才少年”总是津津乐道,其实只是愚蠢无知的表现。编程不需要智商,但需要资料,如果你不掌握一定数量的Win32 API,你很难写出像样的Win32程序。其实程序这东西还和物质力量有关,为什么外国有很多编程方面的“天才少年”?因为他们智商高?显然不是,中国人的智商至少不输于西洋人。主要是因为他们比较富裕,可以很早就接触电脑,并且有钱上网,上网当然可以获得许多有用的资料。靠,说到这里我不得不发牢骚,我正式开始能够经常接触电脑还是在2000年大学毕业以后。我在十几岁的时候,买本书都没钱,更别提电脑了,现在平时在家还是拿猫上网。
我说了这么多,其实一个结论就是,写病毒和写普通程序一样,不需要智商(当然智商也不能太低,如果智商低于80,除非真的是另类天才,否则还是别玩程序的好),但需要资料,这间接的需要物质力量。
所以说,病毒并不神秘,一点都不神秘,而且不难,可以说是相当简单,因为他们通常很小,最大也就几十K。我现在逐渐明白,哪怕是用Delphi这样易用的工具,开发大型程序,也是比较复杂比较困难的事,要有完善的设想,科学的模块划分,等等。
病毒虽然简单,但也有很好玩的地方,因为它的本质就是复制隐藏自己。如何更快的复制传播自己,如何隐蔽自己使得用户不容易发现病毒的踪影,是我的主要研究目标。这种目标显然违背了某些卫道士的“XX精神”(他们鼓吹的是黑客精神,但我不会黑,只会毒,是毒客而不是黑客),但这正是病毒的本质,如果脱离这些本质,那病毒也就不称之为病毒,而且也没必要研究病毒,有那精力不如做点应用软件。这种卫道士,鼓吹的是那种研究“新”、“高深”技术来写病毒的人,这样的病毒也就是AVer说的“学术病毒”、“概念病毒”。但这种病毒有什么用?除了给AVer吹牛的机会以外,没什么用。除了CIH以外,我上面说的基本都是这种病毒。大家可以看看,哪里有新技术?哪里有高深技术?
值得注意的是网上颇为流行江海客的那篇老文章“后英雄时代的AVER与VXER”,而且许多人都认为“受益菲浅”,为了不误导大家,我还是说两句。这篇文章总体来说就是在胡说八道。“AV企业的一般工程师,玩起softice的手法,远没有cracker熟练”?我确实不知道AVer是不是熟悉SI,但这句话有两个大问题,1),玩SI熟练就能代表水平?那好,我从现在开始天天玩SI,一个月后精通SI的各个功能,那我是不是成大师了?这简直是不合人类逻辑。 2),研究病毒一定要用SI?我调试病毒主要用VC,SI只是辅助,非常方便。“想象反病毒公司那些呆头呆脑的样本分析工程师,“**,原来病毒也可以这样编” 的惊呼,然后心情紧张的把样本交给公司中的前辈高人寻求指点他们已经心满意足。”更是胡说八道,有谁相信Symantec的在Nimda,Klez,BugBear爆发几个小时内就拿出解决方案的工程师是呆头呆脑的?”而是用了类似社交工程或者心理学一类的方法“,”当然,那些资深的VXER对此是不耻的,他们从不以自己的病毒传播如何广泛为荣誉,这些人的品性有些类似老牌黑客的绅士风范,这些人中确实也有我很欣赏的“,不是每个人都能做出成功的social engine的,好的S/E会得到全世界人的赞叹(套用卫道士的话,全世界人惊呼,”**,这他都能想得到“),一个人只要有耐心,可以对系统核心钻研很深,这没什么神秘,但好的S/E却需要灵光闪现。不能广泛传播的病毒显然不是好的病毒,无论它的技术如何高超(而且基本如我所说,并没有什么高超的技术),而且病毒和黑客不一样,黑客黑网站,真正有道德的不应该搞破坏,但他有能力可以破上10000个网站。有道德的病毒也不应该搞破坏,但有能力的病毒可以感染1000万台机器,这没什么不对,这是病毒应该做的,当然有能力感染不一定真的感染,不一定真的释放出去,但验证感染能力通常需要实战。还有就是他也是我说的那种卫道士,鼓吹那种黑客精神,其实如果大家都遵循他那种精神,那么结局就是大家整天都在研究不切实际的技术,根本就没有实用品出来。总体说,一个不懂得病毒本质,对病毒没有很深了解,没有写过病毒的人,不配写这种文章。
我一直对知道现在还在奉CIH和其它病毒为神明的无知之人感到气愤,在中国这种人太多了,所以就写了这篇文章,因为气愤,所以言语有不当之处,还望大家见谅,如果你对我感到气愤,可以理论,但不要骂人。

欢迎转载,请注明 by Koms Bomb/CVC.GA。
又:我发现深圳之窗网站曾从CSDN转了我的一篇文章,但去掉了所有作者信息,我给他们发信叫他们改正,也没有回音。这种行为是极度卑鄙无耻的,如果引起众怒将会玩火自焚!

Read: 624