飞道的博客

python3 基于 socket 反向代理 adb 设备

301人阅读  评论(0)

需求

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

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

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

  • 可以采用 frp 搭建隧道转发 tcp 端口

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

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

实验拓扑


   
  1. +----+                +----+                      +----+
  2. |adb |----connect-----| S1 |----proxy connect-----| Tv |
  3. |    |                |    |                      |    |
  4. +----+                +----+                      +----+
  5. clientA               TCP ServerB                 Android device

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

初始adb连接环境

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


   
  1. C:\Users\lijw>adb devices
  2. List of devices attached
  3. emulator -5554   device
  4. C:\Users\lijw>adb connect  127.0 .0 .1: 5555
  5. connected to  127.0 .0 .1: 5555
  6. C:\Users\lijw>adb devices
  7. List of devices attached
  8. 127.0 .0 .1: 5555  device
  9. emulator -5554   device
  10. C:\Users\lijw>adb -s  127.0 .0 .1: 5555 shell
  11. tcl_m7642:/ $ ls
  12. acct         etc                    mnt                     sbin        ueventd.rc
  13. art          factory                odm                     sdcard      userdata
  14. bin          impdata                oem                     sepolicy     var
  15. bugreports   init                   persist                 storage     vendor
  16. cache        init.environ.rc        plat_file_contexts      sys         vendor_file_contexts
  17. charger      init.rc                plat_hwservice_contexts system      vendor_hwservice_contexts
  18. config       init.recovery.m7642.rc plat_property_contexts  tclconfig   vendor_property_contexts
  19. d            init.usb.configfs.rc   plat_seapp_contexts     tcluserdata vendor_seapp_contexts
  20. data         init.usb.rc            plat_service_contexts   tmp         vendor_service_contexts
  21. default.prop init.zygote32.rc       proc                    tvconfig    vndservice_contexts
  22. dev          lost+found             product                 tvos
  23. tcl_m7642:/ $ exit
  24. C:\Users\lijw>

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

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

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

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

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

image-20201204224800081

TCP Client 的代码

client.py


   
  1. from socket  import *
  2. def client():
  3.     # 创建socket
  4.     tcp_client_socket = socket(AF_INET, SOCK_STREAM)
  5.     # 服务器的地址
  6.     #  '192.168.43.1'表示目的ip地址
  7.     #  8080表示目的端口
  8.     dest_addr = ( '127.0.0.1'7788)  # 注意 是元组,ip是字符串,端口是数字
  9.     # 链接服务器,进行tcp三次握手
  10.     tcp_client_socket.connect(dest_addr)
  11.     while True:
  12.         # 从键盘获取数据
  13.         send_data = input( "请输入要发送的数据:")
  14.         # 判断输入stop,则退出客户端
  15.          if send_data ==  "stop":
  16.              break
  17.         # 发送数据到指定的服务端
  18.         tcp_client_socket.send(send_data.encode( "utf-8"))
  19.         # 接收对方发送过来的数据,最大接收 1024个字节
  20.         recvData = tcp_client_socket.recv( 1024)
  21.          print( '接收到的数据为:', recvData.decode( 'utf-8'))
  22.     # 关闭套接字
  23.     tcp_client_socket. close()
  24. if __name__ ==  '__main__':
  25.     client()

TCP ServerA 的代码

server.py


   
  1. from socket  import *
  2. def server():
  3.     # 创建套接字
  4.     tcp_server_socket = socket(AF_INET, SOCK_STREAM)
  5.     # 绑定服务端提供服务的端口号
  6.     local_addr = ( ''7788)  # ip地址和端口号,ip一般不用写,表示本机的任何一个ip
  7.     # 绑定
  8.     tcp_server_socket.bind(local_addr)
  9.     # 使用socket创建的套接字默认的属性是主动的,使用listen将其改为被动,用来监听连接
  10.     tcp_server_socket.listen( 128)
  11.     # 如果有新的客户端来链接服务端,那么就产生一个新的套接字专门为这个客户端服务
  12.     # client_socket用来为这个客户端服务
  13.     # tcp_server_socket就可以省下来专门等待其他新的客户端连接while True:
  14.     client_socket, clientAddr = tcp_server_socket.accept()
  15.     while True:
  16.         # 接收对方发送的数据
  17.         recv_data = client_socket.recv( 1024)  #  1024表示本次接收的最大字节数
  18.          print( '接收到客户端的数据为:', recv_data.decode( 'utf-8'))
  19.         # 将客户端的数据,转发至另一个服务器
  20.         # 创建新的tcp连接,连接上另一个服务器
  21.         proxy_tcp_conn = socket(AF_INET, SOCK_STREAM)
  22.         proxy_tcp_conn.setsockopt(SOL_SOCKET, SO_REUSEADDR,  1) # 建立新的 TCP 端口
  23.         proxy_tcp_conn.connect(( '192.168.43.1'8080)) # 连接 TCP ServerB 服务
  24.         proxy_tcp_conn.send(recv_data)  # 将接收到的数据发向另一个服务器
  25.         proxy_recv_msg = proxy_tcp_conn.recv( 1024)  # 接收TCP ServerB 服务返回的数据
  26.          print( "接收到另一个服务的信息: " + proxy_recv_msg.decode())
  27.         # 发送一些数据到客户端
  28.         client_socket.send(proxy_recv_msg)
  29.         # 将接收到的数据转换为字符串打印
  30.         recv_result = str(recv_data.decode( 'utf-8'))
  31.         #  print( "recv_result", recv_result)
  32.         # 当接收到stop,则停止服务
  33.          if recv_result ==  "stop":
  34.              break
  35.     # 关闭为这个客户端服务的套接字,只要关闭,就意味着不能再为这个客户端服务了。
  36.     # 如果客户端还需要服务,则重新建立连接
  37.     client_socket. close()
  38.     ## 最后关闭监听的socket
  39.     tcp_server_socket. close()
  40. if __name__ ==  '__main__':
  41.     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 转发卡住的情况演示( 半双工模式 )

初步服务端代码


   
  1. from socket  import *
  2. def server():
  3.     # 创建套接字
  4.     tcp_server_socket = socket(AF_INET, SOCK_STREAM)
  5.     # 绑定服务端提供服务的端口号
  6.     local_addr = ( ''7788)  # ip地址和端口号,ip一般不用写,表示本机的任何一个ip
  7.     # 绑定
  8.     tcp_server_socket.bind(local_addr)
  9.     # 使用socket创建的套接字默认的属性是主动的,使用listen将其改为被动,用来监听连接
  10.     tcp_server_socket.listen( 128)
  11.     # 如果有新的客户端来链接服务端,那么就产生一个新的套接字专门为这个客户端服务
  12.     # client_socket用来为这个客户端服务
  13.     # tcp_server_socket就可以省下来专门等待其他新的客户端连接while True:
  14.     client_socket, clientAddr = tcp_server_socket.accept()
  15.     while True:
  16.         # 接收对方发送的数据
  17.         recv_data = client_socket.recv( 1024)  #  1024表示本次接收的最大字节数
  18.          print( '接收到客户端的数据为:', recv_data)
  19.         # 将客户端的数据,转发至另一个服务器
  20.         # 创建新的tcp连接,连接上另一个服务器
  21.         proxy_tcp_conn = socket(AF_INET, SOCK_STREAM)
  22.         proxy_tcp_conn.setsockopt(SOL_SOCKET, SO_REUSEADDR,  1) # 建立新的 TCP 端口
  23.         proxy_tcp_conn.connect(( '192.168.43.1'5555)) # 连接 adb 服务
  24.         proxy_tcp_conn.send(recv_data)  # 将接收到的数据发向另一个服务器
  25.         proxy_recv_msg = proxy_tcp_conn.recv( 1024* 1024)  # 接收adb设备返回的数据
  26.          print( "接收到另一个服务的信息: " , proxy_recv_msg)
  27.         # 发送一些数据到客户端
  28.         client_socket.send(proxy_recv_msg)
  29. if __name__ ==  '__main__':
  30.     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设备的连接。

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

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

全双工服务端代码


转载:https://blog.csdn.net/u012887259/article/details/110730147
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场