微信 iOS 客户端任意地区修改实践

最近接触 iOS 逆向开发,看了各式各样的自动抢红包 Tweak之后,于是自己也来动手实践一下 Tweak:WeChatTweak-iOS

0x01 准备

  1. 越狱手机一台 - 主要用于脱壳(32Bit 最佳,Hopper Disassembler能直接看伪代码)
  2. Hopper Disassembler - 反编译工具,能够将二进制执行文件反编译出伪代码
  3. ldid - 签名工具
  4. chisel - LLDB 增强
  5. class-dump - 导出可执行文件的 Header
  6. dumpdecrypted - 脱壳工具
  7. Clutch - 高级脱壳工具
  8. theos - 越狱开发工具包,安装与配置可参考Theos 安装与配置

0x02 debugserver 与 LLDB

debugserver为LLDB服务端,接收LLDB所提供的命令,并且进行相应的执行。

获取 debugserver

  1. debugserver 存放于 iPhone 设备上 /Developer/usr/bin 目录,如果没有,打开 Xcode 并捅一下手机就能安装上
  2. 通过 scp 或者其它任何手段复制到电脑上

处理 debugserver

保存以下 xml 并命名为 ent.xml

<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">  
<plist version="1.0">  
<dict>  
    <key>com.apple.springboard.debugapplications</key>
    <true/>
    <key>get-task-allow</key>
    <true/>
    <key>task_for_pid-allow</key>
    <true/>
    <key>run-unsigned-code</key>
    <true/>
</dict>  
</plist>  
  1. 执行命令: ldid -Sent.xml debugserver
  2. 通过 scp 或者其它任何手段复制到设备 /usr/bin 目录下
  3. 并加上执行权限(chmod +x)
  4. 执行 debugserver 命令无报错即可

LLDB 连接 debugserver

  1. 打开微信客户端
  2. 通过ssh连接至设备,执行命令: debugserver *:6666 -a 'WeChat'
  3. 电脑中运行 LLDB 并执行命令: process connect connect://devcie_ip:6666
  4. 设备处于 process interrupted 状态,连接成功

补充知识

在进行 LLDB 调试之前需要了解一些概念:

  • 基址:模块在内存中的起始地址
  • ASLR:虚拟内存起始地址与模块基地址的偏移量(通过 image list -o -f 查看)

因此:内存地址 = ASLR + 基址

Hopper Disassembler 进行静态分析中显示的都是基址,LLDB 操作的都是内存地址,因此要进行换算。


0x03 定位函数

数据提交时机

想要实现任意地区修改,首先要知道数据提交的时机,来 Hook 相关的方法修改提交的参数。观察微信客户端修改个人资料页,大概猜测出两种修改机制:

  1. 每修改一项信息,在 Pop 一个 ViewController 的时候提交数据保存
  2. 修改好所有的信息,在 Pop 回至 TopViewController 的时候提交数据保存

验证猜想也很简单,找多一台设备进行观察就可以了,结果猜想 2是正确的!

找出对应 Controller

  1. 打开个人信息页面并执行命令:process interrupt
  2. 执行命令 pviews 输出UI层级树,不难找出MMTableView及其内存地址
  3. 执行命令 presponder MMTableView内存地址 输出响应层级树,找出个人信息页面的 Controller 为 SettingMyProfileViewController
  4. 重复以上步骤找出地区选择页面的 Controller

找出相关方法

利用 dumpdecrypted 脱壳,再通过 class-dump Dump出所有 .h 文件(过程就不再赘述),要关注的是所有与 SettingMyProfileViewControllerMMRegionPickerViewController 有关的 .h 文件。

其中发现 MMRegionPickerViewControllerDelegate-Protocol.h 文件,显然地区选择页面是通过Delegate回调的方式进行传值,并且在 SettingMyProfileViewController.h 文件中找到Delegate对应的方法,因此可以确定 SettingMyProfileViewController 就是实现了对应的Delegate,接受地区参数。

动态调试

既然已经基本上确定了参数传递的方法,那就可以通过 Hopper Disassembler + LLDB 进行开刀!

  1. 在 Hopper Disassembler 中直接搜索 SettingMyProfileViewController MMRegionPickerDidChoosRegion ,找出方法入口基址
  2. 根据ASLR与基址计算出内存地址,通过LLDB设置断点: br s -a 内存地址

  3. 选择任意地区触发断点

  4. 执行命令: po [$r2 class] 查看参数类型

  5. 执行命令: po $r2 查看参数值

根据输出,发现是 NSArray 类型!并且数组内元素分别为:语言类型、国家、地区。

因此关键方法和传递参数已经确定,只要对其进行 Hook 就能实现修改~


0x04 编写 Tweak

通过 theos 创建 Tweak 模板,编辑 Tweak.xm

%hook MMRegionPickerViewController

- (void)viewDidLoad {
    %orig;

    [[self navigationItem] setRightBarButtonItem:[[UIBarButtonItem alloc] initWithTitle:@"自定义" style:UIBarButtonItemStylePlain target:self action:@selector(setCustomRegion)]];
}

%new
- (void)setCustomRegion {
    UIAlertView *alert = [[UIAlertView alloc] init];
    alert.alertViewStyle = UIAlertViewStyleLoginAndPasswordInput;
    alert.delegate = self;
    alert.title = @"设置自定义地区";
    [alert addButtonWithTitle:@"取消"];
    [alert addButtonWithTitle:@"确定"];
    [alert textFieldAtIndex:0].secureTextEntry = NO;
    [alert textFieldAtIndex:0].placeholder = @"国家";
    [alert textFieldAtIndex:0].keyboardType = UIKeyboardTypeDefault;
    [alert textFieldAtIndex:1].secureTextEntry = NO;
    [alert textFieldAtIndex:1].placeholder = @"地区";
    [alert textFieldAtIndex:1].keyboardType = UIKeyboardTypeDefault;
    [alert show];
}

%new
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
    if (buttonIndex == 1) {
        NSString *country = [alertView textFieldAtIndex:0].text;
        NSString *province = [alertView textFieldAtIndex:1].text;
        [[self delegate] MMRegionPickerDidChoosRegion:@[@"", country, province]];
    }
}

%end

五、安装Tweak

  1. 编辑 MakefileTHEOS_DEVICE_IP 为设备IP
  2. 执行命令: make package install

  3. 查看效果