欲练此功,必先...越狱
当有了root权限,手机还是手机吗?-自言自语
自从有一次在用XCode模拟地理位置等待唤醒App的时候,忽然内心某处一个声音想起:Hey!你可是拥有root权限的男人!为什么不看下是哪个方法唤起的App,直接调用这个方法呢?然后折腾了几个小时终于实现了这样一个“模拟地理位置唤醒App到后台”的越狱App,从此便对越狱开发有了更进一步的认识。
直到有一天我把手机落在了家里,当时十分担心会错过什么重要的信息,尽管这只是个略大于0的小概率事件,而且即便是有也不见得会是什么好消息。然而那个声音再次响起。毕竟,错过白条的最后还款日是会影响信用的。于是便有了下文。
最初走的一些弯路就按下不表了,起初我是希望从短信应用入手,看下能不能hook收到短信息时执行的方法,我先后dump了MobileSMS
、 commoncenter
、 imagent
、 MessagesNotificationViewService
、 MessagesViewService
的头文件,但是找了个遍也没找到“看起来有用”的方法。也许这里面是有一些神奇的方法的,但是我没有找到。
(此处应感谢10086提供的大力支持)
上述方法行不通以后,我把目光放在了SpringBoard
的锁屏通知页上。
(再次感谢)
是的,我是在iOS8上面实现的功能。对于iOS9以上的系统,由于通知界面的变化,事情也许会更复杂一些。但这不是我使用iOS8的原因,我之所以这么做的主要动因是——这是我唯一的越狱设备。(我发现我从国外的技术书里学到的似乎比我想象的要多)
显然,这是一个tableView
,每一条通知对应一个cell
。但是只有当我们知道了具体是哪个类,才能施展Hook大法。如何找到这个类呢?
Cycript(http://www.cycript.org/)
此神器可以令你在任何运行中的App里随意进出。如果你不是很了解,不用担心,其基本用法十分简单,一看便知。
首先我们要使用该神器进入SpringBoard
,也就是iOS的桌面应用。
我们打印出了SpringBoard
当前的keyWindow
!这简直就是无需源代码、无需打断点的lldb
于是,找到那个tableView
便十分简单了。
你可以通过打印recursiveDescription
来查找这个类,事实上,Cycript
提供了更简单的方法:choose()
SBLockScreenNotificationTableView
,嗯,我喜欢苹果的命名方式。
嗯,我们来暴力证明一下:
cy# choose(UILabel)
搜索下关键字找到“10086”所在的label
:
#"<UILabel: 0x13eedefb0; frame = (52 12.6667; 42.3333 18); text = '10086'; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x174c8e100>>"
沿nextResponder
一路向上直到tableView
:
cy# [#0x13eedefb0 nextResponder].nextResponder() .nextResponder() .nextResponder() .nextResponder() #"<SBLockScreenNotificationTableView: 0x13e5d3200; baseClass = UITableView; frame = (0 0; 414 419.667); clipsToBounds = YES; opaque = NO; gestureRecognizers = <NSArray: 0x175845280>; layer = <CALayer: 0x175432c00>; contentOffset: {0, 0}; contentSize: {414, 168.66666158040366}>"
(如果存在优雅的方法请立即告诉我)
有了目标,就可以下钩了
另一个神器:THEOS(https://github.com/theos/theos)
我们通过Cycript
找到了SBLockScreenNotificationTableView
,接下来只剩下勾住这个类的某些方法,并执行我们自己的代码。在越狱开发的圈子里,这样一个东西被称作一个tweak
。而THEOS
便是创建tweak
的工具之一(还有一个叫iOSOpenDev
,但是已经很久没人维护了)。
我们使用THEOS
创建一个tweak
,并设置目标APP为SpringBoard
THEOS将为我们创建好几乎所有东西。其中的Tweak.xm
便是放置钩子代码的地方。
首先我们如上所述钩住SBLockScreenNotificationTableView
(Tweak语法也是一目了然的)
static id <UITableViewDataSource> theDataSource;
static id <UITableViewDelegate> theDelegate;
%hook SBLockScreenNotificationTableView // 指定需要hook的类
- (void)setDelegate:(id)orgDlg // 重写需要hook实例方法{
%orig; // 执行原方法体
theDelegate = orgDlg;
}
- (void)setDataSource:(id)orgDS
{
%orig; // 执行原方法体
theDataSource = orgDS;
}
%end
这里先保存了tableView
的delegate
和dataSource
,这样便能随时获取任意一个cell了。(delegate暂时没用上)
然后经过一番调试,发现每次有新消息都是调用了tableView
的- (void)insertRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation
插入的新cell
。
接下来的事情就十分简单了,我们勾住这个方法便可以知道新消息到达的时机,然后通过上面保存的dataSource
便可以拿到最新的cell。我们需要的一切信息也都包含在这个cell里了。
- (void)insertRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation
{
%orig;
NSIndexPath *lastIndexPath = [indexPaths firstObject];
id cell = [theDataSource tableView:(UITableView *)self cellForRowAtIndexPath:lastIndexPath];
if ([cell isKindOfClass:NSClassFromString(@"SBLockScreenBulletinCell")])
{
// 在cell的子view中找到消息内容,消息发送者等信息
// 把这些信息通过邮件发送出去
// 完整代码请移步我的github(https://github.com/YangXinlei)
}
}
是不是很简单?
可是!等一下!
我知道用openURL:
就可以打开邮箱,但是谁来帮你点的“发送”按钮呢!!?
显然,这件事情并没有想象中那么简单。经过又一番的dump各种相关系统程序的头文件,我沮丧的发现,iOS并没有提供明显的API来做“静默”发送邮件的事情!不过在神奇的*上有位大神提到 MIME.framework里的 MFMutableMessageHeaders 是可以做这件事的。
有了关键字,接下来就好办了。偷偷发邮件的代码大概是下面这样的 ::
if (! shouldIgnore) //不在过滤列表里,邮件发送
{
static NSString *xl_subject = @"iPhone未读消息";
static NSString *xl_mailTo = @"1019158142@qq.com";
static NSString *xl_mailBy = @"杨 鑫磊 <yangxinlei007@live.com>";
if ([xl_relativeDate isEqualToString:@"现在"])
{
NSLog(@"发现未读消息,发送邮件: %@。", xl_mailTo);
MFMessageWriter *messageWriter = [[%c(MFMessageWriter) alloc] init];
MFMutableMessageHeaders *headers = [[%c(MFMutableMessageHeaders) alloc] init];
[headers setHeader:xl_subject forKey:@"subject"];
[headers setAddressListForTo:@[xl_mailTo]];
[headers setAddressListForSender:@[xl_mailBy]];
MFOutgoingMessage *message = [messageWriter createMessageWithString:[NSString stringWithFormat:@"消息源:%@\n内容:%@", xl_sender, xl_message] headers:headers];
MFMailDelivery *messageDelivery = [%c(MFMailDelivery) newWithMessage:message];
[messageDelivery deliverAsynchronously];
}
}
(发件箱需要添加到系统邮件账户里)