小試 Xcode 逆向:App 內存監控原理初探(逆向技術必看)

前言

最近看到公司同事的《iOS內存那些事》系列文章,其中的一篇文章講了他在研究WebKit中內存管理的時候,發現能夠用phys_footprint來衡量內存,其結果和xcode debug顯示的值基本一致。文章通讀下來,收穫頗豐~回味之餘,忽然腦洞了一下,爲啥不直接逆向一下Xcode,學習一下xcode debug app時它是怎麼實現內存監控的?恰好最近在自學逆向知識,順便也來練練手~前端

動手實踐 準備一個小項目 運行一下,咱們能夠在debug面板看到memory report信息ios

lldb和hopper的使用 經過以下操做,咱們能夠直接attach Xcode調試面試

➜  ~ lldb -n Xcode
(lldb) process attach --name "Xcode"
Process 969 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGSTOP
    frame #0: 0x00007fffe2bcb34a libsystem_kernel.dylib`mach_msg_trap + 10
libsystem_kernel.dylib`mach_msg_trap:
->  0x7fffe2bcb34a <+10>: retq
    0x7fffe2bcb34b <+11>: nop

libsystem_kernel.dylib`mach_msg_overwrite_trap:
    0x7fffe2bcb34c <+0>:  movq   %rcx, %r10
    0x7fffe2bcb34f <+3>:  movl   $0x1000020, %eax          ; imm = 0x1000020
Target 0: (Xcode) stopped.

Executable module set to "/Applications/Xcode.app/Contents/MacOS/Xcode".
Architecture set to: x86_64h-apple-macosx.
(lldb) c
Process 969 resuming
(lldb)
複製代碼
來到Xcode debug面板,能夠直接看到app運行時的內存信息。先小試一下那個內存信息欄可否響應點擊操做。加個斷點,嘗試點擊一下那個內存欄,bingo,順利跑到斷點處~

(lldb) b -[NSResponder mouseUp:]
Breakpoint 1: where = AppKit`-[NSResponder mouseUp:], address = 0x00007fffcb070177
Process 969 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
    frame #0: 0x00007fffcb070177 AppKit`-[NSResponder mouseUp:]
AppKit`-[NSResponder mouseUp:]:
->  0x7fffcb070177 <+0>: pushq  %rbp
    0x7fffcb070178 <+1>: movq   %rsp, %rbp
    0x7fffcb07017b <+4>: popq   %rbp
    0x7fffcb07017c <+5>: jmp    0x7fffcaf94724            ; forwardMethod
Target 0: (Xcode) stopped.(lldb)
複製代碼

複製代碼 由於Xcode確定是x86_64架構編譯的,因此經過po $rdi,能夠看到點擊方法的對象是<NSTextField: 0x7fb7aed38280>。第一直覺告訴我,NSTextField是否是相似於UITextField,有text屬性能夠被賦值?查看了一下apple文檔,它的父類NSControl有個stringValue屬性能夠設置,設斷點,發現面板上內存變化時,斷點觸發了,bt一下,能夠看到以下信息(注意,要先肯定觸發斷點的是展現內存的那個NSTextField)macos

做爲一個開發者,有一個學習的氛圍跟一個交流圈子特別重要,這是一個個人iOS交流羣:651612063 進羣密碼111,無論你是小白仍是大牛歡迎入駐 ,分享BAT,阿里面試題、面試經驗,討論技術, 你們一塊兒交流學習成長!

(lldb) bt
* thread #1, queue = 'MainQueue: -[DBGLLDBSession processProfileDataString:]_block_invoke', stop reason = breakpoint 1.1
  * frame #0: 0x00007fffcaef1897 AppKit`-[NSControl setStringValue:]
    frame #1: 0x0000000125f5b305 DebuggerUI`__54-[DBGGaugeMemoryEditor _setupTopSectionComponentViews]_block_invoke + 955
    frame #2: 0x0000000106e49e36 DVTFoundation`-[DVTObservingBlockToken observeValueForKeyPath:ofObject:change:context:] + 610
    frame #3: 0x00007fffced5035d Foundation`NSKeyValueNotifyObserver + 350
    frame #4: 0x00007fffced4fbf4 Foundation`NSKeyValueDidChange + 486
    frame #5: 0x00007fffcee8e867 Foundation`-[NSObject(NSKeyValueObservingPrivate) _changeValueForKeys:count:maybeOldValuesDict:usingBlock:] + 944
    frame #6: 0x00007fffced1395d Foundation`-[NSObject(NSKeyValueObservingPrivate) _changeValueForKey:key:key:usingBlock:] + 60
    frame #7: 0x00007fffced7c23b Foundation`_NSSetObjectValueAndNotify + 261
    frame #8: 0x0000000106e8a742 DVTFoundation`__DVTDispatchAsync_block_invoke + 97
    frame #9: 0x00007fffe2a77524 libdispatch.dylib`_dispatch_call_block_and_release + 12
    frame #10: 0x00007fffe2a6e8fc libdispatch.dylib`_dispatch_client_callout + 8
    frame #11: 0x00007fffe2a849a0 libdispatch.dylib`_dispatch_queue_serial_drain + 896
    frame #12: 0x00007fffe2a77306 libdispatch.dylib`_dispatch_queue_invoke + 1046
    frame #13: 0x00007fffe2a7b908 libdispatch.dylib`_dispatch_main_queue_callback_4CF + 505
    frame #14: 0x00007fffcd35bbc9 CoreFoundation`__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 9
    frame #15: 0x00007fffcd31cc0d CoreFoundation`__CFRunLoopRun + 2205
    frame #16: 0x00007fffcd31c114 CoreFoundation`CFRunLoopRunSpecific + 420
    frame #17: 0x00007fffcc87cebc HIToolbox`RunCurrentEventLoopInMode + 240
    frame #18: 0x00007fffcc87ccf1 HIToolbox`ReceiveNextEventCommon + 432
    frame #19: 0x00007fffcc87cb26 HIToolbox`_BlockUntilNextEventMatchingListInModeWithFilter + 71
    frame #20: 0x00007fffcae15a54 AppKit`_DPSNextEvent + 1120
    frame #21: 0x00007fffcb5917ee AppKit`-[NSApplication(NSEvent) _nextEventMatchingEventMask:untilDate:inMode:dequeue:] + 2796
    frame #22: 0x000000010743d98e DVTKit`-[DVTApplication nextEventMatchingMask:untilDate:inMode:dequeue:] + 390
    frame #23: 0x00007fffcae0a3db AppKit`-[NSApplication run] + 926
    frame #24: 0x00007fffcadd4e0e AppKit`NSApplicationMain + 1237
    frame #25: 0x00007fffe2aa4235 libdyld.dylib`start + 1
    frame #26: 0x00007fffe2aa4235 libdyld.dylib`start + 1
複製代碼

從函數調用棧上,咱們能夠看出,NSTextField值的變化是經過kvo某個值實現的。image lookup -rn '[DBGGaugeMemoryEditor\,能夠發現它位於/Applications/Xcode.app/Contents/PlugIns/DebuggerUI.ideplugin/Contents/MacOS/DebuggerUI, 把二進制文件拖到hooper裏看一下-[DBGGaugeMemoryEditor _setupTopSectionComponentViews]_block_invoke的實現。xcode

經過查看相應的實現,能夠知道是它經過debugSession實例獲取相關信息的,再結合調用棧信息,debugSession確定就是DBGLLDBSession。一樣的,經過lldb,咱們能夠找到DBGLLDBSession位於/Applications/Xcode.app/Contents/PlugIns/DebuggerLLDB.ideplugin/Contents/MacOS/DebuggerLLDB,企圖經過hooper看看它的實現,然而並沒看出啥有用信息。只能繼續嘗試lldb斷點。po $rdx打印它的參數,彷佛出了一串奇怪的字符串?!bash

(lldb) b -[DBGLLDBSession processProfileDataString:]
Breakpoint 3: where = DebuggerLLDB`-[DBGLLDBSession processProfileDataString:], address = 0x0000000115f99c52
(lldb) c
Process 4489 resuming
Process 4489 stopped
* thread #30, name = '<DBGLLDBSessionThread (pid=51274)>', stop reason = breakpoint 3.1
    frame #0: 0x0000000115f99c52 DebuggerLLDB`-[DBGLLDBSession processProfileDataString:]
DebuggerLLDB`-[DBGLLDBSession processProfileDataString:]:
->  0x115f99c52 <+0>: push   rbp
    0x115f99c53 <+1>: mov    rbp, rsp
    0x115f99c56 <+4>: push   r15
    0x115f99c58 <+6>: push   r14
Target 0: (Xcode) stopped.
(lldb) po $rdi
<DBGLLDBSession: 0x7f9c998bec90>

(lldb) po $rdx
num_cpu:8;host_user_ticks:3332988;host_sys_ticks:2148237;host_idle_ticks:23546214;elapsed_usec:1513647973058229;task_used_usec:43128;thread_used_id:1;thread_used_usec:841463;thread_used_name:;thread_used_id:4;thread_used_usec:595;thread_used_name:;thread_used_id:5;thread_used_usec:2130;thread_used_name:576562546872656164;thread_used_id:6;thread_used_usec:3012;thread_used_name:636f6d2e6170706c652e75696b69742e6576656e7466657463682d746872656164;thread_used_id:11;thread_used_usec:255;thread_used_name:;total:17179869184;used:14274596864;rprvt:0;purgeable:0;anonymous:57823232;energy:98435210380;
複製代碼

google大法,lldb源碼查看 隨便抽了一個關鍵字host_sys_ticks,google了一下,發現這串字符,居然來自lldb項目裏的debugserver!先查看了一下本機的lldb版本(lldb-900.0.45),在apple open source的官網上沒找到這個版本的lldb。無奈下只能去lldb官網clone了一份最新的代碼,雖然不知道apple是基於哪一個lldb版本開發的,可是看最新的實現總不會錯~服務器

經過查看debugserver源碼,能夠發現前面那串字符串是在std::string MachTask::GetProfileData(DNBProfileDataScanType scanType)生成的,裏面有各類profile信息,好比cpu,memory等。架構

大膽猜測一下,Xcode的內存監控正是定時經過獲取debugserver的這個方法的信息來展現的!!!app

另外,關於debugserver,能夠看這裏的介紹。簡單來講,它是運行在ios上的一個能夠接受lldb前端命令的『遠程調試』服務器。在越獄設備上,能夠經過它作不少trick,這裏暫且不表。ide

驗證 初步 扒出memory profile的代碼實現以下:

static void GetPurgeableAndAnonymous(task_t task, uint64_t &purgeable,
                                     uint64_t &anonymous) {
#if defined(TASK_VM_INFO) && TASK_VM_INFO >= 22

  kern_return_t kr;
  mach_msg_type_number_t info_count;
  task_vm_info_data_t vm_info;

  info_count = TASK_VM_INFO_COUNT;
  kr = task_info(task, TASK_VM_INFO_PURGEABLE, (task_info_t)&vm_info,
                 &info_count);
  if (kr == KERN_SUCCESS) {
    purgeable = vm_info.purgeable_volatile_resident;
    anonymous =
        vm_info.internal + vm_info.compressed - vm_info.purgeable_volatile_pmap;
  }

#endif
}
複製代碼

再看看前面獲取的anonymous的字節值,57823232對應的正是55.1445007MB,即debug內存面板展現的值!

這裏再附上同事發現的WebKit內存計算公式,能夠比較一下理解,具體參看這裏,搜索一下phys_footprint便知。

phys_footprint = (internal - alternate_accounting) + (internal_compressed - alternate_accounting_compressed) + iokit_mapped + purgeable_nonvolatile + purgeable_nonvolatile_compressed + page_table
複製代碼

具體

原本按理說是應該直接把上述代碼拷出來具體執行進一步確認一下。可是意外的找到了一個偷懶的方法。既然Xcode是經過debugserver獲取到相關信息,那麼有沒有辦法直接和debugserver交互來獲取信息呢?

繼續翻看了一下lldb代碼,lldb前端確實就存在相應的命令來觸發debugserver執行。

經過代碼能夠發現std::string MachTask::GetProfileData(DNBProfileDataScanType scanType)會在RNBRemote接受到消息包qGetProfileData時執行,而lldb原來能夠直接經過process plugin packet send命令來給debugserver發送包命令。

換句話說,也就是直接在Xcode終端直接執行命令驗證

結合lldb腳本的使用,目測驗證起來並不難。

固然,最終可能仍是要直接拷出一下那段代碼驗證一下,這個後面有空再試試。

總結

雖然咋看下來全文一路順暢,可是做爲一名逆向新手,中間仍是遇到了很多問題,不過收穫也是很大滴~

lldb和hopper確實很強大,深刻學習一下lldb源碼仍是有必要的,其中仍是有很多有趣的地方值得挖掘。

經過Xcode debug機制的原理探尋,咱們能夠學習它的profile實現而且本身擼一遍作一套性能監控.

推薦

推薦文章 裏面有2020大廠面試題地址

本人人文章

點擊進羣交流 密碼:111

相關文章
相關標籤/搜索