I have a passion for the human brain and human behavior and I love to experiment with anything. My birthday is near so it’s a good time to go forward with this idea.
The starting point is that this blog is absolutely non-profit oriented and that status will remain forever – no banners, no donations, etc. I do it purely for fun, pleasure and knowledge improvement, altough it generates positive externalities (aka work!).
I am curious to observe what would be the result of  sort of asking something in return (sort of what is the value for you). I love books (physical ones!) and that is what I am asking for. My initial idea was to create an Amazon wish list. This doesn’t work because it will disclose my shipping address. The solution that seems to work without major problems is an Amazon gift certificate – an email address and that’s it.

So what kind of amounts are we talking about ? I have a very positive experience with the Amazon Marketplace and second-hand books. The descriptions are very accurate and I purchased books as new for awesome prices. As a reference point the total cost of each book I get is around €8 to €12 ($10 to $15, £6.5 to £10, including shipping (usually the significant cost!). I usually purchase from Amazon.com or Amazon.co.uk (gift certificates can’t be exchanged between different countries). The email address for this experiment is amazonexperience at put.as.

Let’s see how this works out. The only risk you have is if it’s a success and I get a ton of new books. Then I will have to dedicate more time to read them and less time to publish stuff! Hey, life is risky ;-)

fG!

One obstacle that I faced long time ago and came again into spotlight is how to recompile gdb for iOS. It is not useful to fix the arm disassembler and then not be able to compile. As far as I know there isn’t any documentation available or an easy method to accomplish this – Saurik’s build environment is not public (?) and Apple sources do not compile directly. Darwinbuild project works great for OS X but it’s a question mark for iOS.

Darwinbuild it is! After some failed hacking last Friday (progress was great and it was near completation), I decided to try to fix the loose end today. Success was finally achieved.
This post contains almost all the information that you need to recompile gdb yourself. There is something that you will need to complete by trial & error. Let’s start the fun!

The reference post on darwinbuild usage is this one, written by yours truly. You should follow it and modify accordingly with the information provided here. My OS X version is still Snow Leopard but you should have no problems with Lion.
The image size should be 2GB, and you should use the build # 10K540. When you execute the “darwinxref edit”, use the following information:

environment = {
INSTALLED_PRODUCT_ASIDES = YES;
MACOSX_DEPLOYMENT_TARGET = 10.6;
NEXT_ROOT = "";
RC_ARCHS = "armv7 armv6";
RC_JASPER = YES;
RC_NONARCH_CFLAGS = "-pipe";
RC_OS = macos;
RC_PRIVATE = /private;
RC_RELEASE = SnowLeopard;
RC_TARGET_CONFIG = iphoneos;
RC_XBS = YES;
SEPARATE_STRIP = YES;
UNAME_RELEASE = 10.0;
UNAME_SYSNAME = Darwin;
};

Word of caution: be careful with copy & pasting this because of the “” (if you get an error while saving from darwinxref edit).

The next step is to edit the darwinbuild database. It’s located at “.build/xref.db”, inside the Build10K540 folder you should be located at. You need to change the gdb version to the latest one, 1708 instead of 1344. Execute the following sql statement to verify it:

SELECT * FROM properties WHERE project="gdb" AND property="version";

and then update the field:

UPDATE properties SET VALUE="1708" WHERE project="gdb" AND property="version";

Start compilation with “darwinbuild -nochroot gdb”. Version 1708 will be downloaded. When configuration/compilation starts, abort it with ctrl-c.
You will need to create a link (there is probably a more elegant solution to this!). Go to the usr/lib folder inside the iOS SDK. There you need to make a link from “crt1.10.6.o” to “crt1.o”.  Small example from my system:

lrwxr-xr-x  1 root  wheel     6 Apr 14 04:12 /Developer4/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS5.0.sdk/usr/lib/crt1.10.6.o -> crt1.o
-rw-r–r–  1 root  wheel  2720 Aug 30  2011 /Developer4/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS5.0.sdk/usr/lib/crt1.3.1.o
-rw-r–r–  1 root  wheel  4584 Aug 30  2011 /Developer4/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS5.0.sdk/usr/lib/crt1.o

Next step is to modify the file “BuildRoot/SourceCache/gdb/gdb-1708/src/gdb/macosx/macosx.defs”. Here you need to replace the import for exc.defs. Change:

#import

to:

#import "/Developer4/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS5.0.sdk/usr/include/mach/exc.defs"

(modify your path accordingly)

Last step for now is to modify the Makefile. We need to modify it so the ARM cross-compiling tools are used. It’s located at BuildRoot/SourceCache/gdb/gdb-1708/Makefile. To make it easier, you have my Makefile as a reference (all files at the end). I left the places that you need to modify tagged with FIXME. Your task is to change the paths.

Now you are ready to compile and start the trial and error process. This time, compile with “darwinbuild -nochroot -nosource gdb”. This will not unpack again the source package and will keep our previous changes.
The compilation process will start and hopefully you will observe lots of output, which is a good sign! Near completation, errors regarding missing includes will start to appear. Your task is to manually copy them from OS X “/usr/include” to the iOS SDK “usr/include” folder (in my case /Developer4/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS5.0.sdk/usr/include/). The only modifications that you will need to do are to edit some files and change the import location to relative paths (or absolute if you prefer). Not elegant, but it works! When you reach the missing architecture includes, you can use the ones from i386. Sorry for not having a complete file list – I was hacking this without great hope that it would work heheheh.

And that’s it! After you fix the missing includes and defs, the compile should successfully finish and you have your shiny recompile gdb. You can also apply my gdb patches (recommended!). Before starting to compile everything, just go to the SourceCache folder, apply the patch and compile.
Follow the steps from the reference post to copy the compiled binary, apply the necessary entitlements (reference), upload to your device and enjoy :-)

If you don’t feel adventurous enough then I include a fat binary (armv6 and armv7) with my patches. You just need to add the entitlements.
Pancake (from Radare) created a package for this version. Add http://cydia.radare.org to your repo list and install it from there. Thanks to pancake for his work :-)

Any question or problem you run into leave a comment so everyone else can benefit from the (potential) solution.

Have fun,
fG!

Makefile.gz

gdb-arm-apple-darwin.gz

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
 
SHA256(Makefile.gz)= 9aa69bc9b5a77a682c5bc74435440f26e839c0b216861f64a1af4f5a6432dfaf
SHA256(gdb-arm-apple-darwin.gz)= 7c3744c1be024a28c594c0ad90d75f0d187c5e53d9cb09d0183bba19b7415e6d
-----BEGIN PGP SIGNATURE-----
Version: GnuPG/MacGPG2 v2.0.17 (Darwin)
Comment: GPGTools - http://gpgtools.org
 
iQEcBAEBAgAGBQJPkTVwAAoJEAADGo6F9Uj36RUIAJF5E3Ak7d/q6MR0tNPMIoKy
/v9lEkt9bBr0QBo/GHj0bEkcVKp58Ft3y2yE14qkk7BpxHYGalvzTLNGy9uk3TRL
xprJpwKxttpms14+N+tNKBEKu3g5iItMbyWiip60UWbhYMlmXpKQFOMxJeHQIYLy
88KlbqEfiztil4UY04q/CUjxFfV38lvQCosgjDJ2XHHMrsJNvxfLslEkMTxOrbS5
C64TNQ3lj7SWvVBgAQ9OkjrWqNcPJyULth9ScKEixhWNHzcjZmIxP9+9PmrfviAn
rckSlEVhNDtOf9tsDfBaMM2STmPG5unuhaMR2vda+VVAtNOHZ+KO1MY6k6y+Zfk=
=jUdm
-----END PGP SIGNATURE-----

Update: List of added/modified include files (I forgot about the power of find :X)
./_locale.h
./libproc.h
./mach/arm/machine_types.defs
./mach/exc.defs
./mach/mach_types.defs
./mach/mach_vm.h
./mach/machine/machine_types.defs
./mach/machine/thread_state.h
./mach/std_types.defs
./ncurses_dll.h
./net/route.h
./sgtty.h
./sys/dir.h
./sys/ioctl_compat.h
./sys/kern_control.h
./sys/proc_info.h
./sys/ptrace.h
./sys/ttychars.h
./sys/ttydev.h
./termcap.h

Here it is, a merge between the x86 and ARM versions of gdbinit. The only inconvenience is that you need to manually change the target, using the “32bits” and “64bits” commands for x86/x86_64 architectures, and “arm” for ARM. That’s a small price to pay for :-)

This version features a lot of cosmetic fixes (indentation mostly) but also some fixes to the ARM related code, and a new command – dumpmacho. This command will dump the Mach-O header to a file. You need to supply the start address and the output filename. Only the header information is dumped – sometimes I need to dump the header and load it into otool or machoview to verify some things. Just a command to automate things!

The next step is to try to compile a new iOS gdb version that features my fixes. I think I will do another attempt to add the armv7 instructions to gdb so it’s not a major pain to debug these binaries. Let’s see if I can succeed this time.

There’s no test suite for gdbinit (<xxxxxxxx> testing is for chumps) ! From my tests everything is working, if not leave a msg here or at github.

Enjoy,
fG!

gdbinit8.gz
SHA256(gdbinit)= fb510d812dabbad968e68ad1e4916aa85400d6375e0e404f5893946151420238

The title of this post is a partial rip-off of Dynamic Code Encryption as an Anti Dump and Anti Reverse Engineering measure. Alexey describes a technique similar to the one I used in my crackme, which isn’t altogether that new. His post is a good introduction to some possible attack vectors and what is at stake. You should give it a look :-)

The crackme uses a multi-layer dynamic code encryption approach, with two different encryption algorithms (Rabbit and Salsa). The objective was to avoid an easy memory dump of all the code and also as anti-debugging. I decided not to obfuscate the algorithms, except removing some obvious web-searchable stuff, because that wasn’t the crackme objective. Also one of the algorithms was right away exposed – necessary to do the first stage decryption. The “trustable” XOR could have been used for adding another stage of obfuscation, but still not the point.

While I was thinking in a way to store the decryption information and searching around, I came up with this interesting post at Root Labs blog. It is a cute idea to help against breakpoints and patching and it fit nicely into what I had in mind. In this case, the encryption/decryption key is composed by the SHA256 checksum of the function to be protected, to which is added a 128 bit salt value, and then generated the final SHA256, which is the key. The salt values are stored into the four hardware registers. Just a very simple and fast way to try to deter hardware breakpoints. All this work is done by an external binary, the protector, against the compiled but still not crypted crackme.

The last piece of the puzzle is the storage of the decryption information, where to decrypt and its size. Alexey suggests some markers at the start and end of the function. My solution was to store that information inside the function that will decrypt the next stage into a fake piece of code that would desync IDA disassembly (a very basic fake jump), together with a marker to find it. This could be located anywhere in the function and found by a simple loop inserted into every function. The system function mach_vm_protect() is used to make the code section writable and then back to the original protection, and it’s simply obfuscated using a function pointer and XORed strings.

I think I’m not missing any big detail. The best way to explain it is to show a piece of the code. It’s huge and not exactly the most beautiful piece of code :-)

/*
 * the debug loop, we decrypt and encrypt the exception handler and the
 * function that deals with the exceptions
 */
void
debug_loop(void)
{
#if DEBUG
    printf("***************************\n");
    printf("[DEBUG] Started debug loop!\n");
    printf("***************************\n");
#endif
    JUNK_CODE1;
    uint32_t search;
    asm(".att_syntax prefix");
    asm __volatile__("nop\n\t"
                     "call 1f\n\t" // E8 00 00 00 00
                     "1:\n\t"
                     "pop %%eax\n\t" // 58
                     "mov %%eax, %0" // 89 C3
                     : "=r" (search)
                     :
                     :"eax");
    asm(".att_syntax prefix");
#if DEBUG
    printf("Search is %x %x\n", search, *(uint32_t*)(search+5));
#endif
    EVIL_ASM5;
    uint8_t iv[8] = "hackersz";
    mach_vm_address_t addr = 0;
    uint32_t protectSize = 0;
    uint32_t functionSize = 0;
    uint32_t functionBegin = 0;
    volatile uint16_t xorKey = 0x4569;
    for (uint32_t i = search; i < search + 0x2000; i++)
    {    
        if ((*(uint16_t*)i ^xorKey) == 0x745D) // 0x3134
        {
            addr = *(uint32_t*)(i+4);
            protectSize = *(uint32_t*)(i+8);
            functionSize = *(uint32_t*)(i+12);
#if DEBUG
            printf("[DEBUG] Found exception_handler decrypting info at %x . Address:%x Size:%x Function Size:%x!\n", i, 
            *(uint32_t*)(i+4), *(uint32_t*)(i+8),*(uint32_t*)(i+12));
#endif
            break;
        }
    }
    xorKey = 0x1233;
    for (uint32_t i = search; i > 0x1000; i--)
    {
        if ((*(uint16_t*)i ^ xorKey) == 0xF7BA) // 0xe589
        {
            functionBegin = i - 1;
#if DEBUG
            printf("[DEBUG] Found debug_loop() beginning at %x\n", functionBegin);
#endif
            break;
        }
    }
 
    kern_return_t (*mymach_vm_protect)(vm_map_t target_task,mach_vm_address_t address,mach_vm_size_t size,
                                        boolean_t set_maximum,vm_prot_t new_protection);
    volatile uint8_t vmprotectsymbol[] = {0x2e,0x22,0x20,0x2b,0x1c,0x35,0x2e,0x1c,0x33,0x31,0x2c,0x37,0x26,0x20,0x37,0x43};
    DECRYPT_STRING(16, vmprotectsymbol, 0x43);
    mymach_vm_protect = DLSYM_GET(vmprotectsymbol);
 
    while (1)
	{
        // decrypt exception handler
        // build the key
        uint8_t key[32];
        EVIL_ASM3;
        sha2((uint8_t*)functionBegin, functionSize, key, 0);
        // build the salt array
        uint32_t salt[4];
        x86_debug_state32_t debug;
        mach_msg_type_number_t	count;
        thread_state_flavor_t flavor;
        flavor = x86_DEBUG_STATE32;
        count = x86_DEBUG_STATE32_COUNT;
        thread_act_port_array_t thread_list;
        mach_msg_type_number_t thread_count;
        kern_return_t (*mytask_threads)(task_t target_task,thread_act_array_t *act_list,mach_msg_type_number_t *act_listCnt);
        volatile uint8_t taskthreadssymbol[] = {0x20,0x35,0x27,0x3f,0x0b,0x20,0x3c,0x26,0x31,0x35,0x30,0x27,0x54};
        DECRYPT_STRING(13,taskthreadssymbol,0x54);
        mytask_threads = DLSYM_GET(taskthreadssymbol);
        (*mytask_threads)(mach_task_self(), &thread_list, &thread_count);
 
        kern_return_t (*mythread_get_state)(thread_act_t target_act,thread_state_flavor_t flavor,thread_state_t old_state,
                                            mach_msg_type_number_t *old_stateCnt);
        volatile uint8_t threadgetstatesymbol[] = {0x37,0x2b,0x31,0x26,0x22,0x27,0x1c,0x24,0x26,0x37,0x1c,0x30,0x37,0x22,0x37,0x26,0x43};
        DECRYPT_STRING(17,threadgetstatesymbol,0x43);
        mythread_get_state = DLSYM_GET(threadgetstatesymbol);
 
        (*mythread_get_state)(thread_list[0], flavor, (thread_state_t)&debug, &count);
        salt[0] = debug.__dr0;
        salt[1] = debug.__dr1;
        salt[2] = debug.__dr2;
        salt[3] = debug.__dr3;
 
        uint8_t *tempKey = malloc(sizeof(salt) + sizeof(key));
        memcpy(tempKey, key, sizeof(key));
        memcpy(tempKey+sizeof(key), salt, sizeof(salt));
        // compute the final salted key
        sha2((uint8_t*)tempKey, sizeof(salt) + sizeof(key), key, 0);
        free(tempKey);
        EVIL_ASM5;
        // and now we can finally decrypt
        // modify memory protection so we can decrypt and write
        kern_return_t kr;
#if DEBUG
        printf("[DEBUG] Starting to decrypt exception_handler...\n");
#endif
        kr = (*mymach_vm_protect)(mach_task_self(), (mach_vm_address_t)addr, (mach_vm_size_t)protectSize, FALSE,  WRITEPROTECTION);
#if DEBUG
        EXIT_ON_MACH_ERROR("Failurex", 1);
#endif
 
        // start decryption, the input buffer is the same as the output buffer
        SALSA_ctx ctx;
        SALSA_keysetup(&ctx, key, 256, 64);
        SALSA_ivsetup(&ctx, iv);
        SALSA_decrypt_bytes(&ctx, (uint8_t*)addr, (uint8_t*)addr, protectSize);
        EVIL_ASM1;
        // restore original memory permissions
        kr = (*mymach_vm_protect)(mach_task_self(), (mach_vm_address_t)addr, (mach_vm_size_t)protectSize, FALSE,  READPROTECTION);
#if DEBUG
        EXIT_ON_MACH_ERROR("Failure", 1);
        printf("[DEBUG] End exception_handler decrypt\n");
        printf("[DEBUG] Calling exception handler...\n");
#endif
		exception_handler();
        // crypt exception handler?
#if DEBUG
        printf("[DEBUG] Starting to encrypt exception_handler...\n");
#endif
        kr = (*mymach_vm_protect)(mach_task_self(), (mach_vm_address_t)addr, (mach_vm_size_t)protectSize, FALSE,  WRITEPROTECTION);
#if DEBUG
        EXIT_ON_MACH_ERROR("Failurex", 1);
#endif
 
        SALSA_keysetup(&ctx, key, 256, 64);
        SALSA_ivsetup(&ctx, iv);
        EVIL_ASM2;
        SALSA_encrypt_bytes(&ctx, (uint8_t*)addr, (uint8_t*)addr, protectSize);
 
        // restore original memory permissions
        kr = (*mymach_vm_protect)(mach_task_self(), (mach_vm_address_t)addr, (mach_vm_size_t)protectSize, FALSE,  READPROTECTION);
#if DEBUG
        EXIT_ON_MACH_ERROR("Failure", 1);
        printf("[DEBUG] End exception_handler encrypt\n");
        printf("[DEBUG] Return from exception handler...\n");
#endif
        // the tail that will hold our decryption data
        asm(".intel_syntax noprefix");
        asm __volatile__ ("xor edx, edx\n\t" //31d2
                          "test edx, edx\n\t" // 85d2
                          "jz 1f\n\t" // 7416
                          //                                            "jmp 1f\n\t"
                          ".byte 0x00\n\t"
                          ".long 0x0064a990\n\t"
                          ".long 0x00003134\n\t" 
                          ".long 0x00000000\n\t" // address
                          ".long 0x00000000\n\t" // size
                          ".long 0x00000000\n\t" // function size
                          "1:\n\t");
        asm(".att_syntax prefix");
	}
}

You can find a text file with the same code here (stupid plugin loves to mess the code!).

So what was the objective of all this ? Besides trying to hide the other tricks that I really wanted to show, it also demonstrates how easy is to raise the barrier in OS X, both for malware and legit software protection. It’s not exactly rocket science! The crackme and tools took like 3 weeks time to write, and could be vastly improved by fixing some of the assumptions and if the target was something else than a crackme, where you have a single, well defined objective.

I have a feeling this post quality is somewhat mehhhh ! Been too busy with XCode in real-life projects and writing inspiration has been low. Sometimes one must pass the initial barrier, write something and then comeback later to fix it. Feel free to leave comments to clear doubts & questions!

The crackme also uses code from PolarSSL libraries. Salsa and Rabbit are from Ecrypt Project.

Have fun,
fG!

I love to read about the Human brain and yesterday I was feeling weird about this thing. As far as I know, everyone (publicly) was trying to search sysent in one way or another after Apple removed the sysent symbols but not bruteforcing it. It seems no one bothered to question the original method (Landon Fuller?) and just kept using it. Are there any historical reasons for this? I can’t remember any. Sometimes, we are just blind to the simple things and solutions and don’t question them. It’s very probable that someone already posed the same question about the known methods (this is not cold fusion :P ). Let’s continue…

This is a simple method to bruteforce 32 and 64bits kernels (tested with Snow Leopard and Lion) and retrieve sysent address. The __DATA segment where the sysent symbol is located is around 250kbytes in size, which is pretty a small space to search. My first method required the kernel base address to be configured, which isn’t a great issue – historically, it is quite stable. Computers exist to automate tasks and @snare “complained” about hardcoding that address. I was trying to fix checkidt to 64bits and the solution to overcome the hardcoded address just came to mind. The IDT can be used for this! Just retrieve the IDT address, then the address of interrupt 80 (or some other implemented interrupt handler) and you know where the kernel is loaded. Now it is just a matter of finding the start of the kernel mach-o address, reading the __DATA segment to know its address and size, and then bruteforce search sysent array.
This can be even easier if we just bruteforce beyond and before the int 80 handler – not a sexy approach, we want elegant bruteforcing ;-) .

The PoC code is for an userland util that retrieves the information through /dev/kmem. I did a kernel port, which works without any problems (it’s even easier to implement). On my Mac it takes 0m0.035s to find sysent (0m0.012s on a 64bits Mac Mini server – thanks to Saure for all tests). That is very good for what should be a future-proof method to retrieve sysent. Unfortunately, hijacking sysent isn’t sexy anymore!

In other news, Snare finally created his own blog, available at http://ho.ax. He started with a nice article about kernel debugging in VMWare, updating my old one. Good work!
Of course this is competition so he should expect a very evil EFI rootkit one of these days with total destruction of his computer ;-) . Give it a look while it lasts!

Well, time to move forward to the next project. Ideas continue to abound…

If you like to read and are looking for great books, I highly recommend “Thinking, Fast and Slow” by Daniel Kahneman, Dan Ariely’s two books, and “Being Wrong: Adventures in the Margin of Error” by Kathryn Schulz. The world would probably be a better place if everyone knew their potential “shortcomings”. There’s more behind the scenes in our brains that we consciously know and like to admit.

fG!

bruteforcesysent.zip
SHA256(bruteforcesysent.zip)= 14a7b55368ad9ec91d639c3b6f5a61319c8b5ece61d6eb1ae4dd98182abcc33d

P.S.: If you are a github fan, I have been uploading stuff there.
P.S.2: Don’t forget the IRC channel, more active lately, irc.freenode.net, #osxre !

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.

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 at 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 much 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.

__text:00001AB7 C7 44 24 08 00 00+                mov     dword ptr [esp+8], 0
__text:00001ABF C7 44 24 04 DF 19+                mov     dword ptr [esp+4], offset _onaccessintercept_callback
__text:00001AC7 C7 04 24 74 24 00+                mov     dword ptr [esp], offset aCom_apple_kaut ; "com.apple.kauth.vnode"
__text: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),
fG!

av-monster_v0.2.zip
SHA256(av-monster_v0.2.zip)= 554943e9f5d90e65f22d904c96526819ffe4348f391c8c0b8865b797abb490a2

P.S.:
All code available here, unless specifically expressed, is free to be used without any license attached. It’s just nice to mention original credits.

« Older entries