写点什么

有赞移动助手 App 本地抓包方案

  • 2019-07-21
  • 本文字数:4608 字

    阅读完需:约 15 分钟

有赞移动助手App本地抓包方案

一、概述

有赞移动助手(下面简称助手 App)网关切换功能(有赞移动 App 一键切换网关实践),在我们有赞移动整个开发,测试回归,产品回归验收等扮演着重要的角色,近期我们在 App 集成了本地抓包的功能,更是如虎添翼。在测试抓包,线上问题排查等场景发挥着重要的作用。

二、背景

在目前的抓包的场景中,大部分通过手机连接 PC,进行 IP 代理,用三方抓包工具(Charles,Fiddler,Wireshark 等)进行抓包,具体有如下痛点:


  • 抓包需要将移动设备的 Wi-Fi 设置中将代理手动设置成 PC 的 IP,和对应的端口号,过程比较繁琐

  • 移动设备需要安装 Charles 提供的 Root 证书

  • 不能随时随地的用移动设备抓包,必须强依赖于 PC 端


因此我们需要在 App 增加本地抓包的功能, 通过一种技术手段可以实时监听到通过移动助手网关功能连接到 ZanProxy 服务器的所有网络请求,经过调研,实现这个功能应该具备以下几个条件:


  • 利用 Socket 协议来实现消息推送经过助手 App 网关功能的网络请求到客户端

  • 当我们切换到其他 APP 进行网络请求时,要让助手 App 能在后台保活,甚至是常驻在后台,来达到对网络监听的目的

  • 需要在有赞助手有对应的页面去展示监听到的网络请求,header,response,request 等数据

三、有赞移动助手

我们助手 App 网关功能的原理是 Android 提供的 VpnService ,iOS 的 NetworkExtension 将 TCP 连接的 IP 数据包通过 tun2socks 转化成 socks5 代理,将数据转发到 ZanProxy 服务器中,实现整个网关功能。

3.1 tun2socks

tun2socks 实现一种机制,它可以让你无需改动任何应用程序而完全同名地机那个数据用 socks 协议封装,转发给一个 socks 代理,然后由代理程序负责于正式服务器之间转发应用数据。使用代理有两种方式,一种是显示配置代理,数据离开你的主机时它的目标地址就是代理服务器。另一种是做透明代理,即在中途把原始数据重定向到一个应用程序,由该代理程序代理转发。tun2socks 在第二种的基础上,完成了 socks 协议的封装,并且实现该机制时使用了强大的 tun 虚拟网卡而不必再去配置复杂的 iptables 规则,如下图所示


3.2 socks 代理

socks 运作原理,就是在 TCP 数据外包一层 socks 协议头,到达 socks 代理服务器后,脱去 socks 头,然后通过 socks 服务器与真实服务器之间建立的连接将 TCP 数据传给真实服务器,socks 代理并不理解任何应用层协议,它只是负责转发应用层数据而已,这一点使 socks 成为了一个通用的代理协议,这一点和 HTTP 代理服务器是完全不同。

四、技术方案

了解了整个流程以后我们回到最初的痛点,需要利用 Socket 协议来让服务器和 App 建立长链接,实时监听通过助手 App 网关的所有 http/https 请求

4.1 Websocket

WebSocket 是 HTML5 新增的一种通信协议。WebSocket 协议是一种持久化的双向通信协议,它建立在 TCP 之上,同 HTTP 一样通过 TCP 来传输数据,但是它和 HTTP 最大的不同有两点:


  • WebSocket 是一种双向通信协议,在建立连接后,WebSocket 服务器和 Browser/UA 都能主动的向对方发送或接收数据,就像 Socket 一样,不同的是 WebSocket 是一种建立在 Web 基础上的一种简单模拟 Socket 的协议。

  • WebSocket 需要通过握手连接,类似于 TCP,它也需要客户端和服务器端进行握手连接,连接成功后才能相互通信。


下面是一个简单的建立握手的时序图:


4.2 Socket.IO

socket.io 是支持浏览器和服务器之间实时、双向基于事件通信的库,它包括 Node.js 服务端 API 和浏览器的 JavaScript 端的 API,是一个完全由 JavaScript 实现、基于 Node.js、支持 WebSocket 协议的用于实时通信、跨平台的开源框架。底层是基于 engine.io,在此基础上增加了 Namespace、room、自动重连等特性。


socket.io 设计的目标是支持任何的浏览器,任何 Mobile 设备。支持主流的 PC 浏览器 (IE,Safari,Chrome,Firefox,Opera 等),Mobile 浏览器(iphone Safari/ipad Safari/Android WebKit/WebOS WebKit 等)。socket.io 旨在使实时应用在每个浏览器和移动设备上成为可能,模糊不同的传输机制之间的差异。


但是,WebSocket 协议是 HTML5 新推出的协议,浏览器对它的支持并不完善,由此可以看出,socket.io 不可能仅仅是对 WebSocket 的实现,它还支持其他的通信方式,如上面介绍过的 AJAX 轮询和 Long Polling。根据浏览器的支持程度,自主选择使用哪种方式进行通讯。

4.2.1 Socket.io 支持的通信方式:

  • WebSocket

  • Adobe Flash Socket

  • AJAX long-polling

  • AJAX multipart streaming

  • Forever IFrame

  • JSONP polling

4.2.2 可靠性

socket.io 即使在下列情况下也能建立联系:


  • 代理和负载平衡器

  • 个人防火墙和杀毒软件

4.2.3 自动重连机制

除非有特殊指示,否则断开连接的客户端将尝试重新连接,直到服务器再次可用为止

4.2.4 断开检测

engine.io 级别实现心跳极值,允许服务器和客户端都知道对方何时不再响应。通过在服务器和客户端设置计时器,在握手连接期间共享超时值(pinginterval 和 pingTimeout 参数),可以实现该功能。

4.2.5 多路复用

为了在应用程序中创建关注点分离(例如每个模块,或者基于权限),socket.io 允许您创建多个名称空间,这些名称空间将作为单独的通信通道,但将共享相同的底层连接。


其他更多的包括,二进制的支持、房间支持等特性,由于想到以后的可扩展性以及客户端的支持程度,以及因此我们选择 socket.io 作为我们消息推送的技术方案。

4.2.6 源码分析

在建立连接后,每个客户端会被自动加入到一个默认的命名空间。在每个命名空间中,socket 会被默认加入两个名为 None 和 sid 的房间。None 的房间用于广播,而 sid 是当前客户端的 session id,用于单播。除默认的房间外,我们可以根据需要将对应 socket 加入自定义房间,roomid 唯一即可。socket.io 基于engine.io,支持 websocket 和 long polling。如果是 long polling,会定时发送 GET, POST 请求,当没有数据时,GET 请求在拉取队列消息时会 hang 住(超时时间为 pingTimeout ),如果 hang 住期间服务器一直没有数据产生,则需要等到客户端发送下一个 POST 请求时,此时服务器会往队列中存储 POST 请求中的消息,这样上一个 GET 请求才会返回。如果 upgrade 到了 Websocket 连接,则探测成功之后会定期 ping/pong 来保活连接。流程如下图所示:


4.3 Socket.IO 应用

在本次需求中移动端 iOS 用的开源库 socket.io-client-swift 是 swift 语言编写的。Andriod 端用的开源库 socket.io-android-chat 。下面以 iOS 为例看下如何使用:


 // MARK: Initializers
/// Type safe way to create a new SocketIOClient. `opts` can be omitted. /// /// - parameter socketURL: The url of the socket.io server. /// - parameter config: The config for this socket. public init(socketURL: URL, config: SocketIOClientConfiguration = []) { self._config = config self.socketURL = socketURL
super.init()
setConfigs(_config) }
复制代码


创建初始化 SocketManager 实例,指定 socketUrl ,指定对应的 nameSpace(名称空间)来保证单独的通信通道,将 UUID(设备唯一 id)当作 cookie 添加到 header 中,保证能够识别是哪台设备和服务端建立的 socket 通信,调用 connect 方法进行 socket 连接。


socket.on(clientEvent: .connect) { data, ack in            print("connect success")            YZVPNSettingsNetwork.bindDevice(deviceId: device ?? "")                .subscribe (                    onNext: {  response in                        print("websocket, 绑定成功 ")                },                    onError: { e in                        print("websocket, 绑定成功 ")                }            )        }
复制代码


在连接成功后将设备信息发到我们服务器进行设备的绑定。


socket.on("rows") { (data, ack) in            guard let cur = data[0] as? String else { return }            print(cur)        }        socket.on(clientEvent: .disconnect) {data, ack in            print("socket disconnect")        }        socket.on(clientEvent: .error) {data, ack in            print("socket error")        }        socket.on(clientEvent: .pong) {data, ack in            print("socket pong" + data.description)        }
复制代码


接下来就是各种事件的监听,进行相应的处理。我们这边和服务端约定好定义了名叫 rows 的事件,那么在该事件中就可以收到 http/https 请求的所有信息 originRequest、requestData、response 信息,那么如何将每个请求的信息对应起来呢?我们这里封装了一个名叫 HttpSession 的类如下:


class  HttpSession : HandyJSON {
var originRequest : String? var requestData : String? var response : String? var id : String? required init() {
}}
复制代码


返回的数据里面有 id 来作为每个 http/https 请求的唯一标示,当接收到数据时,用 id 进行本地映射,将数据塞入对应的 HttpSession 里面,当每个 HttpSession 对象的所有数据都被拿到时,我们再将该条请求信息展示出来。如下图所示:



以上整个本地抓包功能都已经完结,但是还有一个问题,我们去抓包肯定是打开别的 APP 将助手 App 退出到后台甚至是程序挂起状态,那么如何保证在助手 App 退出到后台模式后还能收到服务端推来的 socket 消息呢?那就需要让程序在后台长时间运行,iOS 有以下几种方法:


  • VOIP

  • Background Audio 后台播放音乐

  • Location Services 定位服务

  • Newsstand downloads 后台下载

  • Remote notifications 静默推送

  • 注册一个后台任务


一般而言,音乐应用在后台是避免 kill 的,如果在后台应用可用时间即将为 0 时,播放一段音乐,就会使应用变为假前端状态。可以尝试的解决方案如下:应用申请到后台执行任务后,使用 NSTimer 开启一个定时任务,主要负责监控应用剩余的后台可执行时间,当可用的时间少于一个值时,播放一段默声音乐,然后调用 UIApplication 对象的 beginBackgroundTaskWithExpirationHandler 方法将之前申请的后台执行任务结束掉,最后再重新申请一个后台执行任务,这样就可以实现后台不限时执行任务了


因此我们这边采用后台音乐和 beginBackgroundTaskWithExpirationHandler 方法保证 APP 能在后台不被 kill 。

五、总结

以上就是整个本地抓包涉及到的所有技术点,总结一下 App 如何完成本地抓包的流程:


  1. 当 App 通过 VpnService/NetworkExtension 配置 VPN 和定制、扩展核心网络功能

  2. 通过 tun2Socks,将请求 TCP 数据外包一层 socks 协议头,到达 socks 代理服务器后,脱去 socks 头,然后通过 socks 服务器与真实服务器之间建立的连接将 TCP 数据传给真实服务器,然后进行 ZanProxy 代理访问,完成一次 http/https 的完整请求

  3. 在整个请求过程中,我们用 Socket.IO 将服务端与 助手 App 建立长连接,将访问的 http/https 数据实时推送给 App 端

  4. 助手 App 监听长连接事件,拿到数据进行处理,展示出来


由于篇幅有限,期间还涉及到一些其他的技术点,有兴趣的同学可以去查阅对应的文献资料,也欢迎大家积极参沟通,写出这篇文章,是抛砖引玉,也是为了其他感兴趣的同学提供一些思路。


参考文献


https://socket.io/docs/logging-and-debugging/


https://socket.io/


本文转载自公众号有赞 coder(ID:youzan_coder)


原文链接


https://mp.weixin.qq.com/s/DY6c2_IBtG6XjKHZiNemdQ


2019-07-21 08:0015897

评论 1 条评论

发布
用户头像
类似的应用app store是有的,贵公司的app开源或者上架了吗
2019-07-22 11:01
回复
没有更多了
发现更多内容

2 月月更获奖名单公布!获奖的小伙伴速速领取奖励啦!

InfoQ写作社区官方

2月月更 热门活动

尤达DDD领域驱动设计思想 第一章作业(理解单纯的面向对象设计思想的缺陷)

代廉洁

尤达DDD领域驱动设计思想

Flutter 图文并茂列表完整实现

岛上码农

flutter ios 安卓 移动端 3月月更

如何从头到脚彻底解决一个MySQL Bug

华为云开发者联盟

MySQL 数据库 华为云 bug GaussDB(for MySQL)

好云推荐官丨飞天加速之星怎样选择云服务器ECS?

阿里云弹性计算

阿里云 采购季 好云推荐官

一文读懂 MongoDB驱动程序 API

MongoDB中文社区

mongodb

MongoDB案例分享:如何使用oplog恢复数据

MongoDB中文社区

mongodb

经验分享 | 搭建帮助中心的最强攻略

小炮

威胁驱动的网络安全方法论

喀拉峻

网络安全

百度连续四年亮相全球量子信息处理顶会QIP 宣布量子战略规划升级

百度大脑

手把手教程|构建无服务器通用文本识别功能

亚马逊云科技 (Amazon Web Services)

架构

你的密钥被我看见了 !逆向获取密钥

H

网络安全 逆向

龙蜥开发者说:做开源,兴趣是最好的源动力 | 第1期

OpenAnolis小助手

开源 创作 开发者故事 兴趣是动力

优秀的测试开发应该具备的六大能力

老张

软件测试 测试开发

对称加密与非对称加密总结

暖蓝笔记

3月月更

拿下10+OFFER总结的HR常问问题

暖蓝笔记

3月月更

免费机器资源、硬核导师、丰厚奖励|飞桨黑客马拉松第二期开始啦!

百度大脑

使用关键点检测打造小工具Padoodle,让涂鸦小人跟随真人学跳舞

百度大脑

什么是以特性为核心的持续交付|阿里巴巴DevOps实践指南

阿里云云效

云计算 阿里云 研发效能 研发 DevOps实践指南

做毕设用不起GPU?亚马逊云SageMaker免费给你用

亚马逊云科技 (Amazon Web Services)

学习

报名啦!中小企业如何借力AI逆势突围?飞桨中国行定档3月23日!

百度大脑

【Python训练营】Python每日一练----第31天: k倍区间

是Dream呀

3月月更

聊聊编程中的 “魔数”

程序员鱼皮

我给公司用了这款工具,领导直接给我涨了两千工资

刘祥

后端技术 编程工具

vim 常用操作键tips

刁架构

xcode vim 快捷键

fastposter v2.6.0 发布 电商海报生成器

物有本末

fastposter 海报生成器 电商海报

有温度的人工智能,零门槛助力企业“鲤跃”智能化“龙门”

百度大脑

吉利控股集团与百度深化战略合作

百度大脑

尤达 DDD 领域驱动设计思想 第二章作业(重新划分SmartRM的子域和限界上下文)

代廉洁

尤达DDD领域驱动设计思想

Hoo虎符研究院| 稳定币的主要分类及发展趋势

区块链前沿News

虎符研究院 稳定币

敏捷小游戏的思考-上篇

LigaAI

团队管理 敏捷实践

有赞移动助手App本地抓包方案_移动_杨彬_InfoQ精选文章