平时在微信使用时,经常出现这样的场景:阅读公众号文章时,突然一条好友消息来了。这时一直很纠结,我该停止阅读还是继续阅读。假如我可以预览消息的话,甚至回复消息后快速回来继续阅读。那太好不过了。
学习过iOS逆向开发的话,利用theos在越狱机器上实现还是可以的。但由于日常使用的是非越狱机器,鱼和熊掌都想要,只好在万能的google上寻找资料,终于找到了insert_dylib工具和念茜的博客上动态库注入相关知识。OK,开工!
效果图
需求分析
公众号文章界面(网页):收到消息后,显示消息内容
公众号文章界面(网页):点击消息内容进入对应聊天界面
聊天界面:点击网页标志,跳回公众号文章界面(网页)
代码分析 结合需求,需要hook的主要是微信消息通知Method
,聊天界面ViewController
,网页ViewController
。利用工具class-dump
, Hopper Disassembler
很快定位出需要hook的微信代码,-[CMessageMgr AsyncOnAddMsg:MsgWrap:]
-[BaseMsgContentViewController viewDidLoad]
-[MMWebViewController viewDidLoad]
磨刀霍霍
定位出hook代码段,接下来要做的就是写代码了。
Xcode现在支持建立动态库工程,但生成的是framework,可以通过修改工程文件下的project.pbxproj productType = "com.apple.product-type.framework"; => productType = "com.apple.product-type.library.dynamic"
利用iOSOpenDev也可以快速生成动态库工程。
这里注意要设置好签名证书,后续可能因为证书问题导致失败。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 - (void)cb_AsyncOnAddMsg:(NSString *)msg MsgWrap:(CMessageWrap *)wrap { [self cb_AsyncOnAddMsg:msg MsgWrap:wrap]; [CBNewestMsgManager sharedInstance].username = msg; [CBNewestMsgManager sharedInstance].content = wrap.m_nsContent; [[NSNotificationCenter defaultCenter] postNotificationName:CBWeChatNewMessageNotification object:nil]; } - (void)cb_msgContentViewControllerViewDidLoad { [self cb_msgContentViewControllerViewDidLoad]; UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(self.view.frame.size.width - 40, 74, 40, 40)]; UIImage *image = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"WeChatMsgPreview_safari@2x" ofType:@"png"]]; [button setImage:image forState:UIControlStateNormal]; [button addTarget:self action:@selector(backToWebViewController) forControlEvents:UIControlEventTouchUpInside]; [self.view addSubview:button]; } - (void)cb_webViewControllerViewDidLoad { [self cb_webViewControllerViewDidLoad]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(cb_didReceiveNewMessage) name:CBWeChatNewMessageNotification object:nil]; } - (void)cb_didReceiveNewMessage { NSString *username = [CBNewestMsgManager sharedInstance].username; NSString *content = [CBNewestMsgManager sharedInstance].content; CContactMgr *contactMgr = [[objc_getClass("MMServiceCenter") defaultCenter] getService:[objc_getClass("CContactMgr") class]]; CContact *contact = [contactMgr getContactByName:username]; dispatch_async(dispatch_get_main_queue(), ^{ NSString *text = [NSString stringWithFormat:@" %@: %@ ", contact.m_nsNickName, content]; [CBMessageHud showHUDInView:self.view text:text target:self action:@selector(backToMsgContentViewController)]; }); } - (void)backToWebViewController { NSArray *webViewViewControllers = [CBNewestMsgManager sharedInstance].webViewViewControllers; if (webViewViewControllers) { [[objc_getClass("CAppViewControllerManager") getCurrentNavigationController] setViewControllers:webViewViewControllers animated:YES]; } } - (void)backToMsgContentViewController { // 返回聊天界面ViewController前记录当前navigationController的VC堆栈,以便快速返回 NSArray *webViewViewControllers = [objc_getClass("CAppViewControllerManager") getCurrentNavigationController].viewControllers; [CBNewestMsgManager sharedInstance].webViewViewControllers = webViewViewControllers; // 返回rootViewController UINavigationController *navVC = [objc_getClass("CAppViewControllerManager") getCurrentNavigationController]; [navVC popToRootViewControllerAnimated:NO]; // 进入聊天界面ViewController NSString *username = [CBNewestMsgManager sharedInstance].username; CContactMgr *contactMgr = [[objc_getClass("MMServiceCenter") defaultCenter] getService:[objc_getClass("CContactMgr") class]]; CContact *contact = [contactMgr getContactByName:username]; MMMsgLogicManager *logicMgr = [[objc_getClass("MMServiceCenter") defaultCenter] getService:[objc_getClass("MMMsgLogicManager") class]]; [logicMgr PushOtherBaseMsgControllerByContact:contact navigationController:navVC animated:YES]; }
最后关键的一步~
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #define CBHookInstanceMethod(classname, ori_sel, new_sel) { Class class = objc_getClass(#classname); Method ori_method = class_getInstanceMethod(class, ori_sel); Method new_method = class_getInstanceMethod(class, new_sel); method_exchangeImplementations(ori_method, new_method); } static void __attribute__((constructor)) initialize(void) { CBHookInstanceMethod(CMessageMgr, @selector(AsyncOnAddMsg:MsgWrap:), @selector(cb_AsyncOnAddMsg:MsgWrap:)); CBHookInstanceMethod(BaseMsgContentViewController, @selector(viewDidLoad), @selector(cb_msgContentViewControllerViewDidLoad)); CBHookInstanceMethod(MMWebViewController, @selector(viewDidLoad), @selector(cb_webViewControllerViewDidLoad)); }
好了,command+B 成功生成动态库文件,下一步,利用insert_dylib修改微信可执行文件,重签名,生成新的微信app,安装到手机。嗯嗯,这样文章到这里就结束了~~
慢着,真正开发时哪会这么简单,代码一次成功。一旦代码出现问题,我们需要一直手动重复这样的工作:修改代码,生成dylib,修改微信可执行文件,重签名,生成新的app,安装到手机。
注意注意,博文的标题里有「调试」,调试!!!怎么做呢?
偷天换日 细心观察可以发现
任意一个app工程,run后在Derived Data文件夹都有对应的。app文件
在Build Phases中增加Run Script,可以在编译工程后执行自定义脚本。
于是,一招偷天换日招数就想出来了(通过脚本,在编译工程后,利用新生成的动态库生成WeChat.app, 替换原有目录下的app文件)
在原有工程中增加Application Target
在Build Phases中设置Target Dependencies,增加dylib,确保每次run app都会编译最新的dylib
然后增加Run Script(修改微信可执行文件,重签名,生成新的app)接下来的事情(安装app,打开手机app,lldb调试)就交给Xcode做了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 BUNDLEIDENTIFIER=com.tencent.xin APPLICATIONIDENTIFIER=***.${BUNDLEIDENTIFIER} WECHATFILEPATH=***/apps/WeChat LIBNAME=$(find *.dylib) TEMPDIR=$(mktemp -d) ORIGINDIR=$(pwd) # 0.get argv if [ x$1 != x ] then BUNDLEIDENTIFIER=$1 fi # 1.unzip ipa if [ $arch == "arm64" ] then unzip -qo ${WECHATFILEPATH}/WeChat-dump-arm64.ipa -d $TEMPDIR else unzip -qo ${WECHATFILEPATH}/WeChat-dump-armv7.ipa -d $TEMPDIR fi # 2.copy files cp ${WECHATFILEPATH}/embedded.mobileprovision $TEMPDIR/ cp ${WECHATFILEPATH}/entitlements.plist $TEMPDIR/ cp ${LIBNAME} $TEMPDIR/ # 3.resign cd $TEMPDIR plutil -replace application-identifier -string ${APPLICATIONIDENTIFIER} entitlements.plist plutil -replace CFBundleIdentifier -string ${BUNDLEIDENTIFIER} Payload/WeChat.app/Info.plist mv ${LIBNAME} Payload/WeChat.app/ insert_dylib --all-yes @executable_path/${LIBNAME} Payload/WeChat.app/WeChat mv Payload/WeChat.app/WeChat_patched Payload/WeChat.app/WeChat chmod +x Payload/WeChat.app/WeChat rm -rf Payload/WeChat.app/_CodeSignature rm -rf Payload/WeChat.app/PlugIns rm -rf Payload/WeChat.app/Watch cp embedded.mobileprovision Payload/WeChat.app/ codesign -fs "iPhone Developer: *** (***)" --no-strict --entitlements=entitlements.plist Payload/WeChat.app/${LIBNAME} codesign -fs "iPhone Developer: *** (***)" --no-strict --entitlements=entitlements.plist Payload/WeChat.app # 4.end mv Payload/WeChat.app ${ORIGINDIR} rm -rf ${TEMPDIR}
常见问题 1 2 3 4 5 6 7 dyld: Library not loaded: @executable_path/libWeChatMsgPreview.dylib Referenced from: /var/mobile/Containers/Bundle/Application/55148CD1-0D6E-4F6B-B55C-08261695B408/WeChat.app/WeChat Reason: image not found
原因:没拷贝libWeChatMsgPreview.dylib到WeChat.app目录下
1 2 3 4 5 6 7 8 9 10 11 dyld: Library not loaded: @executable_path/libWeChatMsgPreview.dylib Referenced from: /var/mobile/Containers/Bundle/Application/F62EF4DE-7A8E-4564-8839-7FED32FB0927/WeChat.app/WeChat Reason: no suitable image found. Did find: /var/mobile/Containers/Bundle/Application/F62EF4DE-7A8E-4564-8839-7FED32FB0927/WeChat.app/libWeChatMsgPreview.dylib: mmap() errno=1 validating first page of '/var/mobile/Containers/Bundle/Application/F62EF4DE-7A8E-4564-8839-7FED32FB0927/WeChat.app/libWeChatMsgPreview.dylib' /private/var/mobile/Containers/Bundle/Application/F62EF4DE-7A8E-4564-8839-7FED32FB0927/WeChat.app/libWeChatMsgPreview.dylib: mmap() errno=1 validating first page of '/private/var/mobile/Containers/Bundle/Application/F62EF4DE-7A8E-4564-8839-7FED32FB0927/WeChat.app/libWeChatMsgPreview.dylib'
原因:签名不对,需保持重签名时codesign -fs "iPhone Developer: *** (***)" --no-strict --entitlements=entitlements.plist Payload/WeChat.app/${LIBNAME} codesign -fs "iPhone Developer: *** (***)" --no-strict --entitlements=entitlements.plist Payload/WeChat.app
证书一致
脚本中涉及到的WeChat-dump-arm64.ipa需要从越狱机器中提取。对App Store App进行重签名–解密
来源:我是如何利用Xcode调试开发微信消息预览插件的
The Why·Liam·Blog by WhyLiam is licensed under a Creative Commons BY-NC-ND 4.0 International License .
由WhyLiam 创作并维护的Why·Liam·Blog 采用创作共用保留署名-非商业-禁止演绎4.0国际许可证 。
本文首发于Why·Liam·Blog (https://blog.naaln.com) ,版权所有,侵权必究。
本文永久链接:https://blog.naaln.com/2016/03/i-is-how-to-use-xcode-debugging-development-wechat-news-preview-of-the-plugin/