How do A/B testing platforms replace Objective-C assets on the fly?
Asked Answered
M

2

13

Leanplum, Apptimize and other A/B testing platforms for iOS have the ability to download assets (nib files ,images, etc...) from the web and replace them at runtime.

The naive approach will be to download the new assets and replace them in the resource bundle directory, but it is impossible to write files to the resource directory because of permissions.

The question is, what techniques does these A/B testing platforms use to replace assets at runtime?

EDIT:

After reading the symbols on leanplum static library file (using nm) it seems that they are Swizzling cocoa file system API's.

for example: (an example line from nm -m leanplum.a)

 -[NSBundle(LeanplumExtension) leanplum_URLForResource:withExtension:]

By using otool I can print the implementation:

-[NSBundle(LeanplumExtension) leanplum_URLForResource:withExtension:]:
0000000000000069        pushq   %rbp
000000000000006a        movq    %rsp, %rbp
000000000000006d        pushq   %r15
000000000000006f        pushq   %r14
0000000000000071        pushq   %r13
0000000000000073        pushq   %r12
0000000000000075        pushq   %rbx
0000000000000076        subq    $0x18, %rsp
000000000000007a        movq    %rcx, %rbx
000000000000007d        movq    %rdi, 0xffffffffffffffc8(%rbp)
0000000000000081        movq    %rdx, %rdi
0000000000000084        callq   _objc_retain
0000000000000089        movq    %rax, %r14
000000000000008c        movq    %rbx, %rdi
000000000000008f        callq   _objc_retain
0000000000000094        movq    %rax, 0xffffffffffffffd0(%rbp)
0000000000000098        movq    _originalMainBundle(%rip), %rcx
000000000000009f        movq    "+[NSBundle(LeanplumExtension) leanplum_mainBundle]"(%rcx), %rdi
00000000000000a2        movq    0x4487(%rip), %rsi
00000000000000a9        movq    _objc_msgSend(%rip), %r12
00000000000000b0        movq    %r14, %rdx
00000000000000b3        movq    %rax, %rcx
00000000000000b6        callq   *%r12
00000000000000b9        movq    %rax, %rdi
00000000000000bc        callq   _objc_retainAutoreleasedReturnValue
00000000000000c1        movq    %rax, %r13
00000000000000c4        movq    _skippedFiles(%rip), %rax
00000000000000cb        movq    "+[NSBundle(LeanplumExtension) leanplum_mainBundle]"(%rax), %rbx
00000000000000ce        movq    0x4463(%rip), %rsi
00000000000000d5        movq    %r13, %rdi
00000000000000d8        callq   *%r12
00000000000000db        movq    %rax, %rdi
00000000000000de        callq   _objc_retainAutoreleasedReturnValue
00000000000000e3        movq    %rax, %r15
00000000000000e6        movq    0x4453(%rip), %rsi
00000000000000ed        movq    %rbx, %rdi
00000000000000f0        movq    %r15, %rdx
00000000000000f3        callq   *%r12
00000000000000f6        movb    %al, %bl
00000000000000f8        movq    %r15, %rdi
00000000000000fb        callq   _objc_release
0000000000000100        testb   %bl, %bl
0000000000000102        je      0x115
0000000000000104        movq    %r13, %rdi
0000000000000107        callq   _objc_retain
000000000000010c        movq    %rax, %r15
000000000000010f        movq    0xffffffffffffffd0(%rbp), %rbx
0000000000000113        jmp     0x13b
0000000000000115        movq    0x4414(%rip), %rsi
000000000000011c        movq    0xffffffffffffffc8(%rbp), %rdi
0000000000000120        movq    %r14, %rdx
0000000000000123        movq    0xffffffffffffffd0(%rbp), %rbx
0000000000000127        movq    %rbx, %rcx
000000000000012a        callq   *_objc_msgSend(%rip)
0000000000000130        movq    %rax, %rdi
0000000000000133        callq   _objc_retainAutoreleasedReturnValue
0000000000000138        movq    %rax, %r15
000000000000013b        movq    %r13, %rdi
000000000000013e        callq   _objc_release
0000000000000143        movq    %rbx, %rdi
0000000000000146        callq   _objc_release
000000000000014b        movq    %r14, %rdi
000000000000014e        callq   _objc_release
0000000000000153        movq    %r15, %rdi
0000000000000156        addq    $0x18, %rsp
000000000000015a        popq    %rbx
000000000000015b        popq    %r12
000000000000015d        popq    %r13
000000000000015f        popq    %r14
0000000000000161        popq    %r15
0000000000000163        popq    %rbp
0000000000000164        jmpq    _objc_autoreleaseReturnValue
  1. Can someone verify my findings?
  2. I wonder how they get this entire list of API covered?
  3. What happens if I open an image with fopen or other C lib?
  4. How can I decipher the output of otool?
Matherly answered 18/2, 2014 at 10:28 Comment(2)
Have you tried using Hopper Decompiler? I believe it's purpose made for this kind of reverse engineering.Certificate
@Certificate I used both ida and hopper, without any significant result. but I am not sure I know how to use them...Matherly
B
6

The question is, what techniques does these A/B testing platforms use to replace assets at runtime?

An educated guess is that they use method swizzling to swap the implementations of standard methods (e.g. [NSBundle URLForResource:withExtension:]) with those of their own versions of those methods (e.g. [NSBundle(LeanplumExtension) leanplum_URLForResource:withExtension:]). This means that your code uses the same method that you would otherwise, but you get different behavior -- in this case, the URL that's returned depends on which version of the resource the A/B testing framework decides to present to the user.

I wonder how they get this entire list of API covered?

By working carefully. The number of methods used to load resources isn't unmanageable.

What happens if I open an image with fopen or other C lib?

I expect that the framework wouldn't be able to swap resources in such a case. There are lots of ways to load data, and the framework can't possibly anticipate all of them. But if you're using an A/B framework, you presumably want the resources to be replaced as appropriate for your testing, so it doesn't make a lot of sense to try to defeat the framework.

How can I decipher the output of tool?

For the case you've shown, learn to read assembly language. (otool has a lot of options, though, and they don't all produce assembly.)

Bah answered 24/2, 2014 at 16:19 Comment(2)
thanks, but it seems (from the .a file) like leanplum is not swizzling NSFileManager, only NSBundle. How can it be?Matherly
@ekeren, their implementation of NSBundle will use native APIs including perhaps NSFileManager to present variable behavior as determined by their machine learning. Only the methods that you use for showing a user interface need to be manipulated. It is in the best interest of everyone to keep the number of such swizzled methods limited to a few highly specialized methods.Indecipherable
M
3

As an example, the company Leanplum offers a Visual Interface Editor for iOS and Android: This requires no coding, and Leanplum will automatically detect the elements and allow you to change them. No engineering or app store resubmissions are required.

Let me give you a little bit more high level insights about it:

  1. With installing the iOS or Android SDK in your app, you enable a feature called Visual Editor. While in development mode and with the Website Dashboard open, the SDK sends information about the view hierarchy in real-time to your Browser. The view hierarchy is scanned in a similar way a DOM is built on a regular website.
  2. You can choose any UI element on your app and change the appearance of it in real-time. This works by identifying the exact element in the view tree and sending the changes to the SDK.
  3. This can be achieved by adding custom hooks or a technique called "swizzling". Take a look at this blog post, how it works.

To learn more about the Leanplum Visual Interface Editor, check out leanplum.com. They offer a free 30-day trial.

(Disclaimer: I'm an engineer at Leanplum)

Mahican answered 6/5, 2016 at 6:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.