最近忙於工作,經過我‘鹹魚’的時間終於是有了些眉目,期間也走了許多彎路,現在做個筆記讓也想學習的朋友們做個參考。
接收的圖
發送的圖
老規矩,來分析一波吧!
剛開始沒發現,看這個佈局分析得到微信的聊天列表和這個聊天界面在同一層級,導致我後面hook listview的時候老是跑到首頁的的列表上。
看到了關鍵點-com.tencent.mm.ui.base.MMPullDownView 在我的上篇的提到的ChattingUI$a類裡面,這個才是正主。
那麼接下來我們在看看撤回消息的時候到底發生了什麼。消息是存到本地數據了的,至於為什麼這麼說,請繼續看
com.tencent.mm.ui.chatting.ChattingUI
話說有這麼個類,前面已經提到過與之關聯的類ChattingUI$a 如果不知道為什麼我找到後面幾個關聯類的請看前面的一篇帖子既可以聯繫起來
然後這個類裡面沒有什麼實質性的東西,於是我就點開他的父類看了看
com.tencent.mm.ui.MMFragmentActivity
發現了
com.tencent.wcdb.database.SQLiteDatabase
我不說其他人看到這個會怎麼想,我平時自己寫聊天應用的時候也會把聊天數據存儲在數據庫,於是乎開始hook起來
/*** 直接hook sql達到獲取撤回消息id的目的** @param applicationContext* @param classLoader*/private void hookDB(final Context applicationContext, final ClassLoader classLoader) {
final Class sQLiteDatabase = XposedHelpers.findClass("com.tencent.wcdb.database.SQLiteDatabase", classLoader);
final Class cancellationSignal = XposedHelpers.findClass("com.tencent.wcdb.support.CancellationSignal", classLoader);
if (sQLiteDatabase == null) return;
XposedHelpers.findAndHookConstructor("com.tencent.wcdb.database.SQLiteProgram",
classLoader,
sQLiteDatabase,
String.class,
Object[].class,
cancellationSignal,
new XC_MethodReplacement() {
@Override
protected Object replaceHookedMethod(MethodHookParam param) throws Throwable {
Object[] objArr = (Object[]) param.args[2];
String originalSql = param.args[1].toString();
//打印所有調用SQLiteProgram的sql //LogUtils.e("hookDB", "sql -> " + param.args[1], "objArr:" + JSON.toJSONString(objArr)); if (objArr != null && originalSql.toUpperCase().startsWith("UPDATE MESSAGE")) {
for (Object obj : objArr) {
String sqlParam = obj.toString();//自己撤回10002 別人撤回10000 if (sqlParam.equals("10000")) {//別人撤回 Object[] newObjArr = new Object[2];
//param.args[1] = "UPDATE message SET type=? WHERE msgId=?"; param.args[1] = "select * from message where type=? and msgId=?";
param.args[2] = newObjArr;
newObjArr[0] = 1;
newObjArr[1] = objArr[objArr.length - 1];
//param.args[1] = "UPDATE message SET content=(select (select content from message where msgId = ?)||X'0D'||X'0A'||X'0D'||X'0A'||("wxInvoke臥槽,TA竟然要撤回上面的信息wxInvoke")),msgId=?,type=? WHERE msgId=?"; LogUtils.e("hookDB", "originalSql->" + originalSql, "newSql->" + param.args[1], "sqlParam->" + JSON.toJSONString(newObjArr));
WxChatInvokeMsg msg = new WxChatInvokeMsg();
msg.setMsgId(newObjArr[1].toString());
WxChatInvokeMsgDB.insertData(applicationContext, msg);
}
}
}
return XposedBridge.invokeOriginalMethod(param.method, param.thisObject, param.args);
}
});
}
看到上面我hook的是SQLiteProgram 會有朋友問為啥?
因為我SQLiteDatabase在裡面找insert和update的方法發現裡面最後走進入了SQLiteProgram 哈哈! 偷個懶我就不具體截圖找給大家看了,然後把撤回的消息id記錄在本地,作為後面標記的依據。
接下來開始處理撤回效果了
/** * 微信聊天界面 * * @param applicationContext * @param classLoader */ private void hookWxChatUIMM(final Context applicationContext, final ClassLoader classLoader) {
XposedHelpers.findAndHookMethod("com.tencent.mm.ui.base.MMPullDownView",
classLoader,
"onLayout",
boolean.class,
int.class,
int.class,
int.class,
int.class,
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
super.beforeHookedMethod(param);
ViewGroup mMPullDownView = (ViewGroup) param.thisObject;// if (mMPullDownView.getVisibility() == View.GONE) return; for (int i = 0; i < mMPullDownView.getChildCount(); i++) {
View childAt = mMPullDownView.getChildAt(i);
if (childAt instanceof ListView) {
final ListView listView = (ListView) childAt;
final ListAdapter adapter = listView.getAdapter();
XposedHelpers.findAndHookMethod(adapter.getClass(),
"getView",
int.class,
View.class,
ViewGroup.class,
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
super.beforeHookedMethod(param);
int position = (int) param.args[0];
View view = (View) param.args[1];
JSONObject itemData = null;// LogUtils.i(position, view.toString()); if (position < adapter.getCount()) {
itemData = JSON.parseObject(JSON.toJSONString(adapter.getItem(position)), JSONObject.class);
int itemViewType = adapter.getItemViewType(position);// LogUtils.i(itemViewType); //經過以上代碼可以知道 itemViewType == 1的時候打印的值是正常對話列表的值 if (itemData != null && (view != null && view.toString().contains("com.tencent.mm.ui.chatting.viewitems.p"))) {// if (itemData != null && itemViewType == 1 && (view != null && view.toString().contains("com.tencent.mm.ui.chatting.viewitems.p"))) { String field_msgId = itemData.getString("field_msgId");
WxChatInvokeMsg wxChatInvokeMsg = WxChatInvokeMsgDB.queryByMsgId(applicationContext, field_msgId);
ViewGroup itemView = (ViewGroup) view;
View itemViewChild = itemView.getChildAt(0);
Object tag = itemViewChild.getTag(R.id.wx_parent_has_invoke_msg);
TextView textView;
if (tag == null) {
textView = new TextView(applicationContext);
textView.setGravity(Gravity.CENTER);
textView.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
itemViewChild.setTag(R.id.wx_parent_has_invoke_msg, textView);
textView.setId(R.id.wx_invoke_msg);
itemView.addView(textView);
} else {
textView = (TextView) itemViewChild.getTag(R.id.wx_parent_has_invoke_msg);
}
textView.setText("");
textView.setVisibility(View.GONE);
if (wxChatInvokeMsg != null) {
textView.setPadding(10, 5, 10, 5);
textView.setBackgroundDrawable(ShapeUtil.getCornerDrawable());
textView.setTextColor(Color.parseColor("#666666"));
View msgView = itemView.getChildAt(3);
RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) textView.getLayoutParams();
lp.addRule(RelativeLayout.CENTER_HORIZONTAL);
lp.addRule(RelativeLayout.BELOW, msgView.getId());
lp.bottomMargin = 50;
textView.setText("這小子想撤回這個消息");
textView.setVisibility(View.VISIBLE);
LogUtils.i(position, view, itemViewType, itemData.toJSONString());
}
}
}// }
});
break;
}
}
}
});
}
記住hook 這個listview的時候會遇到我文章片頭說的問題,導致每個item的view 對應錯誤,所以我加了過濾標識。
以上內容僅僅作為學習交流。代碼很簡單,樂趣在於找到這些代碼位置的過程。
閱讀更多 看雪學院 的文章