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


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 ***91;EAX value***93; in $1
>; Params: query code ***91;EAX value***93; in $1
>; Returns: 0 in $0 if function succeeded and the results of CPUID
>; in $1 ***91;EAX***93;, $2 ***91;EBX***93;, $3 ***91;ECX***93; and $4 ***91;EDX***93;
; non-zero value in $0 if CPUID instruction is not available.
Function cpuid
; int cpuid(unsigned int query_code, unsigned int regs***91;4***93;)
;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,***91;ebp+8***93;
; 19: 0f a2 cpuid
; 1b: 8b 75 0c mov esi,dword ptr ***91;ebp+12***93;
; 1e: 89 06 mov dword ptr ***91;esi***93;,eax
; 20: 89 5e 04 mov dword ptr ***91;esi+4***93;,ebx
; 23: 89 4e 08 mov dword ptr ***91;esi+8***93;,ecx
; 26: 89 56 0c mov dword ptr ***91;esi+12***93;,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 ***91;$0 capable***93;"
Quit
FunctionEnd

Section
SectionEnd
>

as you say, this will cause problems with DEP/NX, you could allocate memory with VirtualAlloc (PAGE_EXECUTE_READWRITE protection)


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***91;4***93;)
;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,***91;ebp+8***93;
; 19: 0f a2 cpuid
>; 1b: 8b 75 0c mov esi,dword ptr ***91;ebp+12***93;
; 1e: 89 06 mov dword ptr ***91;esi***93;,eax
>; 20: 89 5e 04 mov dword ptr ***91;esi+4***93;,ebx
>; 23: 89 4e 08 mov dword ptr ***91;esi+8***93;,ecx
>; 26: 89 56 0c mov dword ptr ***91;esi+12***93;,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 ***91;EAX value***93; in $1
>; Params: query code ***91;EAX value***93; in $1
>; Returns: 0 in $0 if function succeeded and the results of CPUID
>; in $1 ***91;EAX***93;, $2 ***91;EBX***93;, $3 ***91;ECX***93; and $4 ***91;EDX***93;
; 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 ***91;$0 capable***93;"
Quit
FunctionEnd

Section
SectionEnd
>