python3 基于 socket 反向代理 adb 设备

本文介绍了如何通过TCP反向代理技术实现ADB连接的远程操作,包括SSH隧道和FRP隧道的局限性,以及如何通过自定义TCP服务解决用户鉴权等问题。通过示例展示了TCPClient、TCPServerA和TCPServerB的交互,并利用Wireshark分析adb连接的TCP报文,最后探讨了非阻塞式socket在全双工模式下进行adbshell命令转发的必要性。
摘要由CSDN通过智能技术生成

需求
在跨网络的操作中,我们想要连接一些内网服务,例如:对 机房内的安卓设备 进行 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 报文,但是却无法被转发。此时,报文无法进行下去了。

操作演示

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

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

image-20201205102512254
3. 分析阻塞位置的代码

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

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

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

全双工服务端代码

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值