Jekyll2025-12-08T03:33:06+00:00https://vancir.github.io/feed.xmlSong LiuA simple, whitespace theme for academics. Based on [*folio](https://github.com/bogoli/-folio) design. WinDBG CheatSheet2022-10-24T10:00:00+00:002022-10-24T10:00:00+00:00https://vancir.github.io/blog/2022/windbg-cheatsheetMain Extensions

Symbols

  • .sympath: get/set path for symbol search
  • .sympath +XY: append XY directory to the searched symbol path
  • !sym noisy: instructs windbg to display information about its search for symbols
  • dt ntdll!*: display all variables in ntdll

PEB and TEB

  • !peb: display PEB
  • dt nt!_PEB -r @$peb: full PEB dump
  • !teb: display TEB

Many WinDbg commands (lm, !dlls, !imgreloc, !tls, !gle) rely on the data retrieved from PEB and TEB

Process and Module

  • lm: list modules
  • lm vm kernel32: verbose output for kernel32
  • !dlls: dislay list of modules with loader-specific information
  • !dlls -c kernel32: only display information of kernel32
  • !imgreloc: display relocation information
  • !dh kernel32: display the header for kernel32

Threads Information

  • ~: thread status for all threads
  • ~0: thread status for thread 0
  • ~.: thread status for currently active thread
  • ~*: thread status for all threads with some extra info
  • ~* k: call stacks for all threads ~ !uniqstack
]]>
Writing an LLVM Pass2022-09-05T10:00:00+00:002022-09-05T10:00:00+00:00https://vancir.github.io/blog/2022/writing-an-llvm-passCompile LLVM from source
git clone [email protected]:llvm/llvm-project.git
pushd llvm-project
    git checkout llvmorg-10.0.1
    mkdir build
    pushd build
        cmake -G "Unix Makefiles" \
            -DLLVM_ENABLE_PROJECTS="clang;libcxx;libcxxabi" \
            -DCMAKE_BUILD_TYPE=DEBUG \
            ../llvm
        cmake --build .
        make -j$(nproc)
    popd
popd

Commands

  • Generate LLVM IR:
# Textual form
clang -O1 -emit-llvm input.c -S -o out.ll
# Binary/bit-code form
clang -O1 -emit-llvm input.c -c -o out.bc
  • Run HelloWorld pass with opt
opt -load-pass-plugin ./libHelloWorld.{so|dylib} -passes=hello-world -disable-output input_for_hello.ll
]]>
回到2015年看看那时ClusterFuzz的设计2021-04-17T11:59:38+00:002021-04-17T11:59:38+00:00https://vancir.github.io/blog/2021/the-design-of-clusterfuzz-in-20152015年彼时ClusterFuzz尚不够成熟但也已经是初见规模, ClusterFuzz作为Google目前开发了11年的平台, 其大概是19年才算是得以广泛运用. 网上公开了Abhishek Arya于2015年NullCon的分享PPT, 这应该也是他首次全面地介绍了ClusterFuzz的模块设计, 发展至今依旧保留来很多内容, 也是全网对于ClusterFuzz设计最深入的一份资料. 如果你发现NullCon官网上该PDF的链接已经失效, 那么你可以点击以下链接进行访问: ClusterFuzz

Architectural Overview

首先ClusterFuzz进行了前后端分离, 而所谓的AppEngine其实是Google云平台的一项服务, 相当于提供了一个平台让开发者去运行自己的代码而无需关心平台的维护. ClusterFuzz不可免地依赖了Google的不少云服务, 像图片里还有Google Compute Engine, Google Cloud Storage, Datastore, Blobstore就是如此.

前端提供了以下模块:

  • ClusterFuzz UI: 前端的展示界面, 用户可以进行操作, 彼时的UI相比现在而言粗糙不少, 但展示文字内容肯定也没什么问题了.
  • Task Pull Queues: 这应该是ClusterFuzz的任务队列, ClusterFuzz的后端定义了众多功能不同的task.
  • High replication datastore: 存储持续fuzzing过程中产生的各种元数据, 其实对应的是如今的NDB.
  • Blobstore: Blobstore现在应该是属于Google弃用的存储服务. 现今版本的ClusterFuzz在Blob上也是基于NDB实现的, 但也是因为这个历史原因, 所以单独抽出来作为blobs.py并保留了部分旧版本兼容代码. Blob则是指一些二进制数据, 比如binary, testcase, fuzzers, data bundles等.

后端则是从Task Pull Queues里获取Task并以Bot为单位执行, 其中Fuzz Task则是负责对Fuzz Target进行测试. 而这里Bots的Local Storage从我认知来看已经移除了, 目前保存在本地的数据应该只剩下缓存数据以及状态监控统计数据, 其他的数据基本都上传到云服务去了.

Glusterfs是一项分布式文件系统, 彼时被用于同步Bots之间的数据. 而目前版本已经移除了Glusterfs, 我自己也尚不清楚目前是如何处理Bots之间的数据同步问题, 印象里似乎并没有同步Bots之间的数据. 这有待我后续仔细阅读这部分的源码得出结论. Builder Bots也让我很迷惑, 但从名称来看应该是用于对源码的构建. 在目前的ClusterFuzz版本中我也尚未发现有这部分相关代码.

ClusterFuzz的实现目标

ClusterFuzz有三个主要目标:

  1. 自动完成Crash的检测, 分析和管理
  2. 可复现的Crash结果以及最小化的Testcases
  3. 实时的回归测试以及修复性测试

0x01 Crash自动化检测/分析/管理

Fuzzer编写要求

Fuzzer应当具备相同的使用命令以供调用, 比如run.* --input_dir=A --output_dir=B --no_of_files=C设计一套统一的命令行选项. 定义标签前缀, 比如“fuzz-”, “http-”, “flag-”等等. 具备跨平台性, 支持对多种编程语言开发的软件进行Fuzz. 使用Data Bundle来统一管理Fuzz资源, 并且也可以提供其他的脚本比如launcher, coverage等.

持续Fuzz平台的功能

设置好环境并对项目代码进行构建, 能够运行应用程序以及相关的测试代码. 能调整平台的参数比如设置手势, 设置工具的选项, 增加超时限制等. 能解决程序等资源依赖问题, 并对产出的Crash进行复现和去重, 将Crash, 覆盖率, 统计数据等进行妥善保存等.

Testcase的重复判定

禁用inline frames, 比如llvm-symbolizer关闭选项-inlining. 根据Crash Type, Crash State以及Security Flag来对Crash进行去重. 这里Crash State则是崩溃时StackTraces的前3个Frame, 并进行了一定的归一化, 比如保留namespace, 移除具体的line_numbers后作为特征进行去重. (这里的Security Flag我尚不清楚具体指的是什么, 但我想根据Crash Type和Crash State应该就能做到不错的去重效果了.)

Fuzzer的类型

Fuzzer分为三种: 基于生成的Fuzzer, 基于变异的Fuzzer, 可进化的Fuzzer.

基于生成的Fuzzer适用性更窄, 依赖于具体的格式或API. 能够迅速的找到Bug, 但无效也更快, 并且需要编写复杂的策略提高效果, 适合用于检验回归测试.

基于变异的Fuzzer则依赖于初始测试的样例, 而策略比较简单, 能够很好地适用于文件格式fuzz, 协议fuzz, 并且可以源源不断地挖掘Bugs. 但也有一些难以解决的情况比如计算checksum, 数据压缩操作等.

进化式Fuzzer则是基于反馈的Fuzzer, 目前主流的反馈指标则是使用的代码覆盖率. 代码在编译时会进行插装来反馈测试样例运行时的覆盖率信息, 并且通过共享存储聚合多个代码之间的覆盖率情况.

代码覆盖率部分彼时还是使用的fuzzer_utils来控制, 但从目前来看这些这些API都有了比较大的变化. 彼时针对Testcase会有以下规则: 对于原始Testcase, 如果增加了新的覆盖分支, 那么就将其加入到Optimal文件列表里去, 而如果该测试用例没有产生新的覆盖的话, 那么就会将其删除. 而对于Testcase发生改动并且产出了新覆盖率的时候, 就会将其加入到Corpus和Optimal文件列表中去. (无新覆盖分支那么就直接忽略.)

内存调试工具

Valgrind会产生10-300x的性能开销, 启动缓慢并且只能适用于堆漏洞, 因此并没有选择使用Valgrind.

ClusterFuzz选择了结合使用多种内存调试工具.

  • AddressSanitizer(ASan): 检测UAF, Buffer Overflows(heap, stack, globals), Stack-Use-After-Return, Container-Overflow等问题. CPU开销2x, 内存开销1.5x-3x
  • ThreadSanitizer(TSan): 检测Data Race, ESP on UAF, object vptr. CPU开销4x-10x, 内存开销5x-8x
  • MemorySanitizer(MSan): 检测Uninitialized Memory Reads. CPU开销3x, 内存开销2x
  • UndefinedBehaviorSanitizer(UBSan): 检测多约19种类型的bugs, esp on type confusion等.
  • 还提高了一些其他的工具, 但我想应该并没有使用上: SyzyASAN以及DrMemory.

0x02 可复现的Crash结果以及最小化Testcases

作者提出一种方式叫做Delta Debugging可以并行多线程地进行Minimize任务. 并且支持为某些文件类型进行Minimizer的定制.

Minimizer首先会将Input进行token化, 并假定某组Token并不是Crash所需要的, 假设移除掉这些Token后再次执行, 如果程序崩溃, 那么我们的移除就是可靠的. 而没有复现Crash, 那么就将移除的Token再细分成更小的组.

### 0x03 实时的回归测试以及修复性测试

作者提出了一个工具叫FindIt, 可以用于找出崩溃相关的ChangeLog(CL). 它的执行过程如下:

  1. 解析StackTrace获取相关的文件, 对应的崩溃代码行号.
  2. 在回归测试范围内解析ChangeLog, 获得代码文件相关的CL编号.
  3. 根据ChangeLog和之前解析的StackTrace, 对于一个CL编号, 如果该次改动影响了StackTrace中解析出来的代码行号位置, 那么就认为这个CL是可疑的. 并提供一个可疑CL列表.
  4. 如果StackTrace没有关联到相应的Cl编号, 那么就展示Blame信息看谁负责该处代码.
]]>
关于ClusterFuzz以及规模化持续Fuzzing的笔记2021-04-16T22:43:14+00:002021-04-16T22:43:14+00:00https://vancir.github.io/blog/2021/notes-about-clusterfuzz-and-continuous-fuzzingClusterFuzz的魅力在于它向我打开了一扇大门, 在降低了Fuzzing的使用门槛的同时让Fuzzing易于规模化, 只需要编写少量的代码就能让fuzzer持续地运行下去为你服务, 将过程中的崩溃等统计信息实时地展现在你面前, 我觉得这极具诱惑力并且蕴含着庞大的价值, 这就像安全研究的一个梦想, 一直督促我去学习这方面的内容. 以下我将就OSS-Fuzz负责人Abhishek Arya于Black Hat Europe 2019发表的议题ClusterFuzz: Fuzzing At Google Scale进行阐述.

持续Fuzzing

  • Fuzzing非常善于通过探索非预期的状态来找到Bugs.
  • 应当将Fuzzing无缝地嵌入到软件开发的生命周期里去, 用户提交类似单元测试一般的Fuzzer源码, 能通过Fuzzing规模化产出Bugs, Fuzzing Statistics和Code Coverage.
  • ClusterFuzz的目的在于将Fuzzing生命周期中, 除“fuzzer编写”和“Bug修复”过程外的所有流程都自动化运行起来.

Fuzzing生命周期:

0x01 Write fuzzers

fuzzing分为Blackbox fuzzing, Greybox fuzzing和Structure-aware fuzzing. 而对于Fuzzing规模化重要的不是增加服务器的CPU核心数, 而是引导开发者学习使用Fuzzing, 提供丰富的文档和示例便于编写Greybox fuzzer, 提供高效fuzzing的建议(种子池,字典等), 让Greybox fuzzing成为像单元测试那样的一等公民.

0x02 Build fuzzers

使用编译时插桩(ASan, MSan, 覆盖率插桩等), 跟fuzzing引擎或驱动链接起来(libFuzzer: clang -fsanitize=address,fuzzer). 确保Release版本经过了充分fuzzing, 持续地构建fuzzer(理想情况是能加入到已有的CI流程).

0x03 Fuzz at scale

  • Fuzzing任务管理: 可抢占VMs源源不断地产出新的Crashes, 非抢占式VMs则对Crashes进行处理(Minimize, Bisect等), 两种VMs都会将信息写入到Task queue以及DB上.
  • 挑选目标: 大型项目可能会包含有成千上万的fuzz目标, 因此需要具备自动发现fuzz目标的能力, 并且能根据fuzz目标的质量进行优先级挑选(高产出目标>低产出目标>无法正常启动的目标). 同时也可以针对Sanitizer进行优先级排列(ASan>MSan>Others(UBSan/CFI/TSAN))
  • Fuzzing策略: 并没有完美的启发式搜索策略, 可参考的策略包含Corpus subset, Value profiling, Custom mutators, Limiting maximum length of inputs. 另外ClusterFuzz里还采用里Radamsa mutator和ML-based RNN mutator来增强Corpus.
  • Fuzzing策略选择: 多臂老虎机(MAB)能够减少在低效fuzzing策略上的资源浪费, 尽量选择提高覆盖率的策略组合.

0x04 Triage crashes

  • De-duplication: 基于Stacktraces对崩溃进行去重, 选取前3“有趣”的栈帧作为CrashState, 包含debug和release断言, 去除内联frames, 公共库和debug函数. 忽略OOM和超时相关的Stacktrace.
  • Grouping: 第二阶段的去重(相对较慢), 使用编辑距离将所有相似的Crashes划分为同一个组, 去除那些只有轻微差异的相同crash, 就实际效果来看还不错.
  • Testcase minimization: 缩减Testcase更利于root cause分析. Greybox fuzzer通常会提供工具进行快速的缩减, 而Blackbox fuzzers则会使用基于Delta debugging的缩减方法, 效率低一些但胜在可以并行化.
  • Bisection: (这一段二分我不太懂, 我觉得需要阅读完ClusterFuzz源码后才能确定这里的二分是什么含义)
  • Variant analysis: Crash可以在sanitizers, fuzzing engines, platforms, architectures体现出不同的特征.
  • Automatic bug filing: 提供最小化的复现过程以及详细的崩溃报告
  • Prioritization: 不要对Bugs做过于深入的分析, 因为这样并不适合大规模的任务. 最好是假设所有的内存破坏都是可利用的, 将这些问题抛出来让人来判断. 基于崩溃的类型以及崩溃发生点来对崩溃进行简单的优先级排序, 将更有价值的崩溃优先呈现出来.
  • Fix verification: 验证Fix是否会造成崩溃, 并且关闭已经验证完毕的Bugs.
  • Vulnerability reward program: 可以提供额外的PoC来辅助漏洞的触发和缩减其他环节的任务. 让Fuzzer能持续地报告bug并产生高质量的报告.

0x05 Improving fuzzers

对Fuzzer的运行情况, Crash的详细信息进行数据收集和统计. 生成代码覆盖率的报告.

理想的持续集成平台

  • Fuzzing能融入到普通软件开发过程的一部分
  • 可以结合不同的Fuzzing引擎和策略进行大规模测试
  • 大型的软件项目可以仅由一个小团队负责维护的持续fuzzing平台进行高效fuzz.
  • 将Fuzzing作为持续集成CI的一部分, 不断地在回归测试中捕获Bugs
  • 平台也能用于对于一个fuzzer进行性能测试的解决方案
  • 能持续地去提高fuzzing的效率.
  • 能支持对更多的编程语言开发的软件进行测试.
]]>
我在学习和实践图数据库 Neo4j 的漫漫成长路2021-02-11T21:11:22+00:002021-02-11T21:11:22+00:00https://vancir.github.io/blog/2021/the-journey-of-learning-neo4j什么是图数据库和Neo4j?

传统关系型数据库通过文档字段来建立数据的关联, 但是这并不利于海量数据背景下的关系推导. 图数据库应运而生, 图数据库的上层就是我们熟悉的图网络结构, 而下层则是对图结构进行了性能优化, 使之能进行快速的关系推导. 图数据库在知识图谱(比如社交关系推导)和图神经网络(GNN)上有很大的应用.

Neo4j是业界主流的图数据库, 数据库目前排名是19(参见DB-Engines Ranking), 以点/关系进行存储, 支持百亿级别的查询. Neo4j分为免费的社区版和付费的企业版. 社区版只能进行单机部署, 企业版可以部署集群且性能上有着诸多优化, 以及解锁了诸多限制. ONgDB 是Neo4j付费闭源前的分支版本, 目前的最新版本为3.6.2, 跟Neo4j企业版本3.6功能相差无几.

ONgDB因果集群部署

因果集群基于Raft协议开发, Raft是一种更加易于理解的一致性算法, 可支持大规模和多拓扑结构的数据环境. 因果集群主要有以下两大特点:

  1. 安全性:核心服务器(Core)为事物平台处理提供了容错平台

  2. 可扩展性:只读副本(Read Replica)为图查询提供了一个大规模高可扩展的平台

集群部署会有一些坑点,也许是版本迭代升级出现的新问题,从网上搜集的现有资料并没有提及到这些问题和解决方法。

首先我的使用的版本是ongdb-enterprise-3.6.2,这是我之前就下载好存本地的,但此时在ONgDB仓库里将之前发布的版本都删除了(我推测是因为跟Neo4j打官司的原因),只留下约一个月前发布的1.0.0-alpha01版本,这也是ONgDB正式发布的第一个alpha测试版本,原先虽然名为ONgDB但其实代码里面都是以neo4j来命名,而这个1.0.0则将名称都改为了ongdb。并且ongdb和neo4j在配置上也并不完全一致,比如因果集群部分配置就是这样。我在使用1.0.0版本进行部署的时候主节点会卡在Attempting to connect to the other cluster members阶段,而ONgDB的文档内容非常少且网站也挂了,确认我配置没有问题后,我选择切回了旧的ongdb-enterprise-3.6.2版本。(并且此时ongdb-apoc还不兼容1.0.0版本)

我测试使用的是三台服务器,需要先配置好SSH密钥,让这三台服务器能通过SSH直接登录到彼此服务器。

随后参考Neo4j的集群配置文档,为配置文件conf/neo4j.conf改动以下内容(注意:尽量去找配置文件内已有的项进行修改,而不是直接在文件末尾新增,因为多个相同项Neo4j只取第一项作为结果):

dbms.default_listen_address=0.0.0.0
# 其他服务器用该地址来连接到节点,设定为节点的IP即可(无需端口)
dbms.default_advertised_address=core03.example.com
# 指定节点是核心节点还是冗余节点
dbms.mode=CORE
# 集群内所有节点的地址,集群节点启动后会不断通过该列表探测活跃的成员,默认端口5000
causal_clustering.initial_discovery_members=core01.example.com:5000,core02.example.com:5000,core03.example.com:5000
# (可选)该选项能避免集群在选举新的主节点前预先选举候选节点,这样就能避免因网络原因无法选举
causal_clustering.enable_pre_voting=true

所有节点的配置上其实除了dbms.default_advertised_address需要设置为自己的IP地址外,其他的配置都是一致的(当然还有mode你可以自己选定是作为核心节点还是冗余节点)。

配置完后逐个启动各个节点即可,如果没有成功启动,那么很有可能是数据不一致带来的问题,建议先将data清空或重命名其他名称,保证集群内各节点的data都是一致的,重新启动图数据库。

一些报错信息及解决办法

  • Waiting to hear from leader: 清空data或者重命名成其他名称
  • Unable to find transaction 1 in any of my logical logs: 数据不一致造成的问题,可以尝试清空data或者neo4j-admin unbind --database=graph.db
  • 对于因果集群要想使用neo4j-import导入全量数据,也会存在数据不一致造成启动失败的问题,所以官方给出的实践方案是,在一个节点上成功导入数据后,使用scp或rsync等工具直接导入的数据库传输到集群内其他节点服务器上。当然如果依然有报错情况的话,还需要执行neo4j-admin unbind --database=graph.db来解除绑定。

集群成功部署后,就可以通过call dbms.cluster.overview()call dbms.cluster.routing.getServers()来查看集群成员角色和请求路由信息。默认第一个启动的节点为作为主节点,其他节点则作为从节点。主节点负责协调集群并接受所有的写入,数据改动会同步到所有从节点上,主节点挂掉则其他从节点就会开始进行选举,投票出新的节点来作为主节点进行服务,集群在可用性上有很大的强化。

集群的连接方式基本也没有多少变化,原先单实例时的协议头为bolt://,而启用了路由的协议头则变更为bolt+routing://,协议头后面跟任意集群内节点的地址即可。当然集群内各节点也依然可以使用bolt://协议头单独用来查询数据(注意可读不一定可写)。另外要注意自己使用的驱动是否支持直接使用bolt+routing://协议头,像py2neo就不支持直接改协议头,但是可以在初始化Graph对象时增加routing=True选项启用路由模式。

关于性能优化的技巧

  • 图数据库配置conf/neo4j.con, 可以使用bin/neo4j-admin memrec来查看推荐的配置.

    # 堆内存
    dbms.memory.heap.initial_size=16384m
    dbms.memory.heap.max_size=16384m
    # 页面缓存
    dbms.memory.pagecache.size=80g
    
  • 创建索引来提高检索速度: create index on :Person(firstname), 使用:schema可以确认索引状态, 索引状态为ONLINE则表示索引已经生效.

  • 数据预热: 预先进行全图的查询来将图数据载入到缓存里, 来加快检索速度.如call apoc.warmup.run()match (n) optional match (n)-[r]->() return count(n) + count(r)

  • 根据情况将不同类型不同场景的数据进行拆分, 构建存储到不同的服务器上来减轻图数据库的压力.

  • 对于图数据的详细属性信息可以存储到ElasticSearch做复杂检索, 让图数据库专注于图分析和检索能力上(有插件支持). 可以参考这篇文章.

  • 使用apoc.path.subgraphNodes来遍历节点到所有关系远远快于使用多层关系查询, 因为多层关系查询实际会展开关系层数逐个查询.

  • 如果有确定的目标, 编写cypher语句时先match该目标再匹配路径, 如 match (n:Person{name: 'Peter'}) return (n)-[]->(), 而不是匹配路径再过滤路径上节点属性, 如match p=(n)-[]->() where n.name='Peter' return p . 后者会扫描全图.

  • 使用neo4j-import导入海量数据, 但该工具需要脱机并且只适用于空库, 数据可能还需要预处理生成CSV, 不过它的效率非常值得你这么做. 同时需要注意其导入时是可以用正则表达式来匹配多个文件名的(注意不是通配符),比如如下:

    bin/neo4j-admin import \
    	--nodes="import/movies4-header.csv,import/movies4-part.*" \
    	--nodes="import/actors4-header.csv,import/actors4-part.*" \
    	--relationships="import/roles4-header.csv,import/roles4-part.*"
    
  • 双向关系会极大地影响图数据库的遍历性能, 尽可能地不要这样做.如果真的发生了, 也可以参考这篇文章进行删除.

  • 图数据库里的环路也会极大地影响查询性能, 我解决环路的方法是检测路径中是否存在相同的节点. 使用apoc.coll.duplicates可以返回集合中的重复项.

  • 超级节点指的是拥有非常多关系/边的一类节点. 超级节点的存在会极大地影响入库/检索/分析的效率.

    • 在图数据建模的时候就应该确定好实体应该表示成节点还是标签. 比如“国家”这个实体如果设计成节点, 那么就很容易成为超级节点, 但设计成标签则不会带来性能的影响.
    • 关系结构优化: 将超级节点与其他节点的关系按照时间或者其他的层次关系进行分组, 这样既能提高查询的并发性也可以减少对超级节点的遍历开销.
    • 标签细分: 比如原来的标签就是社交媒体, 那么就可以将其细分到某个具体的社交平台.
  • 使用explainprofile来分析cypher语句的性能, 前者不会执行语句而后者会实际执行.

  • 使用带有补全提示的cycli来帮助更高效地编写cypher语句, 使用yFiles来对图进行可视化.

  • 删除两个节点之间的重复关系:

    MATCH p=(A:Test {name:'A'})-[r]->(B:Test {name:'B'})
    WITH ID(r) AS id,r.name AS name
    WITH name,COLLECT(id) AS relIds
    WITH name,relIds,SIZE(relIds) AS relIdsSize
    WHERE relIdsSize>1
    WITH name,apoc.coll.subtract(relIds, [relIds[0]]) AS deleteRelIds
    WITH name,deleteRelIds
    MATCH ()-[r]-() WHERE ID(r) IN deleteRelIds DELETE r
    

    更多条件分支操作可以参考如下

    CALL apoc.do.case([
      relationship=1,
      \'MATCH (from:Label {hcode:$fromHcode}),(to:Label {hcode:$toHcode}) 
        MERGE (from)-[:NEXT]->(to)\',
      relationship=-1,
      \'MATCH (from:Label {hcode:$fromHcode}),(to:Label {hcode:$toHcode}) 
        MERGE (from)<-[:NEXT]-(to)\'],
      \'\',
      {fromHcode:fromHcode,toHcode:toHcode}) 
    YIELD value RETURN value
    
  • 使用apoc.cypher.parallel并行执行查询, 例如如下会从名称列表中并行取出每个姓名, 搜索其邻居节点并返回姓名:

    CALL apoc.cypher.parallel(
      'MATCH (p:Person{name:$name}) -[:FRIEND_OF]-> (p1) RETURN p1.name AS name', 
      {name:['John','Mary','Peter','Wong','Chen','Lynas','Smith','Anna']},
      'name'
    )
    

学习资料

在学习图数据库和实践过程中其实积累了不少资料, 都是自己在初学和实践中遇到困难而去搜索的资料. 而其实这方面的资料差不多也就是这些. 基本上遇到了问题都能在这些地方找到答案.

主要资料

这些是学习和操作图数据库所必需了解的知识部分.

  • The Neo4j Getting Started Guide: Neo4j官方的入门指南
  • Cypher Manual: Cypher是Neo4j的查询语言, 查询语法比较简单直观, 但尽管如此, 如何判断Cypher语句正确/准确, 以及对Cypher语句进行优化是工作一直需要考虑的问题.
  • The Neo4j Operations Manual: Neo4j给出的操作手册, 可以大致浏览其中的内容, 因为遇到的很多问题可能最终都指向这里的解答.

其他资料

以下资料并非不重要, 而是用于扩展自己的学习面.

实际过程中会有许多需要进行谷歌搜索的事情, 大部分参考于stackoverflow的回答.

]]>
实践: 使用Golang进行机器学习2021-01-31T11:16:03+00:002021-01-31T11:16:03+00:00https://vancir.github.io/blog/2021/machine-learning-with-golang主流当下进行机器学习当然都是用的Python, 我的大部分工作也基本都是使用Python完成的. 但是Python的性能实在是太捉襟见肘, 所以我后面转向了Golang. 使用Golang进行机器学习对我而言是能更快地学习两者. 接下来我就来介绍在这过程中的学习和记录.

开发环境配置

我的机器配置是双AMD的3700x+5700xt, 安装的黑苹果macOS Catalina 10.15.7这其实还挺麻烦的.

  • Tensorflow: 只是需要注意Python的版本. Tensorflow的支持版本是3.5-3.8我的版本是3.9, 所以我还需要安装3.8版本的Python

    # brew install [email protected]
    # echo 'export PATH="/usr/local/opt/[email protected]/bin:$PATH"' >> ~/.zshrc
    pip install --upgrade pip
    pip install tensorflow
    
  • Mercurial: brew install mercurial

  • Minikube: brew install minikube

  • Pachyderm: brew tap pachyderm/tap && brew install pachyderm/tap/[email protected]

Golang进行数据处理

如果是手动解析数据的话, 需要手动定义数据格式, 并且对于异常数据的错误处理会很麻烦.

  • csv文件使用go-gota/gota/dataframe进行处理.
irisDF := dataframe.ReadCSV(f)
filter := dataframe.F{
	Colname:    "species",
	Comparator: "==",
	Comparando: "Iris-versicolor",
}
versicolorDF := irisDF.Filter(filter).Select([]string{"sepal_width", "species"}).Subset([]int{0, 1, 2})
  • json文件使用tidwall/gjsontidwall/sjson可以不需要定义schema就可以读取/设置
json, err := ioutil.ReadFile("../data/station_status.json")
value := gjson.Get(string(json), "last_updated")

modified_json, _ := sjson.Set(string(json), "last_updated", "123456")
modified_value := gjson.Get(modified_json, "last_updated")
  • 数据缓存: 使用patrickmn/go-cache在内存中缓存一系列数值
c := cache.New(5*time.Minute, 30*time.Second)
c.Set("mykey", "myvalue", cache.DefaultExpiration)
v, found := c.Get("mykey")
  • 数据本地缓存: boltdb/bolt被归档了, 所以我正在考虑其替代品Badger

  • 数据版本控制: minikube start && pachctl deploy local 在本地部署一个单机节点

向量和矩阵操作

使用 gonum/floats进行向量操作:

  • 定义向量: vector := []float64{11.0, 5.2, -1.3}
  • 点积: dotProduct := floats.Dot(vectorA, vectorB)
  • 标准化: floats.Scale(1.5, vectorA)
  • 范数: normA := floats.Norm(vectorA, 2)

使用gonum/mat进行操作

  • 定义向量: ` vectorA := mat.NewVecDense(3, []float64{11.0, 5.2, -1.3})`
  • 点积: dotProduct := mat.Dot(vectorA, vectorB)
  • 标准化: vectorA.ScaleVec(1.5, vectorA)
  • 范数: normB := blas64.Nrm2(vectorB.RawVector())

给定切片创建矩阵:

components := []float64{1.2, -5.7, -2.4, 7.3}
a := mat.NewDense(2, 2, components)
fa := mat.Formatted(a, mat.FormatPython())

访问和设置矩阵数值

val := a.At(0, 1)
col := mat.Col(nil, 0, a)
row := mat.Row(nil, 1, a)

a.Set(0, 1, 11.2)
a.SetRow(0, []float64{14.3, -4.2})
a.SetCol(0, []float64{1.7, -0.3})

加减乘等: d.Add(a, b), d.Sub(a, b), d.Mul(a, b), d.Pow(a, 5), d.Apply(sqrt, a)

行列式: deta := mat.Det(a), 转置: a.T(), 逆矩阵: aInverse.Inverse(a)

测量数据分布情况

使用gonum/statmontanaflynn/stats进行基本的数据测量: 均值: stat.Mean, 众数: stat.Mode, 中位数: stats.Median

]]>
论文: 开源包托管服务存在的供应链安全问题2021-01-07T23:32:40+00:002021-01-07T23:32:40+00:00https://vancir.github.io/blog/2021/attacks-on-package-ecosystem现代编程开发会依赖于各种功能丰富的库包, 许多编程语言都维护着一套包管理工具以及相应的代码托管平台, 这些的出现大大推进了开发的效率, 同样开发者们也能轻松地编写代码包发布给社区, 和社区成员进行协作分享和互动. 但随着社区规模的日益扩大, 带来的就是依赖包的信任问题, 也就是供应链安全问题.

本文我们来介绍一篇NDSS 2021上的论文“Towards Measuring Supply Chain Attacks on Package Managers for Interpreted Languages”, 该论文大规模测量了当前主流的三个开源包托管平台(PyPi, Npm, RubyGems)上的代码, 并检出了339个新型恶意软件包, 其中有278(82%)个已被平台确认并移除.

包管理生态里的各个角色

首先作者将包管理生态分为了以下四种角色:

  • 托管平台维护者: 负责平台的运行,管理和维护, 为开发者提供软件包的搜索和安装功能. 通常包含一个提供和管理软件包的WEB应用程序(比如pypi.org)和一个帮助访问包的客户端应用程序(比如pip). 托管平台要求开发者注册账户才允许发布自己的代码包.
  • 软件包维护者: 负责软件包的日常维护和开发, 将代码托管到平台上, 通常使用平台如Github来进行协作开发, 可以使用持续集成/持续开发流程自动化地进行包的编译和部署.
  • 开发者: 普通开发者可以进行代码包的开发, 同时也是发布包的直接使用者.
  • 终端用户: 终端用户是供应链的下游, 虽然没有直接跟包和平台进行接触, 但用户会使用到最终的产品.

平台方安全检查

论文作者对PyPi/Npm以及RubyGems的安全性分三部分(功能性/代码审查/响应措施)进行了测量评估.

  • 功能性检查侧重于检查平台方对包管理者的认证授权以及一些安全性的辅助功能.
  • 审核检查则在于平台方检出存在漏洞代码/恶意代码包的能力. 遗憾的是测试的三个平台没有一个具备该能力.
  • 补救响应功能则在于平台方在出现安全事故后是否积极地根据报告移除代码包, 封禁攻击者账户, 通知受害者尽快移除本地的危险包以及提供修复建议.

攻击向量

  • 利于平台服务的漏洞来篡改或注入恶意代码
  • 别名抢注: 注册跟热门包相似名称的包, 使不小心下载错误包的开发者执行恶意功能.

恶意包的功能通常包括窃取用户的敏感信息, 注入后门, 加密文件并进行勒索, 用于挖矿, 传播病毒, 黑产等.

分析方法

  • 元数据分析: 提取包的元数据信息(比如包名,作者,发布版本,下载次数,依赖等)标记出可能的恶意包.
  • 静态分析:
    • 敏感API标记: 标记网络/文件系统/进程/代码生成相关的API, 并可用于后续的数据流分析.
    • API使用情况分析: 将源码解析成AST形式然后搜寻标记API的使用情况.
    • 数据流分析: 检查代码的数据流的源, sink点和传播节点
  • 动态分析:
    • 执行代码包: 通过直接的install命令来安装, 对于嵌入在包内的二进制则在隔离的docker环境内进行执行, 对于import包则触发其包导入时的初始化逻辑, 对于导出函数则进行fuzz测试来触发其功能.
    • 动态跟踪: 使用sysdig来捕获代码运行时的系统调用trace.
  • 启发式规则: 作者定义了一系列的启发式规则来帮助分析和检测.
]]>
20年的光怪陆离和21年的未来可期2020-12-29T22:14:45+00:002020-12-29T22:14:45+00:00https://vancir.github.io/blog/2020/annual-summary-and-future-plan无论谁写下这篇博客, 思考最久的问题, 肯定都是自身吧. 我觉得只有此时此刻自己面对自己的内心时, 真诚的思考才能得出正确的答案, 即便所有的回答都不过像是回声一样, 在封闭的山谷里反复激荡,

振荡的疫情

如果要给20年定一个主线, 那无疑是疫情. 起初了解到武汉疫情的消息时, 谁能想到这场疫情会席卷全球并且持续这么长的时间. 20年过完春节我匆匆去往机场直飞北京, 当时跟出租车上的司机聊了起来. 他也知道武汉的疫情已经到了一个非常严重的地步, 他是武汉人, 但是没拗过家里老人把自己的儿子送回了武汉, 这一送进去就没有办法出来了, 还要冒着疫情的风险, 话语中无不是担忧. 后来我妈也跟我说到, 我当时要是晚走上一天, 小区也要封锁我也没法顺利回京. 疫情既已发生无可挽回, 我仿佛穿越时间回到了03年的非典现场, 恍若隔世犹如曾经柴静《看见》里描述的那样胆破心惊.

大概年中的时候, 一位高中同学邀请我一起做一个有关疫情传播模型的研究, 我觉得正好是新冠肆虐的时期做起来还蛮有意思的. 疫情的传播是一个复杂的问题, 但从查阅的资料来看, 医学和计算的结合并没有到一个很高的水平, 甚至于说其实是有很多研究都还在早期阶段有着大量的工作极需完善, 不过确实也有可能是由于数据的匮乏, 想深入研究并非易事吧.

开始尝试理财

中国的通货膨胀应该是相当严重吧, 所以一直都是有钱花钱的态度. 我比较认可那一套”用金钱换时间”的说法, 但想想可悲的却是人们必须通过工作”拿时间换金钱”, 今年的一个成长就是开始了解理财. 我对理财的期待并非财生财利滚利, 能够跑赢通货膨胀就实属大幸. 由于疫情的影响, 资金大量流入股市, 所以这一整年来看, 只要克制自己没有追涨杀跌的话, 收益情况基本不会太差. 医疗相关的股票和基金当然是水涨船高了, 但我并没有购入, 并非是不认可医疗的机遇, 我只是单纯不相信这个国家除开消费以外的投资能长期稳定地盈利. 不过说来可笑, 什么科技半导体A股, 始终比不上人情社会饭桌上的白酒. 不过我也并没有投入多少资金到白酒上, 而是将大部分的资金都买入另一个长期稳定的混合型基金以及纳斯达克指数, 跑赢通胀就已然足够了, 投机市场我不期望有什么大富大贵, 能不被宰就已经是相当幸运了. 来年计划去银行开户, 降一下基金管理的费率, 再读几本理财相关的书目, 写一个简单的工具来管理自己的定投方案.

阅读的乐趣

今年我给自己定的一个大目标就是阅读, 每一本书里都有着一个简缩的社会, 能从这个社会里读到什么全凭自己的理解. 我时常会思考很多观点是自己探求的还是别人灌输给我的, 我觉得在没有独立思考能力之前, 学到的一切结论都有可能是别人为某个目的灌输给我, 所以我更倾向的是自己去摸索和发现. 不过我阅读其实还有另一个原因, 就是我虽然没有喜欢的女生, 但我知道我喜欢的女生她一定会喜欢阅读. 所以就还是先从自己做起, 说不定能慢慢吸引到喜欢阅读的女生. 不过从结果来看这并没有什么作用, 我也不喜欢混什么圈子, 但如果再想想的话, 我觉得没有才是最好的结果.

少了那一步

当我周末无所事事的时候, 我就会想如果我真的没有了工作, 在我的设想里我并不会变得多快乐. 工作就像是一个强制把我拉出懒散的的念想, 借助这个念头我才能去更专注地完成工作之外我想完成我所热爱的事物. 当我意识到工作并不能让我得到成长的时候, 我就可以将工作和自学分的很开. 我想说个人的爱好喜乐, 都是活着的意义, 对技术的追求对安全的热爱, 很多时候我都觉得是一种自我价值的实现, 快感来自于掌握某项技术或应用于某种棘手的问题.

但从这一年的结果来看, 我并没有做出让自己满意的工作. 我并没有轻视任何一项工作的意思, 因为我知道尽管看起来再简单不过的工作, 也需要花费足够的脑筋去完成优化. 我在工作里做了不少有趣的尝试, 大多是借助数据科学来解决一些传统方法难以解决的问题, 但限于知识水平, 我并没能把工作再往前推进一步, 我对问题的看法只有最初的一瞬, 一瞬间脑海里出现一个我完全没尝试过的方案, 但我深信这是可行的, 我所做的是将脑海里的雏形慢慢实现出来, 但实现完后我的脑海就是一片空白, 就像是踏入了初学的大门, 却发现门后面什么也没有, 没有人告诉我该如何找到通往进阶的路, 我正处于这个摸索的时候.

漫漫的学习路

今年四月的时候也就是母校生日的那段时间, 我也如现在这样思考着, 我非常痛恨自己的处境. 我觉得自己带有着母校历史的光辉, 我希望自己并不止步于一个泯然大众的普通人, 而是尽力想在世界留下自己的足印. 所以给自己定了一个365天的挑战, 并迫切地希望能改变自己去追求新的处境. 但事实证明, 我的毅力只有预想里的一半, 这其中固然有一些原因, 但诚然在结果面前一切都不过是借口, 不一样的是我到现在也还没有放弃, 我还坚持在改变着.

不过我也真的是受益良多, 这一年来我学到了很多有趣的知识. 如果说学习给我带来最明显的变化是什么, 我一定会说是视野. 刚参加工作的时候, 我没有任何独立的想法, 我对安全的认知仅限在我知道有哪些技术, 但如今我会知道有哪些地方可以思考. 视野放宽就会知道哪些地方存在隐患, 知道怎么去切入问题, 我将其总结为我这年来最大的学习收获.

研究和信心

这一年来, 多亏了上交研究组GoSSIP的长期无私分享, 让我也对论文产生了浓厚的兴趣, 算算自己业余时间也看了不少论文, 结果尚可吧. 我之所以没有选择考研而是直接工作, 是因为一种错觉认为研究生其实也学不到什么东西, 那些知识完全能靠自学消化吸收, 所以我一直是将我的前三年工作定位为我的”另类”研究生经历. 但阅读的论文多, 不明觉理囫囵吞枣就会有一种枯燥乏味的感觉, 当时也有请教GoSSIP里的群友, 李卷儒老师的话就像点破了我一样, 跟我说论文其实并非读一遍, 而是需要往复三五遍吸收的, 突然觉得从此阅读论文的角度和心态就也不一样了. 而且我还蛮感谢牙满小姐姐的认可, 聊天的时候跟我说希望我能来GoSSIP继续读研, 虽然我现在并不能立马去准备考研, 但这种认可让我在之后一直充满了信心, 让我知道尽管自己并没有成为独当一面的研究员, 但我依然拥有着这样的可能.

耀眼的偶像

段老师是我的偶像, 在很早前我就梦想着能有机会见到段老师, 而在工作前离段老师最近的距离, 也仅仅是某次会议在台下远远地瞻仰着段老师, 听段老师分享最前沿的学术研究. 段老师对外一直给人的印象是很严谨地讨论和思考, 但近距离接触后发现其实段老师非常亲近没什么架子, 而且有着许多的可爱之处. 当偶像就在身边的时候, 感觉自己变成了婴孩, 做的一切扭捏的动作都不过是想吸引偶像的注意. 只是惜于自己的水平和不满足, 时常会幻想着在平行世界有另一个自己是发表了多篇顶会论文的博士, 那样才够得上跟段老师热烈地讨论学术细节.

如果说今年受段老师感染最多的, 那就是对跑步的爱好和坚持了. 说来我的高中班主任老师也是一个长期坚持跑步锻炼的人, 只是当时我并没有某种决心. 如果自己在朝着某个方向努力的话, 那坚持是必不可少的品质. 我能认清自己的资质平庸, 最起码让我还有勇气去坚持和改变.

身边还有着许多的榜样, 我都会非常羡慕他们, 但我也很清楚他们背后也有着许多不为人知的巨大努力. 我时常告诫自己, 周围人获取了怎样的成就这跟我一点关系也没有. 我警告自己不要陷入某种错觉, 拿别人的成就来作为自己吹嘘的材料. 如果要我总结从周围人身上学到了什么, 那我觉得应该是看见自己的不足. 我可以明显地感觉到自己的理论知识以及挖洞技术有着极大的欠缺, 而这也就是下一阶段我想着力的部分.

未来的期待

当我深入做一件事的时候, 我得到的经验就是任何一件事要想尽心尽力地完成都不简单, 去年我的期望仅仅是脑海中的几个零散的想法, 多多读书, 坚持学习, 但我自认为还算完成的不错, 如果是今年, 我可能会重点关注以下这些:

  • 保持对前沿论文的跟踪, 阅读和分享.
  • 保持长期的学习, 尽可能地记录下来.
  • 保持每周两到三次频率的锻炼.
  • 尽可能多的编写工具去减少繁冗的工作.
  • 将重心转移到漏洞的分析和挖掘上.

我并没有定下具体的指标, 这有点违背SMART原则, 但平心而论, 无论如何当我定下某个计划的时候, 我所关心的并非是能不能完成计划本身, 而是制定计划时的勇气和决心. 如果说人生如一叶扁舟, 我不关心去向何处, 只留意我走了多远.

谢谢20年受到的直接的间接的一切关心和帮助, 明天永远是未知的, 怎么想都比已成事实的昨天要有趣的多.

]]>
论文: Angora加点优化策略的高效fuzzer2020-10-28T23:05:50+00:002020-10-28T23:05:50+00:00https://vancir.github.io/blog/2020/angora-efficient-fuzzer简介

Angora的主要目标是无需借助符号执行的情况下, 求解路径约束以提高分支覆盖率, 为达到该目标, 其引入了四种关键技术: 字节级污点跟踪, 上下文敏感的分支计数, 梯度下降法以及输入长度探索.

  • 上下文敏感的分支覆盖: AFL使用上下文无关的分支覆盖率来近似认为程序状态, 但实验表明上下文敏感能让Angora探索更广泛的状态
  • 字节级别的污点跟踪: 大多数的路径约束其实只跟输入里的少量字节相关, 因此Angora回去跟踪哪些字节跟对应的路径约束相关, 仅变异这些相关的字节而非整个输入.
  • 梯度下降法: Angora使用梯度下降法来解决路径约束.
  • 类型/形状推断: 输入中的许多字节经常会作为整体共同作用于一个值, 比如4字节用作32位有符号整数, 为了让梯度下降能有效地搜索, Angora设法找到上述的组并推断其类型.
  • 输入长度探索: 程序有时对输入有长度的要求, 符号执行和梯度下降都不能告诉Fuzzer何时应当增加输入的长度, Angora则会检测输入是否会影响到路径约束, 并适时增加输入长度.

设计

上下文敏感的分支覆盖

将分支定义为(prev, cur, context), prev和cur是当前分支前后基本块ID, 而context则是h(stack), h代表哈希函数, stack则是调用堆栈状态. 但用堆栈表示上下文的话, 遇到递归就会出现重复很多次. 因此h这个哈希函数仅将每个调用点计算一次来规避递归带来的重复问题.

字节级别的污点跟踪

Angora将程序的每个变量与一个污点标签tx做关联, tx表示可能流入x的输入中字节偏移量. 当然这里的污点标签需要满足快速的Insert/Find/Union操作, 因此作者通过构建二叉树来优化了时空效率.

梯度下降法和类型/形状推断

使用梯度下降法得到局部最优解作为路径约束的解, 而对于字节而言, 简单的应用梯度下降是合适的, 但对于多个字节组成的单个值, 在计算梯度的时候会出现问题, 所以作者必须解决类型推断的问题.

为了解决该问题, Angora需要确定 (1) 输入里哪些字节被组合成单个值 (2) 判断这单个值其类型. 论文里将(1)称为形状推断(shape inference), 将(2)成为类型推断(type inference)

  • shape inference: 初识时所有字节都视为独立的. 在污点分析期间, 当一条指令读取字节序列输入给变量, 且该字节序列长度与原始类型的大小匹配(例如1,2,4,8字节), 则将这些字节序列标记为同一个值
  • type inference: Angora通过指令的语义作为依据来判断类型. 比如是一个对有符号整数进行运算的指令, Angora则将其操作数认作有符号整数, 如果一个数被同时用做有符号和无符号时, Angora则默认认为其是无符号. 当然推断不出来的话, Angora也没辙了.

输入长度探索

污点跟踪期间, Angora会在read类函数调用时, 将目的内存地址和对应输入的字节偏移关联起来. Angora也会将read函数调用的返回值用特殊标签进行标记. 如果在条件语句中使用到了返回值同时又不满足约束条件了, 那么Angora就会增加输入的长度以满足分支的约束.

]]>
论文: SAVIOR以Bug导向的混合模糊测试框架2020-10-27T00:33:45+00:002020-10-27T00:33:45+00:00https://vancir.github.io/blog/2020/savior-bug-driven-hybrid-fuzzing简介

在混合执行有了较大进展的背景下, 针对于漏洞检测场景混合执行的效果并不乐观, 而作者主要认为有两个原因: 首先盲目选择种子用于混合执行以及不加重点地关注所有的代码, 其次就是混合模糊测试注重于让测试过程继续下去而非去检查内部的漏洞缺陷. 于是作者提出了SAVIOR, 它会优先考虑种子的混合执行并验证执行路径上所有易受攻击的程序位置.

说白了, SAVIOR想解决混合模糊测试里乱选种子的行为, 并且希望以Bug为导向去选择种子. 那么具体的策略就是, 在测试前, SAVIOR会静态分析源代码并标记潜在的易受攻击位置. 此外, SAVIOR会计算每个分支可达的基本块集合, 在动态测试期间, SAVIOR优先考虑可以访问更重要分支的种子进行混合执行.

除开上述说的能加快漏洞检测速度, SAVIOR还会验证混合执行引擎遍历过路径上标记的漏洞. 具体就是, SAVIOR综合了各个漏洞路径上的约束, 如果该约束在当前路径条件下可以满足, 那么SAVIOR就会求解该约束以构造输入进行测试. 否则SAVIOR会证明该漏洞在此路径上不可行.

设计

Bug驱动优先级

Bug驱动的关键是找到一个方法去评估某个种子在混合执行时能暴露出的漏洞数量, 这个评估取决于两个先决条件:

  • R1 - 种子执行完后评估可访问代码区域的方法
  • R2 - 量化代码块中漏洞数量的指标

针对R1, SAVIOR结合动静态分析去评估种子的可探索代码区域. 在编译期间, SAVIOR会从每个分支静态地计算可达的基本块集合, 在运行期间, SAVIOR则会在种子的执行路径上标记未探索的分支, 以及计算这些分支可访问的基本块集合.

针对R2, SAVIOR则是利用UBSan来标注待测程序里的三种潜在的错误类型. 然后将每个代码区域中的标签计算为R2的定量指标. 同时SAVIOR也采用了一些过滤方法来删除UBSan的无用标签.

Bug导向验证

该技术可以确保在到达了漏洞函数路径上能进行可靠的漏洞检测. 从模糊测试处给定种子, SAVIOR会将其执行起来并沿执行路径提取各个漏洞标签. 之后SAVIOR会检查当前路径条件下的可满足性, 满足即漏洞有效.

实现

SAVIOR由多个部分组成: 构建在Clang+LLVM之上的工具链, 基于AFL的Fuzzer, KLEE移植过来的混合执行引擎和负责编排的协调器.

SAVIOR的编译工具链可用于漏洞标记, 控制流的可达性分析以及不同组件的构建.

漏洞标记则是基于UBSan, 当然UBSan有一些不如意的地方, SAVIOR对其进行了一些调整.

可达性分析用于计算CFG中每个基本快可到达的漏洞标签的数量. 它分为两个阶段, 第一步是类似SVF方法去构建过程间CFG, 其首先会为每个函数构建过程内CFG再通过调用关系建立过程间的关系. 为了解决间接调用, 算法会反复执行Andersen的指针分析, 以防止SAVIOR丢失间接调用的函数别名信息, 也使得优先级划分不会漏算漏洞标签数量. 此外通过检查CFG, SAVIOR还提取了基本块和子对象之间的边, 以便后续在协调器的进一步使用. 第二步则是计算过程间CFG中每个基本块可到达的代码区域, 并计算这些区域中UBSan标记的数量, 以此作为该基本块的优先级指标.

组件构建则是去编译三个binary: 一个用于fuzzer的binary, 一个用于协调器的SAVIOR-binary, 一个则是用于混合执行引擎的LLVM bitcode.

协调器则是用于挑选优先级高的种子, 以及一些后续处理. 混合执行引擎则采取了一些策略去解决约束问题.

]]>