模拟网页行为之实践四

这篇谈下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对象替换修改函数。

首先需要构造我们新的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。





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值