notes (5814B)
1Find in bootx86.efi that there is a loop that should not terminate, 2that terminates though.. sometimes 1 + 1 does not cmp to 2. 3 4Disassembly the rest of the image, it seems harmless.. probably there 5is a backdoor in the OVMF code. 6 7Also failed to upload a custom disk image with the same .efi file.. 8time stamps / uids in the vfat were different but nothing else.. 9probably also prevented by ovmf with some sort of checksum 10 11Address of bootx86.efi loaded is 0x5ab7000 (found by searching for strings), 12Also once at a different address.. but I assume it first loads the image 13and then puts it at an address to execute once its been verified / modified. 14 15Indeed one of the images was different from the original, but the only 16thing modified was the base address and some values in data, since 17the program had already started running. 18 19 20Can use gdb commands "ignore N M" to ignore hits on breakpoint M for N times. 21After the program finally makes it through the braekpoint we can use "info break" 22to get the amount of times the breakpoint was hit. 23 => 128 hits until the value was modified worked 24 25Use python virt-firmware package virt-fw-dump -i FILE to dump code map and var infos 26 27Whatever is modifying the execution is checking the register state to do so.. 28we can transform the conditional branch to a jmp and it still makes it past.. 29 30Use qemu trace function and sleep to figure out what is triggered in the critical section. 31 32interupts with registers: qemu -d "int" -D log 33apic events: qemu -d "trace:*apic*" -D log 34 35in output we see that SMM is entered at 0x5ad0346 and left at 0x5ad0351 with 36rax changed to 3. 37 38We can confirm that the SMM handler is in the OVMF_CODE, because 39if we build EDK2 ourselves and swap out OVMF_CODE it does not get past the loop 40and we dont see any SMI interrupts in the qemu trace. 41 42I still dont understand why this challenge allows a remote connection.. 43probably the flag is the flash we are given is slightly different 44with the actual flag only recoverable on remote. 45 46 47Since we know we are trying to find out what the SMI handler does, lets 48look at the SMI handler code.. The actual mapping of the smi handler is 49done by the firmware, but we can set a watchpoint on the standard location 50SMBASE+0x8000=0x38000 to find out what is written there when. 51 52 0x38000: cs mov bp,WORD PTR [rsi] 53 0x38004: adc BYTE PTR [rax+0xed8166],al 54 0x3800a: add BYTE PTR [rbx],al 55 0x3800c: add BYTE PTR [rsi-0x1],ah 56 0x3800f: in eax,0x8a 57 0x38011: int1 58 0x38012: cld 59 0x38013: (bad) 60 61If we copy out the memory and dissassemble in 16-bit realmode: 62 63 0000:0000 flags: 64 0000:0000 2e668b2e1080 mov ebp, dword cs:[0x8010] 65 0000:0006 6681ed00000300 sub ebp, 0x30000 66 0000:000d 66ffe5 jmp ebp 67 0000:0010 8af1 mov dh, cl 68 0000:0012 fc cld 69 0000:0013 07 pop es 70 0000:0014 ff invalid 71 72We can break in the SMI handler, but qemu wont show us the actual memory 73or SMM register contents.. so we have a bit more reversing to do. 74 75If we look closely at what is done before qemu reports the SMI, we 76can see that there is a write to 0xfee00300. 77 78Local apic registers are memory-mapped in the physical range 0xFEE00xxx! 79Using the intel programming reference, we find that register 0x300 is the 80interrupt command register. We are wirting 0x00000000, which corresponds to a 81vector number of 0, normal delivery mode and physical destination. 82 83The code is registering a local apic which triggers an SMI and finally 84the "SMM: enter" we saw on the commandline! 85 86To figure out where the SMI handler jumps to we need to inspect the SMM 87state save are (typically at SMBASE+0x3fc00 for 64-bit). Oddly though, 88the layout from the intel 64 architecture reference manual does not 89match the save state layout for 64 or 32-bit. 90 91Section 32.5.1 describes the "Initial SMM Execution Environment", 92where we can see that we start in real-address mode at 0x3000*16+0x8000=0x38000 (cs:rip). 93 94The smm code can be then read as loading the address stored in 0x38010 and jumping 95to it = 0x3000*16+0x7f9f18a = 0x7fcf18a. 96 97In Section 2.1 we see an overview of the architecture functionality, as well 98as the purpos of the global descriptor table. 99 100THe only reason *0x38000 works is that the interrupt is on the read 101before the actual entering of SMI handler.. if you trigger the TRAP 102inside SMM mode qemu freezes up with "check_exception" 103 104PML4T (Page-Map Level-4 Table) -> PDPT (Page-Directory Pointer Table) 105 -> PDT (Page-Directory Table) -> PT (Page Table) -> 4kB 106 107512 * 512 * 512 * 4kB = 256 TB 108 109 110To solve the challenge in different execution modes, we need 111to generate assembly specific to that mode for each level. 112We can use the GCC `.code16gcc` and `.code32` directives for this. 113 114When GS and FS segments are used in 64-bit mode they select from 115tables at the GSBASE and FSBASE addresses stored in model-specific registers. 116 117Far jump to pretend to be in the correct code segment sorta works, but 118only until the SMI triggers while you are still in long mode. 119 120 121Solutions: 122 1231. qemu maps the bios flash to 0x100000000 - len(flash), 124 if we can figure out where the smi handler is stored (and encoded) 125 in the flash we can dump the remote flash and unpack it locally 126 (creds to `emxl`) 1272. to get around the smi handler changing the execution mode of our 128 code segment, we can use a trick: we just switch back and forth 129 to a different code segment. There are different code segments 130 with the same base address (0) but different permissions.. 131 132Lessons learned: 133- how segmented memory maps work on ia32 134- can use ".intel_syntax" and ".att_syntax" in inline assembly 135- can use %= in assembler is replaced by a number unique to that instruction 136 137 138