iOS逆向学习之七(命令行工具开发)

1,083 阅读5分钟

命令行工具开发

在逆向过程中,我们使用了很多快捷工具,例如***Clutch***、class-dump等等,这些工具都是在终端执行。而这些工具的本质其实就是Mach-O类型的可执行文件,如果想要开发属于我们自己的命令行工具,应该怎么做呢?

创建命令行项目

在iOS开发中。我们编译一个项目,在生成的.app包中就能找到项目的可执行文件,通过这种方式生成的可执行文件可以直接运行在iPhone上。所以,如果我们要自己开发命令行工具,并且能在iPhone上使用,最简单的方式就是使用xcode创建iOS项目。

  • 创建iOS项目,删除项目中多余的文件,仅保留***info.plist***和***main.m***文件,并且去掉***main.m***文件中的***UIApplication***相关方法

  • 编译项目,在***Products/.app***中可以找到生成的可执行文件
  • 将可执行文件通过iFunBox拷贝到iPhone的/usr/bin目录下。
  • 连接iPhone,在命令行输入工具名,就可使用刚刚创建的命令行工具

获取可执行文件的架构

给定一个iPhone上的可执行文件路径,利用我们自己开发的命令行工具,读取可执行文件,并且判断出可执行文件包含的架构类型,并打印。

  • 在完成需求之前,我们首先要了解Mach-O文件的内部构造,使用MachOView工具,查看Mach-O文件的内部构造

可以发现,Mach-O文件的头部Header里,第一行就表示当前Mach-O文件是胖二进制文件,也叫做通用二进制文件。而第二行则表示当前Mach-O文件包含哪几种架构。所以我们想要判断当前的Mach-O文件是何种类型,只需要读取Mach-O文件的前四个字节的数据进行判断即可。

  • 根据什么条件来判断Mach-O文件属于哪种架构类型?通过查看xnu源码,在EXTERNAL_HEADERS/mach-o/fat.h和EXTERNAL_HEADERS/mach-o/loader.h文件中可以发现对应几种常用架构类型的定义

上图中FAT_MAGIC和FAT_CIGAM代表通用二进制文件类型,通过以下结构体可以看出FAT_MAGIC和FAT_CIGAM是uint32_t类型

上图是loader.h中定义的文件类型,其中MH_MAGIC和MH_CIGAM代表非64位的架构类型,也就是armv7或者armv7s类型的架构,MH_MAGIC_64和MH_CIGAM_64代表64位类型的架构,也就是arm64架构

通过查看源码可以发现一个问题,上图类型定义都有两种,而且对应的值完全相反。其实这就是大小端模式:
大端模式:数据的高字节保存到内存的低地址中,数据的低字节保存到内存的高地址中。
小端模式:数据的高字节保存到内存的高地址中,数据的低字节保存到内存的低地址中。
这就导致在读取数据的时候可以从高地址向低地址读取,也可以从低地址向高地址读取,所以就存在上图两种定义

  • 拿到了具体架构的定义,我们就可以拿到我们所读取到的Mach-O文件的前四个字节,与以上定义做对比,就可以判断当前Mach-O文件的具体架构模式

  • 首先创建命令行项目,去除多余文件之后,在***main.m***中添加如下代码

#import <UIKit/UIKit.h>
#import <mach-o/loader.h>
#import <mach-o/fat.h>

int main(int argc, char * argv[]) {
    @autoreleasepool {
        NSString *path = @"/var/mobile/Containers/Bundle/Application/048B71C8-42E4-4EE0-8E50-EF262251DE17/WeChat.app/WeChat";
        NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingAtPath:path];
        NSUInteger len = sizeof(uint32_t);
        //读取uint32_t长度的字节数
        NSData *data = [fileHandle readDataOfLength:len];
        //将data数据存放到magicNumber中
        uint32_t magicNumber;
        [data getBytes:&magicNumber length:len];

        if (magicNumber == FAT_MAGIC || magicNumber == FAT_CIGAM) {
            printf("Universal Binary\n");
        }else if (magicNumber == MH_MAGIC_64 || magicNumber == MH_CIGAM_64){
            printf("64bit Binary\n");
        }else if (magicNumber == MH_MAGIC || magicNumber == MH_CIGAM){
            printf("32bit Binary\n");
        }else{
            printf("读取失败\n");
        }
        
        printf("magicNumber = 0x%x\n", magicNumber);
        
        [fileHandle closeFile];
        return 0;
    }
}
  • 在Products/.app中找到可执行文件,复制到iPhone的/usr/bin/目录下,然后连接iPhone,进入终端,执行TestCommand(这里是你的可执行文件的名称),这时候会发现无执行权限==permission denied==,解决方式就是为我们的可执行文件增加可执行权限
chmod +x /usr/bin/TestCommand

增加完可执行权限之后,再次执行此命令,会在终端打印出以下信息

508SC:~ root# TestCommand
64bit Binary
magicNumber = 0xfeedfacf
508SC:~ root#

可以看出微信App的可执行文件是arm64架构的

为命令行工具增加权限

之前写的命令行工具可以获取到指定了路径的可执行文件的架构类型,但是这种方式不稳定,在大多数情况下会获取失败。原因是我们没有为命令行工具增加足够的权限。那么如何为可执行文件增加权限呢?

签名工具ldid的使用

之前在安装Theos的时候我们安装了ldid工具,可以直接在命令行输入==ldid==就可以查看==ldid==的具体用法

  • ldid -e MobileSafari表示导出可执行文件的权限信息到指定的文件中
ldid -e TestCommand > TestCommand.entitlements

在iOS中,权限信息一般存放到entitlements文件中

  • 在==TestCommand.entitlements==文件中增加相应的权限
get-task-allow :YES

如果想要更多权限,可以找一个具有很多权限的可执行文件,导出它的权限信息,然后将所有权限设置到我们自己的==TestCommand.entitlements==文件中

  • 使用添加好权限的==TestCommand.entitlements==文件重新对可执行文件进行签名,如下
ldid -STestCommand.entitlements TestCommand

-S 命令后要紧跟权限文件,不能有空格

  • 将签名好的权限文件复制到iPhone的/usr/bin/目录中,然后就能在终端使用==TestCommand==指令了