Welcome to another “silly” evil idea that abuses bad design decisions, bad implementations and lazyness. It is the last of my ideas in a state of semi-disclosure so let’s move it to full disclosure status.
The full disclosure discussion will probably never end. There are too many interests at stake, mostly in opposite directions. For me it’s worrisome that (security) products are available with notorious design/implementation flaws which put customers at risk and fail on their purpose. The argument that it is full disclosure that puts customers at risk is very hard to defend when companies themselves release poor code and have their marketing machines babbling nonsense. PCanywhere source code disclosure is a great example of this slackness regarding product security. Not the disclosure itself but the request to stop using our product, which is so bad that it can’t resist a source code leak. Companies should care about their customers (and their employees by the way) not just bottom line metrics.
So what is at stake here? I have tested a dozen Mac anti-virus products and all suffer from the same problem(s). The executive summary version of this is that these products can be (easily) runtime patched and disk patched, effectively disabling them. Those tested do not have even a simple checksum of their own binaries and they will happily run their modified code. Is this correct? I don’t think so! There’s not even the slightest effort to protect their binaries (ok, if I’m not mistaken, ESET tries to block debugging of their stuff). Is protecting their binaries a hard problem? Yes, but not an excuse for not doing anything about it.
AV-monster is a kernel extension that will search and runtime patch the anti-virus kernel module responsible to send the files to the userland scanning engine. To disk patch those binaries is even more trivial from an userland application. That is not fun, right?
The reference document for this post is Apple’s Technical Note TN2127 – Kernel Authorization. Anti-virus kernel modules implement one or two listeners, depending on the scopes they are interested in (fileop and vnode). These listeners will be responsible for notifying the userland scanning daemon/engine and returning a value to the kernel, based on the scanning result. This value is one of the following:
- KAUTH_RESULT_DEFER — This value indicates that the listener defers the decision about this request to the other listeners (and ultimately to the default listener).
- KAUTH_RESULT_ALLOW — This value indicates that, as far as this listener is concerned, the request is allowed.
- KAUTH_RESULT_DENY — This value indicates that the request should be denied.
The referenced technical note suggests (best practice?) the use of kauth mechanism to implement an anti-virus scanner: “Kauth allows you to implement an anti-virus program that supports both “on access” and “post modification” file scanning.”. In my opinion this design creates a SPOF (single point of failure) that can be easily exploited. We just need to patch the listener and return “appropriate” values. The easiest way to accomplish this is to force the listener to always return a KAUTH_RESULT_DEFER (I remember about some weird results with allow). For increased stealthness (APT weeeeee!!!) we could hijack that listener with our own and bypass scanning on selected files (if we force a single return value the scanning engine will stop receiving files and that can raise questions). This is not hard to execute – we are in kernel space and it’s more or less like hijacking a system call via sysent.
How is AV-monster implemented?
The first task is to find the address of the anti-virus kernel module (we could patch kernel’s kauth but that would be too noisy and not fun). This could be easily done from userland but it’s also pretty easy from kernel land. The kmod_info_t that is passed to the start and stop functions of the module point to the head of a linked list. We just need to iterate this list and find the right address – I do this by hashing the kernel module name and comparing to pre-computed hashes of known modules. The next step is to process the Mach-O header of target module and retrieving info for __cstring and __text sections. The reason for this is to find the reference address to the scope name strings – com.apple.kauth.fileop and com.apple.kauth.vnode. Only ESET has some changes in this code that installs the listeners.
00001AB7 C7 44 24 08 00 00+ mov dword ptr [esp+8], 0 00001ABF C7 44 24 04 DF 19+ mov dword ptr [esp+4], offset _onaccessintercept_callback 00001AC7 C7 04 24 74 24 00+ mov dword ptr [esp], offset aCom_apple_kaut ; "com.apple.kauth.vnode" 00001ACE E8 31 10 00 00 call near ptr _kauth_listen_scope
After we search for the reference to the scope name we just need to read the value being pushed to esp+4, which is the listener callback that we want to patch/hijack. The patching steps involve storing the original bytes (in this PoC I want the module to exit cleanly), disabling kernel write protection and interrupts, patch the callback and restore disabled protections. That’s it, bye bye anti-virus!
Tested in Snow Leopard 10.6.8 and Lion 10.7.3 against: Intego, Avast, Comodo, Eset, Kaspersky, McAfee, Panda, Sophos, Dr Web, Bit Defender, Mac Keeper and F-Secure (doesn’t run on VMware and not tested but it’s vulnerable anyway). The remaining ones are, most certainly, also vulnerable. One of these is able to detect this particular implementation of AV-monster since I disclosed to them an initial PoC a few months ago – share back with those who share and respect!
What’s really at stake here? The threat level at OS X platform can be easily increased by “attackers” with the right financial incentive to do it (either thru classical malware schemes, industrial espionage – CEOs love Apple gadgets, etc). The barriers to entry are not that high. Don’t worry, I am not in that position.
Enjoy (or not),
All code available here, unless specifically expressed, is free to be used without any license attached. It’s just nice to mention original credits.