本帖最后由 zwo 于 2020-1-19 19:27 编辑
新版Mac wx(2.4.0)带来了一波新功能,包括万众期待的小程序,然而在拥抱新功能的同时,多开插件失效了。
首先想到新版的小程序会不会也是一个小程序对应一个新开的进程?这样小程序挂了不会导致主程序也挂。开一个小程序验证如下
可以看到的确两个实例都是通过主程序启动的。
下面就通过IDA分析一下,把app拖进去,经过若干小时的等待后,IDA完成了分析。由于是复用同一个实例,很可能在程序的入口就做了分支。先看看入口函数的流程,不难看出程序里每个关键的地方几乎都有日志输出,大大方便了我们的分析。
在日志的加持下,重新梳理了一下启动流程如下:
可见如果满足了实例个数小于2就可以直接初始化一个新的微信实例。可以通过hook返回实例数组的方法runningApplicationsWithBundleIdentifier,不过为了避免副作用,还是直接改掉跳转指令吧,也就是把jb改成jmp。
另外一个存在的问题是,如果这样改,会使小程序的正常打开受到影响。可以通过设置个标志来判断是否需要修改跳转指令。
完整代码如下:
[Objective-C] 纯文本查看 复制代码 #import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <objc/message.h>
#import <Cocoa/Cocoa.h>
#include <dlfcn.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <mach-o/dyld.h>
#include <mach-o/loader.h>
#include <mach-o/nlist.h>
#include <stdio.h>
#include <sys/sysctl.h>
#include <mach/mach.h>
#include <unistd.h>
static void patch_mem(uintptr_t p,unsigned int data){
int page = getpagesize();
uintptr_t address = (uintptr_t)(p);
uintptr_t base = address/page * page;
mach_port_t self = mach_task_self();
kern_return_t error;
if((page - (uintptr_t)(p) - base)<12){
page *= 2;
}
if((error = vm_protect(self,base,page,FALSE ,VM_PROT_READ|VM_PROT_WRITE|VM_PROT_COPY))){
return;
}
*(unsigned int *) p = data;
if((error = vm_protect(self,base,page,FALSE,VM_PROT_READ|VM_PROT_EXECUTE))){
return;
}
}
@implementation NSObject (WeChatTweak)
static void __attribute__((constructor)) tweak(void) {
class_addMethod(objc_getClass("AppDelegate"), @selector(applicationDockMenu:), method_getImplementation(class_getInstanceMethod(objc_getClass("AppDelegate"), @selector(tweak_applicationDockMenu:))), "@:@");
[NSObject alwaysOpenNewWeChat];
}
- (void)alwaysOpenNewWeChat
{
NSUserDefaults *defaults=[NSUserDefaults standardUserDefaults];
NSString *flag=[defaults objectForKey:@"com.wechattweak"];
if (flag)
{
const struct mach_header *mhp = _dyld_get_image_header(0);
uintptr_t module_base_cursor = (uintptr_t)mhp;
// 0F 82 9E 02 00 00 jb -> E9 9F 02 00 00 jmp
uintptr_t targetCursor = module_base_cursor + 0xf5a94f;
patch_mem(targetCursor,0x29fe9);
[defaults setObject:nil forKey:@"com.wechattweak"];
[defaults synchronize];
}
}
- (NSMenu *)tweak_applicationDockMenu:(NSApplication *)sender {
NSMenu *menu = [[NSMenu alloc] init];
NSMenuItem *menuItem = [[NSMenuItem alloc] initWithTitle:@"登录新的微信账号" action:@selector(openNewWeChatInstace:) keyEquivalent:@""];
[menu insertItem:menuItem atIndex:0];
return menu;
}
- (void)openNewWeChatInstace:(id)sender {
NSString *applicationPath = [[NSBundle mainBundle] bundlePath];
NSTask *task = [[NSTask alloc] init];
task.launchPath = @"/usr/bin/open";
task.arguments = @[@"-n", applicationPath];
NSUserDefaults *defaults=[NSUserDefaults standardUserDefaults];
[defaults setObject:@"flag" forKey:@"com.wechattweak"];
[defaults synchronize];
[task launch];
}
@end
有一点需要注意的是,如果把上面代码编译成动态库并通过环境变量DYLD_INSERT_LIBRARIES注入,由于NSTask会继承父程序的所有环境变量,建议通过绝对路径来指定,并放在app的沙盒可以访问的位置。适用于以下版本
P.S. 用之前post的app web view调试方法 https://www.52pojie.cn/thread-1068599-1-1.html ,可以看出小程序其实是个web容器,可以通过web调试器看到里面的布局。
可惜网络请求是通过底层的原生代码完成的,没办法直接调试,有时间我还会进一步研究。
|