Generic exploit for all version 7 (maybe others) LM32-based AMD SMU's used in APUs (and probably works on GPUs too). Note that since Zen, the SMU/RSMU (Remote SMU) are Xtensa cores and thus this does not apply to them.
I'll be cleaning up and uploading the code to perform these exploits but for now I will just jot down a description of the exploit.
OK, so Rudolf Marek already exploited an LM32-based AMD SMU in his talk titled AMD x86 SMU firmware analysis Do you care about Matroshka processors? but AMD supposedly patched the SMU message handler vulnerability.
I am lazy, I didn't want to find a new software-based exploit in the SMU firmware even though I wrote a Ghidra LM32 Processor plugin in order to analyze the firmwares. Are there any other ways to get code execution in SMU? Once you do, you can dump its bootrom which contains the HMAC key so you can sign your own firmwares, and do cool stuff like read eFuses, get serial port IO and even read/write arbitrary DRAM. Pretty cool, huh?
The SMU's SRAM has an unlocked region at 0x3F000-0x40000
for communication between x86 world and SMU world. Trying to read out other parts, including the bootrom, will give you 0x55aa55aa or 0xaa55aa55 (TODO: I forget which, its read lockout value is stored in another SMU register).
Well it turns out AMD integrated the LM32 so completely that it retained its debug functionality. The attack proceeds as follows, all accessesss are done via an indirection addresss/data scheme to the PCI root complex:
- Monitor SMU SRAM
0x3F000-0x40000
to find suitable place for a code cave for your shellcode. I've provided a visualization of the activity there
- I ended up picking
0x0003_F400-0x0003_F600
for my tiny shellcode stubs in one instance.
- Monitor SMU's Program Counter by polling in a loop the
SMC_PC_C
register at0x80000370
. Generate a histogram and pick the most executed instruction address. - Write the register
SMC_LM32_DEBA
at0x80000334
to point to your debug vector table residing in your shellcode, in the shared SRAM region. - Write the register
SMC_LM32_BP0
at0x80000338
to point to the most commonly executed instruction address. - Write the register
SMC_LM32_DC
at0x80000330
to enable HW breakpoint zero. - Wait until the firmware runs that common instruction address and the breakpoint will be triggered and jump to your debug vector table shellcode.
- From within the SMU you can turn off the read/write SRAM protections and read out the bootrom.
The bootrom will contain a hardcoded HMAC key but there is an eFuse bit that allows vendors to program a custom HMAC key in the eFuses. Luckily those can be dumped too.
This is the Ghidra decompilation from the bootrom of a Kaveri or Kabini APU (I forget which right now)
AUTH_START_ADDR = (FIRMWARE_HEADER_V7 *)(efuse_read1 & 0x7fffffff);
entry_point = (code *)AUTH_START_ADDR->EntryPoint;
calc_digest[1] = 0xefcdab89;
calc_digest[3] = 0x10325476;
calc_digest[0] = 0x67452301;
calc_digest[2] = 0x98badcfe;
// sha constant shit
calc_digest[4] = 0xc3d2e1f0;
do {
efuse_read1 = read_volatile_4(SMU_FIRMWARE_AUTH);
// spin while SMU_KEY_RD_DONE != 11b
} while ((efuse_read1 & 6) != 6);
efuse_read1 = read_volatile_4(SMU_FIRMWARE_AUTH);
// SMU_KEY_SEL
if ((efuse_read1 & 0x20000) == 0) {
key[0] = 0x3818c563;
key[1] = 0x2f4383c1;
key[2] = 0x21853a57;
key[3] = 0xd7c7169;
}
else {
key[0] = read_volatile_4(SMU_SECURE_KEY_0);
key[1] = read_volatile_4(SMU_SECURE_KEY_1);
key[2] = read_volatile_4(SMU_SECURE_KEY_2);
key[3] = read_volatile_4(SMU_SECURE_KEY_3);
}
hmac(&AUTH_START_ADDR->Version,key,calc_digest,AUTH_START_ADDR->CodeSize >> 2);