隐私安全是一个老生常谈的话题,微信作为IM工具的领导者承担了几亿用户的信息交换,因此保护微信内容尤为重要。本文将从为微信增加“密友”功能的角度,阐述如何Hack微信,并且打造出真正符合用户需求的安全产品。
毫无疑问,微信已成为人们互相沟通、传播消息的主流手段之一,因此保护微信聊天记录的重要性越加明显。本文将在不修改原始微信应用的前提下,介绍如何为微信增加隐藏好友聊天记录的功能及其技术细节和遇到的挑战。
让隐私保护用在实处
Android手机用户对各大主流安全软件的“私密联系人”功能十分熟悉,这个功能可以把一些联系人的通话记录、短信内容保存在一个特殊的地方,输入密码才能看到。但通过观察不难发现,这个功能十分鸡肋。原因在于,用户每天使用传统短信应用发消息的数量少之又少,而使用微信发送的消息则多而又多。在为数不多的使用传统短信的情况下,有多少是具有高度私密性需求的呢?因此,私密联系人的想法是好的,但用错了地方。
前段时间上映的电影《北京爱情故事》想必很多人都看过,王学兵饰演的出轨男吴峥以为把小三的名字改成“孙总”就万事大吉了,错!暧昧短信、大尺度视频都赤裸裸地躺在微信聊天记录里。这年头,治标不治本是不行的。鲶鱼团队(由对安全感兴趣的各个公司的工程师,自发组成的移动互联网安全研究非商业兴趣小组)在某次集体观摩了该电影后,大受启发,突发奇想,如果把“私密联系人”功能用于微信,让用户可以随时隐藏微信好友聊天记录,让保护隐私功能真真正正地被用在用户有强烈需求的地方不是更好吗?对于青少年,不再担心微信中的聊天记录被父母发现;对于恋人,不再郁闷一言一行都被对方检查;对于一个社会关系“复杂”的商务人士,他的商业机密不会被竞争对手窥探,他的“私人生活”不会被家中老小知晓;对于一个普通人,他终于可以在别人借手机时,不用担心别人“无意”看到他跟爱人的甜蜜聊天了。因此,保护微信聊天记录,建立专属自己的“微信密友”,大有必要!
只是一个应用
“微信密友”功能由两部分组成:鲶鱼汇应用和微信应用。这里需要强调一点:密友功能绝不会修改微信应用,因此,我们提供的apk仅仅是“鲶鱼汇”而已,用户可以通过各种应用市场或官网下载微信应用(如图1所示)。
鲶鱼汇应用完成两个功能:1. 开启/关闭微信密友功能;2. 自动静默关注微信公众号“鲶鱼”。
图1 鲶鱼汇应用
启动微信密友功能后,微信中即可拥有以下所有功能:
1. 密友的隐藏与显示;2. 设置密友名单;3. 根据口令进入“显示密友模式”;4. 摇一摇退出“显示密友模式”;5. 用户自定义口令。
功能演示
进入微信好友聊天界面→长按某个好友会话列表→选择“隐藏该聊天”→摇一摇,被选择成隐藏的聊天记录就从界面上消失了。如图2所示。
图2 微信密友演示
技术也性感
也许一部分读者的第一反应是:这是修改了微信应用吗?这里我可以给出一个明确的答复:没有!这款微信密友功能并不会修改微信原始应用,也不是一个插件。用户仅仅需要下载一个鲶鱼汇应用,在其中开启微信密友功能即可。微信密友功能是在对微信内部编码逻辑有了一定的了解的基础上实施起来的。如何了解微信的内部构造?答案是对微信应用进行逆向分析。这里需要读者对逆向分析、Java的Hook技术和注入技术有一定的了解。逆向分析本质上就是对一个apk进行反编译并分析其代码逻辑。Inject即为注入,是指攻击者将自己编写的一段代码强行运行在另一个进程中的行为;Hook即为钩子技术,通过在关键函数中插入判断代码,借此控制此关键函数的运行流程。
密友的隐藏与显示
从根出发:熟悉Android的读者自然会联想到,会话列表中某些会话的消失和出现对于UI层来说就是ListView中某些Item的无和有(或是不可见和可见)。这就是整个思路线索的根。
顺藤摸瓜:有了根,就要抽丝剥茧出一条有价值的线索。ListView→ Adapter→Cursor→ContentResolver→ContentProvider→SQLiteDatabase,在UI上体现出来的某些ListView的Item有和无,可通过在上述这条线索上的任何一点进行技术处理来达到。下面一一介绍。
图3 隐藏方案线索
方案一:修改ListView,将要隐藏的Item设置成GONE,如图3绿色部分。
方案二:修改Adapter,对Cursor行记录进行筛选,过滤某些行;修改Cursor,对查询到的Cursor进行包装或修改,过滤Cursor中的某些数据,如图3蓝色部分。
方案三:修改调用Content-Reso
踩点安钩:上述的三种方案都可以实现,到底在哪里做手脚比较合适?通过逆向分析微信应用,我们发现一个“讨巧”的地方:微信的Adapter中存在一个方法func(List list),该方法的参数是一个String类型的List,List中的元素为微信好友唯一标示的username。该方法的作用是设置黑名单,使List中的好友不显示在好友对话列表中。举个例子:逆向分析微信得到的这个List中原本只有一个元素,就是“floatbottle”(漂流瓶),也就是说大家的好友都包括“漂流瓶”,只不过微信把它隐藏了而已。有了这个巧妙的函数,给微信设置黑名单就轻而易举了。
表1 三种方案局限性对照
精确勿差:下面给出思路梳理,如图4所示。1. 通过工具(比如monitor等)找到微信好友会话界面的MainUI activity;2. 通过该MainUI activity对象,拿到其中使用的ListView对象;3. 通过ListView对象拿到其中的Adapter对象;4. 有了Adapter,可根据Adapter中使用的某些方法,比如setCursor()等跟踪到Cursor对象;5. 跟踪Cursor对象从哪里被query出来;6. 通过query()找到对应的SELECTION语句。
图4 流程梳理
因地制宜,具体问题具体分析
上面给出了理论上的思路和逻辑分析方法,结合反编译微信得到的有用信息,我们决定通过微信本身提供的Adapter中设置黑名单方法func(List list)来设置密友名单。利用这个方法func,可以将微信密友名单作为黑名单设置进去,再触发更新Cursor,更新Adapter,最后触发ListView的重绘,从而更新UI,使密友会话在UI界面不显示。过程如图5所示。同理,将密友名单从黑名单List中删除,触发更新UI,密友即可显示。
图5 隐藏密友操作原理
设置密友名单
设置密友操作如下:长按某个好友会话,在弹出的菜单中单击“隐藏该聊天”,则该好友被设定为密友。在退出“显示密友模式”后,该密友聊天会被隐藏。
这里涉及三个修改:1. 修改会话列表中某个item的长按点击事件;2. 修改菜单(长按item后弹出的菜单,需要为其新添加一个“隐藏该聊天”选项);3. 为菜单中新添加的“隐藏该聊天”选项添加响应事件。使用Hook技术中替换OnItem-LongClickListener对象的方法即可。
进入“显示密友模式”
密友聊天记录是用户私密的信息,通常情况下是不会显示在微信聊天首页的。所以若想跟密友进行聊天,需要凭借用户设定的口令进入“显示密友模式”,做法是:在微信会话页面的搜索框中输入口令(默认口令为“菠萝菠萝蜜”,用户可根据设置口令规则自定义口令,下文有介绍)后自动进入“显示密友模式”。这一步的原理在于:拿到微信会话页面搜索框中输入文字EditText对象,为该对象添加一个TextWatcher,当EditText中的内容有任何改变时,TextWatcher中的方法会被调用,在该方法中可以对EditText内容进行匹配,若为口令,则启动显示密友模式。
退出“显示密友模式”
退出“显示密友模式”需要快、隐、稳,也就是用户跟密友聊天完了以后,可以快速地、神不知鬼不觉地让聊天记录消失。我们特别使用了“摇一摇”来完成退出“显示密友模式”。目的何在?原因就在于微信的“摇一摇”深入人心,人尽皆知!“摇一摇”的功能是基于加速度感应器,通过上报X、Y、Z三轴的加速度来实现功能。刚开始简单地实现为判断某个轴的加速度是否达到阈值,结果发现效果很不理想。最大的原因是不同型号手机的感应器灵敏度都不一样,这个阈值就很难设定了。还有更坑的是某米手机,同一个型号的灵敏度差别都非常大。转念去试了一下微信的“摇一摇”,结果每个手机体验都非常棒,而且只有在倾斜45度角上下摇之外,其他手势一般都不容易触发。后来又试了其他几个比较热的App的“摇一摇”,不比不知道,原来微信的“摇一摇”真的是用户体验最好的。
于是就地取材,逆向了微信的算法,每次计算是对X、Y、Z轴有个加权因子修正。Y轴权重最大,阈值也不是固定的,而是根据手机灵敏度在一个范围内动态调整,由于我们不需要这么灵敏,所以对阈值稍许做了调整。
保证了算法,接下来的事情就水到渠成了。在微信MainUI activity的onStart()和onStop()中添加SensorEventListener,监视手机“摇一摇”状态,上手体验果然非常好。当微信处于前台且手机摇一摇触发时,根据新的黑名单触发刷新UI,隐藏密友会话列表。
如何设置口令
这是一款保护用户隐私的应用,必须要让用户能自定义口令进入显示密友模式。我们的想法是一切都在微信中搞定,不用再到鲶鱼汇中设置。头脑风暴后,我们想到通过一个公众号发消息的入口设置。方法是在微信公众号“鲶鱼”中输入“口令#旧口令#新口令”,可重置口令。初口令为“菠萝菠萝蜜”。实现原理是:拿到EditText对象和“发送”按钮对象,Hook按钮对象的onClickListener方法,在方法中,判断EditText内容,若内容与设置口令模板匹配,则保存新口令,并将该设置口令语句删除。现在引入了新的问题,如果让用户搜索并关注这个公众号实在麻烦,更麻烦的是微信允许同名,筛选更痛苦。所以最好是开启微信密友,就自动关注公众号“鲶鱼”,如何做到呢?
1. 启动关注页面activity。首先找到微信账号关注页面的activity,鲶鱼汇应用通过startActivity来启动该页面。在intent中将公众号“鲶鱼”的信息填入其中,包括“鲶鱼”在微信server端固定的一串标识,“鲶鱼”的简介文字等。也就是鲶鱼汇应用构造出一个微信应用在启动它的关注页面activity时需要用到的一模一样的intent,并用它去startActivity启动关注账号页面。
2. 静默实现关注。关注页面下方此时为绿色“关注”按钮,可通过静默调用该按钮的onClick事件,达到“关注”的效果。
3. 使该activity不可见。在关注界面activity的onResume方法中调用finish()方法将自己结束掉。该activity并不会显示在前台,用户自然感知不到关注页面。
戴着镣铐跳舞
Hack App犹如戴着镣铐在跳舞,各种限制会阻碍Hack的进展、产品的通用性、适配性等。要想尽一切办法,让舞蹈跳得精彩动人!
如何克服混淆带来的困难
微信应用的代码经过混淆,这是毋庸置疑的。大版本的升级,同一个版本的不同编译版本等,都会造成相同的方法/类的混淆名不一样。如果在注入代码中用了混淆名,基本上可以宣告失败了,即便是一个小版本更新,注入代码就很可能无法工作了。因此,微信密友功能的最大难点在于跟混淆名斗争,适配不同微信版本,也就是尽可能地在各种不一样的混淆名中找出线索,做到自适应。比如我明明知道美女在哪里,就是不能直接搭讪,一定要通过熟人介绍。下面是我总结的小技巧,可供参考。
尽量使用未被混淆的类名/方法名/变量名等。比如通过AndroidMenifest.xml的四大组件方法入口;通过SDK API的调用、继承、实现等;通过资源寻找相关代码,如利用monitor工具等)。
通过特征找到混淆的类名/方法名/变量名。例如,假设有一个View中有方法为setAdapter,那么可以考虑这个View是否为ListView。
即便是这样,也依然无法100%解决问题,戴着镣铐跳舞大多还是痛苦的。
如何避免重复注入
微信密友涉及到两个进程:鲶鱼进程和微信进程(如图6所示)。鲶鱼进程在启动阶段会将一段代码(dex文件)注入到微信进程中,而重复注入导致微信加载多次dex文件,致使系统功能异常。因此,如何保证这两个进程任意一方挂掉并重启后,不会重复注入微信进程?
图6 鲶鱼进程与微信进程
解决方案是在鲶鱼进程的SharedPreferences保存上次注入微信的进程ID,同时互相传递Binder。通过linkToDeath监听对方进程挂掉的消息。鲶鱼挂掉,有两种情况:进程异常或卸载。如果是进程异常,则重启后读取SharedPreferences保存的上次注入微信的进程ID;如果进程ID没有变化,则不注入。如果是卸载,微信进程则会重启自己,意味着微信密友功能消失;当微信进程挂掉后,鲶鱼进程则会监听微信进程启动,重新注入,然后保存新的进程ID到本地的SharedPreferences。
如何解决异步加载问题
首次进入微信聊天页面在activity的onPost-Resume()中,需要拿到该sctivity对象中使用的ListView对象,但ListView是异步加载的,即onPostResume()运行完成时ListView对象仍没有生成。解决方案是:将寻找ListView对象的逻辑写成一个Runnable,发现找不到ListView对象时就再Post自己,直到找到或条件不满足时则结束。
总结
IM聊天工具的出现,迅速地取代了传统的短信、电话和邮件,这些社交工具中的聊天消息承载了从生活、情感、金融、商务等各方面的个人重要信息,而这些信息在网络大爆炸的当下,是极为不安定的定时炸弹。当各家安全软件还在着眼于诸如私密通讯录等功能时,相信微信密友能更贴近人们的社交圈,更好地保护个人隐私。
|