class-dump导出iOS系统私有库以及简单的私有API调用

前言

This is a command-line utility for examining the Objective-C runtim information stored in Mach-O files. It generates declarations for the classes, categories and protocols. This is the same information provided by using ‘otool -ov’, but presented as normal Objective-C declarations, so it is much more compact and readable.

可以将Mach-O文件中的Objective-C运行时的声明的信息导出,它利用Objective-C语言的runtime的特性,将存储在mach-O文件中的@interface和@protocol信息提取出来,并生成对应的.h文件。class-dump只能导出未经加密的App的头文件。classdump是对otool -ov 信息的翻译,以一种我们熟悉的易读的方式呈现。官网

otool 用法

Usage: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/otool [-arch arch_type] [-fahlLDtdorSTMRIHGvVcXmqQjCP] [-mcpu=arg] [--version] <object file> ...
	-f print the fat headers
	-a print the archive header
	-h print the mach header
	-l print the load commands
	-L print shared libraries used
	-D print shared library id name
	-t print the text section (disassemble with -v)
	-p <routine name>  start dissassemble from routine name
	-s <segname> <sectname> print contents of section
	-d print the data section
	-o print the Objective-C segment
	-r print the relocation entries
	-S print the table of contents of a library (obsolete)
	-T print the table of contents of a dynamic shared library (obsolete)
	-M print the module table of a dynamic shared library (obsolete)
	-R print the reference table of a dynamic shared library (obsolete)
	-I print the indirect symbol table
	-H print the two-level hints table (obsolete)
	-G print the data in code table
	-v print verbosely (symbolically) when possible
	-V print disassembled operands symbolically
	-c print argument strings of a core file
	-X print no leading addresses or headers
	-m don't use archive(member) syntax
	-B force Thumb disassembly (ARM objects only)
	-q use llvm's disassembler (the default)
	-Q use otool(1)'s disassembler
	-mcpu=arg use `arg' as the cpu for disassembly
	-j print opcode bytes
	-P print the info plist section as strings
	-C print linker optimization hints
	--version print the version of /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/otool
查看依赖库
otool -L 可执行文件

/System/Library/Frameworks/Foundation.framework/Foundation (compatibility version 300.0.0, current version 1575.17.0)
/usr/lib/libobjc.A.dylib (compatibility version 1.0.0, current version 228.0.0)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1252.250.1)
/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation (compatibility version 150.0.0, current version 1575.17.0)
/System/Library/Frameworks/UIKit.framework/UIKit (compatibility version 1.0.0, current version 61000.0.0)

是否加壳
otool -l xxx | grep -B 2 crypt

安装使用

在官网下载最新版本,将dmg文件中的class-dump复制到/usr/bin目录,并在终端执行如下执行进行授权:

sudo chmod 777 /usr/bin/class-dump

只要你搜索这个东西,相关的文章就简单介绍如何使用,一般会看到如下两个案例

第三方App dump测试(有道翻译为例)

class-dump -H /Applications/网易有道词典.app/Contents/MacOS/网易有道词典 -o /Users/mikejing191/Desktop/UIKIt 

在这里插入图片描述
这里我们可以一个个看头文件,由于之前也是写了一个私有API扫描思路的文章,虽然有问题,但是思路可以看看,而且内部封装了一个方法,Python正则提取.h文件中对应类下面所有的方法,组成一条条的数据,导入数据库方便后续做查询,这里可以简单看看代码

# 从头文件正则出来进行模型组装
def __get_apis_from_headers(sdk_version, all_headers):
    # 路径遍历(frameworkname, prefix, 具体路径)
    framework_apis = []
    for header in all_headers:
        # [{'class':'','methods':'','type':''},{},{}]
        apis = api_helpers.get_apis_from_header_file(header[2])

        for api in apis:
            class_name = api["class"] if api["class"] != "ctype" else header[1][0:-2]
            method_list = api["methods"]
            m_type = api["type"]
            for m in method_list:
                tmp_api = {}
                tmp_api['api_name'] = m
                tmp_api['class_name'] = class_name
                tmp_api['type'] = m_type
                tmp_api['header_file'] = header[1]
                tmp_api['source_sdk'] = sdk_version
                tmp_api['source_framework'] = header[0]
                framework_apis.append(tmp_api)

    return framework_apis

最终落库都是这样一条条的数据
在这里插入图片描述
具体代码在之前的博客,也有Github地址,传送门

dump系统framwork和Privateframework
这里的代码上面的传送门也有,咱们这看下有别人用Perl脚本怎么导出的,参数和路劲我已经改好了

#!/usr/bin/perl
#
# 24 November 2008
# Framework Dumping utility; requires class-dump
#

use strict;

use Cwd;
use File::Path;

my $HOME = (getpwuid($<))[7] || $ENV{'HOME'} 
  or die "Could not find your home directory!";

my $CLASS_DUMP = 'class-dump'; 

dump_frameworks('/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks',
                'Frameworks');

# Private Frameworks
dump_frameworks('/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/PrivateFrameworks',
                'PrivateFrameworks');

sub dump_frameworks
{
  my($dir, $subdir) = @_;

  opendir(my $dirh, $dir) or die "Could not opendir($dir) - $!";

  # Iterate through each framework found in the directory
  foreach my $file (grep { /\.framework$/ } readdir($dirh))
  {
    # Extract the framework name
    (my $fname = $file) =~ s/\.framework$//;
    print "Framework: $fname\n";

    my $headers_dir = "$HOME/Desktop/Headers/$subdir/$fname";

    # Create the folder to store the headers
    mkpath($headers_dir);

    # Perform the class-dump
    my $cwd = cwd();
    chdir($headers_dir) or die "Could not chdir($headers_dir) - $!";

    system($CLASS_DUMP, '-H', "$dir/$file");
   
   if($? == -1)
    {
      die "Could not execute $CLASS_DUMP - $!\n";
    }
    chdir($cwd) or die "Could not chdir($cwd) - $!";
  }
}

我这里改了两个路径,会在Desktop下出现两个文件夹,分别是Frameworks和PrivateFrameworks,可以看下执行的过程

Framework: ARKit
Framework: MetalPerformanceShaders
Framework: ExternalAccessory
Framework: MediaPlayer
Framework: IdentityLookupUI
Framework: MediaToolbox
Framework: System
2019-10-31 18:32:21.112 class-dump[74690:1349012] Warning: This file does not contain any Objective-C runtime information.
Framework: UIKit
2019-10-31 18:32:21.131 class-dump[74692:1349017] Warning: This file does not contain any Objective-C runtime information.

这里会出现Warning,而且对应的头文件无法被导出。因此这个就和网上最全的库是一样的RuntimeAPIs

查了资料得出结论:采用swift编写或是关键部分采用C语言编写的模块是无法dump出头文件的

所以,我们要做完整的私有API扫描,就会有缺陷,有些库是无法导出API的。但是按官方所说,你已经用这个可以把大部分私有库导出,那么就能看到AppKit中那些隐藏的私有方法,了解一些潜在的API。

It’s a great tool for the curious. You can look at the design of closed source applications, frameworks, and bundles. Watch the interfaces evolve between releases. Experiment with private frameworks, or see what private goodies are hiding in the AppKit. Learn about the plugin API lurking in Mail.app.

私有API调用

纯纯的私有API(存在于PrivateFramework)

SELECT *FROM private_framework_dump_apis WHERE source_framework = 'FTServices.framework' and class_name = 'FTDeviceSupport'

在这里插入图片描述
可以看到那么多API,我们随便抓一点试试FTDeviceSupport获取设备信息

// 私有库需要load
    NSBundle *b = [NSBundle bundleWithPath:@"/System/Library/PrivateFrameworks/FTServices.framework"];
    BOOL success = [b load];
    
    NSLog(@"%@",@(success));
    Class FTDeviceSupport = NSClassFromString(@"FTDeviceSupport");
    id si = [FTDeviceSupport valueForKey:@"sharedInstance"];
    
    NSLog(@"deviceColor-- %@", [si valueForKey:@"deviceColor"]);
    NSLog(@"deviceName-- %@", [si valueForKey:@"deviceName"]);
    NSLog(@"telephoneNumber-- %@", [si valueForKey:@"telephoneNumber"]);
    NSLog(@"deviceInformationString-- %@", [si valueForKey:@"deviceInformationString"]);
    NSLog(@"productOSVersion-- %@", [si valueForKey:@"productOSVersion"]);
    NSLog(@"supportsApplePay-- %@", [si valueForKey:@"supportsApplePay"]);
    NSLog(@"slowCPUDevice-- %@", [si valueForKey:@"slowCPUDevice"]);
    NSLog(@"supportsAppleIDIdentification-- %@", [si valueForKey:@"supportsAppleIDIdentification"]);
    NSLog(@"supportsSMSIdentification-- %@", [si valueForKey:@"supportsSMSIdentification"]);

这里私有API指的是完全存在于PrivateFramework的API,这种私有API的调用就需要先加载私有API所在的库到内存中。

再比如
MobileContainerManager.framework中的一个类MCMAppContainer来做介绍,利用该API可以根据包名来判断某APP是否存在,不过无法确定应用的状态为安装中或已安装,调用方法如下:

NSBundle *container = [NSBundle bundleWithPath:@"/System/Library/PrivateFrameworks/MobileContainerManager.framework"];
if ([container load]) {
    Class appContainer = NSClassFromString(@"MCMAppContainer");
    id test = [appContainer performSelector:@selector(containerWithIdentifier:error:) withObject:@"com.tencent.xin" withObject:nil];
    if (test) {
        NSLog(@"存在该应用");
    }
}


#import <dlfcn.h>

void *lib = dlopen("/System/Library/PrivateFrameworks/MobileContainerManager.framework", RTLD_LAZY);
if (lib) {
    Class appContainer = NSClassFromString(@"MCMAppContainer");
    id test = [appContainer performSelector:@selector(containerWithIdentifier:error:) withObject:@"com.tencent.xin" withObject:nil];
    if (test) {
       NSLog(@"存在该应用");
    }
    dlclose(lib);
}

以上是两种Privateframework的加载到内存的方式,但是iOS 12的私有中导出来已经移除了这个API,就剩了一个

#import <MobileContainerManager/MCMContainer.h>

@interface MCMAppContainer : MCMContainer
{
}

+ (long long)typeContainerClass;

@end

果然私有API是不稳定的,因此Apple不建议我们使用,因为随时会被删掉

公有framework的未公开API,也叫私有API

再来一个获取手机应用列表(LSApplicationWorkspace类)
这里我们可以在公有的framework中dump出这些未被暴露使用API,因此也成为私有API

// 公有库里面不需要load
Class LSApp = NSClassFromString(@"LSApplicationWorkspace");
    
id kk = [LSApp valueForKey:@"defaultWorkspace"];
    
NSLog(@"%@",[kk valueForKey:@"allApplications"]);
NSLog(@"%ld",[[kk valueForKey:@"allApplications"] count]);

······
file:///Users/mikejing191/Library/Developer/CoreSimulator/Devices/80CC2BD5-05CB-4821-A435-270A04E3884C/data/Containers/Bundle/Application/6F4ED91A-3758-4811-B0B9-3D0E8E077783/SDKSample.app <com.tencent.wc.xin.SDKSample <installed >:0>",
    "<LSApplicationProxy: 0x7ffe60c10490> org.cocoapods.demo.JZBPay-Example file:///Users/mikejing191/Library/Developer/CoreSimulator/Devices/80CC2BD5-05CB-4821-A435-270A04E3884C/data/Containers/Bundle/Application/12122C25-FDA4-4276-8C3B-FBC35139603B/JZBPay_Example.app <org.cocoapods.demo.JZBPay-Example <installed >:0>",
    "<LSApplicationProxy: 0x7ffe60c10c90> com.whty.wxcitizencard file:///Users/mikejing191/Library/Developer/CoreSimulator/Devices/80CC2BD5-05CB-4821-A435-270A04E3884C/data/Containers/Bundle/Application/26497C5C-C7F9-4039-BE4B-31D7881BFCCB/SMT.app <com.whty.wxcitizencard <installed >:0>",
    "<LSApplicationProxy: 0x7ffe60c11020> org.cocoapods.demo.SM2-Example file:///Users/mikejing191/Library/Developer/CoreSimulator/Devices/80CC2BD5-05CB-4821-A435-270A04E3884C/data/Containers/Bundle/Application/131A8DD6-50E3-4355-ADC6-31E2B4700EA9/SM2_Example.app <org.cocoapods.demo.SM2-Example <installed >:0>",
······

可以看到我们获取到的数组中存放的实际上是LSApplicationProxy类型的对象,该对象有一个名为 applicationIdentifier 的属性,调用属性就能拿到所有已安装列表的BundleID。
可以看到,未公开 API 的调用实际上只需要将类名、方法名等从字符串进行转化,随后利用 performSelector 方法进行调用即可,相当简单。而且不需要把私有API存在库主动加载金内存。

总结

虽然这玩意和网上一个现成的库一样,能通过class-dump导出,但是有部分是库内部应该是swift或者c实现的,无法被dump出来,这就影像到我们把全部api搜集到数据库的操作,可惜,但是就单单针对iOS的Privateframework私有API导出,可以通过这个工具导出.h文件看下系统隐藏了那些API,以及我们可以自己试着调用私有API。针对公有framework库导出的未被暴露的API,调用不需要把库加载到内存,如果直接调用私有Framework的API就需要先把库加载到内存。

参考文章
class-dump逆向
逆向砸壳
网上有人导出的头文件也是残缺的
私有API使用
私有API扫描
使用API文章
原理和使用
Perl
otool和class-dump

©️2020 CSDN 皮肤主题: 技术黑板 设计师:CSDN官方博客 返回首页