user9171470
user9171470

Reputation:

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?

Upvotes: 0

Views: 1353

Answers (1)

user9952217
user9952217

Reputation:

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 {}
}

Upvotes: 2

Related Questions