https://asahilina.net/agx-exploit/ I hacked macOS!!! CVE-2022-32947 With Lina & Cyan GitHub * Video On today's menu... * A bit about GPUs * How virtual memory works * What's a GPU command buffer? * Apple's super secret uPPL * Return-oriented programming * Special demo! But first a demo!!! [root] Live demo * Source code GPU intro CPU vs. GPU [cpugpu] How Apple GPUs work [gpufwhw] That day in 2022... [lina_notic] Virtual Memory What's a page table anyway? Pointers #include using namespace std; int main() { int a = 0; void *p = &a; cout << "&a is " << p << " nya!" << endl; } &a is 0x7fffc6ba5ddc nya! This is a virtual address! | 0x7fffc6ba5ddc | 0x814745ddc | This is the physical address! How does this work? [virtualmem] [virtualmem] [pagetables] [pagetables] [cpugpuproc] [cpugpuproc] [cpugpuproc] GPU/CPU Memory [cpugpupt] [cpugpukpt] Page Tables [pt_big] [pt_big2] 40 bits for virtual address (1 TiB) 16 KiB pages - 67,108,864 pages total 8 bytes per page address = 512 MiB page table!!!! Too big!!! [pt_tree] [pt_transla] Permissions [pt_permiss] [uat_space] TTBAT Translation Table Base Address Table [ttbat] That day in 2022... [lina_notic] [lina_notic] [lina_notic] [lina_notic] [lina_notic] [lina_notic] Hacking command buffers Weird machines? Command buffers [cmdbuf] [cmdbuf_1] [cmdbuf_2] [cmdbuf_3] Micro Commands * 00: - * 01: Wait For Idle * 02: Wait 2 * 03: Doorbell * 04: Write 8-bit reg * 05: Write 32-bit reg * 06: Write 64-bit reg * 07: - * 08: Read 8-bit reg * 09: Read 32-bit reg * 0a: Read 64-bit reg * 0b: Wait 32-bit reg * 0c: Wait 64-bit reg * 0d: - * 0e: Store 32-bit * 0f: Store 64-bit * 10: Load 32-bit * 11: Load 64-bit * 12: ? * 13: ? * 14: ? * 15: Test (If) * 16: Logic op * 17: Add 16-bit * 18: Retire * 19: Timestamp * 1a: KTrace * 1b: ? * 1c: ? * 1d: ? * 1e: Jump (Cond) * 1f: ? * 20: ? * 21: ? * 22: Start Vertex * 23: Finish Vertex * 24: Start Fragment * 25: Finish Fragment * 26: Start Blit * 27: Finish Blit * 28: ? * 29: Start Compute * 2a: Finish Compute * 2b: ? * 2c: ? It's Turing Complete! * 2 64-bit scratch registers * Loads and stores (direct only) * AND, OR, XOR, shifts * Test for zero ("if") * Jump / Conditional jump Tricks Indirect read/writes? R0 = *R1; [selfmod] Tricks Multiply? R1 = 10 x R0; [multiply] We can: * Read/write firmware data memory * Do math We can't: * Modify the firmware code * Modify the page tables But... uPPL More secrets... [uppl] [uppl_1] uPPL can map memory! * If page is uPPL: DENY * If page is page table: DENY * If page is secure uPPL/kernel data: DENY * If page is TTBAT.... ??? ALLOWED!!! They forgot to blocklist that one!!!! Hack Idea * Make fake PT that gives access to the real PTs * Ask uPPL to give us access to the TTBAT * Write fake PT to TTBAT (context 63 TTB0) * Ask uPPL to activate context 63 * Write fake PT address to TTB1 page table GPU can now read/write page tables directly!! Problem We can read/write stuff from the microsequence, but we can't call the uPPL... We can't modify the firmware code directly... Solution Return-oriented Programming (ROP)! Return-oriented programming More weird machines???? Call and return [callret] [callret_1] [callret_2] [callret_3] [callret_4] [callret_5] [callret_6] [callret_7] Multiple calls? [callret2_b] [callret2_b] [callret2_b] [callret2_b] [callret2_b] [callret2_b] [callret2_g] [callret2_g] [callret2_g] [callret2_g] [callret2_g] [callret2_g] [callret2_g] [callret2_g] [callret2_g] [callret2_g] [callret2_g] Challenge! [rop_0] [rop_1] [rop_2] [rop_3] [rop_4] [rop_5] [rop_6] [rop_7] [rop_8] [rop_9] [rop_10] Data registers [rop_regs0] [rop_regs1] [rop_regs2] Gadgets can be weird... g_calltwo: Call two functions... But the first argument is a pointer to a pointer to a pointer to the function And the second one is a pointer to 0x70 before a pointer to the function Gadgets can be weird... g_store: Store a value to memory... But it actually stores to the address you pass plus 0xa78 Exploit time! This is going to be so yabai... Putting it together * Shader: Set up microseq and ROP chain data in unused memory * Shader: Overwrite microseq pointers * Microsequence: Calculate and fill in a lot of data using firmware info, then overwrite stack to start ROP * ROP: Use uPPL bug and make a page table accessible to shaders * Shader: Map more page tables and get access to ALL RAM * Profit????? It's hard to demo this all working in practice... Challenge Accepted! [letsgo] Reset Next Step Skip to ROP Mode Microsequence registers R0 0x0000000000000000 R1 0x0000000000000000 X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 X16 X17 X18 X19 X20 X21 X22 X23 X24 X25 X26 X27 X28 X29 LR SP PC