书本实战 1 备忘录 字数统计 10系统实现 characount for Notes 10


#1

需求: 在IOS10系统上 实现备忘录字数统计功能
环境:5S 10.1.1系统

1. 在界面上显示数字

打印界面UI

Z0041:~ root# ps -e | grep /Applications/
  584 ??         0:07.08 /Applications/MobileMail.app/MobileMail
  686 ??         0:00.36 /Applications/ServerDocuments.app/PlugIns/ServerFileProvider.appex/ServerFileProvider
  896 ??         0:02.39 /Applications/MobileNotes.app/MobileNotes
  912 ttys000    0:00.01 grep /Applications/
Z0041:~ root# 

找到 ICTextView 显示的内容

UIApp.keyWindow.recursiveDescription().toString()

<ICTextView: 0x122031e00; baseClass = UITextView; frame = (0 0; 320 568); 
text = '\u5b9e\u6218\u5907\u5fd8\u5f55iOS\u5907\u5fd8\u5f55\u60f3\u5fc5\u662f\u679c\u7c89\u6700\u719f\u6089\u7684app\u4e4b...'; 
clipsToBounds = YES; gestureRecognizers = <NSArray: 0x17405dc10>;
 layer = <CALayer: 0x1742200e0>; contentOffset: {0, -64}; contentSize: {320, 85}>

通过 nextResponder找到 Controller:ICTextViewController

cy# [#0x122031e00 nextResponder]
#"<ICTextViewController: 0x121e77d90>"

尝试设置:ICTextViewController setTitle:@"ccc"发现无效

然后就想着在 ICTextView 中添加一个Label 来显示

cy# @import mjcript
{}
cy# var lb=[[UILabel alloc] init]
#"<UILabel: 0x121d228b0; frame = (0 0; 0 0); userInteractionEnabled = NO; layer = <_UILabelLayer: 0x174282e40>>"
cy# lb.frame=MJRectMake(265,0,50,20);
{0:{0:265,1:0},1:{0:50,1:20}}
cy# lb.text="20"
"20"
cy# [#0x122031e00 addSubview:lb]

样子的确不咋滴,不过功能实现应该没问题

image

2. 找到数据

查看 ICTextViewController 的头文件,发现textview,那么从控制器就可以直接访问,textview的text的值

@property(retain, nonatomic) ICNote *note; // @synthesize note=_note;
@property(readonly, nonatomic) ICTextView *textView;
cy# var ict=#0x121e77d90
#"<ICTextViewController: 0x121e77d90>"
cy# ict.textView
#"<ICTextView: 0x122031e00; baseClass = UITextView; frame = (0 0; 320 568); text = '\xe5\xae\x9e\xe6\x88\x98\xe5\xa4\x87\xe5\xbf\x98\xe5\xbd\x95\niOS\xe5\xa4\x87\xe5\xbf\x98\xe5\xbd\x95\xe6\x83\xb3\xe5\xbf\x85\xe6\x98\xaf\xe6\x9e\x9c\xe7\xb2\x89\xe6\x9c\x80\xe7\x86\x9f\xe6\x82\x89\xe7\x9a\x84app\xe4\xb9\x8b...'; clipsToBounds = YES; gestureRecognizers = <NSArray: 0x17405dc10>; layer = <CALayer: 0x1742200e0>; contentOffset: {0, -64}; contentSize: {320, 85}>"
cy# ict.textView.text
@"\xe5\xae\x9e\xe6\x88\x98\xe5\xa4\x87\xe5\xbf\x98\xe5\xbd\x95\niOS\xe5\xa4\x87\xe5\xbf\x98\xe5\xbd\x95\xe6\x83\xb3\xe5\xbf\x85\xe6\x98\xaf\xe6\x9e\x9c\xe7\xb2\x89\xe6\x9c\x80\xe7\x86\x9f\xe6\x82\x89\xe7\x9a\x84app\xe4\xb9\x8b\xe4\xb8\x80 "
cy# 

ICTextViewController 中也存在note 数据
查看NotesShared.framework 私有框架下的ICNote 文件,以及 noteData 数据,打印结果,发现其 data 数据是加密的,不知道是不是 文本信息,也尝试浏览其他属性,没有发现可以的数据对象

cy# ict.note
#"<ICNote: 0x170382630> (entity: ICNote; id: 0xd000000000140000 <x-coredata://4277D251-A868-4E71-BC72-C0449A57CDC1/ICNote/p5> ; data: {\n    account = \"0xd0000000000c0004 <x-coredata://4277D251-A868-4E71-BC72-C0449A57CDC1/ICAccount/p3>\";\n    assetCryptoInitializationVector = nil;\n    assetCryptoTag = nil;\n    attachmentViewType = 0;\n    attachments =     (\n    );\n    cloudState = \"0xd000000000140002 <x-coredata://4277D251-A868-4E71-BC72-C0449A57CDC1/ICCloudState/p5>\";\n    creationDate = \"2019-03-25 05:54:21 +0000\";\n    cryptoInitializationVector = nil;\n    cryptoIterationCount = 0;\n    cryptoSalt = nil;\n    cryptoTag = nil;\n    cryptoWrappedKey = nil;\n    encryptedValuesJSON = nil;\n    folders =     (\n        \"0xd000000000040008 <x-coredata://4277D251-A868-4E71-BC72-C0449A57CDC1/ICFolder/p1>\"\n    );\n    foldersModificationDate = nil;\n    identifier = \"73463C4C-2A5F-4C74-BF8E-4E438C41DF60\";\n    integerId = nil;\n    isPasswordProtected = 0;\n    lastViewedModificationDate = \"1983-11-07 17:37:00 +0000\";\n    legacyContentHashAtImport = nil;\n    legacyImportDeviceIdentifier = nil;\n    legacyManagedObjectIDURIRepresentation = nil;\n    legacyModificationDateAtImport = nil;\n    legacyNoteIntegerId = 0;\n    legacyNoteWasPlainText = nil;\n    markedForDeletion = 0;\n    minimumSupportedNotesVersion = 0;\n    modificationDate = \"2019-03-27 08:14:38 +0000\";\n    needsInitialFetchFromCloud = 0;\n    needsToBeFetchedFromCloud = nil;\n    needsToSaveUserSpecificRecord = 1;\n    noteData = \"0xd000000000080006 <x-coredata://4277D251-A868-4E71-BC72-C0449A57CDC1/ICNoteData/p2>\";\n    noteHasChanges = 0;\n    passwordHint = nil;\n    serverRecord = nil;\n    serverShare = nil;\n    snippet = \"iOS\\U5907\\U5fd8\\U5f55\\U60f3\\U5fc5\\U662f\\U679c\\U7c89\\U6700\\U719f\\U6089\\U7684app\\U4e4b\\U4e00\";\n    thumbnailAttachmentIdentifier = nil;\n    title = \"\\U5b9e\\U6218\\U5907\\U5fd8\\U5f55\";\n    unappliedEncryptedRecord = nil;\n    userSpecificServerRecord = nil;\n    zoneOwnerName = nil;\n})"
cy# ict.note.noteData
#"<ICNoteData: 0x1742c5a90> (entity: ICNoteData; id: 0xd000000000080006 <x-coredata://4277D251-A868-4E71-BC72-C0449A57CDC1/ICNoteData/p2> ; data: {\n    cryptoInitializationVector = nil;\n    cryptoTag = nil;\n    data = <1f8b0800 00000000 00034d93 4b6bd450 14c77373 6fc6dbdb 69e74c5a c79a3a1a a7b5a663 153f8120 d69d20c5 4fa0a2d2 8d74>;\n    note = \"0xd000000000140000 <x-coredata://4277D251-A868-4E71-BC72-C0449A57CDC1/ICNote/p5>\";\n})"

3. 寻找实时监测数据数据变化的方法

直接用logfiy跟踪,发现其在初始化时会执行loadView 和 layoutManager方法,loadView执行之后,界面的view应该时初始化好了,并且每一次修改内容时也会调用 layoutManager,就想着在这两个方法上做文章,loadView 创建label ,layoutManager 计算字数

使用IDA找到这两个方法的,并且在layoutManager方法基地址上下断点

(lldb) image list -f -o
[  0] /Applications/MobileNotes.app/MobileNotes 0x00000000000fc000(0x00000001000fc000)
(lldb) br s -a 0x1001CB170
Breakpoint 2: where = MobileNotes`_mh_execute_header + 837984, address = 0x00000001001cb170
Process 1005 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 2.1
    frame #0: 0x00000001001cb170 MobileNotes`_mh_execute_header + 848240
MobileNotes`_mh_execute_header:
->  0x1001cb170 <+848240>: stp    x22, x21, [sp, #-0x30]!
    0x1001cb174 <+848244>: stp    x20, x19, [sp, #0x10]
    0x1001cb178 <+848248>: stp    x29, x30, [sp, #0x20]
    0x1001cb17c <+848252>: add    x29, sp, #0x20            ; =0x20 
Target 0: (MobileNotes) stopped.
(lldb) po $x0
<ICTextViewController: 0x121ec8850>

(lldb) po [$x0 textView]
<ICTextView: 0x122189000; baseClass = UITextView; frame = (0 0; 0 0); text = '实战备忘录
iOS备忘录想必是果粉最熟悉的app之...'; clipsToBounds = YES; gestureRecognizers = <NSArray: 0x174444b60>; layer = <CALayer: 0x174225d60>; contentOffset: {0, 8}; contentSize: {0, 61}>

(lldb) po [[$x0 textView] text]
实战备忘录
iOS备忘录想必是果粉最熟悉的app之一 

LayoutManager 方法中可以获取到控制器,并且可以访问到数据

4.整理结果,编写代码

构造类对应的头文件

Characount.h

@interface ICTextView : UITextView

@end

@interface ICTextViewController:UIViewController

@property(readonly, nonatomic) ICTextView *textView;

@end

编写 Tweak.xm


#import <CoreGraphics/CGGeometry.h>
#import "Characount.h"
%hook ICTextViewController

- (id *)layoutManager { 

	%log; 
	
	NSString *content=self.textView.text;
	NSString *contentLength=[NSString stringWithFormat:@"%lu",(unsigned long)[content length]];
	NSLog(@"contentLength:%@",contentLength);

	UILabel *lb =(UILabel *)[self.textView viewWithTag:1000];
	lb.text=contentLength;

	id * r = %orig;
	return r; 
}
- (void)loadView {

	%log; 

	%orig; 

	
	UILabel *lb=[[[UILabel alloc] initWithFrame:CGRectMake(265,0,50,20)] autorelease];
	
	lb.tag=1000;

	[self.textView addSubview:lb];

}

%end

5. 最终效果

image


#2

你这个闪退错误似乎是开始注入进程就没成功


#3

初始化控件的时候出错了
var ll = [[UILabel alloc] init]
缺少了 init 方法
这样子,就可以addSubview 到父节点上面了


#4

重新整理编写过程,有不足指出,希望多多指点


#5

你能重新用markdown语法编辑一下帖子,尤其是代码部分,然后把帖子挪到干货分享区吗?我发个微博推荐一下你的帖子


#6

嗯,好的,我重新整理一下


#7

全部排版好了


#8

我也尝试用monkeyDev写了一个备忘录的tweak,在越狱手机上完美运行,但是到了非越狱手机上,一运行就报找不到CVML.framework,网上找了一个拖进去又报Command CodeSign failed with a nonzero exit code。签名失败了。求大神指导。(我现在的情况是:还在摸索工具的使用,很多底层原理还需要在实践总掌握)