Vulnerabilities are like good ideas - you’re rarely the first one dealing with it. Some vulnerabilities are almost classic, so I’ll proudly present: 7 old but surprisingly useful bugs that might also affect YOUR device. (With “you” either being the designer or attacker.)
Just to be clear: none of these exploits are rocket science. This is kind of the “low tech” hacking approach - no fancy oscilloscopes required, no DPA involved, no FIB to rent. There’s not even code to disassemble!
1. ext2 Symlink Directory Traversal
The first vulnerability is a classic oversight. It applies to many devices that are accepting a mass-storage device (for example USB or micro-SD) and are somehow exposing the contents of this to the outside world. This includes WLAN/3G access points with a “file share” ability, NAS and a lot of special purpose devices. Slightly linux-centric (but I’m sure it applies to many other operating systems as well), these devices usually try to support multiple filesystems by using the equivalent of the “mount -t auto”, automatically trying all filesystems from /proc/filesystems when mounting, until one filesystem handler succeeds in mounting.
While supporting multiple filesystems is normally meant to support FAT, NTFS and maybe HFS+, most kernels also have ext2/ext3 support compiled in; sometimes the rootfs of these devices also uses ext2. This means that if you attach an ext2 formatted storage device, it will be mounted as well. If the device exposes access to this filesystem, it gives you a lot of toys to play with: symlinks, device nodes and all the fancy features.
For example, if the device allows you to browse the attached mass storage, a simple symlink to / might allow you to browse the rootfs:
lrwxrwxrwx 1 root root 13 Oct 25 09:59 rootfs -> /
Certainly, you’re bound to the permissions of the process that does the access to the files. However, in embedded devices, designers rarely use user permissions (and instead only expose restricted functionality in the application), and since they probably never thought about ext2, restricting permissions and jailing (chroot etc.) wasn’t deemed useful. After all, vfat partitions (for example) can’t contain symlinks or useful multi-user permission bits (with HFS+ it’s a different story, of course).
On this particular device, a configuration file was copied on bootup from an insecure attached mass-storage device to an internal (but still insecure) storage. This allows to place a symlink on the mass-storage device pointing to any regular file in the root partition. It didn’t allow browsing in the root file system, but could be used to retrieve regular files (so no character devices) with a known filename.
2. Non-Appropriate Crypto Modes give you a Decryption Oracle
AES (or any other modern cryptographic standard) is considered “secure”. This fact is easy to remember, given that AES is still the recommendation for newly-designed crypto schemes. However, it’s much harder to understand what “secure” actually means. For block ciphers, one important property is that there is, given (even a large number of) ciphertext/plaintext pairs, no more efficient way to recover the key used in the encryption than to do a full brute-force attack over the key space. (There are a few other scenarios that block ciphers need to be “secure” against, but this is the most important one).
However, this is not the only property that’s required when encrypting data. Each usage model has a special requirements, and that’s why there are several so-called “modes” of operation. The simplest mode is ECB, replacing each plaintext block with an encrypted version of the block. Explaining why ECB is not sufficient in most cases is easy. That leads people to think that CBC would fix all their problems. Hint: It doesn’t.
One thing that apparently people sometimes expect CBC to provide, which it actually doesn’t provide, is integrity. Decryption without integrity means that you can replace encrypted content with other encrypted content, and it will produce the corresponding plaintext (assuming you can access parts of the decrypted data). In CBC, the chaining aspect will screw up the first block only, and even that can be manually fixed since the ciphertext is known. If you use AES-CBC for disk encryption, you’ll likely be screwed. The missing integrity aspect is one reason why proper disk encryption doesn’t use plain CBC.
Practically speaking, if you use plain CBC (maybe with a per-sector IV which doesn’t provide any security advantage), and the attacker can get the contents of a random file from your disk (for example using the symlink hole), all the attacker has to guess is the position of that file in the encrypted disk image. He can then inject arbitrary sectors into this file, dump the file, and recover plaintext. This might sound painful - and it is - but if a large-enough file can be found (that isn’t required to make the system run up to the point where you can read the file), you can extract quite a lot of data per run.
There are cryptographic modes (like XTS) which fix these problems. If you don’t understand the issue, go read why they’ve been invented.
3. Configuration String Sanitizing
Sigh. A general rule-of-thumb: If a parser acts on user-supplied data, and that parser isn’t exactly made for what the input file looks like (which would hopefully imply that the creator thought about security side-effects of any invalid user-supplied input string), don’t try to “sanitize” or filter as a frontend. You’ll miss something. This applies for sanitizing file names (think of the popular Unicode directory traversal bugs), SQL arguments and especially configuration files.
I’ve seen devices which allow putting an un-encrypted, un-authenticated configuration file on a user-accessible storage. The configuration file was an ASCII file with a number of
lines. This was then “sanitized” by removing all keys that are not in a whitelist (with some bash string parsing foo), and decorated into the following format:
The resulting file is then sourced (“.”) as part of the init scripts. Needless to say how this can be exploited, leading to arbitrary code execution, for example a conveniently powerful busybox console on the serial port…
Lesson learned: Don’t sanitize and then let another parser do the job. Parse and sanitize at the same time.
This (linux-centric) one is not really a security hole per se. However, it sometimes allows attackers to “reverse” the boot chain - for example to retrieve (parts of) the bootloader or initrd/initramfs used to boot the system, by creating a memory snapshot right after booting. This technique for example was used to retrieve the initrd of the ReadyNAS (which, back then, uncovered a nice little backdoor password).
The device node /dev/mem maps the physical address view of the kernel. Extracting contents can be as simple as using “dd” (with proper “skip” and “bs” arguments). Alternatively, using the mmap system call, any memory (including MMIO) can be mapped into a user process. If /dev/mem is opened with O_SYNC, the mapping is cache-inhibited (on most architectures, everything above the memory size is cache-inhibited, too), which allows you to talk to IO devices.
Reading (writing, and mmap’ing) /dev/mem requires proper access permissions and CAP_RAWIO.
And really, if there was no /dev/mem, you could just load a kernel module providing the same. Root access on an embedded machine equals device pwnage, unless great effort was taken (hint: which is usually not the case).
Again, the existance of /dev/mem isn’t the security hole - the security hole is allowing the attacker to operate on it. It just makes things much more convenient.
5. Write-Only Registers with Partial Override allow Key Recovery
This one is a classic as well. Some hardware implements cryptographic functions (for example AES decryption) in hardware. Some hardware designers cleverly make the registers that are used to configure the key used in the decryption write only - such as that they can be written with the key, and the key cannot be read back. The key can then only be used in actual AES operations.
This sounds clever at first, solving real-world problems - you can’t just dump the key out of memory. However, as usual, things are not that simple. Almost no CPUs or busses in embedded systems these days can handle atomic writes to key-sized (128 or 256 bit) registers. That means that a key register won’t be a single register, but consists of multiple registers that need to be written sequentially.
As long as they are written strictly sequential and completely, there is no problem. However, as soon as partial overwrites are allowed, the scheme breaks. For example, if the CPU can do 32-bit writes, and a 128-bit AES key consists of 4 registers with 32-bit each, this allows the attacker to modify the key. By then using the modified key, ciphertext/plaintext pairs can be generated that have known key bits, allowing a brute-force search on the remaining bits.
Practically, there are two possible ways to exploit this. Assume it’s a decrypt oracle (i.e. the internal key can be used to decrypt arbitrary ciphertext; it works similarly for non-arbitrary ciphertext or encrypt oracles). Now, either
- Take the reference plaintext by decrypting a constant, for example all-zero. Overwrite as few key material as possible. If the registers honor write masks, you might be able to overwrite as few as 8-bit of the key. If it doesn’t, it might be as much as 32-bit. In any case, simply loop through all of the 28 (or 232) combinations, overwrite the key material partially, take a plaintext with the current key, and compare it with the reference. If it matches the reference, then you found a few bits of the key. Rinse, repeat with the rest.
- Overwrite as much key material as possible while still leaving a little bit of key material. Take a plaintext for a constant ciphertext. You can then (offline) run a brute-force attack on this, which is feasible because you reduced the entropy of the key before. Reboot (to restore the full key), Rinse, Repeat
The first method is useful if the write granularity is fine (for example 8-bit) - it will then require 16 * 28=4096 (worst case) decryptions on the device. The second method is useful if the write granularity is larger (for example 32-bit), because the first approach would require 4 * 232=17179869184 (worst case) decryptions on the device, which can take quite a long time. These decryptions can be done offline and parallelized. A 32-bit brute force attack on a modern PC doesn’t take more than a few minutes, so after extracting the 4 possible plaintexts (keeping the first, second, third, fourth key word) the key can be recovered on a PC.
The disadvantage of the second method is that you need to restore the key material (for example by rebooting) before attacking the next key subset.
A similar method can be used (destructively) to dump OTP fuses that store a key, assuming that no redundant encoding is used (i.e. that you can burn more OTP bits to set bits in the key).
- Start with the first bit. Take a plaintext. Burn the first fuse, re-sense (if required, to update the key register), take another plaintext.
- If the plaintext changed, you know that the bit wasn’t set before. If the plaintext did not change, the bit was already set. Note that down.
- Repeat with the next bit.
A somewhat inconvenient method, since it’ll destroy the OTP key (so you better be sure that you can still execute code even after the key has been partially destroyed - otherwise you need up to $keysize devices - which might still be a useful attack).
Some devices will not allow you to overwrite arbitrary parts of the key - some for example start the key schedule only after writing the last part of the key. However, unless they double-buffer or enforce sequential and complete access (which both involves additional state), this is vulnerable and can be exploited.
6. Relying on Success
Back in the d-Box 2 days (German link, sorry), the boot sequence would by default load into a GUI that didn’t provide any remote access. However, if the boot sequence failed, a remote shell daemon would open. This fact could be abused by short cutting a flash data line in the middle of the boot, causing the GUI binary to fail, eventually giving RSH access to the system. Then, a new, modified rootfs could be mounted (only the kernel was protected with a signature, the filesystem or any files within wasn’t), leading to arbitrary code execution.
The mistake they did was to assume that in a secure environment (arbitrary flash editing aside) everything would boot as expected. While it might be hard to change data meaningful, it’s often very easy to change data in a non-meaningful way - i.e. make something fail.
In a particular case, a kernel image was (securely) loaded into memory, and then launched. The problem is that there is no error checking. If the binary doesn’t load for some reason, the bootloader still tries to boot from that RAM location, even if it contains garbage. Usually, the bootloader would just abort with a CRC error.
To turn this denial-of-service attack into something useful, you can just pre-populate the memory (assuming you have code execution), reset the device and somehow make the load fail. The memory’s content will survive a few seconds without refresh, so it’ll execute your good code.
Cross-compiling sometimes leaks information from the host system into the generated binaries (or files). One example is the kernel’s LINUX_COMPILE_BY (that’ll end up in the linux_banner, which is output when the kernel starts and can also be read from /proc/version), but there are more such examples. If you don’t want people to know who you are (hey, there should be no reason for that, right?), be aware of this fact and better grep all your binaries (including your rootfs) to make sure that nothing leaked.