标题: [转贴] VBScript脚本字符串的内部实现 [打印本页]
作者: find 时间: 2012-2-26 11:59 标题: VBScript脚本字符串的内部实现
最近对 VBS 字符串 Chr(0) 注①截断讨论得比较多,看来有必要介绍一下 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://www.jb51.net/article/29358.htm
作者: Demon 时间: 2012-2-26 12:11
垃圾脚本之家
http://demon.tw/programming/vbs-string-internal.html
欢迎光临 批处理之家 (http://bbs.bathome.net/) |
Powered by Discuz! 7.2 |