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

1,868 阅读4分钟
原文链接: blog.sunnyyoung.net

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

一、准备

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

二、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操作的都是内存地址,因此要进行换算。


三、定位函数

数据提交时机

想要实现任意地区修改,首先要知道数据提交的时机,来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-dumpDump出所有.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就能实现修改~


四、编写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. 查看效果