本次測試版本號為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執行監控,短暫的使用過程中也只發現了執行了上述的這一條命令。
Android系統通過WifiManager類來提供對Wifi的掃描、連接接口。應用在請求相應權限之后可以掃描、連接、斷開無線等。在連接無線功能中,客戶端基本上只要指定SSID,Pre-shared-key(即密碼),就可以用代碼的方式連接無線。連接一個WPA(2)無線典型代碼如下,
#!bash
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);
這也是爭議較大的地方,首先該應用肯定是有云端存儲了很多密碼,因為應用會引導用戶備份自己的密碼,但這些密碼有沒有被濫用我們在客戶端就不得而知了。在2月底的這次測試中,筆者先私有備份了自己建立的測試無線(注意不是分享),然后使用另外一個手機安裝該客戶端測試,該客戶端的API請求接口并沒有返回這個測試的無線的密碼。不過這也可能只是個例說明不了什么,還是建議各位自行測試,但注意測試前清除保存的無線并給測試無線設定一個弱密碼以免真的泄露了自己的密碼。
回到正題,筆者通過代理攔截到了該應用獲取wifi密碼的請求。應用發送目標的ssid,mac信息向云端做查詢,獲取到的密碼到本地之后并不是明文的,而是一個AES加密。首先為了證明其在本地最終還是會以明文出現,先取了個巧,沒有去逆這個算法(雖然逆下也不會很困難),而是直接hook了系統添加無線的代碼(回憶上文里密碼就在NetworkConfiguration.preSharedKey里)。
部分HOOK代碼:
#!java
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就很明顯。
#!bash
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
當然真正去逆向加密代碼也不是很困難,簡單的搜尋即可得到解密代碼:(部分直接從反編譯的代碼中摳出,風格未做修飾)
#!java
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鑰匙了。
此版本的WiFi萬能鑰匙不會主動把root之后手機保存的無線密碼發向云端但在做備份操作(安裝時默認勾選自動備份)時會發送,當有足夠的用戶使用該應用時,云端就擁有了一個龐大的WiFi數據庫,查詢WiFi的密碼時,應用會發送目標的ssid,mac信息向云端做查詢,獲取到的密碼到本地之后并不是明文的,而是一個AES加密,本地解密后連接目標WiFi。同時內置了常見的2000條WiFi弱口令,在云端沒有該WiFi密碼的時候,可以嘗試爆破目標的密碼。