需求
在跨网络的操作中,我们想要连接一些内网服务,例如:对 机房内的安卓设备 进行 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转发消息的流程,示意图如下:
image-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 工具实现
image-20201204225222267
功能演示
1.首先我们使用 NetAssist 开启 TCP服务,充当 TCP ServerB 服务
image-20201204225330847
2.开启 TCP Server A 服务
image-20201204225526338
3.开启 TCP Client 客户端
image-20201204225552820
4.在 TCP Client 客户端输入发送的信息,在 TCP ServerB 服务上查看接收到的信息
image-20201204225711800
5.在 TCP ServerB 发送响应的消息,在客户端查看
image-20201204225813077
6.在 TCP ServerA 查看中间转发的消息
image-20201204225904948
7.总结
从上面的效果来看,我们已经成功的将 TCP ServerA 作为反向代理服务,进行中间消息的转发。
那么下一步,我们就要考虑如何将其应用到 其他服务的转发上。
抓取adb连接的TCP报文
下一步我们来考虑如果使用自己开发的 TCP socket 服务来转发 adb 连接,那么我们首先要清楚理解 adb 在与 设备连接过程的 TCP 报文信息。
下面我们使用 wireshark 来抓取报文分析一下。
开启 wireshark 服务
image-20201204232320259
在虚拟机上执行 adb 连接
开启 adb 连接设备:
image-20201204232544245
开启 adb shell 连接:
image-20201204232642329
总上面的报文来看, adb 的连接都需要进行多次 TCP 报文发送,进行握手处理。
所以,我们下面要修改一下代理服务的代理消息功能,因为可能要转发多次。
过滤目标IP 以及 协议端口(只能看到发送的报文)
ip.dst == 192.168.43.1 && tcp.port == 7788
输入过滤器,按下回车,就可以过滤报文信息,如下:
image-20201204233657335
过滤IP 以及 协议端口(可以看到发送以及接收的报文)
ip.addr == 192.168.43.1 && tcp.port == 5555
输入过滤器,按下回车,就可以过滤报文信息,如下:
image-20201205093937657
可以看到 adb 执行 shell 命令的时候,进行了多次 TCP 请求以及信息的返回,过程大致如下:
image-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 报文,但是却无法被转发。此时,报文无法进行下去了。
操作演示
- 在执行 adb connect 的时候,报文时依次来回发送的,所以不会被阻塞
image-20201205102310681
2. 执行 adb shell,因为socket阻塞,远端adb设备的二次发送报文无法往回发送,导致卡住的状态。
image-20201205102512254
3. 分析阻塞位置的代码
image-20201205102658744
修改为 非阻塞 socket 转发 (全双工)
注意:在这里提到非阻塞式 socket 并不是 直接将 socket 维持 TCP 连接的部分设置 server_socket.setblocking(False) 非阻塞模式。这种方式会直接断开 客户端 与 远端adb设备的连接。
而是将接收消息 和 发送消息 分别写为两个线程,同时执行。这种模式就是所谓的 全双工 通讯模式。
下面废话不多说,直接上我多次调试完毕后的代码, 然后再来逐步讲解一下每个部分的代码。
全双工服务端代码