Skip to content

Commit a291cc2

Browse files
committed
First commit, passes a bunch of the Kocher test cases
0 parents  commit a291cc2

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+868
-0
lines changed

.gitignore

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.vscode
2+
__pycache__
3+
venv
4+
*~

driver.py

+155
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
from specvex import SimEngineSpecVEX, SpecState
2+
from oob import OOBState
3+
from spectre import SpectreState
4+
5+
import angr
6+
import claripy
7+
import logging
8+
import monkeyhex
9+
10+
logging.getLogger('angr.engines').setLevel(logging.INFO)
11+
#logging.getLogger('angr.engines.vex.expressions').setLevel(logging.INFO)
12+
#logging.getLogger('angr.engines.unicorn').setLevel(logging.INFO)
13+
#logging.getLogger('angr.engines.hook').setLevel(logging.INFO)
14+
logging.getLogger('angr.state_plugins.symbolic_memory').setLevel(logging.DEBUG)
15+
logging.getLogger('specvex').setLevel(logging.DEBUG)
16+
logging.getLogger('boundstracking').setLevel(logging.DEBUG)
17+
18+
def fauxware():
19+
proj = angr.Project('../angr-binaries/tests/x86_64/fauxware')
20+
state = proj.factory.entry_state()
21+
return (proj, state)
22+
23+
def kocher(s):
24+
"""
25+
Pass a string like "01" or "12" to get an angr project and state for that
26+
Kocher test case.
27+
"""
28+
proj = angr.Project('spectector-clang/'+s+'.o')
29+
arg = claripy.BVS("x", 64) # unconstrained
30+
funcaddr = proj.loader.find_symbol("victim_function_v"+s).rebased_addr
31+
state = proj.factory.call_state(funcaddr, arg)
32+
state.globals['arg'] = arg
33+
return (proj, state)
34+
35+
def kocher11(s):
36+
"""
37+
Pass one of 'gcc', 'ker', of 'sub' to get an angr project and state for
38+
the Kocher test case '11gcc', '11ker', or '11sub' respectively.
39+
"""
40+
proj = angr.Project('spectector-clang/11'+s+'.o')
41+
arg = claripy.BVS("x", 64) # unconstrained
42+
funcaddr = proj.loader.find_symbol("victim_function_v11").rebased_addr
43+
state = proj.factory.call_state(funcaddr, arg)
44+
state.globals['arg'] = arg
45+
return (proj, state)
46+
47+
def blatantOOB():
48+
proj = angr.Project('blatantOOB.o')
49+
arg = claripy.BVS("x", 64) # unconstrained
50+
funcaddr = proj.loader.find_symbol("victim_function_v01").rebased_addr
51+
state = proj.factory.call_state(funcaddr, arg)
52+
state.globals['arg'] = arg
53+
return (proj, state)
54+
55+
def armBoundsChecks(proj,state):
56+
state.register_plugin('oob', OOBState(proj))
57+
assert len(state.oob.inbounds_intervals) > 0
58+
state.oob.arm(state)
59+
assert state.oob.armed()
60+
61+
def armSpectreChecks(proj,state):
62+
state.register_plugin('oob', OOBState(proj))
63+
state.register_plugin('spectre', SpectreState())
64+
state.spectre.arm(state)
65+
assert state.spectre.armed()
66+
67+
def makeSpeculative(proj, state):
68+
proj.engines.register_plugin('specvex', SimEngineSpecVEX(1000))
69+
proj.engines.order = ['specvex' if x=='vex' else x for x in proj.engines.order] # replace 'vex' with 'specvex'
70+
if proj.engines.has_plugin('vex'): proj.engines.release_plugin('vex')
71+
72+
#state.options.discard(angr.options.LAZY_SOLVES) # turns out LAZY_SOLVES is not on by default
73+
state.register_plugin('spec', SpecState())
74+
assert state.spec.ins_executed == 0
75+
76+
def runState(proj, state):
77+
simgr = proj.factory.simgr(state, save_unsat=True)
78+
if state.has_plugin('oob'):
79+
simgr.use_technique(OOBViolationFilter())
80+
if state.has_plugin('spectre'):
81+
simgr.use_technique(SpectreViolationFilter())
82+
simgr.run()
83+
return simgr
84+
85+
def showbbVEX(proj, bbaddr):
86+
proj.factory.block(bbaddr).vex.pp()
87+
88+
def showbbASM(proj, bbaddr):
89+
proj.factory.block(bbaddr).capstone.pp()
90+
91+
class OOBViolationFilter(angr.exploration_techniques.ExplorationTechnique):
92+
def __init__(self):
93+
super().__init__()
94+
95+
def filter(self, simgr, state, **kwargs):
96+
if state.oob.violation: return 'oob_violation'
97+
return simgr.filter(state, **kwargs)
98+
99+
class SpectreViolationFilter(angr.exploration_techniques.ExplorationTechnique):
100+
def __init__(self):
101+
super().__init__()
102+
103+
def filter(self, simgr, state, **kwargs):
104+
if state.spectre.violation: return 'spectre_violation'
105+
return simgr.filter(state, **kwargs)
106+
107+
def runSpec(s):
108+
proj,state = kocher(s)
109+
armSpectreChecks(proj,state)
110+
makeSpeculative(proj,state)
111+
return runState(proj,state)
112+
113+
def runNotSpec(s):
114+
proj,state = kocher(s)
115+
armSpectreChecks(proj,state)
116+
return runState(proj,state)
117+
118+
def run11Spec(s):
119+
proj,state = kocher11(s)
120+
armSpectreChecks(proj,state)
121+
makeSpeculative(proj,state)
122+
return runState(proj,state)
123+
124+
def run11NotSpec(s):
125+
proj,state = kocher11(s)
126+
armSpectreChecks(proj,state)
127+
return runState(proj,state)
128+
129+
def runallSpec():
130+
return unionDicts(
131+
{s:runSpec(s) for s in ['01','02','03','04','05','06','07','08','09','10','12','13','14','15']},
132+
{('11'+s):run11Spec(s) for s in ['gcc','ker','sub']})
133+
134+
def runallNotSpec():
135+
return unionDicts(
136+
{s:runNotSpec(s) for s in ['01','02','03','04','05','06','07','08','09','10','12','13','14','15']},
137+
{('11'+s):run11NotSpec(s) for s in ['gcc','ker','sub']})
138+
139+
def alltests():
140+
notspec = runallNotSpec()
141+
spec = runallSpec()
142+
def violationDetected(simgr):
143+
return 'spectre_violation' in simgr.stashes and len(simgr.spectre_violation) > 0
144+
def testResult(s):
145+
return ("FAIL: detected a violation without speculative execution" if violationDetected(notspec[s])
146+
else "FAIL: no violation detected" if not violationDetected(spec[s])
147+
else "PASS")
148+
return {k:testResult(k) for k in spec.keys()}
149+
150+
def unionDicts(dicta, dictb):
151+
return {**dicta, **dictb} # requires Python 3.5+
152+
153+
if __name__ == '__main__':
154+
from pprint import pprint
155+
pprint(alltests())

oob.py

+123
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import angr
2+
import claripy
3+
4+
import logging
5+
l = logging.getLogger(name=__name__)
6+
7+
class OOBState(angr.SimStatePlugin):
8+
"""
9+
State tracking for crudely marking what is determined 'in-bounds'.
10+
If you 'arm' it, it reports any potential memory read/write to any area not marked 'in-bounds'.
11+
12+
This 'in-bounds' determination is not used by the Spectre checker (which
13+
relies on uninitialized memory for the initial 'secret' determination, then a
14+
form of taint tracking), but it is used by the OOB concretization strategy below
15+
(which both OOB and Spectre checking rely on). However, the Spectre checker doesn't
16+
need/want the OOB state to be 'armed'.
17+
"""
18+
19+
def __init__(self, proj=None, inbounds_intervals=None, armed=False):
20+
"""
21+
Either 'proj' or 'inbounds_intervals' must not be None. Initial inbounds intervals
22+
will be taken from 'proj', or else from the explicit 'inbounds_intervals' if 'proj' is None.
23+
"""
24+
super().__init__()
25+
if proj is None:
26+
self.inbounds_intervals = inbounds_intervals
27+
else:
28+
self.inbounds_intervals = [(obj.min_addr, obj.max_addr) for obj in proj.loader.all_objects]
29+
# this is an overapproximation of what is in-bounds: everything that is mapped is in-bounds.
30+
# the analysis then tells us if we can leak the value of things that are "not mapped".
31+
# of course, we separately need to account for when more memory is allocated, for instance
32+
# malloc() or when the stack is expanded (??)
33+
self._armed = armed
34+
self.violation = None
35+
36+
@angr.SimStatePlugin.memo
37+
def copy(self, memo):
38+
return OOBState(inbounds_intervals=self.inbounds_intervals, armed=self._armed)
39+
40+
def arm(self, state):
41+
"""
42+
Setup hooks and breakpoints to perform bounds tracking.
43+
Also set up concretization to ensure addresses are always made to be OOB when possible.
44+
"""
45+
state.inspect.b('mem_read', when=angr.BP_AFTER, condition=_read_is_oob, action=detected_oob_read)
46+
state.inspect.b('mem_write', when=angr.BP_AFTER, condition=_write_is_oob, action=detected_oob_write)
47+
48+
state.memory.read_strategies.insert(0, OOBStrategy())
49+
state.memory.write_strategies.insert(0, OOBStrategy())
50+
51+
state.options.add(angr.options.SYMBOLIC_WRITE_ADDRESSES)
52+
53+
self._armed = True
54+
55+
def armed(self):
56+
"""
57+
Has arm() been called?
58+
"""
59+
return self._armed
60+
61+
# Call during a breakpoint callback on 'mem_read'
62+
def _read_is_oob(state):
63+
addr = state.inspect.mem_read_address
64+
length = state.inspect.mem_read_length
65+
return _is_oob(state, addr, length)
66+
67+
# Call during a breakpoint callback on 'mem_write'
68+
def _write_is_oob(state):
69+
addr = state.inspect.mem_write_address
70+
length = state.inspect.mem_write_length
71+
return _is_oob(state, addr, length)
72+
73+
def _is_oob(state, addr, length):
74+
l.debug("checking if {} is oob".format(addr))
75+
inbounds_intervals = state.oob.inbounds_intervals + [get_stack_interval(state)]
76+
oob_constraints = [claripy.Or(addr < mn, addr+length > mx) for (mn,mx) in inbounds_intervals]
77+
if state.solver.satisfiable(extra_constraints=oob_constraints): return True # there is a solution to current constraints such that the access is OOB
78+
#l.debug("inbounds: {} >= {} and {} <= {}".format(addr, hexprint(mn), addr+length, hexprint(mx))) # when you also take into account all the current constraints
79+
return False # operation must be inbounds
80+
81+
def get_stack_interval(state):
82+
sp = state.regs.rsp
83+
end_of_stack = 0x7fffffffffffffff # this is an assumption, but I think a good one for ELF programs
84+
return (sp, end_of_stack)
85+
86+
# print a claripy AST as-is, or if it's a Python int, print in hex
87+
def hexprint(val):
88+
if isinstance(val, claripy.Base): return "{}".format(val)
89+
else: return "0x{:02X}".format(val)
90+
91+
def detected_oob_read(state):
92+
print("\n!!!!!!!! OUT-OF-BOUNDS READ !!!!!!!!\n Address {}\n Value {}\n x={}\n constraints were {}\n".format(
93+
state.inspect.mem_read_address, state.inspect.mem_read_expr, state.globals['arg'], state.solver.constraints))
94+
state.oob.violation = (state.inspect.mem_read_address, state.inspect.mem_read_expr)
95+
96+
def detected_oob_write(state):
97+
print("\n!!!!!!!! OUT-OF-BOUNDS WRITE !!!!!!!!\n Address {}\n Value {}\n x={}\n constraints were {}\n".format(
98+
state.inspect.mem_write_address, state.inspect.mem_write_expr, state.globals['arg'], state.solver.constraints))
99+
state.oob.violation = (state.inspect.mem_write_address, state.inspect.mem_write_expr)
100+
101+
class OOBStrategy(angr.concretization_strategies.SimConcretizationStrategy):
102+
"""
103+
Concretization strategy which attempts to resolve every address to
104+
somewhere *outside* of bounds (as determined by the OOBState state
105+
plugin). See notes on superclass (and other subclasses) for more info on
106+
what's happening here.
107+
"""
108+
109+
def __init__(self, **kwargs):
110+
super().__init__(**kwargs)
111+
112+
def _concretize(self, memory, addr):
113+
"""
114+
Attempts to resolve the address to a value *outside* all of the in-bounds
115+
intervals if possible.
116+
Else, defers to fallback strategies.
117+
"""
118+
try:
119+
constraints = [memory.state.solver.Or(addr < mn, addr >= mx) for (mn, mx) in memory.state.oob.inbounds_intervals]
120+
return [ self._any(memory, addr, extra_constraints=constraints) ]
121+
except angr.errors.SimUnsatError:
122+
# no solution
123+
return None

specmem.py

Whitespace-only changes.

specstate.py

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import angr
2+
3+
import collections

spectector-clang/01.c

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#include <string.h>
2+
#include <stdlib.h>
3+
#include <stdint.h>
4+
5+
unsigned int array1_size = 16;
6+
uint8_t array1[16] = { 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16 };
7+
uint8_t array2[256 * 512];
8+
uint8_t temp = 0; /* Used so compiler won't optimize out victim_function() */
9+
10+
void victim_function_v01(size_t x) {
11+
if (x < array1_size) {
12+
temp &= array2[array1[x] * 512];
13+
}
14+
}

spectector-clang/01.o

1.44 KB
Binary file not shown.

spectector-clang/02.c

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#include <string.h>
2+
#include <stdlib.h>
3+
#include <stdint.h>
4+
5+
unsigned int array1_size = 16;
6+
uint8_t array1[16] = { 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16 };
7+
uint8_t array2[256 * 512];
8+
uint8_t temp = 0; /* Used so compiler won't optimize out victim_function() */
9+
10+
void leakByteLocalFunction(uint8_t k) { temp &= array2[(k)* 512]; }
11+
void victim_function_v02(size_t x) {
12+
if (x < array1_size) {
13+
leakByteLocalFunction(array1[x]);
14+
}
15+
}

spectector-clang/02.o

1.6 KB
Binary file not shown.

spectector-clang/03.c

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#include <string.h>
2+
#include <stdlib.h>
3+
#include <stdint.h>
4+
5+
unsigned int array1_size = 16;
6+
uint8_t array1[16] = { 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16 };
7+
uint8_t array2[256 * 512];
8+
uint8_t temp = 0; /* Used so compiler won't optimize out victim_function() */
9+
10+
__declspec(noinline) void leakByteNoinlineFunction(uint8_t k) { temp &= array2[(k)* 512]; }
11+
void victim_function_v03(size_t x) {
12+
if (x < array1_size)
13+
leakByteNoinlineFunction(array1[x]);
14+
}

spectector-clang/03.o

1.54 KB
Binary file not shown.

spectector-clang/04.c

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#include <string.h>
2+
#include <stdlib.h>
3+
#include <stdint.h>
4+
5+
unsigned int array1_size = 16;
6+
uint8_t array1[16] = { 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16 };
7+
uint8_t array2[256 * 512];
8+
uint8_t temp = 0; /* Used so compiler won't optimize out victim_function() */
9+
10+
void victim_function_v04(size_t x) {
11+
if (x < array1_size)
12+
temp &= array2[array1[x << 1] * 512];
13+
}

spectector-clang/04.o

1.44 KB
Binary file not shown.

spectector-clang/05-no-unsigned.c

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#include <string.h>
2+
#include <stdlib.h>
3+
#include <stdint.h>
4+
5+
unsigned int array1_size = 16;
6+
uint8_t array1[16] = { 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16 };
7+
uint8_t array2[256 * 512];
8+
uint8_t temp = 0; /* Used so compiler won't optimize out victim_function() */
9+
10+
// void victim_function_v05(size_t x) {
11+
// int i;
12+
// if (x < array1_size) {
13+
// for (i = x - 1; i >= 0; i--)
14+
// temp &= array2[array1[i] * 512];
15+
// }
16+
// }
17+
18+
// Patched version using signed integers (passing MIN_INT as argument
19+
// have problems due to an integer underflow in i=x-1). Spectector
20+
// X86->muAasm does not support unsigned arithmetic yet.
21+
void victim_function_v05(long x) {
22+
int i;
23+
if (x >=0 && x < array1_size) {
24+
for (i = x - 1; i >= 0; i--)
25+
temp &= array2[array1[i] * 512];
26+
}
27+
}

spectector-clang/05-no-unsigned.o

1.68 KB
Binary file not shown.

spectector-clang/05.c

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#include <string.h>
2+
#include <stdlib.h>
3+
#include <stdint.h>
4+
5+
unsigned int array1_size = 16;
6+
uint8_t array1[16] = { 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16 };
7+
uint8_t array2[256 * 512];
8+
uint8_t temp = 0; /* Used so compiler won't optimize out victim_function() */
9+
10+
void victim_function_v05(size_t x) {
11+
int i;
12+
if (x < array1_size) {
13+
for (i = x - 1; i >= 0; i--)
14+
temp &= array2[array1[i] * 512];
15+
}
16+
}

spectector-clang/05.o

1.65 KB
Binary file not shown.

0 commit comments

Comments
 (0)