I found a nice piece of code here that executes ASM instructions using API calls in order to obtain the serial number of the 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);
}
}
}
I tested it and it's working fine for me. But I still have some questions and problems related to it:
1) I would like to implement this code inside an application that can run in both x86 and x64 environment. If I run this code into a 64x environment, I get an AccessViolationException. The author of the code said that this can be easily achieved implementing also a bytecode array that contains x64 instructions (RAX, RBX, RCX, RDX, ...). My problem is that I absolutely don't know how to convert 86x byte code into x64 byte code, I don't even know ASM in fact. Is there any conversion table or utility that can do this?
2) Is this code snippet valid for any type of processor? I tested it on my laptop that uses an Intel core and it works... but what about AMD for example?
3) I'm not sure that the value I'm obtaining is the correct one. If I run the following code:
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();
}
The result I get is "BFEBFBFF000306A9". The result of the code snippet is "F0B2FF0CA0000". Why? Which one is correct?
The code you posted seems to invoke
CPUID
function #2 (given by theEAX
register, after thePUSH 2; POP EAX
). According to the intel instruction set reference that is not for querying the serial number:Also note this function is not available on AMD processors, but the code should nevertheless execute without errors.
Here's your code modified to get the same result as Win32_Processor.ProcessorId on both x64 and x86:
I made trivial modifications to the C# part without compiling the code (I don't have a Windows dev machine setup at the moment) so if there are syntax errors please just make the obvious fix.
I want to stress one very important point: what your original code was reading back was NOT a CPU serial number:
I'll now explain the process and tools I used.
Paste the array of opcodes into a Python script that will then write the opcodes in a binary file (cpuid-x86.bin):
Disassemble cpuid-x86.bin. I used udcli from udis86.
One thing that immediately stands out is why use "push $0x2; pop %eax" to move the value 2 into EAX when a simple "mov $0x2, %eax" will do?
My guess is that the instruction encoding for "push $0x2", 6a02, is easier to modify in hexadecimal form. Both by hand and programmatically. I'd guess somebody somewhere tried to use CPUID function 3 to get the processor serial number and found that it wasn't supported then switched to using function 2.
The "ret $0x10" at the end is also unusual. The RET IMM16 form of the RET instruction returns to the caller then pops IMM16 bytes off the stack. The fact that the callee is responsible for popping arguments off the stack after function return implies that this is not using the standard x86 calling convention.
Indeed, a quick peek into the C# code reveals that it's using CallWindowProc() to invoke the assembly function. The documentation for CallWindowProc() shows that the assembly code is implementing a C function with a signature like:
__stdcall is the special function calling convention used by 32 bit Windows APIs.
The assembly code uses 0x10(%ebp), which is the third argument to the function, as a character array to store the output from the CPUID instruction. (After a standard function prologue on x86, 8(%ebp) is the first argument. 0xc(%ebp) is the second 4-byte argument and 0x10(%ebp) is the third) The third parameter in our window procedure function prototype above is wParam. It's used as an out parameter and is the only parameter used in the assembly code.
The last interesting thing about the assembly code is that it clobbers the registers EDI and EBX without saving them, violating the __stdcall calling convention. This bug is apparently latent when calling the function through CallWindowProc() but will reveal itself if you try to write your own main function in C to test the assembly code (cpuid-main.c):
A version of the assembly fixed to save and restore EDI, EBX and use CPUID function 1 is like this:
The symbol name _cpuid_wind_proc@16 is how __stdcall function names are mangled on 32 bit Windows. The @16 is the number of bytes the parameters take up. (Four parameters each taking four bytes on 32 bit Windows adds up to 16)
Now I'm ready to port the code to x64.
Here's the x64 assembly:
As you can see the x64 version is shorter and easier to write. There's only one function calling convention on x64 so we don't have to worry about __stdcall.
Build the x64 assembly function along with cpuid-main.c and compare its output with this VBScript (cpuid.vbs):
Run cpuid.vbs with
and verify the outputs match. (I actually cross compiled with MinGW-w64 on Linux and ran the program under Wine64 emulation while doing the C and assembly work up till this point.)
With the x64 assembly CPUID function working, I'm now ready to integrate the code back into C#.
Finally, change ProcessorId() to produce the hexadecimal string with:
Using "X8" instead of just "X" ensures that the UInt32 is formatted as an 8 digit hexadecimal value with zero padding. Otherwise, you can't tell which digits came from EDX and which from EAX when you concatenate them into a single string.
And that's it.