Quantcast
Channel: 睿论坛 - 最新话题
Viewing all 5723 articles
Browse latest View live

使用MonkeyDev hook ViewController报错

$
0
0

@PenguinAndy wrote:

hook ViewController 的时候xm文件报错:
Receiver type ‘ViewController’ for instance message is a forward declaration

如果在%hook前面声明ViewController则提示:
Cannot find interface declaration for ‘ViewController’; did you mean ‘UIViewController’?

/* --------------------------- */
在使用tweak hook ViewController没有出现类似问题

Posts: 5

Participants: 3

Read full topic


求助ida 打开文件问题

MonkeyDev怎么添加依赖?

$
0
0

@olivier wrote:

因为刚开始用MonkeyDev,所以不太熟悉。。。
Theos创建的Daemon在iOS11下没有root权限,执行会报错 Killed: 9
所以使用MonkeyDev的Command-Line Tool创建具有Root权限的Daemon

请问各位大佬,MonkeyDev在哪里添加依赖?

比如我想实现Tweak到Daemon的通信,要使用rocketbootstrap
使用Theos可以修改makefile

xxxx_LIBRARIES = rocketbootstrap
xxxx_PRIVATE_FRAMEWORKS = AppSupport

请问使用MonkeyDev应该修改哪里的配置?

Posts: 2

Participants: 2

Read full topic

Class-dump 腾讯视频导出头文件缺失

$
0
0

@snowsnow wrote:

日志:

2019-02-14 19:36:36.213 class-dump[39355:581253] Warning: Parsing method types failed, collectThreads:crashThread:resumedThreads:resumedThreadCount:
2019-02-14 19:36:36.215 class-dump[39355:581253] Warning: Parsing method types failed, collectAllRegisterInfos:context:needCheckFirstLineOcMsg:
2019-02-14 19:36:52.272 class-dump[39355:581253] Warning: Parsing method types failed, collectThreads:crashThread:resumedThreads:resumedThreadCount:
2019-02-14 19:36:52.274 class-dump[39355:581253] Warning: Parsing method types failed, collectAllRegisterInfos:context:needCheckFirstLineOcMsg:

代码:

class-dump -H live4iphone -o Headers

结论:有头文件导出,但是和大家遇到的问题一样,广告的控制器QNBPlayerVideoAdsViewController的头文件找不到。
求助,有人帮忙

Posts: 1

Participants: 1

Read full topic

一个swift subscript使用的问题

$
0
0

@smartdone wrote:

需求:
我想要下标来获取MLDataTable对象里面的一个column,他默认下标返回的是MLUntypedColumn,我需要的是MLDataColumn。他的subscript定义是这个样子的:

/// Subscript by column name. Returns an invalid UntypedColumn if the DataTable does not contain the name.
    public subscript(columnName: String) -> CreateML.MLUntypedColumn

    /// Subscript by column name. Returns an invalid DataColumn if the DataTable does not contain the name.
    public subscript<Element>(columnName: String) -> CreateML.MLDataColumn<Element> where Element : MLDataValueConvertible

    /// Subscript by column name and type. Returns nil if no column has the given name or if the
    /// named column does not have the given type.
    public subscript<T>(columnName: String, columnType: T.Type) -> CreateML.MLDataColumn<T>? where T : MLDataValueConvertible { get }

    /// Subscript by the list of column names. Returns a DataTable with the specified columns
    public subscript<S>(columnNames: S) -> CreateML.MLDataTable where S : Sequence, S.Element == String { get }

他默认subscript使用直接xxx["xxx"]这样就行了,但是我想使用public subscript<Element>(columnName: String) -> CreateML.MLDataColumn<Element> where Element : MLDataValueConvertible 重载我就不知道怎么写了。翻官方文档没找到,还是我太菜了。刚入手swift语法不太会,去几个swift群里面问也没有啥结果,所以来这个大佬聚集的地方问一下。

代码:

import CreateML
import Cocoa

let manager = FileManager.default

let csvFile = "train.csv"
let url = URL(fileURLWithPath: csvFile)
//maxRows
let option = MLDataTable.ParsingOptions(maxRows: 100000)
var dataTable = try MLDataTable(contentsOf: url, options: option)
let AppVersion = dataTable["IsBeta"]

我是想要获取一列,然后填充缺失值,MLDataColumn才有求平均值中位数相关的方法,但是默认下标引用返回的不是这个对象。

菜鸡在此谢谢大佬们

Posts: 1

Participants: 1

Read full topic

关于第十章检测imessage的问题(queue,completionBlock)

$
0
0

@orange666 wrote:

书中最后一步用于检测的语句为:并报错
[[IDSIDQueryController sharedInstance]_currentIDStatusForDestinations:@[@“mailto:iosbbb@gmail.com”,@“tel:+8615362615364”] service:@“com.apple.Madrid” listenerID:@"__kIMChatServiceForSendingIDSQueryControllerListenerID":1]

throw new Error(“unrecognized selector _currentIDStatusForDestinations:service:listenerID:: sent to object 0x17f7ccd0”) /*

通过logify hook了IDS 在XCODE 输出相关语句后发现:
MobileSMS e[1;36m[messagehack] e[me[0;36mTweak.xm:5e[m e[0;30;46mDEBUG:e[m -[<IDSIDQueryController: 0x16d452e0> currentIDStatusForDestination:tel:+8615327465916 service:com.apple.madrid listenerID:IMIDStatusControllerListenerID queue:<OS_dispatch_queue_root: com.apple.root.default-priority[0x3c860580]> completionBlock:<NSStackBlock: 0x27d85fd0>]

多出了(queue,completionBlock)
系统是ios 7.1.2
请教高手关于(queue,completionBlock) 的相关资料
或是应该怎么继续逆向 ?
非常感谢

Posts: 1

Participants: 1

Read full topic

XCode 控制台 打印问题

$
0
0

@fuzhaorui wrote:

HBLogDebu(@"%@",list);

%log(list);

因为list比较长 XCode 控制台 打印不全怎么处理

Posts: 7

Participants: 3

Read full topic

判断dylib是否运行

$
0
0

@theos wrote:

如果b.dylib 注入com.tencent.xin, debugserver 微信 ,image list -o -f 能查看的到b.dylib是否已经运行。

问题来了 :动态库a.dylib 注入com.apple.UIKit, 如何知道a.dylib在运行? 因为我不知道debugserver哪个进程

求大神吱招

Posts: 3

Participants: 2

Read full topic


解决iOS11下Reveal2Loader失效问题

$
0
0

@lemon4ex wrote:

之所以Reveal2Loader失效是因为dlopen加载RevealServer失败,参考CydiaSubstrate.framework,通过创建一个符号链接来绕过。测试可行。

有需要的移步到:Reveal2Loader

Posts: 2

Participants: 2

Read full topic

CContact 分析

$
0
0

@fuzhaorui wrote:

[CContact m_nsChatRoomMemList]
这个方法打印出来的 怎么有的是微信号,有的是 wxid_z3s6j2gfx0hs12 这样的ID 怎么让这些ID都变成微信号

Posts: 10

Participants: 4

Read full topic

Xcode 自动补全'{ }'不管用

固件(UEFI)逆向到内核(XNU)启动高级调试

$
0
0

@jmpews wrote:

Prologue

把之前在内网写的文章分享下.

原链接在这里 https://github.com/jmpews/NoteZ/issues/41

0x1: 内核探析之路连载

本系列将会分为几篇文章, 主要研究/解析对象是 XNU 内核, 期望达到的目的是介绍一些 Kernel 相关知识(包括 x86/x86_64/ARM64), 并(预计)最终实现 iOS-Kernel-Debugger or iOS-Kernel-Manipulator

0x0: 内核探析 之 <固件(UEFI)逆向到内核(XNU)启动高级调试>

0x1: 内核探析之路 之 [内核初始化] or [ 内存管理, 进程管理] or [中断, 陷阱, 异常] (待定)

0x2: iOS-Kernel-Debugger Refer Ian Beer (@i41nbeer) (待定)

0x3: iOS-Kernel-Manipulator (待定)

0x2: 简介:

常用的内核调试, 通常依赖于 Kernel 内置的 debugger 支持, 比如 XNU 需要设置 boot-args 里开启 debug, 之后通过 kdp 远程挂载正在等待的远程 Kernel. 这种方式在双机调试的时候很有用处, 但是这种调试依赖于 Kernel 支持, 也就说无法从 Kernel 的第一条指令开始调试.

对于需要在实模式就开始进行调试的可以借助 bochs, qemu, 前两种大多数情况下属于 emulator, VMware 则需要依赖于宿主 CPU. 因此虽然都直接在 vm 启动时就开启调试. 还是有很大区别的, 比如对于 bochsqemu 会直接拦截 #DB 而不管 Guest 下的 IDT, 但是对于 VMware 则依赖于 Guest 的 IDT, 这将在下文存在一个很大的坑.

这里使用 VMware, 并没有使用 qemu or bochs, 但 qemu 在对 Linux Kernel Debug 的场景使用非常多.

由于在 Guest VM 启动时进行调试, 可能会认为应该直接进入 kernel, 但是这里没有, 而是进入 UEFI 固件, 因为 XNU 不是采用传统的 BIOS 引导, 所以才有了下文的关于 EFI 的分析.

0x3: 前置知识

  1. x86/x86_64 Architecture Manual 相关知识, 例如 GDT, IDT 的相关知识, 保护模式(PE, protected-mode)下分页机制(Paging)相关.
  2. 熟练阅读 x86/x86_64 汇编, 分清 AT&T 与 intel 两种 assembly syntax.
  3. IDA 的使用, 下文在分析过程有很多需要对 IDA 分析的无符号固件进行手动符号化, 但是具体的步骤没有赘述, 需要读者有这方面的能力.
  4. UEFI 相关知识
  5. 文章涉及比较多的固件逆向

本文也感谢 @0xAA55 @任工黑 等 Windows 内核/虚拟化大佬, 在关键的问题的一同讨论.

也感谢 @乾越 在关键问题给出的一些建议.

0x1: VMware 调试环境配置

Tips: VMware + GDB 的调试文章比较多这里就不详细赘述了.

0x1: 安装 macOS 10.12.6

0x2: 下载 KDK

根据 macOS 下载 KDK.

0x3: 配置 development Kernel

首先需要进入到 recorvery-mode 关闭 SIP(System Integrity Protection), 这里有两种方法可以在 VMware 进入 recorvery-mode

  1. 从 VMwar 进入 recorvery-mode 进行如下步骤, 首先选择 <启动到固件>, 之后按照如下进入 recorvery-modeboot.efi.
  2. Enter Setup -> Boot from a file -> Recorvery File Option

这里在关闭 SIP 之后, 将 KDK 里的 kernel.development 移动到 /System/Library/Kernels, 之后使用 kextcache 将 kernel 和 kexts 共同 link 成 PrelinkedKernel, 添加启动参数 nvram boot-args="-v, 这里并没有像很多文章那里增加 -v debug=0x141 kext-dev-mode=1 kcsuffix=development 之类的参数, 因为首先这里不是通过 kdp 调试, 其次读者也可以在下文发现, 即使不指定参数固件也会首选 deveopment 后缀的 PrelinkedKernel.

如果成功将会看到有 prelinkedkernel.develpment 存在

0x4: 开始调试

调试使用 VMware Fusion(也有使用 Win 下的 VMware), 编辑已安装虚拟机目录下的 macOS 10.12.vmx 文件, 增加以下配置.

debugStub.listen.guest64 = "TRUE" # 本地调试
debugStub.port.guest64 = "55555" # 调试端口
debugStub.hideBreakpoints = "TRUE" # 使用硬断
bios.bootDelay = "3000" # bios 延迟启动
monitor.debugOnStartGuest64 = "TRUE" # 在启动时停止

使用 IDA 的 Remote GDB debugger attach 去挂载 localhost:55555 , 我们在如下的地址断下.

0x2: 分析 EFI64.rom

Tips: 由于是基于 EDK2 开发的固件, 所以很多函数即使没有符号, 依然可以根据特征去定位到很多 EDK2 中函数, 所以以下 IDA 中的"注释", 请根据汇编自行对照 EDK2 中函数自行添加.

0x0: EFI 前言

UEFI 与 Legacy BIOS 两种引导方式.

Legacy BIOS 分析起来还是很简单的, BIOS 加电跳到 0x7C00 开始执行 MBR 一段指令, 然后这段 real-mode 下的 MBR 指令会加载磁盘下一阶段的启动代码, 整体来说分起来简单, 但是限制也比较多, 详细不赘述.

这里主要分析 UEFI 相关, EDK2 由 intel 支持的 UEFI 开发 toolkit.

文档请参考 https://edk2-docs.gitbooks.io/edk-ii-build-specification/content/

UEFI 固件的通常启动流程 https://edk2-docs.gitbooks.io/edk-ii-build-specification/2_design_discussion/23_boot_sequence.html#23-boot-sequence, 这里会主要关注 Drive Execution Environment (DXE)Boot Device Selection (BDS) 这两个流程.

DXE: 从 PEI 切换过来, 此时已经初始化完基本支持环境, 并且处于 64-bit 执行环境, 开始加载并初始化 gRT, gBS, 一些关键 Dxe Driver 模块, 以及包含一些关键函数 例如后面要即将要说到的比较核心的 CoreImageLoad, CoreImageStart, CoreDispatcher.

BDS: 在 DxeCore 最后阶段切换过来, 并且不会返回, 在此阶段最后找到平台对应的用于引导 Kernel 的 EFI Driver 比如 macOS 下的 /System/Library/CoreServices/boot.efi, 跳到其 Entry 入口开始执行.

EDK2 中对文件读取提供两个 EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUIDLOAD_FILE_PROTOCOL_GUID Protocol, 默认 EFI 会使用 EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUID 如果失败则使用 LOAD_FILE_PROTOCOL_GUID, 对于文件的具体打开函数实现在 HfsPlusDxe

0x1: 分析定位

分析: 对于如上断点位置, 没有头绪也没有符号, 所以首先确定这是哪个 binary, 所以可以往下翻一翻去找有没有特征字符串, 通过一些字符串去判断.

通过搜索分析发现这是 应该是一个 使用 edk2 开发的 EFI 固件程序, 所以直接到 VMware Fusion 目录里搜一下有没有 rom 相关的固件.

这里的有很多固件(额外提一下, BIOS.440.ROM 是 BIOS 引导使用的固件) , 这里猜想是 EFI64.ROM, 既然是固件先扔到 binwalk 里跑一下.

典型的 EFI 固件结构, 几个 PE 类型的 EFI application, 最后还有一个 LZMA compressed data, 按理说之后应该通过 binwalk 等工具从 offset 开始提取 binary 进行分析. 这里直接使用 UEFITool 进行分析, uefi-firmware-parser 也是一个不错的工具.

这里直接把 DxeCoreBdsDxe, HfsPlusDxe 这几个关键的 EFI Driver 提取出来.

0x2: 分析 DexCore

这里需要先做两个工作

  1. 在 IDA 进行 Program Rebase
  2. 对 DxeCore 进行部分关键函数符号化, 包括但不限于 DxeMain, CoreDispatcher, CoreLoadImage, CoreStartImage

这里先对 DexCore 进行 Rebase

之后对 DxeCore 的 gBS, gRT, DxeMain,CoreDispatcher, CoreLoadImage, CoreStartImage 进行定位符号化, 并确定进入 BdsEntry 的指令地址.

这里具体定位和符号化的过程, 不会进行具体介绍, 大致是先确定 gBS, gRT, 之后利用 log 字符串的特征, 定位 CoreLoadImage, CoreStartImage.

对应的 IDA-GDB 调试器如下

从这里开始断点进入 BdsDxe Driver PE 的 BdsEntry 函数内.

0x3: 分析 BdsDxe

这里需要先做两个工作

  1. 在 IDA 进行 Program Rebase
  2. 对 BdsDxe 进行部分关键函数符号化, 包括但不限于 BdsEntry, BdsBootDeviceSelect, BdsLibBootViaBootOption

通过分析 EDK2 源码, 可以发现, BdsDxe 加载平台特定 boot.efi 的流程为 BdsEntry -> BdsBootDeviceSelect -> BdsLibBootViaBootOption -> CoreLoadImage -> CoreStartImage, 最终转移控制权给 /System/Library/CoreServices/boot.efi

经过手动对上述符号定位和注释, 如下图.

通过对 FileDevicePath 函数进行定位断点可以辅助观察加载地址.

现在可以断点在 CoreStartImage 函数中 Image->EntryPoint (ImageHandle, Image->Info.SystemTable);. 具体指令地址还需读者去判断.

从此开始进入 boot.efi 的分析阶段

0x3: 分析 boot.efi

Tips: boot.efi 是平台特定的 EFI Driver(application), 由 EFI 固件加载, 并且不会再返回. 主要作用是加载 Kernel, 并将控制权移交给 KernelEntry.

0x0: 分析定位

直接从 /System/Library/CoreServices/boot.efi 拿到, 扔到 IDA 里, 可以发现 boot.efi 存在部分调试 log 信息.

0x1: 不正经分析

通过猜想 + 阅读 <*OS Internals>, 判断大致流程为加载 kernel, 并在最后阶段转移控制权给 KernelEntry, 直接在 Entry 入口往下翻就行, 可以很明显有个全局变量来保存加载 kernel 到内存空间的 entry 入口, 直接断点继续跟进调试即可.

0x2: 正经分析

首先需要处理几件事情

  1. 符号化所有的 EFI GUID, 通过 gBS->LocateProtocol gBS->HandleProtocol 可以帮助我们定位很 GUID 对应的数据结构.
  2. 定位并符号化 gRT, gBS,
  3. 定位 EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUIDLOAD_FILE_PROTOCOL_GUID, 这两个 Protocol 将在将在文件读取时使用.

在对 EFI GUID 进行符号化时, 可以使用 ida-efitools 或者 ida-namer, emmmm, 这两个插件都点小问题, 手动修一下就行, 并且

  1. 这里只包含通用的 EFI GUID, 对于 APPLE 相关的 GUID 是缺少的, 可以参考 Clover EFI bootloader 手动添加关于 APPLE 的 GUID, 具体参考 cloverefiboot-code/CloverPkg.dec,
  2. ida-efitools 里的 GUID 少于 ida-namer 所以需要读者手动做一下合并调整.

都是 IDA Python 插件, 请读者自行修复, 这里就不展开了.

对于 gRTgBS 的定位和符号化, 直接跟踪 EFI_STATUS __cdecl ModuleEntryPoint(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable) 入口函数的两个参数的 DFG 即可.

0x3: Load PrelinkKernel

这里有读者会有疑问为什么不是 /System/Library/Kernels/kernel.develoment 而是 /System/Library/PrelinkedKernels/prelinkedkernel.development, 可是前文明明将 KDK 中的 kernel.development 移动到了 /System/Library/Kernels/ 目录中, 其实是由于 kextcache 的原因. 具体的 tool 请参考 kext_tools.

kextcache 会将 kexts 和 kernel 共同 link 成 PrelinkedKernel, 具体在内核加载时如何解压 PrelinkedKernel 将在将在下文讲解.

尝试获取读取文件的 Protocol 处理函数, 处理 boot-args .

尝试能否打开 Volume, 并读取 kernel 文件, 如果不能则会去检查 EncryptedRoot.plist.wipekey 等配置.

在经过上面过程的 Initialization , 进入关键的加载解析 PrelinkedKernel,

同时这里也可以看到这里使用了 boot-args 里的 kcsuffix 参数. (这里其实并不存在 sprintf_x 这个符号, 只是个人对函数功能判断认为它其实实现的是 spritnf 的功能)

首先这里会根据 PrelinkedKernel 的 filepath, 初始化 kernel_load_info 中的 EFI_FILE_HANDLE.

真正解析 PrelinkedKernel 是在 sub_7E96AF44 函数中.

但是无法直接对这一段伪代码进行分析, 需进行符号还原, 部分还原结果如下.

sub_7E96AF44 前半部分的关键点.

InitLoadKernelMacho 函数中部分关键处理点.

关键的点已经在途中标出. 这里先说下 PrelinkedKernel 的文件结构. 开头是标准的 fat 结构.

struct fat_header {
	uint32_t	magic;		/* FAT_MAGIC */
	uint32_t	nfat_arch;	/* number of structs that follow */
};

struct fat_arch {
	cpu_type_t	cputype;	/* cpu specifier (int) */
	cpu_subtype_t	cpusubtype;	/* machine specifier (int) */
	uint32_t	offset;		/* file offset to this object file */
	uint32_t	size;		/* size of this object file */
	uint32_t	align;		/* alignment as a power of 2 */
};

但是每个 fat_arch 对应的并不是 mach_header 而是 prelinked_kernel_header.

// prelinkVersion value >= 1 means KASLR supported
typedef struct prelinked_kernel_header {
    uint32_t  signature;
    uint32_t  compressType;
    uint32_t  adler32;
    uint32_t  uncompressedSize;
    uint32_t  compressedSize;
    uint32_t  prelinkVersion;
    uint32_t  reserved[10];
    char      platformName[PLATFORM_NAME_LEN]; // unused
    char      rootPath[ROOT_PATH_LEN];         // unused
    char      data[0];
} PrelinkedKernelHeader;

通过对 prelinked_kernel_header 进行 uncompress 后, 才获取到 mach_header.

这里总结下上面的过程.

  1. InitLoadKernelMacho 中通过 EFI_FILE_HANDLE, 读取 PrelinkedKernelfat_header.
  2. 读取 PrelinkedKernelHeader 判断 signaturecompressType
  3. 读取 compressedSize 大小的 CompressedKernelCacheCompressedKernelCacheBuffer
  4. 分配 uncompressedSize 大小 Buffer, 对 CompressedKernelCacheBuffer 进行 uncompress 操作, 解压至 Buffer.
  5. 使用 Adler-32 算法进行完整性校验.

在获取到标准 kernel macho buffer, 同样做一些判断比如 macho 对应的 magic, arch 等, 具体如下图.

下面将对 kernel macho 的 segments, sections 进行遍历, 修正 slide.

LABEL_26:
    v4 = v5;
    goto LABEL_27;
  }
LABEL_38:
  if ( slide_flag & 0x4000 && !(mach_header.flags & 0x200000) )
  {
    slide = 0i64;
    slide_flag &= 0xFFFFFFFFFFFFBFFFui64;
  }
  temp_size = mach_header.sizeofcmds;
  v11 = (char *)AllocateBuffer_(mach_header.sizeofcmds);// v17 == 0x1300
  if ( !v11 )
    sub_7E9674BE((__int64)"Out of memory in LoadKernel\n");
  if ( FileReadToBuffer(kernel_load_info_1, &temp_size, v11) < 0 || temp_size < mach_header.sizeofcmds )
  {
    Free_((__int64)v11);
    goto LABEL_26;
  }
  kernel_info_2 = kernel_info_1;
  cmds_buffer = (__int64)v11;
  ncmds = mach_header.ncmds;
  ncmds_1 = mach_header.ncmds;
  v15 = 0;
  v4 = 0i64;
  if ( (signed int)mach_header.ncmds <= 0 )
    goto LABEL_124;
  v16 = (struct segment_command_64 *)v11;
  while ( 1 )
  {
    cmd = v16->cmd;
    cmdsize = v16->cmdsize;
    if ( cmd > 10 )
    {
      if ( cmd == 0xB )                         // LC_DYSYMTAB 
      {
        if ( slide_flag & 0x4000 && v16[1].cmdsize )
        {
          v37 = qword_7E9E6AE0;
          if ( !qword_7E9E6AE0 || !linkedit_vmaddr )
            goto LABEL_123;
          v38 = (signed int *)(v16[1].cmd + linkedit_vmaddr - (unsigned int)linkedit_fileoff);
          v39 = (unsigned __int64)&v38[2 * v16[1].cmdsize];
          while ( (unsigned __int64)v38 < v39 )
          {
            if ( (v38[1] & 0xFF000000) != 100663296 )
              goto LABEL_123;
            *(_QWORD *)(v37 + *v38) += slide;
            v38 += 2;
          }
        }
        goto LABEL_120;
      }
      if ( cmd != 0x19 )                        // LC_SEGMENT_64   
        goto LABEL_120;
      vmaddr_1 = v16->vmaddr;
      vmaddr_0 = v16->vmaddr;
      vm_size_0 = v16->vmsize;
      vm_size_1 = vm_size_0;
      v59 = v16->fileoff + kernel_macho_header_offset_1;
      filesize_0 = v16->filesize;
      temp_size_1 = v16->filesize;
      v27 = -1i64;
      v28 = 0i64;
      v4 = 0i64;
      if ( !vm_size_0 )
      {
LABEL_112:
        if ( v28 && v4 >= 0 )
        {
          if ( kernel_info_2->unknown_mem_2 - 1 >= v27 )
          {
            kernel_info_2->unknown_mem_2 = v27;
            ncmds_1 = ncmds;
            v42 = v15;
            v43 = (__int64)v16;
            v44 = cmdsize;
            v45 = v28;
            v46 = v27;
            v47 = (void *)sub_7E972E37(v27);
            v27 = v46;
            v28 = v45;
            cmdsize = v44;
            v16 = (struct segment_command_64 *)v43;
            v15 = v42;
            ncmds = ncmds_1;
            kernel_info_2->unknown_mem_0 = v47;
          }
          v48 = kernel_info_2->unknown_mem_3;
          v49 = v27 + v28;
          if ( !v48 || v48 < v49 )
          {
            kernel_info_2->unknown_mem_3 = v49;
            ncmds_1 = ncmds;
            v50 = v15;
            v51 = (__int64)v16;
            v52 = cmdsize;
            v53 = (_EFI_FILE_PROTOCOL *)sub_7E972E37(v49);
            cmdsize = v52;
            v16 = (struct segment_command_64 *)v51;
            v15 = v50;
            ncmds = ncmds_1;
            kernel_info_2->unknown_mem_1 = v53;
          }
        }
        if ( v4 )
          goto LABEL_124;
        goto LABEL_120;
      }
      if ( filesize_0 > vm_size_0 )
      {
        temp_size_1 = vm_size_0;
        filesize_0 = vm_size_0;
      }
      filesize_1 = filesize_0;
      cmdsize_1 = cmdsize;
      v58 = v15;
      if ( slide_flag & 0x4000 )
        vmaddr_0 = slide + vmaddr_1;
      seg_vmaddr = AllocateSegmentMemory(&vm_size_1, &vmaddr_0);
      if ( seg_vmaddr )
      {
        seg_vmaddr_1 = (char *)seg_vmaddr;
        v4 = SetPosition(kernel_load_info_1, v59);
        if ( v4 < 0 )
        {
          sub_7E967449("Set offset failed\n");
        }
        else
        {
          vm_size_2 = 0i64;
          seg_vmaddr_2 = (__int64)seg_vmaddr_1;
          if ( !filesize_1 )
          {
LABEL_74:
            if ( vm_size_1 != vm_size_2 )
              ((void (__fastcall *)(unsigned __int64, unsigned __int64, _QWORD))gBS->SetMem)(
                seg_vmaddr_2 + vm_size_2,
                vm_size_1 - vm_size_2,
                0i64);
            if ( slide_flag & 0x4000 )
            {
              if ( !qword_7E9E6AE0 && v16->initprot & 2 )
                qword_7E9E6AE0 = seg_vmaddr_2;
              if ( !linkedit_vmaddr && !(unsigned int)sub_7E973C4D(v16->segname, "__LINKEDIT") )
              {
                linkedit_vmaddr = seg_vmaddr_2;
                linkedit_fileoff = v16->fileoff;
              }
              if ( !(unsigned int)sub_7E973C4D(v16->segname, "__TEXT") )
              {
                for ( i = (struct segment_command_64 *)getFistSegment(seg_vmaddr_2);
                      ;
                      i = (struct segment_command_64 *)getNextSegment(seg_vmaddr_2, (signed __int64)v40) )
                {
                  v40 = i;
                  if ( !i )
                    break;
                  i->vmaddr += slide;
                  for ( j = (struct section_64 *)sub_7E961537((__int64)i);
                        j;
                        j = (struct section_64 *)sub_7E961553((__int64)v40, (__int64)j) )
                  {
                    j->addr += slide;
                  }
                }
              }
            }
            v27 = vmaddr_0;
            v28 = vm_size_1;
            v4 = 0i64;
            goto LABEL_111;
          }
          v4 = FileReadToBuffer(kernel_load_info_1, &temp_size_1, seg_vmaddr_1);
          vm_size_2 = temp_size_1;
          if ( v4 >= 0 )
          {
            seg_vmaddr_2 = (__int64)seg_vmaddr_1;
            goto LABEL_74;
          }
          sub_7E967449("Read file failed: status %d, buffer size 0x%x\n");
        }
      }
      else
      {
        v4 = -9223372036854775799i64;
      }
      v27 = 0i64;
      v28 = 0i64;
LABEL_111:
      ncmds = ncmds_1;
      v15 = v58;
      cmdsize = cmdsize_1;
      goto LABEL_112;
    }
    if ( cmd == 2 )                             // LC_SYMTAB   
    {
      if ( slide_flag & 0x4000 )
      {
        v34 = *(unsigned int *)&v16->segname[4];
        if ( *(_DWORD *)&v16->segname[4] )
        {
          v35 = slide;
          v36 = (_QWORD *)(linkedit_vmaddr
                         + *(unsigned int *)v16->segname
                         - (unsigned __int64)(unsigned int)linkedit_fileoff
                         + 8);
          do
          {
            if ( *((unsigned __int8 *)v36 - 4) <= 0x1Fu )
              *v36 += v35;
            v36 += 2;
            --v34;
          }
          while ( v34 );
        }
      }
      goto LABEL_120;
    }
    if ( cmd == 5 )
      break;
LABEL_120:
    v16 = (struct segment_command_64 *)((char *)v16 + cmdsize);
    ++v15;
    v4 = 0i64;
    if ( v15 >= ncmds )
      goto LABEL_124;
  }
  if ( *(_DWORD *)v16->segname == 4 )
  {
    v19 = *(_QWORD *)&v16[2].cmd + (slide & (slide_flag << 49 >> 63));
    kernel_info_2->unknown_mem_4 = v19;
    v20 = v15;
    v21 = (__int64)v16;
    v22 = cmdsize;
    v23 = sub_7E972E37(v19);
    cmdsize = v22;
    v16 = (struct segment_command_64 *)v21;
    v15 = v20;
    ncmds = ncmds_1;
    kernel_info_2->LoadAddress = v23;
    goto LABEL_120;
  }
  sub_7E967449("Only 64-bit version of LC_UNIXTHREAD is supported\n");
  v5 = -9223372036854775805i64;

这里对上面的伪代码总结下.

  1. 读取 sizeofcmds 大小的内容至 Buffer
  2. 遍历 load_commandv16(这里使用 IDA 设置不同的 type 比较好分析, 这里对 v16 暂时设置为 segment_command_64 type)
  3. 遍历 LC_DYSYMTAB, LC_SEGMENT_64, LC_SYMTAB , 根据 slide 修复 vmaddr

上面的伪代码循环遍历的退出条件是 cmd == 5, 也就是 LC_UNIXTHREAD.

    if ( cmd == 5 )
      break;

这里关键的地方就是对于 LC_UNIXTHREAD 类型的 load_command 的处理, 在 mach-o/loader.h 并没有给出 struct thread_command 的完整表示, 但这里又涉及到 每一个非常重要的 kernel_info_2->LoadAddress.

struct thread_command {
	uint32_t	cmd;		/* LC_THREAD or  LC_UNIXTHREAD */
	uint32_t	cmdsize;	/* total size of this command */
	/* uint32_t flavor		   flavor of thread state */
	/* uint32_t count		   count of longs in thread state */
	/* struct XXX_thread_state state   thread state for this flavor */
	/* ... */
};
  if ( *(_DWORD *)(v16 + 8) == 4 )
  {
    v19 = *(_QWORD *)(v16 + 0x90) + (slide & (slide_flag << 49 >> 63));
    kernel_info_2->unknown_mem_4 = v19;
    v20 = v15;
    v21 = (struct segment_command_64 *)v16;
    v22 = cmdsize;
    v23 = sub_7E972E37(v19);
    cmdsize = v22;
    v16 = (__int64)v21;
    v15 = v20;
    ncmds = ncmds_1;
    kernel_info_2->LoadAddress = v23;
    goto LABEL_120;
  }
  sub_7E967449("Only 64-bit version of LC_UNIXTHREAD is supported\n");

这里直接去 xnu-3789.70.16 里找一下, 最终在 https://opensource.apple.com/source/xnu/xnu-3789.70.16/bsd/kern/mach_loader.c.auto.html 找到相关处理.

这里判断 entry_state 是否是 x86_THREAD_STATE64, 总结下, 对 LC_UNIXTHREAD 处理过程为 load_unixthread -> load_threadentry -> thread_entrypoint , 所以其实这里的 v16 + 0x90 == v16 + sizeof(struct load_command) + sizeof(__uin64_t) * 16rip.

这里额外附加一张增加了 ARM/ARM64 Kernel Source 的 xnu-4570.71.2

0x4: Kernel LC_UNIXTHREAD

有的读者可能并不理解为什么使用 LC_UNIXTHREAD 作为跳转地址, 这里解释下.

读者可以从 MachOView 里发现, 其实 LC_UNIXTHREADentry_state 里的 rip 对应的是 __HIB, __text.

至于 __HIB,__text 的 vmaddr 则是在编译脚本里指定的.

这里的 hibernate__text 就是下文的即将要从 boot.efi 跳转的入口点.

经过上面很长一段的分析.

终于可以准备开始带着 boot-args 进入 "Kernel" 了.

BootKernelEntry(boot_args, kernel_info.LoadAddress); 此时 rcx = boot_args, rdx = KernelEntry, 从这里开始进入 KernelCallGate.

0x5: Kernel Call Gate

通过 template-code-block, (复制)创建一个 thunk, 也就是一个内核调用门,

现在直接对 BootKernelEntry 进行断点, 进入 KernelCallGateThunk.

thunk_64:000000007EA77000 ; ---------------------------------------------------------------------------
thunk_64:000000007EA77000 ; ===========================================================================
thunk_64:000000007EA77000
thunk_64:000000007EA77000 ; Segment type: Regular
thunk_64:000000007EA77000 thunk_64 segment byte public '' use64
thunk_64:000000007EA77000 assume cs:thunk_64
thunk_64:000000007EA77000 ;org 7EA77000h
thunk_64:000000007EA77000 assume es:nothing, ss:nothing, ds:nothing, fs:nothing, gs:nothing
thunk_64:000000007EA77000 lea     rax, loc_7EA7703B
thunk_64:000000007EA77007 mov     cs:dword_7EA7705E, eax
thunk_64:000000007EA7700D lea     rax, dword_7EA7706E
thunk_64:000000007EA77014 mov     qword ptr cs:dword_7EA77066, rax
thunk_64:000000007EA7701B lgdt    fword ptr cs:word_7EA77064
thunk_64:000000007EA77022 mov     ax, 10h
thunk_64:000000007EA77026 mov     ds, ax
thunk_64:000000007EA77029 mov     es, ax
thunk_64:000000007EA7702C mov     gs, ax
thunk_64:000000007EA7702F mov     fs, ax
thunk_64:000000007EA77032 lea     rax, dword_7EA7705E
thunk_64:000000007EA77039 jmp     fword ptr [rax]
thunk_64:000000007EA77039 thunk_64 ends
thunk_64:000000007EA77039
thunk_32:7EA7703B ; ---------------------------------------------------------------------------
thunk_32:7EA7703B ; ===========================================================================
thunk_32:7EA7703B
thunk_32:7EA7703B ; Segment type: Regular
thunk_32:7EA7703B thunk_32 segment byte public '' use32
thunk_32:7EA7703B assume cs:thunk_32
thunk_32:7EA7703B ;org 7EA7703Bh
thunk_32:7EA7703B assume es:nothing, ss:nothing, ds:nothing, fs:nothing, gs:nothing
thunk_32:7EA7703B
thunk_32:7EA7703B loc_7EA7703B:                           ; DATA XREF: thunk_64:000000007EA77000↑o
thunk_32:7EA7703B mov     eax, cr0
thunk_32:7EA7703E btr     eax, 1Fh
thunk_32:7EA77042 mov     cr0, eax
thunk_32:7EA77045 mov     ebx, ecx
thunk_32:7EA77047 mov     edi, edx
thunk_32:7EA77049 mov     ecx, 0C0000080h
thunk_32:7EA7704E rdmsr
thunk_32:7EA77050 btr     eax, 8
thunk_32:7EA77054 wrmsr
thunk_32:7EA77056 jmp     short $+2
thunk_32:7EA77058 ; ---------------------------------------------------------------------------
thunk_32:7EA77058
thunk_32:7EA77058 loc_7EA77058:                           ; CODE XREF: thunk_32:7EA77056↑j
thunk_32:7EA77058 mov     eax, ebx
thunk_32:7EA7705A jmp     edi
thunk_32:7EA7705A ; ---------------------------------------------------------------------------

emmmm, 这段 assembly-code 总体上是一段 64-bit Paging Long-mode 切换到 32-bit protected-model 的 thunk code, 稍微详细一点解释.

thunk_64:000000007EA77000 lea     rax, loc_7EA7703B
thunk_64:000000007EA77007 mov     cs:dword_7EA7705E, eax
thunk_64:000000007EA7700D lea     rax, dword_7EA7706E
thunk_64:000000007EA77014 mov     qword ptr cs:dword_7EA77066, rax
thunk_64:000000007EA7701B lgdt    fword ptr cs:word_7EA77064

这段指令主要是把 loc_7EA7703B 的线性地址放到 cs:dword_7EA7705E 方便接下来进行切换 CS Segment selector. 这里 dword_7EA7706E 放的就是即将要重载的 GDT, 然后使用 lgdt 重载 GDT.

此时 GDT CS Segment:

MEMORY:00000000FFFFFF38 dd 0FFFFh
MEMORY:00000000FFFFFF3C dd 0AF9B00h // 对应 G, D/B, L, AVL
对应 CS segment selector: 0x18

重载后的 GDT CS Segment:

MEMORY:000000007EA77076 dd 0FFFFh
MEMORY:000000007EA7707A dd 0CF9E00h // 对应 G, D/B, L, AVL

前后 GDT 的 CS Segment 的 base address 相同都是 0x0.

thunk_64:000000007EA77022 mov     ax, 10h
thunk_64:000000007EA77026 mov     ds, ax
thunk_64:000000007EA77029 mov     es, ax
thunk_64:000000007EA7702C mov     gs, ax
thunk_64:000000007EA7702F mov     fs, ax
thunk_64:000000007EA77032 lea     rax, dword_7EA7705E
thunk_64:000000007EA77039 jmp     fword ptr [rax]

这段指令将更新各个 Segment Registers 为新的 sesgment selector, 最后通过 jmp fword ptr [rax] 读取 cs selector + offset 利用 far jmp 更新 CS Segment selector(这里有一个前提, 重载前后的 GDT 的 base address 相同, 并且dword_7EA7705E 存放的就是 jmp 后的下一条指令 7EA7703B)

从这里开始无法使用 单步 以及 硬断, 因为此时并没有 32-bit IDT. 这也是 VMware 在这里一个蛋疼的地方, 需要依赖于 Guest 的 IDT

可以认为: **如果后面 32-bit 的指令没有做重载 IDT 的操作, 那么都不能做任何中断/异常的事情, 或者只有等它再次切到 64-bit 后才可以下断点. **

所以这里先手动跟一下指令执行.

thunk_32:7EA7703B loc_7EA7703B:                           ; DATA XREF: thunk_64:000000007EA77000↑o
thunk_32:7EA7703B mov     eax, cr0
thunk_32:7EA7703E btr     eax, 1Fh
thunk_32:7EA77042 mov     cr0, eax
thunk_32:7EA77045 mov     ebx, ecx
thunk_32:7EA77047 mov     edi, edx
thunk_32:7EA77049 mov     ecx, 0C0000080h
thunk_32:7EA7704E rdmsr
thunk_32:7EA77050 btr     eax, 8
thunk_32:7EA77054 wrmsr
thunk_32:7EA77056 jmp     short $+2
thunk_32:7EA77058 ; ---------------------------------------------------------------------------
thunk_32:7EA77058
thunk_32:7EA77058 loc_7EA77058:                           ; CODE XREF: thunk_32:7EA77056↑j
thunk_32:7EA77058 mov     eax, ebx
thunk_32:7EA7705A jmp     edi

这段指令主要实现了, 将 CR0.PG 置零从而关闭 Paging Mode, 之后对 IA32_EFER.LME 置零从而关闭 IA-32e ModeLong-Mode, 进入保护模式并跳转到 "Kernel" 加载地址.

0x6: Kernel Finally

此时 edi"Kernel" 的地址, eaxstruct boot_args. 并且这一段指令都不能进行任何断点操作, 所以手动跟着走一下先, 跟到 "Kernel" 地址.

现在拿出来 xnu 里的 __HIB, __text 对照下, 终于走到了 _start(pstart) 函数, 赶紧找找哪里有切换到 64-bit 执行环境的地方, 下个硬断试试.

这里可以发现在经过 SWITCH_TO_64BIT_MODE 后, 终于可以正常的使用的硬断, 这也就是前文说的 "如果后面 32-bit 的指令没有做重载 IDT 的操作, 那么都不能做任何中断/异常的事情, 或者只有等它再次切到 64-bit 后才可以下断点. ".

(这里重启过虚拟机, 因为 slide 不同, 导致将 PrelinkedKernel 解压到不同的地址)

0x4: 总结

到这里, 第一阶段的分析结束, 总的来说穿透了UEFI 固件最终拦截到了 Kernel 入口点, Kernel 之后的具体初始化以及相关的之后会涉及到.

配图有点多, 接下来的一篇文章可能介绍 iOS 下的处理或者 Kernel 初始化.

Refer

0x0: boot.efi & EFI64.ROM

download raw boot.efi

download raw EFI64.ROM

0x1: 参考链接

https://www.triplefault.io/2017/07/setup-vmm-debugging-using-vmwares-gdb_9.html
https://rednaga.io/2017/04/09/remote_kext_debugging/
https://wikileaks.org/ciav7p1/cms/page_14588660.html

Posts: 6

Participants: 6

Read full topic

From Userspace(''Bug'') into iOS Kernelcache & IOKit

$
0
0

@jmpews wrote:

Prologue

之前发在内网分享下(字数超了)

原链接在这里 https://github.com/jmpews/NoteZ/issues/43

这里会分析下如何从 iOS 的 Userspace 分析到 Kernelspace 的 IOkit 的大致思路.

0. 问题简述

在使用 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, texWidth, texHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, textureData); 创建 Texture 时, 会导致 task_info 获取到统计信息中 internal 增加, 但是 phys_footprint 并不增加.

1. PreTools

以下工具在分析的过程中起到了非常重要的作用(包括但不限于).

0. IDA
# 辅助分析 iOS KernelCache
1. https://github.com/bazad/ida_kernelcache
# 辅助分析 iOS KernelCache
2. Joker http://newosxbook.com/tools/joker.html
# IOKit Driver Class Dumper
3. iometa https://github.com/Siguza/iometa
# IOKit Runtime Dump
4. IOKitUser-1445.71.1/iodisplayregistry.c
# KernelCache 解压
5. lzssdec http://nah6.com/~itsme/cvs-xdadevtools/iphone/tools/lzssdec.cpp
# Runtime DBI(插桩) 分析
6. HookZz http://github.com/jmpews/HookZz

2. Refer Source

以下源码在分析的过程中起到了非常重要的作用(包括但不限于).

# AppleOpenSource
xnu-4570.71.2
IOKitUser-1445.71.1
IOKitTools-108
IOHIDFamily-1035.70.7
IOGraphics-519.20
# GithubSource
# macOS Display Driver with VMware
vmsvga2
# macOS Simple Display Driver
https://github.com/tSoniq/displayx

3. Analysis Process

在分析的过程, 每一个阶段, 都存在问题的确定/转化.

0. 确定 iOS Userspace 下导致 `internal` 增加, 但是 `phys_footprint` 没有增加的函数.
1. 逆向分析对应的 iOS Kernel `AGXAcceleratorG10P_B0` 内核驱动
2. 根据 `AGXAcceleratorG10P_B0` 驱动中使用 `IOBufferMemoryDescriptor` IOKit 方法. 转化为 macOS 对应的调用
3. 编写 macOS 测试驱动, 复现问题. 
4. 对 XNU 进行内核调试, 确定最终发生位置.

前置知识

本文缺少 ARM64 架构下的异常/中断机制的详细分析, 将在后续 <iOS Kernel Debugger> 详细补充.

  1. xnu 系统调用机制
  2. Mach 消息机制, 以及 MIG
  3. IOKit 机制和内存布局
  4. iOS Kernelcache 解密分析

1. xnu 系统调用机制

本质根据 IDT 做 handler 分发, 不关心的可以跳过此部分.

1.1. Legacy 传统的 int 0x80 (only for 32-bit)

xnu-4570.71.2/osfmk/x86_64/idt_table.h 可以看到.

TRAP(0x00,idt64_zero_div)
TRAP_IST1(0x01,idt64_debug)
TRAP_IST2(0x02,idt64_nmi)
USER_TRAP(0x03,idt64_int3)
USER_TRAP(0x04,idt64_into)
USER_TRAP(0x05,idt64_bounds)
TRAP(0x06,idt64_invop)
TRAP(0x07,idt64_nofpu)
TRAP_IST1(0x08,idt64_double_fault)
TRAP(0x09,idt64_fpu_over)
TRAP_ERR(0x0a,idt64_inv_tss)
TRAP_IST1(0x0b,idt64_segnp)

对于系统调用, Mach 调用.

USER_TRAP_SPC(0x80,idt64_unix_scall)
USER_TRAP_SPC(0x81,idt64_mach_scall)
USER_TRAP_SPC(0x82,idt64_mdep_scall)

对应的分发处理函数.

/*
 * Legacy interrupt gate System call handlers.
 * These are entered via a syscall interrupt. The system call number in %rax
 * is saved to the error code slot in the stack frame. We then branch to the
 * common state saving code.
 */

#ifndef UNIX_INT
#error NO UNIX INT!!!
#endif
Entry(idt64_unix_scall)
    pushq   %rax            /* save system call number */
    pushq   $(HNDL_UNIX_SCALL)
    pushq   $(UNIX_INT)
    jmp L_dispatch
    
Entry(idt64_mach_scall)
    pushq   %rax            /* save system call number */
    pushq   $(HNDL_MACH_SCALL)
    pushq   $(MACH_INT)
    jmp L_dispatch
    
Entry(idt64_mdep_scall)
    pushq   %rax            /* save system call number */
    pushq   $(HNDL_MDEP_SCALL)
    pushq   $(MACHDEP_INT)
    jmp L_dispatch

此时的 x86_64_intr_stack_frame 状态为 trapfn == %rax. (Cpu在处理中断会存在部分寄存器自动压栈, 具体参考 6.4.1 Call and Return Operation for Interrupt or Exception Handling Procedures)

struct x86_64_intr_stack_frame {
    uint16_t    trapno;
    uint16_t    cpu;
    uint32_t    _pad;
    uint64_t    trapfn;
    uint64_t    err;
    uint64_t    rip;
    uint64_t    cs;
    uint64_t    rflags;
    uint64_t    rsp;
    uint64_t    ss;
};
typedef struct x86_64_intr_stack_frame x86_64_intr_stack_frame_t;

int 0x80 后的处理流程

L_dispatch
-> *idt64_hndl_table0
-> ks_dispatch
-> ks_dispatch_user
-> L_dispatch_U32
-> L_common_dispatch
-> *(idt64_hndl_table1 + 8*(trapfn = HNDL_UNIX_SCALL))
-> hndl_unix_scall

1.2. syscall 快速系统调用 (only for x86_64)

# xnu-4570.71.2/osfmk/i386/mp_desc.c

/*
 * Set MSRs for sysenter/sysexit and syscall/sysret for 64-bit.
 */
void
cpu_syscall_init(cpu_data_t *cdp)
{
    ...
    wrmsr64(MSR_IA32_LSTAR, DBLMAP((uintptr_t) hi64_syscall));
    ...
}
Entry(hi64_syscall)
Entry(idt64_syscall)
    swapgs
     /* Use RAX as a temporary by shifting its contents into R11[32:63]
      * The systemcall number is defined to be a 32-bit quantity, as is
      * RFLAGS.
      */
    shlq    $32, %rax
    or  %rax, %r11
.globl EXT(dblsyscall_patch_point)
EXT(dblsyscall_patch_point):
//  movabsq $0x12345678ABCDEFFFULL, %rax
     /* Generate offset to the double-mapped per-CPU data shadow
      * into RAX
      */
    leaq    EXT(idt64_hndl_table0)(%rip), %rax
    mov 16(%rax), %rax
    mov     %rsp, %gs:CPU_UBER_TMP(%rax)  /* save user stack */
    mov     %gs:CPU_ESTACK(%rax), %rsp  /* switch stack to per-cpu estack */
    sub $(ISF64_SIZE), %rsp

    /*
     * Synthesize an ISF frame on the exception stack
     */
    movl    $(USER_DS), ISF64_SS(%rsp)
    mov %rcx, ISF64_RIP(%rsp)       /* rip */

    mov %gs:CPU_UBER_TMP(%rax), %rcx
    mov %rcx, ISF64_RSP(%rsp)       /* user stack --changed */

    mov %r11, %rax
    shrq    $32, %rax       /* Restore RAX */
    mov %r11d, %r11d        /* Clear r11[32:63] */

    mov %r11, ISF64_RFLAGS(%rsp)    /* rflags */
    movl    $(SYSCALL_CS), ISF64_CS(%rsp)   /* cs - a pseudo-segment */
    mov %rax, ISF64_ERR(%rsp)       /* err/rax - syscall code */
    movq    $(HNDL_SYSCALL), ISF64_TRAPFN(%rsp)
    movq    $(T_SYSCALL), ISF64_TRAPNO(%rsp)    /* trapno */
    swapgs
    jmp L_dispatch          /* this can only be 64-bit */

syscall 之后的处理流程

L_dispatch
-> *idt64_hndl_table0
-> ks_dispatch
-> ks_dispatch_user
-> L_dispatch_64bit
-> L_common_dispatch
-> *(idt64_hndl_table1 + 8*(trapfn = HNDL_SYSCALL))
-> hndl_unix_scall

hndl_syscall 中根据 rax 判断 syscall 类型, 例如正常系统调用会在 hndl_unix_scall64 处进行跳转执行.

Entry(hndl_syscall)
    TIME_TRAP_UENTRY

    movq    %gs:CPU_ACTIVE_THREAD,%rcx  /* get current thread     */
    movl    $-1, TH_IOTIER_OVERRIDE(%rcx)   /* Reset IO tier override to -1 before handling syscall */
    movq    TH_TASK(%rcx),%rbx      /* point to current task  */

    /* Check for active vtimers in the current task */
    TASK_VTIMER_CHECK(%rbx,%rcx)

    /*
     * We can be here either for a mach, unix machdep or diag syscall,
     * as indicated by the syscall class:
     */
    movl    R64_RAX(%r15), %eax     /* syscall number/class */
    movl    %eax, %edx
    andl    $(SYSCALL_CLASS_MASK), %edx /* syscall class */
    cmpl    $(SYSCALL_CLASS_MACH<<SYSCALL_CLASS_SHIFT), %edx
    je  EXT(hndl_mach_scall64)
    cmpl    $(SYSCALL_CLASS_UNIX<<SYSCALL_CLASS_SHIFT), %edx
    je  EXT(hndl_unix_scall64)
    cmpl    $(SYSCALL_CLASS_MDEP<<SYSCALL_CLASS_SHIFT), %edx
    je  EXT(hndl_mdep_scall64)
    cmpl    $(SYSCALL_CLASS_DIAG<<SYSCALL_CLASS_SHIFT), %edx
    je  EXT(hndl_diag_scall64)

    /* Syscall class unknown */
    sti
    CCALL3(i386_exception, $(EXC_SYSCALL), %rax, $1)
    /* no return */


Entry(hndl_unix_scall64)
    incl    TH_SYSCALLS_UNIX(%rcx)      /* increment call count   */
    sti

    CCALL1(unix_syscall64, %r15)
    /*
     * always returns through thread_exception_return
     */

1.3. userspace 空间的 vm_fault

* thread #96, name = '0xffffff8026811650', queue = 'cpu-1', stop reason = breakpoint 3.1
  * frame #0: 0xffffff801dd6d5ee kernel`pmap_enter_options(pmap=0xffffff80269de250, vaddr=<unavailable>, pn=<unavailable>, prot=3, fault_type=<unavailable>, flags=<unavailable>, wired=<unavailable>, options=<unavailable>, arg=<unavailable>) at pmap_x86_common.c:986 [opt]
    frame #1: 0xffffff801dcec990 kernel`vm_fault_enter(m=0xffffff80215f0750, pmap=0xffffff80269de250, vaddr=4353159168, prot=3, caller_prot=<unavailable>, wired=0, change_wiring=<unavailable>, wire_tag=<unavailable>, no_cache=<unavailable>, cs_bypass=<unavailable>, user_tag=-2139079024, pmap_options=<unavailable>, need_retry=<unavailable>, type_of_fault=<unavailable>) at vm_fault.c:3279 [opt]
    frame #2: 0xffffff801dcee19b kernel`vm_fault_internal(map=0xffffff802918b700, vaddr=4353159168, caller_prot=3, change_wiring=<unavailable>, wire_tag=0, interruptible=<unavailable>, caller_pmap=<unavailable>, caller_pmap_addr=<unavailable>, physpage_p=<unavailable>) at vm_fault.c:0 [opt]
    frame #3: 0xffffff801dd891a2 kernel`user_trap [inlined] vm_fault(map=<unavailable>, fault_type=<unavailable>, change_wiring=0, wire_tag=0, interruptible=2, caller_pmap_addr=0) at vm_fault.c:3416 [opt]
    frame #4: 0xffffff801dd8917f kernel`user_trap(saved_state=0xffffff8025ad2460) at trap.c:1118 [opt]
    frame #5: 0xffffff801dc1d0a5 kernel`hndl_alltraps + 229

2. Mach 消息机制

所有的 mach 消息, 都要经过 mach_msg.

可以通过 MIG(Mach Interface Generator).defs 生成 mach 接口. 例如:

xcrun -sdk iphoneos mig \
  -arch arm64 \
  -DIOKIT \
  -I/xxx/macOS_10.13.6/xnu-4570.71.2/osfmk \
  iokitmig.defs

这里以 IOConnectCallMethod 举例.

Usermode
---
IOConnectCallMethod => io_connect_method => mach_msg() =>

Kernelmode
---
_Xio_connect_method => is_io_connect_method

3. IOKit 机制和内存布局

xnu 里的驱动是基于 libkern 的 c++ IO Kit 框架开发, 但只是一个 c++ 子集.

所以可以通过获取驱动类的继承关系和虚表关系帮助分析 iOS Kernelcache 中的各种无符号的驱动.

例如 AGXAcceleratorG10P_B0, 这也是下文要分析的驱动:

vtab=0xfffffff006fe2b68 size=0x00001998 meta=0xfffffff007866850 parent=0xfffffff0078666b0 AGXAcceleratorG10P_B0 (com.apple.AGXG10P)
       0x0 func=0xfffffff006d6e264 overrides=0xfffffff006d67308 pac=0x0000 AGXAcceleratorG10P_B0::fn_0x0()
       0x8 func=0xfffffff006d6e268 overrides=0xfffffff006d6730c pac=0x0000 AGXAcceleratorG10P_B0::~AGXAcceleratorG10P_B0()
      0x38 func=0xfffffff006d6e280 overrides=0xfffffff006d67324 pac=0x0000 AGXAcceleratorG10P_B0::getMetaClass() const
     0x2a8 func=0xfffffff006d6e28c overrides=0xfffffff006d67330 pac=0x0000 AGXAcceleratorG10P_B0::start()
     0x2b0 func=0xfffffff006d6e350 overrides=0xfffffff006d67494 pac=0x0000 AGXAcceleratorG10P_B0::stop()
     0x370 func=0xfffffff006d6e388 overrides=0xfffffff006d674cc pac=0x0000 AGXAcceleratorG10P_B0::getWorkLoop() const
     0x548 func=0xfffffff006d6e390 overrides=0xfffffff006d675d8 pac=0x0000 AGXAcceleratorG10P_B0::fn_0x548()
     0x710 func=0xfffffff006d6e398 overrides=0xfffffff006d6779c pac=0x0000 AGXAcceleratorG10P_B0::fn_0x710()
    ...
vtab=0xfffffff006fe0068 size=0x00001998 meta=0xfffffff0078666b0 parent=0xfffffff007866540 AGXAcceleratorG10 (com.apple.AGXG10P)
       0x0 func=0xfffffff006d67308 overrides=0xfffffff006d3648c pac=0x0000 AGXAcceleratorG10::fn_0x0()
       0x8 func=0xfffffff006d6730c overrides=0xfffffff006d36490 pac=0x0000 AGXAcceleratorG10::~AGXAcceleratorG10()
      0x38 func=0xfffffff006d67324 overrides=0xfffffff006d36494 pac=0x0000 AGXAcceleratorG10::getMetaClass() const
     0x140 func=0xfffffff006d454c0 overrides=0xfffffff007564e08 pac=0x0000 AGXAcceleratorG10::copyProperty() const
     0x158 func=0xfffffff006d4644c overrides=0xfffffff007564f9c pac=0x0000 AGXAcceleratorG10::setProperties()
     0x270 func=0xfffffff006d48198 overrides=0xfffffff00758b464 pac=0x0000 AGXAcceleratorG10::systemWillShutdown()
     0x280 func=0xfffffff006d481d4 overrides=0xfffffff00756a538 pac=0x0000 AGXAcceleratorG10::configureReport()
     0x288 func=0xfffffff006d481f0 overrides=0xfffffff00756a76c pac=0x0000 AGXAcceleratorG10::updateReport()
     0x2a0 func=0xfffffff006d57ba4 overrides=0xfffffff00756ac30 pac=0x0000 AGXAcceleratorG10::probe()
     0x2a8 func=0xfffffff006d67330 overrides=0xfffffff006d366ec pac=0x0000 AGXAcceleratorG10::start()
     0x2b0 func=0xfffffff006d67494 overrides=0xfffffff006d37a0c pac=0x0000 AGXAcceleratorG10::stop()
     0x370 func=0xfffffff006d674cc overrides=0xfffffff006d3adc4 pac=0x0000 AGXAcceleratorG10::getWorkLoop() const
     0x3a0 func=0xfffffff006d674d4 overrides=0xfffffff00756c31c pac=0x0000 AGXAcceleratorG10::callPlatformFunction()
     0x4d0 func=0xfffffff006d4ada0 overrides=0xfffffff00758b2e4 pac=0x0000 AGXAcceleratorG10::setPowerState()
     0x548 func=0xfffffff006d675d8 overrides=0xfffffff006d3adcc pac=0x0000 AGXAcceleratorG10::fn_0x548()
    ...
vtab=0x0000000000000000 size=0x00001990 meta=0xfffffff007866540 parent=0xfffffff007866358 AGXFamilyAccelerator (com.apple.AGXG10P)
vtab=0x0000000000000000 size=0x00001990 meta=0xfffffff007866358 parent=0xfffffff0078658a8 AGXAccelerator (com.apple.AGXG10P)
vtab=0xfffffff006fda6a8 size=0x00000c78 meta=0xfffffff0078658a8 parent=0xfffffff007865880 IOGraphicsAccelerator2 (com.apple.iokit.IOAcceleratorFamily2)
       0x0 func=0xfffffff006d3648c overrides=0xfffffff006d363b0 pac=0x0000 IOGraphicsAccelerator2::fn_0x0()
       0x8 func=0xfffffff006d36490 overrides=0xfffffff006d363b4 pac=0x0000 IOGraphicsAccelerator2::~IOGraphicsAccelerator2()
      0x38 func=0xfffffff006d36494 overrides=0xfffffff006d363cc pac=0x0000 IOGraphicsAccelerator2::getMetaClass() const
      0x68 func=0xfffffff006d37f74 overrides=0xfffffff007569c4c pac=0x0000 IOGraphicsAccelerator2::free()
     0x2a8 func=0xfffffff006d366ec overrides=0xfffffff00756ac34 pac=0x0000 IOGraphicsAccelerator2::start()
     0x2b0 func=0xfffffff006d37a0c overrides=0xfffffff00756ac3c pac=0x0000 IOGraphicsAccelerator2::stop()
     0x370 func=0xfffffff006d3adc4 overrides=0xfffffff00756c120 pac=0x0000 IOGraphicsAccelerator2::getWorkLoop() const
     0x418 func=0xfffffff006d3a444 overrides=0xfffffff00756c998 pac=0x0000 IOGraphicsAccelerator2::message()
     0x460 func=0xfffffff006d38560 overrides=0xfffffff00756cf88 pac=0x0000 IOGraphicsAccelerator2::newUserClient()
     0x538 func=0xfffffff006d3a550 overrides=0x0000000000000000 pac=0x0000 IOGraphicsAccelerator2::fn_0x538()
    ...

3.1. Connnect(Open) Driver(Service)

将在分析 AGXAcceleratorG10P_B0 时用到.

Kernelspace
---
_Xio_service_open_extended => is_io_service_open_extended =>
---
provider_service->newUserClient() => userClient_instantiate_class->initWithTask()

Userspace
---
io_service_t _io_get_service(const char *name) {
  static io_service_t service = MACH_PORT_NULL;
  if (service == MACH_PORT_NULL) {
    DLOG("Getting IO service handle...");
    service = _IOServiceGetMatchingService(get_io_master_port(), _IOServiceMatching(name));
    if (!MACH_PORT_VALID(service)) {
      FATAL("Failed to get IO service handle (port = 0x%08x)", service);
    }
  }
  return service;
}

io_service_t platformExpertDevice = _io_get_service("IOPlatformExpertDevice");
io_connect_t connect;
kr = _IOServiceOpen(platformExpertDevice, mach_task_self(), 0, &connect);
---
-> io_service_open_extended => mach_msg

3.2. Invoke driver methods

将在分析 AGXAcceleratorG10P_B0 时用到.

Usermode
---
IOConnectCallMethod => io_connect_method => mach_msg() =>

Kernelmode
---
_Xio_connect_method => is_io_connect_method =>
---
client->externalMethod => dispatch_by_selector
(lldb) bt
* thread #6, name = '0xffffff8013341520', queue = 'cpu-1', stop reason = breakpoint 1.1
  * frame #0: 0xffffff800bf3b8d6 kernel`mach_make_memory_entry_64(target_map=<unavailable>, size=0xffffff886e9d3790, offset=0, permission=409603, object_handle=0xffffff8017546c28, parent_handle=<unavailable>) at vm_user.c:2339 [opt]
    frame #1: 0xffffff800c4b52a9 kernel`IOGeneralMemoryDescriptor::memoryReferenceCreate(this=<unavailable>, options=0, reference=0xffffff8017546c28) at IOMemoryDescriptor.cpp:611 [opt]
    frame #2: 0xffffff800c4b3651 kernel`IOGeneralMemoryDescriptor::initWithOptions(this=<unavailable>, buffers=0x0000000000000001, count=1, offset=310765616, task=<unavailable>, options=<unavailable>, mapper=0x0000000000000000) at IOMemoryDescriptor.cpp:1709 [opt]
    frame #3: 0xffffff800c4a9d58 kernel`IOBufferMemoryDescriptor::initWithPhysicalMask(this=0xffffff8017546c00, inTask=<unavailable>, options=<unavailable>, capacity=2048000, alignment=<unavailable>, physicalMask=<unavailable>) at IOBufferMemoryDescriptor.cpp:289 [opt]
    frame #4: 0xffffff800c4aa9b7 kernel`IOBufferMemoryDescriptor::inTaskWithOptions(inTask=0x0000000000000000, options=65635, capacity=0x00000000001f4000, alignment=0x0000000000001000) at IOBufferMemoryDescriptor.cpp:339 [opt]
    frame #5: 0xffffff7f8e3344ac IOKitDemoDriver`SharedMemoryAlloc(buffer=0xffffff886e9d3a28, options=65635, size=2048000) at IOKitDemoDriver.cpp:87
    frame #6: 0xffffff7f8e334595 IOKitDemoDriver`IOKitDemoDriver::userClientMap(this=0xffffff8012b77360, owningTask=0xffffff8014b4dc28, map=0xffffff8012f93610, mapSize=0xffffff886e9d3a80) at IOKitDemoDriver.cpp:112
    frame #7: 0xffffff7f8e334aac IOKitDemoDriver`IOKitDemoDriverUserClient::userClientMap(target=0xffffff801745c000, reference=0x0000000000000000, args=0xffffff886e9d3b80) at IOKitDemoDriverUserClient.cc:53
    frame #8: 0xffffff800c4cb428 kernel`IOUserClient::externalMethod(this=<unavailable>, selector=<unavailable>, args=0xffffff886e9d3b80, dispatch=0xffffff7f8e336438, target=0xffffff801745c000, reference=0x0000000000000000) at IOUserClient.cpp:5289 [opt]
    frame #9: 0xffffff7f8e334b66 IOKitDemoDriver`IOKitDemoDriverUserClient::externalMethod(this=0xffffff801745c000, selector=1, arguments=0xffffff886e9d3b80, dispatch=0xffffff7f8e336438, target=0xffffff801745c000, reference=0x0000000000000000) at IOKitDemoDriverUserClient.cc:74
    frame #10: 0xffffff800c4d4237 kernel`::is_io_connect_method(connection=0xffffff801745c000, selector=1, scalar_input=<unavailable>, scalar_inputCnt=<unavailable>, inband_input=<unavailable>, inband_inputCnt=0, ool_input=<unavailable>, ool_input_size=<unavailable>, inband_output=<unavailable>, inband_outputCnt=<unavailable>, scalar_output=<unavailable>, scalar_outputCnt=<unavailable>, ool_output=<unavailable>, ool_output_size=<unavailable>) at IOUserClient.cpp:3943 [opt]
    frame #11: 0xffffff800bf44514 kernel`_Xio_connect_method(InHeadP=<unavailable>, OutHeadP=0xffffff8012f935e0) at device_server.c:8376 [opt]
    frame #12: 0xffffff800be70d9e kernel`ipc_kobject_server(request=0xffffff8017555380, option=<unavailable>) at ipc_kobject.c:351 [opt]
    frame #13: 0xffffff800be4dc2d kernel`ipc_kmsg_send(kmsg=0xffffff8017555380, option=3, send_timeout=0) at ipc_kmsg.c:1861 [opt]
    frame #14: 0xffffff800be60d1b kernel`mach_msg_overwrite_trap(args=<unavailable>) at mach_msg.c:570 [opt]
    frame #15: 0xffffff800bf7388d kernel`mach_call_munger64(state=0xffffff80144e1640) at bsd_i386.c:573 [opt]
    frame #16: 0xffffff800be1d996 kernel`hndl_mach_scall64 + 22

4. iOS(10+) Kernelcache 解密分析

这里以 iPhone8 - 11.4.1 为例子.

4.1. 解密 iOS Kernelcache

1. download `iPhone_4.7_P3_11.0_11.4.1_15G77_Restore.ipsw` from `https://ipsw.me/`
2. wget http://nah6.com/~itsme/cvs-xdadevtools/iphone/tools/lzssdec.cpp
3. clang++ lzssdec.cpp -o lzssdec
4. check the offset which lzssdec needed
[0] % hexdump -C -n 600 /Users/jmpews/Downloads/kernelcache.release.iphone10
00000000  30 83 fd 63 59 16 04 49  4d 34 50 16 04 6b 72 6e  |0..cY..IM4P..krn|
00000010  6c 16 1c 4b 65 72 6e 65  6c 43 61 63 68 65 42 75  |l..KernelCacheBu|
00000020  69 6c 64 65 72 2d 31 33  34 38 2e 37 30 2e 31 04  |ilder-1348.70.1.|
00000030  83 fd 63 2a 63 6f 6d 70  6c 7a 73 73 28 31 d7 4d  |..c*complzss(1.M|
00000040  02 02 40 00 00 fd 61 aa  00 00 00 01 00 00 00 00  |..@...a.........|
00000050  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
000001b0  00 00 00 00 ff cf fa ed  fe 0c 00 00 01 d5 00 f6  |................|
000001c0  f0 02 f6 f0 15 f6 f0 70  0f 5a f3 f1 20 f6 f1 00  |.......p.Z.. ...|
000001d0  19 f6 f0 38 f5 f0 3f 5f  5f 54 45 58 54 09 02 1c  |...8..?__TEXT...|
5. ./lzssdec -o 0x1b4 < kernelcache.release.iphone10 > kernelcache.release.iphone10.decrypt

4.2. 获取 iOS dyld_shared_cache

cp /Volumes/xxx/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64 /xxx/xxx

4.3 分析 iOS Pirvate Framework

可以通过 Users/jmpews/Library/Developer/Xcode/iOS DeviceSupport/11.4.1 (15G77)/Symbols/System/Library/Extensions/AGXMetalA11.bundle/ 获取到 AGXMetalA11 由于 shared cache 但会存在大量未知地址引用, 所以如需更进一步的具体分析请使用 IDA 分析 dyld_shared_cache_arm64, 尽量避免完全分析, 只要分析对应 Framework 以及其依赖即可.

5. XNU Kernel 调试

这里的 XNU Kernel 调试并没有使用 Apple 提供的 KDP, 而是基于 VMware + LLDB 的调试, 可以参考我的上篇文章 From UEFI Reversing to Xnu Kernel Debug, 这里贴一下 lldb command script.

#Help lldb figure out we're debugging x86_64
settings set plugin.process.gdb-remote.target-definition-file ~/.lldb/x86_64_target_definition.py

#Use a reasonable disassembly syntax
settings set target.x86-disassembly-flavor intel

#Tell load any lldb scripts and macros hidden inside .dSYM files
settings set target.load-script-from-symbol-file true

#Tell lldb where the source directory really is
settings set target.source-map  /BuildRoot/Library/Caches/com.apple.xbs/Sources/xnu/xnu-4570.20.62 /Users/jmpews/Downloads/xnu-4570.20.62

#This should get loaded automatically when we set the target executable
#command script import "/Library/Developer/KDKs/KDK_10.13.1_17B48.kdk/System/Library/Kernels/kernel.debug.dSYM/Contents/Resources/Python/lldbmacros/xnu.py"

#This does not appear to get loaded automatically, so we load it here.
#command script import "/Library/Developer/KDKs/KDK_10.13.1_17B48.kdk/System/Library/Kernels/kernel.debug.dSYM/Contents/Resources/Python/lldbmacros/memory.py"

# Load the kernel binary we're going to be debugging.
target create /Library/Developer/KDKs/KDK_10.13.1_17B48.kdk/System/Library/Kernels/kernel.debug

6. HookZz Runtime DBI 分析

HookZz 是本人编写的 hook framework, 在分析过程中, 为了确定了 Userspace 下问题问题的发生点, 使用了 HookZz 大量进行了插桩分析.

ZzWrap API 允许对任何函数添加 pre_callpost_call, 可以非常方便确定某一个函数是否是导致问题发生的点.

ZzDynamicBinaryInstrument API 允许对任意指令地址增加 dbi_call, 可以非常方便确定某一个函数内的哪一个区间导致问题发生的点.

分析思路

1.1 vm_fault 导致 internal+compressed 增加

首先判断问题的发生位置:

1). Userspace 下的内存分配 malloc / vm_allocate / mmap(这里其实并不用考虑, 因为 userspace 下在进行分配的时其 vm_object 一定统计在自身的 task->map)
2). IOKit 驱动内部内存分配

对于 1) 这里直接 Inlinehook malloc / vm_allocate / mmap, 并没有发现异常点. 接着怀疑是调用了驱动方法, 直接断点 mach_msg. 可以发现确实调用了 mach 系统调用.

(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 9.1
  * frame #0: 0x0000000184b9fc18 libsystem_kernel.dylib`mach_msg
    frame #1: 0x0000000185407bb8 IOKit`io_connect_method + 416
    frame #2: 0x00000001853a4038 IOKit`IOConnectCallMethod + 232
    frame #3: 0x0000000186f399c0 IOAccelerator`IOAccelResourceCreate + 148
    frame #4: 0x0000000186f75228 Metal`-[MTLIOAccelResource initWithDevice:options:args:argsSize:] + 264
    frame #5: 0x0000000186f6ad48 Metal`-[MTLIOAccelTexture initWithDevice:descriptor:sysMemSize:sysMemRowBytes:vidMemSize:vidMemRowBytes:args:argsSize:] + 176
    frame #6: 0x00000001ad604360 AGXMetalA11`___lldb_unnamed_symbol1181$$AGXMetalA11 + 580
    frame #7: 0x00000001a56ef41c AppleMetalGLRenderer`GLDTextureRec::allocMetalTexture() + 176
    frame #8: 0x00000001a56ef58c AppleMetalGLRenderer`GLDTextureRec::loadPrivateTexture(unsigned int, unsigned short*) + 56
    frame #9: 0x00000001a56ed8e8 AppleMetalGLRenderer`GLDTextureRec::update(unsigned int, unsigned short*) + 168
    frame #10: 0x00000001a56ed7f4 AppleMetalGLRenderer`GLDTextureRec::load() + 96
    frame #11: 0x00000001a56fe1f4 AppleMetalGLRenderer`gldModifyTexSubImage + 88
    frame #12: 0x00000001a6467328 GLEngine`glTexImage2D_Exec + 1752
    frame #13: 0x0000000102941934 libglInterpose.dylib`tex_image2D(__GLIContextRec*, unsigned int, int, unsigned int, int, int, int, unsigned int, unsigned int, void const*) + 388
    frame #14: 0x000000018814c738 OpenGLES`glTexImage2D + 108

这里需要判断是否确实这个 mach 调用, 导致的 internal 增加, 但 footprint 不增加, 尝试使用 HookZzZzWrapIOAccelResourceCreate 进行 PreCallPostCall 的 trace.

void common_pre_call(RegisterContext *reg_ctx, const HookEntryInfo *info)
{
  NSByteCountFormatter *formatter            = [[NSByteCountFormatter alloc] init];
  formatter.countStyle = NSByteCountFormatterCountStyleMemory;
  MemoryInfo *mem_info = memoryFootprint();
  NSLog(@"PRE==:footprint: %@, internal+compressed: %@, virtual:%@",
        [formatter stringFromByteCount:mem_info.footprint],
        [formatter stringFromByteCount:mem_info.internal + mem_info.compressed],
        [formatter stringFromByteCount:mem_info.virtual_size]);
  printf("virtual:%d\n", mem_info.virtual_size);
}

void common_post_call(RegisterContext *reg_ctx, const HookEntryInfo *info)
{
  NSByteCountFormatter *formatter            = [[NSByteCountFormatter alloc] init];
  formatter.countStyle = NSByteCountFormatterCountStyleMemory;
  MemoryInfo *mem_info = memoryFootprint();
  NSLog(@"POST==:footprint: %@, internal+compressed: %@, virtual:%@",
        [formatter stringFromByteCount:mem_info.footprint],
        [formatter stringFromByteCount:mem_info.internal + mem_info.compressed],
        [formatter stringFromByteCount:mem_info.virtual_size]);
  
  printf("virtual:%d\n", mem_info.virtual_size);
}

ZzWrap((void *)0x1a46b619c, common_pre_call, common_post_call);

但是发现 internal+compressed 没有任何增加, 那是不是确实跟这个 mach 系统调用无关呢? 接下来继续通过 Dynamic Binary Instrumentation (DBI) 来确定到底是哪个函数导致了 internal+compressed 的增加.

如下是 -[MTLIOAccelResource initWithDevice:options:args:argsSize:] 的实现, 请注意图中的标注点.

最终在 dyld_shared_cache_arm64AGXMetalA11 如下函数确定导致 internal+compressed 的增加, 但是这段函数没有任何调用, 并且推测是一段内存拷贝的代码. 所以这里推测是 vm_fault 导致.

__int64 __fastcall sub_1A93455C8(__int64 result, __int64 a2, int a3, int a4, int a5, int a6, __int64 a7, unsigned int a8, unsigned int a9, unsigned int a10, unsigned int a11)
{
    ...
      do
      {
        v39 = 0;
        v40 = v38 + 4LL * a8;
        v41 = v26;
        do
        {
          v42 = result + 4 * (v41 + v33);
          v43 = *(v40 + 16);
          v45 = *(v40 + a7);
          v46 = *(v40 + a7 + 16);
          v44 = (v40 + a7 + a7);
          v48 = *v44;
          v49 = v44[1];
          v47 = (v44 + a7);
          v50 = *v47;
          v51 = v47[1];
          v52 = vzip2q_s32(*v40, v45);
          *v42 = vzip1q_s32(*v40, v45);
          *(v42 + 16) = vzip1q_s32(v48, v50);
          *(v42 + 32) = v52;
          *(v42 + 48) = vzip2q_s32(v48, v50);
          v42 += 128LL;
          *v42 = vzip1q_s32(v43, v46);
          *(v42 + 16) = vzip1q_s32(v49, v51);
          *(v42 + 32) = vzip2q_s32(v43, v46);
          *(v42 + 48) = vzip2q_s32(v49, v51);
          v41 = (v41 - v12) & v12;
          v40 += 32LL;
          v39 += 8;
        }
        while ( v39 < a10 );
        v33 = (v33 - v19) & v19;
        v38 += 4 * a7;
        v37 += 4;
      }
      while ( v37 < a11 );
    ...
}

1.2. 内核驱动定位

这里先确定要分析的驱动.

kern_return_t
(*orig_IOServiceOpen)(
                   io_service_t    service,
                   task_port_t  owningTask,
                   uint32_t  type,
                   io_connect_t  *  connect );

kern_return_t
fake_IOServiceOpen(
              io_service_t    service,
              task_port_t  owningTask,
              uint32_t  type,
              io_connect_t  *  connect ) {
  
  kern_return_t kr;
  kr = orig_IOServiceOpen(service, owningTask, type, connect);
  io_name_t name;
  io_string_t path;
  
  kr = IORegistryEntryGetName(service, name);
  kr = IORegistryEntryGetPath(service, "IOService", path );
  if (kIOReturnSuccess != kr) {
    FATAL("open %s (0x%x)\n", mach_error_string(kr), kr);
  }
  CFMutableDictionaryRef properties = NULL;
  kr = _IORegistryEntryCreateCFProperties(service, &properties, kCFAllocatorDefault, kNilOptions);
  NSLog(@"%@", properties);
  printf(">>> service: %d, connect: %d, type: %d, name %s, path: %s\n", service, *connect, type, name, path);
  return kr;
}

// IOServiceOpen
void *IOServiceOpen_addr = dlsym(dlopen(0, RTLD_NOW), "IOServiceOpen");
ZzReplace((void *)IOServiceOpen_addr, (void *)fake_IOServiceOpen, (void **)&orig_IOServiceOpen);

通过 Hook 获取到期间使用到的驱动 AGXAcceleratorG10P_B0.

2018-12-24 10:52:04.671007+0800 ios_memory_usage_demo[4289:2499148] {
    AGXParameterBufferMaxSize = 201326592;
    AGXParameterBufferMaxSizeEverMemless = 134217728;
    AGXParameterBufferMaxSizeNeverMemless = 67108864;
    IOClass = "AGXAcceleratorG10P_B0";
    IOGLESBundleName = AppleMetalGLRenderer;
    InternalStatistics =     {
        "Allocated PB Size" = 1048576;
        agpTextureCreationCount = 0;
        agprefTextureCreationCount = 0;
        bufferSwapCount = 0;
        clientGLWaitTime = 0;
        dataBufferCount = 0;
        dataBytesPerSample = 0;
        freeDataBufferWaitTime = 0;
        gartCacheBytes = 33554432;
        gartFreeBytes = 1361772544;
        gartSizeBytes = 1393639424;
        hardwareSubmitWaitTime = 0;
        inUseSysMemoryBytes = 0;
        iosurfaceTextureCreationCount = 0;
        oolTextureCreationCount = 0;
        orphanedNonReusableSysMemoryBytes = 0;
        orphanedNonReusableSysMemoryCount = 0;
        orphanedReusableSysMemoryBytes = 0;
        orphanedReusableSysMemoryCount = 0;
        orphanedReusableSysMemoryHitRate = 0;
        stdTextureCreationCount = 0;
    };
    InternalStatisticsAccm =     {
        "Allocated PB Size" = 1048576;
        agpTextureCreationCount = 0;
        agprefTextureCreationCount = 0;
        bufferSwapCount = 0;
        clientGLWaitTime = 0;
        dataBufferCount = 0;
        dataBytesPerSample = 0;
        freeDataBufferWaitTime = 0;
        gartCacheBytes = 33554432;
        gartFreeBytes = 1361772544;
        gartSizeBytes = 1393639424;
        hardwareSubmitWaitTime = 0;
        inUseSysMemoryBytes = 0;
        iosurfaceTextureCreationCount = 0;
        oolTextureCreationCount = 0;
        orphanedNonReusableSysMemoryBytes = 0;
        orphanedNonReusableSysMemoryCount = 0;
        orphanedReusableSysMemoryBytes = 0;
        orphanedReusableSysMemoryCount = 0;
        orphanedReusableSysMemoryHitRate = 0;
        stdTextureCreationCount = 0;
    };
    MetalPluginClassName = AGXA11Device;
    MetalPluginName = AGXMetalA11;
    MetalStatisticsName = AGXMetalStatisticsExternalA11;
    PerformanceStatistics =     {
        CommandBufferRenderCount = 0;
        "Device Utilization %" = 0;
        "Renderer Utilization %" = 0;
        SplitSceneCount = 0;
        TiledSceneBytes = 0;
        "Tiler Utilization %" = 0;
        agpTextureCreationBytes = 0;
        agprefTextureCreationBytes = 0;
        contextGLCount = 0;
        finishGLWaitTime = 0;
        freeToAllocGPUAddressWaitTime = 0;
        gartMapInBytesPerSample = 0;
        gartMapOutBytesPerSample = 0;
        gartUsedBytes = 31866880;
        hardwareWaitTime = 0;
        iosurfaceTextureCreationBytes = 0;
        oolTextureCreationBytes = 0;
        recoveryCount = 0;
        stdTextureCreationBytes = 0;
        textureCount = 873;
    };
    PerformanceStatisticsAccum =     {
        CommandBufferRenderCount = 0;
        "Device Utilization %" = 0;
        "Renderer Utilization %" = 0;
        SplitSceneCount = 0;
        TiledSceneBytes = 0;
        "Tiler Utilization %" = 0;
        agpTextureCreationBytes = 0;
        agprefTextureCreationBytes = 0;
        contextGLCount = 0;
        finishGLWaitTime = 0;
        freeToAllocGPUAddressWaitTime = 0;
        gartMapInBytesPerSample = 0;
        gartMapOutBytesPerSample = 0;
        gartUsedBytes = 31866880;
        hardwareWaitTime = 0;
        iosurfaceTextureCreationBytes = 0;
        oolTextureCreationBytes = 0;
        recoveryCount = 0;
        stdTextureCreationBytes = 0;
        textureCount = 873;
    };
}
>>> service: 9219, connect: 39939, type: 2, name AGXAcceleratorG10P_B0, path: IOService:/AppleARMPE/arm-io@8F00000/AppleT8015IO/sgx@4000000/AGXAcceleratorG10P_B0

通过 hook IOConnectCallMethod 发现, 调用的是 selector == 0 的驱动方法

connnection: 8719, selector: 0, input: 0x0, inputCnt: 0,inputStruct: 0x16ba34cf0, inputStructCnt: 96, outputStruct: 0x16ba34b30, outputStructCnt: 72 

接下来需要定位 selector = 0 对应的内核驱动方法实现. 通过使用 iometa -n -bmosv ./kernelcache.release.iphone10.decrypt > IOKitDump.mosv dump 到 IOKit 所有类的关系和 vtable 信息.

通过分析 AGXAcceleratorG10P_B0 继承于 IOGraphicsAccelerator2, 并使用 0xfffffff006d38560 IOGraphicsAccelerator2::newUserClient() 在 Connnect Servevice 时创建 UserClient.

vtab=0xfffffff006fda6a8 size=0x00000c78 meta=0xfffffff0078658a8 parent=0xfffffff007865880 IOGraphicsAccelerator2 (com.apple.iokit.IOAcceleratorFamily2)
     0x460 func=0xfffffff006d38560 overrides=0xfffffff00756cf88 pac=0x0000 IOGraphicsAccelerator2::newUserClient()
signed __int64 __fastcall sub_FFFFFFF006D38560(__int64 a1, __int64 owningTask, __int64 security_id, __int64 type, IOUserClient **handler)
{
... ignore

  switch ( (_DWORD)type_1 )
  {
    case 1:
      v14 = (*(__int64 (__fastcall **)(__int64))(*(_QWORD *)v8 + 0x690LL))(v8);
      v15 = v14;
      if ( v14 )
      {
        if ( sub_FFFFFFF006D23004(v14, 0LL, owningTask_1) & 1 )
          goto LABEL_17;
        goto LABEL_22;
      }
      break;
    case 2:
      v16 = (*(__int64 (__fastcall **)(__int64))(*(_QWORD *)v8 + 0x688LL))(v8);
      v15 = v16;
      if ( v16 )
      {
        if ( sub_FFFFFFF006D30AE4(v16, 0LL, owningTask_1) & 1 )
          goto LABEL_17;
        goto LABEL_22;
      }
      break;
    case 3:

... ignore
}

由于之前 IOServiceOpen 是以 type = 2 打开的服务, 根据 IDA 里的伪代码, 调用 vtable 里 offset == 0x688 的虚方法, 这里

vtab=0xfffffff006fe0068 size=0x00001998 meta=0xfffffff0078666b0 parent=0xfffffff007866540 AGXAcceleratorG10 (com.apple.AGXG10P)
     0x688 func=0xfffffff006d4bf74 overrides=0xfffffff006d38994 pac=0x0000 AGXAcceleratorG10::fn_0x688()
_QWORD *sub_FFFFFFF006D4BF74()
{
  _QWORD *v0; // x19

  v0 = (_QWORD *)OSObject::operator new(336LL);
  sub_FFFFFFF006D7ABD4();
  *v0 = &off_FFFFFFF006FE41F8;
  OSMetaClass::instanceConstructed(&AGXSharedUserClient::gMetaClass);
  return v0;
}

直接找 AGXSharedUserClientexternalMethod 看看怎么实现的 dispatch.

vtab=0xfffffff006fe41f8 size=0x00000150 meta=0xfffffff007866a98 parent=0xfffffff0078657b8 AGXSharedUserClient (com.apple.AGXG10P)
     0x538 func=0xfffffff006d768f8 overrides=0xfffffff006d32098 pac=0x0000 AGXSharedUserClient::externalMethod()

可以看到之后分发给了父类 IOAccelSharedUserClient2, 接着跟踪, 可以看到 IOAccelSharedUserClient2externalMethod 则是直接转交给 IOUserClientexternalMethod 进行处理.

vtab=0xfffffff006fd9228 size=0x00000150 meta=0xfffffff0078657b8 parent=0xfffffff007646dc8 IOAccelSharedUserClient2 (com.apple.iokit.IOAcceleratorFamily2)
     0x538 func=0xfffffff006d32098 overrides=0xfffffff0075c033c pac=0x0000 IOAccelSharedUserClient2::externalMethod()

这里分两种情况分析 1. w1 == #0 2. w1 != #0, 这里 w1 == selector

1). w1 == #0

经过 CSEL X3, X8, X3, EQ, 会将参数 dipatch 设置为 off_FFFFFFF006FD9A38, 经过 IOUserClient::externalMethod 此时 dipatch == off_FFFFFFF006FD9A38, target == AGXSharedUserClient.

2). w1 != #0

那继续分析 getTargetAndMethodForIndex, 因为在 IOUserClient::externalMethod 会调用到 getTargetAndMethodForIndex 进行 dispatch.

分析 AGXSharedUserClient::getTargetAndMethodForIndex, w2 也就是 selector = #unknown, 直接转发给父类 IOAccelSharedUserClient2::getTargetAndMethodForIndex

vtab=0xfffffff006fe41f8 size=0x00000150 meta=0xfffffff007866a98 parent=0xfffffff0078657b8 AGXSharedUserClient (com.apple.AGXG10P)
     0x5a8 func=0xfffffff006d7691c overrides=0xfffffff006d320bc pac=0x0000 AGXSharedUserClient::getTargetAndMethodForIndex()

通过分析 IOAccelSharedUserClient2::getTargetAndMethodForIndex 发现通过取得 [x0,#0xF0] 中的 IOExternalMethod ** 基址, 之后通过 UMADDL X0, W2, W9, X8 取得对应 selector = #unknown 偏移的 IOExternalMethod *. 究竟 [X0,#0xF0] 里的地址到底指向哪里, 这里可以找找相关的方法实现.

vtab=0xfffffff006fd9228 size=0x00000150 meta=0xfffffff0078657b8 parent=0xfffffff007646dc8 IOAccelSharedUserClient2 (com.apple.iokit.IOAcceleratorFamily2)
     0x5a8 func=0xfffffff006d320bc overrides=0xfffffff0075c1824 pac=0x0000 IOAccelSharedUserClient2::getTargetAndMethodForIndex()

通过对 AGXSharedUserClient 和它父类 IOAccelSharedUserClient2 部分方法进行查看, 最后发现在 0xfffffff006d30e28 初始化了 [X0,#0xF0]qword_FFFFFFF006FD9888.

vtab=0xfffffff006fd9228 size=0x00000150 meta=0xfffffff0078657b8 parent=0xfffffff007646dc8 IOAccelSharedUserClient2 (com.apple.iokit.IOAcceleratorFamily2)
     0x5c8 func=0xfffffff006d30e28 overrides=0x0000000000000000 pac=0x0000 IOAccelSharedUserClient2::fn_0x5c8()

这里因为 selector == 0 所以这里直接分析 sub_FFFFFFF006D31FB4 即可

但是由于 iOS Kernel 里很多驱动的数据结构是未知的, 分析难度很大.

1.3. 内核驱动函数分析.

这里将部分符号化的驱动处理函数. 由于缺乏 runtime 的分析, 通过上面的静态分析, 这里也只能分析出使用到 AGXResource 等未知内存分布的内核类. 以及会在内核层使用 IOBufferMemoryDescriptor::createMappingInTask 创建内存 mapping, 并将 VirtualAddress 作为 OutputStruct 的第二个成员返回给 Userspace, 这个和我们在 userspace 看到的行为是一样的.

1.4. IOMemoryDescriptor 调用分析

通过查找一些 IOKit 资料/驱动, 在 IOKit 中会大量使用 IOBufferMemoryDescriptor 进行内存页分配,

所以这里对 IOBufferMemoryDescriptor 特别关注下, 这里看下 IOBufferMemoryDescriptor::inTaskWithOptionsoptions

options = input_1->x1.x_1_1_2 | 0x10063; 其中 0x10063 == kIOMemoryKernelUserShared | kIODirectionInOut | kIOMemoryPageable | kIOMemoryPurgeable, 这里通过对比一些 IOKit 驱动, 例如 VMsvga2 等, 发现这个参数仅在 Texture 绘制时出现, 那么是否和这个参数有关, 需要写一个 DemoDriver 测试下, 这里讲 iOS Kernel IOKit 的问题转化为 macOS Kernel IOKit 的问题.

1.5. 利用 DemoDriver 复现问题

// ===== FrameBufffer Test Case =====

IOReturn SharedMemoryAlloc(IOBufferMemoryDescriptor **buffer, IOOptionBits options, unsigned size) {
  IOKIT_AUTO_LOG_FLAG();

  *buffer = IOBufferMemoryDescriptor::inTaskWithOptions(0, options, size, PAGE_SIZE);
  if (!*buffer) {
    IOLog("failed to allocate buffer memory");
    return kIOReturnNoMemory;
  } else {
    return kIOReturnSuccess;
  }
}

IOReturn IOKitDemoDriver::userClientMap(task_t owningTask, DisplayXMap *map, uint32_t *mapSize) {
  IOKIT_AUTO_LOG_FLAG();
  IOReturn kr = kIOReturnSuccess;

  if (!map || !mapSize || *mapSize != sizeof *map)
    kr = kIOReturnBadArgument;

  FrameBuffferEmu *frameBuffer     = new FrameBuffferEmu;
  IOBufferMemoryDescriptor *buffer = frameBuffer->buffer;
  IOOptionBits options;
#if 0
  options = kIOMemoryKernelUserShared | kIODirectionInOut | kIOMemoryPageable | kIOMemoryPurgeable;
#elif 1
  options = kIOMemoryKernelUserShared | kIODirectionInOut | kIOMemoryPageable;
#endif

  kr = SharedMemoryAlloc(&buffer, options, PAGE_SIZE * 500);

#if 0
#elif 1
  options = kIOMapAnywhere;
#endif

  IOMemoryMap *iomap = buffer->createMappingInTask(owningTask, 0, options, 0, 0);

  map->address = iomap->getVirtualAddress();
  return kr;
}

加载刚才写的 IOKitDemoDriver.kext

sudo rm -rf IOKitDemoDriver.kext
sudo chown -R root:wheel IOKitDemoDriver.kext
sudo kextload IOKitDemoDriver.kext
kernel-exploitdeMac:Desktop kernel_exploit$ sudo chown -R root:wheel IOKitDemoDriver.kext
kernel-exploitdeMac:Desktop kernel_exploit$ sudo kextload IOKitDemoDriver.kext

这是增加了这个 flag 之后的调用结果, 很明显, 我们浮现了这个问题.


kernel-exploitdeMac:Desktop kernel_exploit$ ./IOKitDemoClient
[*] IOKitDemoDriver Open Success, Version: 1.1, Task: 0xffffff80123997e8
2019-02-15 00:03:31.787 IOKitDemoClient:footprint: 712 KB, internal: 712 KB, purges: 328, purgeable_count:5196
[*] userClientMap Address: 0x111984000
2019-02-15 00:03:31.789 IOKitDemoClient:footprint: 3 MB, internal: 4.9 MB, purges: 328, purgeable_count:5196
2019-02-15 00:03:31.789 IOKitDemoClient:footprint: 3 MB, internal: 4.9 MB, purges: 328, purgeable_count:5196
[*] userClientMap Address: 0x111b78000
2019-02-15 00:03:31.791 IOKitDemoClient:footprint: 3 MB, internal: 6.9 MB, purges: 328, purgeable_count:5196
2019-02-15 00:03:31.791 IOKitDemoClient:footprint: 3 MB, internal: 6.9 MB, purges: 328, purgeable_count:5196
[*] userClientMap Address: 0x111d6c000
2019-02-15 00:03:31.793 IOKitDemoClient:footprint: 3 MB, internal: 8.8 MB, purges: 328, purgeable_count:5196
2019-02-15 00:03:31.793 IOKitDemoClient:footprint: 3 MB, internal: 8.8 MB, purges: 328, purgeable_count:5196
[*] userClientMap Address: 0x111f60000
2019-02-15 00:03:31.794 IOKitDemoClient:footprint: 3 MB, internal: 10.8 MB, purges: 328, purgeable_count:5196
2019-02-15 00:03:31.795 IOKitDemoClient:footprint: 3 MB, internal: 10.8 MB, purges: 328, purgeable_count:5196
[*] userClientMap Address: 0x112154000
2019-02-15 00:03:31.798 IOKitDemoClient:footprint: 3 MB, internal: 12.7 MB, purges: 328, purgeable_count:5196
2019-02-15 00:03:31.798 IOKitDemoClient:footprint: 3 MB, internal: 12.7 MB, purges: 328, purgeable_count:5196
[*] userClientMap Address: 0x112348000
2019-02-15 00:03:31.800 IOKitDemoClient:footprint: 3 MB, internal: 14.7 MB, purges: 328, purgeable_count:5196
2019-02-15 00:03:31.800 IOKitDemoClient:footprint: 3 MB, internal: 14.7 MB, purges: 328, purgeable_count:5196
[*] userClientMap Address: 0x11253c000
2019-02-15 00:03:31.802 IOKitDemoClient:footprint: 3 MB, internal: 16.6 MB, purges: 328, purgeable_count:5196
2019-02-15 00:03:31.802 IOKitDemoClient:footprint: 3 MB, internal: 16.6 MB, purges: 328, purgeable_count:5196
[*] userClientMap Address: 0x112730000
2019-02-15 00:03:31.804 IOKitDemoClient:footprint: 3 MB, internal: 18.6 MB, purges: 328, purgeable_count:5196
2019-02-15 00:03:31.804 IOKitDemoClient:footprint: 3 MB, internal: 18.6 MB, purges: 328, purgeable_count:5196
[*] userClientMap Address: 0x112924000
2019-02-15 00:03:31.806 IOKitDemoClient:footprint: 3 MB, internal: 20.6 MB, purges: 328, purgeable_count:5196
2019-02-15 00:03:31.806 IOKitDemoClient:footprint: 3 MB, internal: 20.6 MB, purges: 328, purgeable_count:5196
[*] userClientMap Address: 0x112b18000
2019-02-15 00:03:31.808 IOKitDemoClient:footprint: 3 MB, internal: 22.5 MB, purges: 328, purgeable_count:5196

以下是移除该 flag 后的调用结果, phys_footprintinternal 相同.

kernel-exploitdeMac:Desktop kernel_exploit$ ./IOKitDemoClient
[*] IOKitDemoDriver Open Success, Version: 1.1, Task: 0xffffff801122c7e8
2019-02-14 23:57:29.417 IOKitDemoClient:footprint: 712 KB, internal: 712 KB, purges: 328, purgeable_count:5180
[*] userClientMap Address: 0x110584000
2019-02-14 23:57:29.422 IOKitDemoClient:footprint: 4.9 MB, internal: 4.9 MB, purges: 328, purgeable_count:5180
2019-02-14 23:57:29.422 IOKitDemoClient:footprint: 4.9 MB, internal: 4.9 MB, purges: 328, purgeable_count:5180
[*] userClientMap Address: 0x110778000
2019-02-14 23:57:29.424 IOKitDemoClient:footprint: 6.9 MB, internal: 6.9 MB, purges: 328, purgeable_count:5180
2019-02-14 23:57:29.425 IOKitDemoClient:footprint: 6.9 MB, internal: 6.9 MB, purges: 328, purgeable_count:5180
[*] userClientMap Address: 0x11096c000
2019-02-14 23:57:29.427 IOKitDemoClient:footprint: 8.8 MB, internal: 8.8 MB, purges: 328, purgeable_count:5180
2019-02-14 23:57:29.427 IOKitDemoClient:footprint: 8.8 MB, internal: 8.8 MB, purges: 328, purgeable_count:5180
[*] userClientMap Address: 0x110b60000
2019-02-14 23:57:29.428 IOKitDemoClient:footprint: 10.8 MB, internal: 10.8 MB, purges: 328, purgeable_count:5180
2019-02-14 23:57:29.428 IOKitDemoClient:footprint: 10.8 MB, internal: 10.8 MB, purges: 328, purgeable_count:5180
[*] userClientMap Address: 0x110d54000
2019-02-14 23:57:29.430 IOKitDemoClient:footprint: 12.8 MB, internal: 12.8 MB, purges: 328, purgeable_count:5180
2019-02-14 23:57:29.430 IOKitDemoClient:footprint: 12.8 MB, internal: 12.8 MB, purges: 328, purgeable_count:5180
[*] userClientMap Address: 0x110f48000
2019-02-14 23:57:29.432 IOKitDemoClient:footprint: 14.7 MB, internal: 14.7 MB, purges: 328, purgeable_count:5180
2019-02-14 23:57:29.433 IOKitDemoClient:footprint: 14.7 MB, internal: 14.7 MB, purges: 328, purgeable_count:5180
[*] userClientMap Address: 0x11113c000
2019-02-14 23:57:29.434 IOKitDemoClient:footprint: 16.7 MB, internal: 16.7 MB, purges: 328, purgeable_count:5180
2019-02-14 23:57:29.434 IOKitDemoClient:footprint: 16.7 MB, internal: 16.7 MB, purges: 328, purgeable_count:5180
[*] userClientMap Address: 0x111330000
2019-02-14 23:57:29.436 IOKitDemoClient:footprint: 18.6 MB, internal: 18.6 MB, purges: 328, purgeable_count:5180
2019-02-14 23:57:29.436 IOKitDemoClient:footprint: 18.6 MB, internal: 18.6 MB, purges: 328, purgeable_count:5180
[*] userClientMap Address: 0x111524000
2019-02-14 23:57:29.439 IOKitDemoClient:footprint: 20.6 MB, internal: 20.6 MB, purges: 328, purgeable_count:5180
2019-02-14 23:57:29.439 IOKitDemoClient:footprint: 20.6 MB, internal: 20.6 MB, purges: 328, purgeable_count:5180
[*] userClientMap Address: 0x111718000
2019-02-14 23:57:29.441 IOKitDemoClient:footprint: 22.5 MB, internal: 22.5 MB, purges: 328, purgeable_count:5180

1.6. kIOMemoryPurgeable 作用

对着 xnu-4570.20.62 一顿操作, 发现当存在 kIOMemoryPurgeable 时, 会设置 object->vo_purgeable_owner = ownerkernel_task, 但是这是否就是因为这个愿意导致 phys_footprint 不增加的原因呢, 这里在手动调试下正常情况下 phys_footprint 增加的原因.

1.7. 内核硬断调试

如果没有 kIOMemoryPurgeable, 进程 task 的 phys_footprint 为何增加呢?

这里通过获取进程 task_t task, 对应的保存 phys_footprintle_credit, 并对设置硬件写断点. (注意: lldb 内核调试的时, 硬断会失败, 具体原因由于时间原因尚未深究, 这里可以换成 GDB 或者 IDA remote GDB Debugger 两者是一样的)

p/x &(&(((task_t)0xffffff800fab09b0)->ledger->l_entries[task_ledgers.phys_footprint]))->le_credit

获取到保存 phys_footprintle_credit 后, 在 IDA 进行硬断.

调试器挂载

触发硬断后, 这里直接根据堆栈和调用规约, 根据图中的标注找到 caller. (不可以单步走)

__text:FFFFFF8012606C73 mov     rdi, [r12+168h]                 ; ledger
__text:FFFFFF8012606C7B mov     esi, cs:task_ledgers.iokit_mapped ; entry
__text:FFFFFF8012606C81 mov     rdx, rbx                        ; amount
__text:FFFFFF8012606C84 call    ledger_credit
__text:FFFFFF8012606C89 mov     rdi, [r12+168h]                 ; ledger
__text:FFFFFF8012606C91 mov     esi, cs:task_ledgers.phys_footprint ; entry
__text:FFFFFF8012606C97 mov     rdx, rbx                        ; amount
__text:FFFFFF8012606C9A call    ledger_credit
__text:FFFFFF8012606C9F mov     r9, [rbp+hint_offset]
__text:FFFFFF8012606CA6 mov     r10, qword ptr [rbp+var_68]
__text:FFFFFF8012606CAA mov     r12, [rbp+object]
__text:FFFFFF8012606CB1 test    r15d, r15d
__text:FFFFFF8012606CB4 jz      loc_FFFFFF8012606D8F

通过 lldb 看下对应的源码位置.

(lldb) b 0xFFFFFF8012606C9F
Breakpoint 2: where = kernel.development`vm_map_enter + 5407 [inlined] vm_map_iokit_mapped_region + 48 at vm_map.c:2737, address = 0xffffff8012606c9f

在 xnu 源码里发现实现位置.

1.8. kIOMemoryPurgeable 的对比

最终通过这里的不同点, 确定了问题的发生位置.

1.9. 总结

当调用 glTexImage2D 函数时, iOS 的 AGXAcceleratorG10P_B0 驱动, 会调用 IOBufferMemoryDescriptor::inTaskWithOptions 其中 options0x10063 == kIOMemoryKernelUserShared | kIODirectionInOut | kIOMemoryPageable | kIOMemoryPurgeable, 由于 options 中包含 kIOMemoryPurgeable flag, 导致 object->vo_purgeable_owner 被设置为 kernel_task, 因而当发生 #PFvm_fault 时, 进入 vm_map_enter 函数进行统计的时, 没有将该内存统计给对应进程的 task_t task, 而是统计给 kernel_task.

这里有一篇文章讲解了 为什么 Texture 下要使用 purgeable memory. https://www.khronos.org/registry/OpenGL/extensions/APPLE/APPLE_object_purgeable.txt

Rerfer

https://www.khronos.org/registry/OpenGL/extensions/APPLE/APPLE_object_purgeable.txt

Posts: 1

Participants: 1

Read full topic

请问iOS 12 的怎么抓appstore的https数据包呢

$
0
0

@q1f3 wrote:

需求:请问iOS 12 的怎么抓appstore的https数据包呢
操作步骤:iOS 12.0.1 通过rootlessJB可连接ssh,但安装不了cydia,不能通过frida去除ssl pinning,也运行不了ssl-kill-switch,且iOS 11以上的使用ssl-kill-switch好像没效果。所以想请教一下各位大神,iOS 12的怎么抓appstore的https数据包呢,明文可见的那种,使用wireshark的不算,看不到数据。

Posts: 13

Participants: 5

Read full topic

IOS11越狱 app 没有mobile文件读写权限


IOS9的Theos application项目在IOS11上无法运行

$
0
0

@benka wrote:

需求:本人小白,开发了一个测试小应用,使用的是Theos的application_modern整合tweak,打包deb,在IOS9和IOS10上可以正常运行,但是在IOS11安装后,点击应用程序却提示“XXX”需要更新,应用开发者需要更新此应用以便其在IOS11下正常工作。
我尝试使用export SDKVERSION=11.4.1的SDK进行编译,不行,在theos application里的makefile文件和tweak的makefile文件里都写了编译版本,不行,archs指定了armv7 armv7s arm64,也不行,不知道原因出在哪里了。
求助大神帮助,非常感谢!

Posts: 1

Participants: 1

Read full topic

Unsigned app crash with AppSync on iOS 10.3.3

$
0
0

@cccccccccccc wrote:

iphone 5s 10.3.3 越狱,使用AppSync 插件安装砸壳未签名的ipa文件,可以安装成功,但是App运行就会直接crash,网上没找到具体的解决办法。

Posts: 3

Participants: 2

Read full topic

编译XNU的时候出现 zero as null pointer constant

$
0
0

@qokelate wrote:

需求:
编译XNU的时候出现 zero as null pointer constant, 怎么传递-Werror,-Wzero-as-null-pointer-constant给make?

**日志:

xnu-4570.20.62/BUILD/obj/EXPORT_HDRS/libkern/libkern/OSAtomic.h:170:12: error: zero as
      null pointer constant [-Werror,-Wzero-as-null-pointer-constant]
    return OSAddAtomicLong(-1L, address);

操作步骤:
make SDKROOT=macosx ARCH_CONFIGS=X86_64 KERNEL_CONFIGS=“RELEASE”

** 环境:
macos/10.13.4 (17E199)
Xcode 9.4.1

Posts: 1

Participants: 1

Read full topic

【求助】帮忙砸一个外国App的壳

Theos 链接 AFNetworking 等第三方开源库

$
0
0

@Nectra wrote:

需求: 如何引入 AFNetworking 等第三方开源库

操作步骤: 我创建了一个 DObject 对象,其中引入了 AFNetwokring 作为网络请求库,在 make 编译的时候,提示如下:

Undefined symbols for architecture arm64:
  "_OBJC_CLASS_$_DRequest", referenced from:
      objc-class-ref in DObject.m.669d250e.o
  "_OBJC_CLASS_$_DVerifyManager", referenced from:
      objc-class-ref in DObject.m.669d250e.o
  "_OBJC_CLASS_$_NectraSecurity", referenced from:
      objc-class-ref in DObject.m.669d250e.o
ld: symbol(s) not found for architecture arm64

我初步认为是$(TWEAK_NAME)_FILES 中没有引入 AFNetworking 相关的 .m 文件的问题,如果我引用了就会出现 strongself 等这类不能被 C 语言所接受的 error。请求解决。

Posts: 1

Participants: 1

Read full topic

Viewing all 5723 articles
Browse latest View live