Search code examples
powershellcpucpuid

Retrieve specific CPU feature


There are a lot of information how to inventory computer hardware but I'm intrigued with thought of retrieving information about specific CPU register. Is it possible to do with PowerShell? I'm not talking about inventing CPUID again, I need opinions of professionals is it possible or not. Can someone gimme an advice how to implement this idea if it's possible?


Solution

  • The script below is my old implementation of getting cpuid. I don't remember a reason why I stopped to develop it. Seems there was simply not a free time, no matter. This script should correctly work in PowerShell v5. You can use it like a start point in your research and modify as you wish. Hope this helps.

    using namespace System.Reflection
    using namespace System.Reflection.Emit
    using namespace System.Runtime.InteropServices
    
    # Brief : delegates "creator"
    function Set-Delegate {
      [OutputType([Type])]
      param(
        [Parameter(Mandatory, Position=0)]
        [ValidateScript({$_ -ne [IntPtr]::Zero})]
        [IntPtr]$ProcAddress,
    
        [Parameter(Mandatory, Position=1)]
        [ValidateNotNull()]
        [Type]$Prototype,
    
        [Parameter(Position=2)]
        [ValidateNotNullOrEmpty()]
        [CallingConvention]$CallingConvention = 'StdCall'
      )
    
      $method = $Prototype.GetMethod('Invoke')
      $returntype, $paramtypes = $method.ReturnType, $method.GetParameters().ParameterType
      $holder = New-Object Reflection.Emit.DynamicMethod(
        'Invoke', $returntype, $(if (!$paramtypes) { $null } else { $paramtypes }), $Prototype
      )
      $il = $holder.GetILGenerator()
    
      if ($paramtypes) {
        (0..($paramtypes.Length - 1)).ForEach{$il.Emit([OpCodes]::Ldarg, $_)}
      }
    
      switch ([IntPtr]::Size) {
        4 { $il.Emit([OpCodes]::Ldc_I4, $ProcAddress.ToInt32()) }
        8 { $il.Emit([OpCodes]::Ldc_I8, $ProcAddress.ToInt64()) }
      }
      $il.EmitCalli(
        [OpCodes]::Calli, $CallingConvention, $returntype,
        $(if (!$paramtypes) { $null } else { $paramtypes })
      )
      $il.Emit([OpCodes]::Ret)
    
      $holder.CreateDelegate($Prototype)
    }
    
    # Brief : wrapper for reflected GetModuleHandle and GetProcAddress functions
    # This is required to establish and invoke VirtualAlloc and VirtuallFree functions
    # without creating dynamic assembly into current AppDomain. Be warned, this technique
    # can be used in malware.
    function Get-ProcAddress {
      [OutputType([Hashtable])]
      param(
        [Parameter(Mandatory, Position=0)]
        [ValidateNotNullOrEmpty()]
        [String]$Module,
    
        [Parameter(Mandatory, Position=1)]
        [ValidateNotNull()]
        [String[]]$Function
      )
    
      begin {
        [Object].Assembly.GetType('Microsoft.Win32.Win32Native').GetMethods(
          [BindingFlags]'Static, NonPublic'
        ).Where{$_.Name -cmatch '\AGet(ProcA|ModuleH)'}.ForEach{Set-Variable $_.Name $_}
    
        if (($mod = $GetModuleHandle.Invoke($null, @($Module))) -eq [IntPtr]::Zero) {
          throw (New-Object ComponentModel.Win32Exception(0x7E)).Message
        }
      }
      process {}
      end {
        $table = @{}
        $Function.ForEach{
          if (($$ = $GetProcAddress.Invoke($null, @($mod, $_))) -ne [IntPtr]::Zero) {$table.$_ = $$}
        }
        $table
      }
    }
    
    # Brief : sets functons addresses into delegates
    function New-Delegate {
      [OutputType([Hashtable])]
      param(
        [Parameter(Mandatory, Position=0)]
        [ValidateNotNullOrEmpty()]
        [String]$Module,
    
        [Parameter(Mandatory, Position=1)]
        [ValidateNotNull()]
        [Hashtable]$Signature
      )
    
      $scope, $fname = @{}, (Get-ProcAddress -Module $Module -Function $Signature.Keys)
      $fname.Keys.ForEach{$scope.$_ = Set-Delegate $fname.$_ $Signature.$_}
      $scope
    }
    
    # Brief : helper function for pasrsing bytes
    function Get-Blocks {
      [OutputType([Hashtable])]
      param(
        [Parameter(Mandatory, Position=0)]
        [ValidateNotNull()]
        [Byte[]]$Bytes,
    
        [Parameter()][Switch]$AsInteger,
        [Parameter()][switch]$AsString
      )
    
      $tmp, $reg = @{}, @{eax = $Bytes[0..3];ebx = $Bytes[4..7];ecx = $Bytes[8..11];edx = $Bytes[12..15]}
      if ($AsInteger) {
        $reg.Keys.ForEach{$tmp.$_ = [BitConverter]::ToInt32($reg.$_, 0)}
      }
    
      if ($AsString) {
        $reg.Keys.ForEach{$tmp.$_ = -join [Char[]]$reg.$_}
      }
    
      $tmp
    }
    
    # Brief : helper function for dumping features
    function Set-MapFeatures {
      begin {
        function private:New-Hashtable([String[]]$Regs, [Int32[]]$Bits) {
          $out = @{}
          for ($i = 0; $i -lt $Regs.Length; $i++) {
            $Out.Add($Regs[$i], $Bits[$i])
          }
          $out
        }
    
        $chk = for ($i = 0; $i -le 31; $i++) {1 -shl $i}
        # excluding
        $edx_low, $ecx_low, $ecx_high = (0x00000400, 0x00100000), 0x00010000, (
          0x00004000, 0x00040000, 0x00100000, 0x02000000, 0x20000000, 0x40000000, 0x80000000
        )
        # common
        $edx_high = (
          0x00000800, 0x00080000, 0x00100000, 0x00400000, 0x02000000,
          0x04000000, 0x08000000, 0x20000000, 0x40000000, 0x80000000
        )
        # registers
        $edx_low_reg = ('fpu;vme;de;pse;tsc;msr;pae;mce;cx8;apic;sep;mtrr;pge;mca;cmov;pat;' +
          'pse36;psn;clfsh;ds;acpi;mmx;fxsr;sse;sse2;ss;htt;tm;ia64;pbe').Split(';')
        $ecx_low_reg = ('sse3;pclmulqdq;dtes64;monitor;ds_cpl;vmx;smx;est;tm2;ssse3;cnxt_id;' +
           'sdbg;fma;cx16;xtpr;pdcm;pcid;dca;sse4_1;sse4_2;x2apic;movbe;popcnt;tsc_deadline;' +
           'aes;xsave;osxsave;avx;f16c;rdrnd;hypervisor').Split(';')
        $edx_high_reg = 'syscall;mp;nx;mmxext;fxsr_opt;pdpe1gb;rdtscp;lm;3dnowext;3dnow'.Split(';')
        $ecx_high_reg = ('lahf_lm;cmp_legacy;svm;extapic;cr8_legacy;abm;sse4a;misalignsse;' +
               '3dnowprefetch;osvw;ibs;xop;skinit;wdt;lwp;fma4;tce;nodeid_msr;tbm;topoext;' +
               'perfctr_core;perfctr_nb;dbx;perftsc;pcx_l2i').Split(';')
        $set = @()
      }
      process {}
      end {
        # checkers
        $edx_low  = $chk.Where{$edx_low  -notcontains $_}
        $ecx_low  = $chk.Where{$ecx_low  -notcontains $_}
        $ecx_high = $chk.Where{$ecx_high -notcontains $_}
    
        $set += New-Hashtable $edx_low_reg $edx_low
        $set += New-Hashtable $ecx_low_reg $ecx_low
        $set += New-Hashtable $edx_high_reg $edx_high
        $set += New-Hashtable $ecx_high_reg $ecx_high
    
        $set
      }
    }
    
    # Brief : gets CPUID (CPU name and registers)
    function Get-CpuId {
      begin {
        $kernel32 = New-Delegate kernel32 -Signature @{
          VirtualAlloc = [Func[IntPtr, UIntPtr, UInt32, UInt32, IntPtr]]
          VirtualFree  = [Func[IntPtr, UIntPtr, UInt32, Boolean]]
        }
    
        [Byte[]]$bytes = switch ([IntPtr]::Size) {
          4 {
            0x55,                   # push ebp
            0x8B, 0xEC,             # mov  ebp,  esp
            0x53,                   # push ebx
            0x57,                   # push edi
            0x8B, 0x45, 0x08,       # mov  eax, dword ptr[ebp+8]
            0x0F, 0xA2,             # cpuid
            0x8B, 0x7D, 0x0C,       # mov  edi, dword ptr[ebp+12]
            0x89, 0x07,             # mov  dword ptr[edi+0],  eax
            0x89, 0x5F, 0x04,       # mov  dword ptr[edi+4],  ebx
            0x89, 0x4F, 0x08,       # mov  dword ptr[edi+8],  ecx
            0x89, 0x57, 0x0C,       # mov  dword ptr[edi+12], edx
            0x57,                   # pop  edi
            0x5B,                   # pop  ebx
            0x8B, 0xE5,             # mov  esp, ebp
            0x5D,                   # pop  ebp
            0xC3                    # ret
          }
          8 {
            0x53,                   # push rbx
            0x49, 0x89, 0xD0,       # mov  r8,  rdx
            0x89, 0xC8,             # mov  eax, ecx
            0x0F, 0xA2,             # cpuid
            0x41, 0x89, 0x40, 0x00, # mov  dword ptr[r8+0],  eax
            0x41, 0x89, 0x58, 0x04, # mov  dword ptr[r8+4],  ebx
            0x41, 0x89, 0x48, 0x08, # mov  dword ptr[r8+8],  ecx
            0x41, 0x89, 0x50, 0x0C, # mov  dword ptr[r8+12], edx
            0x5B,                   # pop  rbx
            0xC3                    # ret
          }
        } # cpuid
      }
      process {
        try {
          $ptr = $kernel32.VirtualAlloc.Invoke(
            [IntPtr]::Zero, (New-Object UIntPtr($bytes.Length)), (0x1000 -bor 0x2000), 0x40
          )
          # __cpuid via generic delegate
          $cpuid = Set-Delegate $ptr -Prototype ([Action[Int32, [Byte[]]]]) -CallingConvention 'Cdecl'
          [Marshal]::Copy($bytes, 0, $ptr, $bytes.Length) # copy required bytes
          # shake it, baby! extracting data
          $map = Set-MapFeatures # map of features
          [Byte[]]$buf = New-Object Byte[] 16
          $cpuid.Invoke(0, $buf)
          $features, $vendor = @{}, "$(($str = Get-Blocks $buf -AsString).ebx)$($str.edx)$($str.ecx)"
          # low leaves
          $ids = (Get-Blocks $buf -AsInteger).eax
          for ($i = 0; $i -le $ids; $i++) {
            $cpuid.Invoke($i, $buf)
    
            if ($i -eq 1) {
              $reg = Get-Blocks $buf -AsInteger
    
              $map[0].Keys.ForEach{$features.$_ = $reg.edx -band $map[0].$_}
              $map[1].Keys.ForEach{$features.$_ = $reg.ecx -band $map[1].$_}
            }
          }
          # top leaves
          $cpuid.Invoke(0x80000000, $buf)
          $ids, $name = (Get-Blocks $buf -AsInteger).eax, ''
          for ($i = 0x80000000; $i -le $ids; $i++) {
            $cpuid.Invoke($i, $buf)
    
            if ($i -eq 0x80000001) {
              $reg = Get-Blocks $buf -AsInteger
    
              $map[2].Keys.ForEach{$features.$_ = $reg.edx -band $map[2].$_}
              $map[3].Keys.ForEach{$features.$_ = $reg.ecx -band $map[3].$_}
            }
    
            if ($i -eq 0x80000002 -or $i -eq 0x80000003 -or $i -eq 0x80000004) {
              $name += "$(($reg = Get-Blocks $buf -AsString).eax)$($reg.ebx)$($reg.ecx)$($reg.edx)"
            }
          }
          # wrap data into PSObject
          New-Object PSObject -Property @{
            Vendor      = $vendor
            Name        = $name
            Features    = $features.Keys.ForEach{if ($features.$_) {$_}}
          }
        }
        catch { $_ }
        finally {
          if ($ptr) { [void]$kernel32.VirtualFree.Invoke($ptr, [UIntPtr]::Zero, 0x8000) }
        }
      }
      end {}
    }