Wifi万能钥匙原理与逆向分析

写在前面的话

该文章其实在今年6月份就写出来了,目前wifi万能钥匙的接口可能和这个分析文章不一样,但运气好也有可能一样。很久都没有更新博客了,一是在甲方安全干活,真的是没有时间再研究研究这些有趣的东西,平时工作都忙成狗,真不知道这是好事还是坏事,另一方面是重新养成博客更新的好习惯,虽然之前在微博上挖了很多要分享的坑,但是真的是没时间搞,顿时感觉好蛋疼.....废话说了好多,还是看正文吧......

=====================================我是分割线==========================================================

逆向时间:2016年6月份

具体版本:忘了

(一)Wifi APP大致结构情况

该APP的部分代码被隐藏保护,主要是com.lantern.connect包下的几处Activity代码被隐藏。


上图灰色底即为部分隐藏的代码,尝试脱壳恢复代码未果,在现有APP基础上进行分析。

(二)通信特征

通过手机设置代理的方法,截获了几个APP与服务器端的通信数据包,请求报文一般会有以下固定的字段:

名称

说明

st

m(固定值)

et

a(固定值)

appId

A0008(固定值)

sign

32位定长小写+数字字符串

ed

不定长的大写字母+数字串

pid

有范围的值


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


返回结果中存在pwd字段,该字段被加密传输,背后有一套加密解密机制。

连续几个功能的报文全部是这种组包模式,于是推测主要请求数据包含在加密的ed字段中,因此重点关注了这个字段的生成机制。

 

(三)pwd解密机制

Pwd字段在响应报文中是加密过的,所以在APP中必然存在解密代码。在Killer中搜索pwd关键字。看到pwd字段是通过两个so库进行处理:


这里的WkSecretKeyNativeNewWkSecretKeyNativejava层负责和so层对接的类,分析so库难度过大,因此继续寻找看能不能找到java层的处理代码。

这里改变策略,通过外界分析,该pwd字段是有AES CBC加密处理的,因此,在Killer中搜索AES/CBC字符串,定位到加密解密处理:

 

可以看到是带ivAES 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解密,没有成功,于是转头又去看解密函数,发现万能钥匙APPAES CBC之后又使用了一个自定义的加密函数com.lantern.wifilocating.push.c.a进行双重加密:


加入这个自定义函数之后,可以成功解出密码:

40762A32E15F0376DB6CFDE607A5DAD972C199EA8F018944BC17E1D198882F0C

这一串解出来是:0095315315311464359421419

密码的组成为:[密码长度][密码][时间戳]

因此这里解出明文pwd为:531531531

(四)sign的生成(签名机制)

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字段的生成(数据主体)

由于在交互报文中,并没有发现什么有实际内容的字段,因此这个长度很长的ed字段很关键,应该就是加密后的实际交互数据。

分析思路:

(1)搜索获取密码报文中的pid00300109),定位到组装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万能钥匙通信流程分析

6.1 AP扫描基础

Wifi万能钥匙中需要获取周边位置的AP信息,这里使用Android中的WifiManager对象进行实现。该对象中有一个getScanResults方法,这个方法返回一个ScanResult对象的列表。调用该方法,可以获取周围AP的信息。

ScanResult对象中的几个重要属性:

1capabilities

描述AP的认证、密钥管理和加密机制

2level

信号强度,即rssi,得到的值是一个0-100的区间值,是一个int型数据,其中0-50表示信号最好,-50-70表示信号偏差,小于-70表示最差,有可能连接不上或者掉线,一般Wifi已断则值为-200

(3)SSID

(4)BSSID

 

6.2 流程分析

基本查询顺序:

1)首先扫描周边的wifi信息,获取ssidbssid(可以不需要经纬度信息)

2)根据ssidbssid去服务器查询结果

因此,批量查询是不可能实现的APP上没有接口,接口应该在服务器数据库交互中)

因此,获取明文的步骤如下所述:

1、首先要先获取使用者所处环境中的AP信息

这个步骤对应APP界面上的"一键查询万能钥匙"按钮功能,整理周边环境的AP信息,包括ssidbssidsecurityLevelrssi(信号强度)。

分别对各个字段进行解析:

1ssidAPssid

2bssidAPbssid

3securityLevelWEP=1PSK=2EAP=3other=0(从ScanResultcapabilities获取)

4rssiAP的信号强度,RSSI是信号强弱,得到的值是一个0-100的区间值,是一个int型数据,其中0-50表示信号最好,-50-70表示信号偏差,小于-70表示最差,有可能连接不上或者掉线,一般Wifi已断则值为-200

ed字段的原始json对象(这个对象先urlencode再加密)。

通信过程:

1)构造出ed字段的原始明文map对象,然后组包即可,组包时的字段有:

stetpid00300108:00300111:003001100:0300901),appIdedsign

详细构造可以看test.all包里的Main.java,只需要把ed对象urlencode一次,填入raw_ed变量里,运行该文件即可。

发包时,一定注意带上content-length,否则会报错。

2)获取返回值

 

2、获取pwd并解密

根据第一步中获取的返回值,可以获取到有密码的ap信息,拿着这些信息,可以查询到对应的密码。

通信过程:

1)发送报文

ed字段的原始对象如下,主要填入要查询的bssidssid

组包:

stetpid00300109),appIdedsign

2)获取返回结果

这里的pwd就是获取的对应ap的密码,以apRefId识别。

3)解密pwd

丢进解密函数里解密,获取到明文,完成一次查询。


============================================EOF============================================================

以上就是6月份的wifi万能钥匙的流程,分析清楚这个过程,实际上是可以实现一个走到哪日到哪的wifi hacking客户端的。

实际上,使用XPosed框架进行编写会更加简洁。

PS. 文章里删了一些返回JSON结构的内容,但我觉得也够详细了,如果你不懒,完全可以借鉴这个思路来过一遍最新版的。