如何获取先前连接的USB设备的时间戳?

4
我正在尝试让一个旧的PowerShell脚本显示先前连接的USB设备的时间。在阅读了一些取证博客(如blogsthis)之后,我发现了this script,它来自于this blog(Jason Walker编写的脚本)。
不幸的是,它没有显示任何时间戳或其他有用的设备详情。所以我希望也有办法获取这些信息。只是我不知道如何融合这些信息。
Function Get-USBHistory { 
 [CmdletBinding()] 
Param 
( 
[parameter(ValueFromPipeline=$True,ValueFromPipelinebyPropertyName=$True)] 
    [alias("CN","Computer")] 
    [String[]]$ComputerName=$Env:COMPUTERNAME, 
    [Switch]$Ping     
) 

 Begin { 
     $TempErrorAction = $ErrorActionPreference 
     $ErrorActionPreference = "Stop" 
     $Hive   = "LocalMachine" 
     $Key    = "SYSTEM\CurrentControlSet\Enum\USBSTOR" 
  } 

  Process 
  {             
     $USBDevices      = @() 
     $ComputerCounter = 0         

     ForEach($Computer in $ComputerName) 
     { 
        $USBSTORSubKeys1 = @() 
        $ChildSubkeys    = @() 
        $ChildSubkeys1   = @() 

        $ComputerCounter++         
        $Computer = $Computer.Trim().ToUpper() 
        Write-Progress -Activity "Collecting USB history" -Status "Retrieving USB history from $Computer" -PercentComplete (($ComputerCounter/($ComputerName.Count)*100)) 


        If($Ping) 
        { 
           If(-not (Test-Connection -ComputerName $Computer -Count 1 -Quiet)) 
           { 
              Write-Warning "Ping failed on $Computer" 
              Continue 
           } 
        }#end if ping  

         Try 
         { 
            $Reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($Hive,$Computer) 
            $USBSTORKey = $Reg.OpenSubKey($Key) 
            $USBSTORSubKeys1  = $USBSTORKey.GetSubKeyNames() 
         }#end try              
         Catch 
         { 
            Write-Warning "There was an error connecting to the registry on $Computer or USBSTOR key not found. Ensure the remote registry service is running on the remote machine." 
         }#end catch 

         ForEach($SubKey1 in $USBSTORSubKeys1) 
         {     
            $ErrorActionPreference = "Continue" 
            $Key2 = "SYSTEM\CurrentControlSet\Enum\USBSTOR\$SubKey1" 
            $RegSubKey2  = $Reg.OpenSubKey($Key2) 
            $SubkeyName2 = $RegSubKey2.GetSubKeyNames() 

            $ChildSubkeys   += "$Key2\$SubKeyName2" 
            $RegSubKey2.Close()         
         }#end foreach SubKey1 

         ForEach($Child in $ChildSubkeys) 
         { 

            If($Child -match " ") 
            { 
               $BabySubkey = $null 
               $ChildSubkey1 = ($Child.split(" "))[0] 

               $SplitChildSubkey1 = $ChildSubkey1.split("\") 

               0..4 | Foreach{ [String]$BabySubkey += ($SplitChildSubkey1[$_]) + "\"}  

               $ChildSubkeys1 += $BabySubkey + ($Child.split(" ")[-1]) 
               $ChildSubkeys1 += $ChildSubkey1 

            } 
            Else 
            { 
               $ChildSubkeys1 += $Child 
            } 
                $ChildSubKeys1.count 
         }#end foreach ChildSubkeys 

         ForEach($ChildSubkey1 in $ChildSubkeys1) 
         {     
            $USBKey      = $Reg.OpenSubKey($ChildSubkey1) 
            $USBDevice   = $USBKey.GetValue('FriendlyName')  
            If($USBDevice) 
            {     
               $USBDevices += New-Object -TypeName PSObject -Property @{ 
                     USBDevice = $USBDevice 
                     Computer  = $Computer 
                     Serial    = $ChildSubkey1.Split("\")[-1] 
                       } 
             } 
                 $USBKey.Close()                                           
          }#end foreach ChildSubKey2 

                 $USBSTORKey.Close()            
         #Display results         
     $USBDevices | Select Computer,USBDevice,Serial 
     }#end foreach computer  

  }#end process 

  End 
  {         
     #Set error action preference back to original setting         
     $ErrorActionPreference = $TempErrorAction          
  } 

}#end function 

而且C#代码:

using System;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using Microsoft.Win32;
using Microsoft.Win32.SafeHandles;
class Program
{
    static void Main(string[] args)
    {
        string usbStor = @"SYSTEM\ControlSet001\Enum\USBSTOR";
        using (var keyUsbStor = Registry.LocalMachine.OpenSubKey(usbStor))
        {
            var usbDevices = from className in keyUsbStor.GetSubKeyNames()
                             let keyUsbClass = keyUsbStor.OpenSubKey(className)
                             from instanceName in keyUsbClass.GetSubKeyNames()
                             let keyUsbInstance = new RegistryKeyEx(keyUsbClass.OpenSubKey(instanceName))
                             select new
                             {
                                 UsbName = keyUsbInstance.Key.GetValue("FriendlyName"),
                                 ConnectTime = keyUsbInstance.LastWriteTime
                             };
            foreach (var usbDevice in usbDevices.OrderBy(x => x.ConnectTime))
            {
                Console.WriteLine("({0}) -- '{1}'", usbDevice.ConnectTime, usbDevice.UsbName);
            }
        }
    }
}
/// <summary>
/// Wraps a RegistryKey object and corresponding last write time.
/// </summary>
/// <remarks>
/// .NET doesn't expose the last write time for a registry key 
/// in the RegistryKey class, so P/Invoke is required.
/// </remarks>
public class RegistryKeyEx
{
    #region P/Invoke Declarations
    // This declaration is intended to be used for the last write time only. int is used
    // instead of more convenient types so that dummy values of 0 reduce verbosity.
    [DllImport("advapi32.dll", EntryPoint = "RegQueryInfoKey", CallingConvention = CallingConvention.Winapi, SetLastError = true)]
    extern private static int RegQueryInfoKey(
        SafeRegistryHandle hkey,
        int lpClass,
        int lpcbClass,
        int lpReserved,
        int lpcSubKeys,
        int lpcbMaxSubKeyLen,
        int lpcbMaxClassLen,
        int lpcValues,
        int lpcbMaxValueNameLen,
        int lpcbMaxValueLen,
        int lpcbSecurityDescriptor,
        IntPtr lpftLastWriteTime);
    #endregion
    #region Public Poperties
    /// <summary>
    /// Gets the registry key owned by the info object.
    /// </summary>
    public RegistryKey Key { get; private set; }
    /// <summary>
    /// Gets the last write time for the corresponding registry key.
    /// </summary>
    public DateTime LastWriteTime { get; private set; }
    #endregion
    /// <summary>
    /// Creates and initializes a new RegistryKeyInfo object from the provided RegistryKey object.
    /// </summary>
    /// <param name="key">RegistryKey component providing a handle to the key.</param>
    public RegistryKeyEx(RegistryKey key)
    {
        Key = key;
        SetLastWriteTime();
    }
    /// <summary>
    /// Creates and initializes a new RegistryKeyInfo object from a registry key path string.
    /// </summary>
    /// <param name="parent">Parent key for the key being loaded.</param>
    /// <param name="keyName">Path to the registry key.</param>
    public RegistryKeyEx(RegistryKey parent, string keyName)
        : this(parent.OpenSubKey(keyName))
    { }
    /// <summary>
    /// Queries the currently set registry key through P/Invoke for the last write time.
    /// </summary>
    private void SetLastWriteTime()
    {
        Debug.Assert(Key != null, "RegistryKey component must be initialized");
        GCHandle pin = new GCHandle();
        long lastWriteTime = 0;
        try
        {
            pin = GCHandle.Alloc(lastWriteTime, GCHandleType.Pinned);
            if (RegQueryInfoKey(Key.Handle, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, pin.AddrOfPinnedObject()) == 0)
            {
                LastWriteTime = DateTime.FromFileTime((long)pin.Target);
            }
            else
            {
                LastWriteTime = DateTime.MinValue;
            }
        }
        finally
        {
            if (pin.IsAllocated)
            {
                pin.Free();
            }
        }
    }
}

抱歉,我无法正确突出显示PSH代码。 如何使用此方法来改进脚本?

更新:2017年11月06日

在@iRon的建议下,我尝试直接访问注册表路径:HKLM\SYSTEM\CurrentControlSet\Enum\USBSTOR\<drive>\Propertie‌​s,使用RegEdit,但是我遇到了一个权限错误,这很奇怪,因为我的用户帐户是管理员。(这是在Win8.1上)

enter image description here

我发现的其他几个选项是:
  1. 使用Windows内置的Event Viewer并创建自定义视图。但是,这需要您已经启用了事件日志。
  2. 同样,使用logparser和一个CMD批处理script,如herehere所示。(需要启用事件日志。)
  3. 按照此处所述的取证方法,检查各种注册表条目和日志文件:...\Windows\inf\setupapi.dev.log以获取第一次连接日期,但如何获取最后一次连接不太清楚。(据说通过比较\NTUSER\<username>\Software\Microsoft\Windows\Explorer\MountPoints2数据,但我找不到它。)
  4. 此外,书籍“Windows Registry Forensics:Advanced Digital Forensic Analysis of the Windows Registry”还从第95页开始提供了一些额外的提示(3)。

一个可能有用的 PowerShell 单行代码是:

Get-WinEvent -LogName Microsoft-Windows-DriverFrameworks-UserMode/Operational | where {$_.Id -eq "2003" -or $_.Id -eq "2102"} | Format-Table –Property TimeCreated, Id, Message -AutoSize -Wrap

这提供了事件(2003、2102)的时间戳和消息内容,可以进一步解析。
TimeCreated           Id Message                                                                                                                                                                                                                  
-----------           -- -------                                                                                                                                                                                                                  
2017-11-09 13:37:04 2102 Forwarded a finished Pnp or Power operation (27, 2) to the lower driver for device                                                                                                                                       
                         SWD\WPDBUSENUM\_??_USBSTOR#DISK&VEN_KINGSTON&PROD_DATATRAVELER_G2&REV_PMAP#YYYYY&0#{XXXXX} with status 0x0.                                                            
2017-11-09 13:37:04 2102 Forwarded a finished Pnp or Power operation (27, 23) to the lower driver for device                                                                                                                                      
                         SWD\WPDBUSENUM\_??_USBSTOR#DISK&VEN_KINGSTON&PROD_DATATRAVELER_G2&REV_PMAP#YYYYY&0#{XXXXX} with status 0x0.                                                            
2017-11-09 13:34:38 2003 The UMDF Host Process ({XXXXX}) has been asked to load drivers for device                                                                                                                 
                         SWD\WPDBUSENUM\_??_USBSTOR#DISK&VEN_KINGSTON&PROD_DATATRAVELER_G2&REV_PMAP#YYYYY&0#{XXXXX}.                                                                            
2017-11-06 15:18:41 2102 Forwarded a finished Pnp or Power operation (27, 2) to the lower driver for device SWD\WPDBUSENUM\{XXXXX}#0000000000007E00 with status 0x0.                                               
2017-11-06 15:18:41 2102 Forwarded a finished Pnp or Power operation (27, 23) to the lower driver for device SWD\WPDBUSENUM\{XXXXX}#0000000000007E00 with status 0x0.                                              
2017-11-06 15:18:13 2003 The UMDF Host Process ({XXXXX}) has been asked to load drivers for device SWD\WPDBUSENUM\{XXXXX}#0000000000007E00.                                         

您是否在系统账户下运行此程序?显然,您需要 nt authority\system 权限才能深入查看以下内容:HKLM\SYSTEM\CurrentControlSet\Enum\USBSTOR\<drive>\Properties\。您是否尝试过使用 RegEdit.exe 提取信息? - iRon
@iRon 很有趣,尽管我是管理员,但我无法访问那个。 - not2qubit
1
尝试PSEXEC -i -s -d PowerShell.exe,然后使用RegEdit或您的脚本。 - iRon
1
使用PowerShell命令Get-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Enum\USBSTOR\Disk&Ven_IT1165&Prod_USB_Flash_Disk&Rev_0.00\000000005BE51F59&0\' -Name *可以揭示很多信息。然而,我没有看到任何有关使用时间戳的内容。 - lit
在@iRon的建议下,我终于成功打开了属性(使用从https://learn.microsoft.com/en-us/sysinternals/downloads/psexec下载的psexec)。谢谢! - ShHolmes
显示剩余2条评论
1个回答

2
这不是完整的,但应该能让你开始?
$code = @"
using System;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using Microsoft.Win32;
using Microsoft.Win32.SafeHandles;

/// <summary>
/// Wraps a RegistryKey object and corresponding last write time.
/// </summary>
/// <remarks>
/// .NET doesn't expose the last write time for a registry key 
/// in the RegistryKey class, so P/Invoke is required.
/// </remarks>
public class RegistryKeyEx
{
    #region P/Invoke Declarations
    // This declaration is intended to be used for the last write time only. int is used
    // instead of more convenient types so that dummy values of 0 reduce verbosity.
    [DllImport("advapi32.dll", EntryPoint = "RegQueryInfoKey", CallingConvention = CallingConvention.Winapi, SetLastError = true)]
    extern private static int RegQueryInfoKey(
        SafeRegistryHandle hkey,
        int lpClass,
        int lpcbClass,
        int lpReserved,
        int lpcSubKeys,
        int lpcbMaxSubKeyLen,
        int lpcbMaxClassLen,
        int lpcValues,
        int lpcbMaxValueNameLen,
        int lpcbMaxValueLen,
        int lpcbSecurityDescriptor,
        IntPtr lpftLastWriteTime);
    #endregion
    #region Public Poperties
    /// <summary>
    /// Gets the registry key owned by the info object.
    /// </summary>
    public RegistryKey Key { get; private set; }
    /// <summary>
    /// Gets the last write time for the corresponding registry key.
    /// </summary>
    public DateTime LastWriteTime { get; private set; }
    #endregion
    /// <summary>
    /// Creates and initializes a new RegistryKeyInfo object from the provided RegistryKey object.
    /// </summary>
    /// <param name="key">RegistryKey component providing a handle to the key.</param>
    public RegistryKeyEx(RegistryKey key)
    {
        Key = key;
        SetLastWriteTime();
    }
    /// <summary>
    /// Creates and initializes a new RegistryKeyInfo object from a registry key path string.
    /// </summary>
    /// <param name="parent">Parent key for the key being loaded.</param>
    /// <param name="keyName">Path to the registry key.</param>
    public RegistryKeyEx(RegistryKey parent, string keyName)
        : this(parent.OpenSubKey(keyName))
    { }
    /// <summary>
    /// Queries the currently set registry key through P/Invoke for the last write time.
    /// </summary>
    private void SetLastWriteTime()
    {
        Debug.Assert(Key != null, "RegistryKey component must be initialized");
        GCHandle pin = new GCHandle();
        long lastWriteTime = 0;
        try
        {
            pin = GCHandle.Alloc(lastWriteTime, GCHandleType.Pinned);
            if (RegQueryInfoKey(Key.Handle, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, pin.AddrOfPinnedObject()) == 0)
            {
                LastWriteTime = DateTime.FromFileTime((long)pin.Target);
            }
            else
            {
                LastWriteTime = DateTime.MinValue;
            }
        }
        finally
        {
            if (pin.IsAllocated)
            {
                pin.Free();
            }
        }
    }
}
"@

$type = Add-Type -TypeDefinition $code -Language CSharp

$devices = Get-Item HKLM:\SYSTEM\ControlSet001\Enum\USBSTOR\*

$result = foreach($device in $devices) {
    Write-Verbose -Verbose "New device: $($device.PSPath)"

    Write-Verbose -Verbose "GetClass"
    foreach($classname in $device.GetSubKeyNames()) {
        $class = $device.OpenSubKey($class)

        if($class -eq $null) { 
            Write-Verbose -Verbose "Class is null" 
            continue
        }

        Write-Verbose -Verbose "GetInstance"
        foreach($instancename in $class.GetSubKeyNames()) {
            $instance = $class.OpenSubKey($instancename)

            if($instance -eq $null) {
                Write-Verbose -Verbose "Instance is null"
                continue
            }

            Write-Verbose -Verbose "RegistryKeyEx"
            $keyEx = New-Object RegistryKeyEx $instance

            [pscustomobject]@{
                FriendlyName = $keyEx.key.GetValue('FriendlyName')
                DevicePath = $device.PSPath
                LastWriteTime = $keyEx.LastWriteTime
            }
        }
    }
}

编辑:(由not2qubit)

此脚本为内联C#。当前版本的输出如下:

VERBOSE: New device: Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Enum\USBSTOR\Disk&Ven_Kingston&Prod_DataTraveler_G2&Rev_PMAP
VERBOSE: GetClass
VERBOSE: GetInstance
VERBOSE: RegistryKeyEx
VERBOSE: New device: Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Enum\USBSTOR\Disk&Ven_WD&Prod_My_Passport_0730&Rev_1015
VERBOSE: GetClass
VERBOSE: Class is null
VERBOSE: New device: Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Enum\USBSTOR\Other&Ven_WD&Prod_SES_Device&Rev_1015
VERBOSE: GetClass
VERBOSE: GetInstance
VERBOSE: RegistryKeyEx

因此缺少时间戳...
编辑:
除非您查看$result变量。
PS C:\> $result

FriendlyName                    DevicePath                                                                                                                           LastWriteTime      
------------                    ----------                                                                                                                           -------------      
Corsair Survivor 3.0 USB Device Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Enum\USBSTOR\Disk&Ven_Corsair&Prod_Survivor_3.0&Rev_1.00 2017-11-05 21:08:25

PS C:\> get-date

November 11, 2017 17:02:09

这为你提供了C#代码示例中的内容。至于这些信息是否足够准确,我无法确定。

我并没有给你的回答投反对票,但仍然想知道你的做法与我已经发表的有何不同。另外,你似乎在混合使用PowerShell和C#,这看起来很奇怪... - not2qubit
1
哦,你似乎忽略了我把所有东西都放进了 $result 这个变量里的事实。 - mtnielsen
啊,这好多了!但是似乎只获取到了第一个设备实例(根据我编辑时发现的),第二个部分为空因为“类为空”,第三个则完全没有显示。 - not2qubit
有没有想法为什么交换 DevicePath = ...LastWriteTime = ... 的顺序会得到非常不同的结果? - not2qubit
不幸的是,在我的情况下,它只列出了两个设备,而我连接了很多USB设备。例如,它没有显示联想Dock Gen 2以及连接到该Dock的USB插座的设备,如鼠标、键盘等。 - essential
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接