2 changed files with 315 additions and 0 deletions
@ -0,0 +1,9 @@
@@ -0,0 +1,9 @@
|
||||
--- |
||||
title: "Flash32以上版本无法打开外链问题说明" |
||||
date: 2020-08-12T10:48:22+08:00 |
||||
draft: true |
||||
toc: true |
||||
images: |
||||
tags: [untagged] |
||||
categories: |
||||
--- |
||||
@ -0,0 +1,306 @@
@@ -0,0 +1,306 @@
|
||||
--- |
||||
title: "IWebBrowser2使用F12开发者工具" |
||||
date: 2020-08-12T10:46:10+08:00 |
||||
draft: false |
||||
toc: true |
||||
images: |
||||
tags: [ie] |
||||
categories: windows |
||||
--- |
||||
|
||||
IE11带来了非常完善的开发者工具,但是IE控件默认是没法使用的,这给我们页面调试带来了极大的不便,不过好在Win10系统有了`IEChooser`工具,也勉强能用。无意中发现360安全浏览器可以使用F12开发者工具,所以抽空研究了一下。本文仅针对Win10下的IE11浏览器,对于Win7及以下系统,可以使用一个叫做[SuperF12](https://github.com/titilima/SuperF12)的项目。 |
||||
|
||||
<!--more--> |
||||
|
||||
## 工具准备 |
||||
|
||||
此次研究使用了`x64dbg`调试器(配合[DbgChild](https://github.com/David-Reguera-Garcia-Dreg/DbgChild)插件)用于动态调试,`IDA`用于静态反汇编,`Process Hacker`用于进程信息查看,360安全浏览器12.2.1362.0版本。 |
||||
|
||||
## 调试分析 |
||||
|
||||
通过`Process Hacker`可以发现,在360安全浏览器的IE内核中,按下`F12`键,会创建一个新的子进程,并且命令行参数带有`-windows10-f12=`的字样,在`IDA`中按下`Alt-T`以`windows10-f12`为关键字进行搜索,一共能找到两处,都是在函数`sub_40101F`中调用的,其`FA`为`#41f`,该函数比较长,下面贴出核心代码: |
||||
|
||||
```c++ |
||||
*(_DWORD *)LibFileName = "windows10-f12"; |
||||
v183 = sub_4CF6A0("windows10-f12"); |
||||
sub_42CF50((char *)v10, &v177[1], (int)LibFileName); |
||||
v28 = v180; |
||||
v29 = v178; |
||||
v30 = v180; |
||||
v31 = v180; |
||||
if ( (v180 & 0x80u) != 0 ) |
||||
v31 = v178; |
||||
if ( v31 ) |
||||
{ |
||||
v32 = (DWORD *)v177[1]; |
||||
v33 = (DWORD *)((char *)&v177[1] + v180); |
||||
if ( (v180 & 0x80u) != 0 ) |
||||
v33 = (DWORD *)(v177[1] + v178); |
||||
v34 = &v177[1]; |
||||
if ( (v180 & 0x80u) != 0 ) |
||||
v34 = (DWORD *)v177[1]; |
||||
if ( v34 != v33 ) |
||||
{ |
||||
v35 = (DWORD *)((char *)v33 - 1); |
||||
if ( v35 > v34 ) |
||||
{ |
||||
v36 = (unsigned int)v34 + 1; |
||||
do |
||||
{ |
||||
v37 = *(_BYTE *)(v36 - 1); |
||||
*(_BYTE *)(v36 - 1) = *(_BYTE *)v35; |
||||
*(_BYTE *)v35 = v37; |
||||
v35 = (DWORD *)((char *)v35 - 1); |
||||
v38 = v36++ < (unsigned int)v35; |
||||
} |
||||
while ( v38 ); |
||||
v28 = v180; |
||||
v32 = (DWORD *)v177[1]; |
||||
v29 = v178; |
||||
v30 = v180; |
||||
} |
||||
} |
||||
if ( (v28 & 0x80u) != 0 ) |
||||
v30 = v29; |
||||
HIDWORD(v174) = 0; |
||||
if ( (v28 & 0x80u) == 0 ) |
||||
v32 = &v177[1]; |
||||
sub_443D30(v32, v30, (signed int *)&v174 + 1); |
||||
v39 = (HWND)HIDWORD(v174); |
||||
if ( IsWindow((HWND)HIDWORD(v174)) ) |
||||
{ |
||||
if ( sub_458950() >= 7 ) |
||||
{ |
||||
dwProcessId[1] = 0; |
||||
GetWindowThreadProcessId(v39, &dwProcessId[1]); |
||||
sub_4B89C0(&Filename[2], 0, 260); |
||||
sub_402DBE((int)&Filename[2], (const char *)dword_4E26E0, dwProcessId[1], HIDWORD(v174)); |
||||
sub_4B89C0(LibFileName, 0, 520); |
||||
GetSystemDirectoryW(LibFileName, 0x104u); |
||||
PathAppendW(LibFileName, &pMore); |
||||
if ( PathFileExistsW(LibFileName) ) |
||||
{ |
||||
v40 = LoadLibraryW(LibFileName); |
||||
if ( v40 ) |
||||
{ |
||||
v41 = GetProcAddress(v40, "AttachTools"); |
||||
if ( v41 ) |
||||
((void (__cdecl *)(_DWORD, _DWORD, WCHAR *, _DWORD))v41)(0, 0, &Filename[2], 0); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
v28 = v176; |
||||
v42 = 1; |
||||
} |
||||
``` |
||||
|
||||
前面是参数解析部分,直接跳过不细究了,从函数`sub_443D30`调用的地方开始看,`v174`是`_int64`类型,所以`(signed int *)&v174 + 1`与`HIDWORD`表示的意思一样,结合这两行,我们可以猜到,函数`sub_443D30`是获取窗口句柄,拿到之后赋值给`v39`,到这里,`v39`和`HIDWORD(v174)`一样,都是存储着目标窗口句柄。接下来是两个`if`语句,其中`sub_458950`可能是拿到本身的主版本号,这个没有细看,在实际调试中发现,两个`if`都会进入。然后,通过`GetWindowThreadProcessId`拿到目标窗口所在进程的PID,存储在`dwProcessId[1]`中。`sub_4B89C0`是`memset`,将缓冲区置零,`sub_402DBE`是`sprintf`函数,通过动态调试发现,`sub_402DBE`相当于`sprintf(&Filename[2], "%d %x", dwProcessId[1], HIDWORD(v174))`,这句是在构造命令行参数,构造完之后`Filename[2]`的值就类似于`"123 456"`,这里要注意的是,其中的`456`是十六进制的。`LibFilename`是`c:\Windows\SysWOW64\F12\F12AppFrame.dll`,后面的逻辑就很清晰了,加载dll,然后调用对应的函数,需要用到前面`sub_402DBE`拿到的命令行参数,这里有一点要修改的是,`AttachTools`函数的签名为`void(__stdcall*)(HWND, HINSTANCE, char*, int)`,不知道360中为何使用了`__cdecl`调用。 |
||||
|
||||
## 代码编写 |
||||
|
||||
到这里,我们就分析完了启动参数带了`-windows10-f12`后,360安全浏览器的处理逻辑,很容易可以在我们自己的项目中加入下面的代码 |
||||
|
||||
```c++ |
||||
static BOOL AttachF12Tools(HWND hIE) |
||||
{ |
||||
auto hMod = ::LoadLibrary(L"F12\\F12AppFrame.dll"); |
||||
if (!hMod) |
||||
{ |
||||
return FALSE; |
||||
} |
||||
using AttachToolsType = void(__stdcall*)(HWND, HINSTANCE, char*, int); |
||||
auto AttachTools = (AttachToolsType)::GetProcAddress(hMod, "AttachTools"); |
||||
if (AttachTools) |
||||
{ |
||||
DWORD dwPid = 0; |
||||
::GetWindowThreadProcessId(hIE, &dwPid); |
||||
if (dwPid == 0) |
||||
{ |
||||
::FreeLibrary(hMod); |
||||
return FALSE; |
||||
} |
||||
char name[64] = { 0 }; |
||||
_snprintf_s(name, _countof(name), "%d %x", dwPid, hIE); |
||||
AttachTools(NULL, NULL, name, 0); |
||||
return TRUE; |
||||
} |
||||
::FreeLibrary(hMod); |
||||
return FALSE; |
||||
} |
||||
|
||||
``` |
||||
|
||||
但是此刻还无法启动开发者工具,我们还需要继续分析`AttachTools`函数 |
||||
|
||||
```c++ |
||||
void __stdcall AttachTools(HWND a1, HINSTANCE a2, char *a3, int a4) |
||||
{ |
||||
int v4; // ebx |
||||
HINSTANCE v5; // eax |
||||
unsigned int v6; // ecx |
||||
LPCSTR v7; // esi |
||||
std::_Ref_count_base *v8; // edi |
||||
DWORD v9; // esi |
||||
HWND v10; // edi |
||||
unsigned int v11; // [esp+0h] [ebp-44h] |
||||
unsigned __int16 v12; // [esp+4h] [ebp-40h] |
||||
int v13; // [esp+10h] [ebp-34h] |
||||
int v14; // [esp+14h] [ebp-30h] |
||||
int v15; // [esp+18h] [ebp-2Ch] |
||||
int v16; // [esp+1Ch] [ebp-28h] |
||||
std::_Ref_count_base *v17; // [esp+20h] [ebp-24h] |
||||
HRESULT v18; // [esp+24h] [ebp-20h] |
||||
int v19; // [esp+28h] [ebp-1Ch] |
||||
int pNumArgs; // [esp+2Ch] [ebp-18h] |
||||
LPCSTR lpMultiByteStr; // [esp+30h] [ebp-14h] |
||||
LPCWSTR lpCmdLine; // [esp+34h] [ebp-10h] |
||||
int v23; // [esp+40h] [ebp-4h] |
||||
|
||||
v4 = 0; |
||||
v13 = 0; |
||||
v14 = 0; |
||||
v15 = 0; |
||||
v23 = 0; |
||||
ATL::CSimpleStringT<char,0>::CSimpleStringT<char,0>(&lpMultiByteStr, &ATL::g_strmgr); |
||||
LOBYTE(v23) = 1; |
||||
if ( !a3 ) |
||||
{ |
||||
v6 = 0; |
||||
goto LABEL_7; |
||||
} |
||||
if ( (unsigned int)a3 & 0xFFFF0000 ) |
||||
{ |
||||
v6 = strlen(a3); |
||||
LABEL_7: |
||||
ATL::CSimpleStringT<char,0>::SetString(&lpMultiByteStr, a3, v6); |
||||
goto LABEL_8; |
||||
} |
||||
v5 = ATL::AtlFindStringResourceInstance(v11, v12); |
||||
if ( v5 ) |
||||
ATL::CStringT<char,ATL::StrTraitATL<char,ATL::ChTraitsCRT<char>>>::LoadStringW( |
||||
(unsigned int)&lpMultiByteStr, |
||||
(int)v5, |
||||
(unsigned __int16)a3); |
||||
LABEL_8: |
||||
LOBYTE(v23) = 2; |
||||
ATL::CSimpleStringT<char,0>::CSimpleStringT<char,0>(&lpCmdLine, &ATL::g_strmgr); |
||||
LOBYTE(v23) = 3; |
||||
v7 = lpMultiByteStr; |
||||
if ( !lpMultiByteStr || (unsigned int)lpMultiByteStr & 0xFFFF0000 ) |
||||
ATL::CStringT<unsigned short,ATL::StrTraitATL<unsigned short,ATL::ChTraitsCRT<unsigned short>>>::operator=(lpMultiByteStr); |
||||
else |
||||
ATL::CStringT<unsigned short,ATL::StrTraitATL<unsigned short,ATL::ChTraitsCRT<unsigned short>>>::LoadStringW( |
||||
&lpCmdLine, |
||||
(unsigned __int16)lpMultiByteStr); |
||||
v8 = (std::_Ref_count_base *)CommandLineToArgvW(lpCmdLine, &pNumArgs); |
||||
v17 = v8; |
||||
LOBYTE(v23) = 5; |
||||
if ( v8 ) |
||||
{ |
||||
if ( pNumArgs > 0 ) |
||||
{ |
||||
do |
||||
{ |
||||
ATL::CStringT<unsigned short,ATL::StrTraitATL<unsigned short,ATL::ChTraitsCRT<unsigned short>>>::CStringT<unsigned short,ATL::StrTraitATL<unsigned short,ATL::ChTraitsCRT<unsigned short>>>(*((_DWORD *)v8 + v4)); |
||||
LOBYTE(v23) = 6; |
||||
std::vector<ATL::CStringT<unsigned short,ATL::StrTraitATL<unsigned short,ATL::ChTraitsCRT<unsigned short>>>,std::allocator<ATL::CStringT<unsigned short,ATL::StrTraitATL<unsigned short,ATL::ChTraitsCRT<unsigned short>>>>>::push_back(&v19); |
||||
LOBYTE(v23) = 5; |
||||
ATL::CStringData::Release((ATL::CStringData *)(v19 - 16)); |
||||
++v4; |
||||
} |
||||
while ( v4 < pNumArgs ); |
||||
} |
||||
LocalFree((HLOCAL)v8); |
||||
ATL::CStringData::Release((ATL::CStringData *)(lpCmdLine - 8)); |
||||
LOBYTE(v23) = 0; |
||||
ATL::CStringData::Release((ATL::CStringData *)(v7 - 16)); |
||||
if ( pNumArgs == 2 ) |
||||
{ |
||||
v9 = __o__wtoi(*(_DWORD *)v13); |
||||
v10 = (HWND)_wcstol(*(const wchar_t **)(v13 + 4), 0, 16); |
||||
v18 = CoInitializeEx(0, 2u); |
||||
LOBYTE(v19) = 0; |
||||
v16 = 0; |
||||
v17 = 0; |
||||
std::shared_ptr_long_::_Setpd_long____lambda_e57c4cc13d2c92dd5aaa8f479b84dfdd___(&v18, v19); |
||||
LOBYTE(v23) = 7; |
||||
if ( !v18 ) |
||||
{ |
||||
lpMultiByteStr = 0; |
||||
LOBYTE(v23) = 8; |
||||
if ( !F12::GetDocumentFromHwnd(v10, &lpMultiByteStr) && !IEConfiguration_SetBool(536870925, 1) ) |
||||
InjectTools(v9, (int *)&lpMultiByteStr); |
||||
ATL::CComPtrBase<SHObjIdl::IPackageDebugSettings>::~CComPtrBase<SHObjIdl::IPackageDebugSettings>(&lpMultiByteStr); |
||||
} |
||||
if ( v17 ) |
||||
std::_Ref_count_base::_Decref(v17); |
||||
} |
||||
} |
||||
else |
||||
{ |
||||
ATL::CStringData::Release((ATL::CStringData *)(lpCmdLine - 8)); |
||||
ATL::CStringData::Release((ATL::CStringData *)(v7 - 16)); |
||||
} |
||||
std::vector<ATL::CStringT<unsigned short,ATL::StrTraitATL<unsigned short,ATL::ChTraitsCRT<unsigned short>>>,std::allocator<ATL::CStringT<unsigned short,ATL::StrTraitATL<unsigned short,ATL::ChTraitsCRT<unsigned short>>>>>::~vector<ATL::CStringT<unsigned short,ATL::StrTraitATL<unsigned short,ATL::ChTraitsCRT<unsigned short>>>,std::allocator<ATL::CStringT<unsigned short,ATL::StrTraitATL<unsigned short,ATL::ChTraitsCRT<unsigned short>>>>>(&v13); |
||||
} |
||||
``` |
||||
|
||||
代码依旧很长,不过没关系,我们只需要从`CommandLineToArgvW`开始阅读即可,`lpCmdLine`是我们上面传递的第三个参数,即`"123 456"`,所以`pNumArgs`值为2,这里很明显看到`__o__wtoi`和`_wcstol`,也解释了参数中前一个值为十进制,后一个为十六进制的原因(微软任性),然后是一个`CoInitializeEx(NULL, 2u);`的调用,`2u`即`COINIT_APARTMENTTHREADED`,`v18`保存了返回值,可以看到,只有当`v18`为0(`S_OK`)时,才会进入后面的处理逻辑,否则直接报错了,所以我们需要确保当前进程没有初始化COM组件,最好的办法就是创建一个新进程 |
||||
|
||||
```c++ |
||||
static HWND IsF12DevTool(LPTSTR lpstrCmdLine) |
||||
{ |
||||
int nArgs = 0; |
||||
LPWSTR* ppArgList = ::CommandLineToArgvW(lpstrCmdLine, &nArgs); |
||||
if (ppArgList == NULL) |
||||
{ |
||||
return NULL; |
||||
} |
||||
constexpr wchar_t pstrF12[] = L"-windows10-f12="; |
||||
constexpr UINT nF12 = _countof(pstrF12) - 1; |
||||
HWND hIE = NULL; |
||||
for (int i = 0; i < nArgs; i++) |
||||
{ |
||||
auto pIndex = StrStrNIW(ppArgList[i], pstrF12, nF12); |
||||
if (pIndex == NULL) |
||||
{ |
||||
continue; |
||||
} |
||||
std::wstring strWnd(pIndex + nF12); |
||||
std::reverse(strWnd.begin(), strWnd.end()); |
||||
hIE = reinterpret_cast<HWND>(StrToInt(strWnd.c_str())); |
||||
if (hIE && !::IsWindow(hIE)) |
||||
{ |
||||
hIE = NULL; |
||||
} |
||||
break; |
||||
} |
||||
|
||||
::LocalFree(ppArgList); |
||||
return hIE; |
||||
} |
||||
|
||||
int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPTSTR lpstrCmdLine, int nCmdShow) |
||||
{ |
||||
UNREFERENCED_PARAMETER(nCmdShow); |
||||
|
||||
int result = 0; |
||||
|
||||
auto hIE = IsF12DevTool(lpstrCmdLine); |
||||
if (hIE && AttachF12Tools(hIE)) |
||||
{ |
||||
return result; |
||||
} |
||||
return 0; |
||||
} |
||||
``` |
||||
|
||||
可以看到,新进程逻辑很简单,首先检测命令行中有没有`-windows10-f12`参数,有的话就调用`AttachTools`,没有的话则直接退出。`-windows10-f12`的值为IE窗口的句柄,是底层类名为`Internet Explorer_Server`的那个窗口句柄,这里使用了`std::reverse`进行字符串反转,因为360安全浏览器这样玩的,所以也算是致敬吧。 |
||||
|
||||
最后,就是主进程的`F12`响应,然后获取窗口句柄,当作参数传递给新进程,代码比较简单不贴了,记得一点就是,窗口句柄转换为字符串之后记得反转。 |
||||
|
||||
## 致谢 |
||||
|
||||
- [SuperF12](https://github.com/titilima/SuperF12)基本实现了`IEChooser.exe`做的事情,很厉害。 |
||||
- [360安全浏览器团队](https://browser.360.cn/),直接参考,或者说照抄了360安全浏览器的处理方式。 |
||||
Loading…
Reference in new issue