📊
CTF-All-In-One
  • 简介
  • 前言
  • 一、基础知识篇
    • 1.1 CTF 简介
    • 1.2 学习方法
    • 1.3 Linux 基础
    • 1.4 Web 安全基础
      • 1.4.1 HTML 基础
      • 1.4.2 HTTP 协议基础
      • 1.4.3 JavaScript 基础
      • 1.4.4 常见 Web 服务器基础
      • 1.4.5 OWASP Top Ten Project 漏洞基础
      • 1.4.6 PHP 源码审计基础
    • 1.5 逆向工程基础
      • 1.5.1 C/C++ 语言基础
      • 1.5.2 汇编基础
      • 1.5.3 Linux ELF
      • 1.5.4 Windows PE
      • 1.5.5 静态链接
      • 1.5.6 动态链接
      • 1.5.7 内存管理
      • 1.5.8 glibc malloc
      • 1.5.9 Linux 内核
      • 1.5.10 Windows 内核
      • 1.5.11 jemalloc
    • 1.6 密码学基础
      • 1.6.1 密码学导论
      • 1.6.2 流密码
      • 1.6.3 分组密码
      • 1.6.4 公钥密码
      • 1.6.5 消息认证和哈希函数
      • 1.6.6 数字签名
      • 1.6.7 密码协议
      • 1.6.8 密钥分配与管理
      • 1.6.9 数字货币
    • 1.7 Android 安全基础
      • 1.7.1 Android 环境搭建
      • 1.7.2 Dalvik 指令集
      • 1.7.3 ARM 汇编基础
      • 1.7.4 Android 常用工具
  • 二、工具篇
    • 虚拟化分析环境
      • 2.1.1 VirtualBox
      • 2.1.2 QEMU
      • 2.1.3 Docker
      • 2.1.4 Unicorn
    • 静态分析工具
      • 2.2.1 radare2
      • 2.2.2 IDA Pro
      • 2.2.3 JEB
      • 2.2.4 Capstone
      • 2.2.5 Keystone
      • 2.2.6 Ghidra
    • 动态分析工具
      • 2.3.1 GDB
      • 2.3.2 OllyDbg
      • 2.3.3 x64dbg
      • 2.3.4 WinDbg
      • 2.3.5 LLDB
    • 其他工具
      • 2.4.1 pwntools
      • 2.4.2 zio
      • 2.4.3 metasploit
      • 2.4.4 binwalk
      • 2.4.5 Burp Suite
      • 2.4.6 Wireshark
      • 2.4.7 Cuckoo Sandbox
  • 三、分类专题篇
    • Pwn
      • 3.1.1 格式化字符串漏洞
      • 3.1.2 整数溢出
      • 3.1.3 栈溢出
      • 3.1.4 返回导向编程(ROP)(x86)
      • 3.1.5 返回导向编程(ROP)(ARM)
      • 3.1.6 Linux 堆利用(一)
      • 3.1.7 Linux 堆利用(二)
      • 3.1.8 Linux 堆利用(三)
      • 3.1.9 Linux 堆利用(四)
      • 3.1.10 内核 ROP
      • 3.1.11 Linux 内核漏洞利用
      • 3.1.12 Windows 内核漏洞利用
      • 3.1.13 竞争条件
      • 3.1.14 虚拟机逃逸
    • Reverse
      • 3.2.1 patch 二进制文件
      • 3.2.2 脱壳技术(PE)
      • 3.2.3 脱壳技术(ELF)
      • 3.2.4 反调试技术(PE)
      • 3.2.5 反调试技术(ELF)
      • 3.2.6 指令混淆
    • Web
      • 3.3.1 SQL 注入利用
      • 3.3.2 XSS 漏洞利用
    • Crypto
    • Misc
      • 3.5.1 Lsb
    • Mobile
  • 四、技巧篇
    • 4.1 Linux 内核调试
    • 4.2 Linux 命令行技巧
    • 4.3 GCC 编译参数解析
    • 4.4 GCC 堆栈保护技术
    • 4.5 ROP 防御技术
    • 4.6 one-gadget RCE
    • 4.7 通用 gadget
    • 4.8 使用 DynELF 泄露函数地址
    • 4.9 shellcode 开发
    • 4.10 跳转导向编程(JOP)
    • 4.11 利用 mprotect 修改栈权限
    • 4.12 利用 __stack_chk_fail
    • 4.13 利用 _IO_FILE 结构
    • 4.14 glibc tcache 机制
    • 4.15 利用 vsyscall 和 vDSO
  • 五、高级篇
    • 5.0 软件漏洞分析
    • 5.1 模糊测试
      • 5.1.1 AFL fuzzer
      • 5.1.2 libFuzzer
    • 5.2 动态二进制插桩
      • 5.2.1 Pin
      • 5.2.2 DynamoRio
      • 5.2.3 Valgrind
    • 5.3 符号执行
      • 5.3.1 angr
      • 5.3.2 Triton
      • 5.3.3 KLEE
      • 5.3.4 S²E
    • 5.4 数据流分析
      • 5.4.1 Soot
    • 5.5 污点分析
      • 5.5.1 TaintCheck
    • 5.6 LLVM
      • 5.6.1 Clang
    • 5.7 程序切片
    • 5.8 SAT/SMT
      • 5.8.1 Z3
    • 5.9 基于模式的漏洞分析
    • 5.10 基于二进制比对的漏洞分析
    • 5.11 反编译技术
      • 5.11.1 RetDec
  • 六、题解篇
    • Pwn
      • 6.1.1 pwn HCTF2016 brop
      • 6.1.2 pwn NJCTF2017 pingme
      • 6.1.3 pwn XDCTF2015 pwn200
      • 6.1.4 pwn BackdoorCTF2017 Fun-Signals
      • 6.1.5 pwn GreHackCTF2017 beerfighter
      • 6.1.6 pwn DefconCTF2015 fuckup
      • 6.1.7 pwn 0CTF2015 freenote
      • 6.1.8 pwn DCTF2017 Flex
      • 6.1.9 pwn RHme3 Exploitation
      • 6.1.10 pwn 0CTF2017 BabyHeap2017
      • 6.1.11 pwn 9447CTF2015 Search-Engine
      • 6.1.12 pwn N1CTF2018 vote
      • 6.1.13 pwn 34C3CTF2017 readme_revenge
      • 6.1.14 pwn 32C3CTF2015 readme
      • 6.1.15 pwn 34C3CTF2017 SimpleGC
      • 6.1.16 pwn HITBCTF2017 1000levels
      • 6.1.17 pwn SECCONCTF2016 jmper
      • 6.1.18 pwn HITBCTF2017 Sentosa
      • 6.1.19 pwn HITBCTF2018 gundam
      • 6.1.20 pwn 33C3CTF2016 babyfengshui
      • 6.1.21 pwn HITCONCTF2016 Secret_Holder
      • 6.1.22 pwn HITCONCTF2016 Sleepy_Holder
      • 6.1.23 pwn BCTF2016 bcloud
      • 6.1.24 pwn HITCONCTF2016 House_of_Orange
      • 6.1.25 pwn HCTF2017 babyprintf
      • 6.1.26 pwn 34C3CTF2017 300
      • 6.1.27 pwn SECCONCTF2016 tinypad
      • 6.1.28 pwn ASISCTF2016 b00ks
      • 6.1.29 pwn Insomni'hack_teaserCTF2017 The_Great_Escape_part-3
      • 6.1.30 pwn HITCONCTF2017 Ghost_in_the_heap
      • 6.1.31 pwn HITBCTF2018 mutepig
      • 6.1.32 pwn SECCONCTF2017 vm_no_fun
      • 6.1.33 pwn 34C3CTF2017 LFA
      • 6.1.34 pwn N1CTF2018 memsafety
      • 6.1.35 pwn 0CTF2018 heapstorm2
      • 6.1.36 pwn NJCTF2017 messager
      • 6.1.37 pwn sixstarctf2018 babystack
      • 6.1.38 pwn HITCONCMT2017 pwn200
      • 6.1.39 pwn BCTF2018 house_of_Atum
      • 6.1.40 pwn LCTF2016 pwn200
      • 6.1.41 pwn PlaidCTF2015 PlaidDB
      • 6.1.42 pwn hacklu2015 bookstore
      • 6.1.43 pwn 0CTF2018 babyheap
      • 6.1.44 pwn ASIS2017 start_hard
      • 6.1.45 pwn LCTF2016 pwn100
    • Reverse
      • 6.2.1 re XHPCTF2017 dont_panic
      • 6.2.2 re ECTF2016 tayy
      • 6.2.3 re CodegateCTF2017 angrybird
      • 6.2.4 re CSAWCTF2015 wyvern
      • 6.2.5 re PicoCTF2014 Baleful
      • 6.2.6 re SECCONCTF2017 printf_machine
      • 6.2.7 re CodegateCTF2018 RedVelvet
      • 6.2.8 re DefcampCTF2015 entry_language
    • Web
      • 6.3.1 web HCTF2017 babycrack
    • Crypto
    • Misc
    • Mobile
  • 七、实战篇
    • CVE
      • 7.1.1 CVE-2017-11543 tcpdump sliplink_print 栈溢出漏洞
      • 7.1.2 CVE-2015-0235 glibc __nss_hostname_digits_dots 堆溢出漏洞
      • 7.1.3 CVE-2016-4971 wget 任意文件上传漏洞
      • 7.1.4 CVE-2017-13089 wget skip_short_body 栈溢出漏洞
      • 7.1.5 CVE–2018-1000001 glibc realpath 缓冲区下溢漏洞
      • 7.1.6 CVE-2017-9430 DNSTracer 栈溢出漏洞
      • 7.1.7 CVE-2018-6323 GNU binutils elf_object_p 整型溢出漏洞
      • 7.1.8 CVE-2010-2883 Adobe CoolType SING 表栈溢出漏洞
      • 7.1.9 CVE-2010-3333 Microsoft Word RTF pFragments 栈溢出漏洞
    • Malware
  • 八、学术篇
    • 8.1 The Geometry of Innocent Flesh on the Bone: Return-into-libc without Function Calls (on the x86)
    • 8.2 Return-Oriented Programming without Returns
    • 8.3 Return-Oriented Rootkits: Bypassing Kernel Code Integrity Protection Mechanisms
    • 8.4 ROPdefender: A Detection Tool to Defend Against Return-Oriented Programming Attacks
    • 8.5 Data-Oriented Programming: On the Expressiveness of Non-Control Data Attacks
    • 8.7 What Cannot Be Read, Cannot Be Leveraged? Revisiting Assumptions of JIT-ROP Defenses
    • 8.9 Symbolic Execution for Software Testing: Three Decades Later
    • 8.10 AEG: Automatic Exploit Generation
    • 8.11 Address Space Layout Permutation (ASLP): Towards Fine-Grained Randomization of Commodity Softwa
    • 8.13 New Frontiers of Reverse Engineering
    • 8.14 Who Allocated My Memory? Detecting Custom Memory Allocators in C Binaries
    • 8.21 Micro-Virtualization Memory Tracing to Detect and Prevent Spraying Attacks
    • 8.22 Practical Memory Checking With Dr. Memory
    • 8.23 Evaluating the Effectiveness of Current Anti-ROP Defenses
    • 8.24 How to Make ASLR Win the Clone Wars: Runtime Re-Randomization
    • 8.25 (State of) The Art of War: Offensive Techniques in Binary Analysis
    • 8.26 Driller: Augmenting Fuzzing Through Selective Symbolic Execution
    • 8.27 Firmalice - Automatic Detection of Authentication Bypass Vulnerabilities in Binary Firmware
    • 8.28 Cross-Architecture Bug Search in Binary Executables
    • 8.29 Dynamic Hooks: Hiding Control Flow Changes within Non-Control Data
    • 8.30 Preventing brute force attacks against stack canary protection on networking servers
    • 8.33 Under-Constrained Symbolic Execution: Correctness Checking for Real Code
    • 8.34 Enhancing Symbolic Execution with Veritesting
    • 8.38 TaintEraser: Protecting Sensitive Data Leaks Using Application-Level Taint Tracking
    • 8.39 DART: Directed Automated Random Testing
    • 8.40 EXE: Automatically Generating Inputs of Death
    • 8.41 IntPatch: Automatically Fix Integer-Overflow-to-Buffer-Overflow Vulnerability at Compile-Time
    • 8.42 Dynamic Taint Analysis for Automatic Detection, Analysis, and Signature Generation of Exploits
    • 8.43 DTA++: Dynamic Taint Analysis with Targeted Control-Flow Propagation
    • 8.44 Superset Disassembly: Statically Rewriting x86 Binaries Without Heuristics
    • 8.45 Ramblr: Making Reassembly Great Again
    • 8.46 FreeGuard: A Faster Secure Heap Allocator
    • 8.48 Reassembleable Disassembling
  • 九、附录
    • 9.1 更多 Linux 工具
    • 9.2 更多 Windows 工具
    • 9.3 更多资源
    • 9.4 Linux 系统调用表
    • 9.5 python2到3字符串转换
    • 9.6 幻灯片
Powered by GitBook
On this page
  • 简介
  • 传统符号执行
  • 现代符号执行
  • Concolic Testing
  • Execution-Generated Testing(EGT)
  • 关键的挑战和解决方案
  • 路径爆炸
  • 约束求解
  • 内存建模

Was this helpful?

  1. 八、学术篇

8.9 Symbolic Execution for Software Testing: Three Decades Later

Previous8.7 What Cannot Be Read, Cannot Be Leveraged? Revisiting Assumptions of JIT-ROP DefensesNext8.10 AEG: Automatic Exploit Generation

Last updated 3 years ago

Was this helpful?

简介

近几年符号执行因其在生成高覆盖率的测试用例和发现复杂软件漏洞的有效性再次受人关注。这篇文章对现代符号执行技术进行了概述,讨论了这些技术在路径探索,约束求解和内存建模方面面临的主要挑战,并讨论了几个主要从作者自己的工作中获得的解决方案。

这算是一篇很经典很重要的论文了。

传统符号执行

符号执行的关键是使用符号值替代具体的值作为输入,并将程序变量的值表示为符号输入值的符号表达式。其结果是程序计算的输出值被表示为符号输入值的函数。一个符号执行的路径就是一个 true 和 false 组成的序列,其中第 i 个 true(或false)表示在该路径的执行中遇到的第 i 个条件语句,并且走的是 then(或else) 这个分支。一个程序所有的执行路径可以用执行树(Execution Tree)来表示。举一个例子:

函数 testme() 有 3 条执行路径,组成右边的执行树。只需要针对路径给出输入,即可遍历这 3 条路径,例如:{x = 0, y = 1}、{x = 2, y = 1} 和 {x = 30, y = 15}。符号执行的目标就是去生成这样的输入集合,在给定的时间内遍历所有的路径。

符号执行维护了符号状态 σ 和符号路径约束 PC,其中 σ 表示变量到符号表达式的映射,PC 是符号表示的不含量词的一阶表达式。在符号执行的初始化阶段,σ 被初始化为空映射,而 PC 被初始化为 true,并随着符号执行的过程不断变化。在对程序的某一路径分支进行符号执行的终点,把 PC 输入约束求解器以获得求解。如果程序把生成的具体值作为输入执行,它将会和符号执行运行在同一路径,并且以同一种方式结束。

例如上面的例子。符号执行开始时,符号状态 σ 为空,符号路径约束 PC 为 true。每当遇到输入语句 var = sym_input() 时,符号执行就会在符号状态 σ 中加入一个映射 var->s,这里的 s 是一个新的未约束的符号值,于是程序的前两行得到结果 σ = {x->x0, y->y0}。当遇到一个赋值语句 v = e 时,符号执行就会在符号状态 σ 中加入一个 v 到 σ(e) 的映射,于是程序执行完第 6 行后得到 σ = {x->x0, y->y0, z->2y0}。

当每次遇到条件语句 if(e) S1 else S2 时,PC 会被更新为 PC∧σ(e),表示 then 分支,同时生成一个新的路径约束 PC',初始化为 PC∧¬σ(e),表示 else 分支。如果 PC 是可满足的,那么程序会走 then 分支(σ 和 PC),否则如果 PC' 是可满足的,那么程序会走 else 分支(σ 和 PC')。值得注意的是,符号执行不同于实际执行,其实两条路都是可以走的,分别维护它们的状态就好了。如果 PC 和 PC' 都不能满足,那么符号执行就在对应的路径终止。例如,第 7 行建立了符号执行实例,路径约束分别是 x0 = 2y0 和 x0 ≠ 2y0,在第 8 行又建立了两个实例,分别是 (x0 = 2y0)∧(x0 > y0 + 10) 和 (x0 = 2y0)∧(x0 ≤ y0 + 10)。

如果符号执行遇到了 exit 语句或者 error,当前实例会终止,并利用约束求解器对当前路径约束求出一个可满足的值,这个值就构成了测试输入:如果程序输入了这些实际的值,就会在同样的路径结束。例如上图中三个绿色块里的值。

如果符号执行的代码包含循环或递归,且它们的终止条件是符号化的,那么可能就会导致产生无数条路径。举个例子:

这段程序的执行路径有两种:一种是无数的 true 加上一个 false,另一种是无数的 false。第一种的符号路径约束如下:

其中每个 Ni 都是一个新的符号值,执行结束的符号状态为 {N->Nn+1, sum->Σi∈[1,n]Ni}。在实践中我们需要通过一些方法限制这样的搜索。

传统符号执行的一个关键的缺点是,当符号路径约束包含了不能由约束求解器求解的公式时,就不能生成输入值。例如把上面的 twice 函数替换成下面的:

那么符号执行会得到路径约束 x0 ≠ (y0y0)mod50 和 x0 = (y0y0)mod50。更严格一点,如果我们不知道 twice 的源码,符号执行将得到路径约束 x0 ≠ twice(y0) 和 x0 = twice(y0)。在这两种情况下,符号执行都不能生成输入值。

现代符号执行

现代符号执行的标志和最大的优势是将实际执行和符号执行结合了起来。

Concolic Testing

Directed Automated Random Testing(DART) 在程序执行中使用了具体值,动态地执行符号执行。Concolic 执行维护一个实际状态和一个符号状态:实际状态将所有变量映射到实际值,符号状态只映射那些有非实际值的变量。Concolic 执行首先使用一些给定的或者随机的输入作为开始,收集执行过程中的条件语句对输入的符号约束,然后使用约束求解器推测输入的变化,从而引导下一次的程序执行到另一条路径。这个过程会不断地重复,直至探索完所有的执行路径,或者满足了用户定义的覆盖范围,又或者超出了预计的时间开销。

还是上面那个例子。Concolic 执行会生成一些随机输入,例如 {x = 22, y = 7},然后对程序同时进行实际执行和符号执行。这个实际执行会到达第 7 行的 else 分支,同时符号执行会为该实际执行路径生成路径约束 x ≠ 2y0。然后 Concolic 执行会将路径约束的连接词取反,求解 x0 = 2y0 得到测试输入 {x = 2, y = 1},这个新的输入将会让程序沿着另一条执行路径运行。然后 Concolic 执行在这个新的测试输入上,重复实际执行和符号执行的过程,此时将到达第 7 行的 then 分支和第 8 行的 else 分支,生成路径约束 (x0 = 2y0)∧(x0 ≤ y0 + 10),从而生成新的测试输入让程序执行没有被执行过的路径。通过对将结合项 (x0 ≤ y0 + 10) 取反得到的约束 (x0 = 2y0)∧(x0 > y0 + 10) 进行求解,得到测试输入 {x = 30, y = 15},然后程序到达了 ERROR 语句。这样程序的所有 3 条路径就都探索完了,其使用的策略是深度优先搜索。

Execution-Generated Testing(EGT)

EGT 是由 EXE 和 KLEE 实现和扩展的现代符号执行方法,它将程序的实际状态和符号状态进行了区分。EGT 在每次执行前会动态地检查所涉及的值是不是都是实际的值,如果是,则程序按照原样执行,否则,如果至少有一个值是符号值,程序会通过更新当前路径的条件符号化地执行。例如上面的例子,把 17 行的 y = sym_input() 改成 y = 10,那么第 6 行就会用实参 20 去调用 twice 函数,就像原程序那样执行。然后第 7 行将变成 if(20 == x),符号执行会通过添加约束 x = 20,走 then 分支,同时添加约束 x ≠ 20,走 else 分支。而在 then 分支上,第 8 行变成了 if(x > 20),不会到达 ERROR。

于是,传统符号执行中,因为外部函数或者无法进行约束求解造成的问题通过使用实际值得到了解决。但同时因为在执行中使用了实际值,固定了某些执行路径,由此将造成路径完成性的缺失。

关键的挑战和解决方案

路径爆炸

在时间和资源有限的情况下,符号执行应该对最相关的路径进行探索,主要有两种方法:启发式地优先探索最值得探索的路径,并使用合理的程序分析技术来降低路径探索的复杂性。

启发式搜索是用于确定路径搜索优先级的关键机制。大多数的启发式方法都专注于获得较高的语句和分支的覆盖率。一种有限的方法是使用静态控制流图来知道探索,尽量选择与未覆盖指令最接近的路径。另一种启发式方法是随机探索,即在两边都可行的符号化分支处随机选择一边。最近提出的一种方法是将符号执行与进化搜索相结合,其 fitness function 用于指导输入空间的搜索。

利用程序分析和软件验证的思想,以合理的方式减少路径探索的复杂性。一种简单的方法是使用 selelct 表达式进行静态融合,然后将其直接传递给约束求解器。这种方法在很多情况下都很管用,但实际上是将路径选择的复杂性传递给了约束求解器。还有一种方法是通过缓存和重用底层函数的计算结果,减小分析的复杂性。

约束求解

虽然近几年约束求解器的能力有明显提升,但依然是符号执行的关键瓶颈之一。因此,实现约束求解器的优化十分重要,这里讲两种方法:不相关约束消除和增量求解。

通常一个程序分支只依赖于一小部分的程序变量,因此一种有效的优化是从当前路径条件中移除与识别当前分支不相关的约束。例如,当前路径的条件是:(x + y > 10)∧(z > 0)∧(y < 12)∧(z - x = 0),我们想通过求解 (x + y > 10)∧(z > 0)∧¬(y < 12),其中 ¬(y < 12) 是取反的条件分支,那么我们就可以去掉对 z 的约束,因为其对 ¬(y < 12) 分支不会造成影响。减小后的约束会产生新的 x 和 y,我们用当前执行产生的 z 就可以产生新的输入了。

符号执行生成的约束集有一个重要的特征,就是它们被表示为程序源代码中的静态分支的固定集合。所以,多个路径可能会产生相似的约束集,所以可以使用相似的解决方案。通过重用以前相似请求得到的结果,可以提升约束求解的速度,这种方法被运用到了 CUTE 和 KLEE 中。在 KLEE 中,所有的请求结果都保存在缓存里,该缓存将约束集映射到实际的变量赋值。例如,在缓存中有这样一个映射:(x + y < 10)∧(x > 5) => {x = 6, y = 3}。利用这些映射,KLEE 可以迅速地解决一些相似的请求,例如请求 (x + y < 10)∧(x > 5)∧(y ≥ 0),KLEE 可以迅速检查得到 {x = 6, y = 3} 是可行的。

内存建模

程序语句翻译为符号约束的精确性对符号执行的覆盖率有很大的影响。

paper
img
img
img
img