cscg24-haunted

CSCG 2024 Challenge 'Haunted'
git clone https://git.sinitax.com/sinitax/cscg24-haunted
Log | Files | Refs | sfeed.txt

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