Show Notes

154 - SoCs with Holes, Crow HTTP Bugs, and Bypassing Intel CET

Eight vulnerabilities that were discovered by nccgroup in the UNISOC bootROM. One was in the second-stage recovery mode bootloader (FDL1), five were in the bootROM recovery mode, and two were in U-Boot. Exploiting the first vuln allowed them to RE the bootloader and find the other seven. Basically all the vulnerabilities are trivial and are due to lacking validation.

1. Buffer Overflow in FDL1 USB Recovery Mode in usb_get_packet() FDL1 has a custom USB protocol in their recovery mode, which is handled via usb_get_packet(). The bug is it simply doesn’t validate the payload length and copies the packet into a static buffer, which is also in executable memory. By achieving code execution, they were able to dump the bootROM on devices that had secure boot fuses blown.

2. Unchecked write in cmd_start() The cmd_start() command handler would accept a user-provided target address for receiving data, and performed no validation on it. By simply setting an arbitrary address and using cmd_recv_data(), arbitrary write could be achieved.

3. Out-of-bounds access due to unchecked index in USB command dispatcher (recovery_comms()) The command dispatcher would take an index for the command to execute, and would perform a lookup on the dispatcher function table for the handler. This index was not validated.

4. Buffer overflow in receive_payload_usb() The USB data transfer function would not validate payload length before copying the payload into a static buffer. The same issue also exists in receive_payload_uart().

5. Out-of-bounds read in handle_setup_request() When accepting USB setup requests, the request wLength wasn’t validated, allowing out-of-bounds read and information disclosure to occur.

6. Uninitialized read via recovery_comms()+cmd_start() recovery_comms() can receive a payload, but the length and payload are passed along to the handler. cmd_start() doesn’t validate the payload is at least 12 bytes to occupy the header. cmd_recv_data_usb() copies data of sz bytes without validating the amount of data actually received, so uninitialized memory can be read.

7. Lack of proper RSA verification of secondary bootloader Getting into U-boot, the bootROM needs to validate the signature of the second-stage bootloader. Their RSA verification supports two certificate types, contentcert (0) and keycert (1). Typically a keycert is embedded in the second-stage bootloader, and it’s validated against keys in fuses. But if you switch it to a contentcert type, no validation is performed. Unfortunately, this on it’s own wasn’t exploitable though because of other factors, notably that the cert type 0 path doesn’t initialize a valid hash key either.

8. Buffer overflow in RSA do_rsa_powmod() The RSA implementation itself had a vulnerability in the function responsible for modular exponentiation. When performing a byte swap, the length of the key size is not checked, and so by having a key greater than 2048 bits in size, you could overflow global buffers g_n and g_sig. Since these precede the stack in memory, this can be used to smash the stack and get persistent code execution.

A use-after-free vulnerability in the Crow HTTP Framework owing to the input reader being agnostic to HTTP Pipelining (sending more than one HTTP request without waiting for a response on the same connection) and asynchronous workers tracking state expecting one request per connection.

One of the specific situations called out is regarding the Connection::check_destroy() method which will delete the connection object after both the is_reading and is_writing flags are cleared. These flags are being modified without regard to how many requests have come through. So for example two requests come in, queueing up two write actions. After the first write action the is_writting flag will be cleared and Connection::check_destroy() is called despite the fact that there is still the second write in the queue.

The author also calls out a worrying pattern of potentially blatent use-after-frees where check_destroy is called, and then the application continues to operate on the connection regardless of the response.

In responding to a static file request, the Crow HTTP framework would allocate a 16kb buffer and read the target file into it. It would then send the whole buffer to the client regardless of how many bytes were actually read.

A logic bug in the Linux kernel’s route4_change() function for route filters that lead to use-after-free (UAF). The problem has to do with how filters are added, particularly when a filter already existed on a handle and needs to be copied over to a new filter. At the top of the function, a filter is allocated, data from the previous filter is copied over if there was a previous filter on that handle, and the filter is added to a hash table. Later on, the old filter will be removed from the hash table, but only if fold->handle is non-zero. The bug here is that it’s possible to create filters with a valid handle of 0. When execution reaches the end of the function, they unconditionally free the old filter. In this situation, the old filter is left in the hash table but has been free’d, ultimately resulting in a dangling reference being left in the table.

The exploit strategy was to use this UAF to get a double free in the kmalloc-192 cache. They then used this to corrupt task credentials, similar to DirtyCred.

Starts off with a straight forward memory disclosure in FreeBSD due to not zeroing the context_t before it was partially filled and sent to copyout. The rest of the post is about walking through performing data-flow and control-flow analysis with Binary Ninja.