小试 Xcode 逆向:App 内存监控原理初探(逆向技术必看)
前言
最近看到公司同事的《iOS内存那些事》系列文章,其中的一篇文章讲了他在研究WebKit中内存管理的时候,发现可以用phys_footprint来衡量内存,其结果和xcode debug显示的值基本一致。文章通读下来,收获颇丰~回味之余,突然脑洞了一下,为啥不直接逆向一下Xcode,学习一下xcode debug app时它是怎么实现内存监控的?刚好最近在自学逆向知识,顺便也来练练手~
动手实践 准备一个小项目 运行一下,我们可以在debug面板看到memory report信息
lldb和hopper的使用 通过如下操作,我们可以直接attach Xcode调试
(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 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)
作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个我的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的实现。
通过查看相应的实现,可以知道是它通过debugSession实例获取相关信息的,再结合调用栈信息,debugSession肯定就是DBGLLDBSession。同样的,通过lldb,我们可以找到DBGLLDBSession位于/Applications/Xcode.app/Contents/PlugIns/DebuggerLLDB.ideplugin/Contents/MacOS/DebuggerLLDB,企图通过hooper看看它的实现,然而并没看出啥有用信息。只能继续尝试lldb断点。po $rdx打印它的参数,似乎出了一串奇怪的字符串?!
(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的这个方法的信息来展示的!!!
另外,关于debugserver,可以看这里的介绍。简单来说,它是运行在ios上的一个可以接受lldb前端命令的『远程调试』服务器。在越狱设备上,可以通过它做很多trick,这里暂且不表。
验证 初步 扒出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实现并且自己撸一遍做一套性能监控.
推荐
点击进群密码:111
进群领取2020 大厂面试题
原文地址:https://www.cnblogs.com/ajjx1366/p/12980939.html
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 深入了解Python 变量作用域
- Laravel5.5 数据库迁移:创建表与修改表示例
- python代码能做成软件吗
- php适配器模式简单应用示例
- Python 解析简单的XML数据
- Laravel 实现Eloquent模型分组查询并返回每个分组的数量 groupBy()
- Python分类测试代码实例汇总
- Swoole 5将移除自动添加Event::wait()特性详解
- laravel 数据验证规则详解
- JS操作XML中DTD介绍及使用方法分析
- PHP设计模式之迭代器(Iterator)模式入门与应用详解
- PHP FileSystem 文件系统常用api整理总结
- laravel框架之数据库查出来的对象实现转化为数组
- php apache开启跨域模式过程详解
- laravel5.6实现数值转换