class dump 和 yololib 的使用

class dump 的使用

  • class dump 简介

    class dump 是一个命令行工具,用于检查存储在 MachO 文件中的 Objective-C 运行时信息。class dump 可以导出 MachO 文件中 Class、Category、Protocol 的声明,这与使用 otool -ov 提供的信息相同。class dump 可以将 MachO 文件中 Class、Category、Protocol 的声明分别存储在单独的 .h 文件中,使得导出的结果拥有更紧凑的布局和更高的可读性

    对于 iOS 逆向工程来说,class dump 是一个很好的工具。开发者可以通过 class dump:查看闭源的 application、framework、bundle,观察不同版本之间接口的演变,尝试使用私有框架,查看应用程序包中的私货,了解邮件应用潜在插件的 API

    官方网址

  • class dump 下载与安装

    下载地址:

    1. 官方下载地址
    2. GitHub 地址
    3. MonkeyDev:class dump(可用)

    安装步骤(macOS 10.11 之前):

    1. 点击此地址下载 class-dump 文件
    2. 将 class-dump 文件复制到 /usr/bin/ 目录下
    3. 打开终端,赋予 class-dump 文件可执行权限 sudo chmod +x /usr/bin/class-dump

    安装步骤(macOS 10.11 及以上版本):

    1. 点击此地址下载 class-dump 文件
    2. 因为在 macOS 10.11 及以上版本中引入了系统完整性保护(System Integrity Protection)
      所以即使是 Root 用户,对 /usr/bin/ 目录也只有读权限,没有写权限
      因此需要将 class-dump 文件复制到 /usr/local/bin/ 目录下
    3. 打开终端,赋予 class-dump 文件可执行权限 sudo chmod +x /usr/local/bin/class-dump
  • class dump 常用功能介绍

    注意:class-dump 只能用于已脱壳的 MachO 文件

    ~> class-dump
    class-dump 3.5 (64 bit)
    Usage: class-dump [options] <mach-o-file>
    
    where options are:
    -a				show instance variable offsets
    				显示成员变量的偏移量
    
    -A				show implementation addresses
    				显示方法实现的地址
    
    --arch <arch>	choose a specific architecture from a universal binary (ppc, ppc64, i386, x86_64, armv6, armv7, armv7s, arm64)
    				从通用二进制文件中选择特定的 CPU 架构(ppc, ppc64, i386, x86_64, armv6, armv7, armv7s, arm64)
    
    -C <regex>		only display classes matching regular expression
    				仅显示名称与给定的正则表达式匹配的类
    
    -f <str>		find string in method name
    				查找与给定的字符串匹配的方法名
    
    -H				generate header files in current directory, or directory specified with -o
    				在当前目录中或者在 -o 指定的目录中生成头文件
    
    -I				sort classes, categories, and protocols by inheritance (overrides -s)
    				按继承关系对 Class、Category、Protocol 进行排序(会覆盖 -s 指令)
    
    -o <dir>		output directory used for -H
    				用于 -H 指令中的输出目录
    
    -r				recursively expand frameworks and fixed VM shared libraries
    				递归地扩展 framework 和 fixed VM shared library
    
    -s				sort classes and categories by name
    				按名称对 Class 和 Category 进行排序
    
    -S				sort methods by name
    				按名称对方法进行排序
    
    -t				suppress header in output, for testing
    				禁止输出的头文件中 class-dump 本身的头部注释,用于测试
    
    --list-arches	list the arches in the file, then exit
    				列出 MachO 文件中的 CPU 架构,然后退出
    
    --sdk-ios <version>		specify iOS SDK version (will look in /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS<version>.sdk
    						指定 iOS SDK 版本(将会在 /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS<version>.sdk 中查找)
    
    --sdk-mac <version>		specify Mac OS X version (will look in /Developer/SDKs/MacOSX<version>.sdk)
    						指定 macOS SDK 版本(将会在 /Developer/SDKs/MacOSX<version>.sdk 中查找)
    
    --sdk-root <path>		specify the full SDK root path (or use --sdk-ios/--sdk-mac for a shortcut)
    						指定完整的 SDK 根路径(或使用 --sdk-ios/--sdk-mac 作为快捷方式)
    

class dump 原理简介

class dump 是如何确定 MachO 文件中包含的(类名)与(方法名、属性名、成员变量名)之间的对应关系的呢?让我们一起来分析一下它的实现原理

  • 准备工作

    首先,创建 Person 类。如下所示:
    Person 类
    在 ViewController.m 中创建 Person 类的对象、调用 Person 类的方法、实现 Person 类的协议。如下所示:
    ViewController.m
    其次,在真机环境编译项目,获取对应的 MachO 文件,并将其拖入 MachOView 中进行分析。如下所示:
    ClassDumpDemo-MachO
    MachOView
    最后,将 person.m 文件编译为 person.cpp 文件。如下所示:

    ~/Desktop/Training/ClassDumpDemo/ClassDumpDemo > xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc person.m -o person.cpp
    
  • 探索 class dump 提取 MachO 文件中类的信息的过程

    ① 在 Section64(__DATA, __objc_classlist) 中保存着 MachO 文件中所有 Objective-C 类的信息的地址
    比如,下图中 0x00000001 00008170 处保存着 Person 类的信息的地址 0x00000001 00009D78
    1.0
    ② Person 类的信息的地址 0x00000001 00009D78
    位于 Section64(__DATA, __objc_data)
    ObjC2 Class64: 0x100009D78 (_OBJC_CLASS_$_Person) 条目
    2.0
    对应 Person.cpp 文件中的结构体

    // 类信息结构体
    struct _class_t {
    	struct _class_t *isa;			// 指向该类对象所属的元类对象
    	struct _class_t *superclass;	// 指向该类对象的父类对象
    	void *cache;					// 指向该类对象的方法缓存
    	void *vtable;					// 指向该类对象的虚函数表
    	struct _class_ro_t *ro;			// 指向该类对象的详细信息
    };
    

    ③ 第二步的 Data 字段中保存着 Person 类的详细信息的地址 0x00000001 00009488
    位于 Section64(__DATA, __objc_const)
    ObjC2 Class64 Info: 0x100009488 (__OBJC_CLASS_RO_$_Person) 条目
    3.0
    对应 Person.cpp 文件中的结构体

    // 类详细信息结构体
    struct _class_ro_t {
    	unsigned int flags;									// 该类对象的标志位
    	unsigned int instanceStart;							// 该类的实例对象中成员变量的起始位置(byte)。64 位架构下一般为 8,即该类的实例对象中成员变量的起始位置一般从 isa 指针之后开始
    	unsigned int instanceSize;							// 该类的实例对象的大小(byte,包含 isa 指针)
    	const unsigned char *ivarLayout;					// 指向该类对象的成员变量的内存布局
    	const char *name;									// 指向该类对象的名称
    	const struct _method_list_t *baseMethods;			// 指向该类对象的对象方法列表
    	const struct _objc_protocol_list *baseProtocols;	// 指向该类对象所遵守的协议列表
    	const struct _ivar_list_t *ivars;					// 指向该类对象的成员变量列表
    	const unsigned char *weakIvarLayout;				// 指向该类对象的弱成员变量的内存布局
    	const struct _prop_list_t *properties;				// 指向该类对象的属性列表
    };
    

    ④ 第三步的 Name 字段中保存着 Person 类的类名字符串的地址 0x00000001 000073C9,位于 Section64(__TEXT, __objc_classname)
    4.0
    ⑤ 第三步的 Base Methods 字段中保存着 Person 类的对象方法列表的地址 0x00000001 000092F0
    位于 Section64(__DATA, __objc_const)
    ObjC2 Method64 List: 0x1000092F0 (__OBJC_$_INSTANCE_METHODS_Person) 条目
    5.0
    对应 Person.cpp 文件中的结构体

    // 方法列表结构体
    static struct _method_list_t {
    	unsigned int entsize;					// 方法列表中单个元素的大小,即 sizeof(struct _objc_method)
    	unsigned int method_count;				// 方法列表中元素的个数
    	struct _objc_method method_list[15];	// 用于存储方法的数组
    } 
    
    // 方法结构体
    struct _objc_method {
    	struct objc_selector * _cmd;			// 指向方法的名称
    	const char *method_type;				// 指向方法的类型
    	void  *_imp;							// 指向方法的实现
    };
    

    以 Person 类的对象方法 initWithName:age: 为例
    其方法名称保存在地址 0x00000001 00006EF7 处,位于 Section64(__TEXT, __objc_methname)
    其方法类型保存在地址 0x00000001 00007D4C 处,位于 Section64(__TEXT, __objc_methtype)
    5.1
    5.2
    ⑥ 第三步的 Instance Variables 字段中保存着 Person 类成员变量列表的地址 0x00000001 000093E8
    位于 Section64(__DATA, __objc_const)
    ObjC2 Variable64 List: 0x100009E38 (__OBJC_$_INSTANCE_VARIABLES_Person) 条目
    6.0
    对应 Person.cpp 文件中的结构体

    // 成员变量列表结构体
    static struct _ivar_list_t {
    	unsigned int entsize;			// 成员变量列表中单个元素的大小,即 sizeof(struct _ivar_t )
    	unsigned int count;				// 成员变量列表中元素的个数
    	struct _ivar_t ivar_list[3];	// 用于存储成员变量的数组
    }
    
    // 成员变量结构体
    struct _ivar_t {
    	unsigned long int *offset;  	// 指向成员变量相对于实例对象基地址的偏移量
    	const char *name;				// 指向成员变量的名称
    	const char *type;				// 指向成员变量的类型
    	unsigned int alignment;			// 成员变量的内存对齐
    	unsigned int size;				// 成员变量的大小
    };
    

    以 Person 类的成员变量 _age 为例
    其偏移量保存在地址 0x00000001 00009CC4 处,位于 Section64(__DATA, __objc_ivar)
    其变量名称保存在地址 0x00000001 00006F1B 处,位于 Section64(__TEXT, __objc_methname)
    其变量类型保存在地址 0x00000001 00007D6D 处,位于 Section64(__TEXT, __objc_methtype)
    6.1
    6.2
    6.3
    ⑦ 第三步的 Base Properties 字段中保存着 Person 类属性列表的地址 0x00000001 00009450
    位于 Section64(__DATA, __objc_const)
    ObjC2 Property64 List: 0x100009450 (__OBJC_$_PROP_LIST_Person) 条目
    7.0
    对应 Person.cpp 文件中的结构体

    // 属性列表结构体
    static struct _prop_list_t {
    	unsigned int entsize;  				// 属性列表中单个元素的大小,即 sizeof(struct _prop_t)
    	unsigned int count_of_properties;	// 属性列表中元素的个数
    	struct _prop_t prop_list[3];		// 用于存储属性的数组
    }
    
    // 属性结构体
    struct _prop_t {
    	const char *name;					// 指向属性的名称
    	const char *attributes;				// 指向属性的特征
    };
    

    以 Person 类的属性 delegate 为例
    其属性名称保存在地址 0x00000001 0000730D 处,位于 Section64(__TEXT, __cstring)
    其属性特征保存在地址 0x00000001 00007316 处,位于 Section64(__TEXT, __cstring)
    7.1
    7.2

  • 探索 class dump 提取 MachO 文件中分类的信息的过程

    class dump 提取 MachO 文件中分类的信息的过程,与提取 MachO 文件中类的信息的过程类似,在此不再赘述

  • 探索 class dump 提取 MachO 文件中协议的信息的过程

    class dump 提取 MachO 文件中协议的信息的过程,与提取 MachO 文件中类的信息的过程类似,在此不再赘述

yololib 的使用

  • yololib 简介

    yololib 是一个命令行工具,用于向 MachO 文件中注入动态库

  • yololib 下载与安装

    GitHub 地址

    安装步骤(macOS 10.11 之前):

    1. 点击此地址下载 yololib 工程,编译后得到 yololib 文件
    2. 将 yololib 文件复制到 /usr/bin/ 目录下
    3. 打开终端,赋予 yololib 文件可执行权限 sudo chmod +x /usr/bin/yololib

    安装步骤(macOS 10.11 及以上版本):

    1. 点击此地址下载 yololib 文件
    2. 因为在 macOS 10.11 及以上版本中引入了系统完整性保护(System Integrity Protection)
      所以即使是 Root 用户,对 /usr/bin/ 目录也只有读权限,没有写权限
      因此需要将 yololib 文件复制到 /usr/local/bin/ 目录下
      打开终端,赋予 yololib 文件可执行权限 sudo chmod +x /usr/local/bin/yololib
  • yololib 常用功能介绍

    注意:

    1. yololib 只能用于向已脱壳的 MachO 文件注入动态库
    2. 使用 yololib 向 MachO 文件注入动态库之后,需要对 MachO 文件进行重签名
    3. 注入的动态库与目标 MachO 文件的 CPU 架构需要一致
    # 动态库注入命令格式
    yololib MachO文件路径 动态库二进制文件路径
    
  • 使用 yololib 注入动态库之前和之后,MachO 文件的区别

    使用 yololib 注入动态库之前,MachO 文件为:
    before
    使用 yololib 向 MachO 文件分别注入 .dylib.framework 格式的动态库:

    # 输出当前目录下所包含的动态库与 MachO 文件
    ~/Desktop/YololibDemo > ls -l
    total 512
    drwxr-xr-x  7 Airths  staff    224  8 14 14:55 ATool.framework
    drwxr-xr-x  7 Airths  staff    224  8 14 14:55 BTool.framework
    -rwxr-xr-x@ 1 Airths  staff  80000  8 14 14:59 TargetMachO
    -rwxr-xr-x  1 Airths  staff  86880  8 14 14:55 libCTool.dylib
    -rwxr-xr-x  1 Airths  staff  86880  8 14 14:55 libDTool.dylib
    
    # 1.向 TargetMachO 注入 libCTool.dylib
    ~/Desktop/YololibDemo > yololib TargetMachO libCTool.dylib
    
    # 2.向 TargetMachO 注入 libDTool.dylib
    ~/Desktop/YololibDemo > yololib TargetMachO libDTool.dylib
    
    # 3.向 TargetMachO 注入 ATool.framework/ATool
    ~/Desktop/YololibDemo > yololib TargetMachO ATool.framework/ATool
    
    # 4.向 TargetMachO 注入 BTool.framework/BTool
    ~/Desktop/YololibDemo > yololib TargetMachO BTool.framework/BTool
    

    使用 yololib 注入动态库之后,MachO 文件为:
    after
    after
    由此可见:

    1. 使用 yololib 注入的动态库会按注入顺序以 LC_LOAD_DYLIB 命令的形式拼接在原有 MachO 文件 Load Command 区域的末尾

补充

  • 系统完整性保护(SIP)

    根据苹果官方文档 System Integrity Protection Guide 的介绍,系统完整性保护(System Integrity Protection)包括以下机制:

    1. 文件系统保护(File System Protection),保护系统文件和目录的内容、以及文件系统权限。受保护的目录有 /System/bin/sbin/usr(不包括 /usr/local
    2. 运行时保护(Runtime Protection),保护进程免受代码注入、运行时附件(如调试)、DTrace 的影响
    3. 内核扩展保护(Kernel Extensions Protection),防止运行未经 Apple 授权(签名)的内核扩展(如驱动程序)

    macOS 的系统完整性保护默认情况下处于启用状态,关闭它将会产生以下风险:

    1. 如果失去了文件系统保护,则恶意软件可以通过任何 macOS 的漏洞提权到 Root,更改任意它想要更改的文件,感染任意它想要感染的文件。因为文件系统保护拒绝 Root 用户更改受保护的系统文件和系统目录的内容,所以在文件系统保护下,即使恶意软件通过漏洞提权到 Root,也无法修改系统文件。如果关闭 SIP,一旦 macOS 出现漏洞,使得进程可以自行提权后,则进程将可以为所欲为。这种漏洞并不是没有,之前就有多个非常严重的 dyld 漏洞,软件可以通过这些漏洞直接获得 Root 权限。往小了说,文件系统保护的功能也可以防止小白用户不小心删除系统文件导致系统崩溃的问题
    2. 如果失去了运行时保护,则等于给恶意软件开了一个后门。macOS 的所有进程都有一个对应的内核 Task,通过 task_for_pid Mach 调用可以控制这个进程。Mach 内核同时也有很多主机特权接口,用于控制系统(严重的比如:可以关闭 CPU 内核,直接挂掉系统等)。如果一个系统软件使用了这种特权接口,而又存在漏洞的话,则攻击者可以从栈溢出入手,然后 fork/exec ,即可获得这个特权接口。运行时保护避免了这种可能。
      另外,一票的动态注入程序(如 Cycript、SIMBL 等等),在 SIP 开启的状态下都无法使用。这也就避免了利用这些动态注入手段,非法获取和修改系统信息、应用软件状态的可能
    3. 如果失去了内核扩展保护,则可能会失去整个 macOS 系统的控制权。内核扩展保护是以前版本的 macOS 就已经拥有的功能,它拒绝加载任何未经 Apple 签名的内核扩展(即驱动程序)。如果说前面提到的两点风险还不是特别严重的话,那么这第三点风险应该算是最为严重的。因为内核扩展直接运行在内核态,其拥有几乎全部的系统控制权,它绝对是完全掌握系统控制权的最佳地点。如果你不小心安装了一个恶意的 Kext,而内核扩展保护又没有开启的话,则理论上你的系统就不再属于你了,你的任何隐私都会暴露在攻击者的面前。一个很好的例子可以说明这一点:我们都知道 SIP 的控制程序 csrutil 只能在 Recovery 的状态下开启或者关闭 SIP。但是如果你关闭内核扩展保护,那么攻击者就可以通过加载一个恶意的 Kext 并直接在用户态通知该 Kext,关闭 SIP

    以上这些由关闭 SIP 而产生的潜在风险,并不是危言耸听,而是都已经被证实过,且被实现过的攻击手段
    所以如果你并不知道 SIP 到底有什么用,它到底能保护什么,则请你不要随意地关闭它

    另外,能搞定 SIP 的手段其实并非只有通过 csrutil。SIP 的开启和关闭其实是由一个存在于 NVRAM 中的受保护的变量决定的,当系统启动时,macOS 内核会收到这个由 EFI 读取并传递过来的变量,并决定是否启用 SIP。那么,如果你使用了类似 Clover 等的第三方 Boot Manager/Boot Loader,只要它足够恶意,那么它就可以从 Boot Loader 下手直接在 EFI 环境中更改这个 NVRAM 变量,使得用户在不知情的情况下失去 SIP 保护

    如何关闭和开启系统完整性保护(关闭系统完整性保护后,你的 macOS 将完全暴露在潜在的风险中,如非必要,请勿关闭):

    1. 进入恢复模式(Recovery)。重启 macOS 并按住 option 键,直到出现磁盘图标后松开。然后按住 command + R,直到出现苹果图标后松开。之后等待片刻,进入 macOS 恢复模式

    2. 关闭 SIP。在进入恢复模式后,在顶部菜单栏中选择:实用工具-终端,在终端输入命令 csrutil disable,如果返回提示 Successfully disabled System Integrity Protection.Please restart the machine for the changes to take effect.,则说明 SIP 关闭成功

      然后再输入 reboot 重启 macOS

      macOS 10.15 及以上版本的系统,在关闭 SIP 并重启系统后,还需要在终端运行命令 sudo mount -uw / 才能完全获取系统权限

    3. 开启 SIP。在进入恢复模式后,在顶部菜单栏中选择:实用工具-终端,在终端输入命令 csrutil enable,如果返回提示 Successfully enabled System Integrity Protection.Please restart the machine for the changes to take effect.,则说明 SIP 开启成功

      然后再输入 reboot 重启 macOS

    虽然系统完整性保护可以限制 Root 用户的权限,从而达到保护系统安全的目的
    但是当用户因为一些特殊需求需要修改某些系统文件或者系统目录时,会提示没有权限
    此时用户可以短暂地关闭 SIP 来完成操作,并且记得在操作完成之后立即开启 SIP

    # 例如:将 class-dump 文件拷贝到 /usr/bin/ 下
    # 注意:即使关闭了 SIP,也无法直接通过图形界面对 /usr/bin 目录进行操作
    #	   需要使用 sudo cp 命令将 class-dump 文件拷贝到 /usr/bin/ 目录下
    ~ > sudo cp -r /Users/Airths/Desktop/class-dump   /usr/bin/class-dump
    ~ > sudo chmod +x /usr/bin/class-dump
    
  • macOS 中 shell 配置文件的加载顺序

    /etc/profile
    /etc/paths 
    ~/.bash_profile 
    ~/.bash_login 
    ~/.profile 
    ~/.bashrc
    ~/.zshrc
    

    注意:

    1. / 代表系统根目录,~/ 代表当前用户目录,usr 是 Unix Software Resource 的缩写
    2. /etc/profile/etc/paths 是系统级别的 shell 配置文件,shell 一启动就会加载
    3. ~/.bash_profile~/.bash_login~/.profile 是当前用户级别的 shell 配置文件
      shell 启动时会按照从前往后的顺序读取,但只会执行最先读取到的那一个
    4. 如果默认 shell 是 bash,则 shell 启动时会载入 ~/.bashrc
      如果默认 shell 是 zsh,则 shell 启动时会载入 ~/.zshrc

    可以通过以下命令获取 macOS 默认使用的 shell:

    ~ > echo $SHELL
    /bin/zsh
    

    可以通过以下命令获取 macOS 全部预置的 shell:

    ~ > cd /private/etc
    /private/etc > cat shells
    # List of acceptable shells for chpass(1).
    # Ftpd will not allow users to connect who are not using
    # one of these shells.
    
    /bin/bash
    /bin/csh
    /bin/dash
    /bin/ksh
    /bin/sh
    /bin/tcsh
    /bin/zsh
    

    设置 PATH 环境变量的语法为:

    # 路径中间用冒号隔开
    export PATH="$PATH:<PATH 1>:<PATH 2>:<PATH 3>:---:<PATH N>"
    

    修改环境变量之后,需要重启 macOS 才能生效。如果想立刻使修改生效,则需要执行下面的命令:

    source 相应的配置文件路径
    
  • macOS 中常见的命令行工具存储目录

    /bin
    /sbin
    /usr/bin
    /usr/sbin
    /usr/local/bin
    /usr/local/sbin
    

    /bin/sbin 的区别:
    如果是普通用户和系统管理员都必备的二进制文件,则会存放在 /bin
    如果是系统管理员必备,但是普通用户根本不会用到的二进制文件,则会存放在 /sbin

    /usr/bin/usr/sbin 的区别:
    如果不是普通用户和系统管理员必备的二进制文件,则会存放在 /usr/bin
    如果不是系统管理员必备的二进制文件,则会存放在 /usr/sbin

    /usr/local/bin/usr/local/sbin 的区别:
    如果是普通用户和系统管理员都可用的,且与本地机器无关的二进制文件,则会存放在 /usr/local/bin
    如果是系统管理员可用的,且与本地机器无关的二进制文件,则会存放在 /usr/local/sbin

    注意:bin 是 binary 的缩写

  • Sublime Text 3 的下载与使用

    Sublime Text 是一款流行的代码编辑器软件,也是 HTML 和散文先进的文本编辑器,可运行在 Linux,Windows 和 Mac OS X。也是许多程序员喜欢使用的一款文本编辑器软件

    官网首页

    官方下载地址

    官方说明文档

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值