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