編集: https://github.com/somanuell/SoBrowserAction
これが私の進行中の作業のスクリーンショットです。
私がしたこと:
1. プロテクトモードからの脱出
BHO 登録はHKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Internet Explorer\Low Rights\ElevationPolicy
キーを更新する必要があります。保護モードの Internet Explorer の理解と操作を参照してください。
「ベストプラクティス」として言及されており、デバッグが容易であるため、プロセスの方法を選択しますがRunDll32Policy
、トリックも実行できる場合があります。
rgs
BHO レジストリ設定を含むファイルを見つけます。これは、レジストリ キーへの更新を含むもの'Browser Helper Object'
です。そのファイルに次を追加します。
HKLM {
NoRemove SOFTWARE {
NoRemove Microsoft {
NoRemove 'Internet Explorer' {
NoRemove 'Low Rights' {
NoRemove ElevationPolicy {
ForceRemove '{AE6E5BFE-B965-41B5-AC70-D7069E555C76}' {
val AppName = s 'SoBrowserActionInjector.exe'
val AppPath = s '%MODULEPATH%'
val Policy = d '3'
}
}
}
}
}
}
}
GUID は新しいものでなければなりません。私のものは使用しないでください。GUID ジェネレーターを使用してください。policyの3
値は、ブローカー プロセスが中程度の整合性プロセスとして起動されることを保証します。%MODULEPATH%
マクロは定義済みのものではありません。
マクロを使用する理由
MSI にレジストリの更新が含まれている場合は、RGS ファイル内のその新しいコードを避けることができます。MSI を扱うのは面倒な場合があるため、「完全な自己登録」パッケージを提供する方が簡単な場合がよくあります。ただし、マクロを使用しない場合、ユーザーがインストール ディレクトリを選択することはできません。マクロを使用すると、正しいインストール ディレクトリでレジストリを動的に更新できます。
マクロを機能させるには?
BHO クラスのヘッダーでマクロを見つけてDECLARE_REGISTRY_RESOURCEID
、コメントアウトします。そのヘッダーに次の関数定義を追加します。
static HRESULT WINAPI UpdateRegistry( BOOL bRegister ) throw() {
ATL::_ATL_REGMAP_ENTRY regMapEntries[2];
memset( ®MapEntries[1], 0, sizeof(ATL::_ATL_REGMAP_ENTRY));
regMapEntries[0].szKey = L"MODULEPATH";
regMapEntries[0].szData = sm_szModulePath;
return ATL::_pAtlModule->UpdateRegistryFromResource(IDR_CSOBABHO, bRegister,
regMapEntries);
}
そのコードは、ATL 実装から借用したものですDECLARE_REGISTRY_RESOURCEID
(私の場合、VS2010 に同梱されているものです。ATL のバージョンを確認し、必要に応じてコードを更新してください)。IDR_CSOBABHO
マクロは、REGISTRY
RC ファイルに RGS を追加するリソースのリソース ID です。
このsm_szModulePath
変数には、ブローカー プロセス EXE のインストール パスが含まれている必要があります。これを BHO クラスの public static メンバー変数にすることにしました。これを設定する簡単な方法の 1 つは、DllMain
関数を使用することです。regsvr32
Dll をロードすると、がDllMain
呼び出され、適切なパスでレジストリが更新されます。
extern "C" BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved) {
if ( dwReason == DLL_PROCESS_ATTACH ) {
DWORD dwCopied = GetModuleFileName( hInstance,
CCSoBABHO::sm_szModulePath,
sizeof( CCSoBABHO::sm_szModulePath ) /
sizeof( wchar_t ) );
if ( dwCopied ) {
wchar_t * pLastAntiSlash = wcsrchr( CCSoBABHO::sm_szModulePath, L'\\' );
if ( pLastAntiSlash ) *( pLastAntiSlash ) = 0;
}
}
return _AtlModule.DllMain(dwReason, lpReserved);
}
ムラデン・ヤンコビッチに感謝します。
ブローカー プロセスを起動する方法は?
考えられる場所の 1 つは、SetSite
実装です。何度も起動されますが、プロセス自体で対処します。ホスティング IEFrame の HWND を引数として受け取ると、ブローカ プロセスにメリットがあることが後でわかります。これはIWebBrowser2::get_HWND
メソッドを使用して行うことができます。ここで、あなたはすでにIWebBrowser2*
メンバーを持っていると思います。
STDMETHODIMP CCSoBABHO::SetSite( IUnknown* pUnkSite ) {
if ( pUnkSite ) {
HRESULT hr = pUnkSite->QueryInterface( IID_IWebBrowser2, (void**)&m_spIWebBrowser2 );
if ( SUCCEEDED( hr ) && m_spIWebBrowser2 ) {
SHANDLE_PTR hWndIEFrame;
hr = m_spIWebBrowser2->get_HWND( &hWndIEFrame );
if ( SUCCEEDED( hr ) ) {
wchar_t szExeName[] = L"SoBrowserActionInjector.exe";
wchar_t szFullPath[ MAX_PATH ];
wcscpy_s( szFullPath, sm_szModulePath );
wcscat_s( szFullPath, L"\\" );
wcscat_s( szFullPath, szExeName );
STARTUPINFO si;
memset( &si, 0, sizeof( si ) );
si.cb = sizeof( si );
PROCESS_INFORMATION pi;
wchar_t szCommandLine[ 64 ];
swprintf_s( szCommandLine, L"%.48s %d", szExeName, (int)hWndIEFrame );
BOOL bWin32Success = CreateProcess( szFullPath, szCommandLine, NULL,
NULL, FALSE, 0, NULL, NULL, &si, &pi );
if ( bWin32Success ) {
CloseHandle( pi.hThread );
CloseHandle( pi.hProcess );
}
}
}
[...]
2. IEFrame スレッドの注入
これを行うには多くの方法があり、それぞれに長所と短所があるため、これが最も複雑な部分のようです。
ブローカー プロセスである「インジェクター」は、以前のインスタンスによってまだ処理されていない場合、一意の IEFrame を処理する必要がある単純な引数 (HWND または TID) を 1 つ持つ短命のプロセスである可能性があります。
むしろ、「インジェクター」は、デスクトップを継続的に監視し、新しい IEFrame が表示されるたびに処理する必要がある、長寿命で、最終的には終了しないプロセスである可能性があります。プロセスの単一性は、名前付きミューテックスによって保証される場合があります。
当面はKISS原則(Keep It Simple, Stupid)で行こうと思います。つまり、短命のインジェクターです。これにより、BHO でデスクトップにタブをドラッグ アンド ドロップした場合に特別な処理が行われることは確かですが、後で説明します。
そのルートに進むには、インジェクターの終了後も存続する Dll インジェクションが必要ですが、これを Dll 自体に委譲します。
インジェクタープロセスのコードは次のとおりです。WH_CALLWNDPROCRET
IEFrame をホストするスレッドのフックをインストールし、SendMessage
(特定の登録済みメッセージと共に) 使用して Dll インジェクションを直ちにトリガーし、フックを削除して終了します。BHO Dll は、CallWndRetProc
という名前のコールバックをエクスポートする必要がありますHookCallWndProcRet
。エラー パスは省略されます。
#include <Windows.h>
#include <stdlib.h>
typedef LRESULT (CALLBACK *PHOOKCALLWNDPROCRET)( int nCode, WPARAM wParam, LPARAM lParam );
PHOOKCALLWNDPROCRET g_pHookCallWndProcRet;
HMODULE g_hDll;
UINT g_uiRegisteredMsg;
int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE, char * pszCommandLine, int ) {
HWND hWndIEFrame = (HWND)atoi( pszCommandLine );
wchar_t szFullPath[ MAX_PATH ];
DWORD dwCopied = GetModuleFileName( NULL, szFullPath,
sizeof( szFullPath ) / sizeof( wchar_t ) );
if ( dwCopied ) {
wchar_t * pLastAntiSlash = wcsrchr( szFullPath, L'\\' );
if ( pLastAntiSlash ) *( pLastAntiSlash + 1 ) = 0;
wcscat_s( szFullPath, L"SoBrowserActionBHO.dll" );
g_hDll = LoadLibrary( szFullPath );
if ( g_hDll ) {
g_pHookCallWndProcRet = (PHOOKCALLWNDPROCRET)GetProcAddress( g_hDll,
"HookCallWndProcRet" );
if ( g_pHookCallWndProcRet ) {
g_uiRegisteredMsg = RegisterWindowMessage( L"SOBA_MSG" );
if ( g_uiRegisteredMsg ) {
DWORD dwTID = GetWindowThreadProcessId( hWndIEFrame, NULL );
if ( dwTID ) {
HHOOK hHook = SetWindowsHookEx( WH_CALLWNDPROCRET,
g_pHookCallWndProcRet,
g_hDll, dwTID );
if ( hHook ) {
SendMessage( hWndIEFrame, g_uiRegisteredMsg, 0, 0 );
UnhookWindowsHookEx( hHook );
}
}
}
}
}
}
if ( g_hDll ) FreeLibrary( g_hDll );
return 0;
}
3. サバイビング・インジェクション: 「私をもっと強く引っ掛けて」
ツールバーに新しいボタンを追加するには、メインの IE プロセスで Dll を一時的にロードするだけで十分です。しかし、その新しいボタンを監視できるようにするには、WM_COMMAND
さらに多くのことが必要です。つまり、永続的に読み込まれた DLL と、フック プロセスが終了したにもかかわらず、フックがまだ所定の位置にあるということです。簡単な解決策は、スレッドを再度フックして、Dll インスタンス ハンドルを渡すことです。
タブを開くたびに新しい BHO のインスタンス化、つまり新しいインジェクター プロセスが発生するため、フック関数には、現在のスレッドが既にフックされているかどうかを知る方法が必要です (タブを開くたびにフックを追加したくありません。それはきれいではありません)
Thread Local Storage は次の方法です。
- の に TLS インデックスを割り当て
DllMain
ますDLL_PROCESS_ATTACH
。
- 新しい
HHOOK
ものを TLS データとして保存し、それを使用して、スレッドが既にフックされているかどうかを確認します
- 必要に応じてフックを外します。
DLL_THREAD_DETACH
- TLS インデックスを解放します
DLL_PROCESS_DETACH
これは、次のコードにつながります。
// DllMain
// -------
if ( dwReason == DLL_PROCESS_ATTACH ) {
CCSoBABHO::sm_dwTlsIndex = TlsAlloc();
[...]
} else if ( dwReason == DLL_THREAD_DETACH ) {
CCSoBABHO::UnhookIfHooked();
} else if ( dwReason == DLL_PROCESS_DETACH ) {
CCSoBABHO::UnhookIfHooked();
if ( CCSoBABHO::sm_dwTlsIndex != TLS_OUT_OF_INDEXES )
TlsFree( CCSoBABHO::sm_dwTlsIndex );
}
// BHO Class Static functions
// --------------------------
void CCSoBABHO::HookIfNotHooked( void ) {
if ( sm_dwTlsIndex == TLS_OUT_OF_INDEXES ) return;
HHOOK hHook = reinterpret_cast<HHOOK>( TlsGetValue( sm_dwTlsIndex ) );
if ( hHook ) return;
hHook = SetWindowsHookEx( WH_CALLWNDPROCRET, HookCallWndProcRet,
sm_hModule, GetCurrentThreadId() );
TlsSetValue( sm_dwTlsIndex, hHook );
return;
}
void CCSoBABHO::UnhookIfHooked( void ) {
if ( sm_dwTlsIndex == TLS_OUT_OF_INDEXES ) return;
HHOOK hHook = reinterpret_cast<HHOOK>( TlsGetValue( sm_dwTlsIndex ) );
if ( UnhookWindowsHookEx( hHook ) ) TlsSetValue( sm_dwTlsIndex, 0 );
}
これで、ほぼ完全なフック関数ができました。
LRESULT CALLBACK CCSoBABHO::HookCallWndProcRet( int nCode, WPARAM wParam,
LPARAM lParam ) {
if ( nCode == HC_ACTION ) {
if ( sm_uiRegisteredMsg == 0 )
sm_uiRegisteredMsg = RegisterWindowMessage( L"SOBA_MSG" );
if ( sm_uiRegisteredMsg ) {
PCWPRETSTRUCT pcwprets = reinterpret_cast<PCWPRETSTRUCT>( lParam );
if ( pcwprets && ( pcwprets->message == sm_uiRegisteredMsg ) ) {
HookIfNotHooked();
HWND hWndTB = FindThreadToolBarForIE9( pcwprets->hwnd );
if ( hWndTB ) {
AddBrowserActionForIE9( pcwprets->hwnd, hWndTB );
}
}
}
}
return CallNextHookEx( 0, nCode, wParam, lParam);
}
のコードはAddBrowserActionForIE9
後で編集します。
IE9 の場合、TB を取得するのは非常に簡単です。
HWND FindThreadToolBarForIE9( HWND hWndIEFrame ) {
HWND hWndWorker = FindWindowEx( hWndIEFrame, NULL,
L"WorkerW", NULL );
if ( hWndWorker ) {
HWND hWndRebar= FindWindowEx( hWndWorker, NULL,
L"ReBarWindow32", NULL );
if ( hWndRebar ) {
HWND hWndBand = FindWindowEx( hWndRebar, NULL,
L"ControlBandClass", NULL );
if ( hWndBand ) {
return FindWindowEx( hWndBand, NULL,
L"ToolbarWindow32", NULL );
}
}
}
return 0;
}
4. ツールバーの処理
その部分は大幅に改善される可能性があります。
- 白黒のビットマップを作成したところ、すべて問題ありませんでした。つまり、黒のピクセルが透明になりました。色やグレーレベルを追加しようとするたびに、結果はひどいものになりました。私は、これらの「ツールバーマジックのビットマップ」にまったく流暢ではありません
- ビットマップのサイズは、既にツールバーにある他のビットマップの現在のサイズに依存する必要があります。2 つのビットマップを使用しました (1 つは「ノーマル」、もう 1 つは「ビッグ」)。
- アドレスバーの幅を狭くして、ツールバーの新しい状態を IE に強制的に「再描画」させる部分を最適化できる場合があります。それは機能します.IEメインウィンドウ全体を含む迅速な「再描画」フェーズがあります。
現在、コード形式が機能している回答を編集できないため、質問に対する他の回答を参照してください。