这篇文章解释了苹果使用的两个协议的一部分,一个用于Note格式本身,另一个用于嵌入式对象。更重要的是,它解释了如何计算出协议缓冲区的结构。
本系列以前的文章介绍了如何处理Apple Notes和其中的嵌入对象,包括嵌入表和图库。在这些帖子中,我提到了这样一个事实,即苹果使用协议缓冲区(Protocol Buf)来存储笔记和其中嵌入的对象的信息。实际上,我还没有提供用于生成Ruby输出的.proto文件,或者解释如何在您感兴趣的应用程序上开发相同的.proto文件。如果您只关心其中的第一部分,您可以查看.proto文件或我用于协议错误检查程序的配置。这两个文件只是拉出重要部分进行处理的开始,当然可以改进。
和以前的参赛作品一样,我想确保我在应得的地方给予了赞誉。在拆开Note Protobuf之后,当我试图弄清楚桌子Protobuf时,我偶然发现了Dunhamsteve的作品。结果,我回去修改了我的一些命名,以便更好地与他发布的内容保持一致,并在一些我没有数据要发现的字段(如Version)中添加了内容。
协议缓冲区是Google的语言中立、平台中立的可扩展机制,用于序列化结构化数据-比如XML,但更小、更快、更简单。一旦定义了数据的结构化方式,您就可以使用特殊生成的源代码轻松地将结构化数据写入各种数据流,并使用各种语言从数据流中读取结构化数据。
那是什么意思?这意味着协议缓冲区是一种方式,您可以编写数据规范,并通过一个命令在多种项目和语言中使用它。最终结果是您正在编写的任何语言的源代码。例如,Sean Ballinger的Alfred search Notes App使用我的notestore.proto文件编译To Go,而不是Ruby在MacOS上与Notes交互。当您在您的程序中使用它时,您保存的数据将是一个原始数据流,它看起来不会很多,但对于任何具有该协议缓冲区定义的代码都是可以理解的。
语法=";proto2";;//表示附件(嵌入式对象)消息AttachmentInfo{可选字符串ATTACHING_IDENTIFIER=1;可选字符串TYPE_UTI=2;}。
该定义将只有一个消息类型(AttachmentInfo),具有两个字段(AttachmentIdentifier和TYPE_UTI),这两个字段都是可选的。这使用的是Proto2语法。
Protobuf无处不在,特别是如果您碰巧正在使用或查看基于Google的系统,比如Android。苹果在iOS中也使用了大量的协议,对于必须同时支持两种操作系统的人来说,使用协议使维护两个不同代码库的痛苦略有减轻,因为你可以将相同的定义编译成不同的语言。如果您是法医,您可能会遇到一些看起来不是纯文本的内容,然后发现您实际上正在查看的是一个Protobuf。当它特别涉及到Apple Notes时,Protobuf既用于Note本身,也用于附件。
假设您有一个.proto文件,无论是通过自己构建一个文件还是从您喜爱的应用程序中找到一个.proto文件,您都可以使用proc将其编译成您的目标语言。然后,可以使用该语言的INCLUDE语句来为数据创建必要的类,从而将生成的文件包含在您的项目中。例如,在用Ruby编写Apple Cloud Notes Parser时,我使用了proc--ruby_out=。./proto/notestore.proto编译它,然后要求在我的代码中包含_Relative';notestore_pb.rb';。
相反,如果我想添加对Python的支持,我只需进行以下更改:PRORC--RUBY_OUT=。--python_out=。./proto/notestore.proto。
如果您在正在查看的应用程序中遇到了协议Buf,您可能会在应用程序本身或取证映像中的某个位置找到.proto Protobuf定义文件。今年早些时候,我检查了一个iOS 13取证映像,发现苹果的磁盘上仍然有一些他们的映像:
当你看它们的时候,其中一些真的很有趣,特别是当你关心它们的位置数据和配对的时候。如果您运行sudo find/system/-iname";*.proto";,您甚至不必拥有iOS取证映像,因为所有相同的文件也包含在您的MacOS 10.15.6副本中。我没有包括任何有趣的片段,因为它们是苹果的版权,我要明确指出,没有一个与Apple Notes或这篇文章的内容有关。
通常,您不应该期望找到这些定义,因为一旦生成代码,就不需要定义文件了。对于更多的开源应用程序,您可能会对一些Google Dork感兴趣,特别是在查看Android工件时,因为您可能仍然会找到它们。
但是,如果您找不到定义文件,您如何自己重新构建它呢?这是重写Apple Cloud Notes Parser最有趣的部分,因为我不知道Apple通常如何表示数据,也不知道协议缓冲区,所以这是一次有趣的学习冒险。
如果没有其他内容,proc--decode-raw命令可以让您初步了解数据中的内容,但是这无异于漂亮地打印一个JSON对象,它并不能很好地告诉您数据中可能包含什么内容。我大量使用了米尔德日出的Protobuf-Inspector,它至少试图告诉你你可能看到的是什么。使用它的另一个好处是,它允许您通过编辑Protobuf-insepctor文件夹中名为Protobuf_config.py的文件,以增量方式构建您自己的定义。
例如,下面是当我在测试数据库中的第一个注释的Gunzip内容上运行它时,来自Protobuf-Inspector的输出。
[notta@cuppa protocol buf-spector]$python3 main.。Py<;~/注_18。Blob根:1<;varint>;=0 2<;chunk>;=message:1<;varint>;=0 2<;varint>;=0 3;lt;chunk>;=message:2<;chunk>;=";纯blob title";3<;chunk>;=message:1<;chunk>;=message(1<;varint>;=message;1<;varint>;=message;1<;varint>;=message(1<;varint>;=message;1<;varint>;=message(1<;varint>;=0,2<;varint>;=0)2<;varint&>;=0 3<;块&>=message(1<;varint>;=0,2<;varint>;=0)5;varint>;=1 3<;块&>=message:1<;chunk>;=message(1<;varint>;=1,2<;Varint>;=0)2<;varint&>;=5 3<;块&>=message(1<;varint&>;=1,2<;varint&>;=0)5<;varint&>;=2 3<;块&>=message:1<;varint&>;=message(1<;varint&>;=1,2<;varint>;=1,2<;varint>;=1;=5)2<;varint&>;=53;chunk&>;=message(1<;varint&>;=1,2<;varint>;=8)4<;varint&>;=1 5;varint&>;=3 3<;chunk&>;=message:1<;chunk&>;=message(1<;varint&>;=1,2<;varint>;=1,2<;varint>;=1;=10)2<;varint&>;=43;chunk&>;=message(1<;varint&>;=1,2<;varint>;=0)4;lt;varint&>;=1 5;varint&>;=4 3<;chunk&>;=message:1<;chunk&>;=message(1<;varint&>;=1,2<;varint>;=1,2<;varint>;=1;=14)2<;varint&>;=103;块&>;=消息(1<;varint>;=1,2<;varint>;=4294967295)5;lt;varint>;=53;块&>=消息:1;lt;块&>=消息:1;lt;varint>;=02<;varint>;=0 2<;varint>;=0 2<;varint>;=10 2<;varint>;=0 2<;varint>;=0 2<;varint>;=10 2<;varint>;=0 2<;varint>;=103;=4294967295 3<;块>;=消息:1;lt;varint>;=0 2;lt;varint>;=4294967295 4<;块>;=消息:1;lt;块>;=消息:1<;块>;=字节(16)0000EE FE 10 DA 5 A 79 43 25 88 BA 6 D CA E2 E9 B7 EC...。ZYC%..。我……。2<;chunk&>;=message(1<;varint>;=24)2<;chunk>;=message(1<;varint>;=9)5<;chunk>;=message:1<;varint&>;=5 2<;chunk>;=message(1<;varint>;=0,3<;varint>;=1)5<;chunk>;=message(1<;varint>;=1)5<;chunk>;=Message(1<;varint>;=1)5<;chunk>;=1)5<;varint>;=1;=消息:1<;varint&>;=5 2<;块&>;=message(1<;varint>;=0,3<;varint>;=1)5<;块&>=消息:1;lt;varint&>;=5 2<;块&>=message(1<;varint&>;=0,3<;varint>;=1)。
这里有很多内容是为了一张纸条,上面只写着“纯正的斑点标题”(Pure BLOB Title)!因为我们知道协议是由消息和字段组成的,所以当我们浏览这篇文章时,我们将尝试找出消息是什么以及它们具有什么类型的字段。为此,您需要注意字段类型(如“varint”)和数字(1、2、3,您知道数字是什么)。
在协议中,每个字段编号恰好对应于一个字段,因此当您看到许多相同的字段编号时,您就知道这是一个重复的字段。在上面的示例中,有很多重复的字段5,它是一条包含两个内容的消息,一个varint和另一个消息。您还需要注意给出的值,并寻找可能与时间戳、字符串长度、子字符串长度或整个协议中的索引相对应的幻数。
从一开始看,我们看到该协议有一个带有in的根对象。根对象有两个我们知道的字段:1和2。但是,除了字段2显然是包含其他所有内容的消息类型之外,我们没有足够的信息来说明有关它们的任何有意义的事情。
在领域2中,我们看到一个非常相似的问题。它有三个字段,其中两个(1和2)我们不太了解,无法推断它们的用途。然而,字段3仍然是一个明确的消息,其中包含更多内容。
...2<;块>;=消息:1<;varint>;=0 2;lt;varint>;=0 3<;块>;=消息:...。
领域3是它变得有趣的地方。我们在字段2中看到一些明文,其中包含此特定注释的整个文本。我们看到重复的字段3和5,因此这些消息显然可以多次应用。我们只看到一个字段4,它是一条具有16字节值和两个整数的消息。
...3<;块&>=message:2<;chunk&>;=";纯blob标题";3<;chunk&>;=message:1<;chunk&>;=message(1<;varint&>;=0,2<;varint>;=0)2;varint>;=03;chunk>;=message(1<;varint>;=message(1<;varint>;=0)2<;varint>;=message(1<;varint>;=0)2<;varint>;=0)。=0,2<;varint>;=0)5<;varint>;=1 3<;块>;=消息:1
在这一点上,我们需要更多的数据来进行测试。为了使该测试有意义,我将首先将上面看到的信息保存到一个新的定义文件中,以供Protobuf-Inspector使用。这样,当我们在其他音符上运行时,任何新的东西都会脱颖而出。尽管我们知道的不多,但这可能是您的初始定义文件,保存在运行Protobuf-Inspector的文件夹中,名称为Protobuf_config.py。
类型={#Main Note data protocol buf";root";:{#1:未知?2:(";文档";),},#与备注";文档";相关:{##1:未知?#2:未知?3:(";备注";,";备注";),},";备注";:{#2:(";string";,";备注文本";),3:(";UNKNOWN_CHUNK";,";UNKNOWN_NOTE_STUTH";,";UNKNOWN_NOTE_STUTH";),5:(";UNKNOWN_CHUNK";,";未知块2";),},";未知_块";:{#1:2:(";varint";,";未知整数1";),#3:5:(";varint";,";未知整数2";),},";未知_NOTE_STUTH";:{#1:未知消息},";UNKNOWN_CHUNK2";:{1:(";varint";,";未知整数1";),},}。
然后,当我们对数据库中的下一个注释运行此操作时,我们会看到许多我们“标识”的字段。例如,请注意,在下面的输出中,我们之前考虑的更复杂的字段3现在被清楚地称为“注释”。当你走过它的时候,这会让你更容易理解它。
Notta@cuppa protocol buf-spector]$python3 main.。Py<;~/注_19。Blob根:1<;varint>;=0 2<;document>;=document:1<;varint&>;=0 2;varint&>;=0 3注意=备注:2注释文本=";纯粗体斜体标题";3未知分块=UNKNOWN_CHUNK:1<;块&>=message(1<;varint>;=0,2<;varint>;=0)2未知整数1=03;块=Message(1<;varint&>;=0,2;lt;varint&>;=0)5未知整数2=13未知块=UNKNOWN_CHUNK:1<;块&>;=消息(1<;varint&>;=1,2;varint>;=4)2未知整数1=1 3<;块>;=消息(1<;varint>;=4)2未知整数1=1 3<;块>;=消息(1<;Varint>;=1,2<;varint>;=0)5未知整数2=23未知块=UNKNOWN_CHUNK:1<;CHUNK&>;=message(1<;varint>;=1,2<;varint>;=0)2未知整数1=4.3<;块>;=message(1<;varint>;=1,2<;varint>;=8)4<;Varint&>;=1 5未知整数2=3 3未知块=UNKNOWN_CHUNK:1<;Chunk&>;=message(1<;varint&>;=1,2<;varint>;=5)2未知整数1=213;Chunk&>;=message(1;lt;varint&>;=1,2<;varint&>t;=0)5未知整数2=4 3未知块=UNKNOWN_CHUNK:1<;Chunk&>;=消息:1<;varint&>;=4294967295 2未知整数1=03;块&>;=消息:1;lt;varint&>;=0 2;varint>;=4294967295 4未知填充=UNKNOWN_NOTE_FUSH:1<;块&>=消息:1;lt;块>;=消息:1<;块>;=消息:1;lt;块>;=消息:1;lt;块>;=消息:1;=字节(16)0000 EE FE 10 DA 5 A 79 43 25 88 BA 6 D CA E2 E9 B7 EC...。ZYC%..。我……。2<;Chunk&>;=message(1<;varint&>;=26)2<;chunk&>;=message(1<;varint&>=9)5未知块2=UNKNOWN_CHUNK2:1未知整数1=22 2<;块&>=message(1<;varint&>;=0,3<;varint&>;=1)5<;varint>;=3。
通过编辑该协议buf_config.py文件,您可以快速地重新检查之前导出的BLOB,并且可以随着时间的推移以迭代的方式加深理解。但是你如何建立你的理解呢?在本例中,我看到纯文本字符串没有我在Notes中看到的任何花哨的部分,并假设重复的3或重复的5部分的某些部分处理格式化。
因为有很多奇特的部分可以使用,所以我尝试生成许多测试用例,每个测试用例中只有一处更改。因此,我从上面看到的内容开始,只生成了一个标题,并生成了注释,这些注释反复包含标题中的每种格式可能性。为了让自己更容易识别字符串偏移,我总是设置代表样式的单词的样式。例如,每当我用粗体这个词时,它就是粗体,如果我用斜体,它就是斜体。
当我生成了很多这样的消息,并开始在笔记正文中生成内容,而不仅仅是标题时,我注意到第5栏中出现了一种模式。第5栏中所有消息的长度加起来总是等于文本的长度。在上面注释19的示例中,“未知整数1”是值2
[notta@cuppa protocol buf-spector]$python3 main.。Py<;~/注_21。BLOB 3注意=备注:2注意文本=";纯粗体带下划线的删除线标题";5未知块2=UNKNOWN_CHUNK 2:1未知整数1=40 2<;块>;=message(1<;varint>;=0,3<;varint>;=1)5<;varint>;=3 6<;varint>;=1 7<;varint>;=1[notta@cuppa协议buf-检查器]$python3 main。Py<;~/注_32。BLOB 3注意=备注:2注意文本=";标题\n\n副标题\n正文\n单间距\n\n";5未知块2=UNKNOWN_CHUNK 2:1未知整数1=6 2<;Chunk>;=message(1<;varint&>;=0,3<;varint>;=1)5 UNKNOWN CHUNK 2=UNKNOWN_CHUNK2:1未知整数1=8 2<;Chunk>;=message(1<;varint>;=1)5未知块2=UNKNOWN_CHUNK2:1未知整数1=8 2<;Chunk>;=message(1<;varint>;=1)。Varint>;=1,3<;varint>;=1)5未知块2=UNKNOWN_CHUNK2:1未知整数1=1 2;lt;块&>=message(3;lt;varint&>=1)5未知块2=UNKNOWN_CHUNK2:1未知整数1=11 2<;CHUNK&>=message(1;lt;varint&>;=2,3<;varint>;=2,3<;varint>;=1)5未知块2=UNKNOWN_CHUNK2:1未知整数1=5 2<;Chunk>;=message(3<;varint>;=1)5 UNKNOWN_CHUNK 2=UNKNOWN_CHUNK 2:1未知整数1=132;Chunk>;=message(1<;varint>;=4,3;lt;varint>;=1)[notta@cuppa protocol buf-spector]$python3 main。Py<;~/注_33。BLOB 3注意=备注:2注意文本=非粗体标题\n粗体标题\n粗体正文\n粗体斜体\n斜体2=UNKNOWN_CHUNK 2:1未知整数1=4 2<;Chunk>;=message(1<;varint>;=0,3<;varint>;=1)3<;chunk>;=message:1<;chunk>;=";.SFUI-Regular";5未知块2=UNKNOWN_CHUNK2:1未知整数1=11 2<;Chunk>;=message(1<;varint>;=0,3;lt;varint>;=1)3<;Chunk&>=message:1<;Chunk>;=";.SFUI-Regular";5 UNKNOWN_CHUNK 2=UNKNOWN_CHUNK2:1未知整数1=11 2<;Chunk>;=message(1<;varint&>;=0,3<;varint>;=1)5未知块2=UNKNOWN_CHUNK2:1未知整数1=102<;chun。
.