Linux 与 Android 内存取证

Linux 内存取证在技术上比 Windows 更开放——没有卷级加密,/proc 直接暴露大量运行时信息。

最后更新 2026-10-09版本 v1.0维护 DigiForensics查看历史 0

关键词:task_struct、mm_struct、VMA、slab、btf2json、bash 历史、TTY 钩子、rootkit、ART、dalvik、Frida 难度:高级 前置知识:Linux 内核内存管理、进程结构、Android 运行时(ART)、Volatility 3 Linux 插件

一、概述

Linux 内存取证在技术上比 Windows 更开放——没有卷级加密,/proc 直接暴露大量运行时信息。

但困难不在采集,而在符号。

Volatility 3 分析 Linux 需要与目标内核精确匹配的 ISF,这件事无法自动完成。Windows 侧可以按 PDB GUID 从微软符号服务器自动下载,Linux 做不到。各发行版与自定义内核都会改动内核,同一个版本号的二进制也未必相同。符号必须自行构建(dwarf2json 或 btf2json)。这一步没做,后面所有插件都跑不起来。

Android 是另一个独立子问题:内核是 Linux,用户态却是 ART 而非标准 libc 栈。匿名可执行映射、ART 堆、注入框架痕迹都有自己的判据,采集门槛(root、SELinux)也完全不同。

这一步落在分析定性环节,是 memory 板块里非 Windows 平台的落点。采集已经由内存采集方法完成,要解决的是符号怎么备、镜像怎么读、结果怎么判。它与 Windows 侧是互补而非替代——判断一台 Linux/Android 机器上发生了什么,通常要拿 Windows 侧那套判据(父子关系、命令行、连接归属)加上 Linux/Android 特有的判据(TTY 挂钩、命名空间、ART 堆)。

可用的对象与关键点:

对象 形态 关键点
符号来源 BTF(btf2json,用 vmlinuz)/ DWARF(dwarf2json,需调试包) 必须与目标内核精确匹配
内核结构 task_struct、files_struct、vm_area_struct 插件的解析基础
命令历史 linux.bash 的内存副本 磁盘文件被删也不影响
环境变量 linux.envars 常含 API Key 与数据库口令
TTY 输入 TTY 驱动层输入缓冲 键盘记录器必须挂钩 TTY 层
命名空间 /proc/<pid>/ns/* 的链接目标 容器逃逸与横向移动的判据
Android 侧 ART 堆、匿名可执行映射、/data/app 私有目录 用户态不是标准 libc 栈

第三到第六行是 Linux 特有的优势项,其中 linux.bash 的价值最高:磁盘上的历史被删了,内存里的那份还在。

据此能回答的是这些:目标 Linux 上跑着哪些进程、执行过哪些命令、进程环境变量里有什么、是否有 rootkit 迹象(未挂载的文件系统、隐藏进程、TTY 钩子)、容器有没有命名空间穿越痕迹、Android 上有没有注入框架或监控应用的运行时痕迹。

回答不了的是这些。符号不匹配时不是"结论少一点",是全部结论作废。对已锁定或已重启的 Android 设备,明文在内存里就是密文块,这一项无解。内存里取不到的(已换出页、已释放内存),回落盘侧的工件地图重找一遍。

往下读之前,先确认你已经掌握:Linux 内核符号与 BTF/DWARF 的基本概念、/proc 虚拟文件系统的结构、Android 的 root 与 SELinux 机制、以及 Volatility 的基本操作。符号构建是这一主题最大的前置工作,建议先看Windows 内存 Profile 机制理解符号层在整条链路中的位置,再看proc 虚拟文件系统取证打牢 /proc 结构基础。

二、核心原理

2.1 核心结构与插件对应

物理内存
├── 内核动态内存
│   ├── slab / SLUB ← task_struct、mm_struct 从此分配
│   │   └── kmem_cache:每种对象类型一个池 ← rootkit 常用来藏数据
│   └── 内核模块区、内核栈、kmsg 环形日志
└── 用户空间(每进程独立)
    ├── task_struct ← 进程在内核中的"身份证"
    ├── mm_struct   ← 地址空间描述符
    └── VMA 链表    ← 文本段/数据段/堆/栈/匿名映射 ← 注入常在匿名映射
结构 作用 插件
task_struct PID、状态、地址空间指针、父子关系 linux.pslist、psaux、pstree
VMA 地址空间中一段连续区域 linux.proc.Maps、linux.malware.malfind
slab / kmem_cache 内核对象分配器 linux.malware.check_modules、hidden_modules
kmsg 内核环形日志(dmesg 数据源) linux.kmsg

/proc/kcore 的意义:它把物理内存按 section 暴露为 ELF 视图,section 划分对应物理地址区间。

这正是 LiME 用 format=lime 记录地址范围的原因——分析工具需要"文件偏移 → 物理地址"的映射才能正确解析结构。

2.2 Linux 特有工件

工件 内容 独特性 插件
bash 历史 历史缓冲区,含时间戳 磁盘文件删了也能从内存取回 linux.bash
环境变量 进程环境 常直接带密钥与内网地址 linux.envars
TTY 事件 终端输入的按键 Windows 无对等物 linux.malware.tty_check
打开的文件 文件描述符指向 能看到已 unlink 但仍打开的句柄 linux.lsof
内核日志 dmesg 缓冲内容 模块加载、异常 linux.kmsg
挂载点 挂载表,含网络文件系统 反映运行期的挂载关系 linux.mountinfo

linux.bash 的价值被严重低估。/root/.bash_history 被清空是入侵案的常见反取证手段,但 bash 进程内存中的历史缓冲区仍保留着这些命令——包括清除命令本身。

注意:bash 记录时间戳的前提是 HISTFILE 设置了时间格式(如 %F),否则不记录时间。所以有输出不代表每条都有时间戳。

2.3 rootkit 检测的三个层次

层次 手法 检测插件 局限
一、用户态 替换/劫持 ls、ps、ss、netstat、top linux.lsof(看实际加载了什么) 需与发行版哈希基线比对
二、内核模块 加载隐藏 .ko,钩子函数 check_modules、hidden_modules 若 sysfs 被篡改则失效
三、内核级 改系统调用表/IDT,不加载模块 check_syscall、check_idt、tty_check 隐蔽性最高

check_modules 与 hidden_modules 的区别:前者比对"内存中的模块信息"与"sysfs 中可读到的信息"——若 rootkit 篡改 /sys/module 隐藏了模块,两者会不一致。后者专门寻找"在内存里有完整结构、但未出现在模块列表"的模块。

TTY 检查是 Linux 独有的检测面。键盘记录器必须挂钩 TTY 层的输入处理函数——这是它的工作原理决定的。tty_check 正是检查这些函数指针是否指向预期模块之外的地方。

2.4 符号获取:必须自建

Linux 内核的调试信息(.debug 段或 BTF)不在任何中心服务器上,随发行版 -dbgsym 包分发或需自行编译。

工具 语言 输入 依赖 适用
btf2json Rust(独立工具) --btf <BTF 文件> + --map <符号表> 不需要完整调试内核 现代内核推荐
dwarf2json Go --elf <vmlinux> 完整 DWARF 有发行版 dbgsym 包时

⚠️ 别把两者混用:--btf / --map 是** btf2json 的参数**;dwarf2json 只有 --elf / --elf-symbols / --elf-types / --system-map,给它传 --btf 会直接报错。

关键差异:Windows 是"事后联网取",Linux 是**"事前在目标机上准备"**。

且 Linux 符号文件名不影响匹配——内核 banner 编码在 ISF 内部,框架靠它匹配;这一点与 Windows(靠 GUID)不同。

2.5 Android 特有:ART 运行时与注入迹象

/proc/<pid>/maps 关键映射判读:

映射名 判读
[anon:dalvik-main space (region space)] 正常,ART 主堆,每个 Android 进程都有
[anon:dalvik-LinearAlloc] / [anon:dalvik-indirect ref table] 正常
[anon:dalvik-jit-code-cache] 正常但需注意:可执行,是合法 JIT 代码
[anon:scudo:primary] 正常(内存分配器)
/data/local/tmp/*.so ⚠️ 高度可疑,调试框架常驻位置
/data/data/<pkg>/files/*.so 可疑(除非该应用正常行为)
匿名可执行页(无对应文件) ⚠️ 强注入信号

判读核心:dalvik-* 映射不是注入迹象——每个 Android 进程都有。把 dalvik-main space 报成"隐藏代码"是常见误判。

真正的注入迹象:① 匿名可执行映射且无对应文件;② /data/local/tmp/ 下的 .so;③ 内存中的注入框架字符串(如 frida-agent、gum-js-loop,见第七章);④ 应用主类加载路径与包名不符。在涉案设备上发现 Frida 痕迹,意味着要么设备被用于攻击,要么分析环境被污染——两种情况都要写入报告。

2.6 iOS 内存取证的现实

iOS 的结论先行:现代 iOS 上在线内存采集不可行。

障碍 说明
必须越狱 未越狱设备无法获得内核级访问
公开方案已失效 该领域曾出现的公开方案在现代 iOS 上已失效
idevicesyslog 不是内存 它只是系统日志(等价于 log stream)
Apple 的立场 不向第三方开放内核内存访问,仅企业 MDM 有正式通道

易混淆概念澄清:很多资料把 idevicesyslog、Crash 日志、ipsw 提取列为"iOS 内存取证",这些都不是。

前者是日志,中间是崩溃转储(仅崩溃瞬间的少量内容),后者是固件镜像。真正的内存镜像在 iOS 上没有可行的合法采集路径。

实务建议:iOS 案件的内存维度应作为"证据局限"明确写入报告,而不是勉强用日志冒充内存证据。


三、操作步骤

3.1 第 1 步:符号准备(Linux 侧的前置工作)

# 1) 确认目标内核版本(采集时就应记录)
uname -r                                    # → 5.10.134-android12-9

# 2) 收集符号生成所需的输入(在目标机或同构机器上)
ls -la /boot/vmlinuz-$(uname -r) /boot/System.map-$(uname -r)

# 3) 检查内核是否含 BTF(决定用哪条路线)
#    现代内核可直接从 /sys/kernel/btf/vmlinux 取
ls -la /sys/kernel/btf/vmlinux 2>/dev/null || \
  objcopy -O binary --only-section=.BTF /boot/vmlinuz-$(uname -r) /tmp/vmlinux.btf

路线 A(推荐):btf2json——只需 vmlinuz 的 BTF + System.map,不需要完整调试内核;路线 B:dwarf2json——需要带 DWARF 的 vmlinux。 完整构建命令见第七章。

确认符号可用:

python3 vol.py -f DigiForensics-mem.raw -s /opt/isf linux.pslist
# 正常输出应含 init、内核线程、用户进程;报符号不匹配即 ISF 无效

# 看框架实际加载了哪个符号文件
python3 vol.py -f DigiForensics-mem.raw -s /opt/isf isfinfo

3.2 第 2 步:进程、痕迹、网络与 rootkit

V="python3 vol.py -f DigiForensics-mem.raw -s /opt/isf"

$V linux.pslist        # 进程列表
$V linux.pstree        # 父子关系
$V linux.psaux         # 含命令行、环境、父进程
$V linux.proc.Maps     # 内存映射(识别匿名可执行区)
$V linux.bash          # bash 历史(含时间戳)
$V linux.envars        # 环境变量(常含密钥)
$V linux.kmsg          # 内核日志
$V linux.lsof          # 打开的文件

# 注意:Linux 没有 netscan 插件
$V linux.sockstat       # 套接字统计(当前活动)
$V linux.sockscan       # 套接字扫描(内核池扫描)
$V linux.malware.tty_check

进程摘链检测(与 Windows 同思路,见第 05 篇):

$V linux.pslist | awk '{print $2}' | sort -u > /tmp/pslist_pids.txt
$V linux.psscan | awk '{print $2}' | sort -u > /tmp/psscan_pids.txt
comm -13 /tmp/pslist_pids.txt /tmp/psscan_pids.txt    # psscan 有、pslist 无

与 Windows 的重要差异:linux.sockscan 也是扫描内核池得到,同样存在 stale data 问题。判读原则与第 06 篇一致——扫描结果必须交叉验证。

⚠️ 命名规范:linux.malware.* 是规范名称。顶层 linux.check_modules、linux.tty_check、linux.hidden_modules 等旧名虽仍可用,但已标记弃用并设定了移除日期,脚本里应固定用规范名。

3.3 第 3 步:Android 采集与初筛

完整采集脚本:

cat > /work/android-acquire.sh <<'SCRIPT'
#!/bin/bash
# Android 内存采集(需 root)
set -e
OUT="${1:-/sdcard/DigiForensics-mem.raw}"
LOG="/sdcard/acq-log.txt"

echo "=== Android 内存采集 $(date -Iseconds) ===" > "$LOG"
getprop ro.product.model           >> "$LOG" 2>&1
getprop ro.build.version.release   >> "$LOG" 2>&1
getprop ro.build.fingerprint       >> "$LOG" 2>&1
uname -a                           >> "$LOG" 2>&1
getenforce                         >> "$LOG" 2>&1   # SELinux 状态(关键)
grep -E 'MemTotal|MemAvailable' /proc/meminfo >> "$LOG" 2>&1
ps -A -o PID,PPID,USER,NAME        >> "$LOG" 2>&1

/data/local/tmp/avml "$OUT"        >> "$LOG" 2>&1
ls -l "$OUT"                       >> "$LOG" 2>&1
echo "=== 完成 $(date -Iseconds) ===" >> "$LOG"
cat "$LOG"
SCRIPT

adb push /work/android-acquire.sh /data/local/tmp/
adb shell su -c "setenforce 0"          # 放行(记录此状态变更!)
adb shell su -c "chmod 755 /data/local/tmp/android-acquire.sh"
adb shell su -c "/data/local/tmp/android-acquire.sh /sdcard/DigiForensics-mem.raw"
adb pull /sdcard/DigiForensics-mem.raw
adb shell su -c "rm -f /sdcard/DigiForensics-mem.raw /data/local/tmp/avml /data/local/tmp/android-acquire.sh"

Android 初筛脚本(> ⚠️ 手机镜像同样动辄数 GB,strings 一次读全会 OOM——复用第 06 篇的 scan_img 分块函数,或对单进程用 windows.memmap 等价方式先切出可疑进程内存):

cat > /work/android-triage.sh <<'SCRIPT'
#!/bin/bash
# Android 内存镜像初筛(依赖外部定义的 scan_img <正则> <输出> [最小串长])
IMG="${1:-DigiForensics-mem.raw}"
scan_img 'frida-agent|gum-js-loop|gum-js-bridge|re\.frida'            /tmp/t1.txt
scan_img '/data/local/tmp/.*\.so'                                      /tmp/t2.txt 8
scan_img 'password=|passwd=|jdbc:|mysql://|postgres(ql)?://'          /tmp/t3.txt
scan_img 'api[_-]?key|access[_-]?token|Bearer '                       /tmp/t4.txt
scan_img 'AIza[0-9A-Za-z_-]{35}|AKIA[0-9A-Z]{16}'                     /tmp/t5.txt
scan_img 'magisk|supersu|busybox|xposed|substrate|cydia'             /tmp/t6.txt
cat /tmp/t1.txt /tmp/t2.txt /tmp/t5.txt | head -40   # 注入框架 / 临时 .so / root 框架
grep -hE 'AIza|AKIA' /tmp/t5.txt                      # 云凭据(仅记录,不外传)
SCRIPT
chmod +x /work/android-triage.sh
/work/android-triage.sh DigiForensics-mem.raw

3.4 第 4 步:ART 映射判读

$V linux.proc.Maps > /work/maps.txt
# 正常的 ART 映射(每个进程都有,不是异常)
grep -E 'dalvik-main space|dalvik-LinearAlloc|dalvik-indirect ref table' /work/maps.txt | head
# 可疑:临时目录的库
grep -E '/data/local/tmp/' /work/maps.txt
# 匿名可执行映射(注入的强信号)——先排除 ART 合法映射
grep -E '\[anon' /work/maps.txt | grep -viE 'dalvik|scudo|jit-code' | head -20

四、常见陷阱

Linux 与 Android 的困难不在采集而在解释:Windows 侧能按 PDB GUID 自动取到符号,Linux 侧必须事前自建;Android 侧满屏 dalvik-* 映射,正常与异常在形态上高度相似。

本章按符号、Linux 工件与 rootkit、stale data、Android 判读、阴性表述五组展开。

4.1 符号:Linux 侧的硬前置

四条陷阱全在这一组。共同点是:它们都发生在符号还没备好的时候,而此时插件往往不会报"错",只是给出一片看起来正常的空结果。

陷阱 1:Linux 符号不准备就直接分析

现象:拿到 Linux 内存镜像直接跑 linux.pslist,报符号不匹配或输出为空,转而认为镜像不可用。

为什么会误判:1 把 Linux 的困难点说得很直接——技术上比 Windows 更开放,但困难在别处:符号。Volatility 3 分析 Linux 需要与目标内核精确匹配的 ISF,而 2.1 的结构表里 task_struct、mm_struct、VMA 链表的解析全依赖这些结构定义。没有匹配符号,框架连 task_struct 的字段偏移都不知道,输出为空是必然的。

误判代价:把"没有解释器"当成"数据不可用"。

案例第 1 步整个花在符号构建上(dwarf2json 从带 DWARF 的 vmlinux 生成 ISF),第 2 步才拿到正常的进程列表。跳过这一步,64 GB 的服务器镜像就白采了。

正确做法:符号是分析的前置工作,不是分析的步骤。

3.1 是完整流程:确认 uname -r → 收集 /boot/vmlinux-$(uname -r) 与 System.map-$(uname -r) → 检查是否有 BTF(ls -la /sys/kernel/btf/vmlinux)→ 生成 ISF → 用 linux.pslist + isfinfo 验证。

验证标准要具体:输出里应能看到 swapper/0/0、kthreadd 这类内核线程,以及至少一个用户进程。