谁偷偷删了你的微信?别慌!Python 揪出来
不知道你有没有经历过,想联系一位很长时间没有联系的朋友,发现对方很早以前已经把你删除了,而你还一无所知。
相信每个人的微信通信录里都存在一些「僵尸粉」,他们默默地躺在联系人列表中,你以为对方还是朋友,那就真是太年轻、太天真的;实际上,对方早就把从好友列表中删了,那如何来筛选出这群人呢?
网上的很大量检测僵尸粉的工具,检测的时候会给微信通信录内的每一个好友发送一条检测信息,严重「打扰」到对方;另外一部分软件在检测的时候,会植入一些代码病毒,暗箱操作显得很不安全。
本篇文章的目的是自动化操作微信 App,通过「模拟给好友转账」来筛选出所有的僵尸粉,并一键删除它们。
2
准 备 工 作
在开始编写脚本之前,需要做好如下准备工作
一部 Root 后的 Android 手机或者模拟器,如果没有 Root 的设备,推荐使用网易 MuMu 模拟器
Android 开发环境、Android Studio
sqlcipher 图形化工具
自动化工具:Python 虚拟环境下安装 pocoui
3
编 写 脚 本
整个操作分为 3 步骤,分别是破解微信数据库筛选出通信录中的好友、模拟给好友转账得到僵尸粉数据、删除所有僵尸粉。
第 1 步,我们需要破解微信 App 的数据库。
ps:这里只是简单的说一下破解流程,想一键破解微信通信录数据,可以跳过这一步,直接使用文末提供的 APK。
首先,我们使用 Android Studio 新建一个项目,在项目初始化的时候,授予应用管理员权限以及修改微信目录的读写权限。
//微信 App 的目录
public static final String WX_ROOT_PATH = "/data/data/com.tencent.mm/";
/**
* 执行linux指令
*
* @param paramString
*/
public static void 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 + "
";
localDataOutputStream.writeBytes((String) localObject);
localDataOutputStream.flush();
localDataOutputStream.writeBytes("exit
");
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
*/
public static 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
*/
private void openWxDb(File dbFile, String db_pwd)
{
//所有联系人
List<Contact> contacts = new ArrayList<>();
SQLiteDatabase.loadLibs(this);
SQLiteDatabaseHook hook = new SQLiteDatabaseHook()
{
public void preKey(SQLiteDatabase database)
{
}
public void postKey(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
*/
public static void writeCsvFile(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__)
def export_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 = 0
for 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 = index
break
# 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()
if self.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 = ['微信', '通讯录', '发现', '我']
while True:
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 = True
if is_home:
print('已经回到微信首页~')
break
循环上面的操作,就可以判断出哪些是僵尸粉,哪些好友的账号被限制,哪些是正常的好友关系。
第 3 步,删除上面获取到的僵尸粉列表。
拿到上面的僵尸粉数据列表,就可以利用上面的方式进行一系列自动化UI 操作,删除掉这些好友。
def del_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()
4
结 果 结 论
编译 Android 项目或者直接运行 APK 就能将微信通信录的好友数据保存到项目文件目录下。
然后运行 Python 程序会遍历通讯录好友数据,自动化去操作微信 App,接着将所有的僵尸粉写入到本地文件中,最后可以选择将这些僵尸粉全部删除掉。
谁偷偷删了你的微信?别慌!Python 揪出来的更多相关文章
- Zabbix 微信报警Python版(带监控项波动图片)
#!/usr/bin/python # -*- coding: UTF- -*- #Function: 微信报警python版(带波动图) #Environment: python import ur ...
- 微信开发python+django两个月的成功经历,django是个好框架!
时间:大三 上学期没有用微信内置浏览器而纯对话开发,坑了自己好一下. 下学期选错bottle框架,以为轻量好,谁知开发中什么都自己来很痛苦. 选对了框架django,终于在大三最后的个把月里写 ...
- 微信支付 python版
需求: 微信打开商品列表页面-> 点击商品后直接显示付款页面-> 点击付款调用微信支付 说明 微信支付需要你申请了公众号(appid, key - 用于签名), 商户号(mch_id, A ...
- zabbix之微信告警(python版):微信个人报警,微信企业号告警脚本
微信个人告警脚本 微信个人告警:使用个人微信,发送到微信群组,微信好友 两个脚本执行: 1)能连接网络2)先执行server.py,扫描登录微信,登录之后没有报错,打开新终端查看端口是否起来了3)在z ...
- 微信现金红包 python
微信现金红包发送接口,好像没法限制一个用户一个活动只能领取一次红包,在调用红包接口上,自己做了限制 REDPACK_RECORD 建表sql -- Create table create table ...
- RS(纠删码)技术浅析及Python实现
前言 在Ceph和RAID存储领域,RS纠删码扮演着重要的角色,纠删码是经典的时间换空间的案例,通过更多的CPU计算,降低低频存储数据的存储空间占用. 纠删码原理 纠删码基于范德蒙德矩阵实现,核心公式 ...
- 搜狗微信采集 —— python爬虫系列一
前言:一觉睡醒,发现原有的搜狗微信爬虫失效了,网上查找一翻发现10月29日搜狗微信改版了,无法通过搜索公众号名字获取对应文章了,不过通过搜索主题获取对应文章还是可以的,问题不大,开搞! 目的:获取搜狗 ...
- 微信公众平台开发(免费云BAE+高效优雅的Python+网站开放的API)
虽然校园App是个我认为的绝对的好主意,但最近有个也不错的营销+开发的模式出现:微信平台+固定域名服务器. 微信公众平台的运行模式不外两个: 一.机器人模式或称转发模式,将说话内容转发到服务器上完成, ...
- 【itchat】用Python玩耍微信
[itchat] itchat是个基于网页版微信的python微信API.功能目前做到基本可以满足正常的消息收发,信息的获取等等.不过对于红包之类网页版微信不支持的功能,这个模块自然也就无法支持了. ...
随机推荐
- CSS伪类before,after制作左右横线中间文字效果
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- Java TreeSet集合 比较器排序Comparator的使用
比较器排序Comparator的使用 存储学生对象,并遍历,创建TreeSet集合使用带参构造方法 要求,按照学生年龄从小到大排序,如果年龄相同,则按照姓名的字母循序排序 结论 用TreeSet集合存 ...
- linux虚拟机内网突然不通了
之前安装后 内网,外网测试通常的,今天有开发反应es服务不通了 后来到服务器查看了一下,es和同步服务都停了 重新启动,发现同步服务无法启动,网络问题 报错信息“Failed to initiali ...
- 记录 shell学习过程(2) read的用法
echo -n "login:"read username #read后面直接使用一个变量用于接收输入的数据 echo -n "password:"read ...
- IE浏览器复选框遍历不兼容问题
obj = document.getElementsByName("userIdCheckbox"); ids = []; for(var k=0;k<obj.length; ...
- 自定义jstl标签*
原文链接:https://www.it610.com/article/442039.htm 步骤如下: 1.写tld文档:用来指定标签的名字,标签库等. 2.写标签处理器类. 3.配置到web.xml ...
- Jarvis OJ - 栈系列部分pwn - Writeup
最近做了Jarvis OJ的一部分pwn题,收获颇丰,现在这里简单记录一下exp,分析过程和思路以后再补上 Tell Me Something 此题与level0类似,请参考level0的writeu ...
- RFC3984: RTP Payload Format for H.264 Video(中文版)
转载地址:https://blog.csdn.net/h514434485/article/details/51010950 官方文档,中文版本地址:http://www.rosoo.net/File ...
- 普及C组第三题(8.13)
2334. [NOIP普及组T2]战斗 (File IO): input:fight.in output:fight.out 时间限制: 1000 ms 空间限制: 524288 KB 开始贴图:. ...
- MySQL学习(六)change-buffer
文章部分总结描述来自参考文章,属于半原创. 概述 文章将会介绍 change buffer 相关的知识点 查看 MySQL InnoDB 状态的命令 SHOW ENGINE INNODB ST ...