跟踪文件(Windows 终端)的硬链接(重新分析点?)?

js2*_*010 5 powershell reparsepoint uwp

如何跟踪文件的硬链接(重新分析点?)?管道传输到格式列表不会显示目标。至少在 powershell 7 中,你会得到一个小 ascii 箭头。该文件夹位于 $env:path 中。如果您没有 Windows 终端,则 MicrosoftEdge.exe 链接在同一文件夹中。我的是win10 20h2。

get-item $env:LOCALAPPDATA\Microsoft\WindowsApps\wt.exe

    Directory: C:\Users\admin\AppData\Local\Microsoft\WindowsApps

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
la---           3/31/2022  1:27 PM              0 wt.exe ->


get-item $env:LOCALAPPDATA\Microsoft\WindowsApps\wt.exe | % attributes

Archive, ReparsePoint
Run Code Online (Sandbox Code Playgroud)

这只是给出了很多二进制文件:

fsutil reparsepoint query $env:LOCALAPPDATA\Microsoft\WindowsApps\wt.exe

Reparse Tag Value : 0x8000001b
Tag value: Microsoft

Reparse Data Length: 0x192
Reparse Data:
0000:  03 00 00 00 4d 00 69 00  63 00 72 00 6f 00 73 00  ....M.i.c.r.o.s.
0010:  6f 00 66 00 74 00 2e 00  57 00 69 00 6e 00 64 00  o.f.t...W.i.n.d.
0020:  6f 00 77 00 73 00 54 00  65 00 72 00 6d 00 69 00  o.w.s.T.e.r.m.i.
0030:  6e 00 61 00 6c 00 50 00  72 00 65 00 76 00 69 00  n.a.l.P.r.e.v.i.
0040:  65 00 77 00 5f 00 38 00  77 00 65 00 6b 00 79 00  e.w._.8.w.e.k.y.
0050:  62 00 33 00 64 00 38 00  62 00 62 00 77 00 65 00  b.3.d.8.b.b.w.e.
0060:  00 00 4d 00 69 00 63 00  72 00 6f 00 73 00 6f 00  ..M.i.c.r.o.s.o.
0070:  66 00 74 00 2e 00 57 00  69 00 6e 00 64 00 6f 00  f.t...W.i.n.d.o.
0080:  77 00 73 00 54 00 65 00  72 00 6d 00 69 00 6e 00  w.s.T.e.r.m.i.n.
0090:  61 00 6c 00 50 00 72 00  65 00 76 00 69 00 65 00  a.l.P.r.e.v.i.e.
00a0:  77 00 5f 00 38 00 77 00  65 00 6b 00 79 00 62 00  w._.8.w.e.k.y.b.
00b0:  33 00 64 00 38 00 62 00  62 00 77 00 65 00 21 00  3.d.8.b.b.w.e.!.
00c0:  41 00 70 00 70 00 00 00  43 00 3a 00 5c 00 50 00  A.p.p...C.:.\.P.
00d0:  72 00 6f 00 67 00 72 00  61 00 6d 00 20 00 46 00  r.o.g.r.a.m. .F.
00e0:  69 00 6c 00 65 00 73 00  5c 00 57 00 69 00 6e 00  i.l.e.s.\.W.i.n.
00f0:  64 00 6f 00 77 00 73 00  41 00 70 00 70 00 73 00  d.o.w.s.A.p.p.s.
0100:  5c 00 4d 00 69 00 63 00  72 00 6f 00 73 00 6f 00  \.M.i.c.r.o.s.o.
0110:  66 00 74 00 2e 00 57 00  69 00 6e 00 64 00 6f 00  f.t...W.i.n.d.o.
0120:  77 00 73 00 54 00 65 00  72 00 6d 00 69 00 6e 00  w.s.T.e.r.m.i.n.
0130:  61 00 6c 00 50 00 72 00  65 00 76 00 69 00 65 00  a.l.P.r.e.v.i.e.
0140:  77 00 5f 00 31 00 2e 00  38 00 2e 00 31 00 30 00  w._.1...8...1.0.
0150:  39 00 32 00 2e 00 30 00  5f 00 78 00 36 00 34 00  9.2...0._.x.6.4.
0160:  5f 00 5f 00 38 00 77 00  65 00 6b 00 79 00 62 00  _._.8.w.e.k.y.b.
0170:  33 00 64 00 38 00 62 00  62 00 77 00 65 00 5c 00  3.d.8.b.b.w.e.\.
0180:  77 00 74 00 2e 00 65 00  78 00 65 00 00 00 30 00  w.t...e.x.e...0.
0190:  00 00                                             ..
Run Code Online (Sandbox Code Playgroud)

甚至 Sysinternals Findlinks 也不起作用:

findlinks $env:LOCALAPPDATA\Microsoft\WindowsApps\wt.exe

Findlinks v1.1 - Locate file hard links
Copyright (C) 2011-2016 Mark Russinovich
Sysinternals - www.sysinternals.com

Error opening c:\users\js2010\appdata\local\microsoft\windowsapps\wt.exe:
The file cannot be accessed by the system.
Run Code Online (Sandbox Code Playgroud)

mkl*_*nt0 6

太长了;博士

  • $env:LOCALAPPDATA\Microsoft\WindowsApps\wt.exe是一个AppX 重解析点,又名应用程序执行别名,又名(内部)AppExecLink,是 Microsoft 特定的NTFS 重解析点,与UWP / Microsoft Store应用程序一起用作入口点,即启动它们的可执行文件。

    • 硬链接在概念上和技术上都是不相关的。
  • 虽然此类重分析点的数据确实指向另一个文件(类似于符号链接),但该数据被 Microsoft 视为未记录的实现细节,可能会发生更改[1]

    • 因此,虽然您在技术上可以解析该数据,但您不应该,或者至少不应该在生产代码中依赖它,因为无法保证长期稳定性。

    • 此类重分析点可以像任何其他可执行文件一样执行,这通常就足够了

    • 虽然您可能想知道最终在幕后启动了哪个不同的可执行文件,但重新分析数据的静态分析并不能保证为您提供该信息,因为在某些情况下使用通用启动器可执行文件,这会不透明地启动最终目标可执行文件(详细信息请参见下文)。换句话说:存储在重解析数据中的目标可执行文件不一定是真正的目标可执行文件。

    • 将 AppX 重解析点解析为其目标可执行文件已深深嵌入到系统中,即CreateProcessWinAPI 函数系列中。您需要运行时分析来查看给定启动的 AppX 重解析点最终调用什么可执行文件;一种简单的方法是交互式调用并在任务管理器中查找生成的进程;GitHub 上的此评论展示了基于QueryFullProcessImageNameWinAPI 函数的复杂编程方法


如何跟踪文件的硬链接(重新分析点?)?

链接不是重分析

事实上,硬链接并不像符号链接那样指向其他文件路径

相反,一组相关的硬链接直接指向相同的文件数据,它们的路径之间没有任何关系

然而,与类 Unix 平台不同的是,Windows 上的系统 API 允许您直接发现指向相同文件数据的所有路径(给定这些路径之一)。

Windows PowerShell中,此相关路径数组通过/发出的实例所修饰.Target的属性公开。System.IO.FileInfoGet-ChildItemGet-Item

不幸的是,这在 PowerShell (Core) v6+ 中被删除- 有关详细信息,请参阅此答案


相比之下,其他形式的 NTFS 链接 - 特别是符号链接(symlinks),但也包括较旧的 连接点卷安装点类型 - 重新分析点。下面我将它们统称为符号链接

重解析点本质上只是附加到文件系统条目的元数据,并且它们的使用是开放式的;例如,重新分析点还用于管理云托管 OneDrive 文件的按需下载。

简而言之:符号链接只是重解析点的一种类型,并不是每个重解析点都是链接

公开指向其他文件系统路径的重解析点的子类称为名称代理


$env:LOCALAPPDATA\Microsoft\WindowsApps\wt.exe是另一种重解析点的示例:AppX 重解析点又名应用程序执行别名又名(内部)AppExecLink

这些由UWP应用程序使用,特别是包括从Microsoft Store下载的所有应用程序。

虽然它们在概念上与符号链接相似,但存在重要区别

  • 链接目标不一定是真正的目标可执行文件;它可能只是通用的 UWP 启动器,C:\WINDOWS\system32\SystemUWPLauncher.exe例如 Microsoft Edge。

  • 但更重要的是,微软认为与应用程序执行别名(AppX 重解析点)相关的重解析数据是一个未记录的实现细节,可能会发生变化[1]换句话说:

    • 您应该像对待任何其他(常规的、独立的)可执行文件一样对待应用程序执行别名,并且不是其视为另一个可执行文件的链接

    • 您不应该尝试自己解析重新解析数据。

当然,你可以尝试自己解析,但不能指望数据格式不改变。

  • 注意:即使正确解析,数据也无法告诉您完整的情况,例如在 Microsoft Edge 的情况下:最终使用的可执行文件的路径 - C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe,可能由C:\WINDOWS\system32\SystemUWPLauncher.exe,无法从重新解析数据中收集(独自的)。[2]

事实上,PowerShell(核心)曾短暂实现过自己的解析,但后来在内部反馈后再次将其删除

可以找到删除后的代码可以在此处,该代码涉及 P/Invoke 操作,因此不能直接在 PowerShell 中使用(尽管您可以通过 尝试临时编译嵌入式 C# 代码Add-Type)。

作为JosefZ 的答案所示,您还可以从 的输出中收集原始字节数据fsutil reparsepoint query,但这既不稳健又缓慢。

这是一个替代实现 - 稍微更健壮,但仍然很慢;定义后,按如下方式调用它:

Get-ChildItem -File $env:LOCALAPPDATA\Microsoft\WindowsApps | Resolve-AppXExePath
Run Code Online (Sandbox Code Playgroud)
function Resolve-AppXExePath {
<#
.SYNOPSIS
  Resolves AppX execution aliases to their app IDs and target paths.
.EXAMPLE
  Get-ChildItem -File $env:LOCALAPPDATA\Microsoft\WindowsApps | Resolve-AppXExePath
 .NOTES
  This command is slow, because a call to fsutil.exe is made for each input path.
  #>
  param(
    [Parameter(ValueFromPipeline)]
    [Alias('PSPath')]
    [string] $LiteralPath
  )
  
  process {
    $fullName = Convert-Path -LiteralPath $LiteralPath
    if (-not $?) { return }
  
    # Get a hex-dump representation of the reparse-point data via fsutil reparsepoint query $fullName
    $hexDump = fsutil reparsepoint query $fullName 2>&1
    if ($LASTEXITCODE) { Write-Error $hexDump; return }
  
    # Extract the raw bytes that make up the reparse-point data.
    [byte[]] $bytes = -split (-join ($hexDump -match '^[a-f0-9]+:' -replace '^[a-f0-9]+:\s+(((?:[a-f0-9]{2}) +){1,16}).+$', '$1')) -replace '^', '0x'
    
    # Convert the data to a UTF-16 string and split into fields by NUL bytes.
    $props = [System.Text.Encoding]::Unicode.GetString($bytes) -split "`0"
    
    # Output a custom object with the App ID (Package ID + entry-point name)
    # and the target path (which may just be the universal UWP launcher)
    [PSCustomObject] @{
      AppId = $props[2]
      Target = $props[3]
    }
  
  }
}
Run Code Online (Sandbox Code Playgroud)

示例输出:

Get-ChildItem -File $env:LOCALAPPDATA\Microsoft\WindowsApps | Resolve-AppXExePath
Run Code Online (Sandbox Code Playgroud)

[1] 参见.NET 团队在 GitHub 上的评论

SystemUWPLauncher.exe[2] 是否使用通用启动器可能与给定应用程序是真正的 UWP 应用程序还是通过桌面桥重新打包的桌面应用程序有关。我不清楚如何、何时SystemUWPLauncher.exe使用、确定最终目标可执行文件。


Jos*_*efZ 1

只需解析fsutil.exe输出(请参阅ParseFsutil下面的函数)。在 Microsoft Windows 下测试[Version 10.0.19043.1620]

\n
    \n
  • pwsh.exe(PS版7.1.5),
  • \n
  • powershell.exe/ powershell_ise.exe(PS版本5.1.19041.1620),
  • \n
  • 用户帐户类型:管理员以及标准用户
  • \n
\n

请注意输出中丑陋的硬编码索引 ( $fsuColon) 和宽度 ( $fsuWidth)fsutil以及未优化的ParseFsutil函数\xe2\x80\xa6

\n
[CmdletBinding()]\n[OutputType([System.Management.Automation.PSCustomObject],[System.Object[]])]\nParam (\n    [parameter(Mandatory = $false, ValueFromPipeline)]\n    [string]$Path = "$env:LOCALAPPDATA\\Microsoft\\WindowsApps\\*"\n)\n\nFunction ParseFsutil {\n    Param (\n        [parameter(Mandatory, ValueFromPipelineByPropertyName)]\n        [string]$FullName\n    )\n    $fsusep = [char[]]@([char]0x20,[char]0xA0,"`t","`r","`n")\n    $fsuopt = [System.StringSplitOptions]::RemoveEmptyEntries\n    $fsuexe = fsutil.exe reparsepoint query $FullName\n    $fsuColon = 4             #  ugly hard-coded index in `fsutil` output\n    $fsuWidth = 50            #  ugly hard-coded width in `fsutil` output\n    $fsuarr = $fsuexe | \n        Where-Object { $_.Length -gt $fsuColon -and $_[$fsuColon] -eq ':' } |\n        ForEach-Object {\n            $_.Substring($fsuColon+1, $fsuWidth).Trim().Split($fsusep, $fsuopt)\n        }\n    $fsuapp = [System.Text.UTF32Encoding]::Unicode.GetString(\n        [byte[]]$fsuarr.ForEach({[System.Convert]::ToByte($_,16)}))\n    $fsupes = [char[]](0..0x1F | ForEach-Object { [char]$_ })      # Controls\n    $fsuapp.Split( $fsupes, $fsuopt )\n}\n\n\nWrite-Verbose "PowerShell $($PSVersionTable.PSVersion.ToString())"\n$RePAttr = [System.IO.FileAttributes]::ReparsePoint.value__\nGet-ChildItem $Path -File -ErrorAction SilentlyContinue |\n    ForEach-Object {\n        $RePoint = [psCustomObject]@{\n            Name   = $_.Name;\n            Source = 'Target'\n            Target = ''\n        }\n        if (($_.Attributes.value__ -band $RePAttr) -eq $RePAttr) {\n            $RePoint.Target = $_ | Select-Object -ExpandProperty Target\n            if ( $null -eq $RePoint.Target -or\n                 $RePoint.Target.GetType().Name -ne 'String' -or\n                 $RePoint.Target.Length -lt $RePoint.Name.Length )\n            {\n                Write-Verbose "Target from fsutil.exe for $($RePoint.Name)"\n                $RePoint.Source = 'fsutil'\n                $RePoint.Target = ($_ | ParseFsutil)[2]\n            }\n        } else {\n            Write-Verbose "Item is not a reparse point: $($RePoint.Name)"\n            $RePoint.Source = 'self'\n            $RePoint.Target = $_.FullName\n        }\n        $RePoint\n    }\n
Run Code Online (Sandbox Code Playgroud)\n

输出(截断):pwsh -nopro -file D:\\PShell\\SO\\71697488.ps1 -Verbose

\n
VERBOSE: PowerShell 7.1.5\n\nName              Source Target\n----              ------ ------\nMicrosoftEdge.exe Target C:\\WINDOWS\\system32\\SystemUWPLauncher.exe\n\xe2\x80\xa6\nwt.exe            Target C:\\Program Files\\WindowsApps\\Microsoft.WindowsTerminal_1.12.10393.0_x64__8wekyb3d8bbwe\\wt.exe\n
Run Code Online (Sandbox Code Playgroud)\n

输出(截断):powershell -nopro -file D:\\PShell\\SO\\71697488.ps1 -Verbose

\n
VERBOSE: PowerShell 5.1.19041.1620\nVERBOSE: Target from fsutil.exe for MicrosoftEdge.exe\n\xe2\x80\xa6\nVERBOSE: Target from fsutil.exe for wt.exe\nName              Source Target\n----              ------ ------\nMicrosoftEdge.exe fsutil C:\\WINDOWS\\system32\\SystemUWPLauncher.exe\n\xe2\x80\xa6\nwt.exe            fsutil C:\\Program Files\\WindowsApps\\Microsoft.WindowsTerminal_1.12.10393.0_x64__8wekyb3d8bbwe\\wt.exe\n
Run Code Online (Sandbox Code Playgroud)\n

输出powershell -nopro -file D:\\PShell\\SO\\71697488.ps1 -Path D:\\PShell\\SO\\71697488.ps1 -Verbose

\n
VERBOSE: PowerShell 5.1.19041.1620\nVERBOSE: Item is not a reparse point: 71697488.ps1\n\nName         Source Target\n----         ------ ------\n71697488.ps1 self   D:\\PShell\\SO\\71697488.ps1\n
Run Code Online (Sandbox Code Playgroud)\n

ParseFsutil函数的原始输出

\n
Get-Item "$env:LOCALAPPDATA\\Microsoft\\WindowsApps\\wt.exe" | ParseFsutil\n
Run Code Online (Sandbox Code Playgroud)\n
\n
Microsoft.WindowsTerminal_8wekyb3d8bbwe\nMicrosoft.WindowsTerminal_8wekyb3d8bbwe!App\nC:\\Program Files\\WindowsApps\\Microsoft.WindowsTerminal_1.12.10393.0_x64__8wekyb3d8bbwe\\wt.exe\n0\n
Run Code Online (Sandbox Code Playgroud)\n
\n
Get-Item "$env:LOCALAPPDATA\\Microsoft\\WindowsApps\\MicrosoftEdge.exe" |\n    ParseFsutil\n
Run Code Online (Sandbox Code Playgroud)\n
\n
Microsoft.MicrosoftEdge_8wekyb3d8bbwe\nMicrosoft.MicrosoftEdge_8wekyb3d8bbwe!MicrosoftEdge\nC:\\WINDOWS\\system32\\SystemUWPLauncher.exe\n1\n
Run Code Online (Sandbox Code Playgroud)\n
\n