共计 8248 个字符,预计需要花费 21 分钟才能阅读完成。
微信插件之语音播报(此我的项目仅供逆向学习应用)
本意是为盲人群体做的一款微信语音辅助的小我的项目,尽管最终没有被启用,但其中波及到的反编译思维(逆向思维)以及过程间通信的模块还是对后续我的项目开发有肯定裨益的。
- 对于 Android 逆向我的项目,本我的项目目前应用的技术是 Xposed 框架,然而 Xposed 目前所知是须要手机 Root 后才可应用。
-
Xposed 设计思维是借用 JAVA 的反射机制来实现的,Hook 所需模块来进行批改,从而达到本身需要。像微信机器人、滴滴出行之类的主动抢单等的插件。
-
Xposted 相干材料的网址
* https://forum.xda-developers.com/showthread.php?t=3034811
-
- 在 AndroidStudio 的 gradle 中配置如图:
- 须要手机先装 Xposed 插件,针对不同机型,有对应模块的装置
- 我的项目插件中须要在 assets 资源目录下创立命名为 xposed_init 的文件,外面申明好本身插件入口启动类,如图:
-
既然要做基于微信音讯文本播报的语音插件,那么就要即时获取指标 APP 的即时数据内容,逆向我的项目最耗时最费劲的模块是定位所需的代码模块加调试,因而不像失常需要开发,会有明确技术耗时与定期。定位代码并 Hook 无误执行一般来说是有些难定位且很耗时的。
-
若解决微信的聊天文本信息获取的计划,目前可行有两种:
- 1,通过指标 APP 的数据库用 SQL 语句进行数据的即时查找
- 2,通过 Hook 微信在通信时的调用音讯的 API(不过这块必定是绝对耗时的。所以,此文本播报我的项目我采纳是第一种计划,毕竟工夫老本太高的话,导致做出货色也绝对意义上大打折扣。)
- 对于微信的数据库解密计划,本文不做介绍。
-
思路与实现
-
首先在 Xposed 我的项目初始化时,实时检测微信过程,从而 Hook 住微信并在其运行时做对应的 Hook 解决。
-
- CallingTheDog 为本插件的入口类,用来初始化检测微信的主过程,以及微信的 APP 主 UI(LauncherUI)的启动监听、数据库 Cursor 游标对象的获取。
-
public class CallingTheDog implements IXposedHookLoadPackage {
//Specify the currently required version. public static String currentVersion = WE_CHAT_FLAG.VERSION_6_5_4; private WeChatLauncherUI weChatLauncherUI; private WeChatDBHelper weChatDBHelper; @Override public void handleLoadPackage(XC_LoadPackage.LoadPackageParam LPParam) throws Throwable {if (APP_PACKAGE_NAME.WE_CHAT.equals(LPParam.packageName)) {if (weChatLauncherUI == null) {LoggerUtils.xd("We Chat init."); weChatLauncherUI = new WeChatLauncherUI(LPParam); } toHookWeChatAttach(LPParam); } } private void toHookWeChatAttach(final XC_LoadPackage.LoadPackageParam lpParam) { findAndHookMethod(Application.class, "attach", Context.class, new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable {super.afterHookedMethod(param); if (weChatDBHelper == null) {LoggerUtils.xd("WeChatDBHelper init."); weChatDBHelper = new WeChatDBHelper(); weChatDBHelper.init(lpParam); } } }); } }
-
微信的 Cursor 数据库游标对象获取代码如下
-
public class WeChatDBHelper {
public static Method method = null; public static Object receiver = null; /** * 微信 Cursor 读写初始化。
-
*/
public void init(final XC_LoadPackage.LoadPackageParam loadPackageParam){
String targetSqlClass = "";
if(WE_CHAT_FLAG.VERSION_6_5_4.equals(CallingTheDog.currentVersion)){targetSqlClass = "com.tencent.mm.bg.g";}else if(WE_CHAT_FLAG.VERSION_7_0_4.equals(CallingTheDog.currentVersion)){targetSqlClass = "com.tencent.mm.bb.g";}
String targetSqlMethod = "rawQuery";
findAndHookMethod(targetSqlClass, loadPackageParam.classLoader, targetSqlMethod,
String.class, String[].class, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {super.beforeHookedMethod(param);
//hook 数据库连贯对象,用于发动数据被动查问
if(method == null){method = (Method) param.method;
receiver = param.thisObject;
}
}
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {super.afterHookedMethod(param);
// 监听所有的音讯查问语句
Cursor result = (Cursor) param.getResult();
String sqlStr = String.valueOf(param.args[0]);
if (result != null && result.getCount()>0 && sqlStr.startsWith("select * from message")) {MsgListeners.listenerPath(result, loadPackageParam);
}
}
});
}
}
-
借用游标对象获取文本信息的监听治理类,依据监听数据库插入的新音讯数据来进行即时播报解决
-
public class MsgListeners {
private static Socket mClientSocket;
private static PrintWriter mClientPrintWriter;public static void listenerPath(Cursor cursor, XC_LoadPackage.LoadPackageParam loadPackageParam) {
// 被动发送的 status=2, 接管的为 3
int status = WeChatMessage.getStatus(cursor);
WeChatMessage.Type msgType = WeChatMessage.getType(cursor);// 建设 Client 端
if (mClientPrintWriter == null) {new Thread() { @Override public void run() {connectTCPServer(); } }.start();
} else {
LoggerUtils.xd(“mClientPrintWriter is ” + mClientPrintWriter);
}
switch (msgType) {
case TEXT_MESSAGE:List<TextMessage> textMessages = WeChatMessage.getTextMessage(cursor); // 初始化监测音量键提供者。VolumeProvider volumeProvider = new VolumeProvider(loadPackageParam); for (TextMessage textMessage : textMessages) {if (textMessage == null) {continue;} if (textMessage.getCreateTime() > WeChatMessage.lastSend) {WeChatMessage.lastSend = textMessage.getCreateTime(); // 解决转发的音讯 } SystemClock.sleep(1000); String textContent = QueryWeChatDB.getNickname(textMessage.getFromUser()) + "来音讯:" + textMessage.getContent();
-
LoggerUtils.xd("GenialSir Msg textContent" + textContent);
if (!TextUtils.isEmpty(textContent) && mClientPrintWriter != null) {mClientPrintWriter.println(textContent);
LoggerUtils.xd("GenialSir mClientPrintWriter textContent" + textContent);
} else {LoggerUtils.xd("mClientPrintWriter is null..");
new Thread() {
@Override
public void run() {connectTCPServer();
}
}.start();}
}
break;
case VIDEO_MESSAGE:
break;
case IMG_MESSAGE:
break;
default:
break;
}
}
}
-
再进行语音播报模块, 留神,如果是 Android 零碎 >=21(5.0),则间接应用原生 API 的 TextToSpeech 即可实现语音播报,若 Android 零碎 <21(5.0),则 TextToSpeech 不反对中文。
-
如果须要反对中文,那首先能够想到应用三方语音 API,如讯飞、百度语音等都能够实现,我在首次应用过程中遇到一些意料之外的问题:
-
- 语音 API 的初始化问题,APP 的 key 签名注册问题。显然,这块间接应用微信的 Context 注册是有问题的。
- 如果不依赖微信的 Context,能够应用本身插件的 Context 进行一个三方语音注册,我的 TextVoiceHelper 应用的是讯飞语音。但在应用本身插件的 Context 时,前面又遇到因过程间通信而导致语音无奈播报的问题。
-
在应用 ALDL 过程中,间接反对的数据类型如下:(本我的项目采纳的是 Socket)
- 根本数据类型(int、long、char、boolean、double 等);
-
String 和 CharSequence;
- List:只反对 ArrayList,外面每个元素都必须被 AIDL 反对;
- Map:只反对 HashMap,外面每个元素都必须被 AIDL 反对,包含 key 和 value;
- Parcelable:所有实现了 Parceable 接口的对象;
- AIDL:所有的 AIDL 接口自身也能够在 AIDL 文件中应用(AIDL 接口中只反对办法,不反对申明动态常量,这一点区别于传统接口)。
-
-
若采纳将插件的 Context 传递给微信过程中来进行初始化讯飞语音 API 的操作,这个思路整体感觉很矛盾且不清晰,并且 Context 在多过程状况下也是问题很多。
- 那么就不必在跨过程传输 Context 上下功夫,而是间接将聊天数据跨过程传输,从而借助 Socket 来进行跨过程传输通信,只需在 TextVoiceHelper 过程内注册的讯飞间接播报即可。也防止了应用微信 Context 初始化本人注册讯飞的难堪与 TextVoiceHelper 在注册讯飞后,获取微信聊天数据遇到过程通信的问题。
- 依照上述思路,将微信文本数据监听这块视为 Socket 的发送端,本身插件注册服务视为 Socket 承受端,那么整体语音播报解决流程是不是更清晰简洁了呢?
通过将微信与插件分为客户发送端与服务承受端,失去如下的 Socket 客户端代码:
-
private static void connectTCPServer() {
Socket socket = null;
while (socket == null) {try {socket = new Socket("localhost", 8688); mClientSocket = socket; mClientPrintWriter = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())), true); LoggerUtils.xd("genial sir connect tcp server success."); } catch (IOException e) {e.printStackTrace(); SystemClock.sleep(1000); LoggerUtils.xd("genial sir connect tcp server failed, retry...");
}
}try {
// 承受服务器端的音讯
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
while (true) {LoggerUtils.xd("genial sir receive br :" + br); String msg = br.readLine(); LoggerUtils.xd("genial sir receive :" + msg); if (msg.contains("Close Socket service")) {break;}
}
LoggerUtils.xd(“genial sir quit…”);
CloseUtils.closeQuietly(mClientPrintWriter);
CloseUtils.closeQuietly(br);
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
Socket 服务端初始化与实现:
- private void initSocket() {
// 启动 Socket 服务类
Intent serviceIntent = new Intent(MainActivity.this, VoiceSocketManager.class);
startService(serviceIntent);
} -
public class VoiceSocketManager extends Service {
private TTSUtils ttsUtils;
private boolean mIsServiceDestroyed = false;@Override
public void onCreate() {super.onCreate(); new Thread(new TcpServer()).start();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {return null;
}
private class TcpServer implements Runnable {
@Override
public void run() {
ServerSocket serverSocket;
try {
// 监听本地 8688 端口
serverSocket = new ServerSocket(8688);
} catch (IOException e) {LoggerUtils.d("establish tcp server failed, port:8688");
e.printStackTrace();
return;
}
while (!mIsServiceDestroyed) {
try {
// 承受客户端申请
final Socket client = serverSocket.accept();
new Thread() {
@Override
public void run() {
try {responseClient(client);
} catch (IOException e) {e.printStackTrace();
}
}
}.start();} catch (Exception e) {LoggerUtils.d("error" + e.toString());
e.printStackTrace();}
}
}
}
private void responseClient(Socket client) throws IOException {Context applicationContext = getApplication().getApplicationContext();
initXF(applicationContext);
// 用于承受客户端音讯
BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream()));
// 用于向客户端发送音讯
PrintWriter printWriter = new PrintWriter(new BufferedWriter(new OutputStreamWriter(client.getOutputStream())), true);
String clientContent;
while (true) {clientContent = in.readLine();
LoggerUtils.d("responseClient msg from client:" + clientContent);
if ("Close socket".equals(clientContent)) {
// 客户端断开链接
if (ttsUtils != null) {ttsUtils.speak("我是 Voice Socket Manager, 客户端申请断开链接,撒哟啦啦");
}
LoggerUtils.d("客户端断开链接.");
break;
}
if (ttsUtils != null) {ttsUtils.speak(clientContent);
}else {LoggerUtils.e("ttsUtils is null.");
}
}
LoggerUtils.d("client quit.");
// 敞开流
CloseUtils.closeQuietly(printWriter);
CloseUtils.closeQuietly(in);
client.close();}
private void initXF(Context context) {SpeechUtility.createUtility(context, "appid=5d07631c");
Setting.setShowLog(true);
ttsUtils = TTSUtils.getInstance(context);
ttsUtils.init();}
@Override
public void onDestroy() {
mIsServiceDestroyed = true;
super.onDestroy();}
}
-
我的项目TextVoiceHelperGithub 地址
- https://github.com/GenialSir/…
- 转发注明出处即可,心愿对正在浏览你有所启发与帮忙