需求

在跨网络的操作中,我们想要连接一些内网服务,例如:对 机房内的安卓设备 进行 adb 连接。

一般的做法呢,通常不想自己开发功能,可以有以下两种做法:

  • 可以采用 ssh 隧道的方式直接转发 tcp 端口
  • 可以采用 frp 搭建隧道转发 tcp 端口

但是这两种方式比较固定,没办法自定义一些自己需要的业务,例如:用户鉴权等功能。

那么这种情况就需要自己动手来开发了。

实验拓扑

+----+                +----+                      +----+
|adb |----connect-----| S1 |----proxy connect-----| Tv |
|    |                |    |                      |    |
+----+                +----+                      +----+
clientA               TCP ServerB                 Android device

实现的最终目标是 外部的客户端 能够使用 adb 通过一台 TCP 的服务,连接上机房被的 安卓设备。

初始adb连接环境

首先准备好一个本地可以连接的设备,操作如下:

C:\Users\lijw>adb devices
List of devices attached
emulator-5554   device


C:\Users\lijw>adb connect 127.0.0.1:5555
connected to 127.0.0.1:5555

C:\Users\lijw>adb devices
List of devices attached
127.0.0.1:5555  device
emulator-5554   device


C:\Users\lijw>adb -s 127.0.0.1:5555 shell
tcl_m7642:/ $ ls
acct         etc                    mnt                     sbin        ueventd.rc
art          factory                odm                     sdcard      userdata
bin          impdata                oem                     sepolicy    var
bugreports   init                   persist                 storage     vendor
cache        init.environ.rc        plat_file_contexts      sys         vendor_file_contexts
charger      init.rc                plat_hwservice_contexts system      vendor_hwservice_contexts
config       init.recovery.m7642.rc plat_property_contexts  tclconfig   vendor_property_contexts
d            init.usb.configfs.rc   plat_seapp_contexts     tcluserdata vendor_seapp_contexts
data         init.usb.rc            plat_service_contexts   tmp         vendor_service_contexts
default.prop init.zygote32.rc       proc                    tvconfig    vndservice_contexts
dev          lost+found             product                 tvos
tcl_m7642:/ $ exit

C:\Users\lijw>

现在我们可以知道本地的 tcp 5555 端口号其实就是设备的 adb 连接端口服务。

那么下一步我们只要开发一个 TCP 服务开启 8008 端口服务,提供 adb 连接,然后 TCP服务会将连接转发至 tcp 5555 端口。

前置知识:TCP反向代理转发

在完成上述功能之前,我们首先要理解 socket 反向代理 TCP 端口的知识。

那么下面我们先来编写一个客户端、服务端,然后用一个工具作为服务端B,来演示一下TCP转发消息的流程,示意图如下:

python3 基于 socket 反向代理  adb 设备_socketimage-20201204224800081

TCP Client 的代码

client.py

from socket import *

def client():
    # 创建socket
    tcp_client_socket = socket(AF_INET, SOCK_STREAM)

    # 服务器的地址
    # '192.168.43.1'表示目的ip地址
    # 8080表示目的端口
    dest_addr = ('127.0.0.1', 7788)  # 注意 是元组,ip是字符串,端口是数字

    # 链接服务器,进行tcp三次握手
    tcp_client_socket.connect(dest_addr)

    while True:
        # 从键盘获取数据
        send_data = input("请输入要发送的数据:")

        # 判断输入stop,则退出客户端
        if send_data == "stop":
            break

        # 发送数据到指定的服务端
        tcp_client_socket.send(send_data.encode("utf-8"))

        # 接收对方发送过来的数据,最大接收1024个字节
        recvData = tcp_client_socket.recv(1024)
        print('接收到的数据为:', recvData.decode('utf-8'))

    # 关闭套接字
    tcp_client_socket.close()

if __name__ == '__main__':
    client()

TCP ServerA 的代码

server.py

from socket import *

def server():
    # 创建套接字
    tcp_server_socket = socket(AF_INET, SOCK_STREAM)

    # 绑定服务端提供服务的端口号
    local_addr = ('', 7788)  # ip地址和端口号,ip一般不用写,表示本机的任何一个ip

    # 绑定
    tcp_server_socket.bind(local_addr)

    # 使用socket创建的套接字默认的属性是主动的,使用listen将其改为被动,用来监听连接
    tcp_server_socket.listen(128)

    # 如果有新的客户端来链接服务端,那么就产生一个新的套接字专门为这个客户端服务
    # client_socket用来为这个客户端服务
    # tcp_server_socket就可以省下来专门等待其他新的客户端连接while True:
    client_socket, clientAddr = tcp_server_socket.accept()

    while True:

        # 接收对方发送的数据
        recv_data = client_socket.recv(1024)  # 1024表示本次接收的最大字节数
        print('接收到客户端的数据为:', recv_data.decode('utf-8'))

        # 将客户端的数据,转发至另一个服务器
        # 创建新的tcp连接,连接上另一个服务器
        proxy_tcp_conn = socket(AF_INET, SOCK_STREAM)
        proxy_tcp_conn.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) # 建立新的 TCP 端口
        proxy_tcp_conn.connect(('192.168.43.1', 8080)) # 连接 TCP ServerB 服务
        proxy_tcp_conn.send(recv_data)  # 将接收到的数据发向另一个服务器
        proxy_recv_msg = proxy_tcp_conn.recv(1024)  # 接收TCP ServerB 服务返回的数据
        print("接收到另一个服务的信息: " + proxy_recv_msg.decode())

        # 发送一些数据到客户端
        client_socket.send(proxy_recv_msg)

        # 将接收到的数据转换为字符串打印
        recv_result = str(recv_data.decode('utf-8'))
        # print("recv_result", recv_result)

        # 当接收到stop,则停止服务
        if recv_result == "stop":
            break

    # 关闭为这个客户端服务的套接字,只要关闭,就意味着不能再为这个客户端服务了。
    # 如果客户端还需要服务,则重新建立连接
    client_socket.close()

    ## 最后关闭监听的socket
    tcp_server_socket.close()


if __name__ == '__main__':
    server()

TCP ServerB 使用 NetAssist 工具实现

python3 基于 socket 反向代理  adb 设备_python3 _02image-20201204225222267

功能演示

1.首先我们使用 NetAssist 开启 TCP服务,充当 TCP ServerB 服务

python3 基于 socket 反向代理  adb 设备_socket_03image-20201204225330847

2.开启 TCP Server A 服务

python3 基于 socket 反向代理  adb 设备_socket_04image-20201204225526338

3.开启 TCP Client 客户端

python3 基于 socket 反向代理  adb 设备_socket_05image-20201204225552820

4.在 TCP Client 客户端输入发送的信息,在 TCP ServerB 服务上查看接收到的信息

python3 基于 socket 反向代理  adb 设备_socket_06image-20201204225711800

5.在 TCP ServerB 发送响应的消息,在客户端查看

python3 基于 socket 反向代理  adb 设备_python3 _07image-20201204225813077

6.在 TCP ServerA 查看中间转发的消息

python3 基于 socket 反向代理  adb 设备_python3 _08image-20201204225904948

7.总结

从上面的效果来看,我们已经成功的将 TCP ServerA 作为反向代理服务,进行中间消息的转发。

那么下一步,我们就要考虑如何将其应用到 其他服务的转发上。

抓取adb连接的TCP报文

下一步我们来考虑如果使用自己开发的 TCP socket 服务来转发 adb 连接,那么我们首先要清楚理解 adb 在与 设备连接过程的 TCP 报文信息。

下面我们使用 wireshark 来抓取报文分析一下。

开启 wireshark 服务

python3 基于 socket 反向代理  adb 设备_python3 _09image-20201204232320259

在虚拟机上执行 adb 连接

开启 adb 连接设备:

python3 基于 socket 反向代理  adb 设备_socket_10image-20201204232544245

开启 adb shell 连接:

python3 基于 socket 反向代理  adb 设备_socket_11image-20201204232642329

总上面的报文来看, adb 的连接都需要进行多次 TCP 报文发送,进行握手处理。

所以,我们下面要修改一下代理服务的代理消息功能,因为可能要转发多次。

过滤目标IP 以及 协议端口(只能看到发送的报文)

ip.dst == 192.168.43.1 && tcp.port == 7788

输入过滤器,按下回车,就可以过滤报文信息,如下:

python3 基于 socket 反向代理  adb 设备_python3 _12image-20201204233657335

过滤IP 以及 协议端口(可以看到发送以及接收的报文)

ip.addr == 192.168.43.1 && tcp.port == 5555

输入过滤器,按下回车,就可以过滤报文信息,如下:

python3 基于 socket 反向代理  adb 设备_socket_13image-20201205093937657

可以看到 adb 执行 shell 命令的时候,进行了多次 TCP 请求以及信息的返回,过程大致如下:

python3 基于 socket 反向代理  adb 设备_python3 _14image-20201205094536065

从上面的分析来看,我们可以发现 socket 可以会连续接收远端返回的消息,这就要求我们在做 socket 处理的时候是非阻塞的。

这一点很重要,不然就会卡在第二个报文的地方,不动了。

下面我们来写一个初步的服务端来演示一下默认阻塞式 socket 转发卡住的情况。

默认阻塞式 socket 转发卡住的情况演示( 半双工模式 )

初步服务端代码

from socket import *

def server():
    # 创建套接字
    tcp_server_socket = socket(AF_INET, SOCK_STREAM)

    # 绑定服务端提供服务的端口号
    local_addr = ('', 7788)  # ip地址和端口号,ip一般不用写,表示本机的任何一个ip

    # 绑定
    tcp_server_socket.bind(local_addr)

    # 使用socket创建的套接字默认的属性是主动的,使用listen将其改为被动,用来监听连接
    tcp_server_socket.listen(128)

    # 如果有新的客户端来链接服务端,那么就产生一个新的套接字专门为这个客户端服务
    # client_socket用来为这个客户端服务
    # tcp_server_socket就可以省下来专门等待其他新的客户端连接while True:
    client_socket, clientAddr = tcp_server_socket.accept()

    while True:

        # 接收对方发送的数据
        recv_data = client_socket.recv(1024)  # 1024表示本次接收的最大字节数
        print('接收到客户端的数据为:', recv_data)

        # 将客户端的数据,转发至另一个服务器
        # 创建新的tcp连接,连接上另一个服务器
        proxy_tcp_conn = socket(AF_INET, SOCK_STREAM)
        proxy_tcp_conn.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) # 建立新的 TCP 端口
        proxy_tcp_conn.connect(('192.168.43.1', 5555)) # 连接 adb 服务
        proxy_tcp_conn.send(recv_data)  # 将接收到的数据发向另一个服务器
        proxy_recv_msg = proxy_tcp_conn.recv(1024*1024)  # 接收adb设备返回的数据
        print("接收到另一个服务的信息: " , proxy_recv_msg)

        # 发送一些数据到客户端
        client_socket.send(proxy_recv_msg)

if __name__ == '__main__':
    server()

在上面代码中,我没有设置主动关闭 socket 端口。

  • 首先提供 7788 TCP 端口的 socket 服务,提供后续 adb 客户端的连接
  • 当 adb 客户端连接 7788 TCP 端口的 socket 服务,将新建一个 socket 端口,连接向远端的 adb 设备
  • 等待接收 远端 adb 设备 返回的消息,转发到 adb 客户端
  • 在转发给 adb 客户端之后,socket 就会被阻塞,等待 adb 客户端再次发送消息

出现问题:在这里是关键的,因为 socket 被阻塞住了,远端 adb 设备还想 再发送一个 TCP 报文,但是却无法被转发。此时,报文无法进行下去了。

操作演示

1. 在执行 adb connect 的时候,报文时依次来回发送的,所以不会被阻塞

python3 基于 socket 反向代理  adb 设备_python3 _15image-20201205102310681

2. 执行 adb shell,因为socket阻塞,远端adb设备的二次发送报文无法往回发送,导致卡住的状态。

python3 基于 socket 反向代理  adb 设备_python3 _16image-20201205102512254

3. 分析阻塞位置的代码

python3 基于 socket 反向代理  adb 设备_socket_17image-20201205102658744

修改为 非阻塞 socket 转发 (全双工)

注意:在这里提到非阻塞式 socket 并不是 直接将 socket 维持 TCP 连接的部分设置 server_socket.setblocking(False) 非阻塞模式。这种方式会直接断开 客户端 与 远端adb设备的连接。

而是将接收消息 和 发送消息 分别写为两个线程,同时执行。这种模式就是所谓的 全双工 通讯模式。

下面废话不多说,直接上我多次调试完毕后的代码, 然后再来逐步讲解一下每个部分的代码。

全双工服务端代码