借助动态代码生成技术在基于Webkit引擎的HTML5网页JS内调用易语言函数

作者:庄晓立(Liigo)

日期:2015年3月3日夜(2017年4月更新,详见文中和文末说明)

原创链接:http://blog.csdn.net/liigo/article/details/44045177

版权所有,转载请注明出处:http://blog.csdn.net/liigo


前两天我协助朋友解决了一个技术问题,在此稍作记录和总结。

具体来说,就是在使用基于Webkit引擎的封装组件wke的过程中,需要把一个易语言函数注册给JavaScript引擎,让它可以在网页里被调用(就像在网页里调用普通JavaScript函数一样)。如果能做到这一点,就基本实现了从JavaScript传递参数到易语言、易语言返回值给JavaScript的双向沟通机制,以后有广泛的应用空间。

在整体思路上,还是蛮简单的。因为wke已经提供了颇为直观的接口函数(虽然严重缺乏文档):

#define JS_CALL __fastcall
typedef jsValue (JS_CALL *jsNativeFunction) (jsExecState es);

WKE_API void jsBindFunction(const char* name, jsNativeFunction fn, unsigned int argCount);
WKE_API void jsBindGetter(const char* name, jsNativeFunction fn); /*get property*/
WKE_API void jsBindSetter(const char* name, jsNativeFunction fn); /*set property*/

WKE_API int jsArgCount(jsExecState es);
WKE_API jsType jsArgType(jsExecState es, int argIdx);
WKE_API jsValue jsArg(jsExecState es, int argIdx);
......

这里面最核心的函数是 jsBindFunction(),调用它就能注册一个新的JavaScript函数,只需提供函数名、实现回调函数、参数个数。在回调函数内部,通过 jsArgCount/jsArgType/jsArg 读取js传进来的参数,通过其他一些接口函数创建js值对象,都是一目了然的事情,这都不是事儿。

回调函数(fastcall)

首先卡在该回调函数的调用约定上:jsBindFunction的第二个参数,要求是 fastcall 调用约定的回调函数!可是易语言编译器根本就不支持编译生成fastcall调用约定的函数呀(仅支持stdcall)。fastcall 约定通过寄存器 ecx 和 edx 传递前两个参数,其余参数按照从右向左(从后往前)的顺序压栈,被调用者负责清理、平衡栈。这跟stdcall有一些类似但又明显不同。如果不管三七二十一盲目传递 stdcall 调用约定的回调函数进去,程序运行时非崩溃不可。

那怎么办呢?易语言编译器不支持fastcall,我们只好自食其力,纯手工生成二进制X86机器指令,人肉编译生成符合fastcall调用约定的回调函数。该函数声明的原型是:jsValue (__fastcall *jsNativeFunction) (jsExecState es),唯一个参数可从 ecx 寄存器中读取,没有入栈的参数,因而也不用平衡栈,直接 ret 就完事了。为了方便起见,我们引入两个易语言编写的函数:代理函数和用户函数,其中代理函数负责JS和易语言的类型转换,用户函数负责具体的执行逻辑,这两个函数毫无疑问都只能是stdcall调用约定(易语言编译器也不支持别的什么约定嘛)。下面设计我们的回调函数结构,以伪汇编代码来表示:

PUSH 用户函数地址
PUSH ecx
MOV eax, 代理函数地址
CALL eax
RET
这些伪汇编代码,要是用易语言写的话,其实就是一句话:返回(代理函数(es,用户函数))。(注:参数es是JavaScript引擎通过ecx寄存器传递进来的透明数据。)

易语言代码固然是简单,但因为编译器的限制,我们不能这么写。汇编代码稍微复杂一点,但我们仍然不能直接嵌入汇编(易语言编译器不支持)。只能手写机器码!把Intel指令集手册拿出来,查表,开工。既然是动态生成代码,当然需要先申请一块内存,然后把机器码填进去,然后把这块内存的首地址返回——这个内存的首地址也就是我们人肉编译生成的符合fastcall调用约定的回调函数的首地址。具体代码如下:



2017年4月Liigo更新:发现在Windows Server 2008系统下,“申请内存”申请到的内存区域不具有可执行权限,一尝试执行程序就崩溃了。

解决办法是将下面这一行代码:

函数体 = 申请内存 (32, 真)

替换为如下代码:

' Liigo 20170322
' 确保申请的内存具有可执行权限,否则在Windows Server 2008等系统下执行失败(access violation when executing)
' https://www.corelan.be/index.php/2010/06/16/exploit-writing-tutorial-part-10-chaining-dep-with-rop-the-rubikstm-cube/
函数体 = VirtualAlloc (0, 32, 十六进制 (“1000”), 十六进制 (“40”))  ' MEM_COMMIT(0x1000), PAGE_EXECUTE_READWRITE(0x40)。可不释放。
其中的VirtualAlloc是Windows API函数。


代理函数(stdcall)和用户函数(stdcall)

前面提到的代理函数,是一个很普通的易语言函数(stdcall),它负责解读JavaScript传递进来的参数,转换成易语言数据类型,转调易语言版的用户函数(也是stdcall),最后再把易语言用户函数的返回值转换为JavaScript类型后返回给JavaScript引擎。它接收两个参数,都是我们前面手工生成的回调函数传递进去的。代码如下:


代理函数的返回值是长整数型,也就是64位整数。根据 jsValue 的定义,它是64位指针,恰好可以用易语言的长整数表示。

JavaScript文本确定是UTF-8编码,转换到易语言文本之前,最好先执行编码转换(UTF-8 => GB18030),否则中文乱码。这一步骤非常简单,就作为课后作业吧。

我们完全可以改进这个代理函数,或者写另外一个代理函数,用于支持不同类型的用户函数(例如不同的参数类型和参数个数以及返回值类型)。

剩下的用户函数就更简单了,下面只是一个常规的示例(后面的测试代码就用到此函数):


把易语言函数注册为JavaScript函数

动态生成一个回调函数,作为参数传递给jsBindFunction即可:


函数调用次序总结

到了该总结一下的时候了:我们借助动态代码生成技术,在运行时生成一个符合fastcall调用约定的回调函数(jsNativeFunction),通过jsBindFunction将其注册到Javascript引擎,同时赋予其一个JavaScript函数名。网页脚本调用此JS函数时,回调函数被调用,进而回调函数又调用了代理函数,代理函数又调用了用户函数,用户函数返回后,返回值又被逐层返回给JS引擎。

测试代码

首先注册一个测试用的JS函数,易语言代码:注册JS函数("plus1",用户函数示例)。
再先来一段HTML,加载到浏览器中:
<a href='#' οnclick="document.getElementById('result').value=plus1('liigo');">link</a>
<p>
<textarea rows='6' cols='36' id='result'>hello</textarea>
当点击网页中的链接时,之前注册的JS函数 plus1 将被执行,进而易语言函数 用户函数示例 被调用。易函数返回的文本,成了 plus1 的返回值,最终输出到网页内的编辑框中。如果编辑框中文本显示为“liigo hohoho”,说明测试成功。

写在最后的思考

能否将前面的实现方案用易语言内置函数 “置入代码” 替换?我(Liigo)想,至少有两个阻力妨碍我们在此应用置入代码:1、置入代码只能作用于已经存在的函数,而不能运行时动态生成新的函数;2、置入代码是编译时行为,置入的代码不能包含可变量(例如上文的 CALL eax 恐怕就行不通)。勉强应用置入代码,也不是不行,只是会让每一个用户函数都非常复杂,既包含了置入代码,又包含了JS和易语言的类型转换,还包含了业务逻辑,没有任何封装性可言,易用性约等于零。


全文完。谢谢收看!知道我是谁吗?







































大名鼎鼎的御前四品带刀护士!



----------


2017年4月Liigo更新:

碰巧了,前一阵子我又用到WKE/Webkit。找了一圈也没找到之前备份的源代码,不知道丢哪里去了;只找到本文,照着文中图片上的代码,抄了一遍,你别说,还真管用。顺便说句题外话,话说当年,两年前,2015年3月,我正式发表本文没过几分钟,就有易友照着图片把代码打出来了。说回正题,这次重抄一遍代码,还发现了一个问题并解决掉了,详见文中“回调函数(fastcall)”一节。


发布了275 篇原创文章 · 获赞 442 · 访问量 244万+

猜你喜欢

转载自blog.csdn.net/liigo/article/details/44045177
今日推荐