月度归档:2012年03月

文件沙箱系统设计与实现

前文回顾:
1. 『一种安全沙箱模型
2. 『SSDT Hook

以下进入正题:

1. 文件系统沙箱的设计

Native API是Windows平台中最底层的访问系统资源的函数。应用程序对文件系统的访问最终都要调用Native API里一组以Zw开头的函数。文件沙箱系统的实现原理是通过SSDT Hook技术截获这组API,分析并修改传入参数,把应用程序对文件系统中资源访问的路径进行重定向,从而使应用程序对系统中文件的读写操作限定在沙箱内,不对真实系统环境造成影响。图1展示了文件沙箱系统的结构框架,本系统由沙箱、SSDT Hook模块、主程序三部分组成。

sandbox-arch

图1 沙箱系统

沙箱系统中各模块功能如下:
(1) 沙箱: 沙箱是从文件系统中开辟出的一个子目录。沙箱系统启动后,应用程序试图对文件系统的修改操作都将被重定向到这里。
(2) SSDT Hook模块(Sandbox.sys): SSDT Hook模块以驱动程序的形式加载到操作系统的内核空间里,主要负责拦截Native API函数调用,分析传入参数,根据规则库制定的策略修改参数的值,使得程序对实际文件系统的资源访问重定向到沙箱中,从而达到保护真实系统环境的目的。
(3) 主程序(SbMainPanel.exe):主程序负责沙箱系统的启动与停止,配置沙箱目录,指定沙箱系统作用范围等等。

2. 文件系统沙箱的实现

Windows系统中涉及到文件操作的Native API有如下几个:ZwCreateFile()、ZwOpenFile()、ZwWriteFile()、ZwReadFile()、ZwSetInformationFile()、ZwQueryAttributesFile()。要实现沙箱系统必须拦截这几个API,并对它们参数列表中包含文件路径信息的传入参数进行修改。本小节会详细说明如何实现上述过程。为叙述方便,我们约定称沙箱中的目录为沙箱目录,文件系统中除沙箱目录以外的其他目录为原目录。

(1) 文件的打开、读/写和删除:应用程序要访问一个文件,首先必须调用ZwCreateFile()或ZwOpenFile()打开该文件。ZwCreateFile()和ZwOpenFile()函数的参数列表中有一个存储着待打开文件路径的结构体ObjectAttributes,分析并修改在这个结构体中存储的路径信息,使程序对文件系统中文件的访问重定向到沙箱目录中来,如果执行成功,会返回沙箱目录下该文件句柄。由于文件读函数ZwReadFile()、写函数ZwWriteFile()、删除函数ZwSetInformationFile()(注:使其最后一个参数FileInformationClass等于FileDispositionInformation)都是通过句柄实现对文件的操作,因此只要将返回的沙箱目录下文件句柄作为参数传入上述函数,那么程序读写、删除的对象都是沙箱目录下文件,原目录下文件并不受到影响。

(2) 文件重命名:若将ZwSetInformationFile()函数中最后一个参数FileInformationClass置为FileRenameInformation,该函数就执行文件重命名功能。修改后的文件名(含文件路径)存放在FILE_RENAME_INFORMATION结构体中,分析该结构体的成员变量FileName,如果该文件路径不在沙箱目录下,那么将它修改成沙箱目录下的文件路径。

(3) 文件信息查询:ZwQueryAttributesFile()函数的功能是根据文件路径查询文件信息。该函数参数列表中有一个存储着待查询文件路径的结构体ObjectAttributes,分析并修改在这个结构体中存储的路径信息,即可使ZwQueryAttributesFile()函数对原目录下文件信息查询转变为对沙箱目录下对应文件的信息查询。

通过SSDT Hook以上几种文件操作型Native API,在自定义替换函数中根据以上分析修改传入参数,即可完成沙箱系统关于重定向文件操作路径的功能。

--EOF--

奇怪的C语言数组形式

想起以前读『C陷阱与缺陷』时颠覆过我对数组认知的一个栗子: "0123456789"[n] 居然是一个合法的数组形式。

1
printf("%c", "0123456789"[0]);

以上代码运行后打印字符‘0’,依次类推,printf("%c", "0123456789"[1])打印字符‘1’,printf("%c", "0123456789"[2])打印字符‘2’……这种用法用来解决某些机器的字符集里数字不是顺序排列的问题。例如ASCII码表里字符‘0’-‘9’分别对应着编码0x30-0x39,但有些机器里不是这么按顺序排的……犹记得『K&R』也有提到某些架构的机器上不宜用c+‘0'这样方法来求c的数字表示,原因也是如此,不过具体哪些类型机器会采取这种策略就不得而知了。
  
C语言里,一个字符串常量可以用来表示一个字符数组,所以在数组名出现的地方都可以用字符串常量来替换。

--EOF--

SSDT Hook

1. Windows API调用机制

一般操作系统为保障系统安全,把程序的运行环境分为用户态和内核态两种级别,与此相对应,API也可分为两类,一类是用户态API,它们是供用户态应用程序直接调用的,这类API一般以动态链接库的形式导出。例如Windows的三大核心子系统kernel32.dll,user32.dll,gdi32.dll。另一类是内核态API,它们是供运行在内核态的程序(如设备驱动、内核进程等)调用的,这类API也称为Native API。从分层结构上看,内核态API比用户态API更加底层。图2所示的是用户态API与内核态API之间的依赖关系。

windows-api

图1 用户态API与内核态API的依赖关系

由图1可以看出,用户态API的实现依赖于内核态的Native API,它们之间通过ntdll.dll建立起联系。ntdll.dll处于用户态,它负责将Native API的接口“暴露”给应用程序,而这些Native API的实现是在内核态的ntoskrnl.exe里完成的。因此,ntdll.dll只是API调用从用户态通过软中断陷入到内核态的跳板。中断之前,ntdll.dll中被调函数把Native API的ID(系统服务调用号)和参数堆栈结构指针分别放入EAX和EDX寄存器中,中断陷入内核态后,如图2所示,中断处理函数KiSystemService()通过结构体KeServiceDescriptorTable获得系统服务分配表SSDT(System Service Dispatch Table)的基址,把EAX寄存器中的系统服务调用号当成索引从SSDT中获得该系统服务函数的函数地址,根据EDX寄存器中的参数堆栈结构指针获得参数列表,跳转到系统服务函数入口处继续执行,直至整个系统调用过程完成。

native-api-addressing

图2 内核态Native API的寻址过程

2. SSDT Hook

API拦截技术是指在API被调用时将其截断,插入开发者自己编写的代码,修改API执行代码的正常流程,从而能够改变或扩充API函数的功能。不同运行级别(用户态、内核态)下的API有不同的拦截方法。用户态API的拦截方法有静态挂钩和动态挂钩两种,其中动态挂钩方法又可细分为替换IAT函数地址、嵌入汇编代码、替换消息处理函数等,此外微软公司提供的Detours库也可实现动态拦截任意Win32 API。内核态API的拦截方法主要是SSDT Hook技术和Inline Function Hook两种方法。本文主要介绍SSDT Hook技术实现的内核态API拦截。

通过本文第1节的介绍,我们知道Native API的入口地址是中断处理函数KeSystemService()根据EAX寄存器中的ID值查找SSDT表得到的。若要拦截Native API,首先必须获得SSDT表的基址,修改SSDT表中存储的待拦截Native API的入口地址,使它指向我们自定义替换函数的入口地址。同时,保存原始的Native API入口地址,这么做的原因有两点:一、自定义替换函数中仍需要调用原始Native API,以实现系统调用功能。二、在Hook结束时需要恢复现场,还原SSDT。图3以拦截ZwCreateFile()为例说明了SSDT Hook的过程。SSDT中记录第0x25h号Native API ZwCreateFile()的入口地址为address37,经过Hook之后,我们把ZwCreateFile()的地址保存起来,将SSDT中的address37替换成自定义替换函数HookZwCreateFile()的入口地址,HookZwCreateFile()再根据已保存的address37来调用ZwCreateFile()来完成此次系统调用。

SSDT Hook

图3 SSDT Hook过程示意图

此外,自定义替换函数与被Hook的Native API必须有相同的参数类型、参数个数、函数返回类型,这样的约定可以保证SSDT Hook过程中正确保存寄存器内容,维持堆栈平衡。

--EOF--

VC6.0工程添加SQLite支持

项目中有个鸡肋功能要用到数据库,MySQL太重,Access,Excel又涉及版权问题,于是SQLite就成了首选。SQLite是一款支持标准SQL的非常轻型、便捷的开源数据库,在嵌入式开发中应用较多。

SQLite官网上提供了源代码、各平台下的库文件以及一些工具的下载。SQLite为Windows平台下的软件开发提供了一个动态链接库(sqlite3.dll)和其对应的定义文件(sqlite3.def),要在VC中使用,还需要sqlite3.lib这样一个静态lib文件,用在程序的编译阶段,这个sqlite3.lib文件需要手工调用VC自带的LIB.exe工具生成。在VC项目中添加SQLite支持的过程大致如下:

1. 从官网下载源程序(下载)和动态链接库(下载),目前版本号为3.7.10。将动态链接库中的sqlite3.def文件解压至某目录,例如C:\db\sqlite3.def。

2. 打开命令提示符,CD到VC的安装目录VC98\Bin目录下,如:

1
C:\>cd "C:\Program Files\Microsoft Visual Studio\VC98\Bin"

3. 运行以下命令:

1
C:\~Bin>LIB.EXE /out:C:\db\sqlite3.lib /MACHINE:IX86 /DEF:C:\db\sqlite3.def

上述命令表示,调用VC自带的LIB.EXE,根据(/DEF:参数指定)SQLite提供的定义文件sqlite3.def,自动生成(/out:参数指定)对应的sqlite3.lib文件。如果命令执行过程中报没找到mspdb60.dll这个错误的话,可从网上下载或其他位置拷贝该文件放在LIB.EXE的所在目录即可。如果命令执行成功,则会在C:\db目录下生成sqlite3.exp文件和sqlite3.lib文件。

4. 现已得到程序编译和运行所需的三个文件:sqlite3.h,sqlite3.lib,sqlite3.dll,前两个为编译所需,后一个为运行所需。首先在代码中将sqlite3.h文件包含进去,再导入sqlite3.lib文件。导入sqlite3.lib文件有两种方法,一是通过“工程”->“设置”,选择“连接”选项卡,在“对象/库模块”中添加sqlite3.lib。二是在代码中通过#pragma comment预编译语句引入,这种方法的好处是能让程序与工程文件(.dsp)解除耦合,方便移植。如下:

1
2
#include "sqlite3.h"
#pragma comment(lib, "sqlite3.lib")

至此,项目添加SQLite支持完毕,在代码中可任意调用sqlite3_open、sqlite3_exec、sqlite3_get_table等SQLite API。

5. 最后将sqlite3.dll文件放置在编译出的.exe文件的同级目录下即可。

--EOF--

『大象为什么不长毛』

1. 大象为什么不长毛
大象体积是狮子的30倍,产生的体热大约是狮子的30倍,但是皮肤总面积大约是狮子的10倍,还有20倍的热量需要设法散掉,所以不能像狮子一样长毛。与此类似,犀牛、河马也都没有体毛。此外,仅仅不长毛还不足以大象散掉足够热量,所以它们还长一对大耳朵,大而薄,也是用于散热。因为亚洲象、非洲森林象、非洲丛林象所处的环境气温由低到高,所以它们的耳朵大小也是由小到大的。作为对比,猛犸象因为生活在寒带,所以它们身披长毛,而且耳朵也很小。伯格曼法则认为,生活在寒冷地区的动物体型一般比生活在温暖地区的同类动物大。阿伦法则认为,寒冷地区的动物的耳朵等突出物一般较小。其根本原因都在于保温或散热。

2. 为什么婴儿逗人喜爱
婴儿可爱性的特征:大圆头,大而突出的额头,位置在头部的中线之下的大眼睛,短小、胖乎乎的四肢和手脚,圆胖的体型,柔软、有弹性的皮肤,肥嘟嘟的两颊,动作笨拙。这些特征会让大人不由自主萌生怜爱之心,情不自禁表现关爱之举,这些特征并非为了吸引大人才进化出来的,但是这些特征能在大人心中激发可爱的感觉,则是进化出来的。原因是,如果大人觉得他们的婴儿的这些特征很可爱,就会更愿意去保护、照顾他们,婴儿就能更好地生存下来,基因也能得到更好的传播。那些不觉得这些特征可爱的人的基因将难以遗传下来,而被淘汰。人们觉得熊猫、米老鼠、泰迪熊可爱,也是因为这些形象与婴儿类似,爱屋及乌。

3. 为什么夜空是黑暗的
1823年,奥伯斯提出一个佯谬:为什么夜空是黑暗的?如果宇宙是无限的,恒星均匀地布满天空,那么夜晚的天空也将和白天一样明亮。实际上,宇宙在时间上有一个起点,宇宙的年龄还没有捞到足以让我们见到所有远处恒星发出的光。计算表明,要把地球的夜空全部照亮,要花上以亿亿亿年计的时间,远处的星光才能都抵达地球。而且,由于宇宙在不断膨胀,空间膨胀导致光线波长被拉长,产生“红移”现象,所以,遥远的星光在抵达地球时,能量已经低得肉眼看不见了。由于宇宙太年轻,所以夜空是暗的;由于宇宙在膨胀,让夜空变得更暗。这就是“为什么夜空是黑暗的?”这个问题的答案。

4. 宝贝,宝贵的贝壳
宝贝的本义是宝贵的贝壳,是宝螺科贝类的统称。远古时期,它们是作为货币使用的,因为它们天然就是当货币的料:美丽、轻便、耐磨损,多数地方不生产,除了用作装饰没有实际用途,并且无法假冒。所以汉字里很多与财富有关的字都跟贝有关。

5. 没有阳光的生命世界
海面以下1000米,就几乎没有阳光,因此没有植物,但是生活着许多动物,即便在1万米深的马里亚纳海沟,也能看到鱼和虾,这些动物的食物源自浅海,比如浅海动物的粪便和尸体,或是深海热液口等等。

6. 人之初,爱美善
从科学角度看,美丽并不是什么神秘的东西,它意味着“普通”:如果把几百个人的脸混合在一起,将会出现一张非常美丽的脸。所以,一张美丽的脸其实是一张最典型的脸。有实验发现,新生儿对美脸感兴趣,实际上就是在辨认典型的人脸,这是他们与生俱来的本能,让他们一出生就能认识同类。不过,随着婴儿长大,他们会觉得抚养他们的父母的脸最有吸引力,逐渐意识到人不可全靠貌相。

7. 飞不用学,走也不用学
飞行是鸟类与生俱来的本能,只要翅膀长好了,就自然而然地会飞行,不用靠后天的学习,雏鸟在笨拙地扑打着翅膀实际上不是在学飞,而是在锻炼翅膀的肌肉。鸟不用学飞行,就像鱼不用学游泳,马不用学奔跑,人不用学走路一样。“狼孩”不会直立行走,只会像狼一样用四肢奔跑只是传说,无稽之谈,不足为凭。健康的婴儿即使没有机会学习走路,在其相关骨骼、肌肉发育好之后,也会自然而然地开始走路。父母“教”婴儿走路,实际上是帮其锻炼肌肉,促进获得走路的能力,缩短能够走路的时间。这是一个锻炼过程,而不是学习。

8. “神医”华佗的手术神话
国学大师陈寅格写过一篇论文,考证出华佗故事就是个神话故事,故事原型来自于印度教传说。华佗有两项世人皆知的"外科事迹":为关羽刮骨疗毒,向曹操建议用利斧劈开脑袋治疗头风病而惹下杀身之祸。这些都是『三国演义』的小说家言,连正史都算不上,不可信。并且,且不说开颅术治不了头风,在当时的医疗条件下,接受开颅术无异于自杀。科学家、医生、历史学家都应该有科学精神,切不可盲目相信“正史”记载,对那些违背常识和科学知识的记载更不能相信。有没有科学精神,也是国学大师与庸俗历史学家的区别所在。

9. 假如没有达尔文
拉马克等人虽然相信生物是进化而来的,但是他们把进化过程设想成是一个不断攀升的梯子,生物从低级到高级一步步往上爬,一直进化成位于梯子顶端的人。这种直线式、方向性的进化观念影响至今,受此影响,汉语当初才把evolution译成“进化”,而不是更恰当的“演化”。而达尔文则把生物进化过程设想成一颗不断地生长、分支的大树,现存的所有生物都位于这棵树的某个小分支的顶端,很难说哪一种更高级,在同时存在的生物种类之间做高低级的比较是没有意义的。进化没有预定的方向,进化树不存在一个以人类为顶端的主干,人类只是进化树上一个普通的分支。此外,达尔文提出自然选择时生物进化的主要机制,在他之前的“淘汰”学说仅涉及淘汰不良形态、保留最佳形态的稳定性选择。而达尔文认为:对优良性状的选择将会产生新的形态、新的物种。这个观点太过革命和大胆,因此饱受非议。

10. 当眼保健操成为传统
没有科学的理论、临床实验或调查统计证明做眼保健操确实能够预防近视。世界上只有中国在推行眼保健操,而中国学生的近视率却排世界第二。有多项调查表明,近距离看书或在昏暗的光线下看书并不会增加得近视的风险。近视的发生深受遗传因素的影响,环境因素(例如阅读)可能是近视的诱因。调查表明,受教育的程度与近视发生率存在相关性。眼保健操不能预防近视,但可能会有其他方面的好处,例如消除眼疲劳。当然也有坏处,经常用不洁的手接触眼睛容易造成眼睛感染。

11. 地震预测的梦想与现实
地震本质上时不可预测的。地球处于自组织的临界状态,任何微小的地震都有可能演变成大地震。这种演变是高度敏感的、非线形的,其初始条件不明,很难预测。如果要预测一个大地震,就要准确地知道大范围(不仅仅断层附近)的物理状况的所有细节,因此不可能。

12. 和地震“赛跑”
地震在地下震源发生后,释放出的能量以两种波的形式传播。一种是纵波(P波,压缩波),它让地面上下颠簸。另一种是横波(S波,剪切波),它让地面左右摇晃,横波比纵波强得多,振幅是纵波的3-10倍,是造成地震灾害的主要因素。横波传播速度为4公里/秒,纵波传播速度为7公里/秒,这个时间差就可以被用来进行预警。比如监测到地震纵波时,在横波到达之前的有限的十几秒时间内切断煤气、停止发电、停驶高速列车等,减少地震次生灾害。用这种方法进行预警,和地震“赛跑”,受制于地震波的速度,故可提升的余地很小。

13. 相对论有没有用
相对论主要应用于高速状态、微观世界和宇观世界,但是在日常生活在也有实用价值,例如全球定位系统(GPS)。GPS卫星在空中的位置是精心安排好的,任何时候在地球上的任何地点,都至少能见到其中的四颗。GPS导航仪通过比较从四颗GPS卫星发射来的时间信号的差异,计算出自身所在的位置。根据狭义相对论,当物体运动时,时间会变慢,运动速度越快,时间就越慢。因此在地球上看GPS卫星,它们携带的时钟会走得比较慢,用狭义相对论的公式可计算出,每天慢大约7微秒。根据广义相对论,物质质量的存在会造成时空的弯曲,质量越大,距离越近,时空就弯曲得越厉害,时间则会越慢。因此,地球表面的时空要比GPS卫星所在的时空更加弯曲,这样,从地球上看,GPS卫星上的时钟走得比较快,用广义相对论的公式可计算出,每天快大约45微秒。通过狭义和广义相对论的计算,GPS卫星时钟每天要快上大约38微秒,因此,卫星发射前要调整原子钟时钟频率,卫星运行过程中也需要根据其所在轨道进行调整。

--EOF--