三星Gallery3d应用数据取证:日志解析与文件恢复技术
这一切始于2021年11月初Michael Lacombe在Physical and RAW Mobile Forensics Google Group上的一个帖子。该帖子涉及一个案件:三星手机机主声称收到了特定图片,但在访问后立即将其删除。Mike被问到是否有可能确认这一点。由于当时没有立即找到答案,他开始分析这台运行Android 9系统的三星设备。
Mike发现,在SQLite数据库local.db中可以看到三星Gallery3d应用的一些删除痕迹。具体来说,他在"log"表中看到了各种带有时间戳的日志条目,这些条目与编码过的字符串相关联。在论坛成员"Tony"提示这些字符串是Base64编码后,Mike开始了更深入的研究。
在此过程中,他发现了Cheeky4n6monkey在2016年发表的这篇旧文。将该文信息与当前案件数据对比后,他发现情况在过去几年发生了很大变化,但这足以推动他进行更深入的挖掘。Mike询问这只猴子是否愿意一同探索,于是冒险开始了……以下是我们(主要是Mike,我只是个脚本猴子)在此过程中学到的一些东西……
总有新东西需要研究
三星Gallery3d应用已存在多年,根据Google Play信息,它最后一次更新是在2019年,版本为5.4.11.0。在Android Studio中打开测试设备Gallery3d应用安装包(APK)中的AndroidManifest.xml文件,显示:
|
|
根据Android开发者文档,versionName是显示给用户的版本号,而versionCode是一个正整数,随每个版本递增,可用于防止降级到更早版本。
这个应用更新频繁。在搜索测试数据时,我们发现几乎每台设备上的应用版本都不同,进而导致应用文件夹和数据库本身存储的信息也不同。
深入挖掘应用痕迹可能带来当前工具未解析的额外信息 据我们了解,目前没有商业或非商业的取证工具会处理三星Gallery3d应用的数据库以查找删除痕迹。
我们用于分析数据和APK的一些开源工具包括:
数据分析:
- DB Browser for SQLite – 查看/导出SQLite数据库
- Cyberchef – Base64解码字符串
- Base64 Decode and Encode网站 – Base64解码字符串
- Epochconverter – 确认时间戳类型
- Android Studio
APK逆向:
- dex2jar – 将APK的classes.dex转换为Java .jar
- JD-GUI – 查看.jar文件的源代码
- JADX – 直接从APK文件查看源代码
我们还编写了自己的Python3脚本来协助批量转换Base64编码的字符串并输出为制表符分隔变量(TSV)格式。
对三星Gallery3d应用的一些观察 这是一个预装在三星设备上的应用。它依赖的库是三星Android框架的一部分。因此,似乎没有简单的方法(如果有的话)在非三星设备上安装此应用。
三星Gallery3d应用位于用户数据分区:/data/com.sec.android.gallery3d
从应用内发送到回收站的文件位于:/media/0/Android/data/com.sec.android.gallery3d
由于应用每个版本都存在差异,并且研究是基于Mike的案件驱动的,我们决定将本篇博客的重点放在该应用版本(10.2.00.21)上。
在/data/com.sec.android.gallery3d目录下,有一个cache目录和一个databases目录。
缓存目录
在data/com.sec.android.gallery3d/cache/目录下包含多个缓存子目录。
在这个实例中,/0文件夹包含较大的缩略图,宽度范围在225-512像素,高度范围在256-656像素;而/1文件夹包含较小的缩略图,宽度范围在51-175像素,高度范围在63-177像素。还有/2、/3和/4文件夹。/2和/3是空的,/4有一个尺寸为320x320的缩略图。
除了缩略图本身,这里似乎没有其他有用的信息。缩略图的名称似乎是使用哈希算法生成的。
数据库目录
/data/com.sec.android.gallery3d/cache/databases/目录下包含SQLite数据库local.db。
该数据库包含各种信息,包括:
- 相册(“album"表)
- 记录与应用相关的各种操作(如移动到回收站、清空回收站)的日志(“log"表)。
- 当前在回收站中的项目(“trash"表)
在后续版本中,我们注意到另一个名为"filesystem_monitor"的表。它包含时间戳、应用包名(如com.sec.android.gallery3d)和Base64编码的文件路径。然而,由于Mike的案件数据中没有此表,且我们不确定是什么触发了这些记录,这需要进一步研究。
表观察 “album” 表 “album"表的模式如下:
|
|
一些值得关注的"album"表字段:
| 字段名 | 描述 |
|---|---|
__bucketID |
这是通过对相册的完整路径(”__abspath”)调用Java hashcode算法生成的。示例值:-1313584517 |
__abspath |
相册的路径。示例:/storage/emulated/0/DCIM/Screenshots |
default_cover_path |
与该相册关联的图片。示例:/storage/emulated/0/DCIM/Screenshots/Screenshot_20200530-054103_One UI Home.jpg |
album_count |
当前存储在相册中的文件数量。示例:14 |
由于文件路径保持不变,__bucketID值在不同设备上被发现是一致的。这有助于显示是否存在/曾存在自定义创建的相册,以及特定应用(如Facebook、Snapchat等)的相册。恢复此处已删除的记录可以显示已删除的相册以及曾用作相册封面的已删除图片的名称。封面路径信息可以显示潜在感兴趣的文件名,其中许多文件名通常包含时间戳信息。这可能有助于将特定应用的使用与特定时间联系起来。
我们没有为"album"表编写提取脚本,因为可以直接使用DB Browser for SQLite来复制/粘贴相册数据。
“log” 表 “log"表的模式如下:
|
|
一些值得关注的"log"表字段:
| 字段名 | 描述 |
|---|---|
__timestamp |
特定日志条目发生时的时间戳文本字符串(格式为本地时间YYYY-MM-DD HH:MM:SS)。示例:2020-01-09 16:17:14 |
__log |
专有格式的文本字符串,列出执行的"操作”(见下表)和相关文件的Base64编码路径。示例:[MOVE_TO_TRASH_SINGLE][1][0][location://timeline?position=6&mediaItem=data%3A%2F%2FmediaItem%2F-1566891466&from_expand=false][oKHi/x4pePL+KXj3N0b3Lil49h4pePZ2XimIUvZW3imIV14pePbOKYhWHil4904pePZeKYhWTimIUv4pePMC9E4piFQ0nimIVNL+KYhUZh4pePY+KYhWXil49ib2/imIVr4pePL+KXj0ZCX+KYhUnil49N4pePR1/imIUx4piFNeKYhTfimIU4NOKYhTnimIUw4piFNzTimIU1N+KXjzPimIUy4piFLuKXj2rimIVwZw==ST1puy1] |
一些观察到的日志"操作"包括:
| 日志操作 | 描述 |
|---|---|
MOUNTED |
未知何时触发。它告知当前回收站中有多少文件。示例:[MOUNTED][10][0][0][/storage/emulated/0] |
MOVE_TO_TRASH_SINGLE |
当用户从时间线或图库视图将单个文件移动到回收站时发生。 |
MOVE_TO_TRASH_MULTIPLE |
当用户从时间线或图库视图将多个文件移动到回收站时发生。每个回收站条目用括号括起来。 |
EMPTY_SINGLE |
当手动清空回收站且此时回收站中只有一个文件时发生。 |
EMPTY_MULTIPLE |
当手动清空回收站且回收站中包含多个文件时发生。每个清空条目用括号括起来。 |
EMPTY_EXPIRED |
当文件在回收站中停留预定时长后自动删除时发生(根据应用设置)。示例:[EMPTY_EXPIRED][22][22][2020-05-14 00:00:00] |
在我们的数据中未观察到但在源代码中声明(参见TrashHelper类,DeleteType枚举)的其他操作:DELETE_MULTIPLE DELETE_SINGLE。
以下是手动解码”__log"字段中Base64编码字符串的示例:
原始值为:
[MOVE_TO_TRASH_SINGLE][1][0][location://timeline?position=9&mediaItem=data%3A%2F%2FmediaItem%2F-575841975&from_expand=false][eTgcy4piFL3Pil4904piFb+KXj3LimIVh4pePZ2Uv4pePZeKXj2114pePbOKYhWHimIV0ZeKXj2TimIUvMOKYhS9EQ+KYhUlN4piFL1PimIVj4piFcmXil49l4pePbuKXj3Pil49ob3TimIVzL1PimIVj4piFcmXil49lbnNo4pePb3TimIVf4piFMjAxOTHimIUy4pePM+KXjzEtMeKXjznimIUyMeKXjzXil4844piFX+KXj1Nu4pePYeKYhXBjaGF0LmrimIVw4pePZw==bakWlla]
我们复制由[ ]括起来的Base64字符串(黄色高亮部分):
eTgcy4piFL3Pil4904piFb+KXj3LimIVh4pePZ2Uv4pePZeKXj2114pePbOKYhWHimIV0ZeKXj2TimIUvMOKYhS9EQ+KYhUlN4piFL1PimIVj4piFcmXil49l4pePbuKXj3Pil49ob3TimIVzL1PimIVj4piFcmXil49lbnNo4pePb3TimIVf4piFMjAxOTHimIUy4pePM+KXjzEtMeKXjznimIUyMeKXjzXil4844piFX+KXj1Nu4pePYeKYhXBjaGF0LmrimIVw4pePZw==bakWlla
调整到正确解码长度需要:
- 移除最后7个字符,即
"bakWlla"(上方红色高亮部分) - 从字符串开头移除3到6个字符,直到长度是4的倍数。即移除
"eTgcy"(上方绿色高亮部分)
然后我们:
- Base64解码该字符串
- 移除填充字符,如Black Star(★)和Black Circle(●)
对于我们上面的示例,我们将Base64字符串调整为:
4piFL3Pil4904piFb+KXj3LimIVh4pePZ2Uv4pePZeKXj2114pePbOKYhWHimIV0ZeKXj2TimIUvMOKYhS9EQ+KYhUlN4piFL1PimIVj4piFcmXil49l4pePbuKXj3Pil49ob3TimIVzL1PimIVj4piFcmXil49lbnNo4pePb3TimIVf4piFMjAxOTHimIUy4pePM+KXjzEtMeKXjznimIUyMeKXjzXil4844piFX+KXj1Nu4pePYeKYhXBjaGF0LmrimIVw4pePZw==
通过CyberChef或base64decode.org解码为:
★/s●t★o●r★a●ge/●e●mu●l★a★te●d★/0★/DC★IM★/S★c★re●e●n●s●hot★s/S★c★re●ensh●ot★_★20191★2●3●1-1●9★21●5●8★_●Sn●a★pchat.j★p●g
然后我们可以手动删除以下随机添加的填充字符:
- Unicode码点 U+2605 = “Black Star”
- Unicode码点 U+25CF = “Black Circle”
- Unicode码点 U+25C6 = “Black Diamond”
日志表的”__log“字段格式根据APK版本不同而变化。我们只查看了版本v10.0.21.5、v10.2.00.21(主要焦点)和v11.5.05.1。因此,编写了两个版本的"log"表解析脚本:samsung_gallery3d_log_parser_v10.py和samsung_gallery3d_log_parser_v11.py。
“trash” 表 “trash"表的模式如下:
|
|
此示例表中只存储了10条记录。此表中的条目对应于.Trash目录中的活动文件。位于.Trash中的所有其他文件都是被覆盖的文件,具有”_Title“文件名但没有日期/时间信息。
一些值得关注的"trash"表字段:
| 字段名 | 描述 |
|---|---|
__absPath |
已删除文件的当前路径和文件名。示例:/storage/emulated/0/Android/data/com.sec.android.gallery3d/files/.Trash/135138193438761664 |
__Title |
已删除文件的当前文件名。示例:135138193438761664 |
__originPath |
原始路径和文件名。示例:/storage/emulated/0/Download/unnamed.jpg |
__originTitle |
原始文件名。示例:unnamed.jpg |
__deleteTime |
UNIX毫秒时间(UTC)。示例:1592678711438 |
__restoreExtra |
JSON格式,包含各种元数据,例如:"__dateTaken"(本地时间的UNIX毫秒时间)、"__latitude"、"__longitude"。示例:{"__is360Video":false,"__isDrm":false,"__isFavourite":false,"__cloudOriginalSize":0,"__cloudRevision":-1,"__fileDuration":0,"__recordingMode":0,"__sefFileSubType":0,"__sefFileType":-1,"__cloudTimestamp":1592678711350,"__dateTaken":1592669230000,"__size":98526,"__latitude":0,"__longitude":0,"__capturedAPP":"","__capturedURL":"","__cloudServerPath":"","__hash":"","__mimeType":"image\/jpeg","__resolution":"","__recordingType":0,"__isHdr10Video":false} |
“__Title“值(在”__absPath“中可见)是通过对”__originPath“值调用专有的Crc::getCrc64Long函数派生的。注意:此值的生成方法与相册表的”__bucketID“字段不同。
我们编写了一个脚本来解析"trash"表:samsung_gallery3d_trash_parser_v10.py。
local.db中还有其他表,但由于时间限制和可用的测试数据,我们集中研究了”log“和”trash“表。
在一些较晚的应用版本中,我们注意到一个”filesystem_monitor“表,列出了诸如package、date_event_occurred(推测为自1970年1月1日以来的毫秒数)、__data(Base64编码的文件名)、event_type(当前含义未知)等字段。此表需要进一步研究。
脚本编写
我们编写了一些初始的Python 3脚本来解析”log“和”trash“表。我们没有为”album“表编写提取脚本,因为可以直接使用DB Browser for SQLite来复制/粘贴相册数据。
由于观察到”__log“字段格式不同,我们为”log“表编写了两个版本:samsung_gallery3d_log_parser_v10.py和samsung_gallery3d_log_parser_v11.py。这两个脚本都从”log“表中提取各种字段,并对我们数据中观察到的任何编码路径名进行Base64解码。v11版本是为了处理不同格式的”__log“字段值而编写的。
samsung_gallery3d_log_parser_v10.py(研究的主要焦点)的帮助文本如下:
|
|
使用示例(注意:一个”__log“字段可能包含多个Base64编码的文件路径。脚本应能查找/提取所有路径):
python3 samsung_gallery3d_log_parser_v10.py -d s767vl-local.db -o s767vl-log-output.tsv
我们还编写了一个Python 3脚本来解析”trash“表:samsung_gallery3d_trash_parser_v10.py
samsung_gallery3d_trash_parser_v10.py的帮助文本如下:
|
|
使用示例:
python3 samsung_gallery3d_trash_parser_v10.py -d s767vl-local.db -o s767vl-trash-output.tsv
GitHub仓库中还包含了一些额外的脚本(需要进一步测试)。
java-hashcode.py脚本的编写是为了将给定路径转换为”album“表中可见的”__bucketID“值。使用dex2jar、JD-GUI和JADX等工具,我们能够找到负责路径编码的代码(参见Logger.class::getEncodedString方法),并编写了相应的Python函数来Base64解码编码的路径字符串。
如前所述,路径解码过程是:
- 移除最后7个Base64编码的字符
- 从编码字符串的开头移除3-6个字符,直到获得有效的Base64长度(4字节的倍数)
- 执行Base64解码
- 移除任何特殊的填充字符,例如Black Star、Black Circle
因此,"log“表脚本(samsung_gallery3d_log_parser_v10.py)的基本过程是:
- 查询数据库,例如:
"SELECT _id, __category, __timestamp, __log FROM log ORDER BY __timestamp ASC;" - 对于每一行:
- 提取”
__log“和”timestamp“字段 - 解码Base64编码的路径(每个日志项可能有多条路径)
- 将提取的字段和解码后的路径字段打印到TSV
- 提取”
“trash“表脚本(samsung_gallery3d_trash_parser_v10.py)的过程是:
- 查询数据库,例如:
"SELECT __absID, __absPath, __Title, __originPath, __originTitle, __deleteTime, __restoreExtra FROM trash ORDER BY __deleteTime ASC;" - 对于每一行:
- 提取
__absPath、__originPath、__deleteTime和__restoreExtra(元数据)字段 - 将提取的字段打印到TSV(无需路径解码)
- 提取
总结 所有这些研究让我们对逆向工程Android应用、新颖独特的哈希算法以及不同的编码技术有了更深入的理解。进一步研究现有工具可能解析或未解析的应用数据库,仍然可以发现新的信息、感兴趣的文件夹、日志文件等。你可能会发现Android或特定应用较新版本中引入的新数据。
对于Mike的案件,使用本文中的研究和脚本表明,用户有截屏或下载图片然后不久将其删除并清空回收站的习惯。网络浏览器被用来访问相关图片,但删除浏览器历史记录也是一个频繁的过程。恢复的截屏名称显示用户在特定时间使用了网络浏览器。不幸的是,这些日期和时间与问题中的时间不匹配,但这确实引出了在其他已解析数据中未发现的其他需要调查的时间。
与Mike一起研究这篇文章让Monkey对三星Gallery应用有了更多了解,获得了逆向Android APK的更多经验,并保持了他的Python技能。就像任何语言一样,缺乏使用会生疏。
编写了各种Python 3脚本来协助解析三星Gallery3d应用(v10.2.00.21)的”log“和”trash“表。这些表可能存储有关从三星Gallery3d应用内执行的图片删除的信息,例如时间戳和原始文件路径。
本文也展示了协作研究如何能够提高产出/开发新工具。例如,将Mike的测试观察与Monkey的脚本编写相结合。有机会与拥有不同知识、技能、经验和新鲜视角的人合作是无价的。利用这种经验可能与参加培训课程或网络研讨会一样好,甚至更好。
特别感谢Mike分享他的研究并共同撰写本文——希望我们未来能再次合作 :)