端口转发与内网穿透

好久没来这跟新了,打算从今年开始在这里除除草,我兄弟的公众号也开通了给大家推荐下

小菜鸡的技术之路
在这里插入图片描述

关注公众号推荐给大家,网盘免费的Linux学习课程

在内网渗透过程中有时需要将内网中的应用服务进行端口转发,可以使用很多工具进行实现,主要介绍ssh以及lcx(ew基于lcx实现),并结合源码分析他们的实现原理。

0x01 端口转发

0x1 概念

端口转发(Port forwarding),有时被叫做隧道,是安全壳(SSH) 为网络安全通信使用的一种方法。端口转发是转发一个网络端口从一个网络节点到另一个网络节点的行为。简单来讲就是访问一个转发端口可以将数据转发至该对应的服务端口。

0x2 图解

在这里插入图片描述

如上图所示将PC2 8888端口的数据转发至PC1的80端口,实现在PC2上访问PC1的web服务

从图下面的socket通道来看,端口转发的实现依靠的是两个socket通信的数据转发来实现的。socket1 和socket2 在PC2 上的两个端口1234和2345 通过共享BUF数据缓冲区实现端口转发。具体实现参考第四节(lcx源码分析)

0x02 工具使用分析

端口转发的工具也多种多样,其中最方便的是使用xshell的ssh端口转发功能,最灵活的是lcx的端口转发,earthworm是最全面的端口转发工具。依次介绍其使用方法并在第三节中介绍使用场景。

0x1 lcx

使用方法

Usage:./lcx -m method [-h1 host1] -p1 port1 [-h2 host2] -p2 port2 [-v] [-log filename]
 -v: version
 -h1: host1
 -h2: host2
 -p1: port1
 -p2: port2
 -log: log the data
 -m: the action method for this tool
 1: listen on PORT1 and connect to HOST2:PORT2
 2: listen on PORT1 and PORT2
 3: connect to HOST1:PORT1 and HOST2:PORT2
  • 方法1
    开启监听端口,同时连接PORT2
  • 方法2
    开启监听端口PORT1和PORT2
  • 方法3
    连接两个主机的端口,主机地址可以是本地

0x2 ssh(xshell)

使用方法

ssh -L 8888:45.78.29.252:80 root@server
ssh -R 8888:127.0.0.1:80 root@server
ssh -D 7777
  • 方法 -L
    开启本地转发,转发至远程端口
  • 方法 -R
    开启远程转发,转发至本地端口
  • 方法 -D
    开启动态转发,在远程开启socks5 动态转发来自本地的请求

0x3 ew

使用方法

./xxx ([-options] [values])*
 options :
 Eg: ./xxx -s ssocksd -h 
 -s state setup the function.You can pick one from the 
 following options:
 ssocksd , rcsocks , rssocks , 
 lcx_listen , lcx_tran , lcx_slave
 -l listenport open a port for the service startup.
 -d refhost set the reflection host address.
 -e refport set the reflection port.
 -f connhost set the connect host address .
 -g connport set the connect port.
 -h help show the help text, By adding the -s parameter,
 you can also see the more detailed help.
 -a about show the about pages
 -v version show the version. 
 -t usectime set the milliseconds for timeout. The default 
 value is 1000 
 ......

  • 正向SOCKS V5
    ./ew -s ssocksd -l 1080

  • 反弹 SOCKS v5 服务器
    先在一台具有公网 ip 的主机A上运行以下命令:
    ./ew -s rcsocks -l 1080 -e 8888
    在目标主机B上启动 SOCKS v5 服务 并反弹到公网主机的 8888端口
    ./ew -s rssocks -d 1.1.1.1 -e 8888

  • 多级级联
    ./ew -s lcx_listen -l 1080 -e 8888
    ./ew -s lcx_tran -l 1080 -f 2.2.2.3 -g 9999
    ./ew -s lcx_slave -d 1.1.1.1 -e 8888 -f 2.2.2.3 -g 9999

0x03 使用场景

通过lcx以及ew工具可以实现多层内网的穿透,使用简单,并且灵活性较高。通过写shell得ssh隧道可以方便的进入深层网络使得操作步骤简单,提高使用效率。

0x1 内网穿透

利用ew工具穿透内网,将服务转发到公网ip

实验环境有以下几个

1 本机docker ssh服务转发到公网

在公网服务器执行
./ew_for_Linux32 -s lcx_listen -l 1112 -e 8980
在本机执行
./ew_for_linux64 -s lcx_slave -d serverip -e 8980 -f 172.17.0.13 -g 22
在这里插入图片描述

2 在本机开启Socks 服务 转发到公网

两种实现途径
方法1
在本机执行
./ew_for_linux64 -s ssocksd -l 9999
./ew_for_linux64 -s lcx_slave -d serverip -e 8981 -f 127.0.0.1 -g 9999
在公网服务器执行
./ew_for_Linux32 -s lcx_listen -l 1113 -e 8981

方法2
在本机执行
./ew_for_linux64 -s rssocks -d serverip -e 8981
在公网服务器执行
./ew_for_Linux32 -s lcx_listen -l 1113 -e 8981

0x2 多级代理

实验场景有下几个

1 利用xshell代理登录主机

在这里插入图片描述
通过192.168.43.165 主机登录在另一台主机里docker环境里的172.17.0.16主机,需要做两个跳板
首先登录192.168.43.140开启本地动态转发,通过一层代理登录172.17.0.13 ,开启本地动态转发,设置代理后登录172.17.0.16

利用xshell 配置三台主机的登录信息

192.168.43.140
在这里插入图片描述

在这里插入图片描述

172.17.0.13

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

登录 172.17.0.16
在这里插入图片描述
在这里插入图片描述

2 利用ew代理socks5

场景还是上图拓扑,要求是192.168.43.165的主机能够连接172.17.0.16的socks5服务

具体做法如下

  1. 在172.17.0.13执行./ew_for_linux64 -s rcsocks -l 1111 -e 2222
  2. 在172.17.0.16执行./ew_for_linux64 -s rssocks -d 172.17.0.13 -e 2222
  3. 在192.168.43.140执行./ew_for_linux64 -s lcx_tran -l 3333 -f 172.17.0.13 -g 1111
  4. 在192.168.43.165执行E:\Web\ew\ew_for_Win.exe -s lcx_tran -l 1121 -f 192.168.43.140 -g 3333

设置socks5端口 为127.0.0.1 端口为1121即可

其隧道示意图如下

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hKClVhtS-1587177804270)(./images/决策流程图(29)].png "隧道示意图")

将172.17.0.16的socks5服务转发到本机1121端口

0x04 lcx实现原理及源码分析

源码链接

0x1 函数列表

void usage(char *s);
void transdata(int fd1,int fd2);//进行数据转发 ,是代码实现的核心函数
void closeallfd();
void makelog(char *buffer,int length);
int testifisvalue(char *str);
int bind2conn(int port1,char *host,int port2);//侦听&连接
int bind2bind(int port1,int port2);//侦听&侦听
int conn2conn(char *host1,int port1,char *host2,int port2);//连接&连接
int create_socket();
int create_serv(int sockfd,int port);
int client_connect(int sockfd,char* server,int port);

端口转发的核心函数transdata,bind2conn 、bind2bind、conn2conn都是基于该函数实现的

0x2 transdata 函数

利用select函数实现io复用,同时侦听两个套接字可读可写操作,代码实现如下:

SOCKET fd1, fd2;//用于数据交换的套接字
fd_set readfd,writefd;//设置套接字集合

FD_ZERO(&readfd);//清空集合
FD_ZERO(&writefd); //清空集合

FD_SET((UINT)fd1, &readfd);
FD_SET((UINT)fd1, &writefd);
FD_SET((UINT)fd2, &writefd);
FD_SET((UINT)fd2, &readfd);
//将fd1和fd2分别放在检测可读可写的集合里

result=select(maxfd,&readfd,&writefd,NULL,&timeset);
//如果检测到可读套接字则继续执行

if(FD_ISSET(fd1, &readfd))
        {

                read1=recv(fd1, read_in1, MAXSIZE-totalread1, 0); 
                memcpy(send_out1+totalread1,read_in1,read1);
       
                totalread1+=read1;//totalread1记录收到的数据长度
                memset(read_in1,0,MAXSIZE);//置空读取缓冲区
            }
          
        }

        if(FD_ISSET(fd2, &writefd))
        {
            int err=0;
            sendcount1=0;
            while(totalread1>0)
            {
                send1=send(fd2, send_out1+sendcount1, totalread1, 0);

                sendcount1+=send1;//sedcount1记录发送的总数据长度
                totalread1-=send1; //剩余数据的长度
            }

            if((totalread1>0) && (sendcount1>0))//处理异常情况下buf中的数据
            {
                /* move not sended data to start addr */
                memcpy(send_out1,send_out1+sendcount1,totalread1);
                memset(send_out1+totalread1,0,MAXSIZE-totalread1);
            }
            else
                memset(send_out1,0,MAXSIZE);
        } 

0x3 bind2bind 函数

关键代码如下


void bind2bind(int port1, int port2)
{
    SOCKET fd1,fd2, sockfd1, sockfd2;
    struct sockaddr_in client1,client2;
    int size1,size2;

    HANDLE hThread=NULL;
    transocket sock;
    DWORD dwThreadID;

    if((fd1=create_socket())==0) return;
    if((fd2=create_socket())==0) return;


    if(create_server(fd1, port1)==0)
    {
        closesocket(fd1);
        return;
    }
    if(create_server(fd2, port2)==0)
    {
        closesocket(fd2);
        return;
    }
//创建两个监听套接字

    printf("[+] Listen OK!\r\n");
    size1=size2=sizeof(struct sockaddr);
    while(1)
    {
        
        if((sockfd1 = accept(fd1,(struct sockaddr *)&client1,&size1))<0)
        {
            printf("[-] Accept1 error.\r\n");
            continue;
        }

        if((sockfd2 = accept(fd2, (struct sockaddr *)&client2, &size2))<0)
        {
            printf("[-] Accept2 error.\r\n");
            closesocket(sockfd1);
            continue;
        }
//开启监听

        sock.fd1 = sockfd1;
        sock.fd2 = sockfd2;

//等待端口建立 通信后 执行转发函数

        hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)transmitdata, (LPVOID)&sock, 0, &dwThreadID); 
        if(hThread == NULL) 
        {
            TerminateThread(hThread, 0);
            return;
        }

        Sleep(1000);
    }
}

上述代码可以用下图表示

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xPoajcuq-1587177804271)(./images/决策流程图(30)].png "bind2bind")

在1111端口收到的数据立刻转发到2222端口,同时在1111端口收到的数据立刻转发到2222端口,从而完成端口转发操作

在这里插入图片描述
lcx的指令为./lcx -m 2 -p1 1111 -p2 2222

0x3 bind2conn 函数

关键代码如下


void bind2conn(int port1, char *host, int port2)
{
 
    if((sockfd=create_socket()) == INVALID_SOCKET) return;

    if(create_server(sockfd, port1) == 0) 
    {
        closesocket(sockfd);
        return;
    }//创建监听套接字

    size=sizeof(struct sockaddr);
    while(1)
    {
 
        if((sockfd1=accept(sockfd,(struct sockaddr *)&remote,&size))<0)
        {
            printf("[-] Accept error.\r\n");
            continue;
        }
//开启监听,等待连接

            inet_ntoa(remote.sin_addr), ntohs(remote.sin_port));
        if((sockfd2=create_socket())==0) 
        {
            closesocket(sockfd1);
            continue;      
        }


        if(client_connect(sockfd2,host,port2)==0)//连接另一个监听端口
        {
       
        }

        sock.fd1 = sockfd1;
        sock.fd2 = sockfd2;

//创建新线程,开启数据转发
        hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)transmitdata, (LPVOID)&sock, 0, &dwThreadID); 
        if(hThread == NULL) 
        {
            TerminateThread(hThread, 0);
            return;
        }

        Sleep(1000);
     
    }
}

在这里插入图片描述
lcx的指令为./lcx -m 1 -p1 4444 -h2 127.0.0.1 -p2 1111

0x4 conn2conn 函数

int conn2conn(char *host1,int port1,char *host2,int port2)

{

    int sockfd1,sockfd2;
    int pid;

    while(1)
    {
        if((sockfd1=create_socket())==0) exit(0);
        if((sockfd2=create_socket())==0) exit(0);
        printf("make a connection to %s:%d....",host1,port1);
        fflush(stdout);
        if(client_connect(sockfd1,host1,port1)==0) //连接一个端口
        {
            close(sockfd1);
            close(sockfd2);
            break;
        }
        printf("ok\r\n");
        printf("make a connection to %s:%d....",host2,port2);
        fflush(stdout);
        if(client_connect(sockfd2,host2,port2)==0) //连接另一个端口
        {
            close(sockfd1);
            close(sockfd2);
            break;
        }
        printf("ok\r\n");
       pid=fork();
        if(pid==0) transdata(sockfd1,sockfd2);//端口数据转发
        //sleep(2);
        close(sockfd1);
        close(sockfd2);
    }
}


在这里插入图片描述

lcx的指令为./lcx -m 1 -h1 127.0.0.1-p1 4444 -h2 127.0.0.1 -p2 1111

发布了99 篇原创文章 · 获赞 51 · 访问量 71万+

猜你喜欢

转载自blog.csdn.net/qq_31481187/article/details/105594922