欲练此功,必先…越狱

当有了root权限,手机还是手机吗?–

自从有一次在用XCode模拟地理位置等待唤醒App的时候,忽然内心某处一个声音想起:Hey!你可是拥有root权限的男人!为什么不看下是哪个方法唤起的App,直接调用这个方法呢?然后折腾了几个小时终于实现了这样一个“模拟地理位置唤醒App到后台”的越狱App,从此便对越狱开发有了更进一步的理解。

直到有一天我把手机落在了家里,当时十分担心会错过什么重要的信息,尽管这只是个略大于0的小概率事件,而且即便是有也不见得会是什么好消息。然而那个声音再次响起。毕竟,错过白条的最后还款日是会影响信用的。于是便有了下文。

最初走的一些弯路就按下不表了,起初我是希望从短信应用入手,看下能不能hook收到短信息时执行的方法,我先后dump了MobileSMScommoncenterimagentMessagesNotificationViewServiceMessagesViewService 的头文件,但是找了个遍也没找到“看起来有用”的方法。也许这里面是有一些神奇的方法的,但是我没有找到。

10086

(此处应感谢10086提供的大力支持)

上述方法行不通以后,我把目光放在了SpringBoard的锁屏通知页上。

springboard_10086

(再次感谢)

是的,我是在iOS8上面实现的功能。对于iOS9以上的系统,由于通知界面的变化,事情也许会更复杂一些。但这不是我使用iOS8的原因,我之所以这么做的主要动因是——这是我唯一的越狱设备。(我发现我从国外的技术书里学到的似乎比我想象的要多)

显然,这是一个tableView,每一条通知对应一个cell。但是只有当我们知道了具体是哪个类,才能施展Hook大法。如何找到这个类呢?

Cycript

此神器可以令你在任何运行中的App里随意进出。如果你不是很了解,不用担心,其基本用法十分简单,一看便知。

首先我们要使用该神器进入SpringBoard,也就是iOS的桌面应用。

cycript_springboard

我们打印出了SpringBoard当前的keyWindow!这简直就是无需源代码、无需打断点的lldb

于是,找到那个tableView便十分简单了。

你可以通过打印recursiveDescription来查找这个类,事实上,Cycript提供了更简单的方法:choose()

choose_UITableView

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

我们通过Cycript找到了SBLockScreenNotificationTableView,接下来只剩下勾住这个类的某些方法,并执行我们自己的代码。在越狱开发的圈子里,这样一个东西被称作一个tweak。而THEOS便是创建tweak的工具之一(还有一个叫iOSOpenDev,但是已经很久没人维护了)。

我们使用THEOS创建一个tweak,并设置目标APP为SpringBoard

create_tweak

THEOS将为我们创建好几乎所有东西。其中的Tweak.xm便是放置钩子代码的地方。

首先我们如上所述钩住SBLockScreenNotificationTableView (Tweak语法也是一目了然的)

static id <UITableViewDataSource> theDataSource;
static id <UITableViewDelegate> theDelegate;

%hook SBLockScreenNotificationTableView

- (void)setDelegate:(id)orgDlg
{
  %orig;    // 执行原方法体
  theDelegate = orgDlg;
}

- (void)setDataSource:(id)orgDS
{
  %orig;   // 执行原方法体
  theDataSource = orgDS;
}
%end

这里先保存了tableViewdelegatedataSource,这样便能随时获取任意一个cell了。

然后经过一番调试,发现每次有新消息都是调用了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来做“静默”发送邮件的事情!我可能要失败… … 啊!做了这么多,拿到了信息却传递不出去!

看来,是时候插上网线睁开眼睛认真Google一下了

找来找去,发现方法还是有的: 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 setDelegate:self];
        [messageDelivery deliverAsynchronously];
    }
}

result

👆收到的邮件(当然会有数秒延时)

还有更多

思考:计算机指令和数据的区别是————没有区别!

你可以收到消息,你就可以收到指令。于是你并不需要一个设置页面便可以随意的开关这个功能,设置过滤等。

cmd

想一想,这将成为一个入口,你可以用它来远程控制你的手机,开关一些你实现的小功能。比如向你汇报位置,拍照并发送给你等等。当然,前提是它没有被关机,能够访问互联网,而且刚好处于锁屏状态。