WiFi万能钥匙蹭网原理详细剖析

路人甲 2015-02-26 17:00:00

0x00 wifi万能钥匙究竟有没有获取root之后偷偷上传密码?


本次测试版本号为3.2.3,首先通过可疑shell语句定位到疑问的问题代码:类名com.snda.wifilocating.f.ba

这段代码的作用是在有了root权限的情况下 将系统的wifi.conf拷贝出来到应用自己的目录,并赋予其全局可读写权限(其实这是个漏洞了...)。

对其做cross-ref查找引用之后可以发现,该函数主要在两个地方被直接调用。一个是com.snda.wifilocating.e.av:

这是一个api接口,主要功能是用于用户注册了之后备份自己的ap密码,同时在WpaConfUploadActivity直接调用、GetBackupActivity中间接调用。第一个Activity在分析的版本中已经被从AndroidManifest中删除,而第二个Activity则是用户备份私有wifi时的对应的界面。这证实了备份的时候密码确实会被上传,而且从下文来看这个密码是完全可逆的。

不过在使用过程中,该应用并没有其他可疑的root行为操作。笔者打开了SuperSu的root执行监控,短暂的使用过程中也只发现了执行了上述的这一条命令。

0x01 Android系统Wifi连接API概述


Android系统通过WifiManager类来提供对Wifi的扫描、连接接口。应用在请求相应权限之后可以扫描、连接、断开无线等。在连接无线功能中,客户端基本上只要指定SSID,Pre-shared-key(即密码),就可以用代码的方式连接无线。连接一个WPA(2)无线典型代码如下,

wifiConfiguration.SSID = "\"" + networkSSID + "\"";
wifiConfiguration.preSharedKey = "\"" + networkPass + "\"";
wifiConfiguration.hiddenSSID = true;
wifiConfiguration.status = WifiConfiguration.Status.ENABLED;
wifiConfiguration.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP);
wifiConfiguration.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP);
wifiConfiguration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
wifiConfiguration.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.TKIP);
wifiConfiguration.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP);
wifiConfiguration.allowedProtocols.set(WifiConfiguration.Protocol.RSN);
wifiConfiguration.allowedProtocols.set(WifiConfiguration.Protocol.WPA);

int res = wifiManager.addNetwork(wifiConfiguration);
Log.d(TAG, "### add Network returned " + res);

0x02 wifi万能钥匙是怎么连接上无线的,密码从哪里来?


这也是争议较大的地方,首先该应用肯定是有云端存储了很多密码,因为应用会引导用户备份自己的密码,但这些密码有没有被滥用我们在客户端就不得而知了。在2月底的这次测试中,笔者先私有备份了自己建立的测试无线(注意不是分享),然后使用另外一个手机安装该客户端测试,该客户端的API请求接口并没有返回这个测试的无线的密码。不过这也可能只是个例说明不了什么,还是建议各位自行测试,但注意测试前清除保存的无线并给测试无线设定一个弱密码以免真的泄露了自己的密码。

无线密码获取分析


回到正题,笔者通过代理拦截到了该应用获取wifi密码的请求。应用发送目标的ssid,mac信息向云端做查询,获取到的密码到本地之后并不是明文的,而是一个AES加密。首先为了证明其在本地最终还是会以明文出现,先取了个巧,没有去逆这个算法(虽然逆下也不会很困难),而是直接hook了系统添加无线的代码(回忆上文里密码就在NetworkConfiguration.preSharedKey里)。

部分HOOK代码:

            Class wifimgr = XposedHelpers.findClass(
                    "android.net.wifi.WifiManager",
                    lpparam.classLoader);
            XposedBridge.hookAllMethods(wifimgr, "addNetwork",
                    new XC_MethodHook() {

                        @Override
                        protected void beforeHookedMethod(MethodHookParam param)
                                throws Throwable {
                            WifiConfiguration configuration = (WifiConfiguration) param.args[0];
                            if(configuration.preSharedKey != null)
                            {

                                Log.e("FUCKFUCK", "psk: "+configuration.preSharedKey);
                            }
                        }
                    });

            XposedBridge.hookAllMethods(wifimgr, "updateNetwork",
                    new XC_MethodHook() {

                        @Override
                        protected void beforeHookedMethod(MethodHookParam param)
                                throws Throwable {
                            WifiConfiguration configuration = (WifiConfiguration) param.args[0];
                            if(configuration.preSharedKey != null)
                            {

                                Log.e("FUCKFUCK", "psk: "+configuration.preSharedKey);
                            }
                        }
                    });
            }

这是一个万能钥匙上传wifi ssid以及mac以请求密码的截图:

响应截图:

密码以AES可逆加密的形式通过pwd这个json key传递了回来。

同时,在其尝试通过这个密码连接目标无线的时候,本地hook模块也获取到了真实的明文密码:

个人备份分析


而个人备份模块,也就是直接会读取wifi.conf的模块,是通过findprivateap和saveprivateap这两个json api method进行,具体的http请求逻辑在com.snda.wifilocating.e.av中可以找到,这个类也基本上囊括了所有万能钥匙的api请求逻辑。

备份时的请求:把整个wifi.conf全部上传了上去。

而恢复备份时,只是将密码从云端拖了下来。

其他连接方式分析


除此之外,Wifi万能钥匙还自带了2000条的数据库记录在ap8.db中,记录了常见的弱密码。

这些密码用在所谓的“深度连接”功能中,其实按代码逻辑来看就是一个wifi密码爆破,每次在字典中尝试10个密码。看下logcat就很明显。

I/wpa_supplicant( 884): wlan0: WPA: 4-Way Handshake failed - pre-shared key may be incorrect
I/wpa_supplicant( 884): wlan0: CTRL-EVENT-SSID-TEMP-DISABLED id=1 ssid="aaaaaaaaa" auth_failures=2 duration=20
D/SupplicantStateTracker( 818): Failed to authenticate, disabling network 1
I/wpa_supplicant( 884): wlan0: CTRL-EVENT-SSID-REENABLED id=1 ssid="aaaaaaaaa"
I/wpa_supplicant( 884): wlan0: Trying to associate with 5c:a4:8a:4d:09:a0 (SSID='aaaaaaaaa' freq=2412 MHz)
I/wpa_supplicant( 884): wlan0: Associated with 5c:a4:8a:4d:09:a0
I/wpa_supplicant( 884): wlan0: CTRL-EVENT-DISCONNECTED bssid=5c:a4:8a:4d:09:a0 reason=23
I/wpa_supplicant( 884): wlan0: CTRL-EVENT-SSID-TEMP-DISABLED id=1 ssid="aaaaaaaaa" auth_failures=1 duration=10
I/wpa_supplicant( 884): wlan0: WPA: 4-Way Handshake failed - pre-shared key may be incorrect
I/wpa_supplicant( 884): wlan0: CTRL-EVENT-SSID-TEMP-DISABLED id=1 ssid="aaaaaaaaa" auth_failures=2 duration=20
I/wpa_supplicant( 884): wlan0: CTRL-EVENT-SSID-REENABLED id=1 ssid="aaaaaaaaa"
I/wpa_supplicant( 884): wlan0: Trying to associate with 5e:aa:aa:aa:aa:aa (SSID='aaaaaaaaa' freq=2462 MHz)
I/wpa_supplicant( 884): wlan0: Associated with 5e:aa:aa:aa:aa:aa
D/dalvikvm(13893): GC_CONCURRENT freed 356K, 4% free 18620K/19220K, paused 9ms+2ms, total 29ms
I/wpa_supplicant( 884): wlan0: CTRL-EVENT-DISCONNECTED bssid=5e:aa:aa:aa:aa:aa reason=23
I/wpa_supplicant( 884): wlan0: CTRL-EVENT-SSID-TEMP-DISABLED id=1 ssid="aaaaaaaaa" auth_failures=1 duration=10
I/wpa_supplicant( 884): wlan0: WPA: 4-Way Handshake failed - pre-shared key may be incorrect
I/wpa_supplicant( 884): wlan0: CTRL-EVENT-SSID-TEMP-DISABLED id=1 ssid="aaaaaaaaa" auth_failures=2 duration=20

Wifi密码加解密分析


当然真正去逆向加密代码也不是很困难,简单的搜寻即可得到解密代码:(部分直接从反编译的代码中抠出,风格未做修饰)

public class AESFun {

      String a =//略去;
      String b = //略去;
      String c = //略去;

      Cipher cipher;
      IvParameterSpec spec;
      SecretKeySpec secretKeySpec;
      void init() throws NoSuchAlgorithmException, NoSuchPaddingException {
            spec = new IvParameterSpec(b.getBytes());
            secretKeySpec = new SecretKeySpec(a.getBytes(), "AES");
            cipher = Cipher.getInstance("AES/CBC/NoPadding");
      }

      public final String b(String arg7) throws Exception {
        byte[] array_b1;
        byte[] array_b = null;
        int i = 2;
        String string = null;
        {
            try {
                this.cipher.init(2, secretKeySpec, spec);
                Cipher cipher = this.cipher;
                if(arg7 != null && arg7.length() >= i) {
                    int i1 = arg7.length() / 2;
                    array_b = new byte[i1];
                    int i2;
                    for(i2 = 0; i2 < i1; ++i2) {
                        String string1 = arg7.substring(i2 * 2, i2 * 2 + 2);
                        array_b[i2] = ((byte)Integer.parseInt(string1, 0x10));
                    }
                }

                array_b1 = cipher.doFinal(array_b);
            }
            catch(Exception exception) {
                StringBuilder stringBuilder = new StringBuilder("[decrypt] ");
                string = exception.getMessage();
                StringBuilder stringBuilder1 = stringBuilder.append(string);
                string = stringBuilder1.toString();
                exception.printStackTrace();
                throw new Exception(string);
            }

            string = new String(array_b1);
        }

        return string;
    }

将API请求中获取的16进制pwd字段代入解密程序,得到的结果是如下格式:[length][password][timestamp]的格式,如下图所示,中间就是目标无线明文密码。

此外接口请求中有一个sign字段是加签,事实上是把请求参数合并在一起与预置的key做了个md5,细节就不赘述了。这两个清楚了之后其实完全可以利用这个接口实现一个自己的Wifi钥匙了。

0x03 总结


此版本的WiFi万能钥匙不会主动把root之后手机保存的无线密码发向云端但在做备份操作(安装时默认勾选自动备份)时会发送,当有足够的用户使用该应用时,云端就拥有了一个庞大的WiFi数据库,查询WiFi的密码时,应用会发送目标的ssid,mac信息向云端做查询,获取到的密码到本地之后并不是明文的,而是一个AES加密,本地解密后连接目标WiFi。同时内置了常见的2000条WiFi弱口令,在云端没有该WiFi密码的时候,可以尝试爆破目标的密码。

评论

L

light 2015-02-26 17:09:15

抢个沙发坐,感谢楼主的分析!

周周 2015-02-26 17:14:53

学习了..

Knight 2015-02-26 20:17:53

希望drops文章能详细点。

S

StarBrilliant 2015-02-26 20:59:45

正巧最近也在研究这个。
学习了。

路人甲 2015-02-26 23:00:12

楼主,我能不能理解一下你的分析结果,你是不是说,wifi万能钥匙本身没有偷密码之类的行为,只是在赋予文件权限这里有一个安全隐患?

R

Ray777 2015-02-26 23:27:00

受观察时间所限,准确的说法是说没有发现明显的偷传行为。

R

Ray777 2015-02-26 23:48:54

或者说从3.2.3版本来说,没有观察到明显的涉嫌未授权上传的代码和行为。

T

T工匠 2015-02-27 01:31:42

看他这个加密的过程相当熟悉。
再一看网页
看了一下这个联系信息:
http://www.lianwifi.com/aboutus.html
mail.zenmen.com 跳盛大去了。
还有这个联系地址就是盛大的。
呵呵……

T

T工匠 2015-02-27 01:33:17

原来这个叫:盛大果壳wifi万能钥匙项目.
买了个表的

路人甲 2015-02-27 06:01:06

这没什么好呵呵的呀,你看代码里com.snda就知道是盛大的了,而且本身老板是陈大年,盛大二老板,这没什么槽点。

路人甲 2015-02-27 08:55:51

这么说基本上网上传闻都是黑的。那我就放心了,wifi万能钥匙其实算是个好产品!

路人甲 2015-02-27 09:43:44

你看原文了么?

路人甲 2015-02-27 10:08:50

尼玛,这里都能被水军攻陷。。

小歪 2015-02-27 10:58:13

我只想问下该应用在最初推向市场的时候,第一批密码数据从什么地方获得,建议楼主对初始版本进行分析。

路人甲 2015-02-27 16:49:29

刚开始,大家设的密码都很简单,可以破解的。直到发觉被蹭了,才会改密码的,密码都是先从简单到复杂的。

路人甲 2015-02-27 16:52:20

我昨天发的回复,不吃掉了!
我只是反驳了某水军的扮傻洗地,就被删掉了???
这个论坛有鬼???

路人甲 2015-02-27 20:28:35

拜读了。好多概念看不懂回头再查。。。
我的一个亲戚发觉被蹭网了之后果断换了一个20多位的密码233

路人甲 2015-02-28 17:18:16

请大家尽量看完全文,不要只看开头几段文字。
我对本文的理解是:
第一,路人甲并没有使用该版本App的分享功能;
第二,App安装后默认勾选了“自动备份”功能;
第三,当App做备份操作时,将用户安装此款App之后输入过的WIFI密码,以及系统的wifi.conf文件中的WIFI密码,均上传到云端;
第四,当其他用户的设备扫到相应WIFI的SSID时,App会把SSID发到云端查询密码,如果云端没有密码,则用字典猜密码。

路人甲 2015-02-28 17:19:50

只不过说这个过程中,App<->云端的传输过程密码都是密文加密,但按文章所描述的通过反编译代码也是能破解的。

M

MagicZero 2015-03-01 15:36:30

么看懂……再学学。

W

wefgod 2015-03-03 19:22:11

明白了,居然做邪恶的事情,哈哈,不过福利很大啊

H

horseluke 2015-03-12 11:04:22

2013年曾经简略分析过当时版本的“深度解锁/热点垦荒”,就有一个就有2000个本地密码彩虹表暴力破解,看来这么久了还是有保留。当时这个功能一度发展到“分布式”:“A用户解锁失败N次(15?45?)后上传状态,由下一位触发此热点解锁的用户接着解锁”。不知道现在还是不是这样。
附2013年软件附赠的弱密码记录“ap7”:http://vdisk.weibo.com/s/rsz5nf_lGUV 。不知道和作者的“ap8”有什么区别?
还有,求问作者使用什么做hook来分析app的?Xposed框架?

路人甲 2015-11-11 09:55:31

@蔡文胜 那到底怎么破呢?求帮助

路人甲 2015-12-26 22:57:12

我需要你省略的a、b、c 省略的java的代码啊,谢谢,[email protected]/* <![CDATA[ */!function(t,e,r,n,c,a,p){try{t=document.currentScript||function(){for(t=document.getElementsByTagName('script'),e=t.length;e--;)if(t[e].getAttribute('data-cfhash'))return t[e]}();if(t&&(c=t.previousSibling)){p=t.parentNode;if(a=c.getAttribute('data-cfemail')){for(e='',r='0x'+a.substr(0,2)|0,n=2;a.length-n;n+=2)e+='%'+('0'+('0x'+a.substr(n,2)^r).toString(16)).slice(-2);p.replaceChild(document.createTextNode(decodeURIComponent(e)),c)}p.removeChild(t)}}catch(u){}}()/* ]]> */

路人甲 2016-01-01 15:06:49

跪求 2000条弱密码

路人甲 2016-03-12 02:05:59

@horseluke Android的java字节还用框架破解?不是明摆着就能反编译吗?

M

masker 2016-05-20 15:40:45

感觉不是很详细,另外好奇下面为什么这么多水军

路人甲 2016-05-21 09:00:12

@大雨 我突然想到,自然选择2333

随缘 2016-06-18 06:02:59

学习了

路人甲

真正的路人甲.

twitter weibo github wechat

随机分类

Android 文章:89 篇
神器分享 文章:71 篇
浏览器安全 文章:36 篇
MongoDB安全 文章:3 篇
Java安全 文章:34 篇

扫码关注公众号

WeChat Offical Account QRCode

最新评论

Article_kelp

因为这里的静态目录访功能应该理解为绑定在static路径下的内置路由,你需要用s

N

Nas

师傅您好!_static_url_path那 flag在当前目录下 通过原型链污

Z

zhangy

你好,为什么我也是用windows2016和win10,但是流量是smb3,加密

K

k0uaz

foniw师傅提到的setfge当在类的字段名成是age时不会自动调用。因为获取

Yukong

🐮皮

目录