Programmers naturally assume that different programs require different code. Minesweeper is not the same as AES, Windows is not the same as Linux, and Notepad is not the same as malware. But what if this were not the case? We'll walk through how we can convert all programs into the exact same code - allowing the CPU to execute the same sequence of instructions, to run any possible application. By fundamentally changing our ideas about what it means to "compute", we'll outline the unsettling implications for malware detection, and open some fascinating new doors in exploitation.
3. Observation:
Different paths can produce the same result.
Thought experiment:
Can the inverse be true?
Can one path produce different results?
./intro
4. Observation:
Groups of (machine) instructions can often be reduced
x = x * 2
x = x + x
vs.
x = x * 4
Thought experiment:
How much could one program be reduced?
./intro
12. Removing all but the mov instruction from
future iterations of the x86 architecture
would have many advantages: the
instruction format would be greatly
simplified, the expensive decode unit
would become much cheaper, and silicon
currently used for complex functional
units could be repurposed as even more
cache. As long as someone else
implements the compiler.
- Stephen Dolan
21. Build on Dolan’s ideas
Adapt primitive TM operations for higher level logic
Work on actual data, not abstract symbols
Add new operations
If/else
Arithmetic
Logic
Jumps
Loops
Etc…
Bring it closer to something we can use
Idea…
24. The catch:
We have no branches
All paths execute, no matter what
Solution:
Force a path to operate on “dummy” data,
if we don’t want its results
Implementing if
25. IF X == Y THEN
X = 100
Implementing if
Selector
Data
Scratch
26. IF X == Y THEN
X = 100
Implementing if
Data
Scratch
⇐
⇐
Selector
27. IF X == Y THEN
X = 100
Implementing if
Data
Scratch
Selector
28. IF X == Y THEN
X = 100
Implementing if
Data
Scratch
⇐
⇐
Selector
29. IF X == Y THEN
X = 100
Implementing if
Data
Scratch
Selector
30. ; X == Y
mov eax, [X]
mov [eax], 0
mov eax, [Y]
mov [eax], 4
mov eax, [X]
; X = 100
mov eax, [SELECT_X + eax]
mov [eax], 100
Implementing if
64. How much can we simplify a program?
We’ve gotten rid of…
Functions
Loops
Branches
Arithmetic
… it’s an okay start.
Reduction
65. We have all the same instructions
mov
But they’re not all the ”same”
mov eax, edx
mov [100], bl
mov di, 0x1337
mov eax, [edx + 2 * ecx + 0x1094801]
Reduction
70. Simplify memory addressing
mov eax, [0x1234 + ecx + 8 * edx]
Use the M/o/Vfuscator ALU!
Calculate ecx + 8 * edx
Accumulate results in ecx
Result:
(dozens of mov ALU instructions)
mov eax, [0x1234 + ecx]
x86 addressing
71. No more register to register transfers.
No more constant to register transfers.
No more 8 or 16 bit instructions.
All memory accesses are of the form
[reg + constant]
mov instructions are now all
mov reg, [reg + constant]
or
mov [reg + constant], reg
Almost there.
72. We still use 8 registers!
Decrease to 2 by storing extras to
scratchpad memory
All mov instructions now
mov esi/edi, [esi/edi + constant]
or
mov [esi/edi + constant], esi/edi
Registers
73. Writes: mov [esi/edi + constant], esi/edi
Reads: mov esi/edi, [esi/edi + constant]
Alternate reads and writes
1 read, followed by 1 write,
followed by 1 read, followed by 1 write…
Insert dummy (unused) accesses
Alternating memory
76. Psuedo-registers
Instead of registers,
use memory to hold the register contents
esi becomes [0x890100]
edi becomes [0x890200]
Let’s call these memory locations
“psuedo-registers”
77. A ‘synthetic’ mov
With psuedo-registers, our movs look like
mov [0x890100],[[0x890200]+0x816d0e8]
At this point, we’re just moving values
to/from different locations in memory
… but this is no longer a valid x86 instruction
79. A ‘synthetic’ mov
Translate the old mov instructions
into the more generic MOVE instruction
mov esi,[edi+0x816d0e8]
becomes
MOVE [ *0x890100 + 0 ], [ *0x890200 + 0x816d0e8 ]
mov [edi+0x816d0e8], esi
becomes
MOVE [ *0x890200 + 0x816d0e8 ], [ *0x890100 + 0 ]
80. A ‘synthetic’ mov
Why is this useful?
All instructions now have exactly the same form.
81. Extracting the essence
MOVE [ *0x890100 + 0 ], [ *0x890200 + 0x816d0e8 ]
{ 0x890100, 0, 0x890200, 0x816d0e8 }
Extract all of the MOVE operands into a table.
82. We’ve distilled our entire C program
into a table describing
a long list of simple data transfers
Let’s write a program to
perform the actions described in the table
So now what?
83. Load the table into esi…
table:
0x890100, 0, 0x890200, 0x816d0e8
...
mov esi, table
94. The instruction sequence
executed by the processor
is the same for every program.
mov esi, table
loop:
mov ebx, [esi]
mov ebx, [ebx]
add ebx, [esi+4]
mov ebx, [ebx]
mov edx, [esi+8]
mov edx, [edx]
add edx, [esi+12]
mov [edx], ebx
mov esi, [esi+16]
jmp loop
95. 1.
If every program is the same,
malware detection gets a whole lot harder.
(demo)
Doing the same thing
(In practice)
Implications
96. 2.
Exploitation
“AVROP” (Mark Barnes, MWR Labs)
https://github.com/mwrlabs/avrop
AVR microcontroller (Raspberry Pi)
Harvard architecture: limited to ROP
Small memory: very few gadgets
Fortunately, all we need is mov.
Implications