Помощь с редактированием кода C# для получения идентификатора ЦП

Рейтинг: 1Ответов: 1Опубликовано: 01.02.2023

Дан код для получения идентификатора ЦП

Но есть ошибка, которую я не знаю как исправить...

Недопустимый элемент ref в выражении

на строке ref byte[] code;

Сам код предоставлю ниже:

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

Ответы

▲ 1

Ошибка возникает из-за того, что объявление локальных переменных-ссылок (ref locals) доступно только с версии C# 7.0 (Visual Studio 2017). В данном случае необходимости в ref нет, так как переменная типа byte[] сама по себе является ссылкой на массив (массивы в C# являются ссылочными типами). Присваивание переменной-массива не создает новую копию массива, а лишь присваивает ссылку на тот же массив в новую ячейку памяти. То есть можно просто убрать ref из объявления переменной:

byte[] code;
if (IsX64Process())
    code = code_x64;
else
    code = code_x86;

и код скомпилируется, и даже заработает! У меня он выдает значения, вроде совпадающие с показаниями CPU-Z. Однако вас наверняка интересует, правильно/хорошо ли использовать такой трюк для запуска машинного кода из C#?

Я вижу тут несколько проблем:

  1. Безопасность. Вы разрешаете выполнение машинного кода с целой страницы памяти на управляемой куче. На ней могут располагаться другие данные, а также сборщик мусора может впоследствии переместить на нее вообще что-то другое в ходе сжатия кучи. Это потенциальный вектор для уязвимостей. Лучше убирать разрешение на выполнение кода после завершения работы функции, либо генерировать код не в куче, а в памяти, самостоятельно выделенной вызовом VirtualAlloc.

  2. Согласованность кэша инструкций. По документации Windows положено вызывать FlushInstructionCache после динамической генерации нового машинного кода в памяти:

When protecting a region that will be executable, the calling program bears responsibility for ensuring cache coherency via an appropriate call to FlushInstructionCache once the code has been set in place. Otherwise attempts to execute code out of the newly executable region may produce unpredictable results.

Кроме того, следует помнить, что инструкция cpuid, вызванная, как у вас, с параметром 1, возвращает некоторую информацию о модели CPU, но не уникальный идентификатор конкретного изделия. Проблема уникальных идентификаторов ранее обсуждалась здесь.