需求
在跨网络的操作中,我们想要连接一些内网服务,例如:对 机房内的安卓设备 进行 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 设备_socket](https://s2.51cto.com/images/blog/202106/18/3cd806e8d25aeffd9252796fb6dedbdb.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=/resize,m_fixed,w_1184)
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 _02](https://s2.51cto.com/images/blog/202106/18/900a6ee5197f62c4496de31ae0f8fc5a.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=/resize,m_fixed,w_1184)
功能演示
1.首先我们使用 NetAssist 开启 TCP服务,充当 TCP ServerB 服务
![python3 基于 socket 反向代理 adb 设备_socket_03](https://s2.51cto.com/images/blog/202106/18/ce2571250dcf9d15825ce5c8cb459218.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=/resize,m_fixed,w_1184)
2.开启 TCP Server A 服务
![python3 基于 socket 反向代理 adb 设备_socket_04](https://s2.51cto.com/images/blog/202106/18/d919951be42e6fdf91110b0e9a143343.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=/resize,m_fixed,w_1184)
3.开启 TCP Client 客户端
![python3 基于 socket 反向代理 adb 设备_socket_05](https://s2.51cto.com/images/blog/202106/18/402c7ab050aedc6a864da7e744c3ec90.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=/resize,m_fixed,w_1184)
4.在 TCP Client 客户端输入发送的信息,在 TCP ServerB 服务上查看接收到的信息
![python3 基于 socket 反向代理 adb 设备_socket_06](https://s2.51cto.com/images/blog/202106/18/fcb4c098e06fa56a4d2867830f54a130.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=/resize,m_fixed,w_1184)
5.在 TCP ServerB 发送响应的消息,在客户端查看
![python3 基于 socket 反向代理 adb 设备_python3 _07](https://s2.51cto.com/images/blog/202106/18/bd33cca4752b29a1194ea02f391131b2.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=/resize,m_fixed,w_1184)
6.在 TCP ServerA 查看中间转发的消息
![python3 基于 socket 反向代理 adb 设备_python3 _08](https://s2.51cto.com/images/blog/202106/18/a4a217195a70a0c97a77edde36a69d33.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=/resize,m_fixed,w_1184)
7.总结
从上面的效果来看,我们已经成功的将 TCP ServerA 作为反向代理服务,进行中间消息的转发。
那么下一步,我们就要考虑如何将其应用到 其他服务的转发上。
抓取adb连接的TCP报文
下一步我们来考虑如果使用自己开发的 TCP socket 服务来转发 adb 连接,那么我们首先要清楚理解 adb 在与 设备连接过程的 TCP 报文信息。
下面我们使用 wireshark 来抓取报文分析一下。
开启 wireshark 服务
![python3 基于 socket 反向代理 adb 设备_python3 _09](https://s2.51cto.com/images/blog/202106/18/eb696918a28d6a7f13659a0e9ef970e0.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=/resize,m_fixed,w_1184)
在虚拟机上执行 adb 连接
开启 adb 连接设备:
![python3 基于 socket 反向代理 adb 设备_socket_10](https://s2.51cto.com/images/blog/202106/18/6a401a316d25169395e08d27995072f9.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=/resize,m_fixed,w_1184)
开启 adb shell 连接:
![python3 基于 socket 反向代理 adb 设备_socket_11](https://s2.51cto.com/images/blog/202106/18/870a97f7762300c4d0b695662fe3276d.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=/resize,m_fixed,w_1184)
总上面的报文来看, adb 的连接都需要进行多次 TCP 报文发送,进行握手处理。
所以,我们下面要修改一下代理服务的代理消息功能,因为可能要转发多次。
过滤目标IP 以及 协议端口(只能看到发送的报文)
ip.dst == 192.168.43.1 && tcp.port == 7788
输入过滤器,按下回车,就可以过滤报文信息,如下:
![python3 基于 socket 反向代理 adb 设备_python3 _12](https://s2.51cto.com/images/blog/202106/18/23a82b1acf6776c788694f321778d507.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=/resize,m_fixed,w_1184)
过滤IP 以及 协议端口(可以看到发送以及接收的报文)
ip.addr == 192.168.43.1 && tcp.port == 5555
输入过滤器,按下回车,就可以过滤报文信息,如下:
![python3 基于 socket 反向代理 adb 设备_socket_13](https://s2.51cto.com/images/blog/202106/18/731cb4bd7f26779958749b1f6c0fc52c.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=/resize,m_fixed,w_1184)
可以看到 adb 执行 shell 命令的时候,进行了多次 TCP 请求以及信息的返回,过程大致如下:
![python3 基于 socket 反向代理 adb 设备_python3 _14](https://s2.51cto.com/images/blog/202106/18/44323d57871ef8d57405238ef676a1f0.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=/resize,m_fixed,w_1184)
从上面的分析来看,我们可以发现 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 _15](https://s2.51cto.com/images/blog/202106/18/e2b8765d8be09913d043267d23673668.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=/resize,m_fixed,w_1184)
2. 执行 adb shell,因为socket阻塞,远端adb设备的二次发送报文无法往回发送,导致卡住的状态。
![python3 基于 socket 反向代理 adb 设备_python3 _16](https://s2.51cto.com/images/blog/202106/18/792d7f0b42e081897555668f2fc04487.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=/resize,m_fixed,w_1184)
3. 分析阻塞位置的代码
![python3 基于 socket 反向代理 adb 设备_socket_17](https://s2.51cto.com/images/blog/202106/18/9ab606b28a0556a82201815164984ae7.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_30,g_se,x_10,y_10,shadow_20,type_ZmFuZ3poZW5naGVpdGk=/resize,m_fixed,w_1184)
修改为 非阻塞 socket 转发 (全双工)
注意:在这里提到非阻塞式 socket 并不是 直接将 socket 维持 TCP 连接的部分设置 server_socket.setblocking(False) 非阻塞模式。这种方式会直接断开 客户端 与 远端adb设备的连接。
而是将接收消息 和 发送消息 分别写为两个线程,同时执行。这种模式就是所谓的 全双工 通讯模式。
下面废话不多说,直接上我多次调试完毕后的代码, 然后再来逐步讲解一下每个部分的代码。
全双工服务端代码