
The first analysis framework for CPU microcode
Custom Processing Unit é o primeiro framework de análise dinâmica capaz de fazer hook, patch e trace do microcódigo da CPU em nível de software.
Ele funciona aproveitando instruções não documentadas em CPUs Intel que permitem acesso ao CRBUS. Usando nosso decompilador de microcódigo fizemos engenharia reversa de como a CPU usa o CRBUS e, ao replicar as interações, temos controle total da CPU.
Encontre o framework de análise estática como subárvore na pasta este, ou em https://github.com/pietroborrello/ghidra-atom-microcode.
Confira nossos slides descrevendo este trabalho aqui.
Nota: Custom Processing Unit requer uma CPU Red-Unlocked: atualmente, apenas CPUs Goldmont (GLM) possuem um Red Unlock público. Testamos o Gigabyte GB-BPCE-3350C com CPU stepping 0x9 e 0xa (cpuid 0x000506C9 e 0x000506CA).
Custom Processing Unit é composto por uma aplicação UEFI e algumas bibliotecas. A aplicação UEFI interage com a CPU GLM, enquanto as bibliotecas fornecem diferentes helpers para compilar microcódigo na aplicação UEFI e analisar sua saída.
GNU_EFI_DIR=<path_to_gnu_efi> make
Isso compilará os arquivos de microcódigo fonte e a aplicação UEFI em cpu.efi.
Copie cpu.efi para a pasta \EFI\ do pen drive, conecte-o na GLM e inicialize no EFI shell.
Execute map -r no EFI shell para identificar o dispositivo do pen drive e <deviceid>: para montá-lo.
Execute ./cpu.efi para imprimir a ajuda:
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 fornece helpers para executar instruções simples pela linha de comando:
cpu fornece interfaces para rotinas complexas da CPU que são interessantes de executar para estudar o comportamento da CPU:
u: atualiza o ucode da CPU com o patch fornecido (assinado)f: coleta contadores de performance enquanto executa microcódigocpu fornece interfaces brutas para as instruções não documentadas udbrd e udbgwr.
Os comandos mais interessantes que elas fornecem são:
cpu expõe rotinas de acesso LDAT para leitura e escrita. Especifique os parâmetros [port] [array] [bank] [idx] [addr] para ler ou escrever nesses locais.
Portas interessantes são:
Observe que acessar alguns desses componentes internos pode fazer a CPU travar.
cpu fornece funcionalidades para instalar patches no microcódigo.
bios/ucode_patches/ucode_patch.u (veja os outros patches para exemplos)cpu.efi p para instalar o patch no endereço fornecido em .org.Observe que, no microcódigo, apenas os endereços entre 0x7c00 e 0x7e00 são graváveis e relevantes para patch.
Executando cpu.efi x, ele também executará o microcódigo corrigido e imprimirá os registradores rax, rbx, rcx, rdx como resultado.
Para executar automaticamente microcódigo em determinados eventos da CPU ou pontos de microcódigo, cpu usa o Match & Patch.
Ele define um endereço de microcódigo para hook e o endereço de microcódigo para o qual saltar quando o hook for acionado.
z: redefine todo o match & patch.h: instala um hook, dado um índice (0-0x20), um endereço para hook (0-0x7c00) e um endereço de destino para executar (0x7c00-0x7e00).Instalando vários hooks e executando continuamente uma instrução, cpu é capaz de rastrear as microoperações executadas por tal instrução e despejá-las. Para rastrear:
// [TRACED INSTRUCTION HERE] em get_trace_clock_at().cpu.efi m.
Será criado um arquivo trace.txt contendo todos os endereços que foram atingidos.uasm-lib/uasm.py -t trace.txt > parsed_trace.txt.
Isso gerará um trace completo do microcódigo executado durante a instrução.Observe que uasm.py usará os arquivos ms_arrayX.txt em sua pasta para gerar uma desmontagem das microinstruções executadas. Eles são para GLM com stepping 0x9 (cpuid 0x000506C9). Gere os arrays adequados caso você tenha um stepping diferente.
Você pode usar as funcionalidades de dump LDAT para esse fim.
A CPU possui diferentes buffers inacessíveis pela arquitetura, para os quais fornecemos rotinas de dump:
smm: SMROM (ou qualquer outro endereço enquanto desativa a proteção SMM)rom: ROM internaimms: imediatos fixos da CPUmsrs: configurações internas de MSRsFornecemos um montador que gera arquivos de cabeçalho para serem compilados na aplicação UEFI cpu.efi.
Veja os patches fornecidos em bios/ucode_patches para a sintaxe.
Ele suporta operações simples e rótulos.
Monte um patch de microcódigo com uasm.py -i ucode_patch.u -o ucode_patch.h.
cpu.efi será compilado e incluirá automaticamente o patch de microcódigo que você deseja aplicar.
arquivo: code_patch.u
.org 0x7c00
rax:= ZEROEXT_DSZ32(0x00001337)
rbx:= ZEROEXT_DSZ32(0x00001337)
rcx:= ZEROEXT_DSZ32(0x00001337)
rdx:= ZEROEXT_DSZ32(0x00001337)
recompile e depois execute na GLM:
cpu.efi z # zero out match & patch
cpu.efi p # apply the patch
cpu.efi h 0 0x0428 0x7c00 # rdrand entry point
agora, toda vez que rdrand for executado, ele retornará 0x1337 nos registradores.
Nosso trabalho foi publicado em um paper no 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},
}
Os experimentos descritos no paper podem ser executados com:
cpu.efi e [exp_idx]
com [exp_idx]: