<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>逆向 on Axqora 博客</title>
    <link>https://blog.axqora.com/tags/%E9%80%86%E5%90%91/</link>
    <description>Recent content in 逆向 on Axqora 博客</description>
    <image>
      <title>Axqora 博客</title>
      <url>https://blog.axqora.com/icon.png</url>
      <link>https://blog.axqora.com/icon.png</link>
    </image>
    <generator>Hugo</generator>
    <language>zh</language>
    <lastBuildDate>Mon, 02 Sep 2024 00:00:00 +0000</lastBuildDate>
    <atom:link href="https://blog.axqora.com/tags/%E9%80%86%E5%90%91/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>iOS Tweak 插件开发及非越狱安装</title>
      <link>https://blog.axqora.com/posts/ios-tweak-dev/</link>
      <pubDate>Mon, 02 Sep 2024 00:00:00 +0000</pubDate>
      <guid>https://blog.axqora.com/posts/ios-tweak-dev/</guid>
      <description>&lt;h1 id=&#34;一工具集&#34;&gt;一、工具集&lt;/h1&gt;
&lt;h2 id=&#34;trollstore&#34;&gt;TrollStore&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/opa334/TrollStore&#34;&gt;TrollStore&lt;/a&gt; 利用了 iOS 的 AMFI/CoreTrust 漏洞，允许在非越狱的手机上安装自签名的 ipa，并使通过 TrollStore 安装的应用程序能获得更高的权限（如获取手机内的应用列表等）。&lt;/p&gt;
&lt;p&gt;版本限制：14.0 beta 2 - 16.6.1, 16.7 RC (20H18), 17.0&lt;/p&gt;
&lt;p&gt;根据指引在手机上安装 TrollStore：&lt;a href=&#34;https://ios.cfw.guide/installing-trollstore/&#34;&gt;安装指引&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&#34;trollfools&#34;&gt;TrollFools&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/Lessica/TrollFools&#34;&gt;TrollFools&lt;/a&gt; 通过 TrollStore 安装手机上，可以对手机上的应用进行动态库注入和重签名。&lt;/p&gt;
&lt;p&gt;关键逻辑在 &lt;code&gt;Injector.swift&lt;/code&gt; 文件中，原理大致如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;对所有的 MachO 文件（包含主程序和注入的动态库）使用 Idid 进行重签名、使用 &lt;a href=&#34;https://github.com/opa334/ChOma&#34;&gt;ChOma&lt;/a&gt; 绕过 CoreTrust、使用 chown 修改权限；&lt;/li&gt;
&lt;li&gt;使用 install_name_tool 注入依赖的系统框架，路径为&lt;code&gt;@executable_path/Frameworks&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;使用 &lt;a href=&#34;https://github.com/tyilo/insert_dylib&#34;&gt;insert_dylib&lt;/a&gt; 注入 LC_LOAD_DYLIB；&lt;/li&gt;
&lt;li&gt;注入完成后对 MachO 文件再进行第 1 步。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;theos-和-tweak&#34;&gt;Theos 和 Tweak&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/theos/theos&#34;&gt;Theos&lt;/a&gt; 是一个强大的开源开发工具链，广泛用于开发 iOS 越狱插件（tweaks）。这些插件可以修改和扩展应用程序的功能。&lt;/p&gt;
&lt;h1 id=&#34;二theos&#34;&gt;二、Theos&lt;/h1&gt;
&lt;h2 id=&#34;1环境&#34;&gt;1、环境&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;// 项目安装
sudo git clone --recursive https://github.com/theos/theos.git /opt/theos

// 配置环境变量
export THEOS=/opt/theos
export PATH=/opt/theos/bin/:$PATH
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;环境变量添加到 &lt;code&gt;~/.zshrc&lt;/code&gt; 后需要执行 &lt;code&gt;source ~/.zshrc&lt;/code&gt;或者新开终端窗口。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h1 id="一工具集">一、工具集</h1>
<h2 id="trollstore">TrollStore</h2>
<p><a href="https://github.com/opa334/TrollStore">TrollStore</a> 利用了 iOS 的 AMFI/CoreTrust 漏洞，允许在非越狱的手机上安装自签名的 ipa，并使通过 TrollStore 安装的应用程序能获得更高的权限（如获取手机内的应用列表等）。</p>
<p>版本限制：14.0 beta 2 - 16.6.1, 16.7 RC (20H18), 17.0</p>
<p>根据指引在手机上安装 TrollStore：<a href="https://ios.cfw.guide/installing-trollstore/">安装指引</a></p>
<h2 id="trollfools">TrollFools</h2>
<p><a href="https://github.com/Lessica/TrollFools">TrollFools</a> 通过 TrollStore 安装手机上，可以对手机上的应用进行动态库注入和重签名。</p>
<p>关键逻辑在 <code>Injector.swift</code> 文件中，原理大致如下：</p>
<ol>
<li>对所有的 MachO 文件（包含主程序和注入的动态库）使用 Idid 进行重签名、使用 <a href="https://github.com/opa334/ChOma">ChOma</a> 绕过 CoreTrust、使用 chown 修改权限；</li>
<li>使用 install_name_tool 注入依赖的系统框架，路径为<code>@executable_path/Frameworks</code>；</li>
<li>使用 <a href="https://github.com/tyilo/insert_dylib">insert_dylib</a> 注入 LC_LOAD_DYLIB；</li>
<li>注入完成后对 MachO 文件再进行第 1 步。</li>
</ol>
<h2 id="theos-和-tweak">Theos 和 Tweak</h2>
<p><a href="https://github.com/theos/theos">Theos</a> 是一个强大的开源开发工具链，广泛用于开发 iOS 越狱插件（tweaks）。这些插件可以修改和扩展应用程序的功能。</p>
<h1 id="二theos">二、Theos</h1>
<h2 id="1环境">1、环境</h2>
<pre tabindex="0"><code>// 项目安装
sudo git clone --recursive https://github.com/theos/theos.git /opt/theos

// 配置环境变量
export THEOS=/opt/theos
export PATH=/opt/theos/bin/:$PATH
</code></pre><p>环境变量添加到 <code>~/.zshrc</code> 后需要执行 <code>source ~/.zshrc</code>或者新开终端窗口。</p>
<h2 id="2创建项目">2、创建项目</h2>
<p><img alt="image" loading="lazy" src="/image/iOS-Tweak-Dev/IMG-20240902172221029.png"></p>
<p>运行 <code>$THEOS/bin/nic.pl</code> 可以运行 theos 的项目创建程序，这里选择 19 创建一个 OC 应用的插件，然后按提示输入信息即可。</p>
<h2 id="3代码编写">3、代码编写</h2>
<p>编辑 Tweak.x 文件，添加以下代码</p>
<pre tabindex="0"><code>#import &lt;AVFoundation/AVFoundation.h&gt;

%hook AVCaptureVideoDataOutput
- (void)_processSampleBuffer:(CMSampleBufferRef)buffer {
    %orig(buffer);
    NSLog(@&#34;delegate: %@ _processSampleBuffer: %@&#34;, [self sampleBufferDelegate], buffer);
}
%end

%hook AVCaptureFigVideoDevice
- (void)setVideoZoomFactor:(CGFloat)factor {
    %orig(factor);
    NSLog(@&#34;Device: %@, Video zoom factor was set to: %f&#34;, self, factor);
}
%end
</code></pre><p>这段代码使用了 Logos 语法 hook 了相机数据输出的方法和设置 Zoom 的接口。</p>
<h2 id="4引用第三方库">4、引用第三方库</h2>
<pre tabindex="0"><code>FLEX_ROOT = libs/FLEX

# Function to convert /foo/bar to -I/foo/bar
dtoim = $(foreach d,$(1),-I$(d))

# Gather FLEX sources
SOURCES  = $(shell find $(FLEX_ROOT)/Classes -name &#39;*.c&#39;)
SOURCES += $(shell find $(FLEX_ROOT)/Classes -name &#39;*.m&#39;)
SOURCES += $(shell find $(FLEX_ROOT)/Classes -name &#39;*.mm&#39;)
# Gather FLEX headers for search paths
_IMPORTS  = $(shell /bin/ls -d $(FLEX_ROOT)/Classes/*/)
_IMPORTS += $(shell /bin/ls -d $(FLEX_ROOT)/Classes/*/*/)
_IMPORTS += $(shell /bin/ls -d $(FLEX_ROOT)/Classes/*/*/*/)
_IMPORTS += $(shell /bin/ls -d $(FLEX_ROOT)/Classes/*/*/*/*/)
IMPORTS = -I$(FLEX_ROOT)/Classes/ $(call dtoim, $(_IMPORTS))

$(TWEAK_NAME)_CFLAGS += $(IMPORTS) -g
$(TWEAK_NAME)_CCFLAGS += -std=gnu++11
</code></pre><p>目前我以源码方式引入第三方库。把第三方库的源文件放到 libs 中，在 Makefile 中添加以上代码。</p>
<p>TODO：Theos 支持以动态库或静态库的方式引入第三方库，因本文的前提是非越狱手机，所以这里应该使用静态库的方式引入，并编译到输出的文件中。</p>
<h2 id="5编译">5、编译</h2>
<p>编辑 Makefile 文件，添加以下代码，表示项目依赖 AVFoundation 框架。</p>
<pre tabindex="0"><code>TestTweak_FRAMEWORKS = AVFoundation
</code></pre><p>在项目文件夹直接执行 <code>make</code>。</p>
<p>如果是越狱手机，可以运行 <code>make do</code> 把插件编译后直接安装到手机上，当然也需要其它的配置，这里先不讨论越狱手机的插件开发。</p>
<h2 id="6安装">6、安装</h2>
<p><img alt="image" loading="lazy" src="/image/iOS-Tweak-Dev/IMG-20240902172221150.png"></p>
<p>arm64 架构的编译产物在路径 <code>.theos/obj/debug/arm64/TestTweak.dylib</code> 中。</p>
<p>把编译产物导入到手机（可用 AirDrop 或数据线导入，存到手机的 Files 中）。</p>
<p>打开已经安装的 TrollFools 应用，选择想要注入动态库的应用（如 BigoLive），选择需要注入的动态库（TestTweak.dylib）。</p>
<p>注入成功后，打开 BigoLive 开始直播，这时候可以从 Mac 上的 Console 查看到相关的打印信息。</p>
<h2 id="7demo">7、Demo</h2>
<p><a href="https://github.com/oudushu/DSHookTweak">https://github.com/oudushu/DSHookTweak</a></p>
<p>该 Demo 已实现：</p>
<ol>
<li>摇一摇呼出选择列表；</li>
<li>内置 FLEX 工具集；</li>
<li>内置 LookinServer，可使用 macOS 端的 Lookin 客户端快速查看应用的图层；</li>
<li>日志写文件，并可把文件通过 AirDrop 分享出来；</li>
<li>相机相关 hook。</li>
</ol>
<h1 id="三总结">三、总结</h1>
<p>我们可以利用以上的工具，做到给非越狱手机安装插件，从而方便地 hook 从 App Store 安装的应用程序。</p>
<p>可以开发通用的插件，做到一个插件适用不同的应用，提高逆向的效率。</p>
<p>缺点：</p>
<ol>
<li>非越狱手机上无法调试；</li>
<li>对 iOS 版本有限制，太新的版本暂不支持。</li>
</ol>
]]></content:encoded>
    </item>
    <item>
      <title>iOS 逆向入门 - 动态库注入原理</title>
      <link>https://blog.axqora.com/posts/ios-reverse-insert-dylib/</link>
      <pubDate>Tue, 08 Mar 2022 00:00:00 +0000</pubDate>
      <guid>https://blog.axqora.com/posts/ios-reverse-insert-dylib/</guid>
      <description>&lt;h2 id=&#34;一工具&#34;&gt;一、工具&lt;/h2&gt;
&lt;p&gt;在逆向的过程中，我们经常需要往目标执行文件中注入自己的逻辑，从而实现 hook 的目的。&lt;/p&gt;
&lt;p&gt;我们常用 &lt;a href=&#34;https://github.com/alexzielenski/optool&#34;&gt;optool&lt;/a&gt; 工具实现动态库注入。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Command Line Tool for interacting with MachO binaries on OSX/iOS&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;了解这个工具的原理，有助于我们对 MachO 文件有更深入的了解。&lt;/p&gt;
&lt;h2 id=&#34;二源码解析&#34;&gt;二、源码解析&lt;/h2&gt;
&lt;p&gt;关键代码如下：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;BOOL insertLoadEntryIntoBinary(NSString *dylibPath, NSMutableData *binary, struct thin_header macho, uint32_t type) {
    // 判断 Load Command 类型
    if (type != LC_REEXPORT_DYLIB &amp;amp;&amp;amp;
        type != LC_LOAD_WEAK_DYLIB &amp;amp;&amp;amp;
        type != LC_LOAD_UPWARD_DYLIB &amp;amp;&amp;amp;
        type != LC_LOAD_DYLIB) {
        LOG(&amp;#34;Invalid load command type&amp;#34;);
        return NO;
    }
    // 判断动态库是否已经注入
    // parse load commands to see if our load command is already there
    uint32_t lastOffset = 0;
    if (binaryHasLoadCommandForDylib(binary, dylibPath, &amp;amp;lastOffset, macho)) {
        // there already exists a load command for this payload so change the command type
        uint32_t originalType = *(uint32_t *)(binary.bytes + lastOffset);
        if (originalType != type) {
            LOG(&amp;#34;A load command already exists for %s. Changing command type from %s to desired %s&amp;#34;, dylibPath.UTF8String, LC(originalType), LC(type));
            [binary replaceBytesInRange:NSMakeRange(lastOffset, sizeof(type)) withBytes:&amp;amp;type];
        } else {
            LOG(&amp;#34;Load command already exists&amp;#34;);
        }
        
        return YES;
    }
    
    // create a new load command
    unsigned int length = (unsigned int)sizeof(struct dylib_command) + (unsigned int)dylibPath.length;
    unsigned int padding = (8 - (length % 8));
    
    // 判断 Load Command 段末尾是否还有空白位置
    // check if data we are replacing is null
    NSData *occupant = [binary subdataWithRange:NSMakeRange(macho.header.sizeofcmds + macho.offset + macho.size,
                                                            length + padding)];

    // All operations in optool try to maintain a constant byte size of the executable
    // so we don&amp;#39;t want to append new bytes to the binary (that would break the executable
    // since everything is offset-based–we&amp;#39;d have to go in and adjust every offset)
    // So instead take advantage of the huge amount of padding after the load commands
    if (strcmp([occupant bytes], &amp;#34;\0&amp;#34;)) {
        NSLog(@&amp;#34;cannot inject payload into %s because there is no room&amp;#34;, dylibPath.fileSystemRepresentation);
        return NO;
    }
    
    LOG(&amp;#34;Inserting a %s command for architecture: %s&amp;#34;, LC(type), CPU(macho.header.cputype));
    
    // 新建 dylib_command 并替换到对应位置的空白区域
    struct dylib_command command;
    struct dylib dylib;
    dylib.name.offset = sizeof(struct dylib_command);
    dylib.timestamp = 2; // load commands I&amp;#39;ve seen use 2 for some reason
    dylib.current_version = 0;
    dylib.compatibility_version = 0;
    command.cmd = type;
    command.dylib = dylib;
    command.cmdsize = length + padding;
    
    unsigned int zeroByte = 0;
    NSMutableData *commandData = [NSMutableData data];
    [commandData appendBytes:&amp;amp;command length:sizeof(struct dylib_command)];
    [commandData appendData:[dylibPath dataUsingEncoding:NSASCIIStringEncoding]];
    [commandData appendBytes:&amp;amp;zeroByte length:padding];
    
    // remove enough null bytes to account of our inserted data
    [binary replaceBytesInRange:NSMakeRange(macho.offset + macho.header.sizeofcmds + macho.size, commandData.length)
                      withBytes:0
                         length:0];
    // insert the data
    [binary replaceBytesInRange:NSMakeRange(lastOffset, 0) withBytes:commandData.bytes length:commandData.length];
    
    // 修改 header 内容，
    // fix the existing header
    macho.header.ncmds += 1;
    macho.header.sizeofcmds += command.cmdsize;
    
    // this is safe to do in 32bit because the 4 bytes after the header are still being put back
    [binary replaceBytesInRange:NSMakeRange(macho.offset, sizeof(macho.header)) withBytes:&amp;amp;macho.header];
    
    return YES;
}
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;三macho-文件变化&#34;&gt;三、MachO 文件变化&lt;/h2&gt;
&lt;p&gt;利用 MachOView 工具查看注入动态库后的 MachO 文件，可以发现在 Load Command 段的后面多了一个 LC，此 LC 明确了动态库的加载路径；且可发现其他段的 offset 并没有被修改。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h2 id="一工具">一、工具</h2>
<p>在逆向的过程中，我们经常需要往目标执行文件中注入自己的逻辑，从而实现 hook 的目的。</p>
<p>我们常用 <a href="https://github.com/alexzielenski/optool">optool</a> 工具实现动态库注入。</p>
<blockquote>
<p>Command Line Tool for interacting with MachO binaries on OSX/iOS</p>
</blockquote>
<p>了解这个工具的原理，有助于我们对 MachO 文件有更深入的了解。</p>
<h2 id="二源码解析">二、源码解析</h2>
<p>关键代码如下：</p>
<pre tabindex="0"><code>BOOL insertLoadEntryIntoBinary(NSString *dylibPath, NSMutableData *binary, struct thin_header macho, uint32_t type) {
    // 判断 Load Command 类型
    if (type != LC_REEXPORT_DYLIB &amp;&amp;
        type != LC_LOAD_WEAK_DYLIB &amp;&amp;
        type != LC_LOAD_UPWARD_DYLIB &amp;&amp;
        type != LC_LOAD_DYLIB) {
        LOG(&#34;Invalid load command type&#34;);
        return NO;
    }
    // 判断动态库是否已经注入
    // parse load commands to see if our load command is already there
    uint32_t lastOffset = 0;
    if (binaryHasLoadCommandForDylib(binary, dylibPath, &amp;lastOffset, macho)) {
        // there already exists a load command for this payload so change the command type
        uint32_t originalType = *(uint32_t *)(binary.bytes + lastOffset);
        if (originalType != type) {
            LOG(&#34;A load command already exists for %s. Changing command type from %s to desired %s&#34;, dylibPath.UTF8String, LC(originalType), LC(type));
            [binary replaceBytesInRange:NSMakeRange(lastOffset, sizeof(type)) withBytes:&amp;type];
        } else {
            LOG(&#34;Load command already exists&#34;);
        }
        
        return YES;
    }
    
    // create a new load command
    unsigned int length = (unsigned int)sizeof(struct dylib_command) + (unsigned int)dylibPath.length;
    unsigned int padding = (8 - (length % 8));
    
    // 判断 Load Command 段末尾是否还有空白位置
    // check if data we are replacing is null
    NSData *occupant = [binary subdataWithRange:NSMakeRange(macho.header.sizeofcmds + macho.offset + macho.size,
                                                            length + padding)];

    // All operations in optool try to maintain a constant byte size of the executable
    // so we don&#39;t want to append new bytes to the binary (that would break the executable
    // since everything is offset-based–we&#39;d have to go in and adjust every offset)
    // So instead take advantage of the huge amount of padding after the load commands
    if (strcmp([occupant bytes], &#34;\0&#34;)) {
        NSLog(@&#34;cannot inject payload into %s because there is no room&#34;, dylibPath.fileSystemRepresentation);
        return NO;
    }
    
    LOG(&#34;Inserting a %s command for architecture: %s&#34;, LC(type), CPU(macho.header.cputype));
    
    // 新建 dylib_command 并替换到对应位置的空白区域
    struct dylib_command command;
    struct dylib dylib;
    dylib.name.offset = sizeof(struct dylib_command);
    dylib.timestamp = 2; // load commands I&#39;ve seen use 2 for some reason
    dylib.current_version = 0;
    dylib.compatibility_version = 0;
    command.cmd = type;
    command.dylib = dylib;
    command.cmdsize = length + padding;
    
    unsigned int zeroByte = 0;
    NSMutableData *commandData = [NSMutableData data];
    [commandData appendBytes:&amp;command length:sizeof(struct dylib_command)];
    [commandData appendData:[dylibPath dataUsingEncoding:NSASCIIStringEncoding]];
    [commandData appendBytes:&amp;zeroByte length:padding];
    
    // remove enough null bytes to account of our inserted data
    [binary replaceBytesInRange:NSMakeRange(macho.offset + macho.header.sizeofcmds + macho.size, commandData.length)
                      withBytes:0
                         length:0];
    // insert the data
    [binary replaceBytesInRange:NSMakeRange(lastOffset, 0) withBytes:commandData.bytes length:commandData.length];
    
    // 修改 header 内容，
    // fix the existing header
    macho.header.ncmds += 1;
    macho.header.sizeofcmds += command.cmdsize;
    
    // this is safe to do in 32bit because the 4 bytes after the header are still being put back
    [binary replaceBytesInRange:NSMakeRange(macho.offset, sizeof(macho.header)) withBytes:&amp;macho.header];
    
    return YES;
}
</code></pre><h2 id="三macho-文件变化">三、MachO 文件变化</h2>
<p>利用 MachOView 工具查看注入动态库后的 MachO 文件，可以发现在 Load Command 段的后面多了一个 LC，此 LC 明确了动态库的加载路径；且可发现其他段的 offset 并没有被修改。</p>
<p><img alt="image-20220307113428421" loading="lazy" src="/image/iOS-reverse-insert-dylib/image-20220307113428421.png"></p>
<h2 id="四总结">四、总结</h2>
<p>动态库注入的流程总结如下：</p>
<ol>
<li>判断 Load Command 类型（LC_REEXPORT_DYLIB/LC_LOAD_WEAK_DYLIB/LC_LOAD_UPWARD_DYLIB/LC_LOAD_DYLIB）；</li>
<li>判断动态库是否已经注入（通过遍历 Load Command 段，取出几种 dylib 的 LC 的 name 字段跟加载动态库的路径做比对）；</li>
<li>判断 Load Command 段末尾是否还有空白位置（1. segment 中进行页对齐时会产生空白区域；2. 避免改变可执行文件大小；3. MachO 里几乎所有操作都基于偏移量进行计算，如果直接插入新的数据，会导致后面的偏移量都需要调整。）；</li>
<li>新建 dylib_command 并替换到对应位置的空白区域；</li>
<li>修改 header 内容；</li>
<li>把动态库复制到应用内的 Frameworks 文件夹中，重签名后打包成 ipa 包并安装到手机；</li>
<li>iOS 系统使用 dyld 加载应用，加载的 Load Command 时会找到对应的动态库并进行动态链接。</li>
</ol>
<p><img alt="image-20220308202107300" loading="lazy" src="/image/iOS-reverse-insert-dylib/image-20220308202107300.png"></p>
]]></content:encoded>
    </item>
    <item>
      <title>iOS 逆向入门 - 符号恢复及反汇编</title>
      <link>https://blog.axqora.com/posts/ios-restore-symbol/</link>
      <pubDate>Thu, 06 Jan 2022 00:00:00 +0000</pubDate>
      <guid>https://blog.axqora.com/posts/ios-restore-symbol/</guid>
      <description>&lt;p&gt;在通过逆向分析竞品过程中，经常需要分析其实现逻辑。但由于没有符号，我们会遇到一些阻碍。&lt;/p&gt;
&lt;h2 id=&#34;一符号恢复&#34;&gt;一、符号恢复&lt;/h2&gt;
&lt;p&gt;我们经常 hook 一个方法并加断点可快速获得参数值，但是使用 bt 命令打印堆栈信息时，只能看到 &lt;code&gt;___lldb_unnamed_symbol279180$$TikTok&lt;/code&gt; 的信息，原因是 Objective-C 在打包时会被 stripped out，导致无法看到具体符号。&lt;/p&gt;
&lt;p&gt;&lt;img alt=&#34;image-20220106175647663&#34; loading=&#34;lazy&#34; src=&#34;https://blog.axqora.com/image/iOS-restore-symbol/image-20220106175647663.png&#34;&gt;&lt;/p&gt;
&lt;h3 id=&#34;1-restore-symbol-使用&#34;&gt;1. restore-symbol 使用&lt;/h3&gt;
&lt;p&gt;可以利用 &lt;a href=&#34;https://github.com/tobefuturer/restore-symbol&#34;&gt;restore-symbol&lt;/a&gt; 工具恢复符号（只能恢复 Objective-C 符号，无法恢复 C/C++ 符号），具体使用如下：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# git clone --recursive https://github.com/tobefuturer/restore-symbol.git
# cd restore-symbol
# make
# ./restore-symbol TikTok -o TikTok_symbol
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;2-错误处理&#34;&gt;2. 错误处理&lt;/h3&gt;
&lt;p&gt;&lt;img alt=&#34;image-20220106180745368&#34; loading=&#34;lazy&#34; src=&#34;https://blog.axqora.com/image/iOS-restore-symbol/image-20220106180745368.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt=&#34;image-20220106181010510&#34; loading=&#34;lazy&#34; src=&#34;https://blog.axqora.com/image/iOS-restore-symbol/image-20220106181010510.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt=&#34;image-20220106181518223&#34; loading=&#34;lazy&#34; src=&#34;https://blog.axqora.com/image/iOS-restore-symbol/image-20220106181518223.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;在运行时会报错：&lt;code&gt;2022-01-06 18:07:15.103 restore-symbol[17534:7647405] *** Assertion failure in -[CDObjectiveC2Processor loadClassAtAddress:], CDObjectiveC2Processor.m:258&lt;/code&gt;，原因是使用了 Swift 语言，而解析 Swift 的方法已过期。我们可以简单粗暴地把抛出异常的代码注释掉，重新 make 即可成功恢复符号。&lt;/p&gt;
&lt;h3 id=&#34;3-验证&#34;&gt;3. 验证&lt;/h3&gt;
&lt;p&gt;把 ipa 包里的 MachO 文件替换为恢复符号后的 MachO 文件，重签名后运行到手机上（MonkeyDev 工具已经集成重签名的功能，这里只需要替换 TargetApp 里面的 MachO 文件，clean 后 rebuild 即可）。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>在通过逆向分析竞品过程中，经常需要分析其实现逻辑。但由于没有符号，我们会遇到一些阻碍。</p>
<h2 id="一符号恢复">一、符号恢复</h2>
<p>我们经常 hook 一个方法并加断点可快速获得参数值，但是使用 bt 命令打印堆栈信息时，只能看到 <code>___lldb_unnamed_symbol279180$$TikTok</code> 的信息，原因是 Objective-C 在打包时会被 stripped out，导致无法看到具体符号。</p>
<p><img alt="image-20220106175647663" loading="lazy" src="/image/iOS-restore-symbol/image-20220106175647663.png"></p>
<h3 id="1-restore-symbol-使用">1. restore-symbol 使用</h3>
<p>可以利用 <a href="https://github.com/tobefuturer/restore-symbol">restore-symbol</a> 工具恢复符号（只能恢复 Objective-C 符号，无法恢复 C/C++ 符号），具体使用如下：</p>
<pre tabindex="0"><code># git clone --recursive https://github.com/tobefuturer/restore-symbol.git
# cd restore-symbol
# make
# ./restore-symbol TikTok -o TikTok_symbol
</code></pre><h3 id="2-错误处理">2. 错误处理</h3>
<p><img alt="image-20220106180745368" loading="lazy" src="/image/iOS-restore-symbol/image-20220106180745368.png"></p>
<p><img alt="image-20220106181010510" loading="lazy" src="/image/iOS-restore-symbol/image-20220106181010510.png"></p>
<p><img alt="image-20220106181518223" loading="lazy" src="/image/iOS-restore-symbol/image-20220106181518223.png"></p>
<p>在运行时会报错：<code>2022-01-06 18:07:15.103 restore-symbol[17534:7647405] *** Assertion failure in -[CDObjectiveC2Processor loadClassAtAddress:], CDObjectiveC2Processor.m:258</code>，原因是使用了 Swift 语言，而解析 Swift 的方法已过期。我们可以简单粗暴地把抛出异常的代码注释掉，重新 make 即可成功恢复符号。</p>
<h3 id="3-验证">3. 验证</h3>
<p>把 ipa 包里的 MachO 文件替换为恢复符号后的 MachO 文件，重签名后运行到手机上（MonkeyDev 工具已经集成重签名的功能，这里只需要替换 TargetApp 里面的 MachO 文件，clean 后 rebuild 即可）。</p>
<p><img alt="image-20220106182141938" loading="lazy" src="/image/iOS-restore-symbol/image-20220106182141938.png"></p>
<p>在断点处使用 bt 命令打印堆栈信息：</p>
<p><img alt="image-20220106174114404" loading="lazy" src="/image/iOS-restore-symbol/image-20220106174114404.png"></p>
<p>可以看到大部分  <code>___lldb_unnamed_symbol279180$$TikTok</code> 都被恢复符号了，还有部分未被恢复的猜测是使用 Swift 语言编写的代码。</p>
<h2 id="二ida-pro-反汇编工具">二、IDA Pro 反汇编工具</h2>
<p>下面通过反编译上面输出的堆栈：<code>frame #3: 0x000000010650cdac TikTok -[LSGLPreviewView newFrameReadyAtTime:atIndex:] + 512</code> 简单介绍 IDA 的使用。</p>
<h3 id="1-使用-ida-打开--macho-文件">1. 使用 IDA 打开  MachO 文件</h3>
<p>IDA 分析 MachO 文件时间较长，需要耐心等待。</p>
<h3 id="2-计算对应方法在-macho-文件中的偏移地址">2. 计算对应方法在 MachO 文件中的偏移地址</h3>
<p>在断点处，输入 <code>image list -o -f</code>，可获得 ASLR 偏移地址，运行时基地址(0x000000010650cdac) = ASLR 偏移地址(0x0000000002a00000) + 基地址(0x103B0CDAC)</p>
<p><img alt="image-20220106174139508" loading="lazy" src="/image/iOS-restore-symbol/image-20220106174139508.png"></p>
<h3 id="3-定位到基地址">3. 定位到基地址</h3>
<p>在 IDA 中使用快捷键 <code>g</code>，可跳转到地址</p>
<p><img alt="image-20220106174743933" loading="lazy" src="/image/iOS-restore-symbol/image-20220106174743933.png"></p>
<p><img alt="image-20220106174800988" loading="lazy" src="/image/iOS-restore-symbol/image-20220106174800988.png"></p>
<h3 id="4-反汇编">4. 反汇编</h3>
<p>使用快捷键 <code>F5</code>，反汇编对应代码。至此，我们已经可以大致看到 <code>-[LSGLPreviewView newFrameReadyAtTime:atIndex:]</code> 这个方法的实现逻辑了。</p>
<p><img alt="image-20220106174837906" loading="lazy" src="/image/iOS-restore-symbol/image-20220106174837906.png"></p>
<h2 id="三暂未解决问题">三、暂未解决问题</h2>
<ol>
<li>Swift、C/C++ 的无法恢复符号问题；</li>
<li>恢复符号后，Instruments 依然无法显示符号；</li>
</ol>
]]></content:encoded>
    </item>
    <item>
      <title>iOS 逆向入门 - 常用反逆向手段</title>
      <link>https://blog.axqora.com/posts/ios-reverse-anti/</link>
      <pubDate>Thu, 09 Dec 2021 00:00:00 +0000</pubDate>
      <guid>https://blog.axqora.com/posts/ios-reverse-anti/</guid>
      <description>&lt;p&gt;在「&lt;a href=&#34;https://blog.axqora.com/posts/ios-reverse-lldb-attach/&#34;&gt;iOS 逆向入门 - 绕过抖音反调试&lt;/a&gt;」中提到常用反调试手段，这里写下具体实现。&lt;/p&gt;
&lt;h2 id=&#34;一常用反逆向手段&#34;&gt;一、常用反逆向手段&lt;/h2&gt;
&lt;h3 id=&#34;1反调试ptrace&#34;&gt;1、反调试：ptrace&lt;/h3&gt;
&lt;p&gt;&lt;a href=&#34;https://blog.csdn.net/yiyaaixuexi/article/details/18222339&#34;&gt;&lt;code&gt;ptrace&lt;/code&gt;&lt;/a&gt; 被常用于防止 lldb 依附，原理是一个进程只能被 &lt;code&gt;ptrace&lt;/code&gt; 只能被一次，先于别人调用 &lt;code&gt;ptrace&lt;/code&gt; 则可以防止别人依附。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ptrace&lt;/code&gt; 有几种方式进行调用：&lt;/p&gt;
&lt;h4 id=&#34;1直接调用&#34;&gt;1）直接调用&lt;/h4&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;#import &amp;lt;sys/ptrace.h&amp;gt;

ptrace(PT_DENY_ATTACH,0,0,0);
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;iOS SDK 中不包含 ptrace.h 头文件，无法使用此方法调用，可使用以下方法。&lt;/p&gt;
&lt;h4 id=&#34;2通过-dlopen--dlsym-调用&#34;&gt;2）通过 &lt;code&gt;dlopen&lt;/code&gt; + &lt;code&gt;dlsym&lt;/code&gt; 调用&lt;/h4&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;#import &amp;lt;dlfcn.h&amp;gt;
#import &amp;lt;sys/types.h&amp;gt;
 
typedef int (*ptrace_ptr_t)(int _request, pid_t _pid, caddr_t _addr, int _data);
#if !defined(PT_DENY_ATTACH)
#define PT_DENY_ATTACH 31
#endif
 
void disable_attach() {
    void* handle = dlopen(0, RTLD_GLOBAL | RTLD_NOW);
    ptrace_ptr_t ptrace_ptr = dlsym(handle, &amp;#34;ptrace&amp;#34;);
    ptrace_ptr(PT_DENY_ATTACH, 0, 0, 0);
    dlclose(handle);
}
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id=&#34;3通过-syscall-调用&#34;&gt;3）通过 &lt;code&gt;syscall&lt;/code&gt; 调用&lt;/h4&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;#import &amp;lt;sys/syscall.h&amp;gt;

#if !defined(PT_DENY_ATTACH)
#define PT_DENY_ATTACH 31
#endif

void disable_attach() {
    syscall(SYS_ptrace, PT_DENY_ATTACH, 0, 0, 0); // #define SYS_ptrace 26
}
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id=&#34;4通过汇编调用&#34;&gt;4）通过汇编调用&lt;/h4&gt;
&lt;p&gt;内联 svc + ptrace 实现，相当于直接调用 &lt;code&gt;ptrace(PT_DENY_ATTACH,0,0,0);&lt;/code&gt;&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>在「<a href="https://blog.axqora.com/posts/ios-reverse-lldb-attach/">iOS 逆向入门 - 绕过抖音反调试</a>」中提到常用反调试手段，这里写下具体实现。</p>
<h2 id="一常用反逆向手段">一、常用反逆向手段</h2>
<h3 id="1反调试ptrace">1、反调试：ptrace</h3>
<p><a href="https://blog.csdn.net/yiyaaixuexi/article/details/18222339"><code>ptrace</code></a> 被常用于防止 lldb 依附，原理是一个进程只能被 <code>ptrace</code> 只能被一次，先于别人调用 <code>ptrace</code> 则可以防止别人依附。</p>
<p><code>ptrace</code> 有几种方式进行调用：</p>
<h4 id="1直接调用">1）直接调用</h4>
<pre tabindex="0"><code>#import &lt;sys/ptrace.h&gt;

ptrace(PT_DENY_ATTACH,0,0,0);
</code></pre><p>iOS SDK 中不包含 ptrace.h 头文件，无法使用此方法调用，可使用以下方法。</p>
<h4 id="2通过-dlopen--dlsym-调用">2）通过 <code>dlopen</code> + <code>dlsym</code> 调用</h4>
<pre tabindex="0"><code>#import &lt;dlfcn.h&gt;
#import &lt;sys/types.h&gt;
 
typedef int (*ptrace_ptr_t)(int _request, pid_t _pid, caddr_t _addr, int _data);
#if !defined(PT_DENY_ATTACH)
#define PT_DENY_ATTACH 31
#endif
 
void disable_attach() {
    void* handle = dlopen(0, RTLD_GLOBAL | RTLD_NOW);
    ptrace_ptr_t ptrace_ptr = dlsym(handle, &#34;ptrace&#34;);
    ptrace_ptr(PT_DENY_ATTACH, 0, 0, 0);
    dlclose(handle);
}
</code></pre><h4 id="3通过-syscall-调用">3）通过 <code>syscall</code> 调用</h4>
<pre tabindex="0"><code>#import &lt;sys/syscall.h&gt;

#if !defined(PT_DENY_ATTACH)
#define PT_DENY_ATTACH 31
#endif

void disable_attach() {
    syscall(SYS_ptrace, PT_DENY_ATTACH, 0, 0, 0); // #define SYS_ptrace 26
}
</code></pre><h4 id="4通过汇编调用">4）通过汇编调用</h4>
<p>内联 svc + ptrace 实现，相当于直接调用 <code>ptrace(PT_DENY_ATTACH,0,0,0);</code></p>
<pre tabindex="0"><code>static __attribute__((always_inline)) void disable_attach() {
#ifdef __arm64__
    __asm__(&#34;mov X0, #0x1F&#34;);
    __asm__(&#34;mov X1, #0&#34;);
    __asm__(&#34;mov X2, #0&#34;);
    __asm__(&#34;mov X3, #0&#34;);
    __asm__(&#34;mov X16, #0x1A&#34;);
    __asm__(&#34;svc #0x80&#34;);
#endif
}
</code></pre><p>内联 svc + syscall + ptrace 实现，相当于 <code>syscall(SYS_ptrace, PT_DENY_ATTACH, 0, 0, 0);</code></p>
<pre tabindex="0"><code>static __attribute__((always_inline)) void disable_attach() {
#ifdef __arm64__
    __asm__(&#34;mov X0, #0x1A&#34;);
    __asm__(&#34;mov X1, #0x1F&#34;);
    __asm__(&#34;mov X2, #0&#34;);
    __asm__(&#34;mov X3, #0&#34;);
    __asm__(&#34;mov X4, #0&#34;);
    __asm__(&#34;mov X16, #0&#34;);
    __asm__(&#34;svc #0x80&#34;);
#endif
}
</code></pre><h3 id="2sysctl">2、sysctl</h3>
<p>通过 sysctl 去查看当前进程的信息，看有没有这个标记位即可检查当前调试状态。</p>
<pre tabindex="0"><code>#import &lt;sys/sysctl.h&gt;

bool isBeingAttach() {
    size_t size = sizeof(struct kinfo_proc);
    struct kinfo_proc info;
    int ret = 0, name[4];
    memset(&amp;info, 0, sizeof(struct kinfo_proc));
    
    name[0] = CTL_KERN;
    name[1] = KERN_PROC;
    name[2] = KERN_PROC_PID;
    name[3] = getpid();
    
    if (ret == (sysctl(name, 4, &amp;info, &amp;size, NULL, 0))) {
        return ret != 0;
    }
    return (info.kp_proc.p_flag &amp; P_TRACED) ? YES : NO;
}
</code></pre>]]></content:encoded>
    </item>
    <item>
      <title>iOS 逆向入门 - 实现 TikTok 自动播放下一个视频</title>
      <link>https://blog.axqora.com/posts/ios-reverse-auto-next-video/</link>
      <pubDate>Sat, 18 Sep 2021 00:00:00 +0000</pubDate>
      <guid>https://blog.axqora.com/posts/ios-reverse-auto-next-video/</guid>
      <description>&lt;h2 id=&#34;一背景&#34;&gt;一、背景&lt;/h2&gt;
&lt;p&gt;经过之前对 iOS 逆向的初步了解，想了个实现 TikTok 自动播放下一个视频的小需求实践一下。&lt;/p&gt;
&lt;h2 id=&#34;二实现过程&#34;&gt;二、实现过程&lt;/h2&gt;
&lt;h3 id=&#34;1使用-class-dump-工具获取类信息&#34;&gt;1、使用 class-dump 工具获取类信息&lt;/h3&gt;
&lt;p&gt;class-dump 工具通过解析 MachO 文件生成类信息，包括 OC 方法、属性、成员变量等。&lt;/p&gt;
&lt;p&gt;有几个点需要留意：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;C 语言函数无法 dump；&lt;/li&gt;
&lt;li&gt;OC 方法的参数如果是对象类型，则只会显示 id；&lt;/li&gt;
&lt;li&gt;dump 出来的 OC 方法包含了 .h .m 文件里面的。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;2通过视图堆栈找出对应文件&#34;&gt;2、通过视图堆栈找出对应文件&lt;/h3&gt;
&lt;p&gt;&lt;img alt=&#34;88d9f22ea0434052530532d18a38a1cc&#34; loading=&#34;lazy&#34; src=&#34;https://blog.axqora.com/image/iOS-Reverse-auto-next-video//A7145663-03CA-4BAF-85D9-0422C4FAC13D.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;从视图堆栈里面可以很方便找出播放器相关的文件，从 class-dump 出来的文件中找到相关文件，在文件中找出相关方法。&lt;/p&gt;
&lt;p&gt;比如我需要实现自动播放下一个视频的话，分为两个步骤：1.监听播放完一个视频的事件；2.播放下一个视频。&lt;/p&gt;
&lt;p&gt;找到“播放下一个视频”的方法：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;我从视图堆栈里定位到 &lt;code&gt;AWEFeedTableViewController&lt;/code&gt; 类；&lt;/li&gt;
&lt;li&gt;从 class-dump 出来的文件中打开 &lt;code&gt;AWEFeedTableViewController.h&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;在文件中搜索 &lt;code&gt;NextVideo&lt;/code&gt;，很容易可以找到 &lt;code&gt;- (void)scrollToNextVideo;&lt;/code&gt; 方法。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;类似的方法找出“播放完一个视频的事件”。&lt;/p&gt;
&lt;h3 id=&#34;3快速验证&#34;&gt;3、快速验证&lt;/h3&gt;
&lt;p&gt;从上一步找到的类还有对应方法是否真的可以实现“播放下一个视频”呢？我们可以在代码中 Hook 对应方法验证，但是这样验证的方法比较低效。&lt;/p&gt;
&lt;p&gt;使用 &lt;a href=&#34;http://www.cycript.org/&#34;&gt;Cycript&lt;/a&gt; 可以动态调试 App，这里简单介绍如何通过 Cycript 快速验证：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;越狱手机&lt;/strong&gt;上通过 Cydia 安装 Cycript；&lt;/li&gt;
&lt;li&gt;通过 SSH 连接手机；&lt;/li&gt;
&lt;li&gt;找到进程：&lt;code&gt;root# ps -e | grep &#39;TikTok&#39;&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;依附到进程：&lt;code&gt;cycript -p 进程号&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;打印视图堆栈，输入命令：&lt;code&gt;UIApp.keyWindow.recursiveDescription()&lt;/code&gt; ；&lt;/li&gt;
&lt;li&gt;找到 &lt;code&gt;AWEFeedTableViewController&lt;/code&gt; 内存地址并执行 &lt;code&gt;[#0x1118da200 scrollToNextVideo]&lt;/code&gt;&lt;strong&gt;（内存地址前需要加 # 号）&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;此时发现 App 真的切换到了下一个视频，证明这个方法就是我们需要找的方法。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h2 id="一背景">一、背景</h2>
<p>经过之前对 iOS 逆向的初步了解，想了个实现 TikTok 自动播放下一个视频的小需求实践一下。</p>
<h2 id="二实现过程">二、实现过程</h2>
<h3 id="1使用-class-dump-工具获取类信息">1、使用 class-dump 工具获取类信息</h3>
<p>class-dump 工具通过解析 MachO 文件生成类信息，包括 OC 方法、属性、成员变量等。</p>
<p>有几个点需要留意：</p>
<ol>
<li>C 语言函数无法 dump；</li>
<li>OC 方法的参数如果是对象类型，则只会显示 id；</li>
<li>dump 出来的 OC 方法包含了 .h .m 文件里面的。</li>
</ol>
<h3 id="2通过视图堆栈找出对应文件">2、通过视图堆栈找出对应文件</h3>
<p><img alt="88d9f22ea0434052530532d18a38a1cc" loading="lazy" src="/image/iOS-Reverse-auto-next-video//A7145663-03CA-4BAF-85D9-0422C4FAC13D.png"></p>
<p>从视图堆栈里面可以很方便找出播放器相关的文件，从 class-dump 出来的文件中找到相关文件，在文件中找出相关方法。</p>
<p>比如我需要实现自动播放下一个视频的话，分为两个步骤：1.监听播放完一个视频的事件；2.播放下一个视频。</p>
<p>找到“播放下一个视频”的方法：</p>
<ol>
<li>我从视图堆栈里定位到 <code>AWEFeedTableViewController</code> 类；</li>
<li>从 class-dump 出来的文件中打开 <code>AWEFeedTableViewController.h</code>；</li>
<li>在文件中搜索 <code>NextVideo</code>，很容易可以找到 <code>- (void)scrollToNextVideo;</code> 方法。</li>
</ol>
<p>类似的方法找出“播放完一个视频的事件”。</p>
<h3 id="3快速验证">3、快速验证</h3>
<p>从上一步找到的类还有对应方法是否真的可以实现“播放下一个视频”呢？我们可以在代码中 Hook 对应方法验证，但是这样验证的方法比较低效。</p>
<p>使用 <a href="http://www.cycript.org/">Cycript</a> 可以动态调试 App，这里简单介绍如何通过 Cycript 快速验证：</p>
<ol>
<li><strong>越狱手机</strong>上通过 Cydia 安装 Cycript；</li>
<li>通过 SSH 连接手机；</li>
<li>找到进程：<code>root# ps -e | grep 'TikTok'</code>；</li>
<li>依附到进程：<code>cycript -p 进程号</code>；</li>
<li>打印视图堆栈，输入命令：<code>UIApp.keyWindow.recursiveDescription()</code> ；</li>
<li>找到 <code>AWEFeedTableViewController</code> 内存地址并执行 <code>[#0x1118da200 scrollToNextVideo]</code><strong>（内存地址前需要加 # 号）</strong></li>
</ol>
<p>此时发现 App 真的切换到了下一个视频，证明这个方法就是我们需要找的方法。</p>
<p><img alt="a2ec7e2196f6ed28cec07fe2ad68bb34" loading="lazy" src="/image/iOS-Reverse-auto-next-video//A9849641-1A7B-4C72-B772-D9503C9EE246.png"></p>
<h3 id="4实现">4、实现</h3>
<pre tabindex="0"><code>CHDeclareClass(AWEAwemePlayVideoViewController)
CHDeclareClass(AWEFeedTableViewController)

CHOptimizedMethod1(self, void, AWEAwemePlayVideoViewController, playerWillLoopPlaying, id, arg1) {
    [[NSNotificationCenter defaultCenter] postNotificationName:@&#34;DSScrollToNextVideo&#34; object:nil];
    CHSuper1(AWEAwemePlayVideoViewController, playerWillLoopPlaying, arg1);
}

CHOptimizedMethod0(self, void, AWEFeedTableViewController, _addNotifications) {
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(scrollToNextVideo) name:@&#34;DSScrollToNextVideo&#34; object:nil];
    CHSuper0(AWEFeedTableViewController, _addNotifications);
}

CHConstructor {
    CHLoadLateClass(AWEAwemePlayVideoViewController);
    CHLoadLateClass(AWEFeedTableViewController);
    
    CHHook1(AWEAwemePlayVideoViewController, playerWillLoopPlaying);
    CHHook0(AWEFeedTableViewController, _addNotifications);
}
</code></pre><h3 id="5优化">5、优化</h3>
<p>编译运行后即可以实现自动播放下一个视频了，但是还存在一些问题，比如短视频列表中可能存在广告、直播，这两个场景就不会触发“播放完一个视频的事件”，但是我们也可以使用类似的方法去解决。</p>
]]></content:encoded>
    </item>
    <item>
      <title>iOS 逆向入门 - 绕过抖音反调试</title>
      <link>https://blog.axqora.com/posts/ios-reverse-lldb-attach/</link>
      <pubDate>Wed, 15 Sep 2021 00:00:00 +0000</pubDate>
      <guid>https://blog.axqora.com/posts/ios-reverse-lldb-attach/</guid>
      <description>&lt;h2 id=&#34;一背景&#34;&gt;一、背景&lt;/h2&gt;
&lt;p&gt;在「iOS 逆向入门 - TikTok 调试」文章中介绍了使用 MonkeyDev 工具对 TikTok 进行调试。使用同样的方法对抖音进行调试的过程中遇到了几个问题，在这里记录一下。&lt;/p&gt;
&lt;h2 id=&#34;二动态库注入失败&#34;&gt;二、动态库注入失败&lt;/h2&gt;
&lt;p&gt;按照 TikTok 的方式运行后，发现控制台没有打印 &lt;code&gt;insert dylib success&lt;/code&gt; 的信息，猜测动态库注入失败了，果然在编译信息的输出中找到相关错误打印：&lt;/p&gt;
&lt;p&gt;&lt;img alt=&#34;165c3e57dcade6ada8d0a2fd28b85ee5&#34; loading=&#34;lazy&#34; src=&#34;https://blog.axqora.com/image/iOS-Reverse-LLDB-Attach/CE38BC32-EC74-44A1-89A3-7C92EEEB526A.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt=&#34;85bf3e1b4d094b2ccd415bd033a11a68&#34; loading=&#34;lazy&#34; src=&#34;https://blog.axqora.com/image/iOS-Reverse-LLDB-Attach/017557E8-7B56-4B01-ACA6-3D2ED70A9046.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;在 MonkeyDev 的 change.log 中找到信息，尝试恢复使用 &lt;a href=&#34;https://github.com/alexzielenski/optool&#34;&gt;optool&lt;/a&gt; 工具注入动态库。&lt;/p&gt;
&lt;p&gt;下载 optool 的二进制文件并复制到相应位置，然后修改以下代码：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# 注释原来的 MONKEYPARSER
# &amp;#34;$MONKEYPARSER&amp;#34; install -c load -p &amp;#34;@executable_path/Frameworks/lib&amp;#34;&amp;#34;${TARGET_NAME}&amp;#34;&amp;#34;Dylib.dylib&amp;#34; -t &amp;#34;${BUILD_APP_PATH}/${APP_BINARY}&amp;#34;
# 修改为 optool
OPTOOL=&amp;#34;${MONKEYDEV_PATH}/bin/optool&amp;#34;
&amp;#34;$OPTOOL&amp;#34; install -c load -p &amp;#34;@executable_path/Frameworks/lib&amp;#34;&amp;#34;${TARGET_NAME}&amp;#34;&amp;#34;Dylib.dylib&amp;#34; -t &amp;#34;${BUILD_APP_PATH}/${APP_BINARY}&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;img alt=&#34;d9d23d29c9046b42770d53780277c92a&#34; loading=&#34;lazy&#34; src=&#34;https://blog.axqora.com/image/iOS-Reverse-LLDB-Attach/1BE048B2-B962-4D07-B430-18E2371624E6.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;重新编译运行后可发现控制台中出现 &lt;code&gt;insert dylib success&lt;/code&gt; 信息，证明动态库已经注入成功。&lt;/p&gt;
&lt;p&gt;&lt;img alt=&#34;8c7632998f7a2a5e45a9073f24fc2ae4&#34; loading=&#34;lazy&#34; src=&#34;https://blog.axqora.com/image/iOS-Reverse-LLDB-Attach/BF45431C-F1CD-45C2-988C-45131AFD9809.png&#34;&gt;&lt;/p&gt;
&lt;h2 id=&#34;三绕过反调试&#34;&gt;三、绕过反调试&lt;/h2&gt;
&lt;p&gt;动态库注入成功后，发现控制台没有了任何输出，且 lldb 已经断开，证明抖音使用了某种反调试手段。&lt;/p&gt;
&lt;h3 id=&#34;1反调试常规手段&#34;&gt;1、反调试常规手段&lt;/h3&gt;
&lt;h4 id=&#34;1ptrace&#34;&gt;1）ptrace&lt;/h4&gt;
&lt;p&gt;&lt;a href=&#34;https://blog.csdn.net/yiyaaixuexi/article/details/18222339&#34;&gt;&lt;code&gt;ptrace&lt;/code&gt;&lt;/a&gt; 被常用于防止 lldb 依附，原理是一个进程只能被 &lt;code&gt;ptrace&lt;/code&gt; 只能被一次，先于别人调用 &lt;code&gt;ptrace&lt;/code&gt; 则可以防止别人依附。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h2 id="一背景">一、背景</h2>
<p>在「iOS 逆向入门 - TikTok 调试」文章中介绍了使用 MonkeyDev 工具对 TikTok 进行调试。使用同样的方法对抖音进行调试的过程中遇到了几个问题，在这里记录一下。</p>
<h2 id="二动态库注入失败">二、动态库注入失败</h2>
<p>按照 TikTok 的方式运行后，发现控制台没有打印 <code>insert dylib success</code> 的信息，猜测动态库注入失败了，果然在编译信息的输出中找到相关错误打印：</p>
<p><img alt="165c3e57dcade6ada8d0a2fd28b85ee5" loading="lazy" src="/image/iOS-Reverse-LLDB-Attach/CE38BC32-EC74-44A1-89A3-7C92EEEB526A.png"></p>
<p><img alt="85bf3e1b4d094b2ccd415bd033a11a68" loading="lazy" src="/image/iOS-Reverse-LLDB-Attach/017557E8-7B56-4B01-ACA6-3D2ED70A9046.png"></p>
<p>在 MonkeyDev 的 change.log 中找到信息，尝试恢复使用 <a href="https://github.com/alexzielenski/optool">optool</a> 工具注入动态库。</p>
<p>下载 optool 的二进制文件并复制到相应位置，然后修改以下代码：</p>
<pre tabindex="0"><code># 注释原来的 MONKEYPARSER
# &#34;$MONKEYPARSER&#34; install -c load -p &#34;@executable_path/Frameworks/lib&#34;&#34;${TARGET_NAME}&#34;&#34;Dylib.dylib&#34; -t &#34;${BUILD_APP_PATH}/${APP_BINARY}&#34;
# 修改为 optool
OPTOOL=&#34;${MONKEYDEV_PATH}/bin/optool&#34;
&#34;$OPTOOL&#34; install -c load -p &#34;@executable_path/Frameworks/lib&#34;&#34;${TARGET_NAME}&#34;&#34;Dylib.dylib&#34; -t &#34;${BUILD_APP_PATH}/${APP_BINARY}&#34;
</code></pre><p><img alt="d9d23d29c9046b42770d53780277c92a" loading="lazy" src="/image/iOS-Reverse-LLDB-Attach/1BE048B2-B962-4D07-B430-18E2371624E6.png"></p>
<p>重新编译运行后可发现控制台中出现 <code>insert dylib success</code> 信息，证明动态库已经注入成功。</p>
<p><img alt="8c7632998f7a2a5e45a9073f24fc2ae4" loading="lazy" src="/image/iOS-Reverse-LLDB-Attach/BF45431C-F1CD-45C2-988C-45131AFD9809.png"></p>
<h2 id="三绕过反调试">三、绕过反调试</h2>
<p>动态库注入成功后，发现控制台没有了任何输出，且 lldb 已经断开，证明抖音使用了某种反调试手段。</p>
<h3 id="1反调试常规手段">1、反调试常规手段</h3>
<h4 id="1ptrace">1）ptrace</h4>
<p><a href="https://blog.csdn.net/yiyaaixuexi/article/details/18222339"><code>ptrace</code></a> 被常用于防止 lldb 依附，原理是一个进程只能被 <code>ptrace</code> 只能被一次，先于别人调用 <code>ptrace</code> 则可以防止别人依附。</p>
<p><code>ptrace</code> 有几种方式进行调用：</p>
<ol>
<li>通过 <code>dlsym</code> 调用</li>
<li>通过汇编调用；</li>
<li>通过 <code>syscall</code> 调用，<code>syscall(26,31,0,0)</code>。</li>
</ol>
<h4 id="2sysctl">2）sysctl</h4>
<p>通过 sysctl 去查看当前进程的信息，看有没有这个标记位即可检查当前调试状态。</p>
<pre tabindex="0"><code>bool isBeingAttach() {
    size_t size = sizeof(struct kinfo_proc);
    struct kinfo_proc info;
    int ret = 0, name[4];
    memset(&amp;info, 0, sizeof(struct kinfo_proc));
    
    name[0] = CTL_KERN;
    name[1] = KERN_PROC;
    name[2] = KERN_PROC_PID;
    name[3] = getpid();
    
    if (ret == (sysctl(name, 4, &amp;info, &amp;size, NULL, 0))) {
        return ret != 0;
    }
    return (info.kp_proc.p_flag &amp; P_TRACED) ? YES : NO;
}
</code></pre><h3 id="2反反调试常规手段">2、反反调试常规手段</h3>
<p>我们知道了反调试的常用函数，我们可以使用 fishhook hook 这些函数，达到反反调试的效果。</p>
<p>MonkeyDev 已经为我们写好了相关实现，我们打开注释即可：</p>
<p><img alt="cf4d6c5947ba16867fd9321c3eeb63fa" loading="lazy" src="/image/iOS-Reverse-LLDB-Attach/92A6694D-E2A9-46C1-A079-1DC1D0CB5E5F.png"></p>
<h3 id="3进一步">3、进一步</h3>
<p>打开上面的注释重新运行后，lldb 依然依附失败，说明抖音有更进一步的反调试手段。</p>
<p>通过网上查阅资料，有以下解题套路：</p>
<h4 id="1判断反调试代码的位置">1）判断反调试代码的位置</h4>
<p>Xcode 上添加 AppDelegate 的符号断点：</p>
<pre tabindex="0"><code>-[AppDelegate application:didFinishLaunchingWithOptions:]
</code></pre><p><img alt="f58d783f52007104ea94c8c2d913e0f9" loading="lazy" src="/image/iOS-Reverse-LLDB-Attach/0BCB2D87-9861-45AA-B2C7-7DA22F269189.png"></p>
<p>运行后断点依然失败，证明反调试代码在 <code>didFinishLaunchingWithOptions</code> 之前。</p>
<h4 id="2利用-ida-进行分析">2）利用 IDA 进行分析</h4>
<p><img alt="2615ad09f676c83350c7956e316491cb" loading="lazy" src="/image/iOS-Reverse-LLDB-Attach/6F5336B4-F8A5-4EDC-A0FD-AFCD834D0E40.png"></p>
<p>把二进制文件拖入 IDA 后进行分析，在 start 函数中的 0x0000000109640CF4~0x0000000109640D00 就是反调试代码，相当于调用了上面提到的 <code>syscall(26,31,0,0)</code>，最终就是调用了 <code>ptrace</code> 函数。</p>
<h4 id="3绕过反调试代码">3）绕过反调试代码</h4>
<p><img alt="2b9359f745f52c2f10a05678678f2f2b" loading="lazy" src="/image/iOS-Reverse-LLDB-Attach/486F79C9-8EA1-4AE3-B48B-0349582C6074.png"></p>
<ol>
<li>lldb 中执行 <code>image list -o -f</code> 打印链接的库，其中第一个地址则是 ASLR 的偏移地址。</li>
</ol>
<p>运行时基地址(0x000000010BEC8D00) = ASLR 偏移地址(0x0000000002888000) + 基地址(0x0000000109640D00)</p>
<ol start="2">
<li>
<p>添加地址断点，<code>breakpoint set -a 0x000000010BEC8D00</code>；</p>
</li>
<li>
<p>断点到该地址后，执行 <code>register write $x1 0</code>，修改 x1 寄存器值。</p>
</li>
</ol>
<p>继续运行后则发现已经成功依附到 lldb。</p>
<p><strong>ASLR 偏移地址每次 App 运行都会被修改，所以每次运行都要进行上面的操作。</strong></p>
<p><img alt="5ffff7707fb245d8158b11fa60fe4f37" loading="lazy" src="/image/iOS-Reverse-LLDB-Attach/5BC9360A-3986-459C-8B0C-D6365855DAFD.png">
<img alt="6524af2a0852d90cf54e5c18233ab309" loading="lazy" src="/image/iOS-Reverse-LLDB-Attach/C7469C24-F64A-48D7-8114-B6E26E3E2DDF.png"></p>
]]></content:encoded>
    </item>
    <item>
      <title>iOS 上的 adb 工具 - libimobiledevice</title>
      <link>https://blog.axqora.com/posts/ios-libimobiledevice/</link>
      <pubDate>Mon, 06 Sep 2021 00:00:00 +0000</pubDate>
      <guid>https://blog.axqora.com/posts/ios-libimobiledevice/</guid>
      <description>&lt;h2 id=&#34;一简介&#34;&gt;一、简介&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;libimobiledevice&lt;/code&gt; 是一个与iOS设备通信的工具集，类似于安卓上的 &lt;code&gt;adb&lt;/code&gt; 工具，像爱思助手、PP助手等底层都是用的这个工具。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A library to communicate with services on iOS devices using native protocols.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;code&gt;ideviceinstaller&lt;/code&gt; 依赖于 &lt;code&gt;libimobiledevice&lt;/code&gt;，主要用于操作 App，如获取应用列表、安装卸载应用等。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A command-line application to manage apps and app archives on iOS devices.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;二安装&#34;&gt;二、安装&lt;/h2&gt;
&lt;p&gt;Mac 上可以直接利用 Homebrew 安装：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;// 安装 libimobiledevice
brew install libimobiledevice
// 安装 ideviceinstaller
brew install ideviceinstaller
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;三使用&#34;&gt;三、使用&lt;/h2&gt;
&lt;p&gt;下面介绍常用的命令：&lt;/p&gt;
&lt;h3 id=&#34;1-idevice_id&#34;&gt;1. idevice_id&lt;/h3&gt;
&lt;p&gt;打印连接的设备的 UUID&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;➜  ~ idevice_id -l
0e68ed5333802b17d5ac62bfa708619de597eef2
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;2-idevicecrashreport&#34;&gt;2. idevicecrashreport&lt;/h3&gt;
&lt;p&gt;把设备上的崩溃报告移到指定文件夹。&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;➜  ~ idevicecrashreport -u 0e68ed5333802b17d5ac62bfa708619de597eef2 crash
Move: /com.apple.appstored/appstored.log
Move: /Retired/rtcreportingd_2021-09-02-17-17-59_messageLog.ips
Move: /Retired/liveshow-2021-08-31-165309.ips
Move: /Retired/log-aggregated-2021-08-31-080228.ips
...
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;3-idevicedate&#34;&gt;3. idevicedate&lt;/h3&gt;
&lt;p&gt;获取或者设置设备时间。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h2 id="一简介">一、简介</h2>
<p><code>libimobiledevice</code> 是一个与iOS设备通信的工具集，类似于安卓上的 <code>adb</code> 工具，像爱思助手、PP助手等底层都是用的这个工具。</p>
<blockquote>
<p>A library to communicate with services on iOS devices using native protocols.</p>
</blockquote>
<p><code>ideviceinstaller</code> 依赖于 <code>libimobiledevice</code>，主要用于操作 App，如获取应用列表、安装卸载应用等。</p>
<blockquote>
<p>A command-line application to manage apps and app archives on iOS devices.</p>
</blockquote>
<h2 id="二安装">二、安装</h2>
<p>Mac 上可以直接利用 Homebrew 安装：</p>
<pre tabindex="0"><code>// 安装 libimobiledevice
brew install libimobiledevice
// 安装 ideviceinstaller
brew install ideviceinstaller
</code></pre><h2 id="三使用">三、使用</h2>
<p>下面介绍常用的命令：</p>
<h3 id="1-idevice_id">1. idevice_id</h3>
<p>打印连接的设备的 UUID</p>
<pre tabindex="0"><code>➜  ~ idevice_id -l
0e68ed5333802b17d5ac62bfa708619de597eef2
</code></pre><h3 id="2-idevicecrashreport">2. idevicecrashreport</h3>
<p>把设备上的崩溃报告移到指定文件夹。</p>
<pre tabindex="0"><code>➜  ~ idevicecrashreport -u 0e68ed5333802b17d5ac62bfa708619de597eef2 crash
Move: /com.apple.appstored/appstored.log
Move: /Retired/rtcreportingd_2021-09-02-17-17-59_messageLog.ips
Move: /Retired/liveshow-2021-08-31-165309.ips
Move: /Retired/log-aggregated-2021-08-31-080228.ips
...
</code></pre><h3 id="3-idevicedate">3. idevicedate</h3>
<p>获取或者设置设备时间。</p>
<pre tabindex="0"><code>➜  ~ idevicedate
Thu Sep  2 17:21:12 CST 2021
</code></pre><h3 id="4-idevicediagnostics">4. idevicediagnostics</h3>
<p>获取设备的诊断信息，重启设备、关闭设备等。</p>
<pre tabindex="0"><code>➜  ~ idevicediagnostics diagnostics
➜  ~ idevicediagnostics shutdown
➜  ~ idevicediagnostics restart
</code></pre><h3 id="5-ideviceinfo">5. ideviceinfo</h3>
<p>获取设备信息。</p>
<pre tabindex="0"><code>➜  ~ ideviceinfo
ActivationState: Activated
ActivationStateAcknowledged: true
BasebandActivationTicketVersion: V2
BasebandCertId: 2315222105
BasebandChipID: 241889
...
</code></pre><h3 id="6-idevicename">6. idevicename</h3>
<p>获取或设置设备名称</p>
<pre tabindex="0"><code>➜  ~ idevicename iPhone🤓
device name set to &#39;iPhone🤓&#39;
</code></pre><h3 id="7-ideviceprovision">7. ideviceprovision</h3>
<p>管理设备上的 provisioning profiles。</p>
<p>provisioning profiles 可以理解成包含了 <strong>证书、应用权限开关、appIDs、deviceIds等数据</strong> 的数据包。</p>
<pre tabindex="0"><code>➜  ~ ideviceprovision list
Device has 20 provisioning profiles installed:
50c80637-31ce-4229-805b-97f84cc0e17d - iOS Team Provisioning Profile: test.lydi.shareExtensions
...
</code></pre><h3 id="8-idevicescreenshot">8. idevicescreenshot</h3>
<p>设备截图（<strong>需要设备已越狱</strong>），默认存放路径：<code>~/screenshot-2021-09-06-03-29-15.png</code></p>
<pre tabindex="0"><code>➜  ~ idevicescreenshot
Screenshot saved to screenshot-2021-09-06-03-29-15.png
</code></pre><h3 id="9-idevicesetlocation">9. idevicesetlocation</h3>
<p>设置设备的经纬度，可打开指南针 app 验证是否设置成功。如设置为北京的经纬度：</p>
<pre tabindex="0"><code>➜  ~ idevicesetlocation -- 39.9042 116.4074
➜  ~ idevicesetlocation reset
</code></pre><h3 id="10-idevicesyslog">10. idevicesyslog</h3>
<p>系统日志。</p>
<pre tabindex="0"><code>➜  ~ idevicesyslog -u 0e68ed5333802b17d5ac62bfa708619de597eef2
[connected]
Sep  6 11:44:02 atc(Apps)[43] &lt;Notice&gt;: found 42 apps using 4782030848 bytes (0 purgeable)
Sep  6 11:44:02 atc(LogsPlugin)[43] &lt;Notice&gt;: found 11 files [6361088 bytes] of logs in &#39;/private/var/log/&#39;
Sep  6 11:44:02 atc(LogsPlugin)[43] &lt;Notice&gt;: found 24 files [5181440 bytes] of logs in &#39;/private/var/logs/&#39;
Sep  6 11:44:02 atc(LogsPlugin)[43] &lt;Notice&gt;: found 11 files [4096 bytes] of logs in &#39;/private/var/wireless/Library/Logs/&#39;
...
</code></pre><h3 id="11-ideviceinstaller">11. ideviceinstaller</h3>
<p>获取设备应用列表。</p>
<pre tabindex="0"><code>➜  ~ ideviceinstaller --list-apps
CFBundleIdentifier, CFBundleVersion, CFBundleDisplayName
com.buuuk.Klok, &#34;81&#34;, &#34;Klok&#34;
com.tencent.xin, &#34;7.0.4.40&#34;, &#34;微信&#34;
...
</code></pre><p>安装 ipa 包:</p>
<pre tabindex="0"><code>➜  ~ ideviceinstaller --install /Users/oudushu/Downloads/微信.ipa
WARNING: could not locate iTunesMetadata.plist in archive!
WARNING: could not locate Payload/liveshow.app/SC_Info/liveshow.sinf in archive!
Copying &#39;/Users/oudushu/Downloads/微信.ipa&#39; to device... DONE.
Installing &#39;微信&#39;
Install: CreatingStagingDirectory (5%)
Install: ExtractingPackage (15%)
Install: InspectingPackage (20%)
Install: TakingInstallLock (20%)
Install: PreflightingApplication (30%)
Install: InstallingEmbeddedProfile (30%)
Install: VerifyingApplication (40%)
Install: CreatingContainer (50%)
Install: InstallingApplication (60%)
Install: PostflightingApplication (70%)
Install: SandboxingApplication (80%)
Install: GeneratingApplicationMap (90%)
Install: Complete
</code></pre><p>卸载应用:</p>
<pre tabindex="0"><code>➜  ~ ideviceinstaller --uninstall 微信                                                      (ruby-2.6.5)
Uninstalling &#39;微信&#39;
Uninstall: RemovingApplication (50%)
Uninstall: GeneratingApplicationMap (90%)
Uninstall: Complete
</code></pre>]]></content:encoded>
    </item>
    <item>
      <title>iOS 沙盒挂载工具 - ifuse</title>
      <link>https://blog.axqora.com/posts/ios-mount-tool-ifuse/</link>
      <pubDate>Sun, 05 Sep 2021 00:00:00 +0000</pubDate>
      <guid>https://blog.axqora.com/posts/ios-mount-tool-ifuse/</guid>
      <description>&lt;h2 id=&#34;一ifuse&#34;&gt;一、ifuse&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/libimobiledevice/ifuse&#34;&gt;ifuse&lt;/a&gt; 是一个文件系统工具，在未越狱的设备上可以挂载 App 的文件夹，在已越狱的设备上可以挂载根文件夹&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This project allows mounting various directories of an iOS device locally using the FUSE file system interface.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;二安装&#34;&gt;二、安装&lt;/h2&gt;
&lt;p&gt;利用 Homebrew 进行安装：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;// 期间可能需要输入电脑密码
brew install macfuse
brew install ifuse
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;安装时可能出现错误：&lt;/p&gt;
&lt;p&gt;&lt;img alt=&#34;a146fb09dc7470ed5292740e7c6b29cc&#34; loading=&#34;lazy&#34; src=&#34;https://blog.axqora.com/image/iOS-mount-tool-ifuse/5D78A86E-8916-4648-99BD-EC7F474C62D0.png&#34;&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;// 编辑 ifuse 的安装配置
vim `brew formula ifuse`

// 注释或删除以下几行
#  on_macos do
#    disable! date: &amp;#34;2021-04-08&amp;#34;, because: &amp;#34;requires closed-source macFUSE&amp;#34;
#  end

// 重新安装
brew install ifuse
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;img alt=&#34;aca212e2910b38571d8a47a185bdbfe7&#34; loading=&#34;lazy&#34; src=&#34;https://blog.axqora.com/image/iOS-mount-tool-ifuse/FAACA319-84CA-4DAF-A5C6-AA3AC71BD6C4.png&#34;&gt;&lt;/p&gt;
&lt;h2 id=&#34;三使用&#34;&gt;三、使用&lt;/h2&gt;
&lt;h3 id=&#34;1-挂载&#34;&gt;1. 挂载&lt;/h3&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;// 新建挂载点文件夹
mkdir ~/Sandbox

// 设置挂载点
ifuse --container ifuse.test ~/Sandbox
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;此时 &lt;code&gt;ifuse.test&lt;/code&gt; 的沙盒被成功挂载到 &lt;code&gt;Sandbox&lt;/code&gt; 文件夹：&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h2 id="一ifuse">一、ifuse</h2>
<p><a href="https://github.com/libimobiledevice/ifuse">ifuse</a> 是一个文件系统工具，在未越狱的设备上可以挂载 App 的文件夹，在已越狱的设备上可以挂载根文件夹</p>
<blockquote>
<p>This project allows mounting various directories of an iOS device locally using the FUSE file system interface.</p>
</blockquote>
<h2 id="二安装">二、安装</h2>
<p>利用 Homebrew 进行安装：</p>
<pre tabindex="0"><code>// 期间可能需要输入电脑密码
brew install macfuse
brew install ifuse
</code></pre><p>安装时可能出现错误：</p>
<p><img alt="a146fb09dc7470ed5292740e7c6b29cc" loading="lazy" src="/image/iOS-mount-tool-ifuse/5D78A86E-8916-4648-99BD-EC7F474C62D0.png"></p>
<pre tabindex="0"><code>// 编辑 ifuse 的安装配置
vim `brew formula ifuse`

// 注释或删除以下几行
#  on_macos do
#    disable! date: &#34;2021-04-08&#34;, because: &#34;requires closed-source macFUSE&#34;
#  end

// 重新安装
brew install ifuse
</code></pre><p><img alt="aca212e2910b38571d8a47a185bdbfe7" loading="lazy" src="/image/iOS-mount-tool-ifuse/FAACA319-84CA-4DAF-A5C6-AA3AC71BD6C4.png"></p>
<h2 id="三使用">三、使用</h2>
<h3 id="1-挂载">1. 挂载</h3>
<pre tabindex="0"><code>// 新建挂载点文件夹
mkdir ~/Sandbox

// 设置挂载点
ifuse --container ifuse.test ~/Sandbox
</code></pre><p>此时 <code>ifuse.test</code> 的沙盒被成功挂载到 <code>Sandbox</code> 文件夹：</p>
<p><img alt="ae545a6f0fd48b5e211502e8b50ca8ea" loading="lazy" src="/image/iOS-mount-tool-ifuse/44D41EBC-E7E8-4FF5-818A-6C8220008CB8.png"></p>
<h3 id="2-可能出现错误">2. 可能出现错误：</h3>
<p><img alt="e2fcc9c3d296790a5aea433daea117eb" loading="lazy" src="/image/iOS-mount-tool-ifuse/D8AC4BB8-3058-4653-B7F4-AE60A36DBA77.png"></p>
<p><img alt="ad94f6eb0e29eca07f11257be5024b60" loading="lazy" src="/image/iOS-mount-tool-ifuse/479E3D4C-A947-46CA-9C33-2769B610E613.png"></p>
<p>需要在 <code>Info.plist</code> 添加 key <code>UIFileSharingEnabled</code> value <code>YES</code>，重新编译即可。</p>
]]></content:encoded>
    </item>
    <item>
      <title>iOS 逆向入门 - TikTok 调试</title>
      <link>https://blog.axqora.com/posts/ios-reverse-hook/</link>
      <pubDate>Fri, 03 Sep 2021 00:00:00 +0000</pubDate>
      <guid>https://blog.axqora.com/posts/ios-reverse-hook/</guid>
      <description>&lt;h2 id=&#34;一monkeydev&#34;&gt;一、MonkeyDev&lt;/h2&gt;
&lt;h3 id=&#34;1简介&#34;&gt;1、简介&lt;/h3&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/AloneMonkey/MonkeyDev&#34;&gt;MonkeyDev&lt;/a&gt; 是一个工具集合，基于 &lt;a href=&#34;https://github.com/kokoabim/iOSOpenDev&#34;&gt;iOSOpenDev&lt;/a&gt; 进行升级并集成到 Xcode 里面，可以用来开发越狱机器的插件，可以很方便地注入动态库并重签名安装到非越狱机器，堪称神器。&lt;/p&gt;
&lt;h3 id=&#34;2安装&#34;&gt;2、安装&lt;/h3&gt;
&lt;p&gt;参考官方文档进行安装：&lt;a href=&#34;https://github.com/AloneMonkey/MonkeyDev/wiki&#34;&gt;https://github.com/AloneMonkey/MonkeyDev/wiki&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&#34;3开始使用&#34;&gt;3、开始使用&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;新建 MonkeyApp 项目。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img alt=&#34;0042e463aedfee90fd1ac2dc4fa6ac8e&#34; loading=&#34;lazy&#34; src=&#34;https://blog.axqora.com/image/iOS-Reverse-hook/4A1EF4A6-E825-4D9D-ACDF-CDB0216225AE.png&#34;&gt;&lt;/p&gt;
&lt;ol start=&#34;2&#34;&gt;
&lt;li&gt;拖入 &lt;em&gt;&lt;strong&gt;已砸壳&lt;/strong&gt;&lt;/em&gt; 的 ipa 包到指定文件夹&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img alt=&#34;cbdfe51ba5f5faeee12318e6ac9ad6de&#34; loading=&#34;lazy&#34; src=&#34;https://blog.axqora.com/image/iOS-Reverse-hook/B18B3CBA-9C76-4C70-B9B5-463E11757ED2.png&#34;&gt;&lt;/p&gt;
&lt;ol start=&#34;3&#34;&gt;
&lt;li&gt;配置开发证书。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img alt=&#34;c4fe4957ddcbaea66aa0747783c62fcb&#34; loading=&#34;lazy&#34; src=&#34;https://blog.axqora.com/image/iOS-Reverse-hook/7CB74642-3748-4EB7-9581-1EAEFAC85AE2.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;如无意外即可运行到手机上。&lt;/p&gt;
&lt;h3 id=&#34;4其它运行问题&#34;&gt;4、其它运行问题&lt;/h3&gt;
&lt;h4 id=&#34;1-链接错误&#34;&gt;1. 链接错误&lt;/h4&gt;
&lt;p&gt;&lt;img alt=&#34;6d63a3425c582d18ec96bb089cf07787&#34; loading=&#34;lazy&#34; src=&#34;https://blog.axqora.com/image/iOS-Reverse-hook/646A6B61-2468-4C13-AB09-11E775477602.png&#34;&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;ld: file not found: /usr/lib/libstdc++.dylib
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这是由于 Xcode 10 开始就不再集成 &lt;code&gt;libstdc++&lt;/code&gt; 库，需要把相关的库文件重新添加到对应的路径。&lt;/p&gt;
&lt;p&gt;使用 GitHub 上的这个库 &lt;a href=&#34;https://github.com/devdawei/libstdc-&#34;&gt;https://github.com/devdawei/libstdc-&lt;/a&gt; 可以比较方便地添加  &lt;code&gt;libstdc++&lt;/code&gt; 库。&lt;/p&gt;
&lt;h4 id=&#34;2-bundle-id&#34;&gt;2. Bundle ID&lt;/h4&gt;
&lt;p&gt;TikTok 对 Bundle ID 应该有校验，用其它 Bundle ID 会导致无法联网，这时候可以在 &lt;code&gt;Build Settings&lt;/code&gt; 里面，找到 &lt;code&gt;MONKEYDEV_DEFAULT_BUNDLEID&lt;/code&gt; 设为 &lt;code&gt;YES&lt;/code&gt;，这时候重新编辑安装到手机即可。&lt;/p&gt;
&lt;h4 id=&#34;3-需要国外手机卡&#34;&gt;3. 需要国外手机卡&lt;/h4&gt;
&lt;p&gt;TikTok 对地区有限制，手机需要插上国外手机卡才能进行开播，这样对调试不太友好。&lt;/p&gt;
&lt;p&gt;CTCarrier 是iOS上获取运营商信息的系统框架，我们可以对其进行 Hook，使其返回指定运营商信息。&lt;/p&gt;
&lt;p&gt;得益于 MonkeyDev 维护了非越狱插件 CocoaPods 私有仓库（&lt;a href=&#34;https://github.com/AloneMonkey/MonkeyDevSpecs&#34;&gt;https://github.com/AloneMonkey/MonkeyDevSpecs&lt;/a&gt;），我们可以直接使用别人提供的插件，使用方法跟我们平时使用 CocoaPods 完全一致，添加对应的源地址即可：&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h2 id="一monkeydev">一、MonkeyDev</h2>
<h3 id="1简介">1、简介</h3>
<p><a href="https://github.com/AloneMonkey/MonkeyDev">MonkeyDev</a> 是一个工具集合，基于 <a href="https://github.com/kokoabim/iOSOpenDev">iOSOpenDev</a> 进行升级并集成到 Xcode 里面，可以用来开发越狱机器的插件，可以很方便地注入动态库并重签名安装到非越狱机器，堪称神器。</p>
<h3 id="2安装">2、安装</h3>
<p>参考官方文档进行安装：<a href="https://github.com/AloneMonkey/MonkeyDev/wiki">https://github.com/AloneMonkey/MonkeyDev/wiki</a></p>
<h3 id="3开始使用">3、开始使用</h3>
<ol>
<li>新建 MonkeyApp 项目。</li>
</ol>
<p><img alt="0042e463aedfee90fd1ac2dc4fa6ac8e" loading="lazy" src="/image/iOS-Reverse-hook/4A1EF4A6-E825-4D9D-ACDF-CDB0216225AE.png"></p>
<ol start="2">
<li>拖入 <em><strong>已砸壳</strong></em> 的 ipa 包到指定文件夹</li>
</ol>
<p><img alt="cbdfe51ba5f5faeee12318e6ac9ad6de" loading="lazy" src="/image/iOS-Reverse-hook/B18B3CBA-9C76-4C70-B9B5-463E11757ED2.png"></p>
<ol start="3">
<li>配置开发证书。</li>
</ol>
<p><img alt="c4fe4957ddcbaea66aa0747783c62fcb" loading="lazy" src="/image/iOS-Reverse-hook/7CB74642-3748-4EB7-9581-1EAEFAC85AE2.png"></p>
<p>如无意外即可运行到手机上。</p>
<h3 id="4其它运行问题">4、其它运行问题</h3>
<h4 id="1-链接错误">1. 链接错误</h4>
<p><img alt="6d63a3425c582d18ec96bb089cf07787" loading="lazy" src="/image/iOS-Reverse-hook/646A6B61-2468-4C13-AB09-11E775477602.png"></p>
<pre tabindex="0"><code>ld: file not found: /usr/lib/libstdc++.dylib
</code></pre><p>这是由于 Xcode 10 开始就不再集成 <code>libstdc++</code> 库，需要把相关的库文件重新添加到对应的路径。</p>
<p>使用 GitHub 上的这个库 <a href="https://github.com/devdawei/libstdc-">https://github.com/devdawei/libstdc-</a> 可以比较方便地添加  <code>libstdc++</code> 库。</p>
<h4 id="2-bundle-id">2. Bundle ID</h4>
<p>TikTok 对 Bundle ID 应该有校验，用其它 Bundle ID 会导致无法联网，这时候可以在 <code>Build Settings</code> 里面，找到 <code>MONKEYDEV_DEFAULT_BUNDLEID</code> 设为 <code>YES</code>，这时候重新编辑安装到手机即可。</p>
<h4 id="3-需要国外手机卡">3. 需要国外手机卡</h4>
<p>TikTok 对地区有限制，手机需要插上国外手机卡才能进行开播，这样对调试不太友好。</p>
<p>CTCarrier 是iOS上获取运营商信息的系统框架，我们可以对其进行 Hook，使其返回指定运营商信息。</p>
<p>得益于 MonkeyDev 维护了非越狱插件 CocoaPods 私有仓库（<a href="https://github.com/AloneMonkey/MonkeyDevSpecs">https://github.com/AloneMonkey/MonkeyDevSpecs</a>），我们可以直接使用别人提供的插件，使用方法跟我们平时使用 CocoaPods 完全一致，添加对应的源地址即可：</p>
<pre tabindex="0"><code>source &#39;https://github.com/AloneMonkey/MonkeyDevSpecs.git&#39;
source &#39;https://github.com/CocoaPods/Specs.git&#39;

use_frameworks! 

target &#39;TikTokDevDylib&#39; do
     pod &#39;FLEX&#39;
     pod &#39;MonkeyDevPod&#39;, &#39;2.0.0&#39;
     pod &#39;AYTikTokPod&#39;
     pod &#39;LookinServer&#39;
end
</code></pre><h3 id="5hook-相关函数">5、Hook 相关函数</h3>
<h4 id="1-objective-c">1. Objective-C</h4>
<p>Objective-C 是一门动态语言，可以利用运行时的特性，使用 Method Swizzling 技术进行 Hook。</p>
<p>在 MonkeyDev 上使用如下：</p>
<pre tabindex="0"><code>CHDeclareClass(AVCaptureSession)

CHMethod1(void, AVCaptureSession, setSessionPreset, NSString *, arg1) {
    NSLog(@&#34;---- - sessionPreset:%@&#34;, arg1);
    CHSuper1(AVCaptureSession, setSessionPreset, arg1);
}

CHConstructor {
    CHLoadLateClass(AVCaptureSession);
    CHHook1(AVCaptureSession, setSessionPreset);
}
</code></pre><h4 id="2-c-语言">2. C 语言</h4>
<p>C 语言是静态语言，编译后变量、函数都已确定，我们使用 fishhook 框架即可对<strong>系统函数</strong>进行 Hook，<a href="https://juejin.cn/post/6857699952563978247">fishhook 原理</a>。</p>
<p>使用如下：</p>
<pre tabindex="0"><code>static OSStatus (*oldVTDecompressionSessionCreate)(CM_NULLABLE CFAllocatorRef allocator, CM_NONNULL CMVideoFormatDescriptionRef videoFormatDescription, CM_NULLABLE CFDictionaryRef videoDecoderSpecification, CM_NULLABLE CFDictionaryRef destinationImageBufferAttributes, const VTDecompressionOutputCallbackRecord * CM_NULLABLE outputCallback, CM_RETURNS_RETAINED_PARAMETER CM_NULLABLE VTDecompressionSessionRef * CM_NONNULL decompressionSessionOut);

OSStatus myVTDecompressionSessionCreate(CM_NULLABLE CFAllocatorRef allocator, CM_NONNULL CMVideoFormatDescriptionRef videoFormatDescription, CM_NULLABLE CFDictionaryRef videoDecoderSpecification, CM_NULLABLE CFDictionaryRef destinationImageBufferAttributes, const VTDecompressionOutputCallbackRecord * CM_NULLABLE outputCallback, CM_RETURNS_RETAINED_PARAMETER CM_NULLABLE VTDecompressionSessionRef * CM_NONNULL decompressionSessionOut) {
    NSLog(@&#34;---- - myVTDecompressionSessionCreate&#34;);
    return oldVTDecompressionSessionCreate(allocator, videoFormatDescription, videoDecoderSpecification, destinationImageBufferAttributes, outputCallback, decompressionSessionOut);
}

CHConstructor {
    struct rebinding rebind;
    rebind.name = &#34;VTDecompressionSessionCreate&#34;;
    rebind.replacement = myVTDecompressionSessionCreate;
    rebind.replaced = (void *)&amp;oldVTDecompressionSessionCreate;
    
    struct rebinding rebs[] = {rebind};
    rebind_symbols(rebs, 3);
}
</code></pre><h3 id="6instruments">6、Instruments</h3>
<p><img alt="ca5d606df7ebcba1f8da9ec54b83b3cd" loading="lazy" src="/image/iOS-Reverse-hook/A92F1B8E-FB80-4A2B-8731-5A3D6F7EA69E.png"></p>
<p>可以使用 Instruments，但目前还无法查看符号。</p>
<h2 id="二frida">二、frida</h2>
<p>如果手机<strong>已经越狱</strong>，还可以使用 <a href="https://frida.re/">frida</a> 工具进行调试。</p>
<p><img alt="0b67784ebff3dde604b284656e549586" loading="lazy" src="/image/iOS-Reverse-hook/0544E047-4335-42E8-A1AA-48B8A4AB19BC.png"></p>
<h3 id="1-跟踪函数调用">1. 跟踪函数调用</h3>
<pre tabindex="0"><code>➜  ~ frida-trace -U TikTok -i &#34;VTCompressionSessionCreate&#34;
Instrumenting...
VTCompressionSessionCreate: Loaded handler at &#34;/Users/oudushu/__handlers__/VideoToolbox/VTCompressionSessionCreate.js&#34;
Started tracing 1 function. Press Ctrl+C to stop.
           /* TID 0x25203 */
 45773 ms  VTCompressionSessionCreate()
</code></pre><h3 id="2-获取设备进程信息">2. 获取设备进程信息</h3>
<pre tabindex="0"><code>➜  ~ frida-ps --usb
 PID  Name
----  ------------------------------------------------------------
1139      ACCHWComponentAuthService
1168      AccountExtension
1169      AppManagementExtension
 495      AppSSODaemon
  81      AppleCredentialManagerDaemon
1586      AssetCacheLocatorService
 112      BlueTool
4139      CAReportingService
</code></pre><h3 id="3-获取已连接的设备">3. 获取已连接的设备</h3>
<pre tabindex="0"><code>➜  ~ frida-ls-devices
Id                                        Type    Name
----------------------------------------  ------  ------------
local                                     local   Local System
58e668d38f015b99730912550655d9671f3185fb  usb     iPhone
socket                                    remote  Local Socket
</code></pre><h3 id="4-结束进程">4. 结束进程</h3>
<pre tabindex="0"><code>➜  ~ frida-kill -D 58e668d38f015b99730912550655d9671f3185fb 5136
</code></pre><h3 id="5-调试">5. 调试</h3>
<pre tabindex="0"><code>➜  ~ frida -U Safari浏览器
     ____
    / _  |   Frida 15.0.18 - A world-class dynamic instrumentation toolkit
   | (_| |
    &gt; _  |   Commands:
   /_/ |_|       help      -&gt; Displays the help system
   . . . .       object?   -&gt; Display information about &#39;object&#39;
   . . . .       exit/quit -&gt; Exit
   . . . .
   . . . .   More info at https://frida.re/docs/home/

[iPhone::Safari浏览器]-&gt; help
Help: #TODO :)
</code></pre><h2 id="三其它">三、其它</h2>
<p>目前还处于<strong>使用工具</strong>的阶段，需要往深挖的话需要了解相关技术的原理，这里先挖一些坑：</p>
<ol>
<li>class-dump 原理</li>
<li>restore-symbol 原理</li>
<li>fishhook 原理</li>
<li>动态库注入原理</li>
<li>重签名原理</li>
<li>&hellip;</li>
</ol>
]]></content:encoded>
    </item>
    <item>
      <title>C 语言中利用单引号表示整数</title>
      <link>https://blog.axqora.com/posts/c-int-express/</link>
      <pubDate>Wed, 01 Sep 2021 00:00:00 +0000</pubDate>
      <guid>https://blog.axqora.com/posts/c-int-express/</guid>
      <description>&lt;h2 id=&#34;背景&#34;&gt;背景&lt;/h2&gt;
&lt;p&gt;&lt;img alt=&#34;2c3521a4edff88f7854d7b0bc310f596&#34; loading=&#34;lazy&#34; src=&#34;https://blog.axqora.com/image/C-Int-Express/5341CE7D-7880-43CE-9C7B-C0799EB766A8.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;在进行与竟品的性能对比中，需要知道竟品的部分参数，与竟品对齐参数后再进行性能测试。&lt;/p&gt;
&lt;p&gt;在 hook TikTok 的摄像头输出参数时发现，&lt;code&gt;PixelFormatType&lt;/code&gt; 的值是整数 &lt;code&gt;875704422&lt;/code&gt;，这样的一串整数很难知道其含义。&lt;/p&gt;
&lt;h2 id=&#34;分析&#34;&gt;分析&lt;/h2&gt;
&lt;p&gt;&lt;img alt=&#34;a9ecba8f3f6a0b41acdcb63637f60e80&#34; loading=&#34;lazy&#34; src=&#34;https://blog.axqora.com/image/C-Int-Express/ABCC4B74-B0BC-42F5-A2F0-395E4D7E5F9F.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;从 key &lt;code&gt;PixelFormatType&lt;/code&gt; 可以猜出是 &lt;code&gt;kCVPixelFormatType&lt;/code&gt; 枚举中的一个，&lt;code&gt;kCVPixelFormatType&lt;/code&gt; 枚举中大部分的值是以 &lt;code&gt;&#39;420f&#39;&lt;/code&gt; 这样的单引号形式来表示，通过逐一对比知道，原来 &lt;code&gt;875704422&lt;/code&gt; 对应的就是 &lt;code&gt;kCVPixelFormatType_420YpCbCr8BiPlanarFullRange&lt;/code&gt; 的值，也就是 &lt;code&gt;&#39;420f&#39;&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;img alt=&#34;c0a3caf746deb9cd89ff242957ab9b9e&#34; loading=&#34;lazy&#34; src=&#34;https://blog.axqora.com/image/C-Int-Express/043D4388-A0A4-458D-85EB-2CC1E51E5877.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;那么，为什么 &lt;code&gt;875704422 == &#39;420f&#39;&lt;/code&gt; ?&lt;/p&gt;
&lt;p&gt;原来，可以通过 ascii 编码进行转换，以 &lt;code&gt;&#39;420f&#39;&lt;/code&gt; 为例：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;4/2/0/f&lt;/code&gt; 的 ascii 编码分别为 &lt;code&gt;52/50/48/102&lt;/code&gt; ，转换为二进制分别为 &lt;code&gt;00110100/00110010/00110000/01100110&lt;/code&gt;，合并起来 &lt;code&gt;00110100001100100011000001100110&lt;/code&gt; 转换为十进制，刚好就是 &lt;code&gt;875704422&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;img alt=&#34;c5094241625b01a9af3fd7c370e78bb8&#34; loading=&#34;lazy&#34; src=&#34;https://blog.axqora.com/image/C-Int-Express/19F1ED01-810C-4226-B46E-7184A3F7F5DF.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;为方便看出对应的字符串，写了以下转换算法：&lt;/p&gt;
&lt;h2 id=&#34;转换算法&#34;&gt;转换算法&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;NSString * changeToTypeStr(int value) {
    char *str = malloc(10);
    int count = 0;
    while (value &amp;gt; 0) {
        char ch = (char)(value &amp;amp; 0xFF);
        str[count] = ch;
        value = value &amp;gt;&amp;gt; 8;
        count ++;
    }
    if (count &amp;gt; 1) {
        int left = 0, right = count - 1;
        while (left &amp;lt; right) {
            char tmp = str[left];
            str[left] = str[right];
            str[right] = tmp;
            left ++;
            right --;
        }
    }
    str[count] = &amp;#39;\0&amp;#39;;
    NSString *res = [[NSString alloc] initWithUTF8String:str];
    free(str);
    return res;
}
&lt;/code&gt;&lt;/pre&gt;</description>
      <content:encoded><![CDATA[<h2 id="背景">背景</h2>
<p><img alt="2c3521a4edff88f7854d7b0bc310f596" loading="lazy" src="/image/C-Int-Express/5341CE7D-7880-43CE-9C7B-C0799EB766A8.png"></p>
<p>在进行与竟品的性能对比中，需要知道竟品的部分参数，与竟品对齐参数后再进行性能测试。</p>
<p>在 hook TikTok 的摄像头输出参数时发现，<code>PixelFormatType</code> 的值是整数 <code>875704422</code>，这样的一串整数很难知道其含义。</p>
<h2 id="分析">分析</h2>
<p><img alt="a9ecba8f3f6a0b41acdcb63637f60e80" loading="lazy" src="/image/C-Int-Express/ABCC4B74-B0BC-42F5-A2F0-395E4D7E5F9F.png"></p>
<p>从 key <code>PixelFormatType</code> 可以猜出是 <code>kCVPixelFormatType</code> 枚举中的一个，<code>kCVPixelFormatType</code> 枚举中大部分的值是以 <code>'420f'</code> 这样的单引号形式来表示，通过逐一对比知道，原来 <code>875704422</code> 对应的就是 <code>kCVPixelFormatType_420YpCbCr8BiPlanarFullRange</code> 的值，也就是 <code>'420f'</code>。</p>
<p><img alt="c0a3caf746deb9cd89ff242957ab9b9e" loading="lazy" src="/image/C-Int-Express/043D4388-A0A4-458D-85EB-2CC1E51E5877.png"></p>
<p>那么，为什么 <code>875704422 == '420f'</code> ?</p>
<p>原来，可以通过 ascii 编码进行转换，以 <code>'420f'</code> 为例：</p>
<p><code>4/2/0/f</code> 的 ascii 编码分别为 <code>52/50/48/102</code> ，转换为二进制分别为 <code>00110100/00110010/00110000/01100110</code>，合并起来 <code>00110100001100100011000001100110</code> 转换为十进制，刚好就是 <code>875704422</code>。</p>
<p><img alt="c5094241625b01a9af3fd7c370e78bb8" loading="lazy" src="/image/C-Int-Express/19F1ED01-810C-4226-B46E-7184A3F7F5DF.png"></p>
<p>为方便看出对应的字符串，写了以下转换算法：</p>
<h2 id="转换算法">转换算法</h2>
<pre tabindex="0"><code>NSString * changeToTypeStr(int value) {
    char *str = malloc(10);
    int count = 0;
    while (value &gt; 0) {
        char ch = (char)(value &amp; 0xFF);
        str[count] = ch;
        value = value &gt;&gt; 8;
        count ++;
    }
    if (count &gt; 1) {
        int left = 0, right = count - 1;
        while (left &lt; right) {
            char tmp = str[left];
            str[left] = str[right];
            str[right] = tmp;
            left ++;
            right --;
        }
    }
    str[count] = &#39;\0&#39;;
    NSString *res = [[NSString alloc] initWithUTF8String:str];
    free(str);
    return res;
}
</code></pre>]]></content:encoded>
    </item>
    <item>
      <title>iOS 逆向入门 - 砸壳</title>
      <link>https://blog.axqora.com/posts/ios-reverse-dump/</link>
      <pubDate>Mon, 30 Aug 2021 00:00:00 +0000</pubDate>
      <guid>https://blog.axqora.com/posts/ios-reverse-dump/</guid>
      <description>&lt;h2 id=&#34;一背景&#34;&gt;一、背景&lt;/h2&gt;
&lt;p&gt;iOS 手机通过 App Store 安装的应用都经过加密，无法直接进行逆向分析。我们需要对应用进行解密，称为“砸壳”。&lt;/p&gt;
&lt;p&gt;我手上的设备是 &lt;code&gt;iPhone 6s&lt;/code&gt;，系统是 &lt;code&gt;iOS 13.5.1&lt;/code&gt;&lt;/p&gt;
&lt;h2 id=&#34;二手机越狱&#34;&gt;二、手机越狱&lt;/h2&gt;
&lt;p&gt;&lt;img alt=&#34;7438f42620d7b110699c27f2a1e92ad2&#34; loading=&#34;lazy&#34; src=&#34;https://blog.axqora.com/image/iOS-Reverse-dump/DBDC0602-2AEE-468C-BB0A-D74996B39C97.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;安装爱思助手，一键越狱的工具里面有很多个工具（不一定需要安装爱思助手，可以直接从官网下载越狱工具）。&lt;/p&gt;
&lt;p&gt;这里主要推荐两个：一个是通过硬件漏洞进行越狱的 &lt;a href=&#34;https://checkra.in/&#34;&gt;checkra1n&lt;/a&gt;；一个是通过软件漏洞进行越狱的 &lt;a href=&#34;https://unc0ver.dev/&#34;&gt;Unc0ver&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;相对来说通过硬件漏洞进行越狱的 checkra1n 会比较稳定，我使用的是 checkra1n。&lt;/p&gt;
&lt;p&gt;&lt;img alt=&#34;b8e13fe10d7072773197a107aa6c36bd&#34; loading=&#34;lazy&#34; src=&#34;https://blog.axqora.com/image/iOS-Reverse-dump/0198AE0A-5680-4FB4-A9BA-EC03160EC54F.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt=&#34;b29d4cda912a49aa2a77cf35eedc304e&#34; loading=&#34;lazy&#34; src=&#34;https://blog.axqora.com/image/iOS-Reverse-dump/1B646924-B37E-4CA4-9407-B631EDB93BA5.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;按照提示进行操作，手机重启后会出现 checkra1n 应用，打开应用安装 Cydia &lt;strong&gt;（这个过程手机需要连接代理）&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;注意：不同的越狱工具，对手机跟系统都有要求，太新的设备或者太新的系统都有可能不支持，需要仔细阅读官网的文档。另外，保证手机的重要资料都已经备份。&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;h2 id=&#34;三通过-ssh-连接手机&#34;&gt;三、通过 SSH 连接手机&lt;/h2&gt;
&lt;h3 id=&#34;1-wi-fi&#34;&gt;1. Wi-Fi&lt;/h3&gt;
&lt;p&gt;手机安装 Cydia 后，默认安装了 OpenSSH，可以直接通过 Wi-Fi 或者 USB 连接手机。&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;ssh root@your_device_ip_address
// 密码默认为 alpine
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;2-usb&#34;&gt;2. USB&lt;/h3&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;// 安装 usbmuxd
brew install usbmuxd

// 建立端口映射（把 2222 映射到 22）
iproxy 2222 22

// 建立 ssh 连接
ssh -p 2222 root@localhost
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;img alt=&#34;e342217dae5a9c9408068e018665c221&#34; loading=&#34;lazy&#34; src=&#34;https://blog.axqora.com/image/iOS-Reverse-dump/E92C6731-CF4E-4516-AB5D-F479D089845D.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;建立端口映射时，终端会一直显示 &amp;ldquo;waiting for connection&amp;rdquo;，这是正常的，新建一个终端窗口进行其它操作即可。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h2 id="一背景">一、背景</h2>
<p>iOS 手机通过 App Store 安装的应用都经过加密，无法直接进行逆向分析。我们需要对应用进行解密，称为“砸壳”。</p>
<p>我手上的设备是 <code>iPhone 6s</code>，系统是 <code>iOS 13.5.1</code></p>
<h2 id="二手机越狱">二、手机越狱</h2>
<p><img alt="7438f42620d7b110699c27f2a1e92ad2" loading="lazy" src="/image/iOS-Reverse-dump/DBDC0602-2AEE-468C-BB0A-D74996B39C97.png"></p>
<p>安装爱思助手，一键越狱的工具里面有很多个工具（不一定需要安装爱思助手，可以直接从官网下载越狱工具）。</p>
<p>这里主要推荐两个：一个是通过硬件漏洞进行越狱的 <a href="https://checkra.in/">checkra1n</a>；一个是通过软件漏洞进行越狱的 <a href="https://unc0ver.dev/">Unc0ver</a>。</p>
<p>相对来说通过硬件漏洞进行越狱的 checkra1n 会比较稳定，我使用的是 checkra1n。</p>
<p><img alt="b8e13fe10d7072773197a107aa6c36bd" loading="lazy" src="/image/iOS-Reverse-dump/0198AE0A-5680-4FB4-A9BA-EC03160EC54F.png"></p>
<p><img alt="b29d4cda912a49aa2a77cf35eedc304e" loading="lazy" src="/image/iOS-Reverse-dump/1B646924-B37E-4CA4-9407-B631EDB93BA5.png"></p>
<p>按照提示进行操作，手机重启后会出现 checkra1n 应用，打开应用安装 Cydia <strong>（这个过程手机需要连接代理）</strong>。</p>
<p><em><strong>注意：不同的越狱工具，对手机跟系统都有要求，太新的设备或者太新的系统都有可能不支持，需要仔细阅读官网的文档。另外，保证手机的重要资料都已经备份。</strong></em></p>
<h2 id="三通过-ssh-连接手机">三、通过 SSH 连接手机</h2>
<h3 id="1-wi-fi">1. Wi-Fi</h3>
<p>手机安装 Cydia 后，默认安装了 OpenSSH，可以直接通过 Wi-Fi 或者 USB 连接手机。</p>
<pre tabindex="0"><code>ssh root@your_device_ip_address
// 密码默认为 alpine
</code></pre><h3 id="2-usb">2. USB</h3>
<pre tabindex="0"><code>// 安装 usbmuxd
brew install usbmuxd

// 建立端口映射（把 2222 映射到 22）
iproxy 2222 22

// 建立 ssh 连接
ssh -p 2222 root@localhost
</code></pre><p><img alt="e342217dae5a9c9408068e018665c221" loading="lazy" src="/image/iOS-Reverse-dump/E92C6731-CF4E-4516-AB5D-F479D089845D.png"></p>
<p>建立端口映射时，终端会一直显示 &ldquo;waiting for connection&rdquo;，这是正常的，新建一个终端窗口进行其它操作即可。</p>
<h2 id="四砸壳">四、砸壳</h2>
<p>砸壳分为静态砸壳和动态砸壳，静态砸壳是指直接解密磁盘中的应用（难度较高），而动态砸壳是指系统把解密后的 MachO 文件加载到内存中后，将 MachO 文件拷贝出来。</p>
<h3 id="1-常用工具">1. 常用工具</h3>
<p>常用的砸壳工具有以下三个：</p>
<ol>
<li><a href="https://github.com/stefanesser/dumpdecrypted">dumpdecrypted</a></li>
<li><a href="https://github.com/KJCracks/Clutch">Clutch</a></li>
<li><a href="https://github.com/AloneMonkey/frida-ios-dump">frida-ios-dump</a></li>
</ol>
<p>其中 frida-ios-dump 最为方便，以下简单介绍 frida-ios-dump 的使用。</p>
<h3 id="2-frida-ios-dump-的简单实用">2. frida-ios-dump 的简单实用</h3>
<h4 id="1-手机上的操作">1) 手机上的操作</h4>
<ol>
<li>手机上打开 Cydia，添加源 <code>https://build.frida.re</code>;</li>
<li>搜索 <code>Frida</code> 并安装。</li>
</ol>
<p><img alt="802b59ec02726fab73d54954847e4d01" loading="lazy" src="/image/iOS-Reverse-dump/IMG_0494.PNG"></p>
<h4 id="2-mac上的操作">2) Mac上的操作</h4>
<pre tabindex="0"><code>// 1. 克隆 frida-ios-dump
git clone https://github.com/AloneMonkey/frida-ios-dump.git

// 2. 安装环境
sudo pip3 install -r requirements.txt --upgrade

// 3. 获取应用名称
frida-ps --usb

// 4. 建立端口映射（把 2222 映射到 22）
iproxy 2222 22

// 5. 运行脚本，参数为应用显示名称或者 Bundle Id
// 这个过程需要打开应用（应用已加载到内存中）
./dump.py 美团
</code></pre><p><img alt="7eab7e9533f3aa7190cff2e7b641940c" loading="lazy" src="/image/iOS-Reverse-dump/DF35F545-F0FF-43C7-AC42-65A75CB10EC9.png"></p>
<p><img alt="164e7df92ed8a59608dea25b12cd9126" loading="lazy" src="/image/iOS-Reverse-dump/375DE6C9-A11F-4854-BBF2-F8540E4D9F7B.png"></p>
<h4 id="3-验证是否已经砸壳">3) 验证是否已经砸壳</h4>
<p>利用 otool 工具：</p>
<pre tabindex="0"><code>otool -l Payload/imeituan.app/imeituan | grep &#34;crypt&#34;

// cryptid 0 已砸壳
</code></pre><p><img alt="ff6f735cf0e6efadfac8efcc9f4d7bc0" loading="lazy" src="/image/iOS-Reverse-dump/A2756145-7910-4858-95C5-6DEFF57E8FBF.png"></p>
]]></content:encoded>
    </item>
    <item>
      <title>iOS 逆向入门 - 工具</title>
      <link>https://blog.axqora.com/posts/ios-reverse-tools/</link>
      <pubDate>Sun, 29 Aug 2021 00:00:00 +0000</pubDate>
      <guid>https://blog.axqora.com/posts/ios-reverse-tools/</guid>
      <description>&lt;h4 id=&#34;ios-越狱&#34;&gt;iOS 越狱&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&#34;https://checkra.in/&#34;&gt;https://checkra.in/&lt;/a&gt; （基于硬件漏洞，较稳定）&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://unc0ver.dev/&#34;&gt;https://unc0ver.dev/&lt;/a&gt; （基于软件漏洞，每次重启手机后都要再越狱）&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&#34;app文件系统结构&#34;&gt;App文件系统结构&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&#34;https://www.i-funbox.com/zh-cn/index.html&#34;&gt;https://www.i-funbox.com/zh-cn/index.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://macroplant.com/iexplorer&#34;&gt;https://macroplant.com/iexplorer&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&#34;app-ui调试工具&#34;&gt;App UI调试工具&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&#34;https://revealapp.com/&#34;&gt;https://revealapp.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://lookin.work/&#34;&gt;https://lookin.work/&lt;/a&gt; （免费）&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&#34;反编译工具&#34;&gt;反编译工具&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&#34;https://hex-rays.com/ida-pro/&#34;&gt;https://hex-rays.com/ida-pro/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.hopperapp.com/&#34;&gt;https://www.hopperapp.com/&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&#34;macho&#34;&gt;MachO&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/gdbinit/MachOView&#34;&gt;https://github.com/gdbinit/MachOView&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&#34;越狱开发框架&#34;&gt;越狱开发框架&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/theos/theos&#34;&gt;https://github.com/theos/theos&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&#34;ipa砸壳工具&#34;&gt;ipa砸壳工具&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/stefanesser/dumpdecrypted&#34;&gt;https://github.com/stefanesser/dumpdecrypted&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/KJCracks/Clutch&#34;&gt;https://github.com/KJCracks/Clutch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/AloneMonkey/frida-ios-dump&#34;&gt;https://github.com/AloneMonkey/frida-ios-dump&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&#34;class-dump&#34;&gt;class-dump&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&#34;http://stevenygard.com/projects/class-dump/&#34;&gt;http://stevenygard.com/projects/class-dump/&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&#34;动态库注入&#34;&gt;动态库注入&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/alexzielenski/optool&#34;&gt;https://github.com/alexzielenski/optool&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&#34;获取keychain信息&#34;&gt;获取keychain信息&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/ptoomey3/Keychain-Dumper&#34;&gt;https://github.com/ptoomey3/Keychain-Dumper&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&#34;工具集&#34;&gt;工具集&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/kokoabim/iOSOpenDev&#34;&gt;https://github.com/kokoabim/iOSOpenDev&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/AloneMonkey/MonkeyDev&#34;&gt;https://github.com/AloneMonkey/MonkeyDev&lt;/a&gt; （基于iOSOpenDev开发，可开发非越狱插件）&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;持续更新。。。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h4 id="ios-越狱">iOS 越狱</h4>
<ol>
<li><a href="https://checkra.in/">https://checkra.in/</a> （基于硬件漏洞，较稳定）</li>
<li><a href="https://unc0ver.dev/">https://unc0ver.dev/</a> （基于软件漏洞，每次重启手机后都要再越狱）</li>
</ol>
<h4 id="app文件系统结构">App文件系统结构</h4>
<ol>
<li><a href="https://www.i-funbox.com/zh-cn/index.html">https://www.i-funbox.com/zh-cn/index.html</a></li>
<li><a href="https://macroplant.com/iexplorer">https://macroplant.com/iexplorer</a></li>
</ol>
<h4 id="app-ui调试工具">App UI调试工具</h4>
<ol>
<li><a href="https://revealapp.com/">https://revealapp.com/</a></li>
<li><a href="https://lookin.work/">https://lookin.work/</a> （免费）</li>
</ol>
<h4 id="反编译工具">反编译工具</h4>
<ol>
<li><a href="https://hex-rays.com/ida-pro/">https://hex-rays.com/ida-pro/</a></li>
<li><a href="https://www.hopperapp.com/">https://www.hopperapp.com/</a></li>
</ol>
<h4 id="macho">MachO</h4>
<ol>
<li><a href="https://github.com/gdbinit/MachOView">https://github.com/gdbinit/MachOView</a></li>
</ol>
<h4 id="越狱开发框架">越狱开发框架</h4>
<ol>
<li><a href="https://github.com/theos/theos">https://github.com/theos/theos</a></li>
</ol>
<h4 id="ipa砸壳工具">ipa砸壳工具</h4>
<ol>
<li><a href="https://github.com/stefanesser/dumpdecrypted">https://github.com/stefanesser/dumpdecrypted</a></li>
<li><a href="https://github.com/KJCracks/Clutch">https://github.com/KJCracks/Clutch</a></li>
<li><a href="https://github.com/AloneMonkey/frida-ios-dump">https://github.com/AloneMonkey/frida-ios-dump</a></li>
</ol>
<h4 id="class-dump">class-dump</h4>
<ol>
<li><a href="http://stevenygard.com/projects/class-dump/">http://stevenygard.com/projects/class-dump/</a></li>
</ol>
<h4 id="动态库注入">动态库注入</h4>
<ol>
<li><a href="https://github.com/alexzielenski/optool">https://github.com/alexzielenski/optool</a></li>
</ol>
<h4 id="获取keychain信息">获取keychain信息</h4>
<ol>
<li><a href="https://github.com/ptoomey3/Keychain-Dumper">https://github.com/ptoomey3/Keychain-Dumper</a></li>
</ol>
<h4 id="工具集">工具集</h4>
<ol>
<li><a href="https://github.com/kokoabim/iOSOpenDev">https://github.com/kokoabim/iOSOpenDev</a></li>
<li><a href="https://github.com/AloneMonkey/MonkeyDev">https://github.com/AloneMonkey/MonkeyDev</a> （基于iOSOpenDev开发，可开发非越狱插件）</li>
</ol>
<p>持续更新。。。</p>
]]></content:encoded>
    </item>
  </channel>
</rss>
