Little Snitch is a program for which I was very curious to hack around and try to beat it’s protection. I had a feeling it would be a very nice challenge and I can say it didn’t disappointed me!
The target is version 2.0.3, running on Tiger 10.4.11.
First protection to be defeated was the “classical” PTRACE_DENY_ATTACH. You Control Desktops explains and has links to this protection. If we try to attach gdb to one Little Snitch process (it has at least 3) we get a segmentation fault, so this should be PTRACE_DENY_ATTACH “protection”. Use otx or IDA to disassemble the binaries and search for ptrace calls. Yes, it’s there! Nop those calls and off we go!
Now the real interesting stuff…
After patching the PTRACE_DENY_ATTACH trick, we still can’t attach gdb to processes. We get a misterious EPERM error due to lack of permissions?!?!? Trying to attach gdb running as root gets the same result, so this must be another trick in the bag… To test some ideas, I tried to unload the kernel extension (Little snitch installs a kernel extension) but it was protected against unloading (which makes sense because a program could unload it and off it goes our firewall protection).
My attack point was trying to understand how could the extension be protected against unloading… After some web searching I found an interesting Apple article about KAUTH http://developer.apple.com/technotes/tn2005/tn2127.html.
This talks about a kernel authorization subsystem. It supports various authorizations scopes, where the most interesting and which seems to fit into our problem is:
KAUTH_PROCESS_CANTRACE — Authorizes whether the current process can trace the target process. arg0 (of type proc_t) is the process being traced. arg1 (of type (int *)) is a pointer to an an errno-style error code; if the listener denies the request, it must set this value to a non-zero value.
Kauth uses SCOPES to define an area of interest for authorizations. For example, the authorization we are looking at is located in Process Scope. Each scope has actions, for example KAUTH_PROCESS_CANTRACE. A listener is who makes the authorization decision for a request. You can create or use a default listener. After that, you register the listener into the scope of interest.
Resuming, it should be something like:
Create Listener (make decisions for actions) -> Register Listener
You can find demo code at http://developer.apple.com/samplecode/KauthORama/listing1.html.
To register a listener you use function kauth_listen_scope so we should look first for this one.
Disassemble the Little Snitch kernel extension and look for function calls (extension is located at /System/Library/Extensions) There are two calls: First one:
00006D40 55 push ebp 00006D41 89 E5 mov ebp, esp 00006D43 83 EC 18 sub esp, 18h 00006D46 C7 44 24 08 00 00+ mov [esp+18h+var_10], 0 00006D4E C7 44 24 04 28 00+ mov [esp+18h+var_14], 28h ; '(' 00006D56 C7 04 24 BC 00 01+ mov [esp+18h+var_18], offset unk_100BC 00006D5D E8 1E E9 FF FF call sub_5680 00006D62 C7 44 24 08 4B 00+ mov [esp+18h+var_10], 4Bh ; 'K' 00006D6A C7 44 24 04 00 02+ mov [esp+18h+var_14], 200h 00006D72 C7 04 24 04 FE 00+ mov [esp+18h+var_18], offset off_FE04 00006D79 E8 12 D2 FF FF call sub_3F90 00006D7E C7 44 24 08 00 00+ mov [esp+18h+var_10], 0 00006D86 C7 44 24 04 F0 6B+ mov [esp+18h+var_14], offset sub_6BF0 00006D8E C7 04 24 88 DA 00+ mov [esp+18h+var_18], offset aCom_apple_kaut ; "com.apple.kauth.fileop" <- SCOPE 00006D95 A3 C8 00 01 00 mov ds:dword_100C8, eax 00006D9A E8 D1 29 02 00 call near ptr _kauth_listen_scope 00006D9F A3 C4 00 01 00 mov ds:dword_100C4, eax 00006DA4 C9 leave 00006DA5 C3 retn
0000BC40 55 push ebp 0000BC41 89 E5 mov ebp, esp 0000BC43 83 EC 18 sub esp, 18h 0000BC46 E8 05 D5 01 00 call near ptr _IOLockAlloc 0000BC4B C7 44 24 08 4B 00+ mov [esp+18h+var_10], 4Bh ; 'K' 0000BC53 C7 44 24 04 08 00+ mov [esp+18h+var_14], 8 0000BC5B C7 04 24 48 FE 00+ mov [esp+18h+var_18], offset off_FE48 0000BC62 A3 20 91 02 00 mov ds:dword_29120, eax 0000BC67 E8 24 83 FF FF call sub_3F90 0000BC6C C7 44 24 08 00 00+ mov [esp+18h+var_10], 0 ; idata 0000BC74 C7 44 24 04 50 BB+ mov [esp+18h+var_14], offset sub_BB50 ; <- listener callback 0000BC7C C7 04 24 50 DE 00+ mov [esp+18h+var_18], offset aCom_apple_ka_0 ; "com.apple.kauth.process" <- SCOPE 0000BC83 A3 1C 91 02 00 mov ds:dword_2911C, eax 0000BC88 E8 E3 DA 01 00 call near ptr _kauth_listen_scope 0000BC8D A3 18 91 02 00 mov ds:dword_29118, eax 0000BC92 C9 leave 0000BC93 C3 retn
The prototype for this function is:
extern kauth_listener_t kauth_listen_scope( const char * identifier, kauth_scope_callback_t callback, void * idata );
Where the most interesting parameter is the callback because “callback is the address of your listener callback function”, meaning it’s the function making the decisions :-).
You can see the two scopes being used, the com.apple.kauth.fileop and com.apple.kauth.process. The process scope deals with processes operations and fileop with file operations. We are interested in the process operations.
IDA identified the callback location so let’s see what is there.
0000BB50 ; =============== S U B R O U T I N E ======================================= 0000BB50 0000BB50 ; Attributes: bp-based frame 0000BB50 0000BB50 sub_BB50 proc near ; DATA XREF: sub_BC40+34o 0000BB50 0000BB50 var_18 = dword ptr -18h <- local variable 0000BB50 arg_8 = dword ptr 10h <- action 0000BB50 arg_C = dword ptr 14h <- idata 0000BB50 arg_10 = dword ptr 18h <- credential 0000BB50 0000BB50 55 push ebp 0000BB51 89 E5 mov ebp, esp 0000BB53 53 push ebx 0000BB54 83 EC 14 sub esp, 14h 0000BB57 C7 04 24 14 91 02+ mov [esp+18h+var_18], offset dword_29114 0000BB5E E8 45 D6 01 00 call near ptr _OSIncrementAtomic 0000BB63 A1 84 FE 00 00 mov eax, ds:dword_FE84 0000BB68 85 C0 test eax, eax 0000BB6A 75 06 jnz short loc_BB72 ; change to jump and so give a always defer result ? 0000BB6C 83 7D 10 02 cmp [ebp+arg_8], 2 <- Compare Action in argument against 2. For this scope, value 2 action is KAUTH_PROCESS_CANTRACE, so this should be testing if action is trace attempt 0000BB70 74 1E jz short loc_BB90 <- if equal then process this action 0000BB72 0000BB72 loc_BB72: ; CODE XREF: sub_BB50+1Aj 0000BB72 ; sub_BB50+45j ... 0000BB72 BB 03 00 00 00 mov ebx, 3 ; default is to return KAUTH_RESULT_DEFER (3) 0000BB77 0000BB77 looc_BB77: ; CODE XREF: sub_BB50+6Cj 0000BB77 C7 04 24 14 91 02+ mov [esp+18h+var_18], offset dword_29114 0000BB7E E8 1D D6 01 00 call near ptr _OSDecrementAtomic 0000BB83 89 D8 mov eax, ebx 0000BB85 83 C4 14 add esp, 14h 0000BB88 5B pop ebx 0000BB89 C9 leave 0000BB8A C3 retn 0000BB8A ; --------------------------------------------------------------------------- 0000BB8B 90 90 90 90 90 align 10h 0000BB90 0000BB90 loc_BB90: ; CODE XREF: sub_BB50+20j 0000BB90 8B 45 14 mov eax, [ebp+arg_C] 0000BB93 85 C0 test eax, eax 0000BB95 74 DB jz short loc_BB72 0000BB97 8B 45 14 mov eax, [ebp+arg_C] 0000BB9A 89 04 24 mov [esp+18h+var_18], eax 0000BB9D E8 0A DC 01 00 call near ptr _proc_pid 0000BBA2 89 04 24 mov [esp+18h+var_18], eax 0000BBA5 E8 B6 FE FF FF call sub_BA60 0000BBAA 85 C0 test eax, eax 0000BBAC 74 C4 jz short loc_BB72 0000BBAE 8B 45 18 mov eax, [ebp+arg_10] 0000BBB1 BB 02 00 00 00 mov ebx, 2 ; <- KAUTH_RESULT_DENY (2) 0000BBB6 C7 00 01 00 00 00 mov dword ptr [eax], 1 ; <- EPERM (1) 0000BBBC EB B9 jmp short loc_BB77 0000BBBC sub_BB50 endp 0000BBBC
If you check the KauthORama example, you can see the OSIncrementAtomic being used in the listeners routines. Let’s assume the author used this example to create his code. We should be in the right track.
To analyse this function we need to give a look to the include files (you need to install XCode and SDKs).
/usr/include/sys/errno.h:#define EPERM 1 /* Operation not permitted */ /Developer/SDKs/MacOSX10.4u.sdk/System/Library/Frameworks/Kernel.framework/Headers/sys/kauth.h:#define KAUTH_PROCESS_CANTRACE 2 /Developer/SDKs/MacOSX10.4u.sdk/System/Library/Frameworks/Kernel.framework/Headers/sys/kauth.h:#define KAUTH_RESULT_ALLOW (1) /Developer/SDKs/MacOSX10.4u.sdk/System/Library/Frameworks/Kernel.framework/Headers/sys/kauth.h:#define KAUTH_RESULT_DENY (2) /Developer/SDKs/MacOSX10.4u.sdk/System/Library/Frameworks/Kernel.framework/Headers/sys/kauth.h:#define KAUTH_RESULT_DEFER (3)
With these values we can try to understand what is going on (without kernel debugging, I can only do a dead listing attempt so I have to assume lots of stuff and cross fingers :-)).
The listener prototype is:
static int MyListener( kauth_cred_t credential, void * idata, kauth_action_t action, <- Requested Action uintptr_t arg0, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3 );
Checking the arguments passed to the function, we see the action KAUTH_PROCESS_CANTRACE being tested. The listener must return one of three possible results: ALLOW, DENY or DEFER.
A request might go thru various listeners, but a single DENY from one listener is sufficient to DENY the request. If you check the demo code from the Technical Note, it returns DEFER as the default action. That makes some sense, because if we are not interested in processing that action, then we should let other listeners decide over it.
The logic for this piece of code could be something like this:
if action eq KAUTH_PROCESS_CANTRACE then process_and_deny_action else defer_action
To patch this, we just need to return always the default value. We can force the first JNZ or NOP the second JZ. I patched the first JNZ to JMP. Patch the kernel extension, reboot and try to attach to the process… Hurray, no more errors!
Another one bites the dust… This is a very interesting feature of Mac OS X but we were able to somewhat easily beat it due to the ability to understand what was happening by dead listing the code. If code was obfuscated in some way it would be much harder because not everyone has two Macs to do kernel debugging.