目標場景
不知道你有沒有經歷過,想聯繫一位很長時間沒有聯繫的朋友,發現對方很早以前已經把你刪除了,而你還一無所知。
相信每個人的微信通信錄裡都存在一些「殭屍粉」,他們默默地躺在聯繫人列表中,你以為對方還是朋友,那就真是太年輕、太天真的;實際上,對方早就把從好友列表中刪了,那如何來篩選出這群人呢?
網上的很大量檢測殭屍粉的工具,檢測的時候會給微信通信錄內的每一個好友發送一條檢測信息,嚴重「打擾」到對方;另外一部分軟體在檢測的時候,會植入一些代碼病毒,暗箱操作顯得很不安全。
本篇文章的目的是自動化操作微信 App,通過「模擬給好友轉帳」來篩選出所有的殭屍粉,並一鍵刪除它們。
準備工作
在開始編寫腳本之前,需要做好如下準備工作
一部 Root 後的 Android 手機或者模擬器,如果沒有 Root 的設備,推薦使用網易 MuMu 模擬器Android 開發環境、Android Studiosqlcipher 圖形化工具自動化工具:Python 虛擬環境下安裝 pocoui
編寫腳本
整個操作分為 3 步驟,分別是破解微信資料庫篩選出通信錄中的好友、模擬給好友轉帳得到殭屍粉數據、刪除所有殭屍粉。
第 1 步,我們需要破解微信 App 的資料庫。
ps:這裡只是簡單的說一下破解流程,想一鍵破解微信通信錄數據,可以跳過這一步,直接使用文末提供的 APK。
首先,我們使用 Android Studio 新建一個項目,在項目初始化的時候,授予應用管理員權限以及修改微信目錄的讀寫權限。
//微信 App 的目錄public static final String WX_ROOT_PATH = "/data/data/com.tencent.mm/";/** * 執行linux指令 * * @param paramString*/public staticvoid execRootCmd(String paramString){try { Process localProcess = Runtime.getRuntime().exec("su");Object localObject = localProcess.getOutputStream(); DataOutputStream localDataOutputStream = new DataOutputStream((OutputStream) localObject);String str = String.valueOf(paramString); localObject = str + "\n"; localDataOutputStream.writeBytes((String) localObject); localDataOutputStream.flush(); localDataOutputStream.writeBytes("exit\n"); localDataOutputStream.flush(); localProcess.waitFor(); localObject = localProcess.exitValue(); } catch (Exception localException) { localException.printStackTrace(); }}//獲取權限RootUtils.execRootCmd("chmod 777 -R " + WX_ROOT_PATH);
然後,獲取微信資料庫的密碼。
微信資料庫的密碼是由設備的 imei 和微信的 uid 進過 md5 算法生成的。
/** * 根據imei和uin生成的md5碼,獲取資料庫的密碼(去前七位的小寫字母) * * @param imei * @param uin * @return */publicstatic String getDbPassword(String imei, String uin){if (TextUtils.isEmpty(imei) || TextUtils.isEmpty(uin)) { Log.d("xag", "初始化資料庫密碼失敗:imei或uid為空");return"密碼錯誤"; } String md5 = MD5Utils.md5(imei + uin);assert md5 != null;return md5.substring(0, 7).toLowerCase();}
接著,就可以使用 SQLCipher 依賴庫來對微信資料庫進行查詢,我們需要為項目添加如下依賴,方便操作資料庫。
//我們需要對項目增加依賴implementation'net.zetetic:android-database-sqlcipher:3.5.4@aar'
利用上面得到的密碼打開加密資料庫,然後查詢「rcontact」表獲取微信通訊錄內所有的好友的微信號、暱稱、用戶名等數據。
/** * 連接資料庫 * <p> * 常用庫介紹:【rcontact】聯繫人表,【message】聊天消息表 * * @param dbFile */privatevoidopenWxDb(File dbFile, String db_pwd){//所有聯繫人 List<Contact> contacts = new ArrayList<>(); SQLiteDatabase.loadLibs(this); SQLiteDatabaseHook hook = new SQLiteDatabaseHook() {publicvoidpreKey(SQLiteDatabase database){ }publicvoidpostKey(SQLiteDatabase database){ atabase.rawExecSQL("PRAGMA cipher_migrate;"); //兼容2.0的資料庫 } };try {//打開資料庫連接 SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(dbFile, db_pwd, null, hook);//查詢所有聯繫人//過濾掉本人、群聊、公眾號、服務號等一些聯繫人//verifyFlag != 0:公眾號、服務號//注意黑名單用戶,我-設置-隱私-通訊錄黑名單 Cursor c1 = db.rawQuery("select * from rcontact where verifyFlag =0 and type not in (2,4,8,9,33,35,256,258,512,2051,32768,32770,32776,33024,65536,65792,98304) and username not like \"%@app\" and username not like \"%@qqim\" and username not like \"%@chatroom\" and encryptUsername!=\"\"",null);while (c1.moveToNext()) { String userName = c1.getString(c1.getColumnIndex("username")); String alias = c1.getString(c1.getColumnIndex("alias")); String nickName = c1.getString(c1.getColumnIndex("nickname"));int type = c1.getInt(c1.getColumnIndex("type")); contacts.add(new Contact(userName, alias, nickName)); } Log.d("xag", "微信通訊錄中,聯繫人數目:" + contacts.size() + "個");for (int i = 0; i < contacts.size(); i++) { Log.d("xag", contacts.get(i).getNickName()); } c1.close(); db.close(); } catch (Exception e) { Log.e("xag", "讀取資料庫信息失敗" + e.toString()); Toast.makeText(this, "讀取微信通信錄失敗!", Toast.LENGTH_SHORT).show(); } Toast.makeText(this, "讀取微信通信錄成功!", Toast.LENGTH_SHORT).show();}
需要注意的是,資料庫中 rcontact 表的數據比較雜亂,除了正常的好友數據,黑名單好友、已刪除好友、公眾號、微信群等數據也包含在內,需要我們通過 type 和 verifyFlag 欄位進行篩選。
為了便於 Python 操作,最後將查詢的好友數據寫入到 csv 文件中。
/*** * 寫入數據到csv中 * @param output_path * @param contacts */publicstaticvoidwriteCsvFile(String output_path, List<Contact> contacts){try { File file = new File(output_path);//刪除之前保存的文件if (file.exists()) { file.delete(); } BufferedWriter bw = new BufferedWriter(new FileWriter(file, true));// 添加頭部名稱 bw.write("userName" + "," + "alias" + "," + "nickName"); bw.newLine();for (int i = 0; i < contacts.size(); i++) { bw.write(contacts.get(i).getUserName() + "," + contacts.get(i).getAlias() + "," + contacts.get(i).getNickName()); bw.newLine(); } bw.close(); } catch (IOException e) { e.printStackTrace(); }}
第 2 步,我們需要模擬給好友轉帳,來判斷這個好友關係是否正常。
首先,我們需要初始化 Airtest,然後利用 adb 把第 1 步生成的數據從手機裡導出到本地。
def__init_airtest(self):""" 初始化Airtest :return: """ device_1 = Android('822QEDTL225T7')# device_1 = Android('emulator-5554') connect_device("android:///") self.poco = AndroidUiautomationPoco(device_1, screenshot_each_action=False) auto_setup(__file__)defexport_wx_db_from_phone(target_path):""" 從手機中導出通信錄數據 :param target_path: :return: """# 微信通信錄數據 wx_db_source_path = "/data/data/com.xingag.crack_wx/wx_data.csv"# 導出到本地 os.popen('adb pull %s %s' % (wx_db_source_path, target_path))
然後就是一系列自動化操作。
打開微信,遍歷好友列表,拿到每一個好友的微信號去搜索好友,跳轉到好友的聊天界面。
def__to_friend_chat_page(self, weixin_id):""" 點擊到一個好友的聊天界面 :param weixin_id: :param weixin_name: :return: """# 1、點擊搜索 element_search = self.__wait_for_element_exists(self.id_search) element_search.click() print('點擊搜索')# 2、搜索框 element_search_input = self.__wait_for_element_exists(self.id_search_input) element_search_input.set_text(weixin_id)# 3、搜索列表 element_search_result_list = self.__wait_for_element_exists(self.id_search_result_list)# 3.1 是否存在對應的聯繫人,如果存在就在第一個子View布局下# 注意:可能出現最常用的聊天列表,這裡需要進行判斷 index_tips = 0for index, element_search_result in enumerate(element_search_result_list.children()):# 聯繫人的Tips# if element_search_result_list.children()[0].offspring(self.id_contact_tips).exists():if element_search_result.offspring(text=self.text_contact_tips).exists(): index_tips = indexbreak# 4、點擊第一個聯繫人進入聊天界面 element_search_result_list.children()[index_tips + 1].click()
接著嘗試著給對方轉帳,如果好友關係正常,就會跳出一個支付頁面讓輸入密碼。
def__judge_is_friend(self, weixin_id, weixin_name):""" 判斷是不是微信好友 :param weixin_id: 微信號 :return: """# 嘗試給好友轉帳,設置一個小額度,以防止刷臉直接支付了# 如果對方是你的好友,接下來會讓你輸入密碼,關掉頁面就行了# 如果對方不是你的好友,會提示不是你的好友,不能繼續操作了# 5、點擊好友界面的+按鈕self.poco(self.id_chat_more_button).click()# 6、點擊轉帳按鈕self.poco(self.id_chat_more_container).offspring(text=self.text_chat_transfer_account_text).click()# 7、輸入金額self.poco(self.id_transfer_account_input).set_text(self.money)# 8、點擊轉帳按鈕self.poco(self.id_transfer_account_container).offspring(text=self.text_chat_transfer_account_text).click()
如果是殭屍粉,應用會彈出一個警告對話框,提示你不是收款方好友,沒法完成轉帳的操作。
通過警告對話框是否存在,就可以判斷好友關係是否正常。非正常的好友關係,包含:殭屍粉、對方帳號異常等。
# 10.彈出警告對話框# 彈出好友關係不正常if element_transfer_account_result_button:# 提示內容 ransfer_account_result_tips = self.poco(self.id_transfer_account_result_tips).get_text()ifself.text_friend_no_tips in transfer_account_result_tips:print('注意!%s已經把你拉黑了!!!' % weixin_name)self.friend_black_list.append({'id': weixin_id,'nickName': weixin_name }) write_to_file(self.path_black_list, 'id:%s,nickName:%s' % (weixin_id, weixin_name)) elif self.text_friend_limit_tips in transfer_account_result_tips:print('%s帳號收到限制!!!' % weixin_name) write_to_file(self.path_account_limit, 'id:%s,nickName:%s' % (weixin_id, weixin_name)) elif self.text_friend_is_norm in transfer_account_result_tips:print('%s好友關係不正常!!!' % weixin_name) write_to_file(self.path_relationship_unnormal, 'id:%s,nickName:%s' % (weixin_id, weixin_name))# 點擊確認按鈕 element_transfer_account_result_button.click()# 返回到主頁面self.__back_to_home()else:# 包含正常好友關係和對方帳號限制的情況print('好友關係正常')self.__back_to_home()
最後,模擬點擊手機的返回鍵,一直回退到微信主界面。
def__back_to_home(self):""" 回退到主界面 :return: """ print('準備回退到主界面') home_tips = ['微信', '通訊錄', '發現', '我']whileTrue: keyevent('BACK') is_home = False# 判斷是否到達首頁if self.poco(text=home_tips[0]).exists() and self.poco(text=home_tips[1]).exists() and self.poco( text=home_tips[2]).exists() and self.poco(text=home_tips[3]).exists(): is_home = Trueif is_home: print('已經回到微信首頁~')break
循環上面的操作,就可以判斷出哪些是殭屍粉,哪些好友的帳號被限制,哪些是正常的好友關係。
第 3 步,刪除上面獲取到的殭屍粉列表。
拿到上面的殭屍粉數據列表,就可以利用上面的方式進行一系列自動化 UI 操作,刪除掉這些好友。
defdel_friend_black(self, weixin_id):""" 刪除黑名單好友 :return: """# 到好友聊天界面self.__to_friend_chat_page(weixin_id)# 點擊聊天界面右上角,進入到好友的詳細信息界面self.poco(self.id_person_msg_button).click()# 點擊好友頭像self.poco(self.id_person_head_url).click()# 點擊個人名片的右上角,彈出好友操作菜單self.poco(self.id_person_manage_menu).click()# 查找刪除操作欄# 注意:對於目前主流的手機,都需要滑動到最底部才能出現【刪除】這一操作欄self.poco.swipe([0.5, 0.9], [0.5, 0.3], duration=0.2)# 點擊刪除,彈出刪除對話框self.poco(self.id_person_del, text=self.text_person_del).click()# 確定刪除好友【確定刪除】# 界面會直接回到主界面self.poco(self.id_person_del_sure, text=self.text_person_del).click()
結果結論
編譯 Android 項目或者直接運行 APK 就能將微信通信錄的好友數據保存到項目文件目錄下。
然後運行 Python 程序會遍歷通訊錄好友數據,自動化去操作微信 App,接著將所有的殭屍粉寫入到本地文件中,最後可以選擇將這些殭屍粉全部刪除掉。