写在前面的话
该文章其实在今年6月份就写出来了,目前wifi万能钥匙的接口可能和这个分析文章不一样,但运气好也有可能一样。很久都没有更新博客了,一是在甲方安全干活,真的是没有时间再研究研究这些有趣的东西,平时工作都忙成狗,真不知道这是好事还是坏事,另一方面是重新养成博客更新的好习惯,虽然之前在微博上挖了很多要分享的坑,但是真的是没时间搞,顿时感觉好蛋疼.....废话说了好多,还是看正文吧......
=====================================我是分割线==========================================================
逆向时间:2016年6月份
具体版本:忘了
该APP的部分代码被隐藏保护,主要是com.lantern.connect包下的几处Activity代码被隐藏。

上图灰色底即为部分隐藏的代码,尝试脱壳恢复代码未果,在现有APP基础上进行分析。
通过手机设置代理的方法,截获了几个APP与服务器端的通信数据包,请求报文一般会有以下固定的字段:
|
名称 |
说明 |
|
st |
m(固定值) |
|
et |
a(固定值) |
|
appId |
A0008(固定值) |
|
sign |
32位定长小写+数字字符串 |
|
ed |
不定长的大写字母+数字串 |
|
pid |
有范围的值 |

如上图就是一个功能请求报文,返回结果为:

返回结果中存在pwd字段,该字段被加密传输,背后有一套加密解密机制。
连续几个功能的报文全部是这种组包模式,于是推测主要请求数据包含在加密的ed字段中,因此重点关注了这个字段的生成机制。
Pwd字段在响应报文中是加密过的,所以在APP中必然存在解密代码。在Killer中搜索pwd关键字。看到pwd字段是通过两个so库进行处理:

这里的WkSecretKeyNativeNew和WkSecretKeyNative是java层负责和so层对接的类,分析so库难度过大,因此继续寻找看能不能找到java层的处理代码。
这里改变策略,通过外界分析,该pwd字段是有AES CBC加密处理的,因此,在Killer中搜索AES/CBC字符串,定位到加密解密处理:

可以看到是带iv的AES CBC加密,因此这里会有两个key,通过抓包分析,key 并没有存在于APP与服务器的通信过程中,因此key肯定是以硬编码存放在APP的某处代码中。
查看加密函数com.lantern.core.h的交叉引用,从而寻找key:

来到了com.lantern.core.b.c,看到了加密方法h.a的调用:

这里的两个key分别为v0_3.l()和v0_3.m(),查看这个v0_3对象的这两个方法(在com.lantern.core.i):

由于是类变量,因此该类中肯定会有提供给上层函数的设置类方法,不出所料:

继续查看这个a方法的交叉引用:

跟踪这个v0变量即可,发现是com.lantern.core.f.k类,点开一看,似乎是存放该APP所用到key的一个类:

找到了key,尝试直接用原生AES CBC解密,没有成功,于是转头又去看解密函数,发现万能钥匙APP在AES CBC之后又使用了一个自定义的加密函数com.lantern.wifilocating.push.c.a进行双重加密:

加入这个自定义函数之后,可以成功解出密码:
40762A32E15F0376DB6CFDE607A5DAD972C199EA8F018944BC17E1D198882F0C
这一串解出来是:0095315315311464359421419
密码的组成为:[密码长度][密码][时间戳]
因此这里解出明文pwd为:531531531。
Sign字段是该APP自己实现的一个签名机制,Killer全局搜索sign关键字,来到com.lantern.core.i类中:

跟进com.lantern.core.d.a方法:

可以总结处理过程如下:
(1)将各个字段放入一个map对象中
(2)对这个map对象先按照key进行一次排序,排序之后,将对应的value抽取出来放入一个StringBuilder中。
(3)将一个arg6当做salt加入至StringBuilder中,这里的arg6通过跟踪,其实还是在k类中,salt为:*Lm%qiOHVEedH3%A^uFFsZvFH9T8QAZe。
(4)随后调用了a方法,在jeb中点击没有反应,看了看这个a方法应该在父类中,jeb居然不给链接过去。
进入父类的这个a方法:

发现是个MD5处理,于是明朗了,这个sign实际上就是将key排序后,抽取对应的value组成一个string,在该string后面追加一个salt,然后进行md5得到的。
由于在交互报文中,并没有发现什么有实际内容的字段,因此这个长度很长的ed字段很关键,应该就是加密后的实际交互数据。
分析思路:
(1)搜索获取密码报文中的pid(00300109),定位到组装ed字段前身的map对象的代码部分。
(2)将这个ed字段进行解密,观察其结构组成。
思路1中,经过实践发现该部分代码被隐藏了,因此只能将截获数据包中的ed字段进行解密,对解密内容进行研究。确定了思路,来看分析:
定位代码在com.lantern.core.i中:

这个ed字段的来历是将某个map对象转为json串,先urlencode一遍,然后传入com.lantern.core.h.a方法中。
跟进该方法:

这里是加密的过程,可以看到使用了AES CBC加密之后,又经过了一个com.bluefay.b.c.a方法进行了一次加密,这个方法如下:

所以要写出这个自定义加密函数的解密函数。经过分析,这个类中携带了对应的解密函数:

有了解密函数就可以对ed字段进行解密,过程如下:
(1)首先自定义函数解密一次
(2)然后经过AES CBC解密一次获取明文
通过上述方法就可以获取ed字段的明文信息。
Wifi万能钥匙中需要获取周边位置的AP信息,这里使用Android中的WifiManager对象进行实现。该对象中有一个getScanResults方法,这个方法返回一个ScanResult对象的列表。调用该方法,可以获取周围AP的信息。
ScanResult对象中的几个重要属性:
(1)capabilities
描述AP的认证、密钥管理和加密机制
(2)level
信号强度,即rssi,得到的值是一个0到-100的区间值,是一个int型数据,其中0到-50表示信号最好,-50到-70表示信号偏差,小于-70表示最差,有可能连接不上或者掉线,一般Wifi已断则值为-200。
(3)SSID
(4)BSSID
基本查询顺序:
(1)首先扫描周边的wifi信息,获取ssid和bssid(可以不需要经纬度信息)
(2)根据ssid和bssid去服务器查询结果
因此,批量查询是不可能实现的(APP上没有接口,接口应该在服务器数据库交互中)。
因此,获取明文的步骤如下所述:
1、首先要先获取使用者所处环境中的AP信息
这个步骤对应APP界面上的"一键查询万能钥匙"按钮功能,整理周边环境的AP信息,包括ssid,bssid,securityLevel,rssi(信号强度)。
分别对各个字段进行解析:
(1)ssid:AP的ssid
(2)bssid:AP的bssid
(3)securityLevel:WEP=1,PSK=2,EAP=3,other=0(从ScanResult的capabilities获取)
(4)rssi:AP的信号强度,RSSI是信号强弱,得到的值是一个0到-100的区间值,是一个int型数据,其中0到-50表示信号最好,-50到-70表示信号偏差,小于-70表示最差,有可能连接不上或者掉线,一般Wifi已断则值为-200。
ed字段的原始json对象(这个对象先urlencode再加密)。
通信过程:
(1)构造出ed字段的原始明文map对象,然后组包即可,组包时的字段有:
st,et,pid(00300108:00300111:003001100:0300901),appId,ed,sign
详细构造可以看test.all包里的Main.java,只需要把ed对象urlencode一次,填入raw_ed变量里,运行该文件即可。
发包时,一定注意带上content-length,否则会报错。
(2)获取返回值
2、获取pwd并解密
根据第一步中获取的返回值,可以获取到有密码的ap信息,拿着这些信息,可以查询到对应的密码。
通信过程:
(1)发送报文
ed字段的原始对象如下,主要填入要查询的bssid和ssid。
组包:
st,et,pid(00300109),appId,ed,sign
(2)获取返回结果
这里的pwd就是获取的对应ap的密码,以apRefId识别。
(3)解密pwd
丢进解密函数里解密,获取到明文,完成一次查询。
============================================EOF============================================================
以上就是6月份的wifi万能钥匙的流程,分析清楚这个过程,实际上是可以实现一个走到哪日到哪的wifi hacking客户端的。
实际上,使用XPosed框架进行编写会更加简洁。
PS. 文章里删了一些返回JSON结构的内容,但我觉得也够详细了,如果你不懒,完全可以借鉴这个思路来过一遍最新版的。