1

.NET 2.0 C#コンソールプロジェクトでVMWare検出を行っています。検出コードは、P / Invokeを使用してC#コードから呼び出されるDLLのエクスポートされた関数としてCに実装されます。C#コードは、x86およびx64として2つの別々の実行可能ファイルにコンパイルされます。C DLLも、両方のプラットフォーム用にコンパイルされています。エクスポートされた関数は次のとおりです。

PUBLIC Int32 __declspec(nothrow) WINAPI GetVMType ()
{
    Int32 nVMWareType = 0;

    try
    {
        if ( !IsInVMWare ( nVMWareType ) )
        {
            ... // allocate memory, write data, etc.
        }
    }
    catch ( ... )
    {
        nVMWareType = -1;
    }

    return ( nVMWareType );
} // <-- breakpoint happens here

VMWare検出にインラインアセンブリを使用する次のコードがあります。

PRIVATE Bool IsInVMWare ( Int32& nType )
{
    Bool bResult = false;
    Int32 nVersion = -1;
    nType = -1;

    __try
    {
        #ifndef _WIN64 // 32-bit detection
        __asm
        {
            push    edx
            push    ecx
            push    ebx

            mov     eax, 'VMXh'
            mov     ebx, 0          // anything but 'VMXh'
            mov     ecx, 10         // get VMWare version
            mov     edx, 'VX'       // port number
            in      eax, dx         // read port
            cmp     ebx, 'VMXh'     // is it a reply from VMWare?
            je      lblInVMWare

            xor     ecx, ecx        // not in VMWare - clear return value

        lblInVMWare:
            mov     [nVersion], ecx // vmware product type
            pop     ebx
            pop     ecx
            pop     edx
        }
        #else
        nVersion = GetVMWareVersion (); // 64-bit detection
        #endif

        nType = nVersion;

        if ( nType > 0 )
            bResult = true;
    }
    __except ( EXCEPTION_EXECUTE_HANDLER )
    {
        bResult = false;
    }

    return ( bResult );
}

64ビット検出コードはGetVMWareVersion()アセンブリのように実装されます。

PUBLIC GetVMWareVersion
    .CODE
    ALIGN   8

GetVMWareVersion PROC

    push    rbp
    mov     rbp, rsp

    push    rdx
    push    rcx
    push    rbx

    mov     rax, 'VMXh'
    mov     rbx, 0       ; anything but 'VMXh'
    mov     rcx, 10      ; get VMWare version
    mov     rdx, 'VX'    ; port number
    in      rax, dx      ; read port
    cmp     ebx, 'VMXh'  ; is it a reply from VMWare?
    je      $0@GetVMWareVersion

    xor     rax, rax     ; not in VMWare - clear return value
    jmp     $1@GetVMWareVersion

$0@GetVMWareVersion:
    mov     rax, rcx     ; VMWare product type

$1@GetVMWareVersion:
    pop     rbx
    pop     rcx
    pop     rdx

    mov     rsp, rbp
    pop     rbp
    ret

GetVMWareVersion ENDP

END

32ビットの検出コードはVM以外のWindows7で正常に実行されます。64ビットバージョンが実行されると(同じ環境)、DebugBreak()次の呼び出しスタックで呼び出しがトリガーされます。

KernelBase.dll!DebugBreak() + 0x2 bytes
[Frames below may be incorrect and/or missing, no symbols loaded for KernelBase.dll]
mscorwks.dll!PreBindAssembly() + 0x9ce69 bytes
mscorwks.dll!PreBindAssembly() + 0x9d28e bytes
mscorwks.dll!CreateApplicationContext() + 0x769d bytes
mscorwks.dll!StrongNameTokenFromPublicKey() + 0x64f8 bytes
mscorwks.dll!StrongNameTokenFromPublicKey() + 0x66ff bytes
mscorwks.dll!CreateApplicationContext() + 0x7f62 bytes
ntdll.dll!vsprintf_s() + 0x12b bytes
ntdll.dll!RtlUnwindEx() + 0x852 bytes
ntdll.dll!KiUserExceptionDispatcher() + 0x2e bytes
mscorwks.dll!IEE() + 0xd285 bytes
cccccccccccccccc()
0000000000d78180()
cccccccccccccccc()

VisualStudioでCDLLをデバッグするときに、[出力]ウィンドウにもこれが表示されます。

First-chance exception at 0x000007feedbdfad1 (mscorwks.dll) in MyApp.exe:
    0xC0000005: Access violation reading location 0xffffffffffffffff.

時々(常にではありませんが)、これはイベントログにも記録されます。

.NET Runtime version 2.0.50727.5456 - Fatal Execution Engine Error
    (000007FEEDB27916) (80131506)

なぜこれが起こるのか、私は完全に途方に暮れています。たくさん検索しましたが、この問題に当てはまると思われるものは見つかりませんでした。一部のサイトは.NET4.0への切り替えを提案していますが、それはオプションではありません。

私はすべてのコードを分析しましたGetVMType ()-それはいくつかのメモリ割り当てを行い、動的に割り当てられた配列にデータを書き込みますが、そのコードは正しいです:すべてのメモリが解放され、メモリが誤って上書きされることはありません。

命令をスキップするように64ビットアセンブリコードを変更するinと、ブレークポイントがトリガーされません。これは、32ビットコードとして実行している場合は問題になりません。

呼び出し元のC#プログラムには、最上位の例外ハンドラーとイベントのイベントハンドラーがUnhandledExceptionありますが、この問題が発生すると、アプリケーションは終了します。ハンドラーは呼び出されません。

誰かがこの設定で何が間違っている可能性があるか考えていますか?in何時間もデバッグして何が起こっているのかを確認しようとしましたが、命令の実行後にP / Invoke呼び出しが戻ると、.NET内の何かが壊れているようです。

4

1 に答える 1

1

私の推測では、関数がレジスタを破損していると思います。

実際のハードウェア (非 VM) で実行すると、おそらく "in rax, dx" で例外が発生するはずです。これが発生すると、制御が例外ハンドラに渡され、結果が設定されますが、レジスタは復元されません。この動作は、呼び出し元にとってまったく予期しないものです。たとえば、何かを EBX/RBX レジスタに保存してから、asm コードを呼び出すと、asm コードは "mov RBX, 0" を実行し、実行し、例外をキャッチし、結果を設定し、戻ります。もう EBX/RBX にはありません! EBX/RBX に格納されたポインタがあった場合、ハード クラッシュすることになります。何でも起れる。

確かに、asm コードはレジスタを保存/復元しますが、これは例外が発生しない場合にのみ発生します。つまり、コードが VM で実行されている場合です。その後、コードは通常の実行パスを実行し、例外は発生せず、レジスタは通常どおり復元されます。ただし、例外がある場合、実行は例外ハンドラーに渡されるため、POP はスキップされます。

これが当てはまる場合、32 ビット コードも機能しません。それが機能している場合、それは偶然によってのみ発生します。つまり、まったくの偶然です。また、使用できるレジスタが少ない 32 ビット コードの場合、asm コードを呼び出す前に、誰かがすでにレジスタを保存/復元している可能性があります。

正しいコードはおそらく、内部ではなく、try/except ブロックの外部で PUSH/POP を実行する必要があります。

于 2013-08-18T08:51:13.100 に答える