我找到了一段不错的代码here,该代码使用API调用执行ASM指令以获得CPU的序列号:
using System;
using System.Text;
using System.Runtime.InteropServices;
namespace ConsoleApplication1
{
class Program
{
[DllImport("user32", EntryPoint = "CallWindowProcW", CharSet = CharSet.Unicode, SetLastError = true, ExactSpelling = true)] private static extern IntPtr ExecuteNativeCode([In] byte[] bytes, IntPtr hWnd, int msg, [In, Out] byte[] wParam, IntPtr lParam);
[return: MarshalAs(UnmanagedType.Bool)]
[DllImport("kernel32", CharSet = CharSet.Unicode, SetLastError = true)] public static extern bool VirtualProtect([In] byte[] bytes, IntPtr size, int newProtect, out int oldProtect);
const int PAGE_EXECUTE_READWRITE = 0x40;
static void Main(string[] args)
{
string s = CPU32_SerialNumber();
Console.WriteLine("CPU Serial-Number: " + s);
Console.ReadLine();
}
private static string CPU32_SerialNumber()
{
byte[] sn = new byte[12];
if (!ExecuteCode32(ref sn))
return "ND";
return string.Format("{0}{1}{2}", BitConverter.ToUInt32(sn, 0).ToString("X"), BitConverter.ToUInt32(sn, 4).ToString("X"), BitConverter.ToUInt32(sn, 8).ToString("X"));
}
private static bool ExecuteCode32(ref byte[] result)
{
// CPU 32bit SerialNumber -> asm x86 from c# (c) 2003-2011 Cantelmo Software
// 55 PUSH EBP
// 8BEC MOV EBP,ESP
// 8B7D 10 MOV EDI,DWORD PTR SS:[EBP+10]
// 6A 02 PUSH 2
// 58 POP EAX
// 0FA2 CPUID
// 891F MOV DWORD PTR DS:[EDI],EBX
// 894F 04 MOV DWORD PTR DS:[EDI+4],ECX
// 8957 08 MOV DWORD PTR DS:[EDI+8],EDX
// 8BE5 MOV ESP,EBP
// 5D POP EBP
// C2 1000 RETN 10
int num;
byte[] code_32bit = new byte[] { 0x55, 0x8b, 0xec, 0x8b, 0x7d, 0x10, 0x6a, 2, 0x58, 15, 0xa2, 0x89, 0x1f, 0x89, 0x4f, 4, 0x89, 0x57, 8, 0x8b, 0xe5, 0x5d, 0xc2, 0x10, 0 };
IntPtr ptr = new IntPtr(code_32bit.Length);
if (!VirtualProtect(code_32bit, ptr, PAGE_EXECUTE_READWRITE, out num))
Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
ptr = new IntPtr(result.Length);
return (ExecuteNativeCode(code_32bit, IntPtr.Zero, 0, result, ptr) != IntPtr.Zero);
}
}
}
我测试了它,对我来说很好用。但是我仍然有一些与之相关的问题和疑问:
1)我想在可以同时在x86和x64环境中运行的应用程序中实现此代码。如果我将此代码运行到64x环境中,则会收到AccessViolationException。该代码的作者说,也可以很容易地实现一个包含x64指令(RAX,RBX,RCX,RDX等)的字节码数组。我的问题是我绝对不知道如何将86x字节代码转换为x64字节代码,实际上我什至都不知道ASM。是否有任何转换表或实用程序可以做到这一点?
2)此代码段对任何类型的处理器均有效吗?我在使用Intel内核的笔记本电脑上对其进行了测试,并且可以工作...但是,例如AMD呢?
3)我不确定所获得的值是否正确。如果我运行以下代码:
string cpuInfo = String.Empty;
System.Management.ManagementClass mc = new System.Management.ManagementClass("Win32_Processor");
System.Management.ManagementObjectCollection moc = mc.GetInstances();
foreach (System.Management.ManagementObject mo in moc)
{
if (cpuInfo == String.Empty)
cpuInfo = mo.Properties["ProcessorId"].Value.ToString();
}
我得到的结果是“ BFEBFBFF000306A9”。代码段的结果为“ F0B2FF0CA0000”。为什么?哪一个是正确的?
最佳答案
这是修改后的代码,以在x64和x86上获得与Win32_Processor.ProcessorId相同的结果:
using System;
using System.Text;
using System.Runtime.InteropServices;
namespace ConsoleApplication1
{
class Program
{
[DllImport("user32", EntryPoint = "CallWindowProcW", CharSet = CharSet.Unicode, SetLastError = true, ExactSpelling = true)] private static extern IntPtr CallWindowProcW([In] byte[] bytes, IntPtr hWnd, int msg, [In, Out] byte[] wParam, IntPtr lParam);
[return: MarshalAs(UnmanagedType.Bool)]
[DllImport("kernel32", CharSet = CharSet.Unicode, SetLastError = true)] public static extern bool VirtualProtect([In] byte[] bytes, IntPtr size, int newProtect, out int oldProtect);
const int PAGE_EXECUTE_READWRITE = 0x40;
static void Main(string[] args)
{
string s = ProcessorId();
Console.WriteLine("ProcessorId: " + s);
Console.ReadLine();
}
private static string ProcessorId()
{
byte[] sn = new byte[8];
if (!ExecuteCode(ref sn))
return "ND";
return string.Format("{0}{1}", BitConverter.ToUInt32(sn, 4).ToString("X8"), BitConverter.ToUInt32(sn, 0).ToString("X8"));
}
private static bool ExecuteCode(ref byte[] result)
{
int num;
/* The opcodes below implement a C function with the signature:
* __stdcall CpuIdWindowProc(hWnd, Msg, wParam, lParam);
* with wParam interpreted as a pointer pointing to an 8 byte unsigned character buffer.
* */
byte[] code_x86 = new byte[] {
0x55, /* push ebp */
0x89, 0xe5, /* mov ebp, esp */
0x57, /* push edi */
0x8b, 0x7d, 0x10, /* mov edi, [ebp+0x10] */
0x6a, 0x01, /* push 0x1 */
0x58, /* pop eax */
0x53, /* push ebx */
0x0f, 0xa2, /* cpuid */
0x89, 0x07, /* mov [edi], eax */
0x89, 0x57, 0x04, /* mov [edi+0x4], edx */
0x5b, /* pop ebx */
0x5f, /* pop edi */
0x89, 0xec, /* mov esp, ebp */
0x5d, /* pop ebp */
0xc2, 0x10, 0x00, /* ret 0x10 */
};
byte[] code_x64 = new byte[] {
0x53, /* push rbx */
0x48, 0xc7, 0xc0, 0x01, 0x00, 0x00, 0x00, /* mov rax, 0x1 */
0x0f, 0xa2, /* cpuid */
0x41, 0x89, 0x00, /* mov [r8], eax */
0x41, 0x89, 0x50, 0x04, /* mov [r8+0x4], edx */
0x5b, /* pop rbx */
0xc3, /* ret */
};
ref byte[] code;
if (IsX64Process())
code = ref code_x64;
else
code = ref code_x86;
IntPtr ptr = new IntPtr(code.Length);
if (!VirtualProtect(code, ptr, PAGE_EXECUTE_READWRITE, out num))
Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
ptr = new IntPtr(result.Length);
return (CallWindowProcW(code, IntPtr.Zero, 0, result, ptr) != IntPtr.Zero);
}
private static bool IsX64Process()
{
return IntPtr.Size == 8;
}
}
}
我在不编译代码的情况下对C#部分进行了小小的修改(目前我没有Windows开发机的安装程序),因此,如果存在语法错误,请进行明显的修复。
我要强调一个非常重要的观点:您的原始代码回读的不是CPU序列号:
您使用了CPUID功能2(通过在执行CPUID指令之前将2放在EAX中)。如果阅读Intel和AMD CPUID应用程序说明,您会看到它回读了缓存和TLB硬件配置,并且仅在Intel上受支持。
我修改了您的代码以使用CPUID函数1,该函数回读CPU的步进,模型和系列。这与WIN32_Processor.ProcessorID的行为匹配
现代的x86 CPU没有“在下线”中相同的单元之间唯一的序列号。处理器序列号仅在奔腾3的CPUID功能3上可用。
现在,我将说明使用的过程和工具。
将操作码数组粘贴到Python脚本中,然后将操作码写入二进制文件(cpuid-x86.bin):
cpuid_opcodes = [ 0x55, 0x8b, 0xec, 0x8b, ... ]
open('cpuid-x86.bin', 'w').write(''.join(chr(x) for x in cpuid_opcodes))
拆卸cpuid-x86.bin。我使用了udis86中的udcli。
$ udcli -att cpuid-x86.bin
0000000000000000 55 push %ebp
0000000000000001 8bec mov %esp, %ebp
0000000000000003 8b7d10 mov 0x10(%ebp), %edi
0000000000000006 6a02 push $0x2
0000000000000008 58 pop %eax
0000000000000009 0fa2 cpuid
000000000000000b 891f mov %ebx, (%edi)
000000000000000d 894f04 mov %ecx, 0x4(%edi)
0000000000000010 895708 mov %edx, 0x8(%edi)
0000000000000013 8be5 mov %ebp, %esp
0000000000000015 5d pop %ebp
0000000000000016 c21000 ret $0x10
立刻引起注意的一件事是,为什么当使用简单的“ mov $ 0x2,%eax”将“ push $ 0x2; pop%eax”将值2移入EAX时?
我的猜测是,“ push $ 0x2”的指令编码6a02易于以十六进制形式进行修改。手动和程序化。我猜想某处有人试图使用CPUID函数3来获取处理器序列号,但发现它不受支持,然后切换为使用函数2。
最后的“ ret $ 0x10”也不常见。 RET指令的RET IMM16形式返回到调用方,然后从堆栈中弹出IMM16字节。函数返回后,被调用方负责将参数弹出堆栈的事实表明,这没有使用标准的x86调用约定。
确实,快速浏览一下C#代码后发现它正在使用CallWindowProc()来调用汇编函数。 CallWindowProc()的文档显示汇编代码正在实现带有如下签名的C函数:
__stdcall CpuIdWindowProc(hWnd, Msg, wParam, lParam);
__stdcall是32位Windows API使用的特殊函数调用约定。
汇编代码使用0x10(%ebp)(该函数的第三个参数)作为字符数组来存储CPUID指令的输出。 (在x86上的标准函数序言之后,8(%ebp)是第一个参数。0xc(%ebp)是第二个4字节参数,而0x10(%ebp)是第三个参数)窗口过程函数原型中的第三个参数以上是wParam。它用作out参数,并且是汇编代码中使用的唯一参数。
关于汇编代码的最后一件有趣的事情是,它在不保存寄存器的情况下破坏了EDI和EBX寄存器,这违反了__stdcall调用约定。通过CallWindowProc()调用该函数时,该错误显然是潜在的,但是如果您尝试用C编写自己的main函数以测试汇编代码(cpuid-main.c),则该bug会显示出来:
#include <stdio.h>
#include <stdint.h>
void __stdcall cpuid_wind_proc(uint32_t hWnd, uint32_t msg, uint8_t *wparam, uint32_t lparam);
enum {
RESULT_SIZE = 2 * 4, /* Two 32-bit registers: EAX, EDX */
};
static unsigned int form_word_le(uint8_t a[])
{
return (a[3] << 24) | (a[2] << 16) | (a[1] << 8) | a[0];
}
int main()
{
uint8_t r[RESULT_SIZE];
memset(r, 0, sizeof(r));
cpuid_wind_proc(0, 0, r, 0);
printf("%08x%08x\n", form_word_le(r + 4), form_word_le(r));
return 0;
}
固定以保存和恢复EDI,EBX并使用CPUID功能1的程序集版本如下:
.section .text
.global _cpuid_wind_proc@16
_cpuid_wind_proc@16:
push %ebp
mov %esp, %ebp
push %edi
mov 16(%ebp), %edi
push $1
pop %eax
push %ebx
cpuid
mov %eax, (%edi)
mov %edx, 0x4(%edi)
pop %ebx
pop %edi
mov %ebp, %esp
pop %ebp
ret $16
符号名_cpuid_wind_proc @ 16是在32位Windows上如何处理__stdcall函数名的方法。 @ 16是参数占用的字节数。 (在32位Windows上,每个占用四个字节的四个参数加起来等于16)
现在,我准备将代码移植到x64。
通过咨询this handy ABI table,我看到前四个参数在RCX,RDX,R8和R9中传递,因此wParam在R8中。
英特尔文档告诉我,CPUID指令掩盖了EAX,EBX,ECX和EDX。 EBX是RBX的下半部分,它是ABI中保存的GPR(“保存的GPR”在这里是指通用寄存器,应在函数调用中保留其内容),因此我确保在执行CPUID指令并还原之前先保存RBX RBX之后。
这是x64程序集:
.section .text
.global cpuid_wind_proc
cpuid_wind_proc:
push %rbx
mov $1, %rax
cpuid
movl %eax, (%r8)
movl %edx, 4(%r8)
pop %rbx
ret
如您所见,x64版本更短,更容易编写。在x64上只有一个函数调用约定,因此我们不必担心__stdcall。
与cpuid-main.c一起构建x64汇编函数,并将其输出与此VBScript(cpuid.vbs)进行比较:
Set objProc = GetObject("winmgmts:root\cimv2:Win32_Processor='cpu0'")
WScript.echo objProc.ProcessorId
使用以下命令运行cpuid.vbs
wscript cpuid.vbs
并验证输出是否匹配。 (实际上,我在Linux上与MinGW-w64进行了交叉编译,并在Wine64仿真下运行了该程序,同时进行了C和汇编工作。)
在x64程序集CPUID函数正常工作的情况下,我现在准备将代码重新集成到C#中。
分解cpuid-x64.exe以获得操作码并将其粘贴为新的字节数组(code_x64)。
通过测试IsX64Process()中的IntPtr.Size == 8,更改ExecuteCode()以确定是运行x86还是x64版本的CPUID代码。
最后,更改ProcessorId()以产生具有以下内容的十六进制字符串:
string.Format("{0}{1}", BitConverter.ToUInt32(sn, 4).ToString("X8"), BitConverter.ToUInt32(sn, 0).ToString("X8"));
使用“ X8”而不是“ X”可确保UInt32的格式设置为填充为零的8位十六进制值。否则,将数字连接到单个字符串时,您将无法分辨出哪些数字来自EDX,哪些数字来自EAX。
就是这样。
关于c# - 内联汇编代码以获取CPU ID,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16460485/