1 / 55

第十一讲 高效的静态分析 --- 符号执行 与 缺陷模式

第十一讲 高效的静态分析 --- 符号执行 与 缺陷模式. 内 容. 一、符号执行 1 、符号执行简介 2 、代表工具: PREfix 二、缺陷模式 1 、缺陷模式简介 2 、安全漏洞. 一、符号执行 1 、符号执行简介 2 、代表工具: PREfix. 观察下面程序:. int x, y; 1: if (x > y) { 2: x = x + y; 3: y = x - y; 4: x = x - y; 5: if (x > y) foo1(); 6: else foo2(); }.

durin
Download Presentation

第十一讲 高效的静态分析 --- 符号执行 与 缺陷模式

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. 第十一讲 高效的静态分析 ---符号执行 与 缺陷模式

  2. 内 容 一、符号执行 1、符号执行简介 2、代表工具:PREfix 二、缺陷模式 1、缺陷模式简介 2、安全漏洞

  3. 一、符号执行 1、符号执行简介 2、代表工具:PREfix

  4. 观察下面程序: int x, y; 1: if (x > y) { 2: x = x + y; 3: y = x - y; 4: x = x - y; 5: if (x > y) foo1(); 6: else foo2(); } 有什么问题?

  5. 1、符号执行简介 • J. C. King 于 1976 年提出Symbolic Execution • 使用符号值,而不是实际数据,作为输入 • 将程序变量的值表示为符号表达式 • 程序计算的输出表达为输入符号值的函数

  6. 模型检验 • 优点 • 分析是路径敏感的 • 因为没有对路径、状态做近似,结果精确 • 适合做状态检查、时序检查 • 对并发类型的错误十分有效 • 缺点 • 对所有可能的状态进行穷举搜索,开销大 • 对系统的行为进行近似,可能导致这类结果不精确 • 对于数据密集的系统分析困难 • 在边界处对路径、时序属性近似困难,故复合困难

  7. 抽象解释 • 优点 • 由于对路径、状态进行抽象,扩展性好 • 可以对许多有价值的属性构造格 • 易于组合 • 提供了坚实的数学基础 • 缺点 • 适合的属性需要是简单的、“状态”“值”型的 • 对时序性质支持弱 • 属性格的定义不容易 • 有时近似过强

  8. 演绎方法(定理证明) • 优点 • 支持灵活的属性 • 易于扩展 • 缺点 • 开发人员需要提供额外的信息 • 自动化程度不高 • 路径不敏感,对并发系统不适合

  9. 符号执行 • 记录执行的状态,包括: • 程序变量的符号值 • 路径条件(PC: Path Condition) • 程序标记(后面执行什么) • 路径条件非常重要 • 积累了路径的约束条件 • 符号执行树 • 刻画程序符号执行过程中的执行路径

  10. 例子: • 最初,x 与 y 分别具有符号值X、 Y • 在每个分支点,PC 根据输入的假定确定不同的值 • 如果PC不成立,该路径不可达 • 可以大大减少路径组合 int x, y; 1: if (x > y) { 2: x = x + y; 3: y = x - y; 4: x = x - y; 5: if (x > y) foo1(); 6: else foo2(); } x: X, y: Y (x>y?) x: X, y: Y (X<Y) x: X, y: Y (X>Y) x: X+Y, y: Y (X>Y) x: X+Y, y: X (X>Y) x: Y, y: X (X>Y) x: Y, y: X (X>Y)& (Y>X) x: Y, y: X (X>Y)& (Y<=X)

  11. 符号执行可以被看作是路径敏感分析、演绎方法及抽象解释的组合符号执行可以被看作是路径敏感分析、演绎方法及抽象解释的组合 • 对路径条件进行近似使得抽象解释的属性区间值收缩,因此更加灵活 • 约束近似了许多状态,降低了分析量 • 经常被应用于测试输入的生成 • 传统上,符号执行对有限个整数变量进行符号化,后来扩展到了处理复杂的输入数据结构和并发 • 例如,扩展 JPF 工具以进行符号执行Java 程序

  12. 2、典型工具:PREfix 1)基本特点 2)主要思路 3)主要步骤

  13. 1)基本特点 模拟执行单个的函数! • 模拟过程顺序地跟踪不同的执行路径,模拟每个操作符的动作。在路径执行过程中,通过跟踪内存的状态,应用各种一致性规则,查找不一致性。 • 在对条件选择后,通过检查内存的当前状态,分析器可以限制可达的路径。由于对路径、值的跟踪都很仔细,可以获得精确的信息。 • 函数的行为被描述为:条件、一致性规则及表达式值的集合。行为的这种描述被称为函数的一个模型。在路径执行过程中,不论何时进入一个函数,该模型被使用,以决定应用哪个操作

  14. 在模拟过程中产生的信息足够自动地产生该函数的一个模型在模拟过程中产生的信息足够自动地产生该函数的一个模型 • 为在整个程序中应用这些技术,分析起始于调用图的叶节点,自底向上地向根处理。随着函数被逐层模拟,缺陷被不断发现,函数模型被高层的后续模拟所使用 • 这种自底向上的方法使用一个函数的实现来产生函数的约束,供上层使用。这对于程序不完整时(程序还没有开发完,或者被分析的代码需要适合多个不同的程序)尤其有效

  15. 2)主要思路 1 #include <stdlib.h> 2 #include <stdio.h> 3 4 char *f(int size) 5 { 6 char *result; 7 8 if (size > 0) 9 result = (char *)malloc(size); 10 if (size = = 1) 11 return NULL; 12 result[0] = 0; 13 return result; 14 } 观察右边代码 有什么问题?

  16. 错误消息1: 1 #include <stdlib.h> 2 #include <stdio.h> 3 4 char *f(int size) 5 { 6 char *result; 7 8 if (size > 0) 9 result = (char *)malloc(size); 10 if (size = = 1) 11 return NULL; 12 result[0] = 0; 13 return result; 14 } example1.c(11) : warning 14 : leaking memory problem occurs in function ‘f’ The call stack when memory is allocated is: example1.c(9) : f Problem occurs when the following conditions are true: example1.c(8) : when ‘size > 0’ here example1.c(10) : when ‘size == 1’ here Path includes 4 statements on the following lines: 8 9 10 11 example1.c(9) : used system model ‘malloc’ for function call: ‘malloc(size)’ function returns a new memory block memory allocated 当 size = 1 时,直接返回 NULL, 被分配的内存未被释放,也未被返回。

  17. 1 #include <stdlib.h> 2 #include <stdio.h> 3 4 char *f(int size) 5 { 6 char *result; 7 8 if (size > 0) 9 result = (char *)malloc(size); 10 if (size = = 1) 11 return NULL; 12 result[0] = 0; 13 return result; 14 } 当 size <= 0 时,函数试图解析一个未被初始化的指针 (malloc in line 9 will not be executed). 错误消息2: example1.c(12) : warning 10 : dereferencing uninitialized pointer ‘result’ problem occurs in function ‘f’ example1.c(6) : variable declared here Problem occurs when the following conditions are true: example1.c(8) : when ‘size <= 0’ here Path includes 3 statements on the following lines: 8 10 12

  18. 错误消息3: 1 #include <stdlib.h> 2 #include <stdio.h> 3 4 char *f(int size) 5 { 6 char *result; 7 8 if (size > 0) 9 result = (char *)malloc(size); 10 if (size = = 1) 11 return NULL; 12 result[0] = 0; 13 return result; 14 } example1.c(12) : warning 11 : dereferencing NULL pointer ‘result’ problem occurs in function ‘f’ example1.c(9) : value comes from return value of ‘malloc’ here Problem occurs when the following conditions are true: example1.c(8) : when ‘size > 0’ here example1.c(10) : when ‘size != 1’ here problem occurs when ‘memory exhausted’ Path includes 4 statements on the following lines: 8 9 10 12 example1.c(9) : used system model ‘malloc’ for function call: ‘malloc(size)’ function returns 0 memory exhausted 当size >0, 但malloc失败,result 返回 NULL 指针时,对其赋值出现错误

  19. 算法伪代码: while (there are more paths to simulate) { initialize memory state simulate the path, identifying inconsistencies and updating the memory state perform end-of-path analysis using the final memory state, identifying inconsistencies and creating per-path summary } combine per-path summaries into a model for the function

  20. 模拟单个函数需要模拟可达的路径。可达路径的集合包含所有可能的控制流路径集合 。这个集合的大小往往小于明显的控制流路径。因为相同的条件往往控制多个条件模块。通常可以选择有代表性的路径来模拟。具体路径数可以由用户进行配置 • 模拟一条路径涉及遍历函数的抽象语法树,对树上相关的语句、表达式求值。被模拟函数的内存状态随着语句的执行被不断更新。许多缺陷 (例如, 未初始化的内存,NULL,或者无效的指针解析) 可以通过在路径模拟过程中对内存的状态应用一致性规则发现。其他 (例如,内存泄漏,将指针返回给一个释放的内存) 在到达路径终点时可以被发现 • 当路径被模拟时,函数的最终内存状态被综合处理(summarized)。当所有的路径被模拟后, per-path summaries 被组成一个函数的模型

  21. 1 #include <stdlib.h> 2 #include <stdio.h> 3 4 char *f(int size) 5 { 6 char *result; 7 8 if (size > 0) 9 result = (char *)malloc(size); 10 if (size = = 1) 11 return NULL; 12 result[0] = 0; 13 return result; 14 } 当 size <= 0 时,函数试图解析一个未被初始化的指针 (malloc in line 9 will not be executed). 3)主要步骤 • 路径 • 路径跟踪很容易发现一些问题 • 路径中包括“功能调用、语句、结果” • 模拟执行是基于路径进行的 • 为定位缺陷提供依据 • 但不一定是唯一的路径 • 可能是多条路径累积的结果

  22. 内存(变量):值与断言 • 模拟过程中函数使用的内存需要尽可能准确地跟踪 • 每块内存都有值:3个基本状态 • 知道准确的值(常量) • 有一个初始化的值,但不知道准确的值 • 未初始化的值 • 断言( predicate )也可能与值关联 • 断言可能是由 1 到 3 部分组成 • 单个:initialized • 两个:x>3 • 三个:a = b + c

  23. 内存操作:求值、测试与设置 • 设置(setting)将一个值赋给内存,改变了内存的状态 • 简单推理:( a>3& b>4 ) => a+b>7 • 测试(testing)在一定的上下文中对表达式求值,得到一个布尔结果。需要进行测试的三种情形: • 需要判定表达式,以得到一个布尔结果(例如:条件分支) • 语言语义要求(例如:使用前必须初始化) • 在模型中选择可能的输出(例子见后面关于“模型”的内容)

  24. 条件(condition)、假定(assumption)与选择点 • 条件可以比路径更清晰地描述缺陷 • 在路径模拟执行过程中,条件对应于假定 • 当变量出现在一个表达式中、且值不确定时,可以进行假定 • 例子中的 “size”: 输入参数,模拟是自底向上进行的 • 出现两个假定,分别模拟执行,并记忆当前假定 • 当作出一个假定时,相关内存的状态被更新 • 这个假定可能提高其它断言的准确性 • 对于 a=b+5, 当假定条件表达式 a==2 为真时,a 对应的内存状态更新为2, b 对应的内存状态可以更新为 -3。 • 此时,控制流没有由已知的值完全确定下来,被称为选择点

  25. 路径结尾分析 • 当一条路径中的所有语句被模拟执行后,可以进行一些附加分析 • 发现一些缺陷。例如:内存没有释放等等 • 多路径分析 • 一条长的路径可能因为调用而包含多条子路径 • 需要分别分析 • 为控制总体数目,可以设置上限 • 路径选择是一个问题

  26. 模型 • 模型模拟了函数的行为 • 模拟器遇到一个函数调用时,它通过函数的模型来模拟被调用的函数 • 函数的模型主要是一组输出集合,这些输出代表了函数计算时对内存状态的可能改变 • 每个输出由 Guards, Constraints, Results构成 • 约束(Constraint)是前置条件:fopen 的参数必须有效 • 结果(Result)是后置条件: fopen 执行后返回值有效 • 防卫(Guards)是测试:某个特定的输出可能依赖某个输入 int knot(int j) { if (j= =0) return 1; return 0; } 例子:返回值只有在 “j==0” 时为“1” 只有在 “j!=0” 时为“0”

  27. 一个模型的例子: (deref (param p) (alternate return_0 (guard peq p NULL) (constraint memory_initialized p) (result peq return NULL) ) (alternate return_X (guard pne p NULL) (constraint memory_initialized p) (constraint memory_valid_pointer p) (constraint memory_initialized *p) (result peq return *p) ) ) 1 int deref(int *p) 2 { 3 if (p= =NULL) 4 return NULL; 5 return *p; 6 }

  28. 效果(1998):Mozilla 1.0, Apache 1.3beta 5 Warning Mozilla Apache GDI Using uninitialized memory 26.14% 45% 69% Dereferencing uninitialized pointer 1.73% 0 0 Dereferencing NULL pointer 58.93% 50% 15% Dereferencing invalid pointer 0 5% 0 Dereferencing pointer to freed memory 1.98% 0 0 Leaking memory 9.75% 0 0 Leaking a resource (such as a file) 0.09% 0 8% Returning pointer to local stack variable 0.52% 0 0 Returning pointer to freed memory 0.09% 0 0 Resource in invalid state 0 0 8% Illegal value passed to function 0.43% 0 0 Divide by zero 0.35% 0 0 Total number of warnings 1159 20 13

  29. 二、缺陷模式 1、缺陷模式简介 2、安全漏洞

  30. 1、缺陷模式简介 1) 缺陷模式概述 2)Library Interface(库接口) 3)主要技术是什么?

  31. 模式: 设计模式 分析模式 过程模式 …… 显式的 与 隐式的 私有的 与 共性的 独立的 与 嵌入式的(annotatioin)

  32. 分析算法从事的是举重的工作 模式辨识从事的是射击的事情 缺陷知识 缺陷报告 分析算法 待查代码 代码模型

  33. 符号执行 抽象解释 模型检查 • 与具体应用“无关”的知识 • 词法或者语法 • 共性特性(死锁、空指针、内存泄露、数组越界) • 公共库用法(顺序、参数、接口实现,容错,安全) • 与具体应用“相关”的知识 • 类型定义(操作格式,不含其它信息(信息隐藏)) • 类型约束(调用的顺序、参数值,接口实现) • 需求相关(正确) 模型检查 定理证明 侧重点在技术(算法)方面!

  34. 能侧重在知识上吗? Google 翻译的启示!

  35. 1)缺陷模式概述 • 代码必须遵守各类约束 • 如果违反,就是缺陷 • 对不同约束的违反,构成了不同的缺陷模式 • 约束分类 • BNF:宪法 • 共性约束(库接口):国家法律、条例等 • 特定软件的约束(类型):地方法律、条例

  36. 2)Library Interface(库接口) • 2. 不安全代码 • 3. 脆弱代码 • 3.1. 需求检查 • 3.2. 异常相关 • 3.3. 线程 • 3.4. 其他 • 4.低效率代码 • 4.1. 线程 • 4.2. String和StringBuffer • 4.3. Number 和 Wrapper • 4.4. 其它 • 5. 冗余代码 • 5.1. 无用代码 • 5.2. 不必要代码 • 5.2.1. 不必要的Null检查 • 5.2.2. 不必要的代码 • 5.3. 复制代码 • 5.4. 空集问题 • 1. 错误代码 • 1.1. 操作 • 1.2. 方法调用 • 1.2.1. 单一方法调用 • 1.2.2. 同一个类的多方法调用 • 1.2.3. 不同类的多方法调用 • 1.3. 类定义 • 1.3.1. 单一接口关联 • 1.3.2. 单继承关联 • 1.3.3. 其它 • 1.4. 线程 • 1.5.引用

  37. “/“或者“/=“跟随着0(除数为0) • 在比较两个实体时使用了“==“或者”!=“,例如字符串、引用、浮点型以及双精度型。 • 在比较Doube和Double.NAN以及Integer和Integer.MAX_VALUE时使用“==“或者”<=“。 • 比较String.indexOf()和0是滥用“>”和“<”。 • 在例如if和while的逻辑表达式中误用了操作符“=”、“&”和“|”。正确的的应该是“==”、“&&”和“||”。 • 当某个实例不属于某个类型时使用了instanceof 操作: • 调用SimpleDateFormat的构造函数时没有传递Local参数。 • 调用Calendar.set方法设置月份时使用了超出0到11的参数。 • 调用BigDecimal的构造函数时使用decimal数值。 • 调用substring时使用0作为参数。 • 调用ObjectOutPut.writeObject时使用不可序列化的对象。 • 调用Double.longBitsToDouble时使用整型参数。 • 调用PreparedStatement的setXXX时使用0作为参数索引值。 • 调用ResultSet的setXXX/updateXXX方法时使用0作为参数索引值。 • 调用Pattern的compile时使用了无效句法的正则表达式。 • 调用System.runFinalizersOnExit或者Runtime.runFinalizersOnExit • 显示的调用finalize。 • 在比较数组和非数组、不相关的类和接口、不同类型以及不同接口时调用equals 单一方法调用:

  38. 同一个类的多方法调用: 某些类中必须成对使用的方法(必须有严格的顺序):例如I/O对象中的open和close。 时序方法的约束。 初始化错误。 使用负数索引或者超过数组长度的索引值引发了数组溢出。 更多缺陷模式,请访问:as.pku.edu.cn

  39. 使用约束栈 应用软件 中间件接口约束 DBMS接口约束 操作系统接口约束 语言约束约束(BNF、基本函数)

  40. 3)主要技术是什么? • 如何在目标代码中查找已知的模式? • 先做基本扫描:有相近的代码? • 再努力提高准确度 • 如何描述缺陷模式? • 自然语言 • 形式化:时序逻辑等 • 半形式化/图形化

  41. Java Programming Language TARGET PROGRAM INPUT C/C++ Source code Representation Compiled file Embedded code Representation Description file Code style Unused code DEFECT PATTERN INPUT Property Concerned Temporal property Concurrency Other 涉及的主要技术 Yes Extensibility No Structure Analysis Control Flow Analysis SEMANTIC ANALYSIS Data Flow Analysis Call Graph Analysis Point-to Analysis Text report Representation XML/HTML report Interactive GUI OUTPUT Soundness Evaluation for Result Completeness Other

  42. 特点 • 缺陷定位准确 • 查找效率较高 • 可以针对“常见”的缺陷

  43. 对比 • 模型检查侧重状态 • 状态是否满足性质? • 抽象解释侧重循环 • 不动点是什么? • 符号执行侧重分支 • 状态有冲突吗? • 定理证明侧重 不变量 • 破坏了不变量? • 模式缺陷侧重 共性约束 • 状态与共性缺陷模式匹配? 结合!

  44. 2、安全漏洞 1)SQL注入 2)脚本注入 3)跨站点攻击 4)代码分析 让正确的人得到正确的服务!

  45. 1)SQL注入 • SQL 注入的成因主要是因为向数据库提供的SQL 查询语句是用字符串拼装的方式生成的 • 最经常遭受SQL注入的页面通常是管理员/用户登陆点。不论是asp 或是jsp,如果不正确的编码,都会出现这个漏洞

  46. 假设我们有一个JSP 页面login.jsp,它会把用户名与密码提交到指定的模块 Controller调用chekAdminLogin(String userName, String passWord) 进行登陆验证 如果从表中找到匹配的记录,则表示验证成功。 否则返回Null 表示登陆验证失败。 checkAdminLogin 将收集到的用户名和密码信息拼装出SQL 字符串,供DAO 下层使用,以从数据库中的管理员记录表中读取数据

  47. public Admin checkAdminLogin(String userName, String password){ String strSQL =”SELECT * FROM TD_ADMIN AS A WHERE A.USERNAME=’” + userName + ”’ AND A.PASSWORD=’” + password + ”’”; 如果有人试图在这里进行恶意攻击: 在登陆名输入框中输入 123 (任意值) 而在密码输入框中输入 ’ OR ’1’=’1 那么由于我们的SQL是靠拼出来的,所以最终提交给数据库的将是: SELECT * FROM TD_ADMIN AS A WHERE A.USERNAME=’123’ AND A.PASSWORD=’’ OR ’1’=’1’ 很显然,这句SQL 由于后面被加上了永真条件,登陆是一定成功的。那么不论登陆者是否是管理员,输入 ‘ OR ‘1’=’1 ,他都将能够登陆系统。 对策之一: Java 中对该模式的实现有PreparedStatment或者NamingQuery一类的技术,实现了参数与逻辑的分离,可以从根本上杜绝SQL注入。

  48. 2)脚本注入 • 这里所说的脚本,通常指的是JavaScript脚本,虽然JavaScript运行于客户端,并且有安全沙箱的保护,但是放任恶意JavaScript脚本是十分危险的 • 一个网站,如果对输入未做合理的验证或过滤,在显示输出的时候又未做合适的格式化,那么便存在这种漏洞 • 与SQL注入不同,脚本注入没有直接攻击服务端,实际上是攻击了客户端 • 窃取信息!

  49. 假设我们有一个新闻站点,每个新闻允许浏览者进行评论假设我们有一个新闻站点,每个新闻允许浏览者进行评论 浏览者提交的评论将被存储在数据据库专门的表中,并显示在新闻的下边 如果开发者除了字符长度外没有做任何输入合法验证,那么这个站点的评论输入,就存在安全漏洞。 假设我们在评论中输入如下内容: <script language=”javascript”>alert(“这里存在脚本注入漏洞.”);</script> 那么,这句话将被存储在数据库评论表中。 以后,每一个浏览者打开这个新闻页面是,都将会弹出这样一个消息框。 上面的攻击者很仁慈,没有做过多的破坏。但是如果输入: <script language=”javascript”>window.location.href=”www.baidu.com”;</script> 那么打开这个新闻页面,该页面将被从定向到baidu的页面上。 如果目标页面一个有恶意代码的页面,那么每个浏览者的机器都可能中毒。

  50. 如果输入: 好文! 顶 <iframe src=”带病毒的页面” width=”0” height=”0”></iframe> 那么在新闻页面上看不到任何异状 但点击该信息的浏览器会悄悄下载病毒 WEB2.0的流行使页面效果更加绚丽,同时也使脚本注入的攻击力提高不少 对策之一: 提供合理的过滤或者转换程序,在输入存放于数据库前,或者是显示在页面前对数据进行处理。 尽一切可能,避免用户的输入有执行的可能。

More Related