Skip to content
⌘ NSIS Forum Archive

Proof of concept: Detect 64-bit capable CPU - execute CPUID opcode from System plugin

3 posts

f0rt#

Proof of concept: Detect 64-bit capable CPU - execute CPUID opcode from System plugin

WARNING: The following script should merely illustrate that it is possible to execute x86 machine code embedded in the script via the System plugin. It is likely to fail if the page containing the embedded machine code is flagged as non-executable. So the better approach would be to write a dedicated plugin.

OutFile "cpuid.exe"
!include "LogicLib.nsh"
; cpuid 
; Executes CPUID instruction with the query code [EAX value] in $1
; Params: query code [EAX value] in $1
; Returns: 0 in $0 if function succeeded and the results of CPUID
;          in $1 [EAX], $2 [EBX], $3 [ECX] and $4 [EDX]
;          non-zero value in $0 if CPUID instruction is not available.
Function cpuid
   ; int cpuid(unsigned int query_code, unsigned int regs[4])
   ; cpuid:                       
   ;   0:    55                       push   ebp
   ;   1:    89 e5                    mov    ebp,esp
   ;   3:    53                       push   ebx
   ;
   ;                            ; CPUID instruction available?
   ;                            ; yes if ID flag (= EFLAGS:21) can be modified
   ;
   ;   4:    9c                       pushfd  
   ;   5:    58                       pop    eax
   ;   6:    89 c1                    mov    ecx,eax
   ;   8:    35 00 00 20 00           xor    eax,$0x200000
   ;   d:    50                       push   eax
   ;   e:    9d                       popf   
   ;   f:    9c                       pushf  
   ;  10:    58                       pop    eax
   ;  11:    39 c8                    cmp    eax,ecx
   ;  13:    74 17                    je     2c <cpuid_end>
   ;
   ;                            ; Execute CPUID instruction with the passed query code
   ;                            ; and store the results in the integer array
   ;
   ;  15:    56                       push   esi
   ;  16:    8b 45 08                 mov    eax,[ebp+8]
   ;  19:    0f a2                    cpuid  
   ;  1b:    8b 75 0c                 mov    esi,dword ptr [ebp+12]
   ;  1e:    89 06                    mov    dword ptr [esi],eax
   ;  20:    89 5e 04                 mov    dword ptr [esi+4],ebx
   ;  23:    89 4e 08                 mov    dword ptr [esi+8],ecx
   ;  26:    89 56 0c                 mov    dword ptr [esi+12],edx
   ;  29:    5e                       pop    esi
   ;  2a:    31 c0                    xor    eax,eax
   ;
   ;
   ; cpuid_end:
   ;  2c:    5b                       pop    ebx
   ;  2d:    5d                       pop    ebp
   ;  2e:    c3                       ret  
   ;  2f:       90                      nop
   ;
   ; Little endian => combine 4 bytes from last to first one to an integer
   ;
 
   Push $5
   Push $6
   ; cpuid function in x86 machine code 
   ; (System::Call supports a maximum of 100 parameters)
   System::Call "*( \\
      i 0x53e58955, \\
      i 0xc189589c, \\
      i 0x20000035, \\
      i 0x9c9d5000, \\
      i 0x74c83958, \\
      i 0x458b5617, \\
      i 0x8ba20f08, \\
      i 0x06890c75, \\
      i 0x89045e89, \\
      i 0x5689084e, \\
      i 0xc0315e0c, \\
      i 0x90c35d5b  \\
   ) i.r5"
   ; Allocate integer array 
   System::Call "*(i 0, i 0, i 0, i 0) i.r6"
   ; Execute cpuid machine code
   System::Call "::$5(i r1, i r6) i.r0"
   ; Read data from integer array
   System::Call "*$6(i .r1, i .r2, i .r3, i .r4)"
   ; Free previously allocated memory
   System::Free $6
   System::Free $5
   Pop $6
   Pop $5
FunctionEnd
; Get Vendor Id via CPUID instruction
; Returns Vendor Id in $0
Function VendorId
  Push $1
  Push $2
  Push $3
  Push $4
  Push $5
  Push 0
  Pop $1
  Call cpuid
  ${If} $0 != 0
     StrCpy $0 "CPUID instruction not available!"
  ${Else}
     ; Concatenate vendor string: 12 character ASCII string stored in EBX, EDX and ECX
     System::Call "*(i r2, i r4, i r3, i 0) i.r5"
     System::Call "*$5(&m13 .r0)"
     System::Free $5
  ${EndIf}
  Pop $5
  Pop $4
  Pop $3
  Pop $2
  Pop $1
FunctionEnd
; Determine if CPU supports 64-bit address space
; Returns "64-bit" or "32-bit" in $0
Function Is64bit
  Push $1
  Push $2
  Push $3
  Push $4
  ; 32-bit by default
  Push "32-bit"
  Push 0
  Pop $1
  Call cpuid
  ${If} $0 == 0
  ${AndIf} $1 != 0
     ; Get Highest Extended Function Supported
     Push 0x80000000
     Pop $1
     Call cpuid
     ${If} $0 == 0
     ${AndIf} $1 > 0x80000000
        ; Extended Processor Info and Feature Bits
        Push 0x80000001
        Pop $1
        Call cpuid
        ${If} $0 == 0
            ; Check long mode bit (EDX:29)
            IntOp $4 $4 & 0x20000000
            ${If} $4 != 0
                Pop $0
                Push "64-bit"
            ${EndIf}
        ${EndIf}
     ${EndIf}
  ${EndIf}
  ; Return value
  Pop $0
  Pop $4
  Pop $3
  Pop $2
  Pop $1
FunctionEnd
Function .onInit
  InitPluginsDir
  Call VendorId
  StrCpy $1 $0
  Call Is64bit
  MessageBox MB_OK "Vendor=$1 [$0 capable]"
  Quit
FunctionEnd
Section 
SectionEnd 
Anders#
as you say, this will cause problems with DEP/NX, you could allocate memory with VirtualAlloc (PAGE_EXECUTE_READWRITE protection)
f0rt#
Thanks a lot for your advice.

I took your advice and improved the script:

OutFile "cpuid.exe"
Name    "CPUID"
!include "LogicLib.nsh"
!define TRUE                   1
!define MEM_COMMIT             0x1000
!define MEM_RELEASE            0x8000
!define PAGE_EXECUTE_READWRITE 0x40
!define CRYPT_STRING_HEX       0x00000004
; int cpuid(unsigned int query_code, unsigned int regs[4])
; cpuid:                       
;   0:    55                       push   ebp
;   1:    89 e5                    mov    ebp,esp
;   3:    53                       push   ebx
;
;                            ; CPUID instruction available
;                            ; yes if ID flag (= EFLAGS:21) can be modified
;
;   4:    9c                       pushfd  
;   5:    58                       pop    eax
;   6:    89 c1                    mov    ecx,eax
;   8:    35 00 00 20 00           xor    eax,$0x200000
;   d:    50                       push   eax
;   e:    9d                       popf   
;   f:    9c                       pushf  
;  10:    58                       pop    eax
;  11:    39 c8                    cmp    eax,ecx
;  13:    74 17                    je     2c <cpuid_end>
;
;                            ; Run CPUID with the passed query code
;                            ; and store the results in the integer array
;
;  15:    56                       push   esi
;  16:    8b 45 08                 mov    eax,[ebp+8]
;  19:    0f a2                    cpuid  
;  1b:    8b 75 0c                 mov    esi,dword ptr [ebp+12]
;  1e:    89 06                    mov    dword ptr [esi],eax
;  20:    89 5e 04                 mov    dword ptr [esi+4],ebx
;  23:    89 4e 08                 mov    dword ptr [esi+8],ecx
;  26:    89 56 0c                 mov    dword ptr [esi+12],edx
;  29:    5e                       pop    esi
;  2a:    31 c0                    xor    eax,eax
;
;
; cpuid_end:
;  2c:    5b                       pop    ebx
;  2d:    5d                       pop    ebp
;  2e:    c3                       ret
!define HEXCODE "5589e5539c5889c13500002000509d9c5839c87417568b45080fa28b750c8906895e04894e0889560c5e31c05b5dc3"
Var CODE
; Convert hex string into binary
; Params: hexstring - string in hexadecimal format
;         hexlen    - length of hexstring
;         binbuffer - pointer to buffer that receives the binary output
;         binlen    - size of buffer that receives the binary output
; Returns: Number of bytes stored in buffer
Function hex2bin
  Exch $2 ; Length of resulting string
  Exch
  Exch $1 ; Resulting string
  Exch 2
  Exch $0 ; Length of hex string
  Exch 3 
  Exch $3 ; Hex string
  
  Push $4
  ; CryptStringToBinary only available on Windows XP or its successors
  System::Call "Crypt32::CryptStringToBinary(t r3, i r0, i ${CRYPT_STRING_HEX}, i r1, *i r2r2, i 0, i 0) i.r4"
  ${If} $4 != ${TRUE}
     ; Fallback to generic hex to binary implementation
     ;
     ; Ensure that at least one integer value could be written
     ${If} $2 >= 4
        Push $5
        Push $6
        Push $7
        Push $8
        Push $9
        ; Length of hex string
        StrCpy $5 $0
        ; Size of buffer
        StrCpy $6 $2
        ; Integer string
        StrCpy $7 ""
        ${DoWhile} $6 > 0
           ; Extract two characters working from end to beginning of hex string
           IntOp  $5 $5 - 2
           StrCpy $8 $3 2 $5
           ; Combine the extracted characters to an integer string
           StrCpy $7 "$7$8"
           StrLen $9 $7
           ${If} $9 >= 8
               ; Complete integer string
               ; Determine the address where the integer value is going to be written
               IntOp  $6 $6 - 4
               IntOp  $9 $1 + $6
               ; Write integer value
               System::Call "*$9(i 0x$7)"
               ; Clean integer string
               StrCpy $7 ""
               ${If} $6 < 4
               ${AndIf} $6 > 0
                   ; The first integer value is built from the very first 8 characters of the hexstring
                   StrCpy $5 8
                   ; Set offset to 4 so it becomes the start address of the binary buffer for writing
                   ; the first integer value
                   StrCpy $6 4
               ${EndIf}
           ${EndIf}
        ${Loop}
        StrCpy $0 $2
        Pop $9
        Pop $8
        Pop $7
        Pop $6
        Pop $5
     ${Else}
        StrCpy $0 0
     ${EndIf}
  ${Else}
     StrCpy $0 $2
  ${EndIf}
  Pop $4
  Pop $3
  Pop $2
  Pop $1
  Exch $0
FunctionEnd
; Initialize enviornment for cpuid function
Function cpuid_init
   Push $0
   Push $1
   Push $2
   Push $3
   StrCpy $0 ${HEXCODE}
   ; Length of hex string
   StrLen $1 $0
   ; Length of resulting string
   IntOp  $2 $1 / 2
   ; Allocate memory to store code for execution 
   ; VirtualAlloc is available on Windows 2000 Pro or its successors
   System::Call "kernel32::VirtualAlloc(i 0, i r2, \\
        i ${MEM_COMMIT}, i ${PAGE_EXECUTE_READWRITE}) i.r3"
   ${If} $3 == "error"
      ; Fallback to System::Alloc
      System::Alloc $2
      Pop $3
   ${EndIf}
   ${If} $3 != 0
      Push $0
      Push $1
      Push $3
      Push $2
      Call hex2bin
      Pop $2
      StrCpy $CODE $3 
   ${EndIf}
   Pop $3
   Pop $2
   Pop $1
   Pop $0
FunctionEnd
; Cleanup enviornment for cpuid function
Function cpuid_deinit
   ${If} $CODE != ""
      Push $0
      ; Free memory used to store code for execution
      ; VirtualFree is available on Windows 2000 Pro or its successors
      System::Call "kernel32::VirtualFree(i $CODE, i 0, i ${MEM_RELEASE}) i.r0"
      ${If} $0 == "error"
          ; Fallback to System::Free
          System::Free $CODE
      ${EndIf}
      StrCpy $CODE ""
      Pop $0
   ${EndIf}
FunctionEnd
; cpuid 
; Executes CPUID instruction with the query code [EAX value] in $1
; Params: query code [EAX value] in $1
; Returns: 0 in $0 if function succeeded and the results of CPUID
;          in $1 [EAX], $2 [EBX], $3 [ECX] and $4 [EDX]
;          non-zero value in $0 if CPUID instruction is not available.
Function cpuid
   Push $5
   Push $6
   Push $7
   ; Set return code to a non-zero value (= error) as an initial value
   Push -1
   Pop $0
   ${If} $CODE != ""
      ; Allocate integer array 
      System::Call "*(i 0, i 0, i 0, i 0) i.r7"
      ${If} $7 != 0
         ; Execute cpuid machine code
         System::Call "::$CODE(i r1, i r7) i.r0"
         ; Read data from integer array
         System::Call "*$7(i .r1, i .r2, i .r3, i .r4)"
         ; Free previously allocated memory for integer array
         System::Free $7
      ${EndIf}
   ${EndIf}
   Pop $7
   Pop $6
   Pop $5
FunctionEnd
; Get Vendor Id via CPUID instruction
; Returns Vendor Id in $0
Function VendorId
  Push $1
  Push $2
  Push $3
  Push $4
  Push $5
  Push 0
  Pop $1
  Call cpuid
  ${If} $0 != 0
     StrCpy $0 "CPUID instruction not available!"
  ${Else}
     ; Concatenate vendor string: 12 character ASCII string stored in EBX, EDX and ECX
     System::Call "*(i r2, i r4, i r3, i 0) i.r5"
     System::Call "*$5(&m13 .r0)"
     System::Free $5
  ${EndIf}
  Pop $5
  Pop $4
  Pop $3
  Pop $2
  Pop $1
FunctionEnd
; Determine if CPU supports 64-bit address space
; Returns "64-bit" or "32-bit" in $0
Function Is64bit
  Push $1
  Push $2
  Push $3
  Push $4
  ; 32-bit by default
  Push "32-bit"
  Push 0
  Pop $1
  Call cpuid
  ${If} $0 == 0
  ${AndIf} $1 != 0
     ; Get Highest Extended Function Supported
     Push 0x80000000
     Pop $1
     Call cpuid
     ${If} $0 == 0
     ${AndIf} $1 > 0x80000000
        ; Extended Processor Info and Feature Bits
        Push 0x80000001
        Pop $1
        Call cpuid
        ${If} $0 == 0
            ; Check long mode bit (EDX:29)
            IntOp $4 $4 & 0x20000000
            ${If} $4 != 0
                Pop $0
                Push "64-bit"
            ${EndIf}
        ${EndIf}
     ${EndIf}
  ${EndIf}
  ; Return value
  Pop $0
  Pop $4
  Pop $3
  Pop $2
  Pop $1
FunctionEnd
Function .onInit
  InitPluginsDir
  Call cpuid_init 
  Call VendorId
  StrCpy $1 $0
  Call Is64bit
  Call cpuid_deinit 
  MessageBox MB_OK "Vendor=$1 [$0 capable]"
  Quit
FunctionEnd
Section 
SectionEnd