Anti-debug with VirtualAlloc’s write watch

A lesser-known feature of the Windows memory manager is that it can maintain write watches on allocations for debugging and profiling purposes. Passing the MEM_WRITE_WATCH flag to VirtualAlloc “causes the system to track pages that are written to in the allocated region”. The GetWriteWatch and ResetWriteWatch APIs can be used to manage the watch counter. This can be (ab)used to catch out debuggers and hooks that modify memory outside the expected pattern.

There are four primary ways to exploit this feature.

The first is a simple buffer manipulation check. Allocate a buffer with write watching enabled, write to it once, get the write count, and see if it’s greater than 1.

The second is an API buffer manipulation check. Allocate a buffer with write watching enabled, pass it as a parameter to an API that expects a buffer, but pass invalid values to other parameters. If an API hook doesn’t check parameters properly, or manipulates parameters, it may write to the buffer. Check the number of writes to the buffer after the call, and if it’s nonzero then there’s a hook in place. Any API will do as long as it writes to some memory. A particularly good trick is to use an API where there’s some kind of count value passed as a reference – in the real API the value will likely not be set, thus producing no memory writes, but in a hook there’s a bigger likelihood that they’ll set some placeholder value regardless.

Third, we can use the buffer to store the result of some check we care about, e.g. IsDebuggerPresent. If the write count is one and the value in the buffer is FALSE then we can assume that there’s no debugger attached and nobody tampered with the result of the call (or skipped the call).

Finally, we can allocate some memory with RWX protection and write watching enabled, copy some anti-debug check there, call ResetWriteWatch to ensure the write counter is zeroed, execute our payload, then check the write count.

Obviously in all cases these checks themselves can be skipped over, but it’s not a well known trick and may be missed by novice reverse engineers.

I’ve contributed these tricks to al-khaser, a tool for testing VMs, debuggers, sandboxes, AV, etc. against many malware-like defences.


The anti-virus age is over.

As far as I’m concerned, anti-virus systems are as good as dead. Or, if they aren’t just yet, they’re certainly headed that way.

Signature-based analysis, both static (e.g. SHA1 hash) and heuristic (e.g. pattern matching) is useless against polymorphic malware, which is becoming a big concern when you consider how easy it is to write code generators these days. By the time an identifying pattern is found in a particular morphing engine, the bad guys have already written a new one. When you consider that even most browser scripting languages are Turing complete, it becomes evident that the same malware behaviour is almost infinitely re-writeable, with little effort on the developer’s part. Behavioural analysis might provide a low-success-rate detection method, but it’s a weak indicator of malintent at best.

We’ve also seen a huge surge in attacks that fit the Advanced Persistent Threat (APT) model in the last few years. These threats have a specific target and goal, rather than randomly attacking targets to grab the low-hanging fruit. Attacks under the APT model can involve social engineering, custom malware, custom exploits / payloads and undisclosed 0-day vulnerabilities – exactly the threats that anti-malware solutions have difficulty handling.

The next problem is memory-resident malware. It’s very difficult (i.e. computationally expensive) for AV software to monitor the contents of program memory, let alone provide accurate detection of in-memory exploits on a live system. If malware never touches the disk, most AV software will never catch it. In his  “HTML5 – A Whole New Attack Vector” talk at BSidesLondon, Robert McArdle talked about botnets and other malware, written in HTML5, that reside within a browser tab. Now, assuming the browser doesn’t cache this on disk (you can usually mandate this with HTTP headers) you’ve got a memory-resident malware threat that doesn’t need browser exploits, is easy to obfuscate, and has full networking capabilities. On the other side of things, arbitrary code execution exploits within browsers might be leveraged to load executable modules into process memory. It would be relatively simple to write malware that remains resident in the browser process, or infects other long-running processes on the system. It’s also usually possible to prevent swapping on memory pages, so you can guarantee that the malware never touches the disk. It’s a nightmare for AV software, and a nightmare for forensic analysts.

Whilst the technical aspects of these attacks are a daunting opponent to anti-virus systems, the economic aspect is the nail in the coffin. According to PayScale, an average software developer in India gets about 320,000 INR per year, which equates to roughly 5700 USD. Compare that to the price of a malware analyst or systems security analyst, which is 60,000 USD before insurance, pension and other benefit costs are tacked on. That means that for every analyst that an AV company hires, the bad guys can hire 10 developers. That’s easily enough to work on 3 or 4 malware projects in parallel. There’s no competition here – the bad guys have more people working for them, for less money, and they don’t need to adhere to employment standards or ethical working practices. They can produce and update malware significantly more quickly (and cost-effectively) than the AV guys can analyse and defend against it.

Now don’t get me wrong, AV still has its place in the security world – without it, sysadmins would have to deal with a deluge of common malware used by script kiddies. However, it’s no longer much more than a filter for the most basic attacks.

Kelihos.B takedown

There are two main points to take away from the Kelihos.B takedown. The first is that malware writers, as smart as they are, are really dumb.

Forget about malware for a moment, and imagine the architecture involved in Kelihos.B was part of a legitimate distributed-computing business system. Would you invest all that time and effort to build a P2P network and design a mechanism for message propagation, then leave it wide open to attack by failing to include any form of authentication mechanism? It’s complete lunacy. If you did it in a commercial environment, you’d get fired. Out of a cannon. Into the sun.

The second interesting point is that this botnet exclusively infected OS X, which is a pretty big shift in focus from the previous major botnets like Conficker and Mariposa. I’m actually surprise it took this long for a major OS X botnet to surface, considering the general stereotype of Apple users. They’re usually not tech-savvy, generally more wealthy than the average Windows user, and often under the illusion that “there are no viruses for Macs!”. The perfect storm of getting pwned.

I have a feeling it won’t be very many years before the distribution of malware across Windows and OS X ends up being close to equal.

Why are botnets so bad at authentication?

You’d think that people writing botnets would be well versed in systems security, but from a quick look around I see that most botnets have some serious problems. The biggest issue with any botnet is command and control. How can the owner communicate with their bot nodes without having people steal their botnet by sniffing the traffic? Very few botnets out there seem to do anything to solve this issue, which baffles me. I recently found a botnet’s command and control channel on IRC and sat in there with a nickname similar to one of the bots and waited. The owner came online, authenticated with a password:

* jaxcx (none@C7A8F60F.70A4D926.D9A031DB.IP) has joined #jaxcnc
<jaxcx> .login gemma_2008
<j1F87E5A5> b 0 1 1 16701
<j81E0690F> b 0 1 0 25811
<jA18A5DF3> b 0 1 0 30246
<jaxcx> .av add avast.exe
<jaxcx> .c
* jaxcx (none@C7A8F60F.70A4D926.D9A031DB.IP) Quit (Quit)

I then logged in as him (.login gemma_2008) and tried some commands. After some playing about, I discovered his botnet ran as a process called ‘scvhost.exe’, which I then added to the bot’s AV list just as he added Avast. They all quit due to ping timeout a few minutes later.

The fatal flaw there was authentication – everything was simple plaintext. At an absolute minimum you’d expect challenge-response (CHAP) style login:

<owner> .auth
<bot1234> challenge 4356462135
<bot4567> challenge 3023843571
<bot4321> challenge 5430587478
<owner> .login bot1234 e737b251fb9581502c91e56d95cbe43e
<bot1234> .ok bot5678 owner 325e117a80e658027dd60ea2101823ae
<bot1234> .ok bot4321 owner 51d8e811dc92e387e037f91944218491

Confusing? Let’s break it down line by line.

<owner> .auth
The owner asks to authenticate to the botnet.

<bot1234> challenge 4356462135
<bot4567> challenge 3023843571
<bot4321> challenge 5430587478

The bots give a challenge value which the user must append to his password, which he then hashes with MD5. The owner only has to authenticate to one bot.

<owner> .login bot1234 e737b251fb9581502c91e56d95cbe43e
The owner decides to authenticate to bot1234, so he calculates the MD5 hash of “password4356462135” and uses it to log in. The bot verifies that the hash matches.

<bot1234> .ok bot5678 owner 325e117a80e658027dd60ea2101823ae
<bot1234> .ok bot4321 owner 51d8e811dc92e387e037f91944218491

The bot that was authenticated to now authenticates to the other bots on behalf of the user.

Of course this is just an example. It could be improved by using private messages to perform all the authentication, or by having a more secure password challenge format. The holy grail is, of course, asymmetric cryptography. Since the .NET framework makes it trivial to use RSA, one could simply authenticate by taking a random challenge value and producing a digital signature for it. The botnet clients have the public key embedded in them, which is then used to prove the authenticity of the user.

What I really don’t understand is why these methods aren’t being used. Are bot writers really that lazy, or am I missing something?