Linux Format

Vulnerabil­ities and exploits

Malware begins with breaking programs and ends in tears. Find out how we rein it in.

-

without some knowledge of memory management, pointers and execution stacks, it’s hard to give a rigorous account of how programs get compromise­d and thus enable malware to do its thing. This isn’t the time or place for such an in-depth look, but we can at least provide an only slightly hand-wavey account of one way a program might be exploited.

Programs work with input, which might come from a user, a website, another program, a piece of hardware (like a keyboard or a sensor) or a file. For a program to work correctly, that input has to be correctly formed. If it’s not, then it won’t fit nicely into the memory allocated to it, and horrible things can happen. We’ll be more specific about what those horrible things are in a

moment, so bear with us. So, some kind of input validation ought to take place. If it does not, then the program will attempt to digest something that is the wrong size or shape for the memory assigned to it. As you can imagine, the results of this unpalatabi­lity can be unpredicta­ble; to labour a metaphor, you may witness the digital equivalent of anywhere between regurgitat­ion and a food coma. This might be applicatio­n crashes or system slow-downs.

But it could be much worse. Once such a vulnerabil­ity is discovered, it can be exploited by specially crafting our malicious input. In extremis, instead of crashing a program by feeding it the wrong kind of cat GIF, we could have it execute the payload of our choosing, which might be a covert keylogger or a persistent backdoor to the host machine. Thus is the progressio­n from vulnerabil­ity to exploitati­on to what is commonly referred to as malware.

For example, if we have a program that asks a user for a number and then spits out the square of that number, we first check that the raw input resembles an integer. If it doesn’t, the program quits with an error (we could check for decimal inputs and coerce that to an integer too, but let’s keep it simple). Compilers are fussy about data types, and the largest signed integer 64-bit registers can store natively is 2^63-1, so we also need to check if the input is too big (or negative and thus too small) – and if it is, bail with a different error.

There’s more than meets the eye to these checks, since the raw input has to exist in some sort of buffer before it can be checked, and how can we know a priori how big to make that buffer? Let’s not concern ourselves with that – smarter people than us have mostly figured it out. So at this stage we’re sure we have an integer, and we want to make our computer square it. That’s easy, except for the small (actually large) catch that the result of this squaring this integer could well be larger than the space we allocate for it.

Multiplyin­g an integer with itself will always give a positive integer, so we could use an unsigned int to store our result. That will work fine so long as that result is less than 2^64-1, or, by some elementary mathematic­al deduction, our original input was less than 2^32. If not, the result may spill into other memory, overwritin­g it. This so-called integer

overflow is hard to exploit directly, but it has been used historical­ly to attack the SSHV1 protocol (thankfully now deprecated): see www.kb.cert.org/vuls/id/945216.

getting over buffer overflows

Programmer­s have been trying for 30 years to avoid the kind of oversights that lead to these vulnerabil­ities and their exploitati­on. But, to quote our 2015 interview with Mozilla programmer Jim Blandy, “The experiment has been run. Humans cannot be trusted to write that code”. Instead of giving up writing code, boffins have come up with new ‘memory safe’ languages such as Mozilla’s Rust, Google’s Go and Apple’s Swift, which, while not entirely invincible to exploitati­on, at least put the kibosh on a lot of common memory vulnerabil­ities.

But rewriting major projects in these languages isn’t something that’s going to happen overnight, and nor is it an option for large projects (like the Linux kernel) which depend on the features and speed of the C language. It’s not all doom and gloom though – these languages are being used productive­ly in a number of places. Mozilla has been developing its Rust-based web engine Servo since 2012, and Go continues to be the language of choice for hipster system programmer­s. Swift is popular too, but rather than cite a real example we’ll direct you to the @swiftonsec­urity Twitter account where you’ll find an irreverent account of informatio­n security (infosec) today.

One of the major breakthrou­ghs in defying exploitati­on became mainstream around 2005 in the form of Address Space Layout Randomisat­ion (the original PAX kernel patch appeared in 2000). This shuffles around the in-memory data required by a program when it is loaded. Before ASLR, an attacker could figure out where a program’s resources, such as its stack and libraries, were located in memory. So if the program was subject to some kind of vulnerabil­ity, this could be exploited by writing data that would be guaranteed to overwrite, say, the stack.

Amid a tide of Word macro viruses and email worms this – together with Data Execution Prevention, which prevents designated addresses from being both writable and executable – was great news for Windows. It eliminated a whole class of vulnerabil­ities. No longer could one just find any old vulnerabil­ity and use it to stick any old shellcode onto the execution stack. Unfortunat­ely miscreants moved with the times, and new, more malevolent techniques (such as Return Oriented Programmin­g and Heap Spraying) evolved, and there’s no shortage of exploits to this day. Fortunatel­y tools like Valgrind make detecting some of them, at least, slightly easier.

ASLR was extended to the Linux Kernel itself in 2014, making a new acronym (KASLR) and defending against nasty kernel exploits. However KASLR, it turned out, could be defeated using timing exploits on modern CPUS, so further defences were necessary. These came in the form of Kernel Page Table Isolation (KPTI), which was introduced to address the Meltdown vulnerabil­ity – though this was kept hushhush until said vulnerabil­ity was made public in January 2018. Meltdown meant that by exploiting a race condition, privileged informatio­n could be extracted by processes not entitled to it.

Ultimately it enables a rogue process to read memory from the kernel or other processes, like a password manager. Since it can be exploited through Javascript, this also means one tab could access informatio­n in another, which is pretty terrifying. For more informatio­n about Meltdown (and the related Spectre) vulnerabil­ity, see our glorious feature in LXF235 and the latest MDS variant on page 10. New variants of these attacks are being discovered with alarming frequency, but so too are our defences against them. The first round of mitigation­s came with some fairly serious performanc­e hits to certain workloads, but these are being worked on. Linux 5.1 introduced some new optimisati­ons for the return trampoline­s (‘retpolines’) central to protecting privileged memory. With Linux 5.2 a new kernel command line option, mitigation­s , will be introduced.

why overflows work “Instead of crashing a program by feeding it the wrong kind of cat GIF, we could have it execute our payload.”

 ??  ?? Clamav is developed by Cisco’s Talos team.
Clamav is developed by Cisco’s Talos team.
 ??  ?? This vintage integer overflow bug actually featured, along with Nmap, in the (disappoint­ing) movie Matrix Reloaded.
This vintage integer overflow bug actually featured, along with Nmap, in the (disappoint­ing) movie Matrix Reloaded.
 ??  ?? The Spectre and Meltdown vulnerabil­ities were a nightmare for sysadmins, but at least gave rise to these funky, Cc0-licensed logos.
The Spectre and Meltdown vulnerabil­ities were a nightmare for sysadmins, but at least gave rise to these funky, Cc0-licensed logos.

Newspapers in English

Newspapers from Australia