数字取证实战:解析Google Takeout中的活动检测数据

本文详细介绍了如何通过Python脚本解析Google Takeout中的Records.json文件,提取设备活动检测数据(如车辆停留、步行状态等),并生成TSV和KML文件进行可视化分析的技术方法。

猴子尝试解析Google Takeout(活动检测篇)

注意饮食安全,猴子!

一位猴子的同事(Troy)通过查看设备所有者的Google Takeout “Location History.json"文件,为调查人员提供了一个重要位置信息。具体来说,Troy查看了DetectedActivity分类字符串,这些字符串被Google(Play服务)用来估算设备是静止状态、在车辆中、步行等。从IN_VEHICLE到STILL的状态转换表明车辆在某个位置停了下来(而不仅仅是经过)。

作为Google Play服务的一部分,意味着这些DetectedActivity数据可能仅适用于涉及Android设备的Google账户。我们尚未在iOS设备相关的Google Takeout中看到任何DetectedActivity。

特别感谢:

  • Troy分享他的发现
  • Rasmus Riis Kristensen、Mike Lacombe、Heather Mahalik和Lee Crognale检查他们的Google Takeout数据并进行脚本测试
  • Josh Hickman提供更多测试Android Takeout数据以及关于新Takeout格式和速度/航向/海拔字段的额外反馈

主要的DetectedActivity类别定义如下:

  • IN_VEHICLE 设备在车辆中,例如汽车
  • ON_BICYCLE 设备在自行车上
  • ON_FOOT 设备在行走或跑步的用户身上
  • RUNNING 设备在跑步的用户身上
  • STILL 设备静止(未移动)
  • TILTING 设备相对于重力的角度发生显著变化
  • UNKNOWN 无法检测当前活动
  • WALKING 设备在步行的用户身上

更多详细信息请参见此处

注意:我们还观察到未记录的活动类型,如IN_ROAD_VEHICLE、IN_FOUR_WHEELER_VEHICLE、IN_CAR、IN_RAIL_VEHICLE。

虽然一些取证工具已经显示Google Takeout信息,但通过这些工具按DetectedActivity进行高亮/过滤并不容易/不可能。

对于Troy的Takeout(以及Josh Hickman的Android 12 Google Takeout),有一个"Location History"文件夹,该文件夹的根目录中有"Location History.json"文件。还有一些子文件夹,但它们受账户所有者编辑的影响。显然,“Location History.json"是原始数据,不受用户删除位置/行程的影响。

Ross Donnelly的这篇博客文章有更多详细信息。

最初,猴子为"Location History.json"编写了一个原型Python3脚本,但随后注意到最近的测试Takeout(在2022年1月至2月完成)不再有此JSON文件。

更新 2022年2月28日:用于"Location History.json"的原型"gLocationHistoryActivity.py"脚本现已在GitHub上提供。

注意:这仅使用15-30 MB的小文件进行测试,并且不使用"ijson"库。

相反,Google似乎用一个名为"Records.json"的文件替换了它。较新的文件也是JSON格式,但有一些细微差异,例如时间戳格式、额外字段。

猴子找不到关于此文件格式的任何文档。与其他数字取证人员确认后,证实他们的Takeout也有"Records.json"而不是"Location History.json”。

目前尚不清楚此更改是在Google服务器端实施的,还是设备上的Android/Google Play版本影响创建哪个.json文件。

更新 2022年2月28日:Josh Hickman在他的测试Android 12数据上执行了另一个Takeout,得到了一个"Records.json"而不是"Location History.json”。因此,更改似乎是在Google服务器端实施的。

因此,假设任何Takeout现在都将导出到"Records.json",猴子编写了一个脚本来处理"Records.json"文件,该文件可以描述如下:

1个位置列表,可以存储许多位置元素记录。

每个位置元素具有以下字段:

  • “source”(通常为"UNKNOWN",但在此处也见过"CELL")
  • “deviceTag”(设备标识符)
  • “platformType”(通常为"ANDROID")
  • “formFactor”(例如"PHONE")
  • “serverTimestamp”(例如"2022-02-04T04:40:17.685Z",并非始终存在,多个位置元素可以相同)
  • “deviceTimestamp”(例如"2022-02-04T04:40:16.214Z",并非始终存在,多个位置元素可以相同)
  • “timestamp”(例如"2022-02-02T00:55:06.311Z",随每个元素记录变化,为避免混淆,猴子称此为"元素时间戳")
  • “latitudeE7”(以度为单位,缩放10,000,000倍)
  • “longitudeE7”(以度为单位,缩放10,000,000倍)
  • “altitude”(并非始终存在,单位据Josh Hickman称为米)
  • “heading”(并非始终存在,单位据Josh Hickman称为从真北顺时针的度数,例如90 = 东,180 = 南)
  • “velocity”(并非始终存在,单位据Josh Hickman称为米/秒)
  • “accuracy”(单位未知,怀疑为米)
  • “verticalAccuracy”(并非始终存在,单位未知,怀疑为米)

每个位置元素可能/可能没有"activity"列表。

每个活动列表有一个活动"timestamp",可以存储1个或多个"subactivitys"。

注意:“subactivity"是猴子用来标记活动列表中列出的任何子活动的术语(参见下面的示例)。

每个"subactivity"可以有多个类型和置信度(百分比)对(例如type = ON_FOOT, confidence = 3 和 type = STILL, confidence = 89)。

以下是"Records.json"中父活动声明的示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
"activity": [{
      "activity": [{
        "type": "STILL",
        "confidence": 89
      }, {
        "type": "ON_FOOT",
        "confidence": 3
      }, {
        "type": "WALKING",
        "confidence": 3
      }, {
        "type": "UNKNOWN",
        "confidence": 3
      }, {
        "type": "IN_VEHICLE",
        "confidence": 2
      }, {
        "type": "ON_BICYCLE",
        "confidence": 2
      }, {
        "type": "IN_RAIL_VEHICLE",
        "confidence": 2
      }, {
        "type": "IN_ROAD_VEHICLE",
        "confidence": 1
      }, {
        "type": "IN_FOUR_WHEELER_VEHICLE",
        "confidence": 1
      }, {
        "type": "IN_CAR",
        "confidence": 1
      }],
      "timestamp": "2022-02-04T05:52:43.071Z"
    }]

在上面的示例中,我们可以看到1个父活动列表[红色高亮],它有一个活动时间戳(“2022-02-04T05:52:43.071Z”,黄色)和1个子活动[蓝色]。子活动有多个类型/置信度字段[绿色],这些字段分类了Google认为设备正在进行的活动(例如89% STILL)。

我们观察到每个父活动可以列出多个子活动。

脚本编写

最初,猴子使用标准的Python json库将整个"Records.json"加载到内存中。虽然这对于MB大小范围的.json文件有效,但在处理大的1.3GB数据集(约10年的数据)时导致了内存错误。

在Rasmus有帮助地将猴子指向以下文章后:

猴子使用了第三方的"ijson"库来成功迭代加载1.3 GB的"Records.json"文件。

修订后的脚本(gRecordsActivity_ijson_date.py)搜索所有位置元素中具有"activity"的元素。然后,它处理/存储每个元素时间戳的DetectedActivity到Python字典中。每个元素时间戳可以有多个活动。

然后,每个元素时间戳日期的DetectedActivity数据输出到制表符分隔变量(TSV)文件和Keyhole标记语言(KML)文件进行分析。

以下是使用脚本(gRecordsActivity_ijson_date.py)的方法,该脚本可在GitHub上获得。

第一步 - 通过pip安装ijson库。在Ubuntu 20.04 LTS上,您可以使用以下命令:

1
pip3 install ijson

现在,您可以通过将脚本指向Takeout Records.json和输出目录来运行它。

以下是使用帮助:

1
2
3
4
5
6
7
8
9
python3 gRecordsActivity_ijson_date.py -h
usage:  gRecordsActivity_ijson_date.py [-i input_file -o output_dir -a start_isodate -b end_isodate]
Extracts/parses "Detected Activity" data from Google Takeout "Records.json" (large files) and outputs TSV and KML files to given output dir
optional arguments:
  -h, --help  show this help message and exit
  -i INPUT    Input Records filename
  -o OUTPUT   Output KML/TSV directory
  -a START    Filter FROM (inclusive) Start ISO date (YYYY-MM-DD)
  -b END      Filter BEFORE (inclusive) End ISO date (YYYY-MM-DD)

以下是一些测试数据的缩写示例命令输出:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
python3 gRecordsActivity_ijson_date.py -i Records-4.json -o records4_output
Running gRecordsActivity_ijson_date.py v2022-02-26
ACTIVITY => [{'activity': [{'type': 'STILL', 'confidence': 99}, {'type': 'UNKNOWN', 'confidence': 1}], 'timestamp': '2022-02-04T03:49:57.246Z'}]
Element timestamp: 2022-02-04T03:49:55.263Z
Element serverTimestamp: 2022-02-04T04:22:27.214Z
Element deviceTimestamp: 2022-02-04T04:22:25.715Z
No. (sub)Activitys => 1
(sub)Activity #1 timestamp = 2022-02-04T03:49:57.246Z
No. (sub)Activity types: 2
ACTIVITY => [{'activity': [{'type': 'STILL', 'confidence': 99}, {'type': 'UNKNOWN', 'confidence': 1}], 'timestamp': '2022-02-04T03:51:01.973Z'}]
Element timestamp: 2022-02-04T03:50:48.614Z
Element serverTimestamp: 2022-02-04T04:22:27.214Z
Element deviceTimestamp: 2022-02-04T04:22:25.715Z
No. (sub)Activitys => 1
(sub)Activity #1 timestamp = 2022-02-04T03:51:01.973Z
No. (sub)Activity types: 2
[Output Redacted for brevity ...]
ACTIVITY => [{'activity': [{'type': 'TILTING', 'confidence': 100}], 'timestamp': '2022-02-04T04:24:24.209Z'}, {'activity': [{'type': 'STILL', 'confidence': 99}, {'type': 'UNKNOWN', 'confidence': 1}], 'timestamp': '2022-02-04T04:26:35.525Z'}, {'activity': [{'type': 'UNKNOWN', 'confidence': 40}, {'type': 'IN_VEHICLE', 'confidence': 10}, {'type': 'ON_BICYCLE', 'confidence': 10}, {'type': 'ON_FOOT', 'confidence': 10}, {'type': 'WALKING', 'confidence': 10}, {'type': 'RUNNING', 'confidence': 10}, {'type': 'STILL', 'confidence': 10}, {'type': 'IN_ROAD_VEHICLE', 'confidence': 10}, {'type': 'IN_RAIL_VEHICLE', 'confidence': 10}, {'type': 'IN_FOUR_WHEELER_VEHICLE', 'confidence': 10}, {'type': 'IN_CAR', 'confidence': 10}], 'timestamp': '2022-02-04T04:30:11.757Z'}]
Element timestamp: 2022-02-04T04:30:36.434Z
Element serverTimestamp: 2022-02-04T04:40:17.685Z
Element deviceTimestamp: 2022-02-04T04:40:16.214Z
No. (sub)Activitys => 3
(sub)Activity #1 timestamp = 2022-02-04T04:24:24.209Z
No. (sub)Activity types: 1
(sub)Activity #2 timestamp = 2022-02-04T04:26:35.525Z
No. (sub)Activity types: 2
(sub)Activity #3 timestamp = 2022-02-04T04:30:11.757Z
No. (sub)Activity types: 11
[Output Redacted for brevity ...]
ACTIVITY => [{'activity': [{'type': 'STILL', 'confidence': 99}, {'type': 'UNKNOWN', 'confidence': 1}], 'timestamp': '2022-02-04T10:00:45.110Z'}]
Element timestamp: 2022-02-04T10:00:32.756Z
Element serverTimestamp: 2022-02-04T10:45:20.367Z
Element deviceTimestamp: 2022-02-04T10:45:18.854Z
No. (sub)Activitys => 1
(sub)Activity #1 timestamp = 2022-02-04T10:00:45.110Z
No. (sub)Activity types: 2
Total no. of elements with at least one Activity = 36
No. of elements with multiple Activitys = 2
Processing Activitys ... Number of days = 1
Processing 2022-02-04 = 39 entries
Processed/Wrote 39 Total Activity entries to: records4_output
Exiting ...

这导致在records4_output目录中创建了以下文件:

  • 2022-02-04.kml
  • 2022-02-04.tsv

虽然先前的命令将提取所有日期,但脚本也可以接受ISO格式的日期范围。

日期范围筛选参数是:

  • -a 开始isodate_string(例如2021-01-31)
  • -b 结束isodate_string

日期参数可以单独使用或一起使用。例如,在xxx之前的所有内容将使用”-b xxx",在zzz之后的所有内容将使用"-a zzz"。

要指定日期范围,请使用两个参数,即"-a zzz -b xxx"。

例如:

1
python3 gRecordsActivity_ijson_date.py -i Records.json -o outputdir -a 2022-01-01 -b 2022-02-03

将提取2022-01-01和2022-02-03(包括)之间的日期到给定的outputdir中。

如果Takeout中有多年的数据,但只有某些日期是感兴趣的,这可能会有帮助。

一旦您有了包含每日.KML和.TSV的输出目录,您可以将感兴趣日期的TSV加载到电子表格应用程序(例如Excel、OpenOffice Calc)中,并搜索DetectedActivity转换等。

示例TSV输出

注意1:行按"element_timestamp"排序。在图片中,显示了3行选中的行,具有相同的"element_timestamp"值,但它们有3个不同的"activity_timestamp"。这是一个具有多个(子)活动的元素示例。每个子活动都有自己的时间戳。

注意2:“detected_activity"列可用于更容易地检测DetectedActivity之间的转换。

注意3:“num_subactivity_types"列显示每个(子)活动列出的DetectedActivity数量。

一旦您找到了感兴趣的DetectedActivity条目,您可以在Google Earth Desktop中打开相应的.KML文件,以绘制位置并查看选定的属性。

编辑过的示例KML输出在Google Earth Desktop中查看

注意1:地图已被编辑以保护猴子的丛林健身房。

注意2:每个点的标签由"element timestamp"和(子)活动类型的数量组成。例如"2022-02-04T04:30:36.434Z, num_subactivity_types = 11”。

注意3:“accuracy"字段可以指示应信任位置图的程度。

注意4:选择父ISO日期文件夹名称(例如"2022-02-04”)旁边的复选框将绘制文件夹中的所有点。默认情况下,所有点不会在屏幕上绘制,以提高Google Earth Desktop性能。

最终想法

已经编写了一个脚本来解析Google Takeout “Records.json"中的DetectedActivity。

通过使用此脚本,分析人员可以最小化混乱/最大化Google Earth性能,并且仍然可以可视化DetectedActivity位置和转换(例如IN_VEHICLE到STILL)。

希望此脚本在分析用户的生活模式/寻找异常时证明有用。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计