昨天中午十一点左右,大量网友反应自己的 QQ 号被冻结,这个问题一度冲上知乎和微博热搜第一,很多人表示自己的生活受到了影响。具体的事态发展各位吃瓜群众可以自行了解,这里打算从技术角度分析一下这次故障的原因。
先给结论:这应该是一次第三方依赖库升级引发的。
这个第三方依赖库是一个名为 epic
的项目(地址在这里:https://github.com/tiann/epic);epic
是一个在虚拟机层面、以 Java Method 为粒度的 运行时 AOP Hook 框架。它可以拦截本进程内部几乎任意的 Java 方法调用,可用于实现 AOP 编程、运行时插桩、性能分析、安全审计等。
说到这里也许有童鞋会问:这 epic
听起来不是挺好吗,为啥会导致冻结?
技术是一把双刃剑。由于 epic
可以拦截本进程内几乎任意的方法调用,一旦以合适的方式加以利用,就可以创建上帝视角,控制任意 App 的运行。这个功能与 Android 上大名鼎鼎的 Xposed 框架本质上是一样的。
Xposed 框架可以在不修改应用安装包的情况下修改程序的运行(修改系统),基于它可以制作出许多功能强大的模块,且在功能不冲突的情况下同时运作。
比如说抢红包这功能,大部分都是借助 Xposed 框架通过模块实现的;这些功能看起来对普通人创造了便利,但是实际上滋生了很多阴暗的角落;就拿抢红包来说,有一些团体通过发红包来赌博,而如果有人通过 Xposed 框架干涉这个过程就相当于“出千”。若干年过去,大量灰黑产团体通过 Xposed 框架来干扰 App 的正常运行。因此 App 官方是非常讨厌 Xposed 框架存在的,它们通常会在用户手机上检测到 Xposed 框架存在之后,对用户进行账号冻结操作。
不过通常来说,使用 Xposed 框架的用户相对比较少,因此由于使用 Xposed 被封号的用户也是极少数。而这一次 QQ 更新之后,由于引入了错误的代码依赖,导致它在任何Android 手机上都认为检测到了 Xposed 框架,从而触发账户冻结。
QQ 在很早之前就引入了 epic
,当时应该是为了做线上性能检测。那个时候的 epic
只是为了做性能监控,因此 API 与 Xposed 并不兼容,里面的类基本上与Xposed 没有太大关联;QQ 集成这个库之后,其实就是引入了一个非常普通的依赖;我们看一下上个版本 QQ 的代码:
可以看到,QQ 对 epic
做了少许修改,部分类挪到了 com.qq.android.dexposed
下面;因此这个实际上没有什么影响。然后我们看一下新版QQ的变化:
可以看到,这个类被挪到了 de.robv.android.xposed
这个包下面;而这个包,就是 Xposed 框架所在地方。一般来说,App 进程如果发现有这个包存在,那么就可以认为是手机上装有 Xposed 框架,从而进行一系列接下来的动作。而 QQ 的这个行为,直接就是把 Xposed 所有的类放在了自己名下;因此它如果检测 Xposed
相关类,会 100% 判定为手机上装有 Xposed 框架:
由于 epic
可以实现 Xposed 的功能,因此再后来我通过它实现了一些类 Xposed 框架,为了兼容原来的框架,我对 epic
项目做出了一些调整:让它直接依赖了 Xposed 框架的相关类(具体commit见这里:https://github.com/tiann/epic/commit/dd5a10ddf2daa51a6724a943b73e87ae141fec82#diff-0d1d9100a9b6813b8cb5c470bf313d00)。在这一次调整之后,只要引入了 epic
,就会默认引入 Xposed 相关的类。因此,QQ 在更新epic
项目的版本之后,把 Xposed 相关的类也引入了 QQ。
经过对比分析两个版本 QQ 中 epic
的变化,可以进一步发现,是因为QQ更新了 epic
的版本,导致引入了 Xposed
相关的依赖,从而把所有 Xposed 相关的类集成到了QQ之中:
如上图,新版本的epic
默认引入了两个额外依赖:free_reflection
和Xposed
,进一步我们看到,新版本的QQ的确引入了我的另外一个库free_reflection
:
至此,我们的技术分析就结束了。本次 QQ 被冻结的悲剧,实际上是一次工程失误,开发团队在对第三方依赖进行升级的时候,引入了错误的、不合适的依赖库,从而导致了悲剧的发生。
这也告诫我们,在升级第三方依赖的时候一定要慎之又慎,千万不要盲目追新;很多童鞋有升级强迫症,看到有更新就想试,实际上这是非常危险的。在现实世界中,项目的稳定性一定是第一位,在升级依赖之前,一定要做好充分的评估。这次 QQ 升级引发的事故,必须引以为鉴。