这篇谈下c++如何hook网页中的JS函数,即网页可以执行我们修改的JS函数。
相应的步骤可分为:
1.找到需要修改函数的时机。
2.得到需要修改函数的com对象。
3.将我们新的com对象替换修改函数。
第一步,找到需要修改函数的时机,在谈这个问题之前,需要搞清楚JS执行发生在什么时候。
我们知道在网页加载过程中,会根据接收到的html文本,去解析对应里面的脚本和样式以及js。
譬如:
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>了解html页面的渲染过程 - yuezk - 博客园</title>
<link type="text/css" rel="stylesheet" href="/bundles/blog-common.css?v=Rdf1BBttS5_qVaET1myrajVTd62BSCCoJA9fZxGv1ZM1">
<link id="MainCss" type="text/css" rel="stylesheet" href="/skins/LessIsMoreRight/bundle-LessIsMoreRight.css?v=XnHJrmT6UJMtyGfeJjiTUm7BxKWcwdJrxKsGy7z3YZ81">
<link id="mobile-style" media="only screen and (max-width: 768px)" type="text/css" rel="stylesheet" href="/skins/LessIsMoreRight/bundle-LessIsMoreRight-mobile.css?v=9qDppl1UU68AUflWXI5a_NeoqamVC_84o7AG1HNc4Pg1">
<link title="RSS" type="application/rss+xml" rel="alternate" href="http://www.cnblogs.com/yuezk/rss">
<link title="RSD" type="application/rsd+xml" rel="EditURI" href="http://www.cnblogs.com/yuezk/rsd.xml">
<link type="application/wlwmanifest+xml" rel="wlwmanifest" href="http://www.cnblogs.com/yuezk/wlwmanifest.xml">
<script type="text/javascript" src="http://common.cnblogs.com/script/encoder.js"></script><script src="//common.cnblogs.com/script/jquery.js" type="text/javascript"></script>
<script type="text/javascript">var currentBlogApp = 'yuezk', cb_enable_mathjax=false;var isLogined=false;</script>
<script src="/bundles/blog-common.js?v=hH1lCMV8WaIu271Nx7jPuv36TENW9-RsSxziLxUpjtc1" type="text/javascript"></script>
</head>
在网页加载渲染过程中,本地会逐行解析脚本,css和绘制不是我们这里讨论的,略过,当执行到<scrpt>一行时,网页会停止创建DOM树,开始加载对应的js,加载过程就是把encoder.js和jquery.js里面的对象(也包括函数)会一并创建。如果我们需要修改的js函数在就是在类似的这样的头里面创建的,那么很简单了,我们只要找一个加载点没执行修改函数就行,然后执行我们的第二步。在MFC中,一般考虑OnDocumentComplete函数作为我们修改函数的时机。具体函数实现如下:
void CWebLoginDlg::OnDocumentComplete(LPDISPATCH pDisp, LPCTSTR szUrl)
{
CDHtmlDialog::OnDocumentComplete(pDisp, szUrl);
// TODO: Add your specialized code here and/or call the base class
IUnknown* pUnk;
LPDISPATCH lpWBDisp;
HRESULT hr;
pUnk = m_wndBrowser.GetControlUnknown();
ASSERT(pUnk);
hr = pUnk->QueryInterface(IID_IDispatch, (void**)&lpWBDisp);
ASSERT(SUCCEEDED(hr));
CComPtr<IHTMLDocument2> sphtmlDoc;
GetDHtmlDocument(&sphtmlDoc);
if (sphtmlDoc != NULL)
{
CWebPage web;
VARIANT testV;
web.SetDocument(sphtmlDoc);
CComPtr<IDispatch> pDispatch = NULL;
web.GetJScript(pDispatch);
HRESULT result = GetProperty(pDispatch, L"TK_installPage", &testV); //得到修改函数的com对象
if (result == S_OK)
{
VARIANT params;
params.vt = VT_DISPATCH;
params.pdispVal = new JsFunction(button1_onclick);
result = SetProperty(pDispatch, L"TK_installPage", ¶ms);//将我们的button1_onclick函数替换修改函数
}
}
if (pDisp == lpWBDisp )
{
// Top-level Window object, so document has been loaded
TRACE("Web document is finished downloading\n");
}
lpWBDisp->Release();
第二步,得到修改函数的com对象。这里TK_installPage函数就是我们的要修改的函数。首先获取doc对象,然后根据doc对象通过GetJScript获得脚本对象,再次在脚本对象上通过名字TK_installPage得到js对象。具体代码如下:
bool CWebPage::SetDocument(IDispatch* pDisp)
{
CHECK_POINTER(pDisp);
m_spDoc = NULL;
CComPtr<IDispatch> spDisp = pDisp;
HRESULT hr = spDisp->QueryInterface(IID_IHTMLDocument2,(void**)&m_spDoc);
if(FAILED(hr))
{
ShowError(L"Failed to get HTML document COM object");
return false;
}
return true;
}
bool CWebPage::GetJScript(CComPtr<IDispatch>& spDisp)
{
CHECK_POINTER(m_spDoc);
HRESULT hr = m_spDoc->get_Script(&spDisp);
ATLASSERT(SUCCEEDED(hr));
return SUCCEEDED(hr);
}
DISPID CWebLoginDlg::FindId( IDispatch *pObj, LPOLESTR pName )
{
DISPID id = 0;
if(FAILED(pObj->GetIDsOfNames(IID_NULL,&pName,1,LOCALE_SYSTEM_DEFAULT,&id))) id = -1;
return id;
}
HRESULT CWebLoginDlg::GetProperty( IDispatch *pObj, LPOLESTR pName, VARIANT *pValue )
{
DISPID dispid = FindId(pObj, pName);
if(dispid == -1) return E_FAIL;
DISPPARAMS ps;
ps.cArgs = 0;
ps.rgvarg = NULL;
ps.cNamedArgs = 0;
ps.rgdispidNamedArgs = NULL;
return pObj->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET, &ps, pValue, NULL, NULL);
}
首先需要构造我们新的com对象,
typedef void _stdcall JsFunction_Callback();
class JsFunction:public IDispatch
{
long _refNum;
JsFunction_Callback *m_pCallback;
public:
JsFunction(JsFunction_Callback *pCallback)
{
_refNum = 1;
m_pCallback = pCallback;
}
~JsFunction(void)
{
}
public:
// IUnknown Methods
STDMETHODIMP QueryInterface(REFIID iid,void**ppvObject)
{
*ppvObject = NULL;
if (iid == IID_IOleClientSite) *ppvObject = (IOleClientSite*)this;
else if (iid == IID_IUnknown) *ppvObject = this;
if(*ppvObject)
{
AddRef();
return S_OK;
}
return E_NOINTERFACE;
}
STDMETHODIMP_(ULONG) AddRef()
{
return ::InterlockedIncrement(&_refNum);
}
STDMETHODIMP_(ULONG) Release()
{
::InterlockedDecrement(&_refNum);
if(_refNum == 0)
{
delete this;
}
return _refNum;
}
// IDispatch Methods
HRESULT _stdcall GetTypeInfoCount(
unsigned int * pctinfo)
{
return E_NOTIMPL;
}
HRESULT _stdcall GetTypeInfo(
unsigned int iTInfo,
LCID lcid,
ITypeInfo FAR* FAR* ppTInfo)
{
return E_NOTIMPL;
}
HRESULT _stdcall GetIDsOfNames(
REFIID riid,
OLECHAR FAR* FAR* rgszNames,
unsigned int cNames,
LCID lcid,
DISPID FAR* rgDispId
)
{
//令人费解的是,网页调用函数的call方法时,没有调用GetIDsOfNames获取call的ID,而是直接调用Invoke
return E_NOTIMPL;
}
HRESULT _stdcall Invoke(
DISPID dispIdMember,
REFIID riid,
LCID lcid,
WORD wFlags,
DISPPARAMS* pDispParams,
VARIANT* pVarResult,
EXCEPINFO* pExcepInfo,
unsigned int* puArgErr
)
{
//这里执行我们的替换函数
m_pCallback();
return S_OK;
}
};
static void _stdcall button1_onclick()
{
ATLTRACE("test");
}
其次,通过com对象的invoke函数进行替换。代码如下:
HRESULT CWebLoginDlg::SetProperty( IDispatch *pObj, LPOLESTR pName, VARIANT *pValue )
{
DISPID dispid = FindId(pObj, pName);
if(dispid == -1) return E_FAIL;
DISPID dispidNamed = DISPID_PROPERTYPUT;
DISPPARAMS ps;
ps.cArgs = 1;
ps.rgvarg = pValue;
ps.cNamedArgs = 1;
ps.rgdispidNamedArgs = &dispidNamed;
return pObj->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYPUT, &ps, NULL, NULL, NULL);
}
最终效果,在网页在执行TK_installPage函数时候,实际上是执行的我们的函数button1_onclick。