Reverse Engineering the XignCode Anti-Cheat Library | XEM and XEL File Analysis

Overview

I recently downloaded and started playing the game Boxing Star. I decided to take a peek under the hood to checkout the game's internals. Within the application's lib directory, I found the armeabi-v7a shared-library libxigncode.so, which immediately peaked my interested. After dicking around in IDA and a few Google searches, it was pretty clear to me that this was probably an anti-cheat library. So here we are in another blog series where I pretend to know what I am doing.

XEM and XEL File Analysis

The java portion of the XignCode library is contained in the com.wellbia.xigncode package.

wellbia_package.png

The entry point into this library is the initializeXigncode() method.

.method protected initializeXigncode()I
          .registers 4
00000000  invoke-static       XigncodeClient->getInstance()XigncodeClient
00000006  move-result-object  v0
00000008  sget-object         v1, XigncodeActivity->mXigncodeLicense:String
0000000C  sget-object         v2, XigncodeActivity->mXigncodeParam:String
00000010  invoke-virtual      XigncodeClient->initialize(Activity, String, String, XigncodeClientSystem$Callback)I, v0, p0, v1, v2, p0
00000016  move-result         v0
00000018  if-eqz              v0, :1E
:1C
0000001C  return              v0
:1E
0000001E  const/4             v0, 0
00000020  goto                :1C
.end method

After a few subsequent calls, the library bridges into JNI through the XigncodeClientSystem.ZCWAVE.InitializeEx and XigncodeClientSystem.ZCWAVE.Initialize methods.

call_flow.png

After digging into the initialization functions, I noticed quite a bit calls to stat and fopen, so I became interested in the files being read during this stage. I wrote a basic Frida script to hook each of these functions and print the path to the target file. Some of the things that immediately caught my eye were the following.

/sdcard/Android/data/com.ftt.boxingstar.gl.aos/files/xigncode/xtmp.xem
/data/user/0/com.ftt.boxingstar.gl.aos/xigncode/ynina.xem
/data/user/0/com.ftt.boxingstar.gl.aos/xigncode/xnina.xem
/data/user/0/com.ftt.boxingstar.gl.aos/xigncode/xmag.xem
/data/user/0/com.ftt.boxingstar.gl.aos/xigncode/xmag.xem
/data/user/0/com.ftt.boxingstar.gl.aos/xsariel.xem
/sdcard/Android/data/com.ftt.boxingstar.gl.aos/files/xigncode/xtmp.xem
/data/user/0/com.ftt.boxingstar.gl.aos/xigncode/ynina.xem
/data/user/0/com.ftt.boxingstar.gl.aos/xigncode/ynina.xem
/data/user/0/com.ftt.boxingstar.gl.aos/xigncode/xmag.xem
/data/user/0/com.ftt.boxingstar.gl.aos/xigncode/ynina.xem
/data/user/0/com.ftt.boxingstar.gl.aos/xigncode/xmag.xem
/data/user/0/com.ftt.boxingstar.gl.aos/xigncode/xmag.xem
/data/user/0/com.ftt.boxingstar.gl.aos/xraphael.xem
/sdcard/Android/data/com.ftt.boxingstar.gl.aos/files/xigncode/xtmp.xem
/data/user/0/com.ftt.boxingstar.gl.aos/xigncode/xmag.xem

I pulled xraphael.xmem file from the application's data directory and found that it was actually an ELF binary.

╭─rotlogix@carcossa ~/Downloads
╰─$ file xraphael.xem
xraphael.xem: ELF 32-bit LSB shared object, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /system/bin/linker, stripped

The library xraphael.xmem contained the following exports that looked mad interesting.

zcwave.png

I also pulled xsariel.xem and found that it was also an ELF binary.

╭─rotlogix@carcossa ~/Downloads
╰─$ file xsariel.xem
xsariel.xem: ELF 32-bit LSB shared object, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /system/bin/linker, stripped

After throwing the library into IDA, it also had the same interesting exports. After checking the application's process mappings I saw that only the xraphael.xem library was actually mapped into memory.

maps.png

After digging around in the main library libxigncode.so, I found where the ZCWAVE_SendCommand was actually being referenced.

call_zcwave_send_command.png

When examining the cross-references to the function that contained the call to the ZCWAVE_SendCommand function, I noticed that the originating function was something I had notated earlier in my reverse engineering efforts.

cross_references_to_zwave_send_command.png

The ILLEGAL_PROGRAM_DETECTED function is called during the initialization phase of the main library. My assumption early on was that it was trying to perform some anti-debugging measures. However, once I was able to inject Frida into the process, I stopped caring about what it was attempting to do all together. Regardless, I now know that the ZCWAVE_SendCommand function is called during initialization. At this point I was really starting to think this was some sort of custom protocol.

We still have a few 'xem' libraries to checkout.

/data/data/com.ftt.boxingstar.gl.aos/xigncode # ls -la
total 24552
drwx------ 2 u0_a124 u0_a124    4096 2018-07-21 18:18 .
drwx------ 9 u0_a124 u0_a124    4096 2018-07-21 18:18 ..
-rw------- 1 u0_a124 u0_a124 8376320 2018-07-21 19:42 xmag.xem
-rw------- 1 u0_a124 u0_a124 8376320 2018-07-21 18:18 xnina.xem
-rw------- 1 u0_a124 u0_a124 8376320 2018-07-21 18:18 ynina.xem

After pulling and examining the xmag.xem file, it appeared to only contain data. The xnina.xem and ynina.xem also appeared to be the same.

$ file xmag.xem
xmag.xem: data
$ hexdump -n40 xmag.xem
0000000 00 00 00 00 7d a8 54 be b3 72 73 74 50 75 73 74
0000010 29 11 1d 15 7c 0d 16 19 53 75 73 74 54 75 73 74
0000020 55 75 73 74 4a 2a c5 74
0000028

When I opened up the xmag.xem file in HexFiend, there were a ton of strings that lead me to believe this was also a shared-library which was potentially packed in some way.

strings.png

The xel file I was able to pull from the SDCARD was also just data.

/sdcard/Android/data/com.ftt.boxingstar.gl.aos/files/xigncode # ls -la
total 2828
drwxrwx--x 2 u0_a124 sdcard_rw    4096 2018-07-21 18:18 .
drwxrwx--x 9 u0_a124 sdcard_rw    4096 2018-07-21 18:19 ..
-rw-rw---- 1 u0_a124 sdcard_rw 2880000 2018-07-21 20:25 xigncode.xel
-rw-rw---- 1 u0_a124 sdcard_rw       0 2018-07-21 19:42 xtmp.xem

Conclusion

At this point I've gathered the following information

  • *.xem files can either be ELF binaries or just pure data
  • *.xel files appear to just contain pure data
  • The ZCWAVE_SendCommand function is really interesting and is called from the main library during initilization
  • The anti-cheat library might be using a custom protocol for communication with the game server
  • Only one *.xem appears to mapped into memory once the application has finished booting

At this point I am probably going to head back into the libxigncode.so to investigate further. Initially when I decompiled the application, I was looking at all of the shared-libraries it included.

drwxr-xr-x  18 rotlogix  staff       576 Jul 21 20:51 .
drwxr-xr-x   4 rotlogix  staff       128 Jul 21 12:00 ..
-rw-r--r--   1 rotlogix  staff     40116 Jul 21 12:00 libAnalytics.so
-rw-r--r--   1 rotlogix  staff   1291436 Jul 21 12:00 libApp.so
-rw-r--r--   1 rotlogix  staff     54660 Jul 21 12:00 libMessaging.so
-rw-r--r--@  1 rotlogix  staff   8376320 Jul 21 12:00 libgabriel.so
-rw-r--r--   1 rotlogix  staff   2070484 Jul 21 12:00 libgpg.so
-rw-r--r--   1 rotlogix  staff     19404 Jul 21 12:00 libmain.so
-rw-r--r--   1 rotlogix  staff   3774904 Jul 21 12:00 libmono.so
-rw-r--r--@  1 rotlogix  staff       160 Jul 21 12:00 libpickle.so
-rw-r--r--   1 rotlogix  staff  18281824 Jul 21 12:00 libunity.so
-rw-r--r--   1 rotlogix  staff   1470520 Jul 21 12:00 libxigncode.so

The libpickle.so and libgabriel.so libraries were just data.

file libgabriel.so
libgabriel.so: data

This makes me think they might potentially be packed. When my Frida script was hooking fopen, I did notice they were being opened, but they never appeared in the game's processing mappings, so maybe they are not actually libraries.

~ BW

Reverse Engineering Unity Based Android Games - Part One

Overview

Lately, I have been really interested in reverse engineering Android games for fun. There is a large part of me that loves to reverse engineer software without any type of security context implied. So, I've decided to make a short blog series from my efforts around this project. After some cursory ganders into various popular Android games, I saw that many of these games were being developed on top of the Unity platform. So I decided to look into how Android games utilizing Unity actually worked under the hood. I ended up choosing Temple Run as my game of choice for this project.

temple_run.png

Before I get into this series, I want to apologize at potentially the lack of predicate information provided. I hope if the reader does not understand something, they are motivated enough to backfill their own knowledge before continuing. There is also a chance that this series just becomes a random non-coherent dump of reverse engineering output, so tailor your expectations accordingly.

Activity Flow

Ritualistically, the first thing I also do when digging into an Android application is to checkout the manifest. Basically, I am just trying to get the lay of the land and identify components that look interesting.

ImangiUnityProxyActivity

<activity android:configChanges="0x40002fff" android:label="@string/app_name" android:launchMode="2" android:name="com.imangi.unityactivity.ImangiUnityProxyActivity" android:screenOrientation="1">
  <intent-filter>
     <action android:name="android.intent.action.MAIN" />
     <category android:name="android.intent.category.LAUNCHER" />
     <category android:name="android.intent.category.LEANBACK_LAUNCHER" />
  </intent-filter>
  <meta-data android:name="unityplayer.UnityActivity" android:value="true" />
</activity>

The Temple Run application's com.imangi.unityactivity.ImangiUnityProxyActivity contains a <meta-data/> tag named unityplayer.UnityActivity, which made me think that this is probably the origination of important initialization points within the application. The com.imangi.unityactivity.ImangiUnityProxyActivity component's onCreate method had two potential code paths of interest.

00000024  sget-object         v2, ImangiUnityProxyActivity->ImangiNativeActivityClassName:String
00000028  invoke-direct       ImangiUnityProxyActivity->CreateImangiUnityActivity(String)Z, p0, v2
0000002E  move-result         v0
:30
00000030  if-nez              v0, :3E
:34
00000034  sget-object         v2, ImangiUnityProxyActivity->ImangiActivityClassName:String
00000038  invoke-direct       ImangiUnityProxyActivity->CreateImangiUnityActivity(String)Z, p0, v2
:3E

First there is an attempt to call the CreateImangiUnityActivity method with the ImangiNativeActivity class name as an argument. If that does not succeed, the method is called again, but with the ImangiActivity class name as an argument instead.

The CreateImangiUnityActivity method really only has one purpose and that is to launch the activity from the class name argument it was passed from OnCreate.

.method private CreateImangiUnityActivity(String)Z
          .registers 7
:0
00000000  new-instance        v2, Intent
00000004  invoke-static       Class->forName(String)Class, p1
0000000A  move-result-object  v3
0000000C  invoke-direct       Intent-><init>(Context, Class)V, v2, p0, v3
00000012  const/high16        v3, 0x00010000
00000016  invoke-virtual      Intent->addFlags(I)Intent, v2, v3
0000001C  invoke-virtual      ImangiUnityProxyActivity->getIntent()Intent, p0
00000022  move-result-object  v3
00000024  invoke-virtual      Intent->getExtras()Bundle, v3
0000002A  move-result-object  v1
0000002C  if-eqz              v1, :36
:30
00000030  invoke-virtual      Intent->putExtras(Bundle)Intent, v2, v1
:36
00000036  invoke-virtual      ImangiUnityProxyActivity->startActivity(Intent)V, p0, v2

ImangiUnityNativeProxyActivity

The ImangiUnityNativeProxyActivity extends the UnityPlayerNativityActivity class. All of the component's activity lifecycle methods are proxied through the ImangiUnityActivityHelper class. For example:

  • onActivityResult
  • onDestroy
  • onPause
  • onRestart
  • onResume
.method public onResume()V
          .registers 2
00000000  const-string        v0, "onResume"
00000004  invoke-static       ImangiUnityActivity->LogMessage(String)V, v0
0000000A  invoke-super        UnityPlayerNativeActivity->onResume()V, p0
00000010  iget-object         v0, p0, ImangiUnityNativeActivity->_ImangiHelper:ImangiUnityActivityHelper
00000014  invoke-virtual      ImangiUnityActivityHelper->onResume()V, v0
0000001A  return-void
.end method

The ImangiUnityActivityHelper class is a reflection wrapper in order to proxy calls to Unity's activities.

ImangiUnityNativityActivity

.method public onKeyDown(I, KeyEvent)Z
          .registers 5
00000000  const-string        v1, "onKeyDown"
00000004  invoke-static       ImangiUnityActivity->LogMessage(String)V, v1
0000000A  iget-object         v1, p0, ImangiUnityNativeActivity->_ImangiHelper:ImangiUnityActivityHelper
0000000E  invoke-virtual      ImangiUnityActivityHelper->onKeyDown(I, KeyEvent)Z, v1, p1, p2
00000014  move-result         v0
00000016  if-eqz              v0, :1C
:1A
0000001A  return              v0
:1C
0000001C  invoke-super        UnityPlayerNativeActivity->onKeyDown(I, KeyEvent)Z, p0, p1, p2
00000022  move-result         v0
00000024  goto                :1A
.end method

ImangiUnityActivityHelper

.method public onKeyDown(I, KeyEvent)Z
          .registers 4
00000000  const-string        v0, "onKeyDown"
00000004  invoke-static       ImangiUnityActivity->LogMessage(String)V, v0
0000000A  iget-object         v0, p0, ImangiUnityActivityHelper->Methods_onKeyDown:ArrayList
0000000E  invoke-virtual      ImangiUnityActivityHelper->CallStaticKeyMethods_(ArrayList, I, KeyEvent)Z, p0, v0, p1, p2
00000014  move-result         v0
00000016  return              v0
.end method

Here is a rough diagram of the initial 'activity' flow:

activity_flow.png

Native

In my initial exploration of the Temple Run application, I wanted to know if Unity games where predominantly implemented natively.

╭─rotlogix@carcossa ~/Downloads/Temple Run_v1.8.0_apkpure.com/lib/armeabi-v7a
╰─$ ls -la
total 388456
drwxr-xr-x  14 rotlogix  staff        448 Jun 10 15:08 .
drwxr-xr-x   5 rotlogix  staff        160 Jun  5 22:23 ..
-rw-r--r--   1 rotlogix  staff      38244 Jun  5 22:23 libadcolony.so
-rw-r--r--   1 rotlogix  staff     935488 Jun  5 22:23 libjs.so
-rw-r--r--   1 rotlogix  staff      19404 Jun  5 22:23 libmain.so
-rw-r--r--   1 rotlogix  staff    3762616 Jun  5 22:23 libmono.so
-rw-r--r--   1 rotlogix  staff   16805288 Jun  5 22:23 libunity.so

Based on the shared-libraries included within the Temple Run game, I had the following assumptions.

  • libmono.so is probably the Mono Runtime
  • libmain.so is probably the entry point into everything native
  • If we are dealing with the Mono Runtime, the libunity.so is probably loading DLL(s) somewhere within the assets or resources directories

I wasn't that far off in my initial assessment. Within assets/bin/Data/Managed, you can find a few DLL(s), which I imagined probably implemented most of Unity's core functionality.

╭─rotlogix@carcossa ~/Downloads/Temple Run_v1.8.0_apkpure.com/assets/bin/Data/Managed
╰─$ ls -la
total 7704
drwxr-xr-x   13 rotlogix  staff      416 Jun  5 22:23 .
drwxr-xr-x  461 rotlogix  staff    14752 Jun  6 13:06 ..
-rw-r--r--    1 rotlogix  staff   479232 Jun  5 22:23 Assembly-CSharp-firstpass.dll
-rw-r--r--    1 rotlogix  staff   538112 Jun  5 22:23 Assembly-CSharp.dll
-rw-r--r--    1 rotlogix  staff    29696 Jun  5 22:23 System.Core.dll
-rw-r--r--    1 rotlogix  staff   307712 Jun  5 22:23 System.Xml.dll
-rw-r--r--    1 rotlogix  staff   129536 Jun  5 22:23 System.dll
-rw-r--r--    1 rotlogix  staff    18432 Jun  5 22:23 UnityEngine.Advertisements.Android.dll
-rw-r--r--    1 rotlogix  staff   216576 Jun  5 22:23 UnityEngine.Networking.dll
-rw-r--r--    1 rotlogix  staff    28672 Jun  5 22:23 UnityEngine.PlaymodeTestsRunner.dll
-rw-r--r--    1 rotlogix  staff   231936 Jun  5 22:23 UnityEngine.UI.dll
-rw-r--r--    1 rotlogix  staff   504320 Jun  5 22:23 UnityEngine.dll
-rw-r--r--    1 rotlogix  staff  1439744 Jun  5 22:23 mscorlib.dll

libmain.so

So at this point, I feel like I am gaining some traction. However, I need to take a step back because there are a lot of moving parts. Since my assumption is that libmain.so is probably the entry point for native operations, I decided to start focusing my efforts there. First, I simply needed to identify the class that was loading libmain.so. Ideally, that class is within the activity data flow described above. So, I wrote a simple Jeb script, that would identify all methods that call into Android's load library API methods. That pointed me into the UnityPlayer class, which is at the very end of the activity flow chain.

.method static constructor <clinit>()V
          .registers 1
00000000  const/4             v0, 0
00000002  sput-object         v0, UnityPlayer->currentActivity:Activity
00000006  new-instance        v0, h
0000000A  invoke-direct       h-><init>()V, v0
00000010  invoke-virtual      h->a()Z, v0
00000016  const/4             v0, 0
00000018  sput-boolean        v0, UnityPlayer->m:Z
0000001C  const-string        v0, "main"
00000020  invoke-static       UnityPlayer->loadLibraryStatic(String)Z, v0
00000026  move-result         v0
00000028  sput-boolean        v0, UnityPlayer->m:Z
0000002C  return-void
.end method

After opening libmain.so in IDA, I noticed there wasn't very much functionality, which maybe meant that it just performs some base initialization operations. The library did implement the JNI_OnLoad function, which is called by the virtual machine when a library is loaded via a load library API i.e. System.loadLibrary. Implementing this function in your library is a good way to get immediate control of execution after your library is loaded and is a common practice when developing Android applications that rely on the Java Native Interface.

JNI_OnLoad

jni_onload.png

The first argument passed to JNI_OnLoad is a pointer to the JavaVM structure.

struct _JavaVM {
    const struct JNIInvokeInterface* functions;
#if defined(__cplusplus)
    jint DestroyJavaVM()
    { return functions->DestroyJavaVM(this); }
    jint AttachCurrentThread(JNIEnv** p_env, void* thr_args)
    { return functions->AttachCurrentThread(this, p_env, thr_args); }
    jint DetachCurrentThread()
    { return functions->DetachCurrentThread(this); }
    jint GetEnv(void** env, jint version)
    { return functions->GetEnv(this, env, version); }
    jint AttachCurrentThreadAsDaemon(JNIEnv** p_env, void* thr_args)
    { return functions->AttachCurrentThreadAsDaemon(this, p_env, thr_args); }
#endif /*__cplusplus*/
};

The first member within the JavaVM structure is a pointer to the JNIInvokeInterface structure.

struct JNIInvokeInterface {
    void*       reserved0;
    void*       reserved1;
    void*       reserved2;
    jint        (*DestroyJavaVM)(JavaVM*);
    jint        (*AttachCurrentThread)(JavaVM*, JNIEnv**, void*);
    jint        (*DetachCurrentThread)(JavaVM*);
    jint        (*GetEnv)(JavaVM*, void**, jint);
    jint        (*AttachCurrentThreadAsDaemon)(JavaVM*, JNIEnv**, void*);
};

The JNIInvokeInterface structure contains important JNI functions for performing operations within the virtual machine. If we take a look at the following instructions, it should be clear that this code is looking up a function within the JNIInvokeInterface and branching to it.

.text:000026C0                 LDR             R1, [R0] ; Java_VM
.text:000026C4                 LDR             R3, [R1,#16] ; R1 := JNIInvokeInterface
.text:000026C8                 ADD             R1, SP, #0x18+var_14
.text:000026CC                 BLX             R3      ; AttachCurrentThread

The second argument passed to the AttachCurrentThread function is a local variable, which will be filled out with the content of the JNIEnv structure. This structure is of type JNINativeInterface, which is a table including pointers to all of the JNI functions that can be utilized when importing jni.h into your Android NDK project. I ended up extracting the definition for that structure into a new header and loaded into IDA. With this structure now defined, and we should be able to clearly understand what's going on in JNI_OnLoad.

LDR             R0, [SP,#0x18+var_14] ; JNIEnv**
LDR             R2, =(_GLOBAL_OFFSET_TABLE_ - 0x26E8)
LDR             R6, =(aComUnity3dPlay - 0x5578)
LDR             R1, [R0] ; JNIEnv
ADD             R5, PC, R2 ; _GLOBAL_OFFSET_TABLE_
LDR             R2, [R1,#JavaNativeInterface.FindClass]
ADD             R1, R6, R5 ; "com/unity3d/player/NativeLoader"
BLX             R2      ; FindClass
MOV             R1, R0
LDR             R0, [SP,#0x18+var_14]
LDR             R3, =(off_5514 - 0x5578) ; load
LDR             R2, [R0]
LDR             R4, [R2,#JavaNativeInterface.RegisterNatives]
ADD             R2, R3, R5 ; off_5514
MOV             R3, #2
BLX             R4
CMP             R0, #0
BLT             loc_2728

Essentially the JNI_OnLoad function is calling RegisterNatives in order to register the native method load within the com.unity3d.player.NativeLoader class.

static jint RegisterNatives(JNIEnv* env, jclass java_class, const JNINativeMethod* methods, jint method_count)

RegisterNatives third argument is essentially a table of type JNINativeMethod. In the following snippet you can see the function iterating over the elements within this table.

[Snippet]
for (jint i = 0; i < method_count; ++i) {
    const char* name = methods[i].name;
    const char* sig = methods[i].signature;
    const void* fnPtr = methods[i].fnPtr;
[Snippet]

Going back to IDA, I can see the assembly referencing an offset into the data section which has been annotated as 'load'.

LDR             R3, =(off_5514 - 0x5578) ; load
LDR             R2, [R0]
LDR             R4, [R2,#JavaNativeInterface.RegisterNatives]
ADD             R2, R3, R5 ; off_5514
MOV             R3, #2
BLX             R4

This offset is actually the beginning of the JNINativeMethod structure for the NativeLoader class method > load.

.data:00005514 off_5514        DCD aLoad               ; DATA XREF: JNI_OnLoad+58↑o
.data:00005514                                         ; .text:off_2758↑o
.data:00005514                                         ; "load"
.data:00005518                 DCD aLjavaLangStrin     ; "(Ljava/lang/String;)Z"
.data:0000551C                 DCD sub_2760
  • The first member in the structure is the method's name
  • The second member in the structure is the method's signature
  • The third member in the structure is the method's native function pointer

Now that I have the load method's native function address, I can pivot my reverse engineering focus.

Thanks for reading, stay tuned for part two!

Reverse Engineering The MacOS AirPlayUIAgent - Part 001

Overview

To be honest with you, I have no real purpose for this blog series. I plan on writing about my past time efforts to reverse engineer random things I find on my laptop. Maybe I learn something, maybe you learn something, maybe we don't learn anything, maybe we fuck off and join the circus.

I've dicked around with a lot AirPlay shit in the past, and found the iOS implementations pretty interesting, so I decided to checkout what was going on locally.

AirPlayUIAgent

Checking my own process listing, I could see that AirPlayUIAgent was kicked off by launchd.

ps -x | grep AirPlayUIAgent
  363 ??         0:01.52 /System/Library/CoreServices/AirPlayUIAgent.app/Contents/MacOS/AirPlayUIAgent --launchd

This could also be validated by using the launchctl command to list information about the available services.

launchctl list | grep AirPlay 363    0    com.apple.AirPlayUIAgent

I remebered that on the iOS side there was a ton of XPC shit going on, and I'm always pretty interested in anything that has potentially implemented their own XPC services, so that is where I decided to start diving into first.

XPC

If you're still following along, at this point I've got the AirPlayUIAgent executable loaded into Hopper. I attempted to write a Hopper script that will enumerate all of the executable's procedures, check for out going function calls, and determine if those function calls were to any of the c-based XPC functions. Some of the relevant functions can be found here https://developer.apple.com/documentation/xpc/xpc_services_connection.h

try:
    document = Document.getCurrentDocument()
    segment = document.getSegment(0)
    procedure_count = segment.getProcedureCount()
    procedures = list()
    for i in xrange(procedure_count):
        procedures.append(segment.getProcedureAtIndex(i))
    for p in procedures:
        callees = p.getAllCallees()
        for i in callees:
            if i:
                name = segment.getNameAtAddress(i.toAddress())
                if name and "xpc" in name:
                    print "[+] procedure entry point -> {0} : label -> {1}".format(hex(p.getEntryPoint()), name)
                else:
                    continue
            else:
                continue
except Exception as e:
    raise e

The script produced the following unsorted output.

[+] procedure entry point -> 0x1000022e9L : label -> imp___stubs__xpc_set_event_stream_handler
[+] procedure entry point -> 0x1000022e9L : label -> imp___stubs__xpc_set_event_stream_handler
[+] procedure entry point -> 0x1000022e9L : label -> imp___stubs__xpc_connection_create_mach_service
[+] procedure entry point -> 0x1000022e9L : label -> imp___stubs__xpc_connection_set_event_handler
[+] procedure entry point -> 0x1000022e9L : label -> imp___stubs__xpc_connection_resume
[+] procedure entry point -> 0x100002716L : label -> imp___stubs__xpc_connection_cancel
[+] procedure entry point -> 0x100002716L : label -> imp___stubs__xpc_release
[+] procedure entry point -> 0x100002973L : label -> imp___stubs__xpc_dictionary_get_uint64
[+] procedure entry point -> 0x1000032ffL : label -> imp___stubs__xpc_dictionary_get_uint64
[+] procedure entry point -> 0x1000034c0L : label -> imp___stubs__xpc_transaction_begin
[+] procedure entry point -> 0x1000034c0L : label -> imp___stubs__xpc_transaction_end
[+] procedure entry point -> 0x100004811L : label -> imp___stubs__xpc_dictionary_get_string
[+] procedure entry point -> 0x100006b49L : label -> imp___stubs__xpc_get_type
[+] procedure entry point -> 0x100006b49L : label -> imp___stubs__xpc_connection_has_entitlement
[+] procedure entry point -> 0x100006b49L : label -> imp___stubs__xpc_connection_get_pid
[+] procedure entry point -> 0x100006b49L : label -> imp___stubs__xpc_connection_set_event_handler
[+] procedure entry point -> 0x100006b49L : label -> imp___stubs__xpc_connection_resume
[+] procedure entry point -> 0x100006b49L : label -> imp___stubs__xpc_connection_get_pid
[+] procedure entry point -> 0x100006c7dL : label -> imp___stubs__xpc_dictionary_create_reply
[+] procedure entry point -> 0x100006c7dL : label -> imp___stubs__xpc_get_type
[+] procedure entry point -> 0x100006c7dL : label -> imp___stubs__xpc_dictionary_get_string
[+] procedure entry point -> 0x100006c7dL : label -> imp___stubs__xpc_dictionary_get_data
[+] procedure entry point -> 0x100006c7dL : label -> imp___stubs__xpc_dictionary_get_data
[+] procedure entry point -> 0x100006c7dL : label -> imp___stubs__xpc_dictionary_get_data
[+] procedure entry point -> 0x100006c7dL : label -> imp___stubs__xpc_dictionary_set_int64
[+] procedure entry point -> 0x100006c7dL : label -> imp___stubs__xpc_connection_send_message
[+] procedure entry point -> 0x100006c7dL : label -> imp___stubs__xpc_release
[+] procedure entry point -> 0x100006c7dL : label -> imp___stubs__xpc_dictionary_set_data
[+] procedure entry point -> 0x100006c7dL : label -> imp___stubs__xpc_dictionary_get_bool
[+] procedure entry point -> 0x100006c7dL : label -> imp___stubs__xpc_dictionary_set_string
[+] procedure entry point -> 0x100006c7dL : label -> imp___stubs__xpc_dictionary_set_data
[+] procedure entry point -> 0x100006c7dL : label -> imp___stubs__xpc_dictionary_set_data
[+] procedure entry point -> 0x100006c7dL : label -> imp___stubs__xpc_dictionary_get_string
[+] procedure entry point -> 0x100006c7dL : label -> imp___stubs__xpc_dictionary_get_data
[+] procedure entry point -> 0x100006c7dL : label -> imp___stubs__xpc_dictionary_get_data
[+] procedure entry point -> 0x100006c7dL : label -> imp___stubs__xpc_dictionary_set_data
[+] procedure entry point -> 0x100006c7dL : label -> imp___stubs__xpc_connection_cancel

I now can identify the subroutines that are calling c-based XPC functions. For the most part my assumption is now that the AirPlayUIAgent is implementing one or more XPC services.

Scripting Hopper

For those of you who are interested in writing scripts for Hopper and have not tried yet, there is dick for documentation on the interwebz. I would suggest just diving into their Python API implementation -> /Applications/Hopper Disassembler v4.app/Contents/Resources/hopper_api.py. This is was all I really needed to churn something out.

Alright back to it! Apple's documentation states that the xpc_connection_create_mach_service function is used to create a remote service in which clients may connect.

In the scripts output, the subroutine starting at 0x1000022e9 is calling the xpc_connection_create_mach_service function.

[+] procedure entry point -> 0x1000022e9L : label -> imp___stubs__xpc_connection_create_mach_service

After I navigated to that address in Hopper, it brought me to following ObjC method -> -[AirPlayUIAgent applicationDidFinishLaunching:]

Right before the call to xpc_connection_create_mach_service, in -[AirPlayUIAgent applicationDidFinishLaunching:] something is loaded into rdi at 0x100008bc2, which will be the first argument to the function.

lea        rdi, qword [0x100008bc2] imp___stubs__xpc_connection_create_mach_service
mov        edx, 0x1     ; argument "flags" for method imp___stubs__xpc_connection_create_mach_service
mov        rsi, rax     ; argument "targetq" for method imp___stubs__xpc_connection_create_mach_service
call       imp___stubs__xpc_connection_create_mach_service

That something, is the string com.apple.AirPlayAgent.xpc, which is the name the remote service is registered under.

0000000100008bc2   db   "com.apple.AirPlayAgent.xpc", 0

Checking Entitlements

In the script's ouput there is also a reference to the xpc_connection_has_entitlement function. This function is used to check the calling client's connection for a specific entitlement.

lea        rsi, qword [0x100008bc2] ; "com.apple.AirPlayAgent.xpc"
mov        rdi, rbx
call       imp___stubs__xpc_connection_has_entitlement
mov        r14b, al
test       r14b, r14b
jne        loc_100006c29

So the AirPlayUIAgent is checking whether or not the client has the com.apple.AirPlayAgent.xpc entitlement in order to proceed with the xpc service connection. The subroutine where this check is performed was most likely setup or called through xpc_connection_set_event_handler. A great of example of this would be the following.

http://nshipster.com/inter-process-communication/

static void connection_handler(xpc_connection_t peer) {
    xpc_connection_set_event_handler(peer, ^(xpc_object_t event) {
        peer_event_handler(peer, event);
        // peer_event_handler could handle the entitlement checks
    });

    xpc_connection_resume(peer);
}

int main(int argc, const char *argv[]) {
   xpc_main(connection_handler);
   exit(EXIT_FAILURE);
}

The rsi register is gonna hold the handler to be called when a event is received.

lea        rsi, qword [0x10000b7c0] ; argument "handler" for method imp___stubs__xpc_connection_set_event_handler
mov        rdi, rax     ; argument "connection" for method imp___stubs__xpc_connection_set_event_handler
call       imp___stubs__xpc_connection_set_event_handler

If we checkout the handler's memory at 0x10000b7c0, the fourth item into the structure - 0x100006b49 - is the function that is used to check the caller's entitlements

000000010000b7c0         dq         __NSConcreteGlobalBlock                     ; DATA XREF=-[AirPlayUIAgent applicationDidFinishLaunching:]+540
000000010000b7c8         dd         0x50000000
000000010000b7cc         dd         0x00000000
000000010000b7d0         dq         0x0000000100006b49
000000010000b7d8         dq         0x000000010000ba10

This function appears to allow clients that do not have the special entitlement to connnect.

The last block that any given descision can make in an if else manner is the following.

loc_100006c29:
mov        rax, qword [__NSConcreteStackBlock_10000b040] ; CODE XREF=sub_100006b49+56, sub_100006b49+83, sub_100006b49+113, sub_100006b49+152, sub_100006b49+178
mov        qword [rbp+var_40], rax
mov        dword [rbp+var_38], 0xc2000000
mov        dword [rbp+var_34], 0x0
lea        rax, qword [sub_100006c7d]
mov        qword [rbp+var_30], rax
lea        rax, qword [0x10000ba30]
mov        qword [rbp+var_28], rax
mov        qword [rbp+var_20], rbx
mov        byte [rbp+var_18], r14b
lea        rsi, qword [rbp+var_40] ; argument "handler" for method imp___stubs__xpc_connection_set_event_handler
mov        rdi, rbx     ; argument "connection" for method imp___stubs__xpc_connection_set_event_handler
call       imp___stubs__xpc_connection_set_event_handler
mov        rdi, rbx     ; argument "connection" for method imp___stubs__xpc_connection_resume
call       imp___stubs__xpc_connection_resume

There is another call to xpc_connection_set_event_handler.

call       imp___stubs__xpc_connection_set_event_handler

The handler argument being passed to xpc_connection_set_event_handler is function sub_100006c7d.

lea        rax, qword [sub_100006c7d]
mov        qword [rbp+var_30], rax

In this function the binary starts dealing with the c-based XPC data it was sent.

Wrapping Up

So I've uncovered the following things so far about the AIRPlayUIAgent:

  • The AirPlayUIAgent creates an XPC service registered under the name com.apple.AirPlayAgent.xpc
  • Clients need the com.apple.AirPlayAgent.xpc entitlement in order to connect to the XPC service