Bending microarchitectural �weird machines towards practicality
Ping-Lun Wang, Riccardo Paccagnella, Riad S. Wahby, and Fraser Brown
USENIX Security, 08/14/2024
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
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
Flexo: a new design for µWM
4
ISA
✨ New circuit construction!
✨ Compiler for μWMs!
✨ μWM binary packer!
Credit: irasutoya.com
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
The construction of a weird gate
6
if (condition())
tmp += out[in1[in2[0]]];
Weird AND gate
Misprediction
Weird registers
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
Flexo’s circuit construction: differential encoding
8
AND
Flexo
AND
+-
+-
+-
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
Error correction with dynamic voting
10
5×
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%)
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!
Evaluation: experimental setup
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
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)
Flexo’s performance on crypto applications
14
(ms)
(%)
20× faster
First AES μWM!
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!
Takeaways
✨ Flexo makes μWMs much faster and more accurate
▲ GitHub repo
✨ Flexo’s compiler makes it easy to develop new µWMs
✨ μWMs are practical for program obfuscation or other attacks
Future work
17
Arch vs. μArch
18
Arch
μArch
Program counter
Register file
ALUs
…
Cache residency
Branch predictors
Prefetchers
…
Side channels
State-of-the-art μWMs
19
📜 Computing with time (EBEESG’21, ASPLOS, best paper)
📜 The ghost is the machine (WBW’23, WOOT, best paper)
📜 The Gates of Time (KKCRY’23, USENIX Sec)
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
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
Problems with existing μWMs…
22
💥 Slow and inaccurate [1]
💥 Difficult to program [1]
💥 Limited for program obfuscation [2]
[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
Existing μWMs vs. our work: Flexo
23
✨ New circuit construction
✨ First compiler for μWMs
✨ First μWM binary packer
💥 Slow and inaccurate [1]
[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]
NO! Existing μWMs are slow and unstable!
24
Problems with existing gates…
25
→ We need a more efficient way to build gates!!
The construction of a weird gate
26
if (truish())
out[in2[in1[0]]]++;
Weird gate
(code gadgets for computations)
Weird registers
(inputs/outputs)
Misprediction
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
Toy example: assignments to weird registers
28
if (truish()) {
// assign 1 to WeirdRegister1
WeirdRegister1[0]++;
// assign 0 to WeirdRegister2
clflush(WeirdRegister2);
}
Misprediction
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!
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-)
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
Any n-input gate in 1 transient execution!
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
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!
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…
What are μWMs?
35
if (truish())
out[in2[in1[0]]] = 0;
▲ AND gate
Misprediction
Existing μWMs are perfect, right?
36
AND + NOT = any circuit! 🎉
Can we compute anything, like AES? 🤔
Flexo’s dynamic error correction
37
Flexo: the first practical μWM
38
XOR
AND
Flexo
Error detection: invalid output states
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
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× |
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
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)
From C/C++ to Verilog
43
From Verilog to truth tables
44
From truth tables to Flexo
45
Wire | Cache line |
1 | 3 |
2 | 1 |
3 | 4 |
4 | 2 |
Randomized for each execution
No need to disable the prefetchers!
Evaluation: experimental setup
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 |
GoT vs. Flexo: 4-bit ALU and SHA-1 (1 round)
47
-75%
-87%
+4%
+17%
(%)
48.7×
24.7×
(μs)
Flexo’s accuracy on large circuits
48
AES (one round): 2524 gates
Simon: 4322 gates
(%)
(%)
Flexo’s runtime on large circuits
49
AES (one round): 2524 gates
Simon: 4322 gates
(ms)
(ms)
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)
Compare with GoT: 4-bit ALU and SHA-1
51
4~38.8%
higher accuracy!
5.3~48.7× speedup!
Flexo’s performance on large circuits
52
2524 gates
4322 gates
Flexo’s performance on crypto applications
53
(TODO)
Unpacking speed of Flexo + UPX
54
(TODO)
UPX: an open-source binary packer
55
Compress
Decompress
Unpack
Pack
UPX
Packed program
Input program
</>