微信 iOS 客户端任意地区修改实践
最近接触 iOS 逆向开发,看了各式各样的自动抢红包 Tweak之后,于是自己也来动手实践一下 Tweak:WeChatTweak-iOS~
0x01 准备
- 越狱手机一台 - 主要用于脱壳(32Bit 最佳,Hopper Disassembler能直接看伪代码)
- Hopper Disassembler - 反编译工具,能够将二进制执行文件反编译出伪代码
- ldid - 签名工具
- chisel - LLDB 增强
- class-dump - 导出可执行文件的 Header
- dumpdecrypted - 脱壳工具
- Clutch - 高级脱壳工具
- theos - 越狱开发工具包,安装与配置可参考Theos 安装与配置
0x02 debugserver 与 LLDB
debugserver为LLDB服务端,接收LLDB所提供的命令,并且进行相应的执行。
获取 debugserver
- debugserver 存放于 iPhone 设备上
/Developer/usr/bin
目录,如果没有,打开 Xcode 并捅一下手机就能安装上 - 通过
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>
- 执行命令:
ldid -Sent.xml debugserver
- 通过
scp
或者其它任何手段复制到设备/usr/bin
目录下 - 并加上执行权限(chmod +x)
- 执行
debugserver
命令无报错即可
LLDB 连接 debugserver
- 打开微信客户端
- 通过ssh连接至设备,执行命令:
debugserver *:6666 -a 'WeChat'
- 电脑中运行 LLDB 并执行命令:
process connect connect://devcie_ip:6666
- 设备处于
process interrupted
状态,连接成功
补充知识
在进行 LLDB 调试之前需要了解一些概念:
- 基址:模块在内存中的起始地址
- ASLR:地址空间随机布局地址(通过
image list -o -f
查看)
因此:内存地址 = ASLR + 基址
Hopper Disassembler 进行静态分析中显示的都是基址,LLDB 操作的都是内存地址,因此要进行换算。
0x03 定位函数
数据提交时机
想要实现任意地区修改,首先要知道数据提交的时机,来 Hook 相关的方法修改提交的参数。观察微信客户端修改个人资料页,大概猜测出两种修改机制:
- 每修改一项信息,在 Pop 一个 ViewController 的时候提交数据保存
- 修改好所有的信息,在 Pop 回至 TopViewController 的时候提交数据保存
验证猜想也很简单,找多一台设备进行观察就可以了,结果猜想 2是正确的!
找出对应 Controller
- 打开个人信息页面并执行命令:
process interrupt
- 执行命令
pviews
输出UI层级树,不难找出MMTableView
及其内存地址 - 执行命令
presponder MMTableView内存地址
输出响应层级树,找出个人信息页面的 Controller 为SettingMyProfileViewController
- 重复以上步骤找出地区选择页面的 Controller
找出相关方法
利用 dumpdecrypted
脱壳,再通过 class-dump
Dump出所有 .h
文件(过程就不再赘述),要关注的是所有与 SettingMyProfileViewController
和 MMRegionPickerViewController
有关的 .h
文件。
其中发现 MMRegionPickerViewControllerDelegate-Protocol.h
文件,显然地区选择页面是通过Delegate回调的方式进行传值,并且在 SettingMyProfileViewController.h
文件中找到Delegate对应的方法,因此可以确定 SettingMyProfileViewController
就是实现了对应的Delegate,接受地区参数。
动态调试
既然已经基本上确定了参数传递的方法,那就可以通过 Hopper Disassembler + LLDB 进行开刀!
- 在 Hopper Disassembler 中直接搜索
SettingMyProfileViewController MMRegionPickerDidChoosRegion
,找出方法入口基址 - 根据ASLR与基址计算出内存地址,通过LLDB设置断点:
br s -a 内存地址
- 选择任意地区触发断点
- 执行命令:
po [$r2 class]
查看参数类型 - 执行命令:
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
- 编辑
Makefile
中THEOS_DEVICE_IP
为设备IP - 执行命令:
make package install
- 查看效果