Obfuscation vs. Deobfuscation ISSISP 2018 Christian Collberg - - PowerPoint PPT Presentation

obfuscation vs deobfuscation
SMART_READER_LITE
LIVE PREVIEW

Obfuscation vs. Deobfuscation ISSISP 2018 Christian Collberg - - PowerPoint PPT Presentation

Obfuscation vs. Deobfuscation ISSISP 2018 Christian Collberg University of Arizona 1. Obfuscating Arithmetic 2. Opaque Expressions 3. Control Flow Flattening 4. Constructing Opaque Expressions 5. Generic Deobfuscation 6. Turning up


slide-1
SLIDE 1

Christian Collberg University of Arizona

Obfuscation 


  • vs. 


Deobfuscation

ISSISP 2018

slide-2
SLIDE 2
  • 1. Obfuscating Arithmetic
  • 2. Opaque Expressions
  • 3. Control Flow Flattening
  • 4. Constructing Opaque Expressions
  • 5. Generic Deobfuscation
  • 6. Turning up the heat…
  • 7. Anti Disassembly (if time)
  • 8. Anti Tamper (if time)
slide-3
SLIDE 3

gnitacsufbO Obfuscating citemhtirA Arithmetic

slide-4
SLIDE 4

Encoding Integer Arithmetic

x+y = x−¬y−1 x+y = (x⊕y)+2·(x∧y) x+y = (x∨y)+(x∧y) x+y = 2·(x∨y)−(x⊕y)

slide-5
SLIDE 5

Example

One possible encoding of z=x+y+w is z = (((x ^ y) + ((x & y) << 1)) | w) + (((x ^ y) + ((x & y) << 1)) & w); Many others are possible, which is good for diversity.

slide-6
SLIDE 6

tigress --Environment=x86_64:Linux:Gcc:4.6\

  • -Transform=Virtualize \
  • -Functions=fib \
  • -VirtualizeDispatch=switch\
  • -Transform=EncodeArithmetic \
  • -Functions=fib \
  • -out=fib5.c fib.c
  • What differences do you notice between before and after arithmetic encoding?
  • The virtualizer’s add instruction handler could still be identified by the fact

that it uses a + operator!

  • Try adding am arithmetic transformer:

Exercise!

slide-7
SLIDE 7

int fib(int n ) { ... while (1) { switch (*(_1_fib_$pc[0])) { case PlusA: { (_1_fib_$sp[0] + -1)->_int = (_1_fib_$sp[0] + -1)->_int + (_1_fib_$sp[0] + 0)->_int; break; } }

slide-8
SLIDE 8

int fib(int n ) { ... while (1) { switch (*(_1_fib_$pc[0])) { case PlusA: { (_1_fib_$sp[0] + -1)->_int = ((_1_fib_$sp[0] + -1)->_int ^ (_1_fib_$sp[0] + 0)->_int) + (((_1_fib_$sp[0] + -1)->_int & (_1_fib_$sp[0] + 0)->_int) << 1); break; } }

x+y = (x⊕y)+2·(x∧y)

slide-9
SLIDE 9

euqapO Opaque snoisserpxE Expressions

slide-10
SLIDE 10

Opaque Expressions An expression whose value is known to you as the defender (at

  • bfuscation time) but which is

difficult for an attacker to figure out

slide-11
SLIDE 11

Opaquely indeterminate predicate

P?

FALSE TRUE

PT

TRUE FALSE TRUE

PF

FALSE

Opaquely true/ false predicate An expression


  • f value v

E=42

slide-12
SLIDE 12

Examples

true false

2|(x2 + x)T

ly true predicate:

true false

2|(x2 + x)T

ely indeterminate predicate:

false true

x mod 2 = 0?

slide-13
SLIDE 13

Inserting Bogus Control Flow

if (x[k] == 1) R = (s*y) % n else R = s; s = R*R % n; L = R; if (x[k] == E=1) R = (s*y) % n else R = s; s = R*R % n; L = R;

slide-14
SLIDE 14

Inserting Bogus Control Flow

if (x[k] == 1) R = (s*y) % n else R = s; s = R*R % n; L = R; if (x[k] == 1) R = (s*y) % n else R = s; if (expr=T) s = R*R % n; else s = R*R * n; L = R;

slide-15
SLIDE 15

Inserting Bogus Control Flow

if (x[k] == 1) R = (s*y) % n else R = s; s = R*R % n; L = R; if (x[k] == 1) R = (s*y) % n else R = s; if (expr=?) s = R*R % n; else s = (R%n)*(R%n)%n; L = R;

slide-16
SLIDE 16

Aritmetic
 Encoding

z = (((x ^ y) + ((x & y) << 1)) | w) + (((x ^ y) + ((x & y) << 1)) & w); z=x+y+w z = (((x ^ y) + ((x & y) << E=1)) | w) + (((x ^ y) + ((x & y) << E=1)) & w);

slide-17
SLIDE 17

************************************************************ * 1) Opaque Predicates ************************************************************ tigress --Environment=x86_64:Linux:Gcc:4.6 --Seed=0 \

  • -Transform=InitEntropy \
  • -InitEntropyKinds=vars \
  • -Transform=InitOpaque \
  • -Functions=main\
  • -InitOpaqueCount=2\
  • -InitOpaqueStructs=list,array \
  • -Transform=AddOpaque\
  • -Functions=fib\
  • -AddOpaqueKinds=question \
  • -AddOpaqueCount=10 \
  • -out=fib1.c fib.c

Exercise!

slide-18
SLIDE 18

wolF lortnoC Control Flow sisylanA Analysis

slide-19
SLIDE 19
  • A way to represent the possible flow of control

inside a function.

  • Nodes: called basic blocks. Each block consists
  • f straight-line code ending (possibly) in a

branch.

  • Edges: An edge A → B means that control could

flow from A to B.

  • There is one unique entry node and one unique

exit node.

Control-Flow Graph (CFG)

slide-20
SLIDE 20

int foo() { printf(“Boo!”); }

ENTRY EXIT

printf(“Boo!”);

slide-21
SLIDE 21

int foo() { x=1; y=2; printf(x+y); }

ENTRY EXIT

x=1; y=2; printf(x+y);

slide-22
SLIDE 22

int foo() { read(x); if (x>0) printf(x); }

ENTRY EXIT

x=1; if (x>0) goto B2 printf(x)

slide-23
SLIDE 23

int foo() { read(x); if (x>0) printf(x); else printf(x+1); }

ENTRY EXIT

x=1; if (x>0) goto B2 printf(x) printf(x+1)

slide-24
SLIDE 24

int foo() { x=10; while (x>0){ printf(x); x=x-1; } }

ENTRY EXIT

x=10; if (x<=0) goto B3 printf(x); x=x-1; goto B1;

slide-25
SLIDE 25

wolF lortnoC Control Flow gninettalF Flattening

slide-26
SLIDE 26

Flatten

slide-27
SLIDE 27

int modexp( int y,int x[], int w,int n){ int R, L; int k=0; int s=0; while (k < w) { if (x[k] == 1) R = (s*y) % n else R = s; s = R*R % n; L = R; k++; } return L; }

if (k<w) if (x[k]==1) s=R*R mod n L = R k++ R=s R=(s*y) mod n s=1 k=0 return L

B6 : B1 : B2 : B5 : goto B1 B4 : B3 : B0 :

slide-28
SLIDE 28

int modexp(int y, int x[], int w, int n) { int R, L, k, s; int next=0; for(;;) switch(next) { case 0 : k=0; s=1; next=1; break; case 1 : if (k<w) next=2; else next=6; break; case 2 : if (x[k]==1) next=3; else next=4; break; case 3 : R=(s*y)%n; next=5; break; case 4 : R=s; next=5; break; case 5 : s=R*R%n; L=R; k++; next=1; break; case 6 : return L; } }

slide-29
SLIDE 29

next=3 if (k<w) else next=2 next=6 next=5 R=(s*y)%n R=s next=5 S=R*R%n L=R K++ next=1 return L k=0 s=1 next=1 next=0 switch(next) if (x[k]==1) else next=4

B5 B6 B0 B1 B3 B4 B2

slide-30
SLIDE 30

Flattening Algorithm

switch

case 0: block_0 case n: block_n

  • 1. Construct the CFG
  • 2. Add a new variable int next=0;
  • 3. Create a switch inside an infinite loop, where every basic

block is a case:
 
 
 


  • 4. Add code to update the next variable:

case n: { if (expression) next = … else next = … }

slide-31
SLIDE 31

ten this CFG:

ENTER EXIT goto B2 B6 X := X − 2; B5 Y := X + 5; B4 X := X−1; A[X] := 10; if X <> 4 goto B6 if x >= 10 goto B4 B2 B3 X := 20; B1

Flatten this CFG! Work with your friends!

slide-32
SLIDE 32

euqapO Opaque snoisserpxE Expressions Constructing gnitcurtsnoC

slide-33
SLIDE 33

int modexp(int y, int x[], int w, int n) { int R, L, k, s; int next=E=0; for(;;) switch(next) { case 0: k=0; s=1; next=E=1; break; case 1: if (k<w) next=E=2; else next=E=6; break; case 2: if (x[k]==1) next=E=3; else next=E=4; break; case 3: R=(s*y)%n; next=E=5; break; case 4: R=s; next=E=5; break; case 5: s=R*R%n; L=R; k++; next=E=1; break; case 6: return L; } }

next=E=1

slide-34
SLIDE 34

Opaque Values

Opaque values from array aliasing

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 36 58 1 46 23 5 16 65 2 41 2 7 1 37 11 16 2 21 16

Invariants:

Invariants:

  • every third cell (in pink), starting will cell 0, is ≡ 1 mod 5;
  • cells 2 and 5 (green) hold the values 1 and 5, respectively;
  • every third cell (in blue), starting will cell 1, is ≡ 2 mod 7;
  • cells 8 and 11 (yellow) hold the values 2 and 7,

respectively.

slide-35
SLIDE 35

int modexp(int y, int x[], int w, int n) { int R, L, k, s; int next=0; int g[] = {10,9,2,5,3}; for(;;) switch(next) { case 0 : k=0; s=1; next=g[0]%g[1]=1; break; case 1 : if (k<w) next=g[g[2]]=2; else next=g[0]-2*g[2]=6; break; case 2 : if (x[k]==1) next=g[3]-g[2]=3; else next=2*g[2]=4; break; case 3 : R=(s*y)%n; next=g[4]+g[2]=5; break; case 4 : R=s; next=g[0]-g[3]=5; break; case 5 : s=R*R%n; L=R; k++; next=g[g[4]]%g[2]=1; break; case 6 : return L; } }

slide-36
SLIDE 36

cireneG Generic noitacsufboeD Deobfuscation

slide-37
SLIDE 37

Yadegari, et al., A Generic Approach to Deobfuscation. IEEE S&P’15

INPUT OUTPUT

TRACE ADD SUB BRA SHL CALL DIV XOR TRACE’ ADD BRA DIV XOR

Dynamic Analysis

P(){ }

PUSH() POP()

slide-38
SLIDE 38

ADD SUB BRA SHL CALL DIV XOR ADD BRA SHL DIV XOR ADD

SUB BRA

SHL

CALL DIV

XOR

Backward Taint Analysis (contribute to

  • utput)

ADD

SUB BRA

SHL

CALL DIV XOR

Forward Taint Analysis (depend on input)

ADD BRA DIV XOR

Compiler Optimizations

ADD

SUB BRA

SHL

CALL DIV

XOR

✓c←b;

main (a) { }

b←a;

printf(b);

main () { }

b←a;

slide-39
SLIDE 39

ADD BRA SHL DIV PRINT

P(){ } Not input dependent!

sub add call print

VPC

ADD SUB BRA SHL CALL DIV PRINT ADD BRA DIV PRINT ADD

SUB BRA

SHL

CALL DIV

PRINT

P(){ }

STACK: SP

slide-40
SLIDE 40

int modexp(int y, int x[], int w, int n) { int R, L, k, s; int next=E=0; for(;;) switch(next) { case 0: k=0; s=1; next=E=1; break; case 1: if (k<w) next=E=2; else next=E=6; break; case 2: if (x[k]==1) next=E=3; else next=E=4; break; case 3: R=(s*y)%n; next=E=5; break; case 4: R=s; next=E=5; break; case 5: s=R*R%n; L=R; k++; next=E=1; break; case 6: return L; } }

next=E=1

Not input dependent!

slide-41
SLIDE 41

DEC( ) DEC( ) DEC( ) ENC( ) DEC( )

Not input dependent!

slide-42
SLIDE 42

Anti-Dynamic Analysis

  • Artificially inflate trace size
  • Force the collection of multiple traces
  • Prevent traces from being collected

SUB BRA SHL CALL DIV LEA BLE SLA BRA TEST LEA JREG XOR JMP FDIV CALL LEA MUL JMP BLE SRL CALL BOR STO BRA AND CALL FADD CALL JMP TEST LEA BLE ADD SUB BAND FADD BRLE FDIV JREG BRLE SRA FADD LD STO FSUB CALL BRA LD SEXT CALL BOR JMP

main(){ } if (traced) abort();

Pawlovski, et al., Probfuscation: An Obfuscation Approach …

slide-43
SLIDE 43

Overhead Protection

slide-44
SLIDE 44

Overhead Protection Resources Precision Resources Precision

slide-45
SLIDE 45

Turning Up pU gninruT the Heat… …taeH eat Heat

slide-46
SLIDE 46

ADD SUB BRA SHL CALL DIV XOR ADD BRA SHL DIV XOR ADD

SUB BRA

SHL

CALL DIV

XOR

ADD

SUB BRA

SHL

CALL DIV XOR

Taint Analysis

ADD BRA DIV XOR ADD

SUB BRA

SHL

CALL DIV

XOR

a b;

main () { bit a,b; }

← ?

slide-47
SLIDE 47

main(){ int a ← 0 for(i←0; i<b; i++) a++ }

but control dependence

no data dependence!

😅 ☹

main(){ int a←b }

Implicit Flow

slide-48
SLIDE 48

bit value; void handler(…) { value ← 1 } main(){ value ← 0 signal(31,handler) if (b == 1) raise(31) a ← value }

still control dependence

no data dependence!

😅

main(){ bit a←b }

b

slide-49
SLIDE 49

signal(){ … } I/O

bit value; void handler(…) { value ← 1 } main(){ value ← 0 signal(31,handler) if (b == 1) raise(31) a ← value } b b b b

slide-50
SLIDE 50

signal() read() write() I/O libc GC pthread jitting

main(){ a ← b } b

Program Analysis

}

Visible State Invisible State

  • Runtime clock
  • State of CPU caches
  • State of file cache
  • State of GC
  • State of JITter

a

slide-51
SLIDE 51

main(){ }

leak into invisible state ← b retrieve into visible state ←

a ← b a

Stephens, et al., Probabilistic Obfuscation through Covert Channels, Euro S&P 2018

slide-52
SLIDE 52

start ← clock() a ← (clock()-start) > 𝜺 if (b == 1) else

“a depends

  • n clock()”

Program Analysis

}

no data or control dependence!

😅

slide-53
SLIDE 53

start ← clock() if (b == 1) else a ← (clock()-start) > 𝜺 for(i=0; i<n; i++); for(i=0; i<m; i++);

slide-54
SLIDE 54

signal() read() write() I/O libc GC pthreads jitting

start ← clock() if (b == 1) else a ← (clock()-start) > 𝜺

slide-55
SLIDE 55

start ← clock() a ← (clock()-start) > 𝜺 for (i=0; i<n; i++) if (b == 1) flush ; read ; write else flush ; read ; write

buf1 buf1 buf2 buf1 buf2 buf1

slide-56
SLIDE 56

if (b == 1)

  • pen(“foo”,NOCACHE)

else

  • pen(“foo”,CACHE)

start ← clock() read(“foo”) a ← (clock()-start) > 𝜺

slide-57
SLIDE 57

if (b == 1) ; else foo()

Jitted binary code foo foo bar baz Bytecode

start ← clock() foo() a ← (clock()-start) > 𝜺

slide-58
SLIDE 58

GC() if (b == 1) list ← else list ←

start ← clock() GC() a ← (clock()-start) > 𝜺

slide-59
SLIDE 59

start ← clock() if (b == 1) else a ← (clock()-start )> 𝜺

☐ ← time() if (☐) ☐ else ☐ ☐ ← (time()-☐)>☐

}

Pattern Analysis

Issue #1: Pattern-Matching Attacks

slide-60
SLIDE 60

create( ) a←!b a←b main(){ } b !b create( ) join( , ) bit a← b

slide-61
SLIDE 61

start ← clock() flush if (b == 1) read write else read write a ← (clock()-start) > 𝜺

main(){ bit a←b } b

Issue #2: Correctness

slide-62
SLIDE 62

tigress.cs.arizona.edu

Merge Split Dynamic Jitting

Encode Arithmetic Encode Data Opaque Predicates Encode Literals Branch Functions

Flatten

NEXT

Virtualize

Anti Taint Analysis

slide-63
SLIDE 63

Wait a Minute, aren’t these just Covert Channels?

slide-64
SLIDE 64

main(){ x←E(“secret”) }

Exfiltrate!

“secret”!

slide-65
SLIDE 65

main(){ a ← b }

slide-66
SLIDE 66

main(){ a ← b }

read CPU temperature speed up/ slow down

slide-67
SLIDE 67

main(){ a ← b }

listen to hard drive speed up/ slow down

slide-68
SLIDE 68

main(){ a ← b }

read from camera flash to screen

b

slide-69
SLIDE 69

main(){ a ← b }

read from microphone play sound

slide-70
SLIDE 70

main(){ a ← b }

slide-71
SLIDE 71

main(){ a ← b }

OK, maybe not

slide-72
SLIDE 72

itnA Anti ylbmessasiD Disassembly

slide-73
SLIDE 73

Disassemble

01101010 01010101 00001110

  • Attackers: prefer looking at assembly code than

machine code

int foo() { … … … … } add r1,r2,r3 ld r2,[r3] call bar cmp r1,r4 bgt L2

Compile

slide-74
SLIDE 74
  • Address
  • Assembly
  • Code bytes
  • 1. 100000d78: 55 push %rbp
  • 2. 100000d79: 48 89 e5 mov %rsp,%rbp
  • 3. 100000d7c: 48 83 c7 68 add $0x68,%rdi
  • 4. 100000d80: 48 83 c6 68 add $0x68,%rsi
  • 5. 100000d84: 5d pop %rbp
  • 6. 100000d85: e9 26 38 00 00 jmpq 1000045b0
  • 7. 100000d8a: 55 push %rbp
  • 8. 100000d8b: 48 89 e5 mov %rsp,%rbp
  • 9. 100000d8e: 48 8d 46 68 lea 0x68(%rsi),%rax
  • 10. 100000d92: 48 8d 77 68 lea 0x68(%rdi),%rsi
  • 11. 100000d96: 48 89 c7 mov %rax,%rdi
  • 12. 100000d99: 5d pop %rbp
  • 13. 100000d9a: e9 11 38 00 00 jmpq 1000045b0
  • 14. 100000d9f: 55 push %rbp

55 48 89 e5 48 83 c7 68 48 83 c6 68 5d e9 26 38 00 00 55 48 89 e5 48 89 e5 48 8d 46 68 48 89 c7 5d e9 11 38 00 00 55

slide-75
SLIDE 75
  • Disassembly is hard! And sometimes

disassemblers get it wrong!

  • In general, this is always the

case: program analysis is more or less precise.

Disassemble

01101010 01010101 00001110 add r1,r2,r3 ld r2,[R4] call bar mul r1,r4 bgt L2

slide-76
SLIDE 76
  • 1. push %rbp
  • 2. mov %rsp,%rbp
  • 3. add $0x68,%rdi
  • 4. add $0x68,%rsi
  • 5. pop %rbp
  • 6. jmpq 1000045b0
  • 7. push %rbp

55 48 89 e5 48 83 c7 68 48 83 c6 68 5d e9 26 38 00 00 55

Linear sweep disassembly

slide-77
SLIDE 77
  • 1. 0xd78: push %rbp
  • 2. 0xd79: mov %rsp,%rbp
  • 3. 0xd7c: add $0x68,%rdi
  • 4. 0xd80: add $0x68,%rsi
  • 5. 0xd84: pop %rbp
  • 6. 0xd85: jmpq 0x45b0
  • 7. 0xd8a: push %rbp

55 48 89 e5 48 83 c7 68 48 83 c6 68 5d e9 26 38 00 00 55

Recursive traversal disassembly

0x45b0 0xd8a 0xd78

Stack

slide-78
SLIDE 78
  • 1. 0xd78: push %rbp
  • 2. 0xd79: mov %rsp,%rbp
  • 3. 0xd7c: add $0x68,%rdi
  • 4. 0xd80: add $0x68,%rsi
  • 5. 0xd84: pop %rbp
  • 6. 0xd85: jmpr %rdi

7.0xd8b: mov %rdi,%rbp

Indirect jump!

Exercise!

  • How would a recursive traversal

disassembly handle this code?

slide-79
SLIDE 79
  • Insert unreachable bogus instructions:

if (PF) asm(“.byte 0x55 0x23 0xff…”);

  • This kind of lightweight obfuscation is

common in malware.

Insert Bogus Dead Code

slide-80
SLIDE 80

uint32 Skypes_hash_function () { addr_t addr =(addr_t)((uint32)addr ^(uint32)addr); addr = (addr_t)((uint32) addr + 0 x688E5C); uint32 hash = 0x320E83 ^ 0x1C4C4 ; int bound = hash + 0 xFFCC5AFD ; do { uint32 data =*((addr_t)((uint32)addr + 0x10)); goto b1; asm volatile (".byte 0x19"); b1: hash = hash ⊕ data ; addr -= 1; bound --; } while (bound !=0); goto b2; asm volatile (".byte 0x73"); b2: goto b3; asm volatile (".word 0xC8528417,…”); b3: hash -= 0x4C49F346; return hash; }

slide-81
SLIDE 81

jmp b … … …
 
a: Branch Functions call bf 
 
a: void branchFun(){ 
 } r = ret_addr(); return to (r+α); return;


… … …

.byte 42,…

slide-82
SLIDE 82

************************************************************ * 7) Branch Functions ************************************************************ tigress --Environment=x86_64:Linux:Gcc:4.6 \

  • -Transform=InitBranchFuns \
  • -InitBranchFunsCount=1 \
  • -Transform=AntiBranchAnalysis \
  • -AntiBranchAnalysisKinds=branchFuns \
  • -Functions=fib \
  • -out=fib7.c fib.c

Exercise!

slide-83
SLIDE 83

itnA Anti repmaT Tamper

slide-84
SLIDE 84

Tamperproofing has to do two things:

  • 1. detect tampering
  • 2. respond to tampering

Essentially: but this is too unstealthy! if (tampering-detected()) respond-to-tampering()

slide-85
SLIDE 85

int foo () { if (today > “Aug 17,2016”){ printf(“License expired!”); abort; } } int foo () { if (false){ printf(“License expired!”); abort; } } int foo () { if (today > “Aug 17,2016”){ printf(“License expired!”); abort; } }

check(){ if (hash(foo)!=42) abort() }

slide-86
SLIDE 86

if (foo-has-changed-in-any-way())

Checker1

int foo() { … … … … } int foo_copy() { … … … … }

copy Repair foo!!! int foo() { … … … … } int foo_copy() { … … … … }

slide-87
SLIDE 87

if (foo-has-changed-in-any-way())

Checker1

int foo() { … … … … } int foo_copy() { … … … … }

copy

Repair Checker1!!

if (checker1-has-changed())

Checker2

if (foo-changed())

Checker1

foo_copy copy foo

if (foo-changed())

Checker1_copy

foo_copy copy foo

copy

slide-88
SLIDE 88

Checker Checker Checker Repair Repair Repair Code block Code block Code block

slide-89
SLIDE 89

The future… …rtutuf ehT

slide-90
SLIDE 90

Secure Hardware

MATE Defenses

Crypto-Based Obfuscation

Language-Based Obfuscation

slide-91
SLIDE 91

Language-Based Obfuscation

  • Time-limited protection
  • No security guarantees
  • Easily tunable
  • Easily upgradable
slide-92
SLIDE 92

Secure Hardware

  • Stronger root of trust
  • Break once, break everywhere
  • Hard to upgrade
  • Side channel attacks
  • Cost of physical protection
  • Intel licensing
slide-93
SLIDE 93

Crypto-Based Obfuscation

  • Strong security guarantees
  • Performance issues

Program Generate Run 2-bit multiplier 1027 years 108 years 16-bit point function 7 hours, 25G 4 hours (later, 20 minutes)

slide-94
SLIDE 94

Updatable Security Strong Root of Trust Security guarantees for small security kernels

Problem #2: Combine Protections

slide-95
SLIDE 95

Thank You! !uoY knahT

Supported by NSF CNS-1525820

tigress.cs.arizona.edu collberg.cs.arizona.edu

Thanks to my collaborators: Debray, Stephens, Yadegari, Scheidegger, Levine