1. 程式人生 > >閃訊無線助手-Android端閃訊破解路由器撥號實現原理

閃訊無線助手-Android端閃訊破解路由器撥號實現原理

破解閃訊詳細原理

前言

博主曾在大二下學期的時候寫了一個閃訊無線助手(Android端)用來破解閃訊,撥號路由器發出無線。當時在閃訊吧發了一個相關帖子免費提供給大家使用,同時也力所能及的回答吧友的問題。之後將閃訊無線助手在豌豆莢和應用寶上架。當初寫好時候就想著之後要寫一篇具體實現教程同時開源,也答應了很多人會開源。本來想著在學校的時候就把這件事完成,沒想到之後事情比較多,被什麼實習、工作、畢業一直拖著。一晃眼現在已經畢業了,再不開始搞估計就鴿了,趁著剛畢業還不怎麼忙的時候先把這件事搞定。ps:之前實習的時候比較忙,qq和貼吧上問的問題我都忘了回,在這說聲不好意思。

我還是簡單的講一下破解閃訊路由撥號是什麼意思,其實就是讓路由器撥號閃訊賬號發出無線,實現多臺裝置上網。

ps:上面是閃訊無線助手Android端原始碼,我也寫了一個閃訊無線助手ios端,不過第一次寫ios程式碼寫的十分混亂,如果不介意這些想要ios端的程式碼話可以在下面留言。

閃訊破解原理

破解整體過程

眾所周知路由器有一個管理後臺比如TPLINK的192.168.1.1,在管理後臺頁面中我們可以找到一個撥號介面,正常情況下我們都是輸入寬頻賬號密碼來撥號路由。現在要撥號閃訊,直接輸入閃訊賬號和密碼是撥號不成功的。因為閃訊撥號的時候會對閃訊賬號進行加密處理,要使用加密之後的閃訊賬號進行撥號。閃訊賬號加密是根據當前時間動態加密的,手動在路由器撥號介面輸入加密之後的閃訊賬號時間上會來不及,因而要通過軟體進行模擬撥號。

現在來總結下破解過程,第一步先將閃訊賬號進行相應的加密,第二步將加密之後的閃訊賬號和閃訊密碼通過軟體模擬路由器撥號進行撥號。整體流程如下圖所示

整體撥號流程

閃訊賬號加密實現

閃訊賬號加密整體流程如下圖所示

閃訊賬號加密流程

這裡給出Java版的閃訊賬號加密程式碼,其他語言版本的大家可以自己發揮。ps:要object-c版的可以在下面留言

 /**
     * 獲取加密後的閃訊賬號
     *
     * @return
     */
    public String getRealName(String username, long lasttime) {
        if (TextUtils.isEmpty(username)) {
            return
null; } mUserName = username; mLastTime = lasttime; Calendar calendar = Calendar.getInstance(); long mTime1c; long mTime1convert; byte[] ss = new byte[]{0, 0, 0, 0};// unsigned char byte byte[] ss2 = new byte[]{0, 0, 0, 0}; String strS1 = ""; String mFormatsring = ""; String mMd5 = ""; String mMd5use = ""; { long t; t = calendar.getTimeInMillis() / 1000;// 得到系統時間 t *= 0x66666667; t >>= 0x20; t >>= 0x01; mTime1c = (long) t; } if (mTime1c <= mLastTime) { mTime1c = mLastTime + 1; } mLastTime = mTime1c; { long t; t = mTime1c; ss2[3] = (byte) (t & 0xFF); ss2[2] = (byte) ((t & 0xFF00) / 0x100); ss2[1] = (byte) ((t & 0xFF0000) / 0x10000); ss2[0] = (byte) ((t & 0xFF000000) / 0x1000000); { try { strS1 = new String(ss2, "ISO-8859-1"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } } { int t, t1, t2, t3; t = (int) mTime1c; t1 = t; t2 = t; t3 = t; t3 = t3 << 0x10; t1 = t1 & 0x0FF00; t1 = t1 | t3; t3 = t; t3 = t3 & 0x0FF0000; t2 = t2 >> 0x10; t3 = t3 | t2; t1 = t1 << 0x08; t3 = t3 >> 0x08; t1 = t1 | t3; mTime1convert = t1; } { long t; t = mTime1convert; ss[3] = (byte) (t & 0xFF); ss[2] = (byte) ((t & 0xFF00) / 0x100); ss[1] = (byte) ((t & 0xFF0000) / 0x10000); ss[0] = (byte) ((t & 0xFF000000) / 0x1000000); } /** * sun ss byte !負數 */ int ssInt[] = new int[]{0, 0, 0, 0}; { ssInt[3] = (int) (ss[3] & 0xff); ssInt[2] = (int) (ss[2] & 0xff); ssInt[1] = (int) (ss[1] & 0xff); ssInt[0] = (int) (ss[0] & 0xff); } byte[] pp = new byte[]{0, 0, 0, 0}; { int i = 0, j = 0, k = 0; for (i = 0; i < 0x20; i++) { j = i / 0x8; k = 3 - (i % 0x4); pp[k] *= 0x2; if (ssInt[j] % 2 == 1) { pp[k]++; } ssInt[j] /= 2; } } /** * sun pp byte !負數 */ int ppInt[] = new int[]{0, 0, 0, 0}; { ppInt[3] = (int) (pp[3] & 0xff); ppInt[2] = (int) (pp[2] & 0xff); ppInt[1] = (int) (pp[1] & 0xff); ppInt[0] = (int) (pp[0] & 0xff); } /** * 格式符計算,mFormatsring為結果 */ byte[] pf = new byte[]{0, 0, 0, 0, 0, 0}; { short t1, t2; t1 = (short) ppInt[3]; t1 /= 0x4; pf[0] = (byte) t1; t1 = (short) ppInt[3]; t1 = (short) (t1 & 0x3); t1 *= 0x10; pf[1] = (byte) t1; t2 = (short) ppInt[2]; t2 /= 0x10; t2 = (short) (t2 | t1); pf[1] = (byte) t2; t1 = (short) ppInt[2]; t1 = (short) (t1 & 0x0F); t1 *= 0x04; pf[2] = (byte) t1; t2 = (short) ppInt[1]; t2 /= 0x40; t2 = (short) (t2 | t1); pf[2] = (byte) t2; t1 = (short) ppInt[1]; t1 = (short) (t1 & 0x3F); pf[3] = (byte) t1; t2 = (short) ppInt[0]; t2 /= 0x04; pf[4] = (byte) t2; t1 = (short) ppInt[0]; t1 = (short) (t1 & 0x03); t1 *= 0x10; pf[5] = (byte) t1; } { int i; for (i = 0; i < 6; i++) { pf[i] += 0x20; if ((pf[i]) >= 0x40) { pf[i]++; } } } { for (int i = 0; i < 6; i++) { // mFormatsring += pf[i]; mFormatsring += (char) ((int) (pf[i] & 0xff)); } } String strInput; strInput = strS1 + mUserName.split("@")[0] + RADIUS; try { mMd5 = getMD5(strInput, "ISO-8859-1"); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } mMd5use = mMd5.substring(0, 2); mReallyUserName = mFormatsring + mMd5use.toLowerCase() + mUserName; return mReallyUserName; }

路由器模擬撥號

模擬路由器撥號主要是通過對路由器管理員平臺的撥號頁面(tplink的撥號介面一般是http://192.168.1.1/userRpm/PPPoECfgRpm.htm)抓包,然後傳送模擬請求來完成,mac使用者可以使用charles來抓包,Windows使用者可以使用ie自帶的開發者工具來抓包。
在這裡說下幾個注意點:1.路由器管理後臺登入是抓不了包的,因為路由器管理員登入是通過http basic authorization認證的,簡單的來講就是將管理員賬號和密碼拼接起來然後進行BASE64加密,將加密之後的結果放到http請求頭中。2.要設定http referer請求頭為路由器管理後臺地址,因為路由器撥號的時候會通過referer來判斷當前請求之前的頁面是否為路由器管理後臺,不設定的話會返回You have no authority to access this device!。因而通過js或微信小程式來模擬撥號都是行不通的(博主兩個都嘗試過了-_-),js和微信小程式都不允許使用者設定請求的referer,大家也可以找找有沒有其他方法突破這個限制。

模擬路由撥號整體流程如下圖所示

模擬路由撥號整體流程

模擬路由撥號請求程式碼

 /**
     * 撥號
     *
     * @param routerName
     * @param routerPwd
     * @param shanXunName
     * @param shanXunPwd
     * @return
     */
    public int dial(String routerName, String routerPwd, String shanXunName, String shanXunPwd) {
        if (TextUtils.isEmpty(routerName) || TextUtils.isEmpty(routerPwd) || TextUtils.isEmpty(shanXunName) || TextUtils.isEmpty(shanXunPwd)) {
            sErrCode = -1;
            return NetConstant.ERR;
        }
        int r;
        shanXunName = "\r\n" + SXRealName.getInstance().getRealName(shanXunName, 0);
        String acc = str2HexStr(shanXunName);
        String str = routerName + ":" + routerPwd;
        String authorization = new Base64().encode(str.getBytes());
        String path = "http://192.168.1.1/userRpm/PPPoECfgRpm.htm?wan=0&wantype=2&acc=" + acc +
                "&psw=" + shanXunPwd + "&confirm=" + shanXunPwd + "&specialDial=100&SecType=1&sta_ip=0.0.0.0&sta_mask=0.0.0.0&linktype=4&waittime2=0&Connect=%C1%AC+%BD%D3";
        try {
            URL url = new URL(path);
            HttpURLConnection con = (HttpURLConnection) url.openConnection();
            con.setRequestProperty("Authorization", "Basic " + authorization);
            con.setRequestMethod("GET");
            con.setRequestProperty("Host", "192.168.1.1");
            con.setRequestProperty("Referer", "http://192.168.1.1/userRpm/PPPoECfgRpm.htm?wan=0&wantype=2&acc=" + acc + "&psw=" + shanXunPwd +
                    "&confirm=" + shanXunPwd + "&SecType=1&sta_ip=0.0.0.0&sta_mask=0.0.0.0&linktype=4&waittime2=0&Disconnect=%B6%CF+%CF%DF");
            con.setRequestProperty("Cookie", "Authorization=Basic " + authorization);
            String result = getHttpResponse(con);
            if (result == null) {
                sErrCode = -2;
                return NetConstant.ERR;
            } else if ("401".equals(result)) {
                return NetConstant.ROUTER_LOGIN_FAIL;
            }
            String[] strs = result.split("Hello123World");
            if (strs.length >= 2) {
                strs = strs[1].split(",");
                if (strs.length >= 19) {
                    r = Integer.valueOf(strs[18].trim());
                    Log.d(TAG, "dial: " + strs[18].trim());
                } else {
                    sErrCode = -3;
                    return NetConstant.ERR;
                }
            } else {
                sErrCode = -4;
                return NetConstant.ERR;
            }
        } catch (Exception e) {
            e.printStackTrace();
            sErrCode = -5;
            r = NetConstant.ERR;
        }

//        Log.d(TAG, "dial: " + shanXunName);
        return r;
    }

Emmmmmm 兩個關鍵的部分都說了,有什麼補充我會直接在這篇教程上加的,原始碼在github地址,大家可以自己動手看看,有什麼問題可以給我留言哈!

如果教程部落格、原始碼對你有所幫助可以給個star。