
The first analysis framework for CPU microcode
Custom Processing Unit è il primo framework di analisi dinamica in grado di agganciare, patchare e tracciare il microcodice della CPU a livello software.
Funziona sfruttando istruzioni non documentate nelle CPU Intel che consentono l'accesso al CRBUS. Usando il nostro decompilatore di microcodice abbiamo effettuato il reverse engineering di come la CPU utilizza il CRBUS e, replicando le interazioni, abbiamo il pieno controllo della CPU.
Il framework di analisi statica è disponibile come subtree in questa cartella, oppure su https://github.com/pietroborrello/ghidra-atom-microcode.
Consulta le nostre slide che descrivono questo lavoro qui.
Nota: Custom Processing Unit richiede una CPU Red-Unlocked: attualmente, solo le CPU Goldmont (GLM) hanno una Red Unlock pubblica. Abbiamo testato Gigabyte GB-BPCE-3350C con CPU stepping 0x9 e 0xa (cpuid 0x000506C9 e 0x000506CA).
Custom Processing Unit è composta da un'applicazione UEFI e da alcune librerie. L'applicazione UEFI interagisce con la CPU GLM, mentre le librerie forniscono diversi helper per compilare il microcodice nell'applicazione UEFI e analizzarne l'output.
GNU_EFI_DIR=<path_to_gnu_efi> make
Questo compilerà i file sorgente del microcodice e l'applicazione UEFI in cpu.efi.
Copia cpu.efi nella cartella \EFI\ della chiavetta USB, inseriscila nella GLM e avvia la shell EFI.
Esegui map -r nella shell efi per identificare il dispositivo della chiavetta USB e <deviceid>: per montarlo.
Esegui ./cpu.efi per stampare l'aiuto:
Usage:
patch: <tool> p
patch & exec: <tool> x
perf: <tool> f
zero out m&p: <tool> z
hook: <tool> h [m&p idx] [uop addr] [patch addr]
template: <tool> m
dump imms: <tool> di
dump rom: <tool> dr
dump msrs: <tool> dm
dump SMM: <tool> ds [address] [size]
cpuid: <tool> c [rax] [rcx]
rdmsr: <tool> rm [msr]
wrmsr: <tool> wm [msr]
read: <tool> r [cmd] [addr]
write: <tool> w [cmd] [addr] [value]
invoke: <tool> i [addr]
update ucode: <tool> u [size]
ldat read: <tool> lr [port] [array] [bank] [idx] [addr] [optional size]
ldat write: <tool> lw [port] [array] [bank] [idx] [addr] [value]
cpu fornisce helper per eseguire semplici istruzioni dalla riga di comando:
cpu fornisce interfacce a routine complesse della CPU che è interessante eseguire per studiarne il comportamento:
u: aggiorna l'ucode della CPU con la patch (firmata) fornitaf: raccoglie i contatori delle prestazioni durante l'esecuzione del microcodicecpu fornisce interfacce raw alle istruzioni non documentate udbrd e udbgwr.
I comandi più interessanti che forniscono sono:
cpu espone routine di accesso LDAT per leggere e scrivere. Specifica i parametri [port] [array] [bank] [idx] [addr] per leggere o scrivere lì.
Le porte interessanti sono:
Nota: l'accesso ad alcuni di questi componenti interni potrebbe causare il blocco della CPU.
cpu fornisce funzionalità per installare patch nel microcodice.
bios/ucode_patches/ucode_patch.u (guarda le altre patch per esempi)cpu.efi p per installare la patch all'indirizzo fornito in .org.Nota che nel microcodice, solo gli indirizzi tra 0x7c00 e 0x7e00 sono scrivibili e significativi per la patch.
Eseguendo cpu.efi x, eseguirà anche il microcodice patchato e stamperà i registri rax, rbx, rcx, rdx come risultato.
Per eseguire automaticamente il microcodice in determinati eventi della CPU o punti del microcodice, cpu sfrutta il Match and Patch.
Definisce un indirizzo di microcodice da agganciare (hook) e l'indirizzo di microcodice a cui saltare quando l'hook viene attivato.
z: reimposta tutto il match & patch.h: installa un hook, dato un indice (0-0x20), un indirizzo da agganciare (0-0x7c00) e un indirizzo target da eseguire (0x7c00-0x7e00).Installando più hook ed eseguendo continuamente un'istruzione, cpu è in grado di tracciare le microoperazioni eseguite da tale istruzione e di scaricarle (dump). Per tracciare:
// [TRACED INSTRUCTION HERE] in get_trace_clock_at().cpu.efi m.
Creerà un file trace.txt che contiene tutti gli indirizzi che sono stati raggiunti.uasm-lib/uasm.py -t trace.txt > parsed_trace.txt.
Genererà una traccia completa del microcodice eseguito durante l'istruzione.Nota che uasm.py utilizzerà i file ms_arrayX.txt nella sua cartella per generare un disassembly delle microistruzioni eseguite. Questi sono per GLM con stepping 0x9 (cpuid 0x000506C9). Genera gli array corretti nel caso tu abbia uno stepping diverso.
Puoi usare le funzionalità di dump LDAT per questo scopo.
La CPU ha diversi buffer inaccessibili dall'architettura, per i quali forniamo routine per il dump:
smm: SMROM (o qualsiasi altro indirizzo disabilitando la protezione SMM)rom: ROM internaimms: immediati hardcoded della CPUmsrs: configurazioni interne degli MSRForniamo un assembler che genera file header da compilare nell'applicazione UEFI cpu.efi.
Guarda le patch fornite in bios/ucode_patches per la sintassi.
Supporta semplici operazioni ed etichette.
Assembla una patch di microcodice con uasm.py -i ucode_patch.u -o ucode_patch.h.
cpu.efi verrà compilato e includerà automaticamente la patch di microcodice che vuoi applicare.
file: code_patch.u
.org 0x7c00
rax:= ZEROEXT_DSZ32(0x00001337)
rbx:= ZEROEXT_DSZ32(0x00001337)
rcx:= ZEROEXT_DSZ32(0x00001337)
rdx:= ZEROEXT_DSZ32(0x00001337)
ricompila, quindi esegui nella GLM:
cpu.efi z # zero out match & patch
cpu.efi p # apply the patch
cpu.efi h 0 0x0428 0x7c00 # rdrand entry point
ora ogni volta che rdrand viene eseguita, restituirà 0x1337 nei registri.
Il nostro lavoro è stato pubblicato in un articolo al WOOT 2023:
@inproceedings{Borrello2023CustomProcessingUnit,
title = {{CustomProcessingUnit}: Reverse Engineering and Customization of Intel Microcode},
author = {Borrello, Pietro and Easdon, Catherine and Schwarzl, Martin and Czerny, Roland and Schwarz, Michael},
booktitle = {IEEE Workshop on Offensive Technologies (WOOT 23)},
year = {2023},
}
Gli esperimenti descritti nell'articolo possono essere eseguiti con:
cpu.efi e [exp_idx]
con [exp_idx]: