最近对 VBS 字符串 Chr(0) 注①截断讨论得比较多,看来有必要介绍一下 VBS 字符串的内部实现。Demon 友情提示:本文需要一些 C 语言和 Windows 编程的知识,VBScript 初学者慎入。

VBS 是基于微软的 ActiveX/COM 技术实现的,而 COM 对象为了做到支持任何语言,定义了一系列通用的数据类型,微软称之为自动化对象类型(Automation data types),其中之一就是 BSTR。VBS 在内部是以 BSTR 来表示字符串的,BSTR 在 WTypes.h 中定义:
复制代码 代码如下:
typedef wchar_t WCHAR;
typedef WCHAR OLECHAR;
typedef OLECHAR *BSTR;

从定义可以看出,BSTR 是指向 wchar_t 类型(也就是 C 语言中的 Unicode)的指针,但是 BSTR 并不是普通的 wchar_t 指针。标准 BSTR 指向一个有长度前缀和 NUL 结束符的 wchar_t 数组。BSTR 的前4字节是一个表示字符串长度的前缀。BSTR 长度域的值是字符串的字节数,并且不包括 NUL 结束符。常用的 BSTR 处理函数请参考 MSDN 文档。

理论说的有点抽象,下面用代码来说明:
复制代码 代码如下:
str = "Hello" & Chr(0) & "world"

这是一句很简单的 VBS 代码,但是 VBScript 解释器在内部做了什么呢?其实就是初始化了一个 BSTR 变量(不考虑字符串连接过程):
复制代码 代码如下:
/* 仅仅为了演示,实际代码肯定不是这样的 */
BSTR str = SysAllocStringLen(L"Hello\0world", 11);为了更清楚地了解 BSTR 的结构,我们换一种写法:

/* BSTR 包含长度前缀,但是却实际指向第一个字符 */
wchar_t arr[] = {22,0,'H','e','l','l','\0','w','o','r','l','d','\0'};
BSTR str = &arr[2];这个 BSTR 在内存中的结构为:

00000000 16 00 00 00 48 00 65 00 6C 00 6C 00 6F 00 00 00
00000010 77 00 6F 00 72 00 6C 00 64 00 00 00

橙色表示四个字节的长度前缀。红色高亮表示 BSTR 指针的当前指向,蓝色高亮表示字符串中的 Chr(0) 字符,绿色高亮表示 BSTR 的结束字符 NUL(该字符是 SysAllocStringLen 函数加上去的,因为是 Unicode,所以要占两个字节)。也就是说,如果不考虑前面四个字节,BSTR 就是 C 语言中的 null-terminated string。

再看一段 VBS 代码:

MsgBox Len(str)用 MsgBox 来显示刚才定义的字符串长度,VBScript 解释器内部又做了什么呢?是不是像 C 语言标准库函数 strlen 一样,遍历整个字符串,以 NUL 作为字符串结束的标识呢?
复制代码 代码如下:
/* C语言 strlen 函数的简单实现 */
size_t strlen (const char * str)
{
const char *eos = str;
while( *eos++ ) ;
return( (int)(eos - str - 1) );
}

答案显然是否定的,因为字符串中含有 Chr(0),如果像 strlen 这样实现,那么就会被 Chr(0) 截断,Len 函数应该返回5才对,然而实际上返回的是11这个正确的数字。

VBS 的 Len 函数内部应该是这么实现的:
复制代码 代码如下:
/* 同上,仅为演示 */
size_t Len(const BSTR str)
{
return SysStringLen(str);
}

或者不调用 Windows API,由于 BSTR 前4个字节前缀表示字符串的字节数(不包括结尾的 BUL 字符),所以只要移动一下指针就行了:
复制代码 代码如下:
/* 强制转换成int指针减一后读取,然后除以2(一个Unicode字符两字节) */
size_t Len(const BSTR str)
{
return *((int *)str - 1) / 2;
}

可以看出,由于 BSTR 的长度可以通过前缀取得,并不需要以 NUL 来作为字符串结束符,也就是说,VBS 字符串是 binary safe (二进制安全)的。

那么为什么下面的代码只能显示 Hello 呢?

MsgBox str这看起来好像和上面说的矛盾,其实不然。VBS 字符串的确是兼容 Chr(0) 字符的,MsgBox 之所以会被 Chr(0) 截断,是因为 MsgBox 在内部调用了 MessageBox 函数,而该函数是以 NUL 作为字符串结束符的。
复制代码 代码如下:
/* 简单起见只实现一个参数
* MessageBox 的第二个参数是以 NUL 作为结束符的
* Pointer to a null-terminated string that contains the message to be displayed.
* 所以 VBS 字符串中包含的 Chr(0) 会把字符串截断
*/
int MsgBox(const BSTR str)
{
return MessageBoxW(NULL, str, L"", 0);
}

也就是说,如果 VBS 内置的函数或者 COM 组件的某些方法在其内部实现中调的 Windows API 的字符串参数是以 NUL 作为结束符的话,就会被 Chr(0) 字符截断。

现在再去看《ASP/VBScript中CHR(0)的由来以及带来的安全问题》、《ASP上传漏洞之利用CHR(0)绕过扩展名检测脚本》、《ASP缺陷—-一个特殊字符chr(0)》、《用Python脚本写ASP页面》,应该就不会有疑问了吧。

时间关系就不再展开了,如果你想了解更多关于 COM 组件的知识,我推荐你拜读一下 Jeff Glatt 的神作《COM in plain C》。

仅以此文回答雨中风铃的问题。

注①:本文中 Chr(0) 和 NUL 交替使用,表示同一个意思。

原文: http://demon.tw/programming/vbs-file-unicode.html

标签:
VBS,内部实现

免责声明:本站文章均来自网站采集或用户投稿,网站不提供任何软件下载或自行开发的软件! 如有用户或公司发现本站内容信息存在侵权行为,请邮件告知! 858582#qq.com
白云城资源网 Copyright www.dyhadc.com

评论“VBS字符串的内部实现”

暂无“VBS字符串的内部实现”评论...

《魔兽世界》大逃杀!60人新游玩模式《强袭风暴》3月21日上线

暴雪近日发布了《魔兽世界》10.2.6 更新内容,新游玩模式《强袭风暴》即将于3月21 日在亚服上线,届时玩家将前往阿拉希高地展开一场 60 人大逃杀对战。

艾泽拉斯的冒险者已经征服了艾泽拉斯的大地及遥远的彼岸。他们在对抗世界上最致命的敌人时展现出过人的手腕,并且成功阻止终结宇宙等级的威胁。当他们在为即将于《魔兽世界》资料片《地心之战》中来袭的萨拉塔斯势力做战斗准备时,他们还需要在熟悉的阿拉希高地面对一个全新的敌人──那就是彼此。在《巨龙崛起》10.2.6 更新的《强袭风暴》中,玩家将会进入一个全新的海盗主题大逃杀式限时活动,其中包含极高的风险和史诗级的奖励。

《强袭风暴》不是普通的战场,作为一个独立于主游戏之外的活动,玩家可以用大逃杀的风格来体验《魔兽世界》,不分职业、不分装备(除了你在赛局中捡到的),光是技巧和战略的强弱之分就能决定出谁才是能坚持到最后的赢家。本次活动将会开放单人和双人模式,玩家在加入海盗主题的预赛大厅区域前,可以从强袭风暴角色画面新增好友。游玩游戏将可以累计名望轨迹,《巨龙崛起》和《魔兽世界:巫妖王之怒 经典版》的玩家都可以获得奖励。