论文笔记请见这里
COIN Attacks符号执行SGX_SQLite
从COIN Attack符号执行SGX_SQLite的过程入手分析COIN Attacks。从run.sh
开始。
第一步 编译SGX_SQLite
SGX SIM Mode编译SGX_SQLite。(位于run.sh
)
第二步 提取EDL信息
分析EDL文件并记录所有会将数据传入Enclave的E/OCALL函数及对应参数,具体包括OCALL返回值、[out]
标记的OCALL参数、[in]
标记的及其它所有的ECALL参数。这些参数称为不安全参数。信息放到unsafe_input_stat.tmp
和unsafe_ecall_stat.tmp
文件。(位于edlParse.py
)
第三步 提取不安全参数的类型信息
结合第二步得出的信息,使用ModulePass读取(似乎会漏掉SDK的接口)每个不安全参数所包含内容的类型(如pointer所指Reference的类型,struct内的各个成员的类型)。基于LLVM Pass的RTTI能更简单地获取类型信息。信息放到unsafe_input_complete.tmp
文件。(位于EnclaveSemantics.cpp
)
第四步 符号执行Enclave
加载Enclave.so
load_binary
中,使用lief.parse
解析enclave.so
,并使用Triton的setConcreteMemoryAreaValue
将Enclave加载起来。
加载不安全的输入信息
load_input_semantics
函数将unsafe_input_complete.tmp
里的不安全函数及其参数信息分别加载到数据结构interface_hook_fn
和input_semantics
中。
加载ECALL信息
load_ECALLs
将unsafe_ecall_stat.tmp
里的Ecall信息加载到数据结构primary_ECALLs
中。permutations
构建Ecall函数的各种排列。
为模拟器配置Hooks
config_hook_table
中,利用lief.ELF.Binary
丰富的信息,动态设置pltgot符号项Unsafe函数的地址。动态更新dlmalloc
、dlfree
、memcpy
函数的地址信息。动态更新__stack_chk_fail
tag的地址信息。
初始化triton上下文
单线程模拟执行(包括乱序执行)
定位urts
中sgx_ocall
、sgx_ocfree
函数地址(也代表了Enclave退出)。Triton具体化所有符号内存引用,设置栈帧为每个Ecall分配0x10000
大小的栈空间。选择需要乱序执行的Ecalls,对其参数设置内存值,用于模拟执行。关于设置参数内存值,初始用默认种子设置内存,每执行完一轮Ecall序列就随机化种子并改变内存值,最多执行MAX_SEED_ATTEMPT
轮。
"""
run_single_thread_emul(perm_ECALLs_list) prepares and handles single thread mode of emulation
"""
def run_single_thread_emul(perm_ECALLs_list):
...
for order_ECALLs in list(perm_ECALLs_list):
...
while seed_worklist:
...
Triton.concretizeAllMemory()
...
for it_ECALL in order_ECALLs:
...
stack_base_addr -= 0x10000
...
if it_ECALL in input_semantics:
for param_ECALL in input_semantics[it_ECALL]:
config_memory_for_param(it_ECALL, param_ECALL, it_ECALL_addr, init_seed_flag, True)
res = emulate(it_ECALL_addr, sgx_ocall, sgx_ofree, False)
...
if (len(seed_worklist) < MAX_SEED_ATTEMPT):
new_seeds = query_new_seed()
...
多线程模拟执行(包括并发执行)
总体类似于单线程模拟执行,但是对每个Ecall都起了单独的线程来执行,从而模拟并发。一个重要的细节是,在模拟执行中,每执行一条指令都会上锁,保证每个线程中每条指令都是原子性的完成的。
模拟执行
初始化模拟器。从每个Ecall函数第一条指令开始最多的模拟执行中,最多从入口指令开始往后执行MAX_INST_CNT
条指令。检查当前指令是否符合策略。
检查当前指令是不是某个hook函数(针对dlmalloc
的sgx_malloc_hook
、针对dlfree
的sgx_free_hook
、针对memcpy
的sgx_memcpy
)或tag(__stack_chk_fail
),陷入到hook或者tag时也进行策略检查,若违反策略就构建报告。
策略检查是这项工作的关键所在。
"""
emulate(pc, sgx_ocall, sgx_free, is_threaded) emulates instructions
"""
def emulate(pc, sgx_ocall, sgx_free, is_threaded):
...
ret = Triton.processing(instruction)
...
policies.inspection(instruction)
...
hook_process_inst(instruction)
...
print_report()
...
检查策略(*)
(Enclave接口处的输入往往可以是任意值并被攻击者控制,因此用符号变量表示这些接口输入。)
条件1 | 条件2 | 条件3 | 漏洞类型 |
---|---|---|---|
Triton模拟完一条指令后会对指令检查 | 拦截操作数为内存的指针解引用操作(指令为mov 、movsx 、lea )(位于oob_uaf_policy 函数中) | 内存对象尾部跨出了堆分配时记录的Enclave堆对象的尾部 | OOB |
(多线程下,当前线程Free内存后相较其它线程延迟10个指令确保Memory-reuse Delay)内存对象头部未指向堆分配时记录的Enclave堆对象内,并且与堆分配历史记录吻合 | UAF | ||
第一步找到包含符号变量的cmp 指令;第二步在紧跟的20条指令找到立即数高位为1(?) 的mov 指令;第三步在紧跟的10条指令找到jmp 指令(test_cmp_sides 函数中) | Ineffectual Condition | ||
此外对存在对应Hook的指令进一步处理 | Enclave内调用memcpy 函数时会被sgx_memcpy Hook函数拦截 | 检测Enclave栈内存是否被memcpy 复制到Enclave堆内存,后续人工检查堆内存是否通过Enclave接口被带出,从而造成栈信息泄漏(test_stack_leak 函数中) | 栈信息泄漏 |
检查memcpy 目标地址是否为Null(is_nd_or_heap_overflow 函数中) | Null Pointer Deference | ||
检查目标堆对象有效大小(堆分配时会记录堆对象信息)是否小于memcpy 参数指定的大小。(is_nd_or_heap_overflow 函数中) | 堆溢出 | ||
Enclave内malloc 内存时会被sgx_malloc_hook 拦截 | |||
Enclave内free 内存时会被sgx_free_hook 拦截 | 如果待释放内存未在堆分配情况记录表中记录,则说明该内存可能已经被释放或从未被分配,存在Double Free | Double Free | |
当程序执行到__stack_chk_fail (编译器对程序代码插桩检查Stack Canary破坏触发__stack_chk_fail ),报告存在栈溢出。(位于hook_process_inst ) | 栈溢出 | ||
ECALL被超过30个地址调用 | DDoS |
(具体细节见这里)