VBS字符串的内部实现

最近对 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

(0)

相关推荐

  • java中Servlet处理乱码的方法

    今天在部署一个webservices程序的时候,从页面获取数据的servlet出现了乱码问题,在servlet中我已经把request.setCharacterEncoding("GB2312");这段文字加入到代码中去,但是还是出现乱麻问题. 到网上找资料,如下:JAVA是Unicode编码,你先转换成ISO8859-1,然后再转换成GBK或是GB2312. java 代码 复制代码 代码如下: request.setCharacterEncoding("ISO8859-1

  • Zend Studio 实用快捷键一览表(精心整理)

    注:本文省略"通用快捷键"描述,诸如:ctrl+N=新建,ctrl+O=打开,ctrl+C=复制,ctrl+V,ctrl+X--等等几乎所有软件都通用的一组快捷键,而着重介绍zde独有的快捷键,了解并灵活运用这些快捷键以后,一定能帮助您在实际的项目操作中更加游刃有余.(怎么越写越觉得我是在做游戏秘籍,哈哈!) 编辑功能 组合键 实现功能 适用条件 Ctrl+/ 单行注释.当前为php代码时,则在光标所在行添加双斜杠行注释,选择多行则每一行都添加双斜杠:而当代码为html时则在行前后添加

  • zend studio 快捷键收集

    编辑功能快捷键 Ctrl + / 单行注释 Ctrl + Shift + / 块注释 Ctrl + U 选择的代码片段全部转换为大写 Ctrl + L 选择的代码片段全部转换为小写 Ctrl + D 复制光标所在行 Ctrl + E 删除光标所在行 Tab 增加代码缩进 Shift + Tab 减少缩进 Ctrl + Alt+ F 在文件中查找 Ctrl + BackSpace 删除光标前一个单词或一个符号 Ctrl + G 转到行 Alt + → 定位到光标的下一个位置 Alt + ← 定位到

  • zend stdio8.0 快捷键汇总集合

    对于任何一款IDE,都设置了很多快捷方式,比如说,快速查找,代码多行缩进,删除缩进等等,都提供了一些快捷键,只要你了解了这些快捷键,编码速度肯定会有大大的提高.接下来我们就介绍一下Zend Stdio 8.0版本的快捷键. F3 快速跳转到当前所指的函数,常量,方法,类的定义处,相当常用.当然还可以用Ctrl+鼠标左键win+D 显示桌面(可切回来)shift+end 此行第一个到最后一个shift+home 此行最后一个到第一个 Ctrl+home 文件头Ctrl+end 文件尾Ctrl+1

  • VBS字符串的内部实现

    最近对 VBS 字符串 Chr(0) 注①截断讨论得比较多,看来有必要介绍一下 VBS 字符串的内部实现.Demon 友情提示:本文需要一些 C 语言和 Windows 编程的知识,VBScript 初学者慎入. VBS 是基于微软的 ActiveX/COM 技术实现的,而 COM 对象为了做到支持任何语言,定义了一系列通用的数据类型,微软称之为自动化对象类型(Automation data types),其中之一就是 BSTR.VBS 在内部是以 BSTR 来表示字符串的,BSTR 在 WTy

  • vbs字符串分割函数

    复制代码 代码如下: Function CutAndJoin(sSource, iLong, sJoiner) Dim I, N N = Len(sSource) / iLong If(N <> Fix(N))Then N = Fix(N) + 1 For I = 0 To N - 1 CutAndJoin = CutAndJoin & Mid(sSource, I * iLong + 1, iLong) & sJoiner Next If(N > 0)Then CutA

  • vbs字符串操作小考题

    str="a|b|c|d" 要求写一段小程序得出: a|b|c|d b|c|d|a c|d|a|b d|a|b|c 这样的结果,str长度未定,格式是上边的格式. 实现代码如下: 复制代码 代码如下: str="a|b|c|d" Call Sort(str, "|") Function Sort(sSource, sDelimiter) Dim I, J, N, sItems, sTemp sItems = Split(sSource, sDel

  • JavaScript中将一个值转换为字符串的方法分析[译]

    译者注:前两天在看ES5的时候顺便出了一道题,今天看到这篇文章,刚好解释的很清楚,就翻译了一下.在JavaScript中,主要有三种方法能让任意值转换为字符串.本文讲解了每种方法以及各自的优缺点. 1.转换字符串的三种方法 这三种将value转换为字符串的方法是: 1.value.toString() 2."" + value 3.String(value) 第一种方法存在的问题是,它不能把null和undefined转换为字符串.还有第二种和第三种方法,这两种方法的效果基本一样. •

  • vbs中将GB2312转Unicode的代码

    今天写了一个类似于下面的程序: 复制代码 代码如下: Dim http Set http = CreateObject("msxml2.xmlhttp") http.open "GET","http://www.sina.com.cn/",False http.send WScript.Echo http.responseText 但是却发现返回的中文都是乱码,看了一下发现新浪的编码竟然是gb2312的,汗,现在都是utf-8编码的时代了.res

  • 解析JavaScript中的字符串类型与字符编码支持

    定义 字符串就是零个或多个排在一起的字符,放在单引号或双引号之中. 'abc' "abc" 单引号字符串的内部,可以使用双引号.双引号字符串的内部,可以使用单引号. 'key = "value"' "It's a long journey" 上面两个都是合法的字符串. 如果要在单引号字符串的内部,使用单引号(或者在双引号字符串的内部,使用双引号),就必须在内部的单引号(或者双引号)前面加上反斜杠,用来转义. 'Did she say \'Hell

  • Python字符串对象实现原理详解

    在Python世界中将对象分为两种:一种是定长对象,比如整数,整数对象定义的时候就能确定它所占用的内存空间大小,另一种是变长对象,在对象定义时并不知道是多少,比如:str,list, set, dict等. >>> import sys >>> sys.getsizeof(1000) 28 >>> sys.getsizeof(2000) 28 >>> sys.getsizeof("python") 55 >&

  • Java String 字符串常量池解析

    作为最基础的引用数据类型,Java 设计者为 String 提供了字符串常量池以提高其性能,那么字符串常量池的具体原理是什么,我们带着以下三个问题,去理解字符串常量池: 字符串常量池的设计意图是什么? 字符串常量池在哪里? 如何操作字符串常量池? 字符串常量池的设计思想 字符串的分配,和其他的对象分配一样,耗费高昂的时间与空间代价,作为最基础的数据类型,大量频繁的创建字符串,极大程度地影响程序的性能 JVM为了提高性能和减少内存开销,在实例化字符串常量的时候进行了一些优化 为字符串开辟一个字符串

  • GO语言入门学习之基本数据类型字符串

    目录 字符串 字符串转义符 byte和rune类型 修改字符串 类型转换 总结 字符串 Go语言中的字符串以原生数据类型出现. Go 语言里的字符串的内部实现使用UTF-8编码. 字符串的值为双引号(")中的内容,可以在Go语言的源码中直接添加非ASCII码字符 GO语言中字符串是用双引号包裹的 GO语言中单引号包裹的是字符 // 字符串 s := "Hello 中国" // 单独的字母.汉字.符合表示一个字符 c1 := 'h' c2 := '1' c3 := '中' //

  • JavaScript中Object.prototype.toString方法的原理

    在JavaScript中,想要判断某个对象值属于哪种内置类型,最靠谱的做法就是通过Object.prototype.toString方法. var arr = []; console.log(Object.prototype.toString.call(arr)) //"[object Array]" 本文要讲的就是,toString方法是如何做到这一点的,原理是什么. ECMAScript 3 在ES3中,Object.prototype.toString方法的规范如下: 15.2.

随机推荐