Windows应急响应 - 07 文件系统取证

07-文件系统取证 (Filesystem Forensics)

Windows 文件系统(NTFS)包含大量 Linux ext4 没有的取证特性:Alternate Data Streams(ADS)可以隐藏数据、$MFT 记录完整的文件元数据、$STANDARD_INFORMATION 和 $FILE_NAME 双时间戳可以检测 timestomping。从 Linux 背景转到 Windows IR,文件系统取证是差异最大的领域之一。

前置知识01-系统基础与注册表 | 04-取证制品分析

关联章节06-进程与网络分析 | 09-注册表持久化审计

Linux 对照06-文件系统取证


1. Alternate Data Streams (ADS)

1.1 ADS 基本概念

ADS 是 NTFS 文件系统的特性,允许一个文件拥有多个数据流

默认的数据流是 $DATA(即文件的正常内容),额外的流不改变文件大小

Linux 类比:Linux ext4 没有 ADS,最接近的概念是 Extended Attributes(xattr),但 ADS 能存储任意大小的数据

攻击者利用场景

在正常文件中隐藏恶意 payload

存储 C2 配置数据

绕过安全产品的文件扫描(部分产品不扫描 ADS)

1.2 ADS 创建方法(了解攻击手法)

1
2
3
4
5
6
7
8
9
10
11
12
:: 创建 ADS(攻击者视角,了解即可)
:: 将 payload.exe 隐藏在 normal.txt 的 ADS 中
type payload.exe > normal.txt:hidden.exe

:: 写入文本到 ADS
echo "C2 config data" > normal.txt:config.ini

:: PowerShell 创建 ADS
Set-Content -Path "normal.txt" -Stream "hidden.exe" -Value (Get-Content payload.exe -Raw)

:: 甚至可以在目录上创建 ADS
echo "hidden data" > C:\Users\Public:secret.txt

1.3 ADS 检测方法

方法 1:dir /r

1
2
3
4
5
6
7
8
9
10
:: 显示文件的所有数据流
dir /r C:\Users\Public\

:: 输出示例:
:: 2024-01-15 03:22 1,024 normal.txt
:: 512 normal.txt:hidden.exe:$DATA
:: 28 normal.txt:config.ini:$DATA

:: 递归扫描
dir /r /s C:\Users\ 2>nul | findstr ":$DATA"

方法 2:PowerShell Get-Item -Stream

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 检查单个文件的 ADS
Get-Item "C:\Users\Public\normal.txt" -Stream * |
Where-Object { $_.Stream -ne ':$DATA' -and $_.Stream -ne 'Zone.Identifier' }

# 批量扫描目录(排除常见的 Zone.Identifier)
Get-ChildItem "C:\Users" -Recurse -ErrorAction SilentlyContinue | ForEach-Object {
$streams = Get-Item $_.FullName -Stream * -ErrorAction SilentlyContinue |
Where-Object { $_.Stream -ne ':$DATA' -and $_.Stream -ne 'Zone.Identifier' }
if ($streams) {
foreach ($stream in $streams) {
[PSCustomObject]@{
FilePath = $_.FullName
StreamName = $stream.Stream
StreamSize = $stream.Length
}
}
}
} | Format-Table -AutoSize

# 读取 ADS 内容
Get-Content "C:\Users\Public\normal.txt" -Stream hidden.exe -Encoding Byte |
Set-Content "C:\IR\extracted_hidden.exe" -Encoding Byte

方法 3:streams.exe(Sysinternals)

1
2
3
4
5
:: 扫描目录中的 ADS
streams.exe -s C:\Users\

:: 递归扫描并显示详细信息
streams.exe -s -d C:\inetpub\wwwroot\

1.4 Zone.Identifier 流

Windows 从网络下载的文件会被标记 Zone.Identifier ADS

这是 Mark of the Web (MOTW) 机制

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
# 查看下载文件的来源信息
Get-Content "C:\Users\Admin\Downloads\tool.exe" -Stream Zone.Identifier
# 输出示例:
# [ZoneTransfer]
# ZoneId=3 # 3 = Internet
# ReferrerUrl=https://evil.com/tool.exe
# HostUrl=https://evil.com/tool.exe

# ZoneId 含义:
# 0 = My Computer (本地)
# 1 = Local Intranet (内网)
# 2 = Trusted Sites (受信任)
# 3 = Internet (互联网)
# 4 = Restricted Sites (受限)

# 批量检查下载来源
Get-ChildItem "C:\Users\*\Downloads\*" -ErrorAction SilentlyContinue | ForEach-Object {
$zone = Get-Content $_.FullName -Stream Zone.Identifier -ErrorAction SilentlyContinue
if ($zone) {
[PSCustomObject]@{
File = $_.Name
Size = $_.Length
Modified = $_.LastWriteTime
ZoneInfo = ($zone -join ' | ')
}
}
} | Format-Table -AutoSize

2. MACE 时间戳与 Timestomping 检测

2.1 NTFS 时间戳体系

NTFS 为每个文件维护两组时间戳:

**$STANDARD_INFORMATION (SI)**:

Modified, Accessed, Changed (MFT), Created — 即 MACE

用户和程序可以通过 API 修改(SetFileTime

这是 dir 和 Explorer 显示的时间戳

**$FILE_NAME (FN)**:

同样有 MACE 四个时间戳

只有操作系统内核在文件被移动/重命名时才会更新

用户无法直接修改(除非直接修改磁盘原始数据)

Linux 对比

Linux ext4 有 mtime, atime, ctime, crtime(birth time)

touch -t 可以修改 mtime 和 atime,但 ctime 无法直接修改

Windows 的 $FILE_NAME 时间戳类似于 Linux 的 ctime,更难被篡改

2.2 Timestomping 检测方法

原理:攻击者修改了 $STANDARD_INFORMATION 时间戳,但 $FILE_NAME 时间戳未被修改

如果 SI 时间戳明显早于 FN 时间戳,说明被篡改

1
2
3
4
5
6
7
8
9
10
11
12
# 使用 MFTECmd(Eric Zimmerman)解析 $MFT
# 先提取 $MFT(需管理员权限)
# 方法 1:使用 RawCopy 或 KAPE
# 方法 2:使用 fsutil

# MFTECmd 解析
# MFTECmd.exe -f "C:\IR\$MFT" --csv "C:\IR\output" --csvf mft_output.csv

# 在 CSV 输出中,比较以下字段:
# SI_Created vs FN_Created
# SI_Modified vs FN_Modified
# 如果 SI 时间 << FN 时间,则为 timestomping

PowerShell 基础检测(只能看到 SI 时间戳):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 检测时间戳异常:创建时间晚于修改时间
Get-ChildItem "C:\Windows\System32" -ErrorAction SilentlyContinue | Where-Object {
$_.CreationTime -gt $_.LastWriteTime
} | Select-Object Name, CreationTime, LastWriteTime,
@{N='Diff_Hours'; E={ [math]::Round(($_.CreationTime - $_.LastWriteTime).TotalHours, 1) }} |
Sort-Object Diff_Hours -Descending | Select-Object -First 20

# 检测与系统安装时间不一致的文件
$osInstall = (Get-CimInstance Win32_OperatingSystem).InstallDate
Get-ChildItem "C:\Windows\System32" | Where-Object {
$_.CreationTime -lt $osInstall.AddYears(-1) # 创建时间早于系统安装 1 年以上
} | Select-Object Name, CreationTime | Format-Table

# 检查文件的编译时间戳(PE Header)vs 文件系统时间戳
# 使用 sigcheck 获取 PE 编译时间
# sigcheck.exe -e -s C:\suspect_dir

2.3 $MFT 和 $UsnJrnl 分析

**$MFT (Master File Table)**:记录所有文件的元数据,包括已删除文件

**$UsnJrnl (Update Sequence Number Journal)**:记录文件变更日志

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 提取 $MFT(需要管理员权限 + 原始磁盘访问)
# 使用 RawCopy.exe
# RawCopy.exe /FileNamePath:C:0 /OutputPath:C:\IR\ /OutputName:MFT_copy

# 使用 MFTECmd 解析 $MFT
# MFTECmd.exe -f "C:\IR\MFT_copy" --csv "C:\IR\" --csvf mft.csv

# 提取 $UsnJrnl(记录文件创建/删除/重命名操作)
# 使用 ExtractUsnJrnl
# ExtractUsnJrnl.exe /DevicePath:C: /OutputPath:C:\IR\

# 使用 MFTECmd 解析 $UsnJrnl
# MFTECmd.exe -f "C:\IR\$J" --csv "C:\IR\" --csvf usnjrnl.csv

# 在解析后的 CSV 中搜索:
# - 在可疑时间窗口内创建/修改/删除的文件
# - 扩展名为 .exe/.dll/.ps1/.bat 的新建文件
# - 在 System32 或 Web 目录中的变更

3. Recent Files 与 Jump Lists

3.1 Recent Files

用户打开的文件会在 Recent 目录中创建 LNK 快捷方式

位置C:\Users\<user>\AppData\Roaming\Microsoft\Windows\Recent\

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 列出 Recent 文件(按时间排序)
Get-ChildItem "$env:APPDATA\Microsoft\Windows\Recent" -Filter "*.lnk" |
Sort-Object LastWriteTime -Descending |
Select-Object Name, LastWriteTime | Format-Table -AutoSize

# 解析 LNK 文件获取目标路径
$shell = New-Object -ComObject WScript.Shell
Get-ChildItem "$env:APPDATA\Microsoft\Windows\Recent" -Filter "*.lnk" |
Sort-Object LastWriteTime -Descending | ForEach-Object {
$lnk = $shell.CreateShortcut($_.FullName)
[PSCustomObject]@{
LnkName = $_.Name
TargetPath = $lnk.TargetPath
Arguments = $lnk.Arguments
WorkingDir = $lnk.WorkingDirectory
LnkModified = $_.LastWriteTime
}
} | Format-Table -AutoSize

# 检查所有用户的 Recent 目录
Get-ChildItem "C:\Users\*\AppData\Roaming\Microsoft\Windows\Recent" -Filter "*.lnk" -ErrorAction SilentlyContinue |
Sort-Object LastWriteTime -Descending |
Select-Object @{N='User';E={$_.FullName.Split('\')[2]}}, Name, LastWriteTime |
Select-Object -First 50 | Format-Table -AutoSize

3.2 Jump Lists

Jump Lists 记录了应用程序最近打开的文件列表

位置

自动(AutomaticDestinations):C:\Users\<user>\AppData\Roaming\Microsoft\Windows\Recent\AutomaticDestinations\

自定义(CustomDestinations):C:\Users\<user>\AppData\Roaming\Microsoft\Windows\Recent\CustomDestinations\

文件名格式:<AppID>.automaticDestinations-ms

常见 AppID

AppID (十六进制) 应用程序
5f7b5f1e01b83767 cmd.exe
b91050d8b077a4e8 记事本
9b9cdc69c1c24e2b 画图
12dc1ea8e34b5a6 画图 (Win10)
7e4dca80246863e3 照片
1b4dd67f29cb1962 Explorer (文件夹)
1
2
3
4
5
6
7
8
# 使用 JLECmd(Eric Zimmerman)解析 Jump Lists
# JLECmd.exe -d "C:\Users\Admin\AppData\Roaming\Microsoft\Windows\Recent\AutomaticDestinations" --csv "C:\IR\" --csvf jumplists.csv

# 列出 Jump List 文件
Get-ChildItem "C:\Users\*\AppData\Roaming\Microsoft\Windows\Recent\AutomaticDestinations\*.automaticDestinations-ms" -ErrorAction SilentlyContinue |
Select-Object Name, Length, LastWriteTime,
@{N='User';E={$_.FullName.Split('\')[2]}} |
Sort-Object LastWriteTime -Descending | Format-Table -AutoSize

4. 回收站取证

4.1 回收站结构

回收站位于 C:\$Recycle.Bin\<SID>\

每个用户有独立的回收站目录,以 SID 命名

删除文件后生成两个文件:

$I<随机ID>.<原扩展名>:元数据文件(原路径、删除时间、文件大小)

$R<随机ID>.<原扩展名>:实际文件内容

Linux 对比:Linux 的回收站(如 GNOME Trash)在 ~/.local/share/Trash/,结构类似但格式不同

4.2 回收站分析

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
43
44
# 列出所有用户的回收站内容
Get-ChildItem "C:\`$Recycle.Bin" -Recurse -Force -ErrorAction SilentlyContinue |
Where-Object { $_.Name -like '$I*' } | ForEach-Object {
[PSCustomObject]@{
User_SID = $_.DirectoryName.Split('\')[-1]
MetaFile = $_.Name
Size = $_.Length
DeleteTime = $_.LastWriteTime
FullPath = $_.FullName
}
} | Format-Table -AutoSize

# 解析 $I 文件获取原始路径(Windows 10+ 格式)
function Parse-RecycleBinInfo {
param([string]$IFilePath)
$bytes = [System.IO.File]::ReadAllBytes($IFilePath)
# 版本 2 (Windows 10+):
# Offset 0: Header version (8 bytes)
# Offset 8: File size (8 bytes)
# Offset 16: Deletion timestamp (8 bytes, FILETIME)
# Offset 24: File name length (4 bytes)
# Offset 28: Original path (Unicode string)

$fileSize = [BitConverter]::ToInt64($bytes, 8)
$deleteTime = [DateTime]::FromFileTime([BitConverter]::ToInt64($bytes, 16))
$nameLen = [BitConverter]::ToInt32($bytes, 24)
$originalPath = [System.Text.Encoding]::Unicode.GetString($bytes, 28, $nameLen * 2)

[PSCustomObject]@{
OriginalPath = $originalPath
FileSize = $fileSize
DeleteTime = $deleteTime
}
}

# 批量解析
Get-ChildItem "C:\`$Recycle.Bin" -Recurse -Force -ErrorAction SilentlyContinue |
Where-Object { $_.Name -like '$I*' -and $_.Length -gt 28 } | ForEach-Object {
try {
$info = Parse-RecycleBinInfo $_.FullName
$info | Add-Member -NotePropertyName "SID" -NotePropertyValue $_.DirectoryName.Split('\')[-1]
$info
} catch {}
} | Sort-Object DeleteTime -Descending | Format-Table -AutoSize

使用 RBCmd(Eric Zimmerman)

1
2
3
4
5
:: 解析单个回收站目录
RBCmd.exe -d "C:\$Recycle.Bin\S-1-5-21-xxxx-500" --csv "C:\IR\" --csvf recycle_bin.csv

:: 解析所有用户的回收站
RBCmd.exe -d "C:\$Recycle.Bin" --csv "C:\IR\" --csvf recycle_bin_all.csv

5. 浏览器取证

5.1 浏览器数据路径

浏览器 数据目录 关键文件
Chrome %LOCALAPPDATA%\Google\Chrome\User Data\Default\ History, Downloads, Cookies, Login Data, Bookmarks
Edge %LOCALAPPDATA%\Microsoft\Edge\User Data\Default\ 同 Chrome(Chromium 内核)
Firefox %APPDATA%\Mozilla\Firefox\Profiles\<profile>\ places.sqlite, cookies.sqlite, logins.json
IE/Legacy Edge %LOCALAPPDATA%\Microsoft\Windows\WebCache\ WebCacheV01.dat (ESE 数据库)

5.2 Chrome/Edge 历史记录查询

Chrome 和 Edge 使用 SQLite 数据库存储历史记录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 复制 Chrome 历史数据库(Chrome 运行时文件被锁定)
$chromeHistory = "$env:LOCALAPPDATA\Google\Chrome\User Data\Default\History"
$copyPath = "C:\IR\Chrome_History_copy"
Copy-Item $chromeHistory $copyPath -Force -ErrorAction SilentlyContinue

# 使用 sqlite3 查询(需要安装 sqlite3)
# sqlite3 "C:\IR\Chrome_History_copy" "SELECT url, title, visit_count, datetime(last_visit_time/1000000-11644473600,'unixepoch','localtime') as last_visit FROM urls ORDER BY last_visit_time DESC LIMIT 100;"

# 查看下载记录
# sqlite3 "C:\IR\Chrome_History_copy" "SELECT target_path, tab_url, total_bytes, datetime(start_time/1000000-11644473600,'unixepoch','localtime') as download_time FROM downloads ORDER BY start_time DESC LIMIT 50;"

# PowerShell 方式(需要 PSSQLite 模块或内置 .NET SQLite)
# Install-Module PSSQLite
# Import-Module PSSQLite
# Invoke-SqliteQuery -DataSource $copyPath -Query "SELECT url, title FROM urls ORDER BY last_visit_time DESC LIMIT 50"

# 快速查看浏览器数据文件列表
Get-ChildItem "$env:LOCALAPPDATA\Google\Chrome\User Data\Default\" -ErrorAction SilentlyContinue |
Where-Object { $_.Name -in @('History','Downloads','Cookies','Login Data','Web Data','Bookmarks') } |
Select-Object Name, Length, LastWriteTime | Format-Table

5.3 Firefox 历史记录查询

1
2
3
4
5
6
7
8
9
10
11
12
# 查找 Firefox Profile 目录
$ffProfiles = Get-ChildItem "$env:APPDATA\Mozilla\Firefox\Profiles\*" -Directory -ErrorAction SilentlyContinue
foreach ($profile in $ffProfiles) {
Write-Host "Profile: $($profile.Name)" -ForegroundColor Cyan
$placesDb = Join-Path $profile.FullName "places.sqlite"
if (Test-Path $placesDb) {
$copyPath = "C:\IR\ff_places_$($profile.Name).sqlite"
Copy-Item $placesDb $copyPath -Force
Write-Host " 已复制到:$copyPath"
# sqlite3 $copyPath "SELECT url, title, visit_count, datetime(last_visit_date/1000000,'unixepoch','localtime') FROM moz_places ORDER BY last_visit_date DESC LIMIT 50;"
}
}
1
2
3
4
5
6
7
8
9
10
11
12
# Chrome 缓存目录
$chromeCache = "$env:LOCALAPPDATA\Google\Chrome\User Data\Default\Cache\Cache_Data"
if (Test-Path $chromeCache) {
$cacheFiles = Get-ChildItem $chromeCache -ErrorAction SilentlyContinue
Write-Host "Chrome 缓存文件数量:$($cacheFiles.Count)"
Write-Host "最近缓存文件:"
$cacheFiles | Sort-Object LastWriteTime -Descending |
Select-Object Name, Length, LastWriteTime -First 10 | Format-Table
}

# 使用 ChromeCacheView (NirSoft) 或 hindsight 工具进行完整解析
# hindsight.exe -i "C:\Users\Admin\AppData\Local\Google\Chrome\User Data" -o C:\IR\chrome_forensics

6. Temp 目录与可疑文件扫描

6.1 关键 Temp 目录

路径 说明 重点关注
%TEMP% / %TMP% 当前用户临时目录 恶意软件 drop 位置
C:\Windows\Temp 系统临时目录 服务/计划任务的 drop 位置
C:\Users\Public 公共用户目录 攻击者常用的写入目录
C:\ProgramData 程序数据 恶意软件配置/数据
C:\Users\<user>\Downloads 下载目录 钓鱼附件
C:\Users\<user>\Desktop 桌面 用户手动保存的工具
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
# 扫描 Temp 目录中的可执行文件
$tempDirs = @(
$env:TEMP,
"C:\Windows\Temp",
"C:\Users\Public",
"C:\ProgramData"
)

$suspiciousExts = @('.exe','.dll','.ps1','.bat','.cmd','.vbs','.js',
'.wsf','.hta','.scr','.pif','.com','.msi','.jar')

foreach ($dir in $tempDirs) {
$files = Get-ChildItem $dir -Recurse -ErrorAction SilentlyContinue |
Where-Object { $suspiciousExts -contains $_.Extension.ToLower() }
if ($files) {
Write-Host "[!] $dir 中发现可疑文件:" -ForegroundColor Yellow
$files | Select-Object Name, Length, CreationTime, LastWriteTime, FullName |
Format-Table -AutoSize
}
}

# 检查最近 7 天创建的可执行文件(全盘扫描)
$cutoff = (Get-Date).AddDays(-7)
Get-ChildItem "C:\" -Recurse -ErrorAction SilentlyContinue |
Where-Object {
$suspiciousExts -contains $_.Extension.ToLower() -and
$_.CreationTime -gt $cutoff
} | Select-Object FullName, Length, CreationTime |
Sort-Object CreationTime -Descending | Format-Table -AutoSize

6.2 文件哈希计算与 VirusTotal 查询

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
# 计算文件哈希
Get-FileHash "C:\suspect\malware.exe" -Algorithm SHA256
Get-FileHash "C:\suspect\malware.exe" -Algorithm MD5

# 批量计算目录下所有可执行文件的哈希
Get-ChildItem "C:\Windows\Temp" -Recurse -ErrorAction SilentlyContinue |
Where-Object { $_.Extension -in @('.exe','.dll','.ps1','.bat') } |
ForEach-Object {
[PSCustomObject]@{
File = $_.FullName
SHA256 = (Get-FileHash $_.FullName -Algorithm SHA256).Hash
Size = $_.Length
Created = $_.CreationTime
}
} | Format-Table -AutoSize

# 使用 VirusTotal API 查询哈希(需要 API Key)
function Check-VTHash {
param([string]$Hash, [string]$ApiKey)
$headers = @{ "x-apikey" = $ApiKey }
$response = Invoke-RestMethod -Uri "https://www.virustotal.com/api/v3/files/$Hash" -Headers $headers -ErrorAction SilentlyContinue
if ($response) {
$stats = $response.data.attributes.last_analysis_stats
[PSCustomObject]@{
Hash = $Hash
Detections = "$($stats.malicious)/$($stats.malicious + $stats.undetected)"
Name = $response.data.attributes.meaningful_name
}
}
}

7. 文件签名验证

7.1 Authenticode 签名检查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 检查单个文件的数字签名
Get-AuthenticodeSignature "C:\Windows\System32\svchost.exe"

# 签名状态含义:
# Valid - 签名有效
# NotSigned - 未签名
# HashMismatch - 文件已被篡改(签名不匹配)
# NotTrusted - 签名者不受信任
# UnknownError - 无法验证

# 批量检查 System32 中未签名的 EXE
Get-ChildItem "C:\Windows\System32\*.exe" | ForEach-Object {
$sig = Get-AuthenticodeSignature $_.FullName
if ($sig.Status -ne 'Valid') {
[PSCustomObject]@{
File = $_.Name
Status = $sig.Status
Path = $_.FullName
}
}
} | Format-Table -AutoSize

7.2 sigcheck.exe(Sysinternals)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
:: 检查目录中未签名的文件
sigcheck.exe -u -e -s C:\Windows\System32\

:: 检查并提交到 VirusTotal
sigcheck.exe -u -e -s -vt C:\Windows\System32\

:: 参数说明:
:: -u 仅显示未签名文件
:: -e 扫描可执行文件(EXE/DLL 等)
:: -s 递归子目录
:: -vt 提交到 VirusTotal 检查
:: -h 显示文件哈希
:: -c CSV 输出

:: 检查特定文件并显示 PE 信息
sigcheck.exe -a C:\suspect\file.exe
:: 显示:签名者、编译时间、熵值、版本信息等

8. Webshell 检测

8.1 IIS 网站目录排查

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
# 获取 IIS 网站物理路径
Import-Module WebAdministration -ErrorAction SilentlyContinue
Get-Website | Select-Object Name, PhysicalPath, State

# 或直接检查默认路径
$webRoots = @(
"C:\inetpub\wwwroot",
"C:\inetpub"
)
# 动态获取 IIS 站点路径
try {
$iisRoots = (Get-Website).PhysicalPath | ForEach-Object { [Environment]::ExpandEnvironmentVariables($_) }
$webRoots += $iisRoots
} catch {}

# 扫描 Webshell 常见特征
$webshellExts = @('.aspx','.asp','.ashx','.asmx','.cshtml','.php','.jsp','.jspx')

foreach ($root in ($webRoots | Select-Object -Unique)) {
if (Test-Path $root) {
Write-Host "[*] 扫描 Web 目录:$root" -ForegroundColor Yellow

# 1. 近期新增的 Web 文件
Get-ChildItem $root -Recurse -ErrorAction SilentlyContinue |
Where-Object { $webshellExts -contains $_.Extension.ToLower() -and
$_.CreationTime -gt (Get-Date).AddDays(-30) } |
Select-Object FullName, CreationTime, LastWriteTime, Length | Format-Table -AutoSize

# 2. 检查文件内容中的 Webshell 关键字
Get-ChildItem $root -Recurse -Include *.aspx,*.asp,*.ashx -ErrorAction SilentlyContinue |
Select-String -Pattern 'eval\(|Execute\(|cmd\.exe|powershell|System\.Diagnostics\.Process|Runtime\.getRuntime|ProcessStartInfo|unsafe|Request\[|Request\.Item' -List |
Select-Object Path, LineNumber, Line | Format-Table -AutoSize
}
}

8.2 Webshell 特征关键字

语言 常见 Webshell 关键字
ASPX eval(, Execute(, ProcessStartInfo, cmd.exe, Request["cmd"]
ASP Execute(, Eval(, CreateObject("WScript.Shell"), cmd /c
PHP eval(, system(, exec(, passthru(, shell_exec(, $_POST[
JSP Runtime.getRuntime().exec(, ProcessBuilder, <%@ page import="java.io.*"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 检查小文件(Webshell 通常很小)
Get-ChildItem $webRoots[0] -Recurse -Include *.aspx,*.asp,*.ashx -ErrorAction SilentlyContinue |
Where-Object { $_.Length -lt 10KB } |
Select-Object FullName, Length, CreationTime |
Sort-Object CreationTime -Descending | Format-Table -AutoSize

# 检查文件权限异常(IIS 用户不应创建文件)
Get-ChildItem $webRoots[0] -Recurse -Include *.aspx -ErrorAction SilentlyContinue |
ForEach-Object {
$acl = Get-Acl $_.FullName
$owner = $acl.Owner
if ($owner -like '*IIS*' -or $owner -like '*IUSR*' -or $owner -like '*NETWORK SERVICE*') {
[PSCustomObject]@{
File = $_.FullName
Owner = $owner
Created = $_.CreationTime
Size = $_.Length
}
}
} | Format-Table -AutoSize

9. 实战练习:发现隐藏 ADS 与回收站取证

9.1 练习 1:ADS 检测

目标:在指定目录中发现隐藏的 ADS,提取其内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Step 1:扫描目录中的 ADS
Get-ChildItem "C:\Investigation\Sample\" -Recurse | ForEach-Object {
Get-Item $_.FullName -Stream * -ErrorAction SilentlyContinue |
Where-Object { $_.Stream -ne ':$DATA' -and $_.Stream -ne 'Zone.Identifier' }
}

# Step 2:查看 ADS 内容
# Get-Content "C:\Investigation\Sample\readme.txt" -Stream "hidden_payload"

# Step 3:提取 ADS 到独立文件
# Get-Content "C:\Investigation\Sample\readme.txt" -Stream "hidden_payload" -Raw |
# Set-Content "C:\IR\extracted_payload.bin" -Encoding Byte

# Step 4:计算提取文件的哈希
# Get-FileHash "C:\IR\extracted_payload.bin" -Algorithm SHA256

9.2 练习 2:回收站元数据恢复

目标:从回收站中恢复被删除文件的原始路径和删除时间

1
2
3
4
5
6
7
8
9
10
11
# Step 1:列出回收站中的 $I 文件
Get-ChildItem "C:\`$Recycle.Bin" -Recurse -Force -ErrorAction SilentlyContinue |
Where-Object { $_.Name -like '$I*' }

# Step 2:使用 RBCmd 或自定义解析脚本
# RBCmd.exe -d "C:\$Recycle.Bin" --csv "C:\IR\" --csvf recycle_analysis.csv

# Step 3:分析结果 —— 关注以下类型被删除的文件:
# - .exe/.dll/.ps1 可执行文件
# - 攻击工具(mimikatz, psexec, nc 等)
# - 日志文件(攻击者可能删除以掩盖痕迹)

9.3 Linux 工程师对照

Linux 操作 Windows 对应
find / -newer ref_file Get-ChildItem -Recurse | Where { $_.LastWriteTime -gt $date }
find / -name "*.php" -exec grep eval {} \; Get-ChildItem -Recurse -Include *.aspx | Select-String "eval"
stat file.txt (查看时间戳) Get-Item file.txt | Select *Time*
file malware.bin (文件类型) sigcheck.exe malware.bin
sha256sum file Get-FileHash file -Algorithm SHA256
ls -la ~/.local/share/Trash/ Get-ChildItem "C:\$Recycle.Bin" -Force -Recurse
Extended Attributes (getfattr) Alternate Data Streams (Get-Item -Stream *)

10. 快速参考

10.1 文件取证命令速查

目标 命令/工具
ADS 检测 dir /r, Get-Item -Stream *, streams.exe
时间戳分析 MFTECmd.exe, Get-Item | Select *Time*
Recent Files Get-ChildItem %APPDATA%\...\Recent\*.lnk
Jump Lists JLECmd.exe
回收站 RBCmd.exe, 解析 $I 文件
浏览器历史 sqlite3 + Chrome History DB
文件签名 Get-AuthenticodeSignature, sigcheck.exe
Webshell 扫描 Select-String + 关键字匹配
文件哈希 Get-FileHash -Algorithm SHA256


上一章 目录 下一章
06-进程与网络分析 Windows应急响应 08-计划任务与服务审计