The S2E Platform
Finding vulnerabilities in Linux and Windows programs Vitaly Chipounov
https://s2e.systems Ready-for-use docker image, demos, tutorials, source code, documentation
The S2E Platform Finding vulnerabilities in Linux and Windows - - PowerPoint PPT Presentation
The S2E Platform Finding vulnerabilities in Linux and Windows programs Vitaly Chipounov https://s2e.systems Ready-for-use docker image, demos, tutorials, source code, documentation Vitaly Chipounov Ph.D. in 2014 at EPFL, Switzerland
Finding vulnerabilities in Linux and Windows programs Vitaly Chipounov
https://s2e.systems Ready-for-use docker image, demos, tutorials, source code, documentation
Vitaly Chipounov
DSLAB, George Candea
device drivers using symbolic execution
performance analysis, etc. => created the S2E platform
Two teams used S2E
Team CodeJitsu Cyberhaven UC Berkeley Syracuse University Team Disekt
Finding vulnerabilities in user-space apps with S2E
>12 KLOC driver, large input files
fuzzer integration, symbolic fault injection, function models, etc.
Testing Windows device drivers with S2E
FAT12/16/32 driver from the WDK (fastfat.sys)
Bug finding Verification Testing Security checking … Extensible Write your own tools Symbolic execution Concolic execution State merging Fuzzing … On real OSes, with real apps, libraries, drivers Pretty much anything that runs on computers
void main(int argc, char **argv) { int r = 1, i = 1; if (i < argc) { if (argv[i][0] == 'n') { r = 0; ++i; } }
void main(int argc, char **argv) { int r = 1, i = 1; if (i < argc) { if (argv[i][0] == 'n') { r = 0; ++i; } }
30 GB disk 4 GB RAM?
30 GB disk 4 GB RAM?
void main(int argc, char **argv) { int r = 1, i = 1; if (i < argc) { if (argv[i][0] == 'n') { r = 0; ++i; } }
#include <stdio.h> int main(int argc, char **argv) { FILE *fp = fopen(argv[1], "rb"); if (!fp) { goto err; } int data; if (fread(&data, sizeof(data), 1, fp) != 1) { goto err; } if (data == 0xdeadbeef) { printf("Hello world\n"); } else { *(char *)0xbadcafe = 0; } err: if (fp) { fclose(fp); } return 0; }
ssh -CX userXX@issisp.anu.edu.au $ wget -O crash.c https://pastebin.com/raw/B1BbapMV $ gcc -g -O0 —m32 -o ./crash ./crash.c $ source s2e/s2e_activate $ s2e new_project ./crash @@ $ cd s2e/projects/crash $ ./launch-s2e.sh …
$ ls -1 s2e/projects/crash/s2e-last/ assembly.ll debug.txt ExecutionTracer.dat info.txt module.bc run.stats tbcoverage-0.json tbcoverage-1.json testcase-crash:0x50f:0x804858a-0-_tmp_input testcase-kill-0-_tmp_input testcase-kill-1-_tmp_input warnings.txt
~/s2e/projects/crash$ gdb --args ./crash s2e-last/testcase-crash:0x50f:0x804858a-0-_tmp_input (gdb) r Starting program: /home/vitaly/s2e/projects/crash/crash s2e-last/ testcase-crash:0x50f:0x804858a-0-_tmp_input Program received signal SIGSEGV, Segmentation fault. 0x0804858a in main (argc=2, argv=0xffffcec4) at crash.c:18 18 *(char *)0xbadcafe = 0; (gdb)
$ s2e forkprofile crash … 01295 crash:0x08048571 1 /home/ubuntu/s2e/env/crash.c:15 (main)
if (data == 0xdeadbeef) { printf("Hello world\n"); } else { *(char *)0xbadcafe = 0; }
More on that in the 2nd part of the tutorial…
$ s2e coverage lcov --html crash Overall coverage rate: lines......: 84.6% (11 of 13 lines) functions..: no data found Line coverage saved to /home/vitaly/s2e/env/projects/crash/s2e-last/crash.info. An HTML report is available in /home/vitaly/s2e/env/projects/crash/s2e-last/crash_lcov
$ s2e coverage lcov --html crash
Finding vulnerabilities in user-space apps with S2E
Valgrind AddressSanitizer KLEE Astrée TEMU BitBlaze VMware CoreDet SyncFinder ESD Parfait Saturn Calysto LLBMC Isabelle CUTE EXE SJPF Coverity CodeSonar DTrace Oprofile BAP IDApro Jakstab Angr SimOS Pin PinOS LFI ThreadSanitizer Manticore Dingo SFI Nooks CFI Dimmunix VeriSoft Eraser SAGE DART Cloud9 BitScope bddbddb … Testing Verification P r
l i n g Simulation Isolation Immunity Debugging …
./prog argc != 2 ./prog 123 argc == 2
int main(argc, argv) { if (argc == 2) { printf(“%c”, *argv[2]); … } return 0; }
int main(argc, argv) { void *p = malloc(…); if (!p) { exit(-1); } … return 0; }
Program Kernel Libraries Hardware
int main(argc, argv) { void *p = malloc(…); if (!p) { exit(-1); } … return 0; }
Program Environment Model
KLEE Cloud9 SLAM LLBMC Coverity …
int main(argc, argv) { void *p = malloc(…); if (!p) { exit(-1); } … return 0; }
Program Kernel Libraries Hardware
KLEE EXE DART Fuzzing …
False positives vs. false negatives
Testing, verification, profiling, etc.
Applications, libraries, kernel modules, etc.
Symbolic execution, state merging, fuzzing, etc. You can write models, but don’t have to!
Wide range of analyses
In-vivo, at any level of software stack
Works on binaries
Kernel Libraries Hardware Applications Distributed systems Reverse engineering
RevNIC [EUROSYS’10]
Profiling
PROFs [ASPLOS’11]
Testing
BIOS Testing [WOOT’15] Avatar [NDSS’14] CHEF [ASPLOS’14] Achilles [ASPLOS’13] SWIFT [EUROSYS’12] SymDrive [OSDI’12] SymNet [WRIPE’12] DDT [USENIX’11] …
Verification
Software router dataplanes [NSDI’14]
Security
CVE-2015-1536
Analysis Plugins Symbolic Execution Engine Dynamic Binary Translator Instrumentation Engine Path Selection Plugins S2E Applications Libraries Kernel Drivers Virtual Hardware VM What input to make symbolic What input to make concrete Search heuristics Check for crashes, vulnerability conditions, performance metrics, etc.
decoupled
replaces /dev/kvm functionality
to intercept DMA, disk R/W, and device state snapshotting
S2E
Analysis Plugins Symbolic Execution Engine Dynamic Binary Translator Instrumentation Engine Path Selection Plugins S2E Applications Libraries Kernel Drivers Virtual Hardware VM libs2e.so KVM-compatible interface
/dev/kvm
make it standalone
generation libraries
libvmi, etc.
projects
execution engine with your own if you want
Analysis Plugins Symbolic Execution Engine Dynamic Binary Translator Instrumentation Engine Path Selection Plugins S2E Applications Libraries Kernel Drivers Virtual Hardware VM libs2e.so KVM-compatible interface
/dev/kvm
0x80000000: mov [ebx], eax void tb_0x80000000(cpu) { tmp1 = cpu->regs[EBX]; tmp2 = cpu->regs[EAX]; __stl_mmu(tmp1, tmp2); } while(true) { tb = translate(cpu->pc) tb->func(cpu); }}
0x80000000: mov [ebx], eax void tb_0x80000000(cpu) { tmp1 = cpu->regs[EBX]; tmp2 = cpu->regs[EAX]; __stl_mmu(tmp1, tmp2); } Host-independent micro-operations Frontend Host instructions (x86, arm, mips, etc.) Backend (one per target architecture) while(true) { tb = translate(cpu->pc) tb->func(cpu); }} translate()
translate(pc) { do { ins = disassemble(pc); emit_uops(ins); pc += ins.size; } while (ins != jmp); }} while(true) { tb = translate(cpu->pc) tb->func(cpu); }} translate(pc) { do { if (s2e_instrument_ins(pc)) { emit_uops_s2e(); }} ins = disas(pc); emit_uops(ins); pc += ins.size; } while (ins != jmp); }}
translate(pc) { do { ins = disassemble(pc); emit_uops(ins); pc += ins.size; } while (ins != jmp); }} translate(pc) { do { if (s2e_instrument_ins(pc)) { emit_uops_s2e(); }} ins = disas(pc); emit_uops(ins); pc += ins.size; } while (ins != jmp); }} 0x80000000: mov [ebx], eax void tb_0x80000000(cpu) { s2e_call_plugins(); tmp1 = cpu->regs[R_EBX]; tmp2 = cpu->regs[R_EAX]; __stl_mmu(tmp1, tmp2); } void tb_0x80000000(cpu) { tmp1 = cpu->regs[EBX]; tmp2 = cpu->regs[EAX]; __stl_mmu(tmp1, tmp2); }
0x80000000: mov [ebx], eax void tb_0x80000000(cpu) { tmp1 = cpu->regs[EBX]; tmp2 = cpu->regs[EAX]; __stl_mmu(tmp1, tmp2); } Host-independent micro-operations Frontend Host instructions (x86, arm, mips, etc.) Backend (one per target architecture) while(true) { tb = translate(cpu->pc) tb->func(cpu); }} translate()
define i64 @tb_0x80000000(i64*) #12 { entry: %loc_18ptr = alloca i32 %loc_19ptr = alloca i32 %1 = getelementptr i64, i64* %0, i32 0 %2 = load i64, i64* %1 %eax_ptr = getelementptr %struct.CPUX86State, …, i32 0, i32 0 %ebx_ptr = getelementptr %struct.CPUX86State, …, i32 0, i32 2 %eax = load i32, i32* %eax_ptr %ebx = load i32, i32* %ebx_ptr call void @__stl_mmu(i32 %ebx, i32 %eax) … }
0x80000000: mov [ebx], eax
KLEE
Applications Libraries Kernel Drivers Analysis Plugins Symbolic Execution Engine Dynamic Binary Translator Instrumentation Engine Virtual Hardware Path Selection Plugins S2E VM
Core execution events
…
void MyPlugin::initialize() { s2e()->getCorePlugin()->onTranslateInstructionStart-> connect(MyPlugin::onInstructionTranslation); } void MyPlugin::onInstructionTranslation(signal, pc) { if (pc == crashHandlerPc) { signal->connect(MyPlugin::onInstructionExecution, …); } } void MyPlugin::onInstructionExecution(state) { s2e()->getExecutor()->terminateState(…); }
Where do I get the crash handler pc? Is it going to change between OSes? Where do I get information about the crash (pid, pc, etc.)? Is the crash handler pc in the correct address space? => Requires complex virtual machine introspection
void MyPlugin::initialize() {} void MyPlugin::handleOpcodeInvocation(state, data, size) { my_plugin_data_t data; s2e()->mem()->read(&data, data, size…); s2e()->getExecutor()->terminateState(data.pid, “crashed”); }} // linux-4.9.3/arch/x86/mm/fault.c static void force_sig_info_fault(int si_signo, int si_code, unsigned long address, …) { unsigned lsb = 0; siginfo_t info; my_plugin_data_t data; data.pid = current->pid; data.address = address; s2e_invoke_plugin(“MyPlugin”, &data, sizeof(data); … }}
Do as much work as possible inside the guest!
Applications Libraries Kernel Drivers Analysis Plugins Symbolic Execution Engine Dynamic Binary Translator Instrumentation Engine Virtual Hardware Path Selection Plugins S2E VM
s2e_invoke_plugin(); OS Monitoring Plugins
… S2E Guest Tools #!/bin/bash s2ecmd kill -1 “error” s2eget file.txt …
Finding simple crashes and getting code coverage
Finding vulnerabilities in user-space apps with S2E
state arbitrarily
register to an arbitrary agreed upon value
itself may not be exploitable).
$ gdb —args ./vulnerable-program pov_input.txt SEGFAULT at 0xdeadbeef
int main(int argc, char **argv) { char buf[4]; receive(buf, 12); return 0; } push ebp mov ebp, esp sub esp, 4 ; Allocate 4 bytes on the stack for buf lea eax, [ebp-4] ; Compute address of buf push 12 ; Push 12 for 2nd parameter of receive push eax ; Push address of buf for 1st param of receive call receive xor eax, eax ; Set return value to 0 leave ; Clean the stack frame ret ; Return from the main function echo AAAAAAAABBBBBBBBCCCCCCCC | xxd -r -p | ./program Segfault at address 0xCCCCCCCC
push argv push argc call main main: push ebp mov ebp, esp sub esp, 4 lea eax, [ebp-4] push 12 push eax call receive xor eax, eax leave ret 0xf8: 0xabc0 ; argv 0xf4: 0xdef0 ; argc 0xf0: 0x800231 ; return address 0xec: 0x1000 ; saved frame pointer 0xe8: ???????? ; space for the buffer 0xe4: 12 ; size of the parameter passed to receive 0xe0: 0xe8 ; address of the buffer on the stack 0xf8: 0xabc0 ; argv 0xf4: 0xdef0 ; argc 0xf0: CCCCCCCC ; return address 0xec: BBBBBBBB ; saved frame pointer 0xe8: AAAAAAAA ; space for the buffer 0xe4: 12 ; size of the parameter passed to receive 0xe0: 0xe8 ; address of the buffer on the stack
int main(int argc, char **argv) { char buf[4]; receive(buf, 12); return 0; } echo λ0λ1λ2λ3λ4λ5λ6λ7λ8λ9λ10λ11 | ./program Segfault at address λ11λ10λ9λ8
push argv push argc call main main: push ebp mov ebp, esp sub esp, 4 lea eax, [ebp-4] push 12 push eax call receive xor eax, eax leave ret 0xf8: 0xabc0 ; argv 0xf4: 0xdef0 ; argc 0xf0: 0x800231 ; return address 0xec: 0x1000 ; saved frame pointer 0xe8: ???????? ; space for the buffer 0xe4: 12 ; size of the parameter passed to receive 0xe0: 0xe8 ; address of the buffer on the stack 0xf8: 0xabc0 ; argv 0xf4: 0xdef0 ; argc 0xf0: λ11λ10λ9λ8 ; return address 0xec: λ4λ5λ6λ7 ; saved frame pointer 0xe8: λ0λ1λ4λ3 ; space for the buffer 0xe4: 12 ; size of the parameter passed to receive 0xe0: 0xe8 ; address of the buffer on the stack
0xf8: 0xabc0 ; argv 0xf4: 0xdef0 ; argc 0xf0: λ11λ10λ9λ8 ; return address 0xec: λ4λ5λ6λ7 ; saved frame pointer 0xe8: λ0λ1λ4λ3 ; space for the buffer 0xe4: 12 ; size of the parameter passed to receive 0xe0: 0xe8 ; address of the buffer on the stack
klee::ref<klee::Expr> address, …)
push argv push argc call main main: push ebp mov ebp, esp sub esp, 4 lea eax, [ebp-4] push 12 push eax call receive xor eax, eax leave ret
Segfault at address 0xbadcafe echo 0000000000000000fecaad0b | xxd -r -p | ./program
ssh -CX userXX@issisp.anu.edu.au
$ wget -O vulnerable.c https://pastebin.com/raw/rBYFGEJS $ gcc -g -w -fno-stack-protector -D_FORTIFY_SOURCE=0 -O0 -m32 \
$ source s2e/s2e_activate $ s2e new_project —-enable-pov-generation ./vulnerable @@ $ cd s2e/projects/vulnerable $ ./launch-s2e.sh …
$ ls ~/s2e/projects/vulnerable/s2e-last
_tmp_input-crash@0x0-pov-unknown-0 _tmp_input-crash@0x0-pov-unknown-3 _tmp_input-recipe-type1_i386_generic_reg_ebp.rcp@0x8048760-pov-type1-0 _tmp_input-recipe-type1_i386_generic_reg_edi.rcp@0x8048760-pov-type1-0 _tmp_input-recipe-type1_i386_generic_shellcode_eax.rcp@0x804883e-pov-type1-3 ...
$ gdb --args ./vulnerable s2e-last/_tmp_input-recipe- type1_i386_generic_shellcode_eax.rcp@0x804883e-pov-type1-3 (gdb) r Starting program: /home/user/s2e/env/projects/vuln-lin32-32/vulne… Demoing function pointer overwrite Program received signal SIGSEGV, Segmentation fault. 0x44556677 in ?? () (gdb) info registers eax 0xccddeeff -857870593 ecx 0x44556677 1146447479 edx 0x40 64 ebx 0x0 0 esp 0xffffcd1c 0xffffcd1c ebp 0xffffcd68 0xffffcd68 esi 0x804b008 134524936 …
add_plugin("FilePovGenerator") pluginsConfig.FilePovGenerator = {
target_pc = 0x0011223344556677,
target_gp = 0x8899aabbccddeeff }
s2e-config.lua
to get interesting PoVs
pointers
pointers, it looks at the available recipes and applies those that satisfy constraints
$ ls ~/s2e/projects/vulnerable/recipe type1_i386_generic_reg_eax.rcp type1_i386_generic_reg_ebp.rcp type1_i386_generic_reg_esi.rcp … type1_i386_generic_reg_esp.rcp type1_i386_generic_shellcode_eax.rcp type1_i386_generic_shellcode_ebp.rcp
# Set GP and EIP with shellcode # mov eax, $gp # mov ecx, $pc # jmp ecx :type=1 :reg_mask=0xffffffff :pc_mask=0xffffffff :arch=i386 :platform=generic :gp=EAX :exec_mem=EIP [EIP+0] == 0xb8 [EIP+1] == $gp[0] [EIP+2] == $gp[1] [EIP+3] == $gp[2] [EIP+4] == $gp[3] [EIP+5] == 0xb9 [EIP+6] == $pc[0] [EIP+7] == $pc[1] [EIP+8] == $pc[2] [EIP+9] == $pc[3] [EIP+10] == 0xff [EIP+11] == 0xe
# Set GP and EIP with shellcode # mov eax, $gp # mov ecx, $pc # jmp ecx :reg_mask=0xffffffff :pc_mask=0xffffffff :type=1 :arch=i386 :platform=generic :gp=EAX :exec_mem=EIP [EIP+0] == 0xb8 [EIP+1] == $gp[0] [EIP+2] == $gp[1] [EIP+3] == $gp[2] [EIP+4] == $gp[3] [EIP+5] == 0xb9 [EIP+6] == $pc[0] [EIP+7] == $pc[1] [EIP+8] == $pc[2] [EIP+9] == $pc[3] [EIP+10] == 0xff [EIP+11] == 0xe [EIP+0] == 0xb8 [EIP+1] == 0xaa [EIP+2] == 0xaa [EIP+3] == 0xaa [EIP+4] == 0xaa [EIP+5] == 0xb9 [EIP+6] == 0xbb [EIP+7] == 0xbb [EIP+8] == 0xbb [EIP+9] == 0xbb [EIP+10] == 0xff [EIP+11] == 0xe
EIP=λ11λ10λ9λ8
Look for address ranges that are
When such a range is found (e.g, at address 0xb8001200)
If constraints satisfiable => we have a PoV
Randomness (/dev/random, ASLR, etc.)
Require plugin support to track interesting memory regions
We have seen file-based PoVs so far
More details on s2e.systems/docs
Source/binary, environment, SW stack level, etc.
https://s2e.systems Ready-for-use docker image, demos, tutorials, source code, documentation
We are hiring!