.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 symbolsdt ntdll!*: display all variables in ntdll!peb: display PEBdt nt!_PEB -r @$peb: full PEB dump!teb: display TEBMany WinDbg commands (lm, !dlls, !imgreloc, !tls, !gle) rely on the data retrieved from PEB and TEB
lm: list moduleslm 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~: 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 ~ !uniqstackgit 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
# 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
HelloWorld pass with optopt -load-pass-plugin ./libHelloWorld.{so|dylib} -passes=hello-world -disable-output input_for_hello.ll
首先ClusterFuzz进行了前后端分离, 而所谓的AppEngine其实是Google云平台的一项服务, 相当于提供了一个平台让开发者去运行自己的代码而无需关心平台的维护. ClusterFuzz不可免地依赖了Google的不少云服务, 像图片里还有Google Compute Engine, Google Cloud Storage, Datastore, Blobstore就是如此.
前端提供了以下模块:
后端则是从Task Pull Queues里获取Task并以Bot为单位执行, 其中Fuzz Task则是负责对Fuzz Target进行测试. 而这里Bots的Local Storage从我认知来看已经移除了, 目前保存在本地的数据应该只剩下缓存数据以及状态监控统计数据, 其他的数据基本都上传到云服务去了.
Glusterfs是一项分布式文件系统, 彼时被用于同步Bots之间的数据. 而目前版本已经移除了Glusterfs, 我自己也尚不清楚目前是如何处理Bots之间的数据同步问题, 印象里似乎并没有同步Bots之间的数据. 这有待我后续仔细阅读这部分的源码得出结论. Builder Bots也让我很迷惑, 但从名称来看应该是用于对源码的构建. 在目前的ClusterFuzz版本中我也尚未发现有这部分相关代码.
ClusterFuzz有三个主要目标:
Fuzzer应当具备相同的使用命令以供调用, 比如run.* --input_dir=A --output_dir=B --no_of_files=C设计一套统一的命令行选项. 定义标签前缀, 比如“fuzz-”, “http-”, “flag-”等等. 具备跨平台性, 支持对多种编程语言开发的软件进行Fuzz. 使用Data Bundle来统一管理Fuzz资源, 并且也可以提供其他的脚本比如launcher, coverage等.
设置好环境并对项目代码进行构建, 能够运行应用程序以及相关的测试代码. 能调整平台的参数比如设置手势, 设置工具的选项, 增加超时限制等. 能解决程序等资源依赖问题, 并对产出的Crash进行复现和去重, 将Crash, 覆盖率, 统计数据等进行妥善保存等.
禁用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适用性更窄, 依赖于具体的格式或API. 能够迅速的找到Bug, 但无效也更快, 并且需要编写复杂的策略提高效果, 适合用于检验回归测试.
基于变异的Fuzzer则依赖于初始测试的样例, 而策略比较简单, 能够很好地适用于文件格式fuzz, 协议fuzz, 并且可以源源不断地挖掘Bugs. 但也有一些难以解决的情况比如计算checksum, 数据压缩操作等.
进化式Fuzzer则是基于反馈的Fuzzer, 目前主流的反馈指标则是使用的代码覆盖率. 代码在编译时会进行插装来反馈测试样例运行时的覆盖率信息, 并且通过共享存储聚合多个代码之间的覆盖率情况.
代码覆盖率部分彼时还是使用的fuzzer_utils来控制, 但从目前来看这些这些API都有了比较大的变化. 彼时针对Testcase会有以下规则: 对于原始Testcase, 如果增加了新的覆盖分支, 那么就将其加入到Optimal文件列表里去, 而如果该测试用例没有产生新的覆盖的话, 那么就会将其删除. 而对于Testcase发生改动并且产出了新覆盖率的时候, 就会将其加入到Corpus和Optimal文件列表中去. (无新覆盖分支那么就直接忽略.)
Valgrind会产生10-300x的性能开销, 启动缓慢并且只能适用于堆漏洞, 因此并没有选择使用Valgrind.
ClusterFuzz选择了结合使用多种内存调试工具.
作者提出一种方式叫做Delta Debugging可以并行多线程地进行Minimize任务. 并且支持为某些文件类型进行Minimizer的定制.
Minimizer首先会将Input进行token化, 并假定某组Token并不是Crash所需要的, 假设移除掉这些Token后再次执行, 如果程序崩溃, 那么我们的移除就是可靠的. 而没有复现Crash, 那么就将移除的Token再细分成更小的组.
### 0x03 实时的回归测试以及修复性测试
作者提出了一个工具叫FindIt, 可以用于找出崩溃相关的ChangeLog(CL). 它的执行过程如下:
fuzzing分为Blackbox fuzzing, Greybox fuzzing和Structure-aware fuzzing. 而对于Fuzzing规模化重要的不是增加服务器的CPU核心数, 而是引导开发者学习使用Fuzzing, 提供丰富的文档和示例便于编写Greybox fuzzer, 提供高效fuzzing的建议(种子池,字典等), 让Greybox fuzzing成为像单元测试那样的一等公民.
使用编译时插桩(ASan, MSan, 覆盖率插桩等), 跟fuzzing引擎或驱动链接起来(libFuzzer: clang -fsanitize=address,fuzzer). 确保Release版本经过了充分fuzzing, 持续地构建fuzzer(理想情况是能加入到已有的CI流程).
对Fuzzer的运行情况, Crash的详细信息进行数据收集和统计. 生成代码覆盖率的报告.
传统关系型数据库通过文档字段来建立数据的关联, 但是这并不利于海量数据背景下的关系推导. 图数据库应运而生, 图数据库的上层就是我们熟悉的图网络结构, 而下层则是对图结构进行了性能优化, 使之能进行快速的关系推导. 图数据库在知识图谱(比如社交关系推导)和图神经网络(GNN)上有很大的应用.
Neo4j是业界主流的图数据库, 数据库目前排名是19(参见DB-Engines Ranking), 以点/关系进行存储, 支持百亿级别的查询. Neo4j分为免费的社区版和付费的企业版. 社区版只能进行单机部署, 企业版可以部署集群且性能上有着诸多优化, 以及解锁了诸多限制. ONgDB 是Neo4j付费闭源前的分支版本, 目前的最新版本为3.6.2, 跟Neo4j企业版本3.6功能相差无几.
因果集群基于Raft协议开发, Raft是一种更加易于理解的一致性算法, 可支持大规模和多拓扑结构的数据环境. 因果集群主要有以下两大特点:
安全性:核心服务器(Core)为事物平台处理提供了容错平台
可扩展性:只读副本(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.dbneo4j-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可以返回集合中的重复项.
超级节点指的是拥有非常多关系/边的一类节点. 超级节点的存在会极大地影响入库/检索/分析的效率.
使用explain和profile来分析cypher语句的性能, 前者不会执行语句而后者会实际执行.
删除两个节点之间的重复关系:
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'
)
在学习图数据库和实践过程中其实积累了不少资料, 都是自己在初学和实践中遇到困难而去搜索的资料. 而其实这方面的资料差不多也就是这些. 基本上遇到了问题都能在这些地方找到答案.
主要资料
这些是学习和操作图数据库所必需了解的知识部分.
其他资料
以下资料并非不重要, 而是用于扩展自己的学习面.
实际过程中会有许多需要进行谷歌搜索的事情, 大部分参考于stackoverflow的回答.
]]>我的机器配置是双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]
如果是手动解析数据的话, 需要手动定义数据格式, 并且对于异常数据的错误处理会很麻烦.
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})
tidwall/gjson和tidwall/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进行操作
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/stat 和montanaflynn/stats进行基本的数据测量: 均值: stat.Mean, 众数: stat.Mode, 中位数: stats.Median
本文我们来介绍一篇NDSS 2021上的论文“Towards Measuring Supply Chain Attacks on Package Managers for Interpreted Languages”, 该论文大规模测量了当前主流的三个开源包托管平台(PyPi, Npm, RubyGems)上的代码, 并检出了339个新型恶意软件包, 其中有278(82%)个已被平台确认并移除.
首先作者将包管理生态分为了以下四种角色:
论文作者对PyPi/Npm以及RubyGems的安全性分三部分(功能性/代码审查/响应措施)进行了测量评估.
恶意包的功能通常包括窃取用户的敏感信息, 注入后门, 加密文件并进行勒索, 用于挖矿, 传播病毒, 黑产等.
如果要给20年定一个主线, 那无疑是疫情. 起初了解到武汉疫情的消息时, 谁能想到这场疫情会席卷全球并且持续这么长的时间. 20年过完春节我匆匆去往机场直飞北京, 当时跟出租车上的司机聊了起来. 他也知道武汉的疫情已经到了一个非常严重的地步, 他是武汉人, 但是没拗过家里老人把自己的儿子送回了武汉, 这一送进去就没有办法出来了, 还要冒着疫情的风险, 话语中无不是担忧. 后来我妈也跟我说到, 我当时要是晚走上一天, 小区也要封锁我也没法顺利回京. 疫情既已发生无可挽回, 我仿佛穿越时间回到了03年的非典现场, 恍若隔世犹如曾经柴静《看见》里描述的那样胆破心惊.
大概年中的时候, 一位高中同学邀请我一起做一个有关疫情传播模型的研究, 我觉得正好是新冠肆虐的时期做起来还蛮有意思的. 疫情的传播是一个复杂的问题, 但从查阅的资料来看, 医学和计算的结合并没有到一个很高的水平, 甚至于说其实是有很多研究都还在早期阶段有着大量的工作极需完善, 不过确实也有可能是由于数据的匮乏, 想深入研究并非易事吧.
中国的通货膨胀应该是相当严重吧, 所以一直都是有钱花钱的态度. 我比较认可那一套”用金钱换时间”的说法, 但想想可悲的却是人们必须通过工作”拿时间换金钱”, 今年的一个成长就是开始了解理财. 我对理财的期待并非财生财利滚利, 能够跑赢通货膨胀就实属大幸. 由于疫情的影响, 资金大量流入股市, 所以这一整年来看, 只要克制自己没有追涨杀跌的话, 收益情况基本不会太差. 医疗相关的股票和基金当然是水涨船高了, 但我并没有购入, 并非是不认可医疗的机遇, 我只是单纯不相信这个国家除开消费以外的投资能长期稳定地盈利. 不过说来可笑, 什么科技半导体A股, 始终比不上人情社会饭桌上的白酒. 不过我也并没有投入多少资金到白酒上, 而是将大部分的资金都买入另一个长期稳定的混合型基金以及纳斯达克指数, 跑赢通胀就已然足够了, 投机市场我不期望有什么大富大贵, 能不被宰就已经是相当幸运了. 来年计划去银行开户, 降一下基金管理的费率, 再读几本理财相关的书目, 写一个简单的工具来管理自己的定投方案.
今年我给自己定的一个大目标就是阅读, 每一本书里都有着一个简缩的社会, 能从这个社会里读到什么全凭自己的理解. 我时常会思考很多观点是自己探求的还是别人灌输给我的, 我觉得在没有独立思考能力之前, 学到的一切结论都有可能是别人为某个目的灌输给我, 所以我更倾向的是自己去摸索和发现. 不过我阅读其实还有另一个原因, 就是我虽然没有喜欢的女生, 但我知道我喜欢的女生她一定会喜欢阅读. 所以就还是先从自己做起, 说不定能慢慢吸引到喜欢阅读的女生. 不过从结果来看这并没有什么作用, 我也不喜欢混什么圈子, 但如果再想想的话, 我觉得没有才是最好的结果.
当我周末无所事事的时候, 我就会想如果我真的没有了工作, 在我的设想里我并不会变得多快乐. 工作就像是一个强制把我拉出懒散的的念想, 借助这个念头我才能去更专注地完成工作之外我想完成我所热爱的事物. 当我意识到工作并不能让我得到成长的时候, 我就可以将工作和自学分的很开. 我想说个人的爱好喜乐, 都是活着的意义, 对技术的追求对安全的热爱, 很多时候我都觉得是一种自我价值的实现, 快感来自于掌握某项技术或应用于某种棘手的问题.
但从这一年的结果来看, 我并没有做出让自己满意的工作. 我并没有轻视任何一项工作的意思, 因为我知道尽管看起来再简单不过的工作, 也需要花费足够的脑筋去完成优化. 我在工作里做了不少有趣的尝试, 大多是借助数据科学来解决一些传统方法难以解决的问题, 但限于知识水平, 我并没能把工作再往前推进一步, 我对问题的看法只有最初的一瞬, 一瞬间脑海里出现一个我完全没尝试过的方案, 但我深信这是可行的, 我所做的是将脑海里的雏形慢慢实现出来, 但实现完后我的脑海就是一片空白, 就像是踏入了初学的大门, 却发现门后面什么也没有, 没有人告诉我该如何找到通往进阶的路, 我正处于这个摸索的时候.
今年四月的时候也就是母校生日的那段时间, 我也如现在这样思考着, 我非常痛恨自己的处境. 我觉得自己带有着母校历史的光辉, 我希望自己并不止步于一个泯然大众的普通人, 而是尽力想在世界留下自己的足印. 所以给自己定了一个365天的挑战, 并迫切地希望能改变自己去追求新的处境. 但事实证明, 我的毅力只有预想里的一半, 这其中固然有一些原因, 但诚然在结果面前一切都不过是借口, 不一样的是我到现在也还没有放弃, 我还坚持在改变着.
不过我也真的是受益良多, 这一年来我学到了很多有趣的知识. 如果说学习给我带来最明显的变化是什么, 我一定会说是视野. 刚参加工作的时候, 我没有任何独立的想法, 我对安全的认知仅限在我知道有哪些技术, 但如今我会知道有哪些地方可以思考. 视野放宽就会知道哪些地方存在隐患, 知道怎么去切入问题, 我将其总结为我这年来最大的学习收获.
这一年来, 多亏了上交研究组GoSSIP的长期无私分享, 让我也对论文产生了浓厚的兴趣, 算算自己业余时间也看了不少论文, 结果尚可吧. 我之所以没有选择考研而是直接工作, 是因为一种错觉认为研究生其实也学不到什么东西, 那些知识完全能靠自学消化吸收, 所以我一直是将我的前三年工作定位为我的”另类”研究生经历. 但阅读的论文多, 不明觉理囫囵吞枣就会有一种枯燥乏味的感觉, 当时也有请教GoSSIP里的群友, 李卷儒老师的话就像点破了我一样, 跟我说论文其实并非读一遍, 而是需要往复三五遍吸收的, 突然觉得从此阅读论文的角度和心态就也不一样了. 而且我还蛮感谢牙满小姐姐的认可, 聊天的时候跟我说希望我能来GoSSIP继续读研, 虽然我现在并不能立马去准备考研, 但这种认可让我在之后一直充满了信心, 让我知道尽管自己并没有成为独当一面的研究员, 但我依然拥有着这样的可能.
段老师是我的偶像, 在很早前我就梦想着能有机会见到段老师, 而在工作前离段老师最近的距离, 也仅仅是某次会议在台下远远地瞻仰着段老师, 听段老师分享最前沿的学术研究. 段老师对外一直给人的印象是很严谨地讨论和思考, 但近距离接触后发现其实段老师非常亲近没什么架子, 而且有着许多的可爱之处. 当偶像就在身边的时候, 感觉自己变成了婴孩, 做的一切扭捏的动作都不过是想吸引偶像的注意. 只是惜于自己的水平和不满足, 时常会幻想着在平行世界有另一个自己是发表了多篇顶会论文的博士, 那样才够得上跟段老师热烈地讨论学术细节.
如果说今年受段老师感染最多的, 那就是对跑步的爱好和坚持了. 说来我的高中班主任老师也是一个长期坚持跑步锻炼的人, 只是当时我并没有某种决心. 如果自己在朝着某个方向努力的话, 那坚持是必不可少的品质. 我能认清自己的资质平庸, 最起码让我还有勇气去坚持和改变.
身边还有着许多的榜样, 我都会非常羡慕他们, 但我也很清楚他们背后也有着许多不为人知的巨大努力. 我时常告诫自己, 周围人获取了怎样的成就这跟我一点关系也没有. 我警告自己不要陷入某种错觉, 拿别人的成就来作为自己吹嘘的材料. 如果要我总结从周围人身上学到了什么, 那我觉得应该是看见自己的不足. 我可以明显地感觉到自己的理论知识以及挖洞技术有着极大的欠缺, 而这也就是下一阶段我想着力的部分.
当我深入做一件事的时候, 我得到的经验就是任何一件事要想尽心尽力地完成都不简单, 去年我的期望仅仅是脑海中的几个零散的想法, 多多读书, 坚持学习, 但我自认为还算完成的不错, 如果是今年, 我可能会重点关注以下这些:
我并没有定下具体的指标, 这有点违背SMART原则, 但平心而论, 无论如何当我定下某个计划的时候, 我所关心的并非是能不能完成计划本身, 而是制定计划时的勇气和决心. 如果说人生如一叶扁舟, 我不关心去向何处, 只留意我走了多远.
谢谢20年受到的直接的间接的一切关心和帮助, 明天永远是未知的, 怎么想都比已成事实的昨天要有趣的多.
]]>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)
污点跟踪期间, Angora会在read类函数调用时, 将目的内存地址和对应输入的字节偏移关联起来. Angora也会将read函数调用的返回值用特殊标签进行标记. 如果在条件语句中使用到了返回值同时又不满足约束条件了, 那么Angora就会增加输入的长度以满足分支的约束.
]]>在混合执行有了较大进展的背景下, 针对于漏洞检测场景混合执行的效果并不乐观, 而作者主要认为有两个原因: 首先盲目选择种子用于混合执行以及不加重点地关注所有的代码, 其次就是混合模糊测试注重于让测试过程继续下去而非去检查内部的漏洞缺陷. 于是作者提出了SAVIOR, 它会优先考虑种子的混合执行并验证执行路径上所有易受攻击的程序位置.
说白了, SAVIOR想解决混合模糊测试里乱选种子的行为, 并且希望以Bug为导向去选择种子. 那么具体的策略就是, 在测试前, SAVIOR会静态分析源代码并标记潜在的易受攻击位置. 此外, SAVIOR会计算每个分支可达的基本块集合, 在动态测试期间, SAVIOR优先考虑可以访问更重要分支的种子进行混合执行.
除开上述说的能加快漏洞检测速度, SAVIOR还会验证混合执行引擎遍历过路径上标记的漏洞. 具体就是, SAVIOR综合了各个漏洞路径上的约束, 如果该约束在当前路径条件下可以满足, 那么SAVIOR就会求解该约束以构造输入进行测试. 否则SAVIOR会证明该漏洞在此路径上不可行.
Bug驱动的关键是找到一个方法去评估某个种子在混合执行时能暴露出的漏洞数量, 这个评估取决于两个先决条件:
针对R1, SAVIOR结合动静态分析去评估种子的可探索代码区域. 在编译期间, SAVIOR会从每个分支静态地计算可达的基本块集合, 在运行期间, SAVIOR则会在种子的执行路径上标记未探索的分支, 以及计算这些分支可访问的基本块集合.
针对R2, SAVIOR则是利用UBSan来标注待测程序里的三种潜在的错误类型. 然后将每个代码区域中的标签计算为R2的定量指标. 同时SAVIOR也采用了一些过滤方法来删除UBSan的无用标签.
该技术可以确保在到达了漏洞函数路径上能进行可靠的漏洞检测. 从模糊测试处给定种子, 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.
协调器则是用于挑选优先级高的种子, 以及一些后续处理. 混合执行引擎则采取了一些策略去解决约束问题.
]]>