Long Long Ago
小半年前,我曾寫下越獄平臺下的防QQ撤回的相關文章:逆向的嘗試:讓你的 QQ 不被撤回。前幾天,閱讀了楊蕭玉大神的 Make WeChat Great Again。(⊙v⊙)嗯,突發奇想,好,那繼續弄未越獄平臺下的 QQ 的防撤回吧!
配置歷險裝備
- iOSOpenDev 正常來說都會安裝失敗,這里給個修復的工具 iOSOpenDevInstallFix
- yololib dylib 注入工具
- machOview 查看 dylib 是否注入成功
- cycript 強大的調試工具
- class-dump 砸出頭文件
- iOS App Signer 圖形化重簽名工具
本文不涉及具體安裝過程,請自行 google!
歷險開始咯
1. 尋找目標 - 撤回函數
(⊙v⊙)嗯 ... 會不會和我半年前猜的一樣呢,經過驗證,果然是一樣的,具體請看逆向的嘗試:讓你的 QQ 不被撤回。
撤回函數分別是如下幾個,這里只需干掉第一個就可以了。
- (void)handleRecallNotify:(struct RecallModel *)arg1 isOnline:(_Bool)arg2;
- (void)handleDiscussRecallNotify:(char *)arg1 bufferLen:(unsigned int)arg2 isOnline:(_Bool)arg3;
- (void)handleGroupRecallNotify:(char *)arg1 bufferLen:(unsigned int)arg2 isOnline:(_Bool)arg3;
- (void)handleC2CRecallNotify:(const void *)arg1 bufferLen:(int)arg2 subcmd:(int)arg3 isOnline:(_Bool)arg4;
2. 編寫 Tweak
這里我設置了一個 recallStatus,就是可以開關防撤回的功能。開的時候,就直接在函數中 return
,不讓它繼續執行,就是這么簡單粗暴。
CHOptimizedMethod2(self, void, QQMessageRecallModule, handleRecallNotify, RecallModel *, arg1, isOnline, _Bool, arg2){
if ([XRKQQInfos sharedInstance].recallStatus) {
return ;
}
CHSuper2(QQMessageRecallModule, handleRecallNotify, arg1, isOnline, arg2);
}
3. dylib 注入
將第二步得到的 dylib
庫注入到從某助手下載的已經砸殼的 QQ 里面。
./yololib QQ hook_qq.dylib
使用 machOview
來查看是否注入成功。截圖中 dylib
的名稱和上面不同,就把它看成 hook_qq.dylib
就可以。
4. 重簽名
由于 QQ 會進行簽名驗證,所以你要找一個通用匹配的證書來重簽名。
5. 安裝
使用Xcode 自帶的安裝工具就可以。
6. 測試
歷險再啟程
弄個撤回好像有點單調,就問我同學有沒有好的意見,他是這樣說的。
1. 尋找目標 - QQ level 的設置
推薦閱讀:http://iphonedevwiki.net/index.php/Cycript_Tricks
1.1 注入 Cycript.framework 并安裝到手機
略
1.2 連上手機調試
觀察這個頁面,我最開始的思路是先找到這個 level
展示的 view
然后在它 controller
里面直接修改的。所以我就 choose(UILabel)
,然后再 iterm
里面搜索 Tsui YuenHong
,找到它的 superview
,再根據這個 superview
找到它的 subviews
,里面肯定包括這個 level
展示的 view
,就可以修改到等級了。
$ ./cycript -r 192.168.0.202:8888
cy# choose(UILabel)
...... 一堆東西輸出
cy# #0x117f3b9e0
#"<UIButtonLabel: 0x117f3b9e0; frame = (0 6.5; 195.5 33.5); text = 'Tsui YuenHong'; opaque = NO; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x113b291e0>>"
cy# #0x117f3b9e0.superview
#"<UIButton: 0x113c69030; frame = (47 -3; 196 46); opaque = NO; layer = <CALayer: 0x17022f420>>"
cy# #0x117f3b9e0.superview.superview
#"<UIControl: 0x113bbc130; frame = (20 113.38; 283 85); layer = <CALayer: 0x11277edb0>>"
cy# #0x117f3b9e0.superview.superview.superview
#"<DrawerMyInfoView: 0x105d4abf0; baseClass = UIButton; frame = (0 0; 375 213.38); opaque = NO; layer = <CALayer: 0x17022f440>>"
找到了這個 view
的父容器是 DrawerMyInfoView
,再找DrawerMyInfoView
的響應者就可以找到它對應的 controller
。
cy# choose(DrawerMyInfoView)
[#"<DrawerMyInfoView: 0x105d4abf0; baseClass = UIButton; frame = (0 0; 375 213.38); opaque = NO; layer = <CALayer: 0x17022f440>>"]
cy# #0x105d4abf0.nextResponder
#"<UIView: 0x10f4595b0; frame = (0 0; 375 667); clipsToBounds = YES; autoresize = W+H; layer = <CALayer: 0x175032400>>"
cy# #0x105d4abf0.nextResponder.nextResponder
#"<DrawerContentsViewController: 0x106828a00>"
是你了,皮皮蝦,DrawerMyInfoView & DrawerContentsViewController
。興致勃勃地在 QQ 頭文件里面找,發現并木有引用到和 level 相關的方法。
好吧,那我就是搜索全部頭文件來找到和 level 相關的方法,excuse me?發現并木有。
1.3 轉戰 hopper
在 hopper
中偶然發現有個一個類叫 QQAcountModel
,這個類有點特殊,在 class-dump
出來的頭文件是看不到的,而且它的 description
方法是被重寫了,這樣在一定程度可以避免被逆向的可能。這里我選擇 hook
進 description
和 level
方法。
CHOptimizedMethod0(self, unsigned int, QQAccountsModel, level){
return [XRKQQInfos sharedInstance].qqLevel;
}
CHOptimizedMethod0(self, NSString *, QQAccountsModel, description){
return [NSString stringWithFormat:@"%p", self];
}
2. 其余步驟和防撤回功能一樣
...
歷險成果
- 防撤回功能
- 修改 Level(自娛自樂的功能)
為了方便修改,我還在設置界面新增自定義選項(不過,本地化并木有做,每次重啟都恢復默認項)。
如果你發現什么有趣的功能,我們可以一起探討,Make QQ Fun Again!
項目的 Github 地址:https://github.com/xurunkang/MakeQQFunAgain