12

iOS RE 4 beginners 1 - MachO && class-dump

 3 years ago
source link: https://o0xmuhe.github.io/2021/07/11/iOS-RE-4-beginners-1/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
2021-07-11iOS18 minutes read (About 2705 words)

iOS RE 4 beginners 1 - MachO && class-dump

roadmap

之前在 iosre看到一张比较系统的iOS逆向学习路线图,因为接触过一段时间macOS上服务的漏洞挖掘,所以对*OS安全还是挺有兴趣的,也一直想系统性地学习下iOS逆向,之前的一直不成体系,也很零碎,正好对着这个图重构下知识体系。

ios_re

macho file format

类似Windows/Linux平台逆向学习,首先要学习正向开发的基础知识,以及涉及的文件格式(指可执行文件):

  • Windows - PE
  • Linux - ELF
  • *OS - MachO

根据roadmap中的app分析流程,第一步就是“砸壳“,就是在根据文件格式做文章,因为macho文件是加密的,被加载到内存执行的时候才会解密,所以我们做静态分析,需要把内存中解密之后的可执行文件dump出来,并修复文件才可以拖入hopper/IDA正常分析。

Overview

Untitled

我感觉这些可执行文件大同小异的味道,基本都是文件头+各种节区。 在macOS上你可以使用:

  • MachOView
  • MachOExplorer

来查看一个macho文件的结构,推荐前者,后者不知道为什么总是卡卡的,而且很容易崩溃 :(

总体上来看,macho文件格式可以看做:

  • Header

  • Load Commands

    • segment load(1-n)
    • Segment(1-n)

Header

只关注几个基本字段

  • magic number : 表示macho的类型,FAT, ARMv7,ARM64,x86_64
    • FAT 就是 “胖文件”,表示这个文件里包含了多个架构的MachO文件,可以使用lipo分离
  • CPU Type, CPU SubType : arch
  • Number of load commands : Load commands的数量
  • flags:表示一些标识位,比如是否开了PIE,checksec可以从这里获取一些信息。
  • reversed:64位保留字段

CleanShot 2021-07-11 at 15.09.41@2x

Load Commands

CleanShot 2021-07-11 at 15.17.40@2x

即告诉操作系统,该如何加载文件中的数据。

  • LC_SEGMENT_64:定义一个段,加载后被映射到内存中,包括里面的节。 比如代码段 数据段
  • LC_DYLD_INF0_0NLY:记录了有关链接的重要信息,包括在_LINKEDIT中动态链接 相关信息的具体偏移和大小。ONLY表示这个加载指令是程序运行所必需的,如果旧的 链接器无法识别它,程序就会出错。
  • LC_SYMTAB:为文件定义符号表和字符串表,在链接文件时被链接器使用,同时也用于调试器映射符号到源文件。符号表定义的本地符号仅用于调试,而已定义和未定义的external符号被链接器使用。
  • LC_DYSYMTAB:将符号表中给出符号的额外符号信息提供给动态链接器。
  • LC_LOAD_DYLINKER:默认的加载器路径。 /usr/lib/dyld
  • LC.UUID:用于标识Mach-0文件的ID,也用于崩溃堆栈和符号文件的对应解析。
  • LC_VERSION_MIN_IPHONEOS:系统要求的最低版本。
  • LC.SOURCE.VERSION:构建二进制文件的源代码版本号。
  • LC.MAIN:程序的入口。dykl获取该地址,然后跳转到该处执行。
  • LC_ENCRYPTION_INFO_64:文件是否加密的标志,加密内容的偏移和大小。
    • lldb dump 砸壳修复文件之后,需要修改该标识位以确保正常反汇编文件。
  • LC_LOAD_DYLIB:依赖的动态库,包括动态库名称、当前版本号、兼容版本号。可以 使用 “otool -L xxx”命令查看。
  • LC_RPATH: Runpath Search Paths, @rpath 搜索的路径。
  • LC_FUNCTION_STARTS:函数起始地址表,使调试器和其他程序能很容易地看到一个地址是否在函数内。
  • LC_DATA_IN_CODE:定义在代码段内的非指令的表。
  • LC_CODE_SIGNATURE:代码签名信息。

Data-Segments

各种节区,比如代码段,数据段,只读数据段等:

CleanShot 2021-07-11 at 15.19.59@2x

这里可以看到很多__DATA, __objc__? 节区,Symbol Table String Table也单独列了出来。

  • __objc_protolist
  • __objc_classlist
  • __objc_catlist section

这些节区保存了OC中类名,函数名等信息,这就为从MachO中dump出来头文件打下了基础。

Get class info from macho file

__DATA, __objc_protolist节区:

CleanShot 2021-07-11 at 15.30.45@2x

存储的都是指针,指向一个又一个protocol的结构,可以参考objc的代码 :

struct protocol_t : objc_object {
const char *mangledName;
struct protocol_list_t *protocols;
method_list_t *instanceMethods;
method_list_t *classMethods;
method_list_t *optionalInstanceMethods;
method_list_t *optionalClassMethods;
property_list_t *instanceProperties;
uint32_t size; // sizeof(protocol_t)
uint32_t flags;
// Fields below this point are not always present on disk.
const char **_extendedMethodTypes;
......

}

struct objc_object {
private:
isa_t isa;

public:
...
}

所以我们可以按照结构体索引 __DATA, __objc_protolist 里指针指向的位置的数据,就可以解析出来protocol的类型,名字,方法等信息。

class-dump read notes

macos11.4 + xcode12

compile

Q : openssl/aes.h not found

A : add header file path

export LDFLAGS="-L/usr/local/opt/openssl/lib"
export CPPFLAGS="-I/usr/local/opt/openssl/include"

XCode中的配置是:

111

Q : Library not found for -lcrypto

A : add the missing dylib

fix_crypto

raed && debug

核心逻辑就看

- (void)processObjectiveCData;
{
for (CDMachOFile *machOFile in self.machOFiles) {
CDObjectiveCProcessor *processor = [[[machOFile processorClass] alloc] initWithMachOFile:machOFile];
[processor process];
[_objcProcessors addObject:processor];
}
}
- (void)process;
{
if (self.machOFile.isEncrypted == NO && self.machOFile.canDecryptAllSegments) {
[self.machOFile.symbolTable loadSymbols];
[self.machOFile.dynamicSymbolTable loadSymbols];

[self loadProtocols];
[self.protocolUniquer createUniquedProtocols];

// Load classes before categories, so we can get a dictionary of classes by address.
[self loadClasses];
[self loadCategories];
}
}

1. symbolTable loadSymbols

Load Commands 里找到 LC_SYMTAB,然后找到 __DATA(依赖属性 RW)。

然后利用 LC_SYMTAB 初始化了cursor开始遍历找符号。

CleanShot 2021-07-11 at 15.47.33@2x

strtab 从 string table 开始 : 一个 symbol起始位置,一个string起始位置。

然后根据 arm 还是 x64 走不同的逻辑(这里目标是ARM64的Binary) :

CleanShot 2021-07-11 at 15.48.18@2x

开始解析 symbol table,item by item

string table index  -->  在string table里找到对应的 string
type
section index
desc
value

然后根据string table index里找到对应的string,放到symbols数组里,

根据 string 的 value 判断是不是 class,这里是根据字符串的开头是不是 @"*OBJC_CLASS*$_"

对于解析出来class name,添加到 class symbols dict里,这样处理之后,symbols, classSymbols都有了。

2. dynamicSymbolTable loadsymbols

3. loadProtocols

__DATA , __objc_protolist 读取 对应的value

比如得到地址0x1009ccc58

走到 - (CDOCProtocol *)protocolAtAddress:(uint64_t)address

初始化对应的CDOCProtocol对象

依赖这个地址,从文件对应地址读取出来 这个 proto的相关信息:

struct cd_objc2_protocol objc2Protocol;
objc2Protocol.isa = [cursor readPtr];
objc2Protocol.name = [cursor readPtr];
objc2Protocol.protocols = [cursor readPtr];
objc2Protocol.instanceMethods = [cursor readPtr];
objc2Protocol.classMethods = [cursor readPtr];
objc2Protocol.optionalInstanceMethods = [cursor readPtr];
objc2Protocol.optionalClassMethods = [cursor readPtr];
objc2Protocol.instanceProperties = [cursor readPtr];
objc2Protocol.size = [cursor readInt32];
objc2Protocol.flags = [cursor readInt32];
objc2Protocol.extendedMethodTypes = 0;

name protocols这些字段是一个地址,指向对应的值(字符串/数组)

最后参照objc2Protocol的值,分别获取protocol 的 name, 各种methods,属性等,初始化了protocol对象

所以protocols就都处理出来了,最后得到了

_protocolsByAddress __NSDictionaryM * 6781 key/value pairs 0x0000000112f93820

4. protocolUniquer createUniquedProtocols

依赖3中找到的 _protocolsByAddress

name -> protocol 对应关系的dict addr -> protocol 对应关系的dict

p1->protocols 里还有protocol,merge进来(adopted protocols)

  • p1 : _name __NSCFString * @”AWEFriendsActivityWidgetConfigurationIntentHandling” 0x0000000112fbc710

  • p2 : _name NSTaggedPointerString * @”NSObject” 0x07518ee6ed78d7f9

@interface AWEFriendsActivityWidgetConfigurationIntentHandling : NSObject { //blablabla… }

5. loadClasses

解析section : __DATA __objc_classlist

和3类似的套路,先得到 一个 地址,然后根据地址,去文件中索引对应的结构:

CDOCClass *aClass = [self loadClassAtAddress:val]

只调试一次过程分析即可: val uint64_t 4335166480 In [2]: hex(4335166480) Out[2]: '0x102656410'

这个0x102656410,使用machoview也能看到,调试+machoview对比看,更容易理解。

loadClassAtAddress方法分析:

struct cd_objc2_class objc2Class;
objc2Class.isa = [cursor readPtr];
objc2Class.superclass = [cursor readPtr];
objc2Class.cache = [cursor readPtr];
objc2Class.vtable = [cursor readPtr];
objc2Class.data = [cursor readPtr];
objc2Class.reserved1 = [cursor readPtr];
objc2Class.reserved2 = [cursor readPtr];
objc2Class.reserved3 = [cursor readPtr];

也是读取对应的class结构,这个过程其实很眼熟,如果读过iOS逆向的书,比如庆神的书,有一章介绍oc方法调用过程的,会把oc->cpp代码,那里面这个 oc object的结构分析的很清楚。

然后解析 class->data 字段

struct cd_objc2_class_ro_t objc2ClassData;
objc2ClassData.flags = [cursor readInt32];
objc2ClassData.instanceStart = [cursor readInt32];
objc2ClassData.instanceSize = [cursor readInt32];
if ([self.machOFile uses64BitABI])
objc2ClassData.reserved = [cursor readInt32];
else
objc2ClassData.reserved = 0;

objc2ClassData.ivarLayout = [cursor readPtr];
objc2ClassData.name = [cursor readPtr];
objc2ClassData.baseMethods = [cursor readPtr];
objc2ClassData.baseProtocols = [cursor readPtr];
objc2ClassData.ivars = [cursor readPtr];
objc2ClassData.weakIvarLayout = [cursor readPtr];
objc2ClassData.baseProperties = [cursor readPtr];

然后得到class 的 name,methods,protocol, property信息 然后返回这个class

展开说下 获取 methods && property的时候

  • (NSArray *)loadMethodsAtAddress:(uint64_t)address; { return [self loadMethodsAtAddress:address extendedMethodTypesCursor:nil]; }

loadMethodsAtAddress :

objc2Method.name  = [cursor readPtr];
objc2Method.types = [cursor readPtr];
objc2Method.imp = [cursor readPtr];
NSString *name = [self.machOFile stringAtAddress:objc2Method.name];
NSString *types = [self.machOFile stringAtAddress:objc2Method.types];

一样的套路,都是解析出来对应的字段,然后按照这些字段读取信息(string) CDOCMethod *method = [[CDOCMethod alloc] initWithName:name typeString:types address:objc2Method.imp]; [methods addObject:method]; 最后获得methods数组,给前面填充class的地方使用

loadIvarsAtAddress ,loadPropertiesAtAddress , loadMethodsOfMetaClassAtAddress 同理

至此,class解析完毕

6. loadCategories

关于Categories 可以看 https://zhuanlan.zhihu.com/p/24925196

处理 __DATA __objc_catlist section :

  • (CDOCCategory *)loadCategoryAtAddress:(uint64_t)address;

一样的处理方法

struct cd_objc2_category objc2Category;
objc2Category.name = [cursor readPtr];
objc2Category.class = [cursor readPtr];
objc2Category.instanceMethods = [cursor readPtr];
objc2Category.classMethods = [cursor readPtr];
objc2Category.protocols = [cursor readPtr];
objc2Category.instanceProperties = [cursor readPtr];
objc2Category.v7 = [cursor readPtr];
objc2Category.v8 = [cursor readPtr];

可以看到和对objc2Class的处理有点像,就是因为是category的原因,所以字段有不同, 简单的理解成 处理一种特殊的class,并且提取出相应的 methods 和 properties就行

至此整个 process函数的处理结束

7. 处理 or 输出

这部分主要是处理输出了,如果没什么参数就直接stdout输出,如果有指定文件目录,就遍历之前process得到的信息,写文件(.h)到指定的目录。

output

Reference

https://zhuanlan.zhihu.com/p/24925196

https://en.wikipedia.org/wiki/Mach-O

https://iosre.com/

iOS应用逆向与安全 (刘培庆著)


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK