我是如何利用 Xcode 调试开发微信消息预览插件的

Posted iOS开发by唐巧

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了我是如何利用 Xcode 调试开发微信消息预览插件的相关的知识,希望对你有一定的参考价值。

推荐序

相对于 android 来说,ios 安全一直是被忽视的领域。就拿微信来说,由于微信的代码没有混淆,所以可以很方便地对它进行修改。在本文中,作者介绍了通过动态库注入来修改微信的技术细节。

需要强调的是,我接受该作者投稿,更多的是希望借此推动包括微信在内的众多 iOS App 关注代码安全。其实应用一些简单的代码混淆技术,App 的修改都会难得多。

作者介绍:Corbin,中山大学大四准毕业生,从事 iOS 开发。喜欢学习、专研技术。有同样兴趣的欢迎加我微信 aiyowei772950145。长得不帅放妹子照片涨涨粉吧~

序言

效果图

我是如何利用 Xcode 调试开发微信消息预览插件的

需求分析

代码分析

结合需求,需要 hook 的主要是微信消息通知 Method,聊天界面 ViewController,网页 ViewController。利用工具 class-dump, Hopper Disassembler 很快定位出需要 hook 的微信代码:

  • -[CMessageMgr AsyncOnAddMsg:MsgWrap:]

  • -[BaseMsgContentViewController viewDidLoad]

  • -[MMWebViewController viewDidLoad]

磨刀霍霍

定位出 hook 代码段,接下来要做的就是写代码了:

  1. Xcode 现在支持建立动态库工程,但生成的是 framework,可以通过修改工程文件下的 project.pbxproj productType = "com.apple.product-type.framework"; => productType = "com.apple.product-type.library.dynamic"
    我是如何利用 Xcode 调试开发微信消息预览插件的  

  2. 利用 iOSOpenDev 也可以快速生成动态库工程。
    我是如何利用 Xcode 调试开发微信消息预览插件的

这里注意要设置好签名证书,后续可能因为证书问题导致失败。

- (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];
}

最后关键的一步~

#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,安装到手机。
注意注意,博文的标题里有 “调试”,调试!!!怎么做呢?

偷天换日

细心观察可以发现:

  1. 任意一个 App 工程,运行后在 Derived Data 文件夹都有对应的 .app 文件

  2. 在 Build Phases 中增加 Run Script,可以在编译工程后执行自定义脚本。

于是,一招偷天换日招数就想出来了(通过脚本,在编译工程后,利用新生成的动态库生成 WeChat.app, 替换原有目录下的 App 文件)

  1. 在原有工程中增加 Application Target

  2. 在 Build Phases 中设置 Target Dependencies,增加 dylib,确保每次 run app 都会编译最新的 dylib
    我是如何利用 Xcode 调试开发微信消息预览插件的  

  3. 然后增加 Run Script(修改微信可执行文件,重签名,生成新的 app)

接下来的事情(安装 app,打开手机 app,lldb 调试)就交给 Xcode 做了。

#!/bin/bash

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}

常见问题

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 目录下

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 进行重签名—解密 [上](http://alayshchen.github.io/2015/11/05/Resign-AppStore-App/)

相关资料

  • insert_dylib:https://github.com/Tyilo/insert_dylib

  • iOS 安全攻防(十二):iOS7 的动态库注入:http://www.cocoachina.com/industry/20140211/7800_4.html

全文完。
感谢投稿,本文所有的打赏归作者 Corbin 所有。



赞助商:


以上是关于我是如何利用 Xcode 调试开发微信消息预览插件的的主要内容,如果未能解决你的问题,请参考以下文章

JAVA开发微信小程序客服,如何让客服使用手机接收用户消息啊?

配置webstorm开发微信小程序

用 Node.js 开发微信墙简明教程

C#开发微信门户及应用--微信消息的处理和应答

uni-app开发微信小程序使用微信小程序的插件

记录uni-app开发微信小程序踩过得坑,包括iconfont不显示v-if插槽上不生效pdf在线预览等