あなたが言ったように、VS 2008 はそのまま Windows 2000 をターゲットにするように動作します。余分なものは必要ありません。新しいバージョンの IDE を使用したい場合は、Visual Studio の複数のバージョンを同時にインストールすると (常に最も古いバージョンを最初にインストールし、時間をかけて「順方向」に作業します)、たとえば、VS 2010 で作業しながらビルドするように指示することができます。 VS 2008 ツールチェーンを使用します。明らかに、VS 2010 で導入されたコンパイラ機能の恩恵を受けることはできませんが、新しい IDE を使用できるようになります。
VS 2010 を使用して、EncodePointer/DecodePointer トリックで Windows 2000 をターゲットにすることができます。ここでの問題は、VS 2010 C ランタイム ライブラリ (CRT) がこれらの関数を必要とする (内部的に呼び出す) ことですが、これらの関数は Windows XP SP2 より前のバージョンの OS には存在しません。ただし、スタブを作成し、それらに対して実行可能ファイルをリンクする場合 (静的にもCRT にリンクすると、実際にそれらのスタブが検出されて使用されます)、Windows 2000 で VS 2010 でコンパイルされた EXE を実行できます。また、リンカ設定で必要な最小バージョンを 5.0 に設定する必要があることに注意してください。(これを行うと、これが有効でサポートされているバージョンではないというリンク時の警告が表示されますが、この警告は単に無視できます。これは機能し、フィールドは PE ヘッダーに適切に設定されます。)間違いなく汚いトリックですが、非常にうまく機能することはわかっています。私はいくつかのプロジェクトでそれを行います。私のスタブは、現在の OS で利用できる場合は実際の EncodePointer/DecodePointer 関数を動的に呼び出し、利用できない場合は、基本的にノーオペレーションに戻ります (これらのダウンレベル OS でのセキュリティ上の利点を放棄します)。Suma は、関連する質問への回答で、このトリックについて既にかなり詳しく説明しています。.
WINVER
と定義は、実際にはこれ_WIN32_WINNT
とは何の関係もありません。これらは、Windows SDK ヘッダーが実際に定義する関数プロトタイプを制御するだけです。これらをターゲットの Windows バージョンに設定すると、そのバージョンの Windows に実際に存在する関数にのみ静的にリンクできるようになります。適切なバージョンの Windows で実行している場合は、(GetModuleHandle/LoadLibrary → GetProcAddress を介して) 新しい関数を動的に呼び出すことができ、サポートされていない場合は適切にフォールバックできます。存在しない関数に静的にリンクしようとすると、アプリケーションを実行しようとするとローダーがエラーを生成します。ただし、これは開発者として完全に制御できるものであるため、簡単です。問題は、CRT (ライブラリが制御しない) は、EncodePointer など、存在しない関数を呼び出します。そのため、上記の回避策が必要です。WINVER
およびの値は_WIN32_WINNT
、コンパイラまたはリンカーに実際の影響を与えません。
おそらく、VS 2012 の同様のトリックで逃げることができます。組み込みの XP ターゲット サポートを使用して、VS 2015 でしばらく前にこれに取り組み始めました。 . どちらかといえば、VS 2015 よりも VS 2012 の方が簡単なはずですが、それは簡単ではなく、おそらく実際のアプリケーションのサポートの悪夢になるでしょう. それでも、これは楽しい実験であり、誰もが既に知っていることを裏付けるものです。ここでの問題は、コンパイラやリンカではありません。PE 形式は同じです。Win32 を対象とするコンパイラまたはリンカーは、Windows NT の任意のバージョンで実行されるバイナリをビルドできます。問題は、ダウンレベルのオペレーティング システムに存在しない関数を呼び出そうとする C ランタイム ライブラリだけです。
これをテストするには、前述の EncodePointer/DecodePointer のトリックを使用して、VS 2012 で EXE をコンパイルします。もちろん、リンカ設定で最低限必要なバージョンを 5.0 に設定していることも確認する必要があります。(これが機能しない場合、またそうでない可能性がある場合は、ビルド後の手順として手動で変更する必要があります。editbin.exe
.) 次に、その実行可能ファイルを Windows 2000 で実行してみます。静的にリンクされた関数がないためにアプリケーションを起動できないというエラー メッセージが表示されることは間違いありません。次に、EncodePointer/DecodePointer の場合と同じように、その関数を調査してスタブ化する必要があります。おそらく、意味のある作業を行う関数になるため、より困難になる可能性があります。つまり、単純に NOP を実行することはできません。その関数への依存関係を修正したら、W2K ローダーが文句を言う関数ごとにプロセスを繰り返します。(Dependency Walker または同等のユーティリティを使用して、この情報を取得することもできます。) 存在しない関数をすべて処理すると、実行する EXE が最終的に得られます。
VS 2015 では、EncodePointer と DecodePointer に加えて、InitializeSListHead、GetModuleHandleEx、および SystemFunction036 (RtlGenRandom のエクスポート名) のスタブを作成する必要がありました。VS 2012 でも同様の経験があると思います。最初の 2 つを置き換えるのは、実際には比較的簡単です。InitializeSListHead については、Windows XP で対応する関数をリバース エンジニアリングし、下位レベルの OS バージョン用に独自の実装を作成しました。GetModuleHandleEx の場合、マネージド アプリのサポートを有効にするコンテキストで CRT によってのみ呼び出されます。私はそれらを気にしなかったので、失敗を返すノーオペレーションに変えました。SystemFunction036 (RtlGenRandom) はより困難ですが、rand を使用していない (おそらく使用すべきではない) 場合は、これも必要ありません。ブレークポイントとしてスタブしただけです(int 3
)。CryptGenRandom を呼び出すためにスタブすることもできます。散文よりもコードの方が得意な場合は、「Hello world」アプリで使用したスタブの概算を次に示します。
.386
.MODEL flat, stdcall
.DATA
;; Override the import symbols from kernel32.dll
__imp__InitializeSListHead@4 DWORD DownlevelInitializeSListHead
__imp__GetModuleHandleExW@12 DWORD DownlevelGetModuleHandleExW
EXTERNDEF STDCALL __imp__InitializeSListHead@4 : DWORD
EXTERNDEF STDCALL __imp__GetModuleHandleExW@12 : DWORD
;; Declare functions that we will call statically
EXTRN STDCALL _imp__GetModuleHandleW@4 : DWORD
EXTRN STDCALL _imp__GetProcAddress@8 : DWORD
CONST SEGMENT
kszKernel32 DB 'k', 00H, 'e', 00H, 'r', 00H, 'n', 00H, 'e', 00H, 'l', 00H, '3', 00H, '2', 00H, 00H, 00H
kszAdvApi32 DB 'a', 00H, 'd', 00H, 'v', 00H, 'a', 00H, 'p', 00H, 'i', 00H, '3', 00H, '2', 00H, 00H, 00H
kszInitializeSListHead DB "InitializeSListHead", 00H
kszGetModuleHandleExW DB "GetModuleHandleExW", 00H
; Windows XP and Server 2003 and later have RtlGenRandom, which is exported as SystemFunction036.
; If needed, we could fall back to CryptGenRandom(), but that will be much slower
; because it has to drag in the entire crypto API.
; (See also: https://blogs.msdn.microsoft.com/michael_howard/2005/01/14/cryptographically-secure-random-number-on-windows-without-using-cryptoapi/)
kszSystemFunction036 DB "SystemFunction036", 00H
CONST ENDS
.CODE
; C++ translation:
; extern "C" VOID WINAPI DownlevelInitializeSListHead(PSLIST_HEADER pHead)
; {
; const HMODULE hmodKernel32 = ::GetModuleHandleW(L"kernel32");
; typedef decltype(InitializeSListHead)* pfnInitializeSListHead;
; const pfnInitializeSListHead pfn = reinterpret_cast<pfnInitializeSListHead>(::GetProcAddress(hmodKernel32, "InitializeSListHead"));
; if (pfn)
; {
; // call WinAPI function
; pfn(pHead);
; }
; else
; {
; // fallback implementation for downlevel
; pHead->Alignment = 0;
; }
; }
DownlevelInitializeSListHead PROC
;; Get a handle to the DLL containing the function of interest.
push OFFSET kszKernel32
call DWORD PTR _imp__GetModuleHandleW@4 ; Returns the handle to the library in EAX.
;; Attempt to obtain a pointer to the function of interest.
push OFFSET kszInitializeSListHead ; Push 2nd parameter (string containing function's name).
push eax ; Push 1st parameter (handle to the library).
call DWORD PTR _imp__GetProcAddress@8 ; Returns the pointer to the function in EAX.
;; Test for success, and call the function if we succeeded.
test eax, eax ; See if we successfully retrieved a pointer to the function.
je SHORT FuncNotSupported ; Jump on failure (ptr == 0), or fall through in the most-likely case.
jmp eax ; We succeeded (ptr != 0), so tail-call the function.
;; The dynamic call failed, presumably because the function isn't available.
;; So do what _RtlInitializeSListHead@4 (which is what we jump to on uplevel platforms) does,
;; which is to set pHead->Alignment to 0. It is a QWORD-sized value, so 32-bit code must
;; clear both of the DWORD halves.
FuncNotSupported:
mov edx, DWORD PTR [esp+4] ; get pHead->Alignment
xor eax, eax
mov DWORD PTR [edx], eax ; pHead->Alignment = 0
mov DWORD PTR [edx+4], eax
ret 4
DownlevelInitializeSListHead ENDP
; C++ translation:
; extern "C" BOOL WINAPI DownlevelGetModuleHandleExW(DWORD dwFlags, LPCTSTR lpModuleName, HMODULE* phModule)
; {
; const HMODULE hmodKernel32 = ::GetModuleHandleW(L"kernel32");
; typedef decltype(GetModuleHandleExW)* pfnGetModuleHandleExW;
; const pfnGetModuleHandleExW pfn = reinterpret_cast<pfnGetModuleHandleExW>(::GetProcAddress(hmodKernel32, "GetModuleHandleExW"));
; if (pfn)
; {
; // call WinAPI function
; return pfn(dwFlags, lpModuleName, phModule);
; }
; else
; {
; // fallback for downlevel: return failure
; return FALSE;
; }
; }
DownlevelGetModuleHandleExW PROC
;; Get a handle to the DLL containing the function of interest.
push OFFSET kszKernel32
call DWORD PTR _imp__GetModuleHandleW@4 ; Returns the handle to the library in EAX.
;; Attempt to obtain a pointer to the function of interest.
push OFFSET kszGetModuleHandleExW ; Push 2nd parameter (string containing function's name).
push eax ; Push 1st parameter (handle to the library).
call DWORD PTR _imp__GetProcAddress@8 ; Returns the pointer to the function in EAX.
;; Test for success, and call the function if we succeeded.
test eax, eax ; See if we successfully retrieved a pointer to the function.
je SHORT FuncNotSupported ; Jump on failure (ptr == 0), or fall through in the most-likely case.
jmp eax ; We succeeded (ptr != 0), so tail-call the function.
;; The dynamic call failed, presumably because the function isn't available.
;; The basic VS 2015 CRT (used in a simple Win32 app) only calls this function
;; in try_cor_exit_process(), as called from common_exit (both in exit.cpp),
;; where it uses it to attempt to get a handle to the module mscoree.dll.
;; Since we don't care about managed apps, that attempt should rightfully fail.
;; If this turns out to be used in other contexts, we'll need to revisit this
;; and implement a proper fallback.
FuncNotSupported:
xor eax, eax ; return failure
ret 12
DownlevelGetModuleHandleExW ENDP
DownlevelSystemFunction036 PROC
int 3 ; break --- stub unimplemented
ret 8
DownlevelSystemFunction036 ENDP
END
Roy は、Microsoft がInterlockedCompareExchange() のみを必要とするSList の MIT ライセンスの実装を提供したことをコメントで指摘しています。これにより、私のように SList 関数をリバース エンジニアリングする必要がないため、作業が少し楽になります。
言うまでもなく、MFC、ATL、およびソース コードが制御できないその他のライブラリは、絶対に避けるべきです。ダウンレベル バージョンのオペレーティング システムでは使用できない機能への依存関係を引きずり込み、さらに多くの作業が必要になります。実際には生の Win32 に制限する必要があります。つまり、心配しなければならないライブラリは CRT だけです。
うわー!これで始められるはずです。興味をそそられるのではなく、これにひどく怯えている場合は、このようなハッキングとはほぼ確実に関係ありません. 古いバージョンのコンパイラを使用してください。