符号执行入坑指南

2024-01-22 21:48
文章标签 指南 入坑 符号执行

本文主要是介绍符号执行入坑指南,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

0x10 符号执行的定义

符号执行是软件测试行业新型的一个方向,当下较为火热,也给安全测试提供了新的思路,衍生出了诸多安全测试工具。维基百科中定义的符号执行如下

在计算机科学中,符号执行技术指的是通过程序分析的方法,确定哪些输入向量会对应导致程序的执行结果向量的方法。

抛开专业术语的表层,拨云见日,符号执行就是使用符号值代替真实值执行。在计算机程序中,尤其是C语言,符号是指函数名和变量名,在这里,更多的是指变量。符号执行就是说,不给定函数变量(比如入参)特定的值,而是用变量本身来替代,达到模拟输入的效果,即模拟每个路径来产生每一个执行的可能性,将执行语句的结果整合成若干条数学表达式。

符号执行有两篇文章可以参考。其一是2010年David Brumley团队在S&P会议上发表的《All You Ever Wanted to Knowabout Dynamic Taint Analysis and Forward Symbolic Execution (but Might HaveBeen Afraid to Ask)》[1]。这篇文章同时介绍了动态污点分析和前向符号执行的基本概念,作者构造了一种简化的中间语言,用来形式化地描述这两种程序分析技术的根本原理。其二是2011年CristianCadar发表在ACM通讯上的一篇短文《Symbolic execution forsoftware testing: three decades later》[2],以较为通俗的语言和简单的例子阐述了符号执行的基本原理,并介绍了符号执行技术的发展历程和面临挑战。

0x20 符号执行的分类

符号执行技术是一种白盒的静态分析技术。即,分析程序可能的输入需要能够获取到目标源代码的支持。同时,它是静态的,因为并没有实际的执行程序本身,而是分析程序的执行路径。符号执行根据发展状况可以分为传统的符号执行、动态符号执行和选择性符号执行。

  • 传统的符号执行其实是通过解析程序,利用符号值来模拟执行
  • 动态符号执行结合真实执行和传统的模拟执行,两者并行,达到真实值以及符号同时执行的效果
  • 选择性符号执行则是按照手动划分感兴趣的部分,有选择性的进行符号执行,剩下则用真实值进行执行

0x30 控制流程图-符号执行树

我们以实际的例子来说明。函数代码如下

int d(int x){int y = 2*x;y--;if(y==2)x++;elsex--;return x;
}

该代码很简单,有一个if语句的条件分支,下图就是程序的全部路径运行结果,可以选择对应的赋值来到达对应的运行结果在这里插入图片描述

0x40 实际应用

安全客上有一篇文章,讲解的很好:符号执行——从入门到高速。我更欣赏该文章讲解的实际应用方面。我们知道,由于物联网设备逐渐的大众化,嵌入式设备的漏洞挖掘需求更加显著。那么在嵌入式的环境下,使用符号执行技术挖掘漏洞可行吗?答案是肯定的。但是要解决很多问题,需要解决的问题除了类似于KLEE中出现的与外设交互的系统调用建模以外,还需要解决嵌入式设备中经常出现的混合代码情况。即,给出的嵌入式源代码中,有c语言代码、二进制代码(共享库)和汇编代码。面对这种情况应该如何启用符号执行呢?

举个实际的例子,以下是基于ARMv7架构的一段嵌入式代码。学过嵌入式的人都知道,在嵌入式设备中,仅仅使用C语言往往还是不够的,因为汇编有时候能够更好的与硬件交互,以下代码使用了汇编和C语言的混合编程,能够极大提高效率。

unsigned char msg[] = "world";
int index;void uart_send(unsigned char a)
{__asm volatile("SVC #0");__asm volatile("BX LR");
}void os_uart_send(){...}int main(int argc, char* argv[])
{uart_send(msg[index++]);return 0;
}

该代码的主要作用是通过uart发送消息。函数uart_send()中,通过指令“SVC #0”切换成SVC的寄存器模式之后,将实际上调用函数os_uart_send()发送消息。其中,uart_send函数的参数将直接在os_uart_send()函数中使用。上述代码,通过LLVM转换成LLVM-IR的形式,为下面的代码。

@msg = global [6xi8]c"world0"
@index = global i32 0define void @uart_send(i8 zeroext) #0
{entry:call void asm sideeffect "SVC #0",""()call void asm sideeffect "BX LR",""()unreachable
}define void @os_uart_send(){...}define int @main() #1
{entry:%0 = load i32* @index%inc = add nsw i32 %0, 1store i32 %inc, i32* @index%arrayidx = getelementptr inbounds [6xi8]* @msg, i32 0, i32 %0%1 = load i8* %arrayidxcall void @uart_send(i8 zeroext %1)ret i32 0
}

为什么要转换成LLVM-IR的代码呢。LLVM-IR是LLVM的一种中间的语言表达形式,也是一种汇编语言的形式。现有的KLEE工具就是LLVM-IR工具实现的符号执行虚拟机。在解释上面的代码之前,简单介绍几个LLVM-IR的基础语法,以便更清楚的理解。

LLVM-IR的变量有三种,通过前缀@或者%的形式区分,其中@表示全局变量,%表示局部变量:

  1. %或者@接数字,表示的是临时变量,在一个函数中,从0开始编号使用。比如%0,%1,。。
  2. %或者@接字符串,表示有名字的变量,可以任意使用;
  3. 第三类就是立即数
@msg = global [6xi8]c"world0"
@index = global i32 0

上述代码初始化了全局变量msg和index。

entry:
%0 = load i32* @index
%inc = add nsw i32 %0, 1
store i32 %inc, i32* @index

上述代码中,i32表示是32位的类型,i32表示的指向i32类型的指针类型。%0 = load i32 @index表示将全局变量index的值赋值给局部变量%0. Add nsw是有符号的相加。再加完之后,又把数值存储回了全局变量index。

%arrayidx = getelementptr inbounds [6xi8]* @msg, i32 0, i32 %0
%1 = load i8* %arrayidx
call void @uart_send(i8 zeroext %1)

上述代码中,[6xi8] 这种形式表示的是数组类型,这里就是含有6个8位的元素的数组。Getelementptr inbounds即使获得数组对应元素的指针。之后利用语句%1 = load i8* %arrayidx获取指针指向的8位的值,在传入到函数uart_send中执行。

可以发现,如果按照已有的KLEE方法,在转换成LLVM-IR代码之后,由于代码中含有arm架构的汇编,而该arm代码中没有明显的调用参数的代码和方式,使得符号数值的传递再次中断,导致KLEE方法不能执行。因此,我们需要对上述含有混合LLVM-IR代码的内存进行再次转化,使得含有低级语意的arm汇编也能够被KLEE的符号执行虚拟机分析。

通过程序分析的方式,将混合由高级语言和低级语言的代码同时转化成KLEE能够分析的语言,从而执行符号执行的分析。根据Inception里面的思想,上述LLVM-IR转化之后,可以有下面的表达形式:

@msg = global [6xi8]c"world0"
@index = global i32 0; stack is stored in global variables
@R0 = global i32 0, align 4
@SP = global i32 0, align 4 
@_SVC_fe = global i32 0, align 4 
@LR = global i32 0, align 4 
@.stack = global [8202xi4] zeroinitializer define void @uart_send(i8) #0
{; pass the parameters from high level to the low level.entry:%1 = zext i8 %0 to i32store i32 %1, i32* @R0br label %"uart_send+0" ; jmp to the actual code"uart_send+0":%SP1 = load i32* @SP ; load the stack pointerstore i32 0, iew* @_SVC_festore i32 268436580, i32* @PC ; store pointer executioncall void (...)* @_sv_call()call void (...)* os_uart_send()    ; invoke uart send function and  using the value from the R0%LR1 = load i32* @LR1              ;load return addressret void
}define void @os_uart_send(){...}define int @main() #1
{entry:%0 = load i32* @index%inc = add nsw i32 %0, 1store i32 %inc, i32* @index%arrayidx = getelementptr inbounds [6xi8]* @msg, i32 0, i32 %0%1 = load i8* %arrayidxcall void @uart_send(i8 zeroext %1)ret i32 0
}

可以发现,相比于之前的LLVM-IR。函数uart_send中多了一部分内容。更多内容参考之前提到的文章就行了。

0x50 总结

鉴于代码覆盖率越高越好,符号执行的要求如下

  • 尽可能的覆盖所有的可能的执行路径.
  • 速度尽可能快.

因为这样才能达到测试所有代码的目的,当然这是理论上的想法,实际过程中,项目庞大而复杂。例如对于一组分支组成的树形结构,最少有O(n)O(n)O(n)种指派方案可以覆盖到所有路径,也就是说最小的复杂度是线性的,最多有O(2n)O(2^n)O(2 n )种指派方案,是指数增长类型。这就是路径爆炸

所以,需要找到一个平衡点。符号执行实质是通过限制路径搜索空间来进行程序遍历,运行时间与所需要遍历的路径空间大小成正比。利用可靠的程序分析技术来减小路径爆炸的复杂度,利用启发式搜索搜索最佳路径,这些都是符号执行追求的终极目标。

这篇关于符号执行入坑指南的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/634339

相关文章

Retrieval-based-Voice-Conversion-WebUI模型构建指南

一、模型介绍 Retrieval-based-Voice-Conversion-WebUI(简称 RVC)模型是一个基于 VITS(Variational Inference with adversarial learning for end-to-end Text-to-Speech)的简单易用的语音转换框架。 具有以下特点 简单易用:RVC 模型通过简单易用的网页界面,使得用户无需深入了

Java 创建图形用户界面(GUI)入门指南(Swing库 JFrame 类)概述

概述 基本概念 Java Swing 的架构 Java Swing 是一个为 Java 设计的 GUI 工具包,是 JAVA 基础类的一部分,基于 Java AWT 构建,提供了一系列轻量级、可定制的图形用户界面(GUI)组件。 与 AWT 相比,Swing 提供了许多比 AWT 更好的屏幕显示元素,更加灵活和可定制,具有更好的跨平台性能。 组件和容器 Java Swing 提供了许多

基于UE5和ROS2的激光雷达+深度RGBD相机小车的仿真指南(五):Blender锥桶建模

前言 本系列教程旨在使用UE5配置一个具备激光雷达+深度摄像机的仿真小车,并使用通过跨平台的方式进行ROS2和UE5仿真的通讯,达到小车自主导航的目的。本教程默认有ROS2导航及其gazebo仿真相关方面基础,Nav2相关的学习教程可以参考本人的其他博客Nav2代价地图实现和原理–Nav2源码解读之CostMap2D(上)-CSDN博客往期教程: 第一期:基于UE5和ROS2的激光雷达+深度RG

如何掌握面向对象编程的四大特性、Lambda 表达式及 I/O 流:全面指南

这里写目录标题 OOP语言的四大特性lambda输入/输出流(I/O流) OOP语言的四大特性 面向对象编程(OOP)是一种编程范式,它通过使用“对象”来组织代码。OOP 的四大特性是封装、继承、多态和抽象。这些特性帮助程序员更好地管理复杂的代码,使程序更易于理解和维护。 类-》实体的抽象类型 实体(属性,行为) -》 ADT(abstract data type) 属性-》成

使用条件变量实现线程同步:C++实战指南

使用条件变量实现线程同步:C++实战指南 在多线程编程中,线程同步是确保程序正确性和稳定性的关键。条件变量(condition variable)是一种强大的同步原语,用于在线程之间进行协调,避免数据竞争和死锁。本文将详细介绍如何在C++中使用条件变量实现线程同步,并提供完整的代码示例和详细的解释。 什么是条件变量? 条件变量是一种同步机制,允许线程在某个条件满足之前进入等待状态,并在条件满

Java 入门指南:Java 并发编程 —— 并发容器 ConcurrentLinkedDeque

文章目录 ConcurrentLinkedDeque特点构造方法常用方法使用示例注意事项 ConcurrentLinkedDeque ConcurrentLinkedDeque 是 Java 并发工具包(java.util.concurrent 包)中的一个线程安全的双端队列(Deque)实现,实现了 Deque 接口。它使用了链表结构,并且针对高并发环境进行了优化,非常适合

使用Nginx部署前端Vue项目的详细指南

在本文中,我们将详细介绍如何使用Nginx部署一个前端Vue项目。此过程涵盖Vue项目的构建、Nginx的安装与配置、以及最后的项目启动。下面是步骤的详细说明。 步骤 1: 准备你的Vue项目 确保你已经创建并构建了一个Vue项目。如果你尚未创建Vue项目,可以使用以下命令创建一个: # 安装Vue CLInpm install -g @vue/cli# 创建一个新的Vue项目vue c

入门指南 | Datavines 安装部署篇

摘要:本文主要介绍基于源码部署 Datavines 和执行检查作业,内容主要分为以下几个部分: 平台介绍快速部署运行数据质量检查作业 Datavines 的目标是成为更好的数据可观测性领域的开源项目,为更多的用户去解决元数据管理和数据质量管理中遇到的问题。在此我们真诚欢迎更多的贡献者参与到社区建设中来,和我们一起成长,携手共建更好的社区。 https://github.com/dat

如何将卷积神经网络(CNN)应用于医学图像分析:从分类到分割和检测的实用指南

引言 在现代医疗领域,医学图像已经成为疾病诊断和治疗规划的重要工具。医学图像的类型繁多,包括但不限于X射线、CT(计算机断层扫描)、MRI(磁共振成像)和超声图像。这些图像提供了对身体内部结构的详细视图,有助于医生在进行准确诊断和制定个性化治疗方案时获取关键的信息。 1. 医学图像分析的挑战 医学图像分析面临诸多挑战,其中包括: 图像数据的复杂性:医学图像通常具有高维度和复杂的结构

从零开始构建大语言模型并进行微调:全面指南

要从0开始搭建并训练一个大语言模型(LLM),涉及到多个步骤和资源,包括理论理解、工具使用、数据准备、模型训练与微调。以下是一个从基础到应用的指南,帮助你理解并逐步实现这一目标。 1. 理解基础概念 在开始搭建大语言模型之前,了解以下基本概念至关重要: 生成式AI:通过大语言模型生成自然语言文本,例如GPT、BERT等。机器学习:通过数据训练模型,使其具备从数据中学习规律的能力。深度学习:机