The LLDB has a memory debugging tool called the Malloc Stack, which allows you to view malloc and free records for a memory address and track where the object was created.

This tool can print out the stack of object creation, but in reverse, it is often necessary to trace the call stack of certain methods. If you can print out the creation of an object at any time, you can find the class and method directly, without spending a lot of time logging and dynamic debugging tracing.

malloc stack

To enable malloc Stack in your own project, check the Malloc Stack option in Product->Scheme->Edit Scheme->Diagnistic.

The effect is as follows.

Test code:

- (IBAction)create:(id)sender {
    NSString *testString = [NSString stringWithFormat:@"string created by %@",self];
    
}
Copy the code

Malloc_info: lldb.macosx.heap malloc_info: lldb.macosx.heap malloc_info: lldb.macosx.heap malloc_info: lldb.macosx.heap malloc_info: lldb.macosx.heap malloc_info: lldb.macosx.heap malloc_info: lldb.macosx.heap malloc_info: lldb.macosx.heap

(lldb) p/x testString
(__NSCFString *) $3 = 0x16eac000 @"string created by <ViewController: 0x16e9d7c0>"
(lldb) commandScript import lldb.macosx.heap // Load lldb.macosx.heap"malloc_info"."ptr_refs"."cstr_refs"."find_variable", and "objc_refs" commands have been installed, use the "--help" options on these commands for detailed help.
(lldb) malloc_info -s 0x16eac000
0x0000000016eac000: malloc(    64) -> 0x16eac000 __NSCFString.NSMutableString.NSString.NSObject.isa
stack[0]: addr = 0x16eac000, type=malloc, frames:
     [0] 0x00000000242948ab libsystem_malloc.dylib`malloc_zone_malloc + 123
     [1] 0x00000000244e3bc1 CoreFoundation`_CFRuntimeCreateInstance + 237
     [2] 0x00000000245a6ffd CoreFoundation`__CFStringCreateImmutableFunnel3 + 1657
     [3] 0x00000000244ee0f7 CoreFoundation`CFStringCreateCopy + 359
     [4] 0x00000000245a725d CoreFoundation`_CFStringCreateWithFormatAndArgumentsAux2 + 89
     [5] 0x0000000024d17dd3 Foundation`-[NSPlaceholderString initWithFormat:locale:arguments:] + 139
     [6] 0x0000000024d17cd1 Foundation`+[NSString stringWithFormat:] + 61
     [7] 0x00000000000d7343 testMallocStack`-[ViewController create:] + 97 at ViewController.m:23:28
     [8] 0x00000000287a5771 UIKit`-[UIApplication sendAction:to:from:forEvent:] + 81
     [9] 0x00000000287a5701 UIKit`-[UIControl sendAction:to:forEvent:] + 65 [10] 0x000000002878d61f UIKit`-[UIControl _sendActionsForEvents:withEvent:] + 447 [11] 0x00000000287a5051 UIKit`-[UIControl touchesEnded:withEvent:] + 617 [12] 0x00000000287a4cbf UIKit`-[UIWindow _sendTouchesForEvent:] + 647 [13] 0x000000002879d5d7 UIKit`-[UIWindow sendEvent:] + 643 [14] 0x000000002876e119 UIKit`-[UIApplication sendEvent:] + 205 [15] 0x000000002876c757 UIKit`_UIApplicationHandleEventQueue + 5135 [16] 0x0000000024599257 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 15 [17] 0x0000000024598e47 CoreFoundation`__CFRunLoopDoSources0 + 455 [18] 0x00000000245971af CoreFoundation`__CFRunLoopRun + 807 [19] 0x00000000244e9bb9 CoreFoundation`CFRunLoopRunSpecific + 517 [20] 0x00000000244e99ad CoreFoundation`CFRunLoopRunInMode +  109 [21] 0x0000000025763af9 GraphicsServices`GSEventRunModal + 161 [22] 0x00000000287d5fb5 UIKit`UIApplicationMain + 145 [23] 0x00000000000d7587testMallocStack`main + 107 at main.m:14:9
     [24] 0x000000002419c873 libdyld.dylib`start + 3
     [25] 0x000000003a9c0001 libsystem_pthread.dylib`_thread + 1
Copy the code

This tool is derived from GDB’s malloc_history, but malloc_history can only be used on emulators, whereas malloc_info can be used on both emulators and real machines. Xcode also adds a new LLDB tool, Memory History. Go to Product->Scheme->Edit Scheme->Diagnistic and check Address Sanitizer for a similar effect.

Use an unofficial version of heap.py

Note that using malloc_info after Xcode8.3 causes the LLDB debugger to crash. There seems to be a bug that has not been fixed. It works fine on Xcode8.2.

So we need to replace the llDB.macosx.heap module that comes with LLDB. Use the unofficial version: heap.py.

LLDB can load custom Pthon scripts. Just type in the LLDB:

commandScript import Address of a Python scriptCopy the code

So after downloading the above heap.py locally, type:

commandScript import/path/LLDB/examples/Darwin heap_find/heap. PyCopy the code

Can.

Enable the Malloc Stack on any app

Address Sanitizer’s Memory History requires recompiling the app, But malloc stack only need to set the environment variable MallocStackLogging before the app start and MallocStackLoggingNoCompact can. When enabled, an.index file is generated in the/TMP directory of the system. The contents of this file are dependent on the app runtime environment, and this file is useless after the process exits.

Now, the question becomes how to set the app’s startup environment variables.

Method one: Execve

That’s the way I started. Use the execve function to run the app binary.

Due to sandbox limitations, you need to give your app root permission to use execve. The steps are as follows.

1. Re-sign ipA

Re-signing requires reverse APP. Because the app content needs to be modified. Re-sign and install it on the jailbroken device.

2. Move the APP to the app directory of the system and modify the permission

Only apps in the system directory have the root permission.

Suppose the app you want to reverse is your_app.app. Move the app system app directory: mv -f/var/containers/Bundle/Application/XXXXXXXXXXXXX/YOUR_APP app/Applications/YOUR_APP app.

Then change the file permissions:

cd /Applications

chown -R root:wheel YOUR_APP.app

chmod 4755 YOUR_APP.app/YOUR_APP

After the SpringBoard is moved, refresh the APP icon with uicache and restart the SpringBoard with killall SpringBoard.

3. Start the APP using the boot program

The ultimate goal is to start the app with execve using a bootloader, setting environment variables before starting.

Start by renaming the original binary: mv your_app. app/YOUR_APP your_app. app/YOUR_APP_Orig.

Then create a bootstrap program and create an iOS project, replacing the contents of main.m with:

int main(int argc, char * argv[]) {
    @autoreleasepool {
        NSString* string = [[NSBundle mainBundle] pathForResource:@"YOUR_APP_Orig"ofType:nil]; Argv [0] = (char*)[string UTF8String]; char *envp[] = {"HOME=/var/root"."LOGNAME=root"."PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/bin/X11:/usr/games"."USER=root"."MallocStackLogging=1"."MallocStackLoggingNoCompact=1"0}; execve([string UTF8String], argv, envp);return0; }}Copy the code

After compiling, take out the binary file, rename it YOUR_APP, and copy it to the /Application/ your_app.app/directory of the jailbroken device.

Set execution permissions for the boot program: chmod +x /Application/ your_app. app/YOUR_APP.

Killall SpringBoard.

This way, every time you start the app, you start it indirectly using the bootstrap.

disadvantages

  • The steps are tedious.
  • Some apps are cumbersome to re-sign.
  • After jailbreaking the system partition capacity is very small, it is easy to fill up, trying to test a larger app is difficult.
  • Can’t usedebugserverWake up the APP and debug the startup process. becauseYOUR_APPandYOUR_APP_OrigThere are two processes. The first one is inexecveExecute and exit.
  • Putting apps in the system directory sometimes causes crashes.

Method 2: DebugServer parameters

Method 1 is really too troublesome, and sometimes it is even more troublesome when the app fails to re-sign. But there’s another, more direct way. Is to use the command of the debugServer.

The debugServer is a dynamic debugging tool. For details, see Introduction to DYNAMIC LLDB Debugging on the IOS platform.

Once installed, Enter debugServer *:1234 on the jailbroken device / var/containers/Bundle/Application / 589822 b6 – a3d BFDA – 4 – A71C – AD0D30BA6077 / WeChat app/WeChat can wake up app for debugging.

The debugServer also has a hidden parameter, env(-env,-e), which is used to set the environment variable of the process:

debugserver *:1234 /var/containers/Bundle/Application/589822B6-BFDA-4A3D-A71C-AD0D30BA6077/WeChat.app/WeChat -env MallocStackLogging=1 -env MallocStackLoggingNoCompact=1

At that time, I thought the debugServer will not have the function of setting environment variables, unexpectedly tried a random -env success. Debugserver. CPP (search g_long_options to find env).

This way, even if the app is not re-signed, it can be debugged directly.

disadvantages

The DebugServer cannot start the debug Extension app because the extension app is dependent on the host app and cannot run alone. In this case, you have to use method 1.

test

Here a re-signature is used, and the symbol table is restored for the wechat test.

For example, find the interface of wechat to check the expression and print out the memory address 0x108795C20:

<MMEmoticonView: 0x108795c20; Frame = (276.25 404.25; 215.5, 215.5); autoresize = LM+RM+TM+BM; layer = <CALayer: 0x170828700>>Copy the code

The first time you use malloc_info you need to import lldb.macosx.heap in LLDB. Here you need to import the unofficial heap.py:

(lldb) commandPath to script import heap.py"malloc_info"."ptr_refs"."cstr_refs"."find_variable", and "objc_refs" commands have been installed, use the "--help" options on these commands for detailed help.
Copy the code

Create stack with malloc_info print:

(lldb) malloc_info -s 0x108795c20
0x0000000108795c20: malloc(   480) -> 0x108795c20 MMEmoticonView.UIView.UIResponder.NSObject.isa
stack[0]: addr = 0x108795c20, type=malloc, frames: [0] 0x000000018374e0ac libsystem_malloc.dylib`calloc + 40 [1] 0x000000018318b624 libobjc.A.dylib`class_createInstance + 76 [2] 0x0000000183199ae4 libobjc.A.dylib`_objc_rootAlloc + 52 [3] 0x00000001026d8fd4 WeChat`-[MMImageBrowseView InitEmoticonView:] + 432 [4] 0x000000010245e950 WeChat`-[MMEmotionMsgBrowseViewController initImageViewWithFrame:] + 404  [5] 0x000000010245ea74 WeChat`-[MMEmotionMsgBrowseViewController setupImageView] + 156 [6] 0x000000010245e024 WeChat`-[MMEmotionMsgBrowseViewController initView] + 224 [7] 0x000000010245d76c WeChat`-[MMEmotionMsgBrowseViewController viewDidLoad] + 112 [8] 0x000000018a5f7924 UIKit`-[UIViewController loadViewIfRequired] + 1056 [9] 0x000000018a60f4b4 UIKit`-[UIViewController __viewWillAppear:] + 132 [10] 0x00000001026e05f8 WeChat`-[MMUIViewController beginAppearanceTransition:animated:] + 92 [11] 0x000000018a7975b4 UIKit`-[UINavigationController _startCustomTransition:] + 1136 [12] 0x000000018a6afe74 UIKit`-[UINavigationController _startDeferredTransitionIfNeeded:] + 676 [13] 0x000000018a6afadc UIKit`-[UINavigationController __viewWillLayoutSubviews] + 64 [14] 0x000000018a6afa40 UIKit`-[UILayoutContainerView layoutSubviews] + 188 [15] 0x000000018a5f4a80 UIKit`-[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 1196 [16] 0x0000000187aa29d8 QuartzCore`-[CALayer layoutSublayers] + 148 [17] 0x0000000187a974cc QuartzCore`CA::Layer::layout_if_needed(CA::Transaction*) + 292 [18] 0x0000000187a9738c QuartzCore`CA::Layer::layout_and_display_if_needed(CA::Transaction*) + 32 [19] 0x0000000187a143e0 QuartzCore`CA::Context::commit_transaction(CA::Transaction*) + 252 [20] 0x0000000187a3ba68 QuartzCore`CA::Transaction::commit() + 512 [21] 0x0000000187a3c488 QuartzCore`CA::Transaction::observer_callback(__CFRunLoopObserver*, unsigned long, void*) + 120 [22] 0x00000001846f60c0 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 32 [23] 0x00000001846f3cf0 CoreFoundation`__CFRunLoopDoObservers + 372 [24] 0x00000001846f4180 CoreFoundation`__CFRunLoopRun + 1024 [25] 0x00000001846222b8 CoreFoundation`CFRunLoopRunSpecific + 444 [26] 0x00000001860d6198 GraphicsServices`GSEventRunModal + 180 [27] 0x000000018a6627fc UIKit`-[UIApplication _run] + 684
     [28] 0x000000018a65d534 UIKit`UIApplicationMain + 208
     [29] 0x00000001000ebea4 WeChat`-[WATemplateMsgMngSwitchCell .cxx_destruct] + 372
     [30] 0x00000001836055b8 libdyld.dylib`start + 4
Copy the code

This will direct you to the class where the emoticon interface resides and where to initialize it.

This way, if you can find an object, you can quickly locate the module in which it resides. It’s a lot more efficient than logging back and forth.

Restore symbol table

It is recommended to restore the symbol table when re-signing the APP. After restoring the symbol table, you can see the method names directly on the stack, eliminating the need to calculate offsets and then look them up in hopper.

Reference: iOS symbol table Recovery & Reverse Alipay, restorest-symbol.

Several other debugging commands

ptr_refs

You can find out in memory which addresses refer to a pointer, which is equivalent to seeing where a variable is referred to.

cstr_refs

Find in memory where a certain C String is referenced.

find_variable

Find where a local variable is referenced on the current stack frame.

objc_refs

Find an instance of a class in memory.

Go to Xcode and debug

To debug and enable the Malloc stack in Xcode, you need to start the app with the DebugServer, connect to the LLDB on the terminal, and then disconnect with Process Detach. Next, Attach to Process with Xcode, for reference: iOS Reverse: Directly debug third-party app with Xcode.