# CryptOracle v2 `CryptOracle v2` is the "Hardened" version of the HSM simulator. The direct memory read vulnerability from v1 has been fixed, but a new "Cryptographic Engine" has been added. The goal is the same: dump the secret key from the secure memory region at `0x10000`. ## Information Gathering After connecting to the server, we see a familiar interface with new commands like `enc` and `dec`. ``` CryptOracle v2.0 (Hardened!) Setting up memory... 0x10000 - 0x11000 : Secure Memory (Keys/ROM) 0x20000 - 0x28000 : User Memory Type 'help' for commands. ``` ## Reverse Engineering We open the binary in Ghidra and check the functions that have changed. ### 1. The Patch (`do_read`) The `do_read` function now includes an explicit check that blocks any attempt to read from addresses below the user memory region. ```c void do_read(uint32_t offset,uint32_t len) { uint8_t *data; // This check prevents us from reading 0x10000 directly. if (offset < 0x20000) { puts("ERR_ACCESS_VIOLATION"); } else { // ... (rest of the function) } return; } ``` The simple `rm 0x10000 32` from v1 will no longer work. ### 2. The Cryptographic Engine (`do_cipher`) With the direct read vulnerability from v1 patched, we must now investigate other commands as a potential attack surface. The `enc` and `dec` commands, which were present before, now become our primary focus. They are handled by the `do_cipher` function. It allows encrypting or decrypting data from a source address to a destination address using a key stored in a secure "slot". ```c void do_cipher(char mode, uint32_t src_off, uint32_t len, int slot, uint32_t dst_off) { // ... // Get pointer to source buffer (can be anywhere) src_ptr = get_ptr(src_off, len, 0); // Get pointer to destination buffer (must be writable) dst_ptr = get_ptr(dst_off, len, 1); // Get pointer to the key from a secure slot // Address is calculated as 0x10000 + slot * 32 key_ptr = get_ptr((slot + 0x800) * 0x20, 0x20, 0); if (/* all pointers are valid */) { // ... performs AES encryption/decryption block by block ... } } ``` The key insight is in how the pointers are validated, which all happens inside the `get_ptr` function. ### 3. Vulnerability Analysis (`get_ptr`) Here is the decompiled `get_ptr` function from Ghidra. It takes an offset, a length, and a flag `is_write` that is `1` for write operations and `0` for read operations. ```c uint8_t * get_ptr(uint32_t offset,uint32_t len,int is_write) { uint8_t *puVar1; if (len + offset < offset) { puVar1 = (uint8_t *)0x0; } else if ((offset < 0x10000) || (0x11000 < len + offset)) { if ((offset < 0x20000) || (0x28000 < len + offset)) { puVar1 = (uint8_t *)0x0; } else { puVar1 = (uint8_t *)(ulong)offset; } } else if ((is_write == 0) || (0x7ff < offset - 0x10000)) { puVar1 = (uint8_t *)(ulong)offset; } else { puVar1 = (uint8_t *)0x0; } return puVar1; } ``` **The Vulnerability:** The logic in the final `else if` block is flawed. Let's trace it for a read operation (`is_write == 0`) on the secure flag region (`offset = 0x10000`). * The expression is `(is_write == 0) || (0x7ff < offset - 0x10000)`. * Since `is_write` is `0`, the first part of the OR `(0 == 0)` is **true**. * The entire expression becomes `true`, and access is granted, returning a valid pointer. The check that should prevent writing to the first 2KB of secure memory (`0x7ff < offset - 0x10000`) is completely bypassed for any read operation. The `do_cipher` function abuses this by requesting a read (`is_write=0`) on the secure flag, which `get_ptr` allows. This creates a **Confused Deputy** scenario, where we use the oracle's legitimate read permissions to exfiltrate data. ## Solution The attack is a two-step process: 1. **Encrypt**: Use the `enc` command to instruct the oracle to read from the secure flag region (`0x10000`) and write the encrypted result (ciphertext) into user-accessible memory (`0x20000`). 2. **Decrypt**: Use the `dec` command to instruct the oracle to read the ciphertext from user memory (`0x20000`) and write the decrypted result (plaintext) back into a different location in user memory (`0x20100`). 3. **Read**: Use the now-fixed `rm` command to read the plaintext flag from `0x20100`. ### Execution ```bash # Step 1: Encrypt the flag from secure memory to user memory > enc 0x10000 32 30 0x20000 OK # Step 2: Decrypt the ciphertext from user memory to another user memory location > dec 0x20000 32 30 0x20100 OK # Step 3: Read the plaintext flag from user memory > rm 0x20100 32 00020100: 7b 66 6c 61 67 3a 20 73 65 6c 66 5f 65 6e 63 72 {flag: self_encr 00020110: 79 70 74 69 6f 6e 5f 69 73 5f 6e 69 63 65 21 7d yption_is_nice!} ``` The flag is revealed: `{flag: self_encryption_is_nice!}`