Enarx provides a CPU-architecture independent run-time TEE based on WebAssembly. It provides isolation to the application from the host and vice versa.
On May 16, 2022 the Cryptle Hack Challenge was launched in an effort to discover vulnerabilities in Enarx.
In order support multiple CPU-architectures Enarx has different shims for different platforms. The shims each implement a small microkernel that loads a CPU-architecture independent executable. On May 23, 2022 I reported a vulnerability in the Intel SGX shim. This repo contains an exploit against that vulnerability that can execute arbitrary shellcode inside the enclave.
The Intel SGX shim communicates with the host through a block of host memory called the "sallyport block". When the host enters the SGX enclave it passes a pointer to the sallyport block in the rdi register (see here). When the shim wants to execute syscalls or other enarx specifc commands, it writes the parameters to the sallyport block and passes control back to the host.
The bug is that the shim never checks that the pointer to the sallyport block passed in by the host actually points to host memory and not enclave memory. By passing in a pointer to the enclave's memory the host can trick the shim into corrupting it's own memory.
A few ground rules about exploiting cryptle and SGX applications in general:
- The goal of the Cryptle Challenge is to hack an instance of cryptle running on the same server. The attacker is assumed to have root privileges.
- The attacker knows where the enclave is mapped by inspecting
/proc/<pid>/maps. - The host application runs in the same virtual memory space as the enclave (though the CPU prevents it from accessing enclave memory), so the attacker can easily map additional memory accessible to the enclave to help exploitation.
- Even though the host application and enclave share the same virtual memory space, the enclave can only execute code in enclave memory. Trying to execute code outside enclave memory causes a General Protection Fault.
In order to manipulate the passed in sallyport block pointer, I use ptrace to read and write to the host registers and memory and intercept host syscalls. I also used ptrace to set breakpoints just before and after the transition to the enclave to figure out the right timing for manipulating the pointer.
The first challenge is that I'd like to precisely control the syscall parameters Enarx writes when I cause the memory corruption:
Enarx uses TLS right out of the box and the ServerHello packet conveniently contains a field called legacy_session_id_echo which contains the exact same data sent by the client in the legacy_session_id of the its ClientHello packet. This is a variable length field that can be up to 32 bytes long. Thus by starting a TLS handshake with a specially crafted legacy_session_id I can trick the shim into writing up to 32 bytes of our choosing.
I used this to write a small ropchain onto the shim's stack. Unfortunately because of the size limit of legacy_session_id I can only write up to 32 bytes and that's not a lot of space for a ropchain, so I chose pivot to a new stack. When I attach to the process I use ptrace to allocate some regular host memory and write the rest of the ropchain to it. The ropchain in the legacy_session_id then pivots to a new stack by first using a gadget that pops rbp from the stack and then another gadget that moves rbp into rsp. Intel SGX only disallows executing code in non-enclave memory but doesn't disallow executing on a stack in non-enclave memory, so this works perfectly.
Ropchains are inherently limited by the gadgets already in memory, so I'd prefer to execute my own shellcode.
At the time of writing, Enarx doesn't allow mapping pages as both writable and executable, so I have to change the protection flags somehow. Usually SGX enclaves change the protection flags by issuing an emodpe enclu instruction in coordination with the host, but I couldn't find any good rop gadget with an enclu instruction.
Instead I relied on the fact the shim is still mostly functional at this point and used a regular syscall instruction that gets translated by the shim into the corresponding enclu instructions. The only hiccup there was that the shim assumes that it's running on a stack in enclave memory. This is relevant because some syscalls pass references to the stack to the enclu instruction with generally wants to only receive pointer to enclave memory. To get around that I used the ropchain I already have to write another ropchain into enclave memory, pivot to it, execute the syscall and then pivot back.
Putting this all together, I used the ropchain to write the shellcode to some arbitrary location in enclave memory, change the protection flags by using a syscall gadget and finally jump to the shellcode. I also pivot the stack back to the enclave, just in case the shellcode wants to execute syscalls.
Enclave memory is assumed to be confidential, so I figured that dumping the enclave's entire memory would be a good proof of concept.
I wrote some shellcode that repeatedly reads an address from stdin and then writes 4096 bytes from that address to stdout. I used ptrace to intercept the corresponding read and write syscalls on the host.
First start cryptle in Enarx:
$ ./enarx --wasmcfgfile Enarx.toml cryptle.wasmWait a minute until Enarx is all started up, then go have some fun and play some cryptle on https://<host>:8443! I recommend playing in multiplayer mode because then cryptle keeps all played words in memory, so that we can leak them later!
Next start the exploit:
$ cargo run --release `pidof enarx`
Compiling enarx-exploit v0.1.0 (/home/freax13/code/enarx-exploit)
Finished release [optimized] target(s) in 5.72s
Running `target/release/enarx-exploit 355957`
2022-06-12T13:49:26.876800Z INFO enarx_exploit::tracee: Attaching to process pid=355957
2022-06-12T13:49:26.915064Z INFO enarx_exploit: Located executable path=/home/freax13/code/enarx-exploit/enarx
2022-06-12T13:49:26.920074Z INFO enarx_exploit: Located enarx base address enarx_base_address=139665326891008
2022-06-12T13:49:26.924445Z INFO enarx_exploit: Located sgx base address sgx_base_address=139659451564032
2022-06-12T13:49:26.924725Z INFO enarx_exploit: Allocate fake stack fake_stack=139665320574976
2022-06-12T13:49:26.925714Z INFO enarx_exploit: Creating breakpoint for `Thread::enter` start
2022-06-12T13:49:26.925754Z INFO enarx_exploit: Creating breakpoint for `Thread::enter` end
2022-06-12T13:49:26.926831Z INFO enarx_exploit: Syscall nr=288
2022-06-12T13:49:26.927414Z INFO enarx_exploit: Syscall nr=16
2022-06-12T13:49:26.927876Z INFO enarx_exploit: Syscall nr=45
2022-06-12T13:49:26.928658Z INFO enarx_exploit: Syscall nr=228
2022-06-12T13:49:26.930126Z INFO enarx_exploit: Attacking!
2022-06-12T13:49:26.938665Z INFO enarx_exploit: Dumped memory at 00007f0500000000
2022-06-12T13:49:26.939605Z INFO enarx_exploit: Dumped memory at 00007f0500001000
2022-06-12T13:49:26.940316Z INFO enarx_exploit: Dumped memory at 00007f0500002000
2022-06-12T13:49:26.940976Z INFO enarx_exploit: Dumped memory at 00007f0500003000
...
2022-06-12T13:53:20.756343Z INFO enarx_exploit: Dumped memory at 00007f05fde94000
2022-06-12T13:53:20.758545Z INFO enarx_exploit: Dumped memory at 00007f05fffff000
2022-06-12T13:53:20.758619Z INFO enarx_exploit::tracee: Detaching from process pid=355957The dumped memory will be written to dump/<addr>.
One cheap way to run SGX applications is to get one of the Intel NUCs that have SGX support. Unfortunately, as of time of writing, Enarx does work on the NUCs right out of the box because it requires some CPU features that the CPUs in the NUCs don't support. For that reason I patched Enarx to not require those CPU features. The patched binary can be found here. I also created another patch that enables the debug mode for the enclave. That patched binary can be found here.
The shim should check the pointer passed in by the host to make sure that the sallyport block will not overlap with the enclave's memory.
The bug was patched by @jarkkojs: enarx/enarx#1918.