1 of 55

Bending microarchitectural �weird machines towards practicality

Ping-Lun Wang, Riccardo Paccagnella, Riad S. Wahby, and Fraser Brown

USENIX Security, 08/14/2024

2 of 55

A new μArch side-channel

2

▲ CPU

µArch weird machines (µWMs)

µArch

ISA

🤯 Prevents code analysis

😎 µArch states → invisible to Arch

😈 Defeats debuggers and emulators

μArch computer?

Seems to be great for program obfuscation?

µWMs: difficult to analyze!

Credit: irasutoya.com

3 of 55

Existing µWMs need improvements…

3

Thousands of gates

Acc. (%)

💥 Not scalable for large computations

_32BitValueToCacheState(C_arch, C1, NULL);

_32BitValueToCacheState(D_arch, D1, NULL);

_32BitValueToCacheState(E_arch, E, NULL);

_32BitValueToCacheState(K[0], K0, NULL);

_32BitValueToCacheState(W_arch, W, NULL);

LeftShift(A1, A1, 27);

mfence();

lfence();

trash = _32BitAnd(B1, C1, B_AND_C, trash);

mfence();

lfence();

trash = _32BitNot(B2, NOT_B, trash);

mfence();

lfence();

trash = _32BitAnd(NOT_B, D1, NOT_B_AND_D, trash);�// 128 lines in total

💥 Difficult to create new µWMs

4 of 55

Flexo: a new design for µWM

4

ISA

✨ New circuit construction!

  • 4× smaller, 25× faster, ≥ 97.8% accuracy

✨ Compiler for μWMs!

  • C/C++ to µWMs

✨ μWM binary packer!

  • Obfuscate UPX with AES and Simon

Credit: irasutoya.com

5 of 55

Arch vs. μArch “weird” computation

5

“Normal” computation

“Weird” computation

How to compute

Where values are stored

ISA instructions

“Weird” gates: memory operations during transient execution

Registers or memory

“Weird” registers: cache residency information

- In the cache: value 1

- Out of cache: value 0

6 of 55

The construction of a weird gate

6

if (condition())

    tmp += out[in1[in2[0]]];

Weird AND gate

Misprediction

Weird registers

7 of 55

Prior work’s AND gate

7

Time

in1

in2

in1

condition returns

in2

in1

in2

Misprediction

in1

in2

out = 1

out = 0

in1

in2

1

1

1

0

0

1

0

0

out

1

0

0

0

if (condition())

    tmp += out[in1[in2[0]]];

Misprediction

8 of 55

Flexo’s circuit construction: differential encoding

  • Use two wires for one binary value
    • 1 → (+, -) = (1, 0)
    • 0 → (+, -) = (0, 1)
    • Invalid states: (1, 1) or (0, 0)
    • Provides a lot of benefits!

8

AND

Flexo

AND

+-

+-

+-

9 of 55

New encoding → much more scalable gates!

9

if (condition()) {   

tmp += out_p[in1_p[0] + in2_m[0]];

    tmp += out_p[in1_m[0] + in2_p[0]];

    tmp += out_m[in1_p[0] + in2_p[0]];

    tmp += out_m[in1_m[0] + in2_m[0]];

}

AND gates

OR gates

in1

in2

out

1

0

1

0

1

1

1

1

0

0

0

0

Any gate with ≤ 4 inputs works!!

E.g., 4-bit XOR or 2-bit adder

10 of 55

Error correction with dynamic voting

10

Prior work (GoT) [1]: 3-out-of-5 voting

+-

+-

+-

Error?

Flexo: rerun only when error occurs

[1] The Gates of Time: Improving Cache Attacks with Transient Execution, Katzman et al., USENIX Sec ‘23

💥 Fixed 5× overhead (81% → 96%)

💥 Low efficacy (57% → 83%)

✨ Dynamic overhead (1.24× to fix 81% → 100%)

✨ High efficacy (0.3% → 99.9%)

11 of 55

Flexo’s compiler: the first compiler for μWMs

11

void __weird__sha1_round(

    unsigned* input,

    unsigned* output, unsigned* error_output

) {

    unsigned a = input[0];

    unsigned b = input[1];

    unsigned c = input[2];

    unsigned d = input[3];

    unsigned e = input[4];

    unsigned w = input[5];

    unsigned k = 0x5A827999;

�    unsigned f = (b & c) | ((~b) & d);

    unsigned temp = ROL(a, 5) + f + e + w + k;

�    output[0] = temp;

    output[1] = a;

    output[2] = ROL(b, 30);

    output[3] = c;

    output[4] = d;

}

_32BitValueToCacheState(C_arch, C1, NULL);

_32BitValueToCacheState(D_arch, D1, NULL);

_32BitValueToCacheState(E_arch, E, NULL);

_32BitValueToCacheState(K[0], K0, NULL);

_32BitValueToCacheState(W_arch, W, NULL);

LeftShift(A1, A1, 27);

mfence();

lfence();

trash = _32BitAnd(B1, C1, B_AND_C, trash);

mfence();

lfence();

trash = _32BitNot(B2, NOT_B, trash);

mfence();

lfence();

trash = _32BitAnd(NOT_B, D1, NOT_B_AND_D, trash);

mfence();

lfence();

trash = _32BitOr(B_AND_C, NOT_B_AND_D, B_AND_C_OR_NOT_B_AND_D, trash);

mfence();

lfence();

trash = _32BitAdder_impl(A1, B_AND_C_OR_NOT_B_AND_D, SUM_1, WORDSIZE, trash);

mfence();

lfence();

…�

(128 lines in total)

(21 lines in total)

😇 No low-level details

✨ Optimizations!

12 of 55

Evaluation: experimental setup

  • 8 shared AWS EC2 instances
  • All Flexo µWMs are generated by our compiler
  • Compare with prior work (GoT) [1] on the Skylake machine

12

Microarchitecture

Instance type

AMD Zen 1

t3a.xlarge

AMD Zen 2

c5a.xlarge

AMD Zen 3

c6a.xlarge

AMD Zen 4

m7a.xlarge

Intel Skylake

c5n.xlarge

Intel Cascade Lake

m5n.xlarge

Intel Icelake

m6in.xlarge

Intel Sapphire Rapids

m7i.xlarge

[1] The Gates of Time: Improving Cache Attacks with Transient Execution, Katzman et al., USENIX Sec ‘23

13 of 55

GoT vs. Flexo: 4-bit ALU and SHA-1 (1 round)

13

4.1×

smaller

7.8×

smaller

+4%

+17%

(%)

48.7×

faster

24.7×

faster

(μs)

14 of 55

Flexo’s performance on crypto applications

14

(ms)

(%)

20× faster

First AES μWM!

15 of 55

UPX + Flexo: the first μWM binary packer

15

Pack

Unpack

Decompress

Compress

UPX + WM

Input program

Packed program

</>

Decrypt

(Using Flexo)

Encrypt

chksum

chksum

Roughly 1 minute to unpack 132 KB using Simon

(5 minutes using AES)

Slower, but defeats anti-virus tools!

16 of 55

Takeaways

✨ Flexo makes μWMs much faster and more accurate

  • Bending microarchitectural weird machines towards practicality
  • GitHub: https://github.com/joeywang4/Flexo
  • Contact: Ping-Lun Wang, pinglunw@andrew.cmu.edu

▲ GitHub repo

✨ Flexo’s compiler makes it easy to develop new µWMs

✨ μWMs are practical for program obfuscation or other attacks

17 of 55

Future work

  • Optimize circuits for µWMs
  • Expand attack surfaces (e.g., bypass sandbox)
  • Detect and defend µWMs

17

18 of 55

Arch vs. μArch

18

Arch

μArch

Program counter

Register file

ALUs

Cache residency

Branch predictors

Prefetchers

Side channels

19 of 55

State-of-the-art μWMs

19

📜 Computing with time (EBEESG’21, ASPLOS, best paper)

  • SHA-1: 26.5 minutes
  • Obfuscate XOR operations in malware

📜 The ghost is the machine (WBW’23, WOOT, best paper)

  • First NOT gate construction in the literature
  • Work on Intel, AMD, and ARM processors
  • SHA-1: <1 second with 95.1% accuracy (prefetchers disabled)

📜 The Gates of Time (KKCRY’23, USENIX Sec)

20 of 55

OR gate: works in parallel

20

if (truish()) {

    out[in1[0]]++;

    out[in2[0]]++;

}

Time

in = 1

in = 0

truish returns

out = 1

Misprediction

(transient window)

out = 0

21 of 55

NOT gate: variable transient window size

21

if (in[0])

    out[delay()]++;

Time

out = 1

delay()

Transient execution ends

Transient execution starts

in = 1

small window

in = 0

large window

22 of 55

Problems with existing μWMs…

22

💥 Slow and inaccurate [1]

💥 Difficult to program [1]

  • SHA-1 takes ~1 second
  • Need to disable prefetchers
  • SHA-1: ~2K LoC
  • Only work on specific CPUs

💥 Limited for program obfuscation [2]

  • Obfuscate trivial computations

[1] The Gates of Time: Improving Cache Attacks with Transient Execution, Katzman et al., USENIX Sec ‘23

[2] Computing with time: microarchitectural weird machines, Evtyushkin et al., ASPLOS ‘21

23 of 55

Existing μWMs vs. our work: Flexo

23

✨ New circuit construction

  • SHA-1 is ~20× faster
  • ≥ 97.8% accuracy with prefetchers enabled

✨ First compiler for μWMs

  • SHA-1: 274 LoC
  • Tested on 8 μArchs from AMD and Intel
  • Obfuscate UPX with AES and Simon encryption

✨ First μWM binary packer

💥 Slow and inaccurate [1]

  • SHA-1 takes ~1 second
  • Need to disable prefetchers
  • SHA-1: ~2K LoC
  • Only work on specific CPUs
  • Obfuscate trivial computations

[1] The Gates of Time: Improving Cache Attacks with Transient Execution, Katzman et al., USENIX Sec ‘23

[2] Computing with time: microarchitectural weird machines, Evtyushkin et al., ASPLOS ‘21

💥 Difficult to program [1]

💥 Limited for program obfuscation [2]

24 of 55

NO! Existing μWMs are slow and unstable!

  • SHA-1 takes ~1 second
  • Can only obfuscate XOR
  • Accuracy drops quickly
    • AES is ~5× larger than SHA-1

24

25 of 55

Problems with existing gates…

  • Some gates are too complex to build
    • XOR: (in1[0] ∨ in2[0]) ∧ (in1[0] ∧ in2[0])
    • Existing work: at least 5 weird gates for XOR

25

→ We need a more efficient way to build gates!!

26 of 55

The construction of a weird gate

  • Weird registers: store 1-bit values
    • In cache/cache hit: value 1
    • Out of cache/cache miss: value 0
  • truish(): mis-trains the branch predictor to trigger transient execution

26

if (truish())

    out[in2[in1[0]]]++;

Weird gate

(code gadgets for computations)

Weird registers

(inputs/outputs)

Misprediction

27 of 55

Prior work’s AND and OR gates

27

if (truish())

    out[in2[in1[0]]]++;

// out = in1 & in2

Time

in1

in2

in1

truish returns

in2

in1

in2

Misprediction

in1

in2

out = 1

out = 0

if (truish()) {

    out[in1[0]]++;

    out[in2[0]]++;

} // out = in1 | in2

Time

in = 1

in = 0

truish returns

out = 1

Misprediction

out = 0

Misprediction

Misprediction

28 of 55

Toy example: assignments to weird registers

  • truish(): mis-trains the branch predictor to trigger transient execution
  • Weird registers: store 1-bit values
    • In cache/cache hit: value 1
    • Out of cache/cache miss: value 0

28

if (truish()) {

// assign 1 to WeirdRegister1

    WeirdRegister1[0]++;

// assign 0 to WeirdRegister2

    clflush(WeirdRegister2);

}

Misprediction

29 of 55

New encoding → much more powerful gates

29

💥Prior work: 5+ gates for XOR

in1

in2

out

in1+

out+

out-

in2+

in1-

in2-

✨Flexo: XOR in 1 gate!

30 of 55

Constructing Flexo gates: from truth tables

30

in1

in2

out

1

1

0

1

0

1

0

1

1

0

0

0

Truth table of XOR

out- = AND(in1+, in2+)

out+ = AND(in1+, in2-)

out+ = AND(in1-, in2+)

out- = AND(in1-, in2-)

31 of 55

Constructing Flexo gates: XOR!

31

if (truish()) {

    out_m[in1_p[in2_p[0]]]++;

    out_p[in1_p[in2_m[0]]]++;

    out_p[in1_m[in2_p[0]]]++;

    out_m[in1_m[in2_m[0]]]++;

}

in1

in2

out

1

1

0

1

0

1

0

1

1

0

0

0

Truth table of XOR

OR the rows together

32 of 55

Any n-input gate in 1 transient execution!

  • n = 4 in our experiments
  • Reduce the circuit size by 75~87%

32

if (truish()) {

    c1_p[b1_p[a1_m[0]]] = 0;             // -0 + -1 = -1

    c1_p[b1_m[a1_p[0]]] = 0;             // -1 + -0 = -1

    c1_m[b1_m[a1_m[0]]] = 0;             // -0 + -0 = -0

    c1_m[b1_p[a1_p[0]]] = 0;             // -1 + -1 = -0

    c2_p[b2_m[a2_m[b1_p[a1_p[0]]]]] = 0; // 01 + 01 = 1-

    c2_p[b2_p[a2_m[b1_m[0]]]] = 0;       // 0- + 10 = 1-

    c2_p[b2_p[a2_m[a1_m[0]]]] = 0;       // 00 + 1- = 1-

    c2_p[b2_m[a2_p[b1_m[0]]]] = 0;       // 1- + 00 = 1-

    c2_p[b2_m[a2_p[a1_m[0]]]] = 0;       // 10 + 0- = 1-

    c2_p[b2_p[a2_p[b1_p[a1_p[0]]]]] = 0; // 11 + 11 = 1-

    c2_m[b2_m[b1_m[a2_m[0]]]] = 0;       // 0- + 00 = 0-

    c2_m[b2_m[a2_m[a1_m[0]]]] = 0;       // 00 + 0- = 0-

    c2_m[b2_p[b1_p[a2_m[a1_p[0]]]]] = 0; // 01 + 11 = 0-

    c2_m[b2_m[b1_p[a2_p[a1_p[0]]]]] = 0; // 11 + 01 = 0-

    c2_m[b2_p[b1_m[a2_p[0]]]] = 0;       // 1- + 10 = 0-

    c2_m[b2_p[a2_p[a1_m[0]]]] = 0;       // 10 + 1- = 0-

} // c2c1 = a2a1 + b2b1

33 of 55

Constructing Flexo gates: AND

33

AND

+-

+-

+-

out

in1

in2

1. Negate the minus wire

out+ = in1∙in2

out- = ¬(in1∙in2)

2. Expand to sum-of-product

out+ = in1∙in2

out- = ¬in1 + ¬in2

3. Replace inputs with +/- wires

out+ = in1+∙in2+

out- = in1- + in2-

Removed all negations!

34 of 55

Constructing Flexo gates: AND

34

AND

+-

+-

+-

out

in1

in2

out+ = in1+∙in2+

out- = in1- + in2-

if (truish()) {

    out_p[in2_p[in1_p[0]]] = 0;

    out_m[in1_m[0]] = 0;

    out_m[in2_m[0]] = 0;

}

Composing AND and OR gates

🤔 Extra OR for an AND…

35 of 55

What are μWMs?

  • Compute in transient execution
  • Store values in cache residency
  • Invisible architecturally
    • Great for obfuscation!

35

if (truish())

    out[in2[in1[0]]] = 0;

▲ AND gate

Misprediction

36 of 55

Existing μWMs are perfect, right?

36

AND + NOT = any circuit! 🎉

Can we compute anything, like AES? 🤔

37 of 55

Flexo’s dynamic error correction

  • Prior work: 3-out-of-5 majority voting
    • Fixed 5× overhead for circuits with high accuracy
    • Not enough votes for circuits with low accuracy
  • Flexo: error detection + majority voting
    • Rerun circuits until one of the following is true
      • No error detected
      • Have enough votes

37

38 of 55

Flexo: the first practical μWM

  • New circuit construction
    • Much more powerful gates
    • SHA-1 is 24.7× faster
    • AES with ≥ 99.7% accuracy
  • Flexo compiler
    • Build and optimize μWM easily
  • Obfuscate real-world programs
    • UPX with μWMs for decryption

38

XOR

AND

Flexo

39 of 55

Error detection: invalid output states

  • Suppose an AND gate should output 1

39

AND

+-

+-

+-

10

10

10

Correct!

AND

+-

+-

+-

10

10

11

Error detected!

AND

+-

+-

+-

10

10

00

Invalid states

AND

+-

+-

+-

10

10

01

Incorrect!

(undetectable)

Valid but incorrect

40 of 55

Flexo’s error correction: fast and accurate

40

EC: off

EC: on

Overhead

81%

96%

5.01×

57%

83%

4.96×

EC: off

EC: on

Overhead

80.9%

100%

1.24×

25.6%

99.9%

3.84×

Flexo

Prior work (GoT)

1. Circuit with high accuracy

2. Circuit with low accuracy

3. Circuit with extremely low accuracy

0.3%

99.9%

298×

41 of 55

Applications of μWMs

1. Program obfuscation

41

if (truish()) {

    malicious_gates();

}

2. Amplify side-channel signals

if (truish()) {

out1[in[0]] = 0;

out2[in[0]] = 0;

out3[in[0]] = 0;

    …

}

One cache hit/miss

Many cache hits/misses

⇒ coarse timers

  • Not visible in architectural states
  • Cannot debug (no single-stepping)
  • Computations → memory accesses

42 of 55

Flexo’s compiler: the first compiler for μWMs

42

1. Input C/C++ program

2. Verilog circuit

3. Truth tables for Flexo gates

4. Output LLVM IR (with Flexo)

Generate and optimize Flexo

Logic synthesis using Yosys (EDA tool)

Translate LLVM IR to high-level Verilog (not HLS)

43 of 55

From C/C++ to Verilog

  • Some limitations
    • No branches or function calls
    • No memory operations (except for input/output wires)

43

44 of 55

From Verilog to truth tables

  • EDA tool (Yosys) synthesizes the circuit into truth tables (LUTs)
  • Circuit optimizations
  • Future work: circuit optimization for Flexo
    • Schedule the gates
    • Synthesize gates with multiple outputs

44

45 of 55

From truth tables to Flexo

  • Generate Flexo gates, allocate wires, and perform optimizations
  • Example: optimizing the memory layout
    • Prefetchers may track memory access patterns
    • Solution: randomize wires to memory mapping

45

Wire

Cache line

1

3

2

1

3

4

4

2

Randomized for each execution

No need to disable the prefetchers!

46 of 55

Evaluation: experimental setup

  • 8 shared AWS EC2 instances
  • All Flexo circuits are generated by our compiler

46

Microarchitecture

Instance type

AMD Zen 1

t3a.xlarge

AMD Zen 2

c5a.xlarge

AMD Zen 3

c6a.xlarge

AMD Zen 4

m7a.xlarge

Intel Skylake

c5n.xlarge

Intel Cascade Lake

m5n.xlarge

Intel Icelake

m6in.xlarge

Intel Sapphire Rapids

m7i.xlarge

47 of 55

GoT vs. Flexo: 4-bit ALU and SHA-1 (1 round)

47

-75%

-87%

+4%

+17%

(%)

48.7×

24.7×

(μs)

48 of 55

Flexo’s accuracy on large circuits

48

AES (one round): 2524 gates

Simon: 4322 gates

(%)

(%)

49 of 55

Flexo’s runtime on large circuits

49

AES (one round): 2524 gates

Simon: 4322 gates

(ms)

(ms)

50 of 55

Unpacking speed of Flexo + UPX

50

AES

Simon

Zen 1

355.63

2126.06

Zen 2

242.17

64.66

Zen 3

259.49

304.72

Zen 4

263.98

2008.87

Skylake

354.48

79.37

Cascade Lake

300.12

58.92

Icelake

306.12

353.28

Sapphire Rapids

268.35

50.13

(Seconds)

51 of 55

Compare with GoT: 4-bit ALU and SHA-1

51

4~38.8%

higher accuracy!

5.3~48.7× speedup!

52 of 55

Flexo’s performance on large circuits

52

2524 gates

4322 gates

53 of 55

Flexo’s performance on crypto applications

53

(TODO)

54 of 55

Unpacking speed of Flexo + UPX

54

(TODO)

55 of 55

UPX: an open-source binary packer

55

Compress

Decompress

Unpack

Pack

UPX

Packed program

Input program

</>