使用CodeInspect破解WiFi万能钥匙接口协议

在上篇文章中我已经使用CodeInspect对WiFi万能钥匙进行了演示分析,在大概分析了下它的代码后,我发现这款APP并没有做什么加固措施,导致接口很容易就可以被分析出来。事实上在去年网上就有针对WiFi万能钥匙接口协议的分析,在经过多次版本迭代后,新版的Wifi万能钥匙在安全性上似乎并没有太大的提升,虽然接口有一些变化,但是核心的加密算法却没有改变并且很容易被逆向。凭借CodeInspect,我很快的就分析出了它的代码逻辑,并且写了一个查询脚本,在文章后面会提到。

功能分析

我们首先来看一下Wifi万能钥匙的主界面,可以看到主界面有一个一键查询万能钥匙的按钮,这个按钮的功能是扫描附近的Wifi,根据Wifi信息找出服务器上存在密码的Wifi。如果服务器上有这个Wifi的密码在这个Wifi一栏的后面显示一个蓝色的小钥匙,如下图中的”360免费WiFi-10”,这个Wifi是我故意上传密码来测试接口的。

但是这个查找过程并没有返回密码到客户端,只是标记了可以连接的Wifi,真正根据Wifi的SSID和BSSID获取密码的过程是在点击了标记了的Wifi后,再点击”钥匙连接”,并且”钥匙连接”这个选项只有被标记了的Wifi有,没有被蓝色钥匙标记的Wifi只有密码连接的选项。

我们的需求是输入Wifi的SSID和BSSID就能查询到Wifi的密码,所以可以知道我们要找的关键代码就在点击了这个”钥匙连接”后的过程中。通过配置好网络代理后(这个网上已经有很多教程了),我们就可以抓包分析它的接口。在点击”钥匙连接”后,可以看到APP首先向”http://ap.51y5.net/ap/fa.sec“ 提交了这样一段数据:

 

我们注意到这段数据中并没有明文包含需要查询的Wifi的BSSID和SSID,并且ed这个字段的数据特别大,所以可以猜测Wifi的信息被加密成ed这个字段。知道这个后我们需要定位到关键代码,也就是这段数据生成的地方,方法有很多种,我是通过搜索字符串’sign’然后定位到了’com.lantern.core.i’这个类的b方法。

i这个类中有几个方法都和b方法相似,那么到底哪个才是查询密码的时候调用的呢,这就需要用到动态调试,使用CodeInspect对这几个方法下断,然后逐一排除最终确定了b方法就是查询密码所使用的方法。分析这个方法我们可以看到ed这个字段是WkSecretKeyNative.a方法返回的,其中第一个参数是一个Json对象转成的字符串,v1和v0是有k.b()返回的固定字段,分别是下图b()方法中的的b和c。

所以可以确定的是Wifi的信息都被保存在第一个参数中,也就是一段Json数据中,那么我们是不是需要继续逆向找出这段Json数据的生成代码呢?那样做就太麻烦了,可以直接通过动态调试找到这段Json。如下图,需要注意的是在这个方法下断后,Debug时会多次断在这个地方,有的是其他代码调用了这个方法,所以传入的数据不是我们需要找的数据,一定要在点击”钥匙连接”后,弹出的”正在连接”的窗口出现并且一直等待的时候才是我们要找的数据。

将这段数据复制出来,然后格式化一下,像下面这样:


 

我们主要关注的就是上面字段中的”bssid”和”ssid”,这两个就是我们要查找wifi的密码的标识。其他字段都可以就用上面的值,另外”dhid”这个字段是一个标识,当查询过多这个值会被服务器封锁导致无法使用,所以需要重新生成一个值,这里先不考虑这种情况。
获得上面的数据后我们的工作就是来分析ed这个字段是怎么生成的了。继续跟踪代码,可以看到最终调用了so层的ep方法,这个方法在”libwkcore.so”中。

使用IDA打开这个so,可以发现并没有做加密,代码的逻辑也很简单,主要就是将传入的第2,3个字符串解密,然后作为key和IV和第1个字符串也就是传入的Json数据做AES加密。

这里的process_str函数主要用来将前面传入的第2,3个字符串也就是”FciCx&q6E!I50#LSSC”和”C474pXF$t%s%12#2bB”解密,可以看到这两个字符串都是18位的那么解密后应该是16位的。所以我们需要知道解密后的字符串,可以逆向分析这个函数,但是那样其实麻烦了,可以直接通过动态调试直接获得加密后的字符串,因为这个so并没有任何加固处理,所以F5后的代码非常清晰,我这里直接把这部分解密代码拷贝出来然后编译,得到了AES加密用的key和IV,这种方法也是在逆向算法时常用的方法。加密代码如下:


 

编译执行后得到AES用到的key和IV分别为”!I50#LSSciCx&q6E”和”$t%s%12#2b474pXF”。有了这些数据,我们就可以编写python脚本通过ssid和bssid查询密码了,得到ed字段的代码如下:


 

但是只改变ed字段,其他字段使用抓包得到的数据提交,会返回如下信息:

很明显是提交字段中的sign字段不正确,继续分析com.lantern.core.i的b函数,可以看到sign是调用com.lantern.core.d的b方法,传入的参数是一个HashMap也就是保存提交数据的HashMap和一个固定字符串,这个字符串也是k.b()方法返回的,值为”CedH3%A^uFFsZvFH9T8QAZe*Lm%qiOHVEB”。

d.b方法主要就是将传入的HashMap排序然后将所有的value取出来组成一个字符串然后传入到WkSecretKeyNative的a方法中。

a方法调用native层的md方法,获得一个字节数组,然后通过位运算将字节数组转换成一个字符数组。通过md这个方法的名字我们可以猜测这个方法是求md5的值,使用IDA打开so后可以看到。

打开getDigestedBytes这个方法可以发现这个方法是调用了Java层的MessageDigest类来求MD5的值。

md方法中同样使用了process_str方法来解密字符串,使用前面的方法,获得解密的字符串为”*Lm%qiOHVEedH3%A^uFFsZvFH9T8QAZe”。解密后md方法将这个字符串拼接在第1个字符串的后面然后求MD5。

获得签名的python代码如下:


 

知道了这些后我们就可以向服务器提交数据并且返回正确的数据了。如下图:

很明显,返回的Json数据中的pwd字段就是密码,但是是加过密的,要得到明文密码我们还需要对这个密码进行解密。通过搜索”pwd”字符,我们可以找到关于密码的关信息息。在com.wifi.connect.d.a的a方法,这里将获取到的明文密码加密然后放到一个HashMap中,可以猜测这个是用来备份密码上传到服务器的,这里上传到服务器上的密码是调用WkSecretKeyNative这个类加密的,所以要想解密密码,只需要找到这个类是怎么加密密码的就可以了。

打开这个方法后我们发现它最终调用的加密函数和加密ed字段的函数一样,并且传入的key和IV也是一样的,所以就不用分析了,直接使用前面加密ed字段的对象解密就可以了。


 

最后运行脚本,就可以获得明文密码了,如果需要查询其他Wifi密码只需要将ed字段中的ssid和bssid修改成要查询的wifi的就可以了。完整的脚本可以在我的github上找到,因为时间关系,没有考虑到dhid,并且代码本身也没有怎么优化,以后有空了再更新吧。

 

转载自:http://zke1ev3n.me/2016/04/06/WiFi%E4%B8%87%E8%83%BD%E9%92%A5%E5%8C%99%E6%8E%A5%E5%8F%A3%E5%8D%8F%E8%AE%AE%E7%A0%B4%E8%A7%A3/

原文标题:WiFi万能钥匙接口协议破解

原文作者:zke1ev3n