Running OS X 10.10 Yosemite’s iTunes in LLDB

With each version of iTunes, Apple tries to prevent people reverse-engineering it, in order to protect some sensitive stuff, involving DRMs, device management, etc.

Different levels of protection are used in the iTunes binary, as well as in some private frameworks.

Machine code for sensitive procedures is heavily obfuscated, a few tricks are used to fool disassembly softwares and debuggers, and usually, iTunes doesn’t run out of the box in a debugger like LLDB or GDB.

Previous versions of iTunes used a very simple protection mechanism in order to prevent it running in a debugger.

At startup, iTunes called the ptrace function, with the PT_DENY_ATTACH argument.
So it would quit almost immediately, when run inside a debugger.

The workaround was also very simple: a breakpoint on ptrace followed by an immediate return when the breakpoint is hit.
On LLDB, that means:

lldb /Applications/iTunes.app/Contents/MacOS/iTunes
Current executable set to '/Applications/iTunes.app/Contents/MacOS/iTunes' (x86_64).
(lldb) b ptrace
Breakpoint 1: where = libsystem_kernel.dylib`__ptrace, address = 0x00000000000163b8
(lldb) br command add 1
Enter your debugger command(s).  Type 'DONE' to end.
> thread return
> c
> DONE

That’s it… iTunes now runs fine in LLDB.

With OS X 10.10 Yosemite, a few things have changed…

iTunes no longer calls ptrace at startup, but still quits when running inside LLDB:

lldb /Applications/iTunes.app/Contents/MacOS/iTunes
Current executable set to '/Applications/iTunes.app/Contents/MacOS/iTunes' (x86_64).
(lldb) r
Process 48470 launched: '/Applications/iTunes.app/Contents/MacOS/iTunes' (x86_64)
Process 48470 exited with status = 1 (0x00000001) 
(lldb) 

As we can see, the process exits almost immediately with status code 1.
Let’s see if we can find a workaround.

As the application exits cleanly, we’ll simply break on the system exit function, in order to see where exactly the application exits:

(lldb) b _Exit
Breakpoint 1: where = libsystem_c.dylib`_Exit, address = 0x00007fff8b16b042

When we run iTunes again, it eventually hit the breakpoint, so we can ask for a backtrace:

* thread #1: tid = 0x8a841, 0x00007fff8b16b042 libsystem_c.dylib`_Exit, name = 'iTunes main', queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
    frame #0: 0x00007fff8b16b042 libsystem_c.dylib`_Exit
libsystem_c.dylib`_Exit:
-> 0x7fff8b16b042:  pushq  %rbp
   0x7fff8b16b043:  movq   %rsp, %rbp
   0x7fff8b16b046:  callq  0x7fff8b1914c0            ; symbol stub for: _exit
(lldb) bt
* thread #1: tid = 0x8a841, 0x00007fff8b16b042 libsystem_c.dylib`_Exit, name = 'iTunes main', queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
  * frame #0: 0x00007fff8b16b042 libsystem_c.dylib`_Exit
    frame #1: 0x00000001007762b8 iTunes`___lldb_unnamed_function35905$$iTunes + 230
    frame #2: 0x0000000100006262 iTunes`___lldb_unnamed_function115$$iTunes + 196
    frame #3: 0x000000010000616a iTunes`___lldb_unnamed_function114$$iTunes + 42
    frame #4: 0x0000000100005454 iTunes`___lldb_unnamed_function83$$iTunes + 50
    frame #5: 0x0000000100005320 iTunes`___lldb_unnamed_function79$$iTunes + 56
    frame #6: 0x0000000100002ac6 iTunes`___lldb_unnamed_function11$$iTunes + 231
    frame #7: 0x00007fff8c0247f4 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ + 20
    frame #8: 0x00007fff8c024483 CoreFoundation`__CFRunLoopDoTimer + 1059
    frame #9: 0x00007fff8c09915d CoreFoundation`__CFRunLoopDoTimers + 301
    frame #10: 0x00007fff8bfe05c2 CoreFoundation`__CFRunLoopRun + 2018
    frame #11: 0x00007fff8bfdfb98 CoreFoundation`CFRunLoopRunSpecific + 296
    frame #12: 0x00007fff8fb868ff HIToolbox`RunCurrentEventLoopInMode + 235
    frame #13: 0x00007fff8fb86672 HIToolbox`ReceiveNextEventCommon + 431
    frame #14: 0x00007fff8fb864b3 HIToolbox`_BlockUntilNextEventMatchingListInModeWithFilter + 71
    frame #15: 0x00007fff852495a5 AppKit`_DPSNextEvent + 1000
    frame #16: 0x00007fff85248d79 AppKit`-[NSApplication nextEventMatchingMask:untilDate:inMode:dequeue:] + 139
    frame #17: 0x00007fff8523cd53 AppKit`-[NSApplication run] + 594
    frame #18: 0x000000010093cad0 iTunes`___lldb_unnamed_function48166$$iTunes + 360
    frame #19: 0x000000010037c7e0 iTunes`___lldb_unnamed_function14308$$iTunes + 52

Now we know that the function iTunes`___lldb_unnamed_function35905$$iTunes is the one that calls exit.
So we’ll just prevent this, by adding another breakpoint:

(lldb) b iTunes`___lldb_unnamed_function35905$$iTunes
Breakpoint 2: where = iTunes`___lldb_unnamed_function35905$$iTunes, address = 0x00000001007761d2

The breakpoint is hit when iTunes is run again, and we'll simply return immediately, without executing the code that leads to the exit call:

(lldb) thread return
(lldb) c

Unfortunately, the breakpoint is hit again and again.
Looking closer at the backtrace, we can see:

  • __CFRunLoopDoTimer
  • __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__

It means that the function that checks if iTunes is running inside a debugger is repeatedly called at a specific time interval, using a CoreFoundation timer.

Fortunately, we can add a command to our breakpoint, so it will automatically returns when the breakpoint is hit:

(lldb) br command add 2
Enter your debugger command(s).  Type 'DONE' to end.
> thread return
> c
> DONE

Now we can run iTunes again.
The breakpoint will be hit quite a few times, but our command will just return and continues the program’s execution normally.

But we’re not done yet…

At some point, iTunes will crash:

Process 49840 stopped
* thread #1: tid = 0x8b7a4, 0x00007fff8c024483 CoreFoundation`__CFRunLoopDoTimer + 1059, name = 'iTunes main', queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x0)
    frame #0: 0x00007fff8c024483 CoreFoundation`__CFRunLoopDoTimer + 1059
CoreFoundation`__CFRunLoopDoTimer + 1059:
-> 0x7fff8c024483:  leaq   -0x163c94f7(%rip), %rax   ; __CF120290
   0x7fff8c02448a:  movb   $0x1, (%rax)
   0x7fff8c02448d:  leaq   -0x163c94ff(%rip), %rax   ; __CF120293
   0x7fff8c024494:  cmpb   $0x0, (%rax)

EXC_BAD_ACCESS, meaning we’ve got a segmentation fault somewhere...

So what can we do now? Looks like there’s now way to have iTunes running from here…

Well, actually there is.
A segmentation fault means that the software is trying to access a portion of memory it doesn’t own.
Usually, this results in a crash, but it doesn’t mean the software and the system can’t sometimes recover from it.

So let’s try to ignore the segmentation fault, by returning from where we are and continuing the program’s execution as if nothing happened:

(lldb) thread return
(lldb) c
Process 49840 resuming

That’s it…
iTunes resumes its execution normally, and never calls again the function that prevented it to run in LLDB.
You are now free to work with iTunes in LLDB as usual... : )

Comments

Author
DJ
Date
09/28/2014 01:46
I tried this but it doesn't work. Could you provide a solution for iTunes 12.1.2? Please?

Thanks
Author
Macmade
Date
09/28/2014 01:46
The exact solution does indeed not work with latest iTunes build. Different builds will have different functions, so you may need to adapt quite a bit.

But that being said, the exact same procedure does work with latest iTunes versions. Simply follow the entire procedure, breaking on _Exit, and then breaking on functions displayed on the backtrace, returning directly as explained.

At the time I wrote this article, the function was called iTunes`___lldb_unnamed_function35905$$iTunes.

In latest iTunes build (12.1.2.27), you'll simply need to break on iTunes`___lldb_unnamed_function3677$$iTunes instead.