一种基于系统源代码搜索隐通道的信息流分析方法

文档序号:6458692阅读:294来源:国知局
专利名称:一种基于系统源代码搜索隐通道的信息流分析方法
技术领域
本发明是属于信息安全技术领域,涉及一种信息流分析技术应用于系统进行隐通道 分析的方案,具体地说涉及一种基于系统源代码搜索隐通道的信息流分析方法。
背景技术
自隐通道问题提出以来,各国的信息安全专家,以及计算机、数学、通讯等方面的 相关学者通过广泛地研究发现,隐通道在信息安全方面特别是信息的机密性泄露方面具 有一定的危害性。搜索出系统中存在的隐通道是进行隐通道分析工作的前提和基础。在 众多隐通道搜索方法中,信息流分析法较为引人关注。
信息流分析法是最早提出的隐通道搜索方法,分为语法信息流方法和语义信息流方 法。Denning的信息流格模型是语法信息流方法的典型代表。此方法的核心思想是,任 何对系统安全策略的违反(包含隐通道),都将体现为对"格(Lattice)"定义的破坏。 通常,语法信息流分析方法的分析步骤是(1)将信息流附加在每条语句之后。以赋值 语句a:-Z)为例,产生由6到a的信息流,用a — 6表示。(2)定义安全信息流策略,如 果有信息流a —6,则"的安全级必须支配6的安全级。(3)将流策略应用于形式化顶级
描述或源代码,生成信息流公式,例如":=6语句的流公式为^(")2 5*"6),其中虹(。)
和虹(6)分别表示变量"和6的安全级。(4)证明信息流公式的正确性,如果无法证明某
个流公式的正确性,则需要进一步对语句进行语义分析。该方法的优点在于不会漏掉可 能产生隐通道的非法信息流,但同时也产生大量的伪非法流,需要通过手工的语义分析 消除伪非法流。
此后,Tsai等人对信息流分析方法作了很大的改进,增加了语义分析,提出了一种 基于语义信息流分析的隐通道搜索方法,即一种语义信息流方法。此方法的核心思想是: (1)分析编程语言的语义、代码和内核中使用的数据结构,发现变量的可见性和可修 改性;(2)解决变量的别名问题,确定变量的间接可修改性;(3)对源代码进行信息流 分析,确定变量的间接可见性。改进的语义信息流方法可以发现大量伪非法流。
信息流分析法既可以应用于系统形式化顶级描述,也可以应用于系统源代码进行隐 通道分析。对于一些形式化语言,已经有了成功的自动信息流分析工具,能够对系统的 形式化顶级描述作比较完全的信息流分析。但形式化描述与具体实现的系统源代码之间 总是有差别的,而目前比较有影响的应用于系统源代码的信息流自动分析工具仅见He
与Gligor研制的工具。
目前基于系统形式化顶级描述分析信息流的隐通道搜索方法缺陷 为简化工作量、得到较高的工作效率,目前主要是基于系统顶级描述进行系统中的 隐通道搜索工作。因为与系统源代码相比较,系统顶级描述简单易懂,对分析人员的要 求相对较低,从而省去很多繁琐的工作。然而,尽管这样可以得到较高的工作效率,但 由于系统顶级描述并没有真实地体现系统实现的细节,所以搜索结果的精度并不高,会 出现漏报和错报的情况。
目前基于系统源代码分析信息流的隐通道搜索方法缺陷
由于源代码复杂难懂,直接针对系统源代码分析信息流的工作量很大,目前这类搜 索方法缺乏行之有效的自动化工具,需要辅之以手工操作分析源代码中的信息流,对分 析人员的要求相当高,显然对于较大规模的系统来说,工作效率低,容易出错。He与 Gligor研制的自动化工具应用了系统强制安全模型在源代码中的解释,不能区分伪非法 流与真实非法流。

发明内容
本发明的目的是解决基于系统源代码搜索隐通道的问题。基于程序设计语言语义, 设计一套系统源代码中信息流的描述方法,结合共享资源矩阵法,提出一种基于系统源 代码搜索隐通道的信息流分析方法。
本发明方法利用二叉树结构来描述系统源代码中的信息流,通过遍历树将这些信息 存放在直观、易于理解的信息流向图中,为搜索隐通道提供信息流分析结果,再以此作 为输入,构造共享资源矩阵,分析系统中的隐通道。
本发明的技术方案是 一种基于系统源代码搜索隐通道的信息流分析方法,包括下 列步骤
(1) 借助程序设计语言编译系统中的词法分析程序,扫描系统源代码,分别识别 出源代码中的函数和函数中的语句、变量;
(2) 根据扫描程序识别出的函数以及函数调用语句,确定函数间的调用关系,构 造语句树段,给出每个函数的函数依赖集;
(3) 根据扫描程序识别出的能产生信息流的语句和变量,作为信息流分析的输入, 构造函数信息流树,并进行剪枝;
(4) 遍历函数信息流树,输出每个函数的信息流向(5) 根据函数依赖集和它的信息流向图,以函数依赖集为单位,获得依赖集中函 数间的共享变量可见性和可修改性信息,生成共享资源矩阵;
(6) 以共享资源矩阵作为输入,搜索隐通道,输出系统中存在的隐通道序列。 为说明信息流树结构,这里介绍几个和信息流树相关的定义。① 结点信息流树中的最小单位,对应着函数各语句中的语句关键字(如if, while 等)、运算符(如"=")及变量。为能描述出所有的信息流,我们定义了三种结点"stmt" 结点为语句结点;"var"结点为变量结点,记录信息流中源变量和目标变量的信息(如 变量名,变量类型等信息);"nullstmt"结点没有任何意义,仅用于嵌套控制语句中控 制语句紧邻出现的情况,用此结点作为一个过渡结点。
② 语句树段结点按照一定的规则所构成的用来描述一条语句中信息流的二叉树, 称为语句树段。对函数中每一条语句,都要构造其语句树段,用来描述语句中的信息流。
③ 信息流树也是一种二叉树,由若干个语句树段连接而成,用来描述一个函数 中的信息流。信息流树对应着函数,.而构成信息流树的这些语句树段则分别对应着函数 中的各条语句。
所述步骤(1)进一步包括下列步骤
(1.1) 预定义关键字表的结构和内容,存放所有关键字;用字符表存放扫描源代码 时识别出的标识符,字符表包括全局变量字符表和局部变量字符表;用函数名表存放扫 描源代码时识别出的函数名;
(1.2) 用词法分析程序对系统源代码进行扫描,对一个输入的字符串(即词素),通 过查找关键字表、字符表和函数名表,来确定字符串的属性(是关键字,还是标识符, 或者是函数名),以二元式的形式输出语句记号序列,语句记号序列包括标识记号和记 号属性。
为实现一个基于系统源代码的隐通道搜索工作的自动化,首先要解决的是自动扫描 并分析系统源代码,根据扫描结果构造出用于信息流分析的函数信息流树,并且确定函 数间的相互调用关系,即函数依赖集。为达到这一目的,考虑到一个语言的编译程序可 以对源代码进行扫描并分析,借助高级语言编译系统中的词法分析程序,来完成对系统 源代码的扫描。
词法分析器以源程序作为输入,将其转化为记号输出。记号是指从源程序读入的一 组字符串(如变量名、关键字、常数),通过词法分析器转化输出的形式,而读入的这 一组字符串就称为记号的一个词素。程序设计语言中常把标识符(包括变量名、数组名、 函数名等等)、关键字、操作符、常量、文字串和标点符号作为记号处理。 一个记号往 往会对应多个词素,例如,高级语言中常用id作为标识符的记号,如pi、 D2这两个不 同的变量名,在以记号形式输出时都表示为id,这样不足以区分不同的标识符。因此, 词法分析器必须为这个记号提供附加的信息,称之为记号的属性,与记号构成一个二元 式输出。
上述信息流树构造步骤进一步包括如下步骤 首先分类定义程序设计语言的语句信息流规则(以C语言为例),然后依据语句信 息流规则,给出各类语句树段的构造方法;在此基础上,构造函数信息流树。 (1)所述语句信息流规则
在Tsai和Gligor给出的简单信息流规则的基础上,针对高级语言中语句各自的特 点,将语句分为赋值语句、函数返回值语句、函数调用语句、控制语句和嵌套控制语句 五类,分别给出它们的信息流规则。
① 赋值语句信息流规则
规则1. 1 va=vb;则有信息流vb-〉va; 规则1.2 va--或va+屮;则有信息流va-〉va;
规则1.3 va=vb;且va, vb均为指针,则有信息流vb-〉va, va-〉vb;
规则1.4va:&vb;且va为指针,则有信息流vb->va, va-〉vb;
规则1. 5 va=vb op…op vn;其中op代表运算符,则有信息流vb->va,…,vn->va。
② 函数返回值语句信息流规则
规则2. 1 return (va);则有信息流va_〉*,其中,*代表语句所在的函数; 规则2. 2 return (va op…op vn);则有信息流va->*, ..., vn-〉*。
③ 函数调用语句信息流规则
规则3. 1 va=F(X);其中F(x)代表函数,则有信息流x-〉va;
规则3.2F(x);则有x->*,其中*代表语句所在的函数;
规则3. 3 va=F(xl,…,xn);则有信息流xl-〉va,…,xn-〉va; 规则3. 4 F(xl,…,xn);则有x1->*,…,xn-〉承。
控制语句信息流规则 规则4. 1对于下面四条语句
if / while (va pp_co/c K) vb=m;
if / while (va op—cow7 K) vb++;
if / while (va o/ —co历K) vb—;
if / while (va 。p一, K) vb=F(x);贝瞎信息流va-〉vb; 规贝U 4. 2 if / while (va op—cm K) {vb=ml;...; vn=mn;};贝U有信息流va—〉vb,…, va—>vn;
夫见贝ij 4. 3 switch (va) (case: vb=ml;…;default: vn=mn;);贝U有《言息流va—〉vb,…, vei-〉vru
规则4. 4 if / while (va o/ _co/t K) return;则有信息流va-〉*;
规贝U 4. 5 switch (va) (case: return;…;default: return;);贝瞎信息流va->*;
规贝U 4. 6 if / while (va o/ —cot/7 K) {vb=ml; ' ;return;};贝U有《言息 荒va—〉vb,,
规贝!j 4. 7 switch (va) (case: vb=m;…default: return;);贝ll有《言息、流va—〉vb,…, V3—>水;
规则4.8 if / while (F(x)邵—麵K) va=m;则有信息流x-〉va; 规则4.9 if / while (F(x) K) return;则有信息流x->*;
规则4. 10 if/while (F(x)叩—co历K) {va=ml;…vn-mn;};则有信息流:x-〉va,…, x->vn',
规则4. 11 if / while (F(x) 一譜K) {va=ml;…return;};则有信息流 x-〉va,…,x-〉承;
规则4.12 if / while (F(xl,…,xn) o/lco/zK) {va=ml ; };则有信息流 xl—〉va, xl-》.',…,xn—〉va, xn-》";
规则4.13 if (va叩一簡K) {vb=m,.} else {vc=ir,..};则有信息流 va—>vb,…;va-〉vc…;
规贝'J 4.14 if/while ((va Kl) <9々」<9《…< / _7c^ (vn c^—co/z Kn))
{vm=m;;贝瞎信息流va->vm, va_》",…,vn->vm, vn-〉…。
规则4. 15 va=(vb op…vn) vc:vd;则有信息流vb->va,, vn-〉va。
⑤嵌套控制语句信息流规则 规则5. 1
controlstmtl (va op…) (…①;controlstmt2 (vb op…)
…③
其中controlstmt代表if 、 while、 switch (若为switch,则相应的改为分支语句 的结构形式),op代表比较运算符或逻辑运算符,则有信息流va-〉…①,va-〉…②, va-〉…③,…,vb-〉…②,其中①、②、③是为区分三个"…"部分。
规则5. 2
controlstmtl (va op…) (controlstmt2 (vb op…) (…①h …②
则有信息流va-》..①,va-》..②,…,vb-〉…①,…。 规则5. 3
controlstmtl (Kl)
{…;①
controlstmt2 (va op…) {…②
break;}; …;③
则有信息流va-》..①,va-》"②,va-》..③,…。 (2)所述语句树段构造规则
在构造语句树段时,遵循一个大的原则语句信息流的源变量信息始终记录在左孩 子变量结点中,而语句信息流的目标变量信息始终记录在右孩子变量结点中。基于这一 原则给出语句树段的构造规则。其中,相对于嵌套控制语句,赋值语句、函数返回值语 句、函数调用语句和控制语句的语句树段构造过程比较简单,所以将这四类语句统称为 简单语句。下面首先讨论简单语句树段的构造规则,再讨论嵌套控制语句树段的构造规 则。
① 简单语句树段构造规则
a) 对每一个语句,生成一个语句结点作为根结点;
b) 对语句信息流中的源变量,若源变量只有一个,则生成一个记录该源变量信息 的变量结点,作为根结点的左孩子;若源变量有多个,则分别生成记录不同源变量信息
的变量结点,生成的过程中将后一源变量结点作为前一源变量结点的左孩子,构成一棵
只有左分支的二叉树,并把此树作为根结点的左子树;
c) 对语句信息流中的目标变量,若目标变量只有一个,则生成一个记录该目标变 量信息的变量结点,作为根结点的右孩子;若目标变量有多个,则分别生成记录不同目 标变量信息的变量结点,生成的过程中将后一目标变量结点作为前一目标变量结点的右 孩子,构成一棵只有右分支的二叉树,并把此树作为根结点的右子树。
② 嵌套控制语句树段构造规则
a) 对于最外层的控制语句,先生成一个语句结点作为根结点;再生成一个或多个 变量结点,记录控制语句中判断变量或循环控制变量(源变量)的信息,生成的过程中 将后一源变量结点作为前一源变量结点的左孩子,构成一棵只有左分支的二叉树,并把 此树作为根结点的左子树。
b) 对于控制语句语句体内的赋值表达式、自增/自减表达式、函数调用语句或函数 返回值表达式,生成记录表达式中目标变量信息的变量结点,若控制语句根结点没有右 孩子,则将此变量结点作为其右孩子;否则,将其作为在它之前生成的结点的右孩子。
c) 对于控制语句语句体内的某一嵌套控制语句,若其与上一层控制语句不紧邻(如 规则5.1),则先将该嵌套控制语句作为一独立的控制语句,按照策略a)至c)生成其 语句树段,然后将此语句树段作为在它之前生成的目标变量结点的左子树;若其与上一
层控制语句紧邻(如规则5.2),则先生成一个"millstmt"结点,作为上一层控制语句 的右子树结点,然后按照策略a)至c)生成该嵌套控制语句的信息流树,再将其作为 "nullstmt"结点的左子树。
d)若某一控制语句的循环控制条件为空或为一常量,则其语句体必然有一个嵌套 的控制语句中有跳出循环的break语句。对于这种情况,将该嵌套控制语句的源变量结 点作为外层控制语句的源变量结点。
(3)所述信息流树构造规则
有语句树段构造规则作为基础,信息流树的生成很简单按照函数中语句的执行顺 序,将后一语句的树段作为前一语句树段最末目标变量结点的右子树。函数中所有语句 树段按此规则生成的二叉树即为函数的信息流树。
对于信息流树剪枝步骤进一步说明如下-
上述信息流树剪枝主要是为了将函数信息流树中记录函数内部信息流的树枝剪掉。 函数内部信息流指的是和函数中局部变量相关的信息流,这些信息对隐通道分析来说没 有意义,因此为方便接下来的隐通道搜索工作,在信息流树的构造过程中,对其进行剪 枝。通过分析函数内部信息流的特点,结合信息流的传递性,给出了信息流树剪枝的依 据。
剪枝的目的就是使得函数信息流树中只反映和隐通道分析相关的共享变量的信息 流动情况。函数间可以共享的变量即为全局变量,因此,在对信息流树进行剪枝时,只 保留树中和全局变量相关的信息流即可。
信息流树剪枝是在函数信息流树生成的过程中同时进行的,这样可以大大地节省时 间和存储空间,得到更高的效率。
剪枝算法包括以下步骤
(1) 对函数中的语句,构造语句树段,若语句为简单语句,则按(2)处理;若 语句为嵌套控制语句,则按(3)处理;
(2) 对函数中简单语句树段进行遍历,遍历的过程中查看是否需要剪枝遍历源 变量结点时,按(4)处理;遍历目标变量结点时,按(5)处理;遍历非变量结点时, 无需任何处理;
(3) 函数中嵌套控制语句的信息流树进行遍历,遍历的过程中査看是否需要剪 枝遍历目标变量结点时,按(5)处理;遍历源变量结点时,査看以它为源变量的目 标变量结点若有某一目标变量结点与其记录的变量相同,则无需任何处理;否则,按
(4)处理;遍历非变量结点时,无需任何处理;
(4) 对源变量结点,考察已生成的函数信息流树中的每一个变量结点,若其中某 一变量结点是它父结点的右孩子,且与该源变量结点记录的是同一变量,则分以下两种 情况处理
① 记录的是局部变量,则将这一变量结点的父结点的左子树复制,并替换该源 变量结点;
② 记录的是全局变量,则将这一变量结点的父结点的左子树复制,并做为该源 变量结点的左子树;
(5) 对目标变量结点,考察已生成的函数信息流树中的每一个变量结点,若其中 某一变量结点是它父结点的右孩子,且与该结点记录的是同一变量,则分以下三种情况 处理
① 这一变量结点有左子树,则用"nullstmt"类型结点替换它;
② 这一变量结点无左子树,且它的父结点和右孩子结点均为语句结点,则将它 所在的语句树段从树中删除;
③ 这一变量结点无左子树,且它的父结点或右孩子结点不是语句结点,则仅将 此变量结点从信息流树中删除;
(6) 将经过剪枝处理的语句树段连到函数信息流树中;
(7) 对函数中每一条语句,重复以上步骤,直到函数体结束;
(8) 对得到的函数信息流树考察其所有变量结点,若某一变量结点是它父结点的 左孩子,且结点中记录的是局部变量,则将该变量结点从树中删除;
(9) 对得到的函数信息流树考察其每一个语句树段,对简单语句树段,按(10) 处理;对嵌套控制语句树段,按(11)处理;
(10) 对简单语句树段,若其中某一变量结点是它父结点的右孩子,且它记录的是 局部变量,则分以下两种情况处理
① 这一变量结点的父结点和右孩子结点均为语句结点,则将此树段从信息流树 中删除;
② 这一变量结点的父结点或右孩子结点是变量结点,则仅将此结点从语句树段 中删除;
(11) 对嵌套控制语句树段,若其中某一变量结点是它父结点的右孩子,且它记录 的是局部变量,则分以下两种情况处理
① 这一变量结点的父亲结点和右孩子结点都不是变量结点,则用null类型结 点代替此结点;
② 这一变量结点的父亲结点或右孩子结点是变量结点,则仅将此右子树结点从 语句树段中删除。
对于所述信息流向图生成步骤进一步说明如下
由于各变量间的信息流动不是单一的线性关系,有可能构成复杂的网状,因此采用 图结构来描述函数各变量间的信息流向情况,称之为"信息流向图"。生成信息流向图 是为了提供直观的信息流分析结果。构造出函数信息流树之后,根据给出的遍历算法,
对函数信息流树进行遍历,生成函数的信息流向图,图中记录函数中共享变量的可见性 和可修改性信息,即信息流分析的结果。
由函数信息流树生成信息流向图的方法如下
(1) 信息流树线索化
为方便遍历信息流树生成信息流向图,在构造函数信息流树和简枝的过程中,还要 将其线索化。线索的建立过程很简单,在构造语句树段时,令源变量结点(即左子树变 量结点)的右子树指针始终指向其后生成的目标变量或null类型结点,并令目标变量 结点的右子树指针也始终指向其后生成的结点。
(2) 遍历线索化的信息流树,生成信息流向图
信息流向图的生成过程实际上就是对于树中每一个语句树段的每一个左子树变量 结点,遍历所有与它在同一语句树段且在同一语句结点下的右子树变量结点,在遍历的 过程中将信息流信息写入信息流向图中。下面说明对于一个源变量结点,遍历语句树段 寻找目标变量的过程,以及在信息流树中寻找源变量结点的过程。
① 遍历语句树段寻找目标变量的过程-
a) 对树中某一语句树段中的一个源变量结点,设有一指针Q,令其指向该源变量 结点的右指针指向的结点。
b) 如果Q指向结点是变量类型结点,表示该结点记录的是目标变量,找到一条信 息流,将信息流信息写入信息流向图中。若此结点左指针不为空,则Q指向其左指针指 向的结点,跳至b);
c) 若其左指针为空,则Q指向其右指针指向的结点。若Q为空,或Q指向结点为 语句结点(表示Q已指向树中的下一个语句树段的根结点),或Q指向结点与源变量结 点不在同一语句结点下(表示Q指向的变量结点不在源变量的信息流作用域内),则此 次遍历结束。
d) 如果Q指向结点是语句类型结点(表示一个控制语句中嵌套了另一条控制语 句),则Q指向它的右指针指向的结点,跳至b)。
e) 如果Q指向结点是null类型结点(表示一个控制语句中嵌套了另一条紧邻的 控制语句),表示则Q指向它的左子树结点,跳至b)。
② 寻找源变量结卢、过程
设P为指向信息流树中某一源变量结点的指针,假设根据上述遍历语句树段中目标 变量结点的过程,已经找出了所有以P指向结点为源变量的信息流的目标变量结点,接 下来要找到树中下一个源变量结点,进行遍历,则在树中寻找下一个源变量结点的过程 如下
a) 如果P的左指针不为空,则P指向它的左指针指向的结点,此结点即为要找的 下一个源变量结点,寻找过程结束。
b) 如果P的左指针为空,则P指向它的右指针指向的结点。C)跳至第b)步,直至P的左指针不为空。
d) 指向它的左指针指向的结点。
e) 如果P指向结点是语句结点,则P指向它的左指针指向的结点,此结点即为要 找的下一个源变量结点,寻找过程结束。
对于上述生成共享资源矩阵并搜索隐通道步骤进一步说明如下
由于在信息流向图中已记录下共享变量在函数中的可见性和可修改性信息,因此矩 阵的生成十分简单(见具体实施方式
中的共享资源矩阵生成算法)。而到目前为止对隐 通道的研究仍然不能刻画出它的本质特征,因此要给出隐通道充分且必要的判定条件并 不容易,但可以依据隐通道存在的最小条件来縮小查找范围。
一个隐通道需要一个发送进程来修改共享变量,以及一个接收进程发现变量的修 改,即要求该共享变量兼具可见性和可修改性两种性质。如果某一共享变量只具有可见 性或只具有可修改性,则该变量不能用于构成隐通道,因此,在经过传递闭包运算得出 的共享资源矩阵中,找出在所有原语中只具有可见性或只具有可修改性的变量,剔除这 些变量,得到的矩阵中记录的就是同时具有可见性和可修改性的共享变量。
接下来,利用剩下的共享变量进行隐通道分析。分析是逐变量进行的,对于某个变 量,首先找出和它相联系的原语操作,然后运用系统的强制安全策略, 一般这种策略都 遵从bell-lapadula模型,即信息不能从高安全级传递到低安全级。因此,只要发送方安 全级高于或等于接收者的安全级,就产生一个潜在隐通道。分析开始时常假定发送者拥 有高于或等于接收者的安全级,接着,应用其中安全模型策略,就可以发现共享变量构
成的潜在隐通道。这一分析重复进行,就可以找出待分析系统中所有的潜在隐通道。
本发明方法提出了一种基于系统源代码搜索隐通道的信息流分析方法。本方法基于 系统源代码进行分析工作,可以提高隐通道搜索工作的精度;另外,本方法中提出了一 套信息流描述和分析方法,为基于系统源代码搜索隐通道的自动化进行提供算法和理论 依据;并且在信息流分析过程中,本方法避免了应用系统强制安全策略模型在源代码中 的解释,采用与共享资源矩阵结合的方式,从而避免引入伪非法流。


图1是信息流分析方法工作流程示意图。
图2是词法分析器与信息流树构造交互的示意图。
图3是语句树段构造算法实现流程示意图。
图4是赋值语句树段构造算法实现流程示意图。
图5是函数调用和函数返回值语句树段构造算法实现流程示意图。
图6是控制语句树段构造算法实现流程示意图。
图7是实例中函数os—creat—task的信息流树和信息流图示意图。 图8是实例中共享资源矩阵输出示意图。 图9是实例中部分隐通道序列输出示意图。
具体实施例方式
下面根据附图对本发明作更详细的阐述,并给出相应的算法描述。 本发明是一种基于系统源代码搜索隐通道的信息流分析方法,以系统源代码中信息 流分析工作为前提,结合共享资源矩阵法,完成基于系统源代码的隐通道搜索工作。根 据本发明方法提供的方法,可以实现基于系统源代码搜索隐通道工作的自动化。
如图1所示,本发明的信息流分析方法具体包括下列步骤
(1) 借助程序设计语言编译系统中的词法分析程序,扫描系统源代码,分别识别 出源代码中的函数和函数中的语句、变量;
(2) 根据扫描程序识别出的函数以及函数调用语句,确定函数间的调用关系,构 造语句树段,给出每个函数的函数依赖集;
(3) 根据扫描程序识别出的能产生信息流的语句和变量,作为信息流分析的输入, 构造函数信息流树,并进行剪枝;
(4) 遍历函数信息流树,输出每个函数的信息流向(5) 根据函数的信息流向图,以函数依赖集为单位,获得依赖集中函数间的共享 变量可见性和可修改性信息,生成共享资源矩阵;
(6) 以共享资源矩阵作为输入,搜索隐通道,输出系统中存在的隐通道序列。
具体实施过程如下
一、利用词法分析器扫描系统源代码,将源代码转化为二元式输出 为方便构造信息流树,将词法分析程序稍作改动,使得其可以以语句为单位,每次 输出一个语句的记号序列,作为构造语句树段算法的输入,具体见图2。
在图2中,关键字表用来存放所有关键字,表结构和内容是预先定义好的。字符表 用来存放扫描源代码时识别出的标识符,是在对源代码进行词法分析时写入内容的,字 符表包括全局变量字符表和局部变量字符表。函数名表用来存放扫描源代码时识别出的 函数名,也是在词法分析的过程中逐渐填充的。在对源程序进行词法分析时,对一个输 入的字符串(即词素),通过查找关键字表、字符表和函数名表,来确定字符串的属性, 即是关键字,还是标识符,或者是函数名,给出该字符串的语句记号序列,语句记号序 列为二元式的形式,包括标识记号和记号属性。
相关数据结构定义如下 (1) 二元式记号
将由词法分析生成的二元式以结构体的形式存放,结构定义如下
typedef struct BufToken
{intsyl; 〃记号 intpos; 〃记号属性 }阔;
此结构体中标识记号和记号属性的两个域分别是Syl和pOS,都采用整数类型;p0S
域就用来描述记号的属性,记录记号在各表中的位置。 一条语句中所有的记号序列都存 放在buf []这个结构体数组中,这个数组就为构造信息流树提供输入。
(2) 关键字表 关键字表结构定义如下
struct rwords( char sp[10]; int sy; }; struct rwords reswords[]=
{ {"if',sy—if}, {"do",sy一do〉,{"else",sy—else}, {"while",sy—while}, {"switch" ,sy一switch), {"case" ,sy一case) , {"return" ,sy—return}, {"for",sy_for},...};
其中,关键字表采用结构体数组形式存放,数组中每一个记录有两个域,分别存放 每一个关键字的拼写和它在关键字表中的位置;此结构定义仅给出了一部分,关键字全 部存放在此结构中。
(3) 字符表
考虑到要经常对字符表进行查找和字符串匹配操作,所以采用哈希表存储结构来存 放字符表信息,定义如下
typedef struct BucketListRec
{ char承name; 〃变量名
intpos; 〃变量在表中存放的位置 struct BucketListRec *next; 〃指向下一个同一地址的变量信息结点 } BucketList typedef struct 〃字符表
{ BucketList 、extarc;〃指向记录在此地址下的变量信息结点 HdHashList[];
其中,结构体BucketListRec表示的是一个记录变量信息的结点,*name域记录下 识别出的变量名,pos域用来存放变量在表中存放的位置,另有一个指针域^ext,指向 同在一个哈希地址的下一个结点;结构体类型的HashNode数组就描述了字符表,由指 向变量信息结点的指针数组构成,每一变量在指针数组中存放的位置(即数组下标)由 哈希函数生成。
二、利用词法分析结果,以二元式为输入,构造信息流树,确定函数依赖集
在构造函数信息流树时,以词法分析程序输出的语句记号序列为输入。扫描源代码
过程中,每当一个函数体扫描结束后,词法分析程序都输出一个(-2, ) 二元式来标 识函数结束。而词法分析程序何时输出这样的二元式标识,即如何判断函数体的结束, 是通过在词法分析程序中引入一个标识变量,标识源代码中的"{"和"}":在扫描一
个函数时,当出现一个"r'时,该标识变量就增l;当出现一个"}"时,该标识变量
就减l。由于"{"和"}"必定是成对出现的,所以当标识变量变为O时,即表示函数 体部分结束,此时,输出一个(-2, ) 二元式。以此为前提,信息流树构造算法如下 所示
Stmt *R; //R始终指向一个语句树段的最末结点
CreatFunlnfTree(buf^]) {Q=R;
do{ getbuf[]; 〃获得函数中语句记号序列数组bufl] Treehead=StmtInfTree(); 〃构造语句树段 Q->nextrchiId=Treehead; 〃将语句树段连到函数信息流树中 Q=R;}
while (buf[l].sy!=-2); 〃当函数体未结束时,构造函数信息流树
上述算法中,调用了StmtlnfTree子程序来生成信息流树中的各个语句树段。该程序 根据不同的语句类型,调用相应的子程序来构造语句树段。
图3是语句树段构造算法实现流程
首先,输入语句记号序列数组;
然后,判断二元式语句记号序列是否结束;
如果没有结束,根据语句记号序列内容,构造赋值语句树段或控制语句树段或函数 调用和函数返回语句树段;
如果语句记号为for,则找到for语句体中第一条语句的首个语句记号序列,重复判 断二元式语句记号序列是否结束。
图4是赋值语句树段构造算法,设P为指向bufl]的指针,R为指向语句树段末结点 的指针,其实现流程如下 首先,生成语句根结点;
如果该语句为自增或自减语句,则根据二元式记号生成变量结点,作为根结点的左 右孩子,令R指向根结点的右孩子,结束;
否则,根据P指向的二元式记号,生成变量结点,作为根结点的右孩子,并令R指 向根结点的右孩子;
接下来,重复下述过程,直至P指向的二元式记号为";"时结束
令P跳过语句记号序列中的运算符、赋值号函数名及左右括号,指向存放变量名(包
括函数参数)的二元式记号,根据P指向的二元式记号,生成变量结点,作为根结点或 前面生成的源变量结点的左孩子;P指向下一个二元式。
图5是函数调用和函数返回值语句树段构造算法,实现流程如下 首先,生成语句根结点;
然后,令P跳过函数名或关键字return及左括号;
如果P指向的二元式记号是变量名,则根据P指向的二元式记号,生成变量结点, 作为根结点或前面生成的源变量结点的左孩子,P指向下一个二元式;重复此过程,直 至P指向的二元式记号为右括号;
最后,生成记录代表当前函数的"*"结点,作为根结点的右孩子。
图6是控制语句树段构造算法,实现流程如下
首先,生成语句根结点;
然后,判断该语句是否为"doihile"语句;
如果是,则令P跳过关键字do及"{",按照控制语句树段构造规则,先生成根结 点的右子树,再生成根结点的左子树;
如果不是,则令P跳过语句关键字及"(",按照控制语句树段构造规则,先生成根 结点的左子树,再生成根结点的右子树。
在扫描源代码构造函数信息流树的同时,还要根据输出的记号序列来确定函数间的 调用关系,即生成函数依赖集,以便得到调用了若干函数的原语间共享变量的可见性和 可修改性信息,从而提供给隐通道搜索工具来进行隐通道分析工作。
为此,下面给出确定函数调用关系的算法,算法生成的函数间调用关系存放在函数 名表中。在介绍算法之前,先给出函数名表的结构定义
#define MAX—FUNCNODE一NUM 〃函数名表长度
typedef struct ArcNode
{intadjvex; //函数中调用的其他函数在表中的表头结点位置 struct ArcNode * nextarc; 〃指向下一个被调用函数的指针
typedef struct VNode
{char name; int Modifyj ArcNode *firstarc;
typedef struct
〃函数名
〃标识函数是否带参;"r'表示带参,"o"表示无参
〃指向第一个被调用的函数的指针
{ Vnode NodeList[MAX一FUNCNODE—NUM];
int vnum; 〃函数名表中的结点数
} FuncList; 下面给出函数依赖集生成算法
FunCallRel(FunGraph)
{i= the function's position in FuncList; 〃获得当前正在生成信息流树的函数在
函数名表中的位置
Q->adjvex=P->pos; Q->next=NULL; 〃记录下被调用函数在函数名表中的位置 T= FimList[i]-〉firstarc; 〃指向当前函数的调用函数链
While (T->next!=NULL) T=T->next;
T->nextarc=Q; 〃将被调用函数链到当前函数的函数调用链中
这样,在构造语句树段的过程中就记录下了函数间的调用关系,并存放在函数名表 中,由此可确定函数的依赖集。
三、遍历信息流树,生成信息流向图
选用邻接表作为信息流向图的存储结构,其数据结构定义如下
#define MAX—NODE—NUM 〃信息流向图长度
typedef struct ArcNode
{ int adjvex; 〃信息流目标变量的表头结点位置
struct ArcNode * nextarc; 〃指向下一个信息流目标变量的指针
};
typedef struct VNode
{ char name; 〃变量名或代表函数的"*"字符
int Modify; 〃标识变量的可修改性;"1"表示具有,"0"表示不具有
ArcNode *firstarc; 〃指向第一条以该变量做源变量的信息流的目标变量的指针
typedef struct
{Vnode NodeList[MAX一NODE—NUM]; char *funcname; 〃函数名 int para—state; 〃函数带参情况;"1"表示带参数,"0"表示无参
intvnum; 〃信息流图当前的结点数 } Inforgraph;
结构体类型Inforgraph定义的即为图结构。其中,*ftincname域用来记录信息流向
图所属函数的函数名,para—state用来标识该函数有无参数,vnum域用来记录图中的结 点数,即函数中出现的共享变量的个数,Nodelist域是一个Vnode结构体类型的数组, 描述的是图中存放共享变量信息的表头结点,表头结点中的、ame域用来存放共享变量 名,Modify域用来标识变量的可修改性,*6^1^0域指向一个单链表,链表由若干个 ArcNode结构体类型的结点链接而成,用来存放以表头结点中为源变量的信息流中的目 标变量。
依据前面阐述的遍历思想,给出遍历信息流树生成信息流向图的算法 CreatlnfGraph()
{P和Q分别指向根结点的左右孩子结点;
while (P!-NULL) 〃对树中每一源变量结点,通过遍历树寻找它的目标变量结点 {if (Q->ntype==stmtnode) { Q=Q->nextrchild; continue; } if (Q->ntype==nullstmtnode) { Q=Q->nextlchild; continue; } if (Q->ntype==vamode) 〃找到目标变量结点 {将信息流信息写入信息流图; if (Q->nextlchild!=NULL) 〃寻找下一目标变量结点
{ Q=Q->nextlchild; continue; } else if (Q->nextrchild!=NULL) { Q=Q->nextrchild; if(Q->ntype==stotnode) II (Q指向结点不在P的信息流作用域下) 〃目标变量结点已全部找到,寻找树中下一个源变量结点 {P-下一源变量结点;Q=P->nextrchild;continue;}
}}
四、扫描信息流向图,生成共享资源矩阵,搜索隐通道 先给出矩阵的结构定义
typedef struct
{ char * name[B
int numj
)Func, Variable;
int Property [][];
用一个结构体和一个二维数组来描述矩阵结构,其中结构体Func和Variable分别 记录源代码中出现的函数和共享变量,其中納ame[]是一个指向字符串的指针数组,用 来记录函数或共享变量名,num用来记录源代码中函数和共享变量的个数,这两个结构 体是在词法分析程序扫描系统源代码时生成的,当识别出函数及全局变量的定义或声明 时,就将函数名和变量名、以及数量分别写入帕ame域,其中,Func与FuriList中函数 的存放位置相同。另外,Variable中除记录共享变量名以外,还需补充记录两个特殊的 变量Userjn和User—out,分别对应矩阵中的用户输入和输出。二维数组Property[][] 用来记录一个共享变量在各个函数中的可见性和可修改性信息,和函数中用户的输入和 输出信息。我们约定,若变量在某一函数中只具有可见性,则用"1"来标识;若只具 有可修改性,则用字符"2"来标识;若两种性质兼具,则用"3"来标识,若两种性质 都没有,则用字符"0"标识。而对于用户输入和输出,若有输入,则用"1"标识,若 有输出,则用"2"标识,否则为"0"。
根据函数信息流树的构造、剪枝,以及信息流向图的生成过程可知,在某一函数信 息流向图中,若某一变量其在图中头结点的Modify域中标记为"1",则此变量在该函 数中具有可修改;若图中记录了信息流"A-〉*",就说明变量A在此函数中具有可见性 (包括直接可见性和间接可见性)。依据这一判断条件,可以根据信息流向图生成记录 函数中变量可修性和可见性信息的矩阵,存放在Property [][]中,算法如下
CreatMatrix()
{初始化Property][数组;
!^函数在矩阵中的列下标;
if (FuncList.NodeList[k] .Modify== 1) Property[Variable.num-2]=1 ;〃函数中有用户输入 for 0=0;1++;1<111^ @3 11.^1 11)〃对信息流向图中每一个共享变量,考察它在函数中的
可修改性和可见性 {1=共享变量在矩阵中的行下标;
if (Inforgraph.NodeList[i].Modify二-l)〃共享变量在函数中具有可修改性
{ if (Property[l] [k] =0) Property[l] [k] = 2; if (Property[l] [k] ==1) Property[l] [k] = 3;}
Q=Inforgraph.NodeList[i] .firstarc;
while (QNNULL)
{j=Q->adjvex;
if(j==0) //共享变量在函数中具有可见性 { if (Property[l] [k] ==0) Property[l] [k] = if (Property [1] [k] ==2) Property [1〗[k] = 3j if (Property[Variable.num-l]!=2) Property[Variable.num-l]=2;} Q=Q->nextarc;}}}
上述算法生成的矩阵记录的是共享变量在每个函数中的可见性和可修改性的信息, 而最终要提供给共享资源矩阵搜索方法作为输入的矩阵,记录的应该是共享变量在原语 中(即函数依赖集)这两种性质的信息。因此,还要根据函数依赖集和Pr叩erty[][] 数组,生成共享资源矩阵,该矩阵中记录共享变量在每个函数依赖集中的可见性和可修 改性信息。生成共享矩源矩阵的依据是若某一函数中有一个共享变量具有某种性质, 则这一共享变量在包括该函数的函数依赖集中也具有这种性质。共享资源矩阵生成算法 如下所示
CreatSharedMatrix()
{for (i=0;i++;i<Func.num) for (j=0;j++;j<Variable.num) { Q=FuncList.NodeList[i].firstarc; while (Q!=NULL) { k=Q->adjvex; if (SharedMaxtrix[i][j] !=3)
SharedMaxtrix[i][O-Property[i] [j] | Property[k][j]; else break; Q=Q->nextarc;}}}
利用上述算法生成的共享资源矩阵,就可以作为共享资源矩阵搜索方法的输入来搜 索系统中的隐通道。
实例分析
下面以一个功能较为简单的RTX-430实时多任务操作系统(RTOS)中的部分源代码 为例,来说明本发明方法的应用情况。
在这一RTX-430实时多任务操作系统中,采用的是"占先式内核"任务调度机制, 即当一个低优先级的任务正在运行时, 一个高优先级的任务就绪,那么RTOS就会把低 优先级的任务挂起,来运行高优先级的任务;等高优先级的任务执行了一个循环挂起之 后,再回到低优先级任务的断点继续运行。下面将有选择的介绍一些函数的功能及函数 间的全局变量代表的意义。
函数主要有
(1) os一system一init:操作系统初始化,进行任务栈、任务延时计数、任务状态 的初始化。
(2) os一system—start:操作系统启动运行,在系统初始化完成后,系统直接切 换到最高优先级的任务,多任务系统启动。
(3) os一create一task:创建一个任务,如果要创建的任务已经存在,则返回出错
"f曰息。
(4) os一delete一task:撤消一个任务,若要撤消的任务不存在,则返回出错信息。
(5) task_switch:任务切换,此函数先把每个任务的延时数减1,然后再找出 最高优先级的就绪任务,并切换到这个就绪任务。
相关的全局变量主要有
(1) TKP[]:指向前一任务堆栈的尾地址;
(2) RTX_RobinTime:每个任务最长的运行周期;
(3) TASK_Current:当前运行的任务号;
(4) STATE[]:标识每个任务的状态;
以其中的os—create—task函数为例,给出根据本方法为其构造的信息流树及信息 流向图,见图7,下面列出该函数的源代码
<formula>formula see original document page 23</formula>
j++;*j=0
j++;*j=o
j++;*j=0
return(0);}
在得到每个函数的信息流树及信息流图之后,我们借助前面给出的矩阵生成算法得
到该系统的共享资源矩阵如图8所示。结合共享资源矩阵方法,得到该系统中可能存在 的隐通道序列见图9。图中的SENDER列表示可能的发送方,RECIEVER列则表示可能的 接收方,而SHARED VARIABLE列是它们之间可能用来隐蔽传输的共享资源。
经过分析发现在该系统实施实时多任务处理功能模块中,提供的创建任务原语、 撤消任务原语等一些操作存在着隐通道漏洞,有安全隐患。
权利要求
1、一种基于系统源代码搜索隐通道的信息流分析方法,其特征在于,所述信息流分析方法包括下列步骤(1)借助程序设计语言编译系统中的词法分析程序,扫描系统源代码,分别识别出源代码中的函数和函数中的语句、变量;(2)根据扫描程序识别出的函数以及函数调用语句,确定函数间的调用关系,构造语句树段,给出每个函数的函数依赖集;(3)根据扫描程序识别出的能产生信息流的语句和变量,作为信息流分析的输入,构造函数信息流树,并进行剪枝;(4)遍历函数信息流树,输出每个函数的信息流向图;(5)根据函数依赖集和它的信息流向图,以函数依赖集为单位,获得依赖集中函数间的共享变量可见性和可修改性信息,生成共享资源矩阵;(6)以共享资源矩阵作为输入,搜索隐通道,输出系统中存在的隐通道序列。
2、 根据权利要求1所述的分析方法,其特征在于,所述步骤(1)进一步包括下列 步骤(1.1) 预定义关键字表的结构和内容,存放所有关键字;用字符表存放扫描源代码 时识别出的标识符,字符表包括全局变量字符表和局部变量字符表;用函数名表存放扫 描源代码时识别出的函数名;(1.2) 用词法分析程序对系统源代码进行扫描,对一个输入的字符串,通过査找关 键字表、字符表和函数名表,来确定字符串的属性,以二元式的形式输出语句记号序列, 语句记号序列包括标识记号和记号属性。
3、 根据权利要求1所述的分析方法,其特征在于,所述构造函数信息流树步骤中, 语句信息流规则为,将语句分为赋值语句、函数返回值语句、函数调用语句、控制语句 和嵌套控制语句五类,分别给出它们的信息流规则。
4、 根据权利要求1所述的分析方法,其特征在于,所述构造函数信息流树步骤中, 语句树段构造规则是语句信息流的源变量信息始终记录在左孩子变量结点中,而语句 信息流的目标变量信息始终记录在右孩子变量结点中。.
5、 根据权利要求1所述的分析方法,其特征在于,所述步骤(3)中,剪枝的算法 包括以下步骤(3.1) 对函数中的语句,构造语句树段,若语句为简单语句,则按步骤(3.2)处 理;若语句为嵌套控制语句,则按步骤(3.3)处理;(3.2) 对函数中简单语句树段进行遍历,遍历的过程中査看是否需要剪枝遍历源变量结点时,按步骤(3.4)处理;遍历目标变量结点时,按步骤(3.5)处理;遍历非 变量结点时,无需任何处理;(3. 3)函数中嵌套控制语句的信息流树进行遍历,遍历的过程中査看是否需要剪枝: 遍历目标变量结点时,按步骤(3.5)处理;遍历源变量结点时,查看以它为源变量的 目标变量结点若有某一目标变量结点与其记录的变量相同,则无需任何处理;否则, 按步骤(3.4)处理;遍历非变量结点时,无需任何处理;(3.4) 对源变量结点,考察己生成的函数信息流树中的每一个变量结点,若其中某 一变量结点是它父结点的右孩子,且与该源变量结点记录的是同一变量,则分以下两种 情况处理① 记录的是局部变量,则将这一变量结点的父结点的左子树复制,并替换该源 变量结点;② 记录的是全局变量,则将这一变量结点的父结点的左子树复制,并作为该源 变量结点的左子树;(3.5) 对目标变量结点,考察已生成的函数信息流树中的每一个变量结点,若其中 某一变量结点是它父结点的右孩子,且与该结点记录的是同一变量,则分以下三种情况 处理① 这一变量结点有左子树,则用"nullstmt"类型结点替换它;② 这一变量结点无左子树,且它的父结点和右孩子结点均为语句结点,则将它所在的语句树段从树中删除;③ 这一变量结点无左子树,且它的父结点或右孩子结点不是语句结点,则仅将此变量结点从信息流树中删除;(3. 6)将经过剪枝处理的语句树段连到函数信息流树中;(3.7) 对函数中每一条语句,重复以上步骤,直到函数体结束;(3.8) 对得到的函数信息流树考察其所有变量结点,若某一变量结点是它父结点的 左孩子,且结点中记录的是局部变量,则将该变量结点从树中删除;(3. 9)对得到的函数信息流树考察其每一个语句树段,对简单语句树段,按步骤(IO) 处理;对嵌套控制语句树段,按步骤(11)处理;(3.10)对简单语句树段,若其中某一变量结点是它父结点的右孩子,且它记录的是局部变量,则分以下两种情况处理① 这一变量结点的父结点和右孩子结点均为语句结点,则将此树段从信息流树 中删除;② 这一变量结点的父结点或右孩子结点是变量结点,则仅将此结点从语句树段 中删除;(3.11)对嵌套控制语句树段,若其中某一变量结点是它父结点的右孩子,且它记 录的是局部变量,则分以下两种情况处理① 这一变量结点的父亲结点和右孩子结点都不是变量结点,则用mill类型结点 代替此结点;② 这一变量结点的父亲结点或右孩子结点是变量结点,则仅将此右子树结点从 语句树段中删除。
6、根据权利要求1所述的分析方法,其特征在于,所述步骤(4)中,由函数信息 流树生成信息流向图的方法如下(4.1) 信息流树线索化在构造语句树段时,令源变量结点的右子树指针始终指向其后生成的目标变量或 null类型结点,并令目标变量结点的右子树指针也始终指向其后生成的结点;(4.2) 遍历线索化的信息流树,生成信息流向图① 遍历语句树段寻找目标变量的过程a) 对树中某一语句树段中的一个源变量结点,设有一指针Q,令其指向该源变量结 点的右指针指向的结点;b) 如果Q指向结点是变量类型结点,表示该结点记录的是目标变量,找到一条信 息流,将信息流信息写入信息流向图中;C)若此结点左指针不为空,则Q指向其左指针指向的结点,跳至步骤b);若其左指针为空,则Q指向其右指针指向的结点。若Q为空,或Q指向结点为语句结点,或Q指向结点与源变量结点不在同一语句结点下,则此次遍历结束;d) 如果Q指向结点是语句类型结点,则Q指向它的右指针指向的结点,跳至步骤b);e) 如果Q指向结点是null类型结点,表示则Q指向它的左子树结点,跳至步骤b);② 寻找源变量结点过程设P为指向信息流树中某一源变量结点的指针,假设根据上述遍历语句树段中目标 变量结点的过程,己经找出了所有以P指向结点为源变量的信息流的目标变量结点,接 下来要找到树中下一个源变量结点,进行遍历,则在树中寻找下一个源变量结点的过程 如下a) 如果P的左指针不为空,则P指向它的左指针指向的结点,此结点即为要找的 下一个源变量结点,寻找过程结束;b) 如果P的左指针为空,则P指向它的右指针指向的结点; C)跳至第f)步,直至P的左指针不为空;d) 指向它的左指针指向的结点;e) 如果P指向结点是语句结点,则P指向它的左指针指向的结点,此结点即为要 找的下一个源变量结点,寻找过程结束。
全文摘要
一种基于系统源代码搜索隐通道的信息流分析方法,包括下列步骤借助词法分析程序,扫描系统源代码,识别出源代码中的函数和函数中的语句、变量;根据扫描程序识别出的函数以及函数调用语句,确定函数间的调用关系,构造语句树段,给出每个函数的函数依赖集;根据扫描程序识别出的能产生信息流的语句和变量,作为信息流分析的输入,构造函数信息流树,并进行剪枝;遍历函数信息流树,输出每个函数的信息流向图;根据函数依赖集和信息流向图,以函数依赖集为单位,获得依赖集中函数间的共享变量可见性和可修改性信息,生成共享资源矩阵;以共享资源矩阵作为输入,搜索隐通道,输出系统中存在的隐通道序列。本发明可以提高隐通道搜索工作的精度。
文档编号G06F21/22GK101377806SQ20081002270
公开日2009年3月4日 申请日期2008年7月24日 优先权日2008年7月24日
发明者周从华, 宋梅香, 建 曾, 王昌达, 鞠时光 申请人:江苏大学
网友询问留言 已有0条留言
  • 还没有人留言评论。精彩留言会获得点赞!
1