IOS安全 APP 越狱检测技术

admin 2024年1月21日16:58:18评论14 views字数 9909阅读33分1秒阅读模式

8点击蓝字

IOS安全 APP 越狱检测技术

关注我们

声明

本文作者:Jammny

本文字数:8000字

阅读时长:约10分钟

附件/链接:点击查看原文下载

本文属于【狼组安全社区】原创奖励计划,未经许可禁止转载

由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,狼组安全团队以及文章作者不为此承担任何责任。

狼组安全团队有对此文章的修改和解释权。如欲转载或传播此文章,必须保证此文章的完整性,包括版权声明等全部内容。未经狼组安全团队允许,不得任意修改或者增减此文章内容,不得以任何方式将其用于商业目的。

IOS安全 APP 越狱检测技术

以防护的角度去思考如何检测越狱环境。

IOS安全 APP 越狱检测技术
image.png

检测越狱后的敏感文件路径

当越狱后会系统产生一些新的文件和应用,比如说ssh工具、Cydia应用等。下面是常用的检测路径:

"/Applications/Cydia.app",
"/usr/sbin/sshd",
"/bin/bash",
"/etc/apt",
"/Library/MobileSubstrate",
"/User/Applications/"

OC语言中,常常用NSFileManager的fileExistsAtPath方法,来检测文件/文件夹路径是否存在:

- (BOOL)fileExistsAtPath:(NSString *)path;

该方法的参数如下:

  • path:指定的路径。

该方法的返回值如下:

  • 存在:返回 YES。
  • 不存在:返回 NO。

fileExistsAtPath方法使用例子:

// 判断文件是否存在 
BOOL isFileExists = [[NSFileManager defaultManager]fileExistsAtPath:@"/Users/bard/Desktop/file.txt"]; 
//判断目录是否存在 
BOOL isDirectoryExists = [[NSFileManager defaultManager] fileExistsAtPath:@"/Users/bard/Desktop"];

也可以使用C语言函数stat来实现,函数原型:

int stat(const char *pathname, struct stat *buf);

该函数参数如下:

  • pathname:指向文件或目录的路径。
  • buf:指向用于存储状态信息的结构。

返回值如下:

  • 成功:返回 0。
  • 失败:返回 -1,并设置 errno 变量。

stat函数使用例子:

static char *JailbrokenPathArr[] = {"/Applications/Cydia.app","/usr/sbin/sshd","/bin/bash","/etc/apt","/Library/MobileSubstrate","/User/Applications/"};

+ (BOOL)isJailbroken{
    if(TARGET_IPHONE_SIMULATOR)return NO;
    for (int i = 0;i < sizeof(JailbrokenPathArr) / sizeof(char *);i++) {
        if([[NSFileManager defaultManager] fileExistsAtPath:[NSString stringWithUTF8String:JailbrokenPathArr[i]]]){
            return TRUE;
        }
    }
    return FALSE;
}

当然实现检测某个文件/路径是否存在方法还有其他的,比如说C语言的lstat函数、fopen函数等等。

检测非法动态链接库

_dyld_get_image_name 函数是 macOS 和 iOS 中 dyld 系统库中的函数,用于获取指定的动态链接库的名称。

const char *_dyld_get_image_name(const struct mach_header *header);

参数说明:

  • header:指向 Mach-O 文件头的指针。表示动态链接库的索引,动态链接库的索引从 0 开始,表示第一个动态链接库。

返回值:

  • 函数的返回值是动态链接库的名称。如果动态链接库不存在,则返回 NULL。

可以先通过 _dyld_image_count() 函数计算出加载的动态链接库的数量,然后遍历一下检测是否存在敏感的链接库。检测实现:

+ (Boolean)isDylib{
    NSArray* illegalDylibs = @[
        @"/Library/MobileSubstrate/MobileSubstrate.dylib",
    ];
    for(int i=0;i<_dyld_image_count();i++){
        const char* name = _dyld_get_image_name(i);
        NSString* dylib_path = [NSString stringWithFormat:@"%s", name];
        NSLog(@"(%d) 已加载dylib>> %s", i, name);
        for(NSString* illegalDylib in illegalDylibs){
            if ([dylib_path isEqual:illegalDylib]){
                return TRUE;
            }
        }
    }
    
    // 获取系统函数stat的地址 
    int (*func_stat)(const char *, struct stat *) = stat; 
    // 使用dladdr获取函数地址和所在的动态库信息 
    Dl_info dylib_info; int ret = dladdr(func_stat, &dylib_info); 
    // 如果函数地址出自非系统动态库,则说明设备可能已越狱 
    if (ret && strcmp(dylib_info.dli_fname, "/usr/lib/system/libsystem_kernel.dylib") != 0) { 
     printf("设备已越狱n"); 
        return TRUE;
    } else {
     printf("设备未越狱n");
    }

    return FALSE
}

检测越狱应用

越狱后会安装对应的应用商店,用于下载越狱的插件或应用。比如我们从cydia中安装了Filza应用,可以通过 cydia://package/com.tigisoftware.Filza 链接打开它。在oc可以使用UIApplication的canOpenURL方法检测是否能打开:

- (BOOL)canOpenURL:(NSURL *)url;

参数说明:

  • url:要检查的 URL 对象。

返回值:

  • YES:可以打开 URL。
  • NO:不能打开 URL

检测方法例子:

+ (Boolean)isOpenJailApp{
    NSArray* appNames = @[
        @"re.frida.server",
        @"com.tigisoftware.Filza",
        @"com.saurik.Cydia",
    ];
    for(NSString* appName in appNames){
        NSString* appStr = [NSString stringWithFormat: @"cydia://package/%@", appName];
        NSLog(@"app>> %@", appStr);
        if ([[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:appStr]]){
            return TRUE;
        };
    };
    return FALSE;
}

检测系统文件路径的权限

iOS设备越狱后,系统文件的权限将发生改变,以下是一些具体的例子:

  • 在越狱之前,用户无法访问 /private 路径,private 路径是系统保留的路径,只有系统组件才能访问。
  • 在越狱之前,用户无法访问 /var/root 路径,但越狱后可以通过ssh或其他工具来访问该路径。
  • 在越狱之前,用户无法修改 /System/Library 路径下的文件,但越狱后可以通过越狱工具来修改这些文件。
  • 在越狱之前,用户无法访问 /var/mobile 路径下的敏感数据,但越狱后可以通过越狱工具来访问这些数据。

在 Objective-C 中,可以使用 writeToFile: 方法将数据写入文件。该方法的函数原型如下:

- (BOOL)writeToFile:(NSString *)path atomically:(BOOL)atomically encoding:(NSStringEncoding)encoding error:(NSError **)error;

参数说明:

  • path:指定文件的路径。
  • atomically:是否原子写入。
  • encoding:指定编码格式。
  • error:用于接收错误信息的对象。

返回值:

  • 成功返回 YES,失败返回 NO。

以下是一个使用 writeToFile: 方法写入文件的示例:

+ (Boolean)isWritePrivatePath{
    // 检测能否写入私有路径
    Boolean result = FALSE;
    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSString *path = @"/private/test.txt";
    @try {
        NSError* error;
        NSString *txt = @"jailbreak";
        if ([txt writeToFile:path atomically:YES encoding:NSStringEncodingConversionAllowLossy error:&error]) {
            NSLog(@"沙盒已被破坏");
            result = TRUE;
            [fileManager removeItemAtPath:path error:nil];
        }
        if(error==nil)
        {
            result = TRUE;
        }else{
            NSLog(@"沙盒未被破坏");
            NSLog(@"文件写入失败: %@", path);
        }
    }
    @catch (NSException *exception) {
    }
    return result;
}

在示例中,我们创建了一个字符串 "Hello, world!",然后将其写入沙盒目录下的 "test.txt" 文件中。如果指定 atomically 为 YES,则会使用原子写入方式,即在写入文件时会先创建一个临时文件,然后将数据写入临时文件,最后将临时文件重命名为目标文件。原子写入方式可以确保数据的完整性,但会降低写入速度。如果指定 encoding 为非空字符串,则会使用指定的编码格式来写入文件。默认情况下,使用 UTF-8 编码。 如果写入文件失败,则会通过 error 对象返回错误信息。

检测文件路径软链接

对一些重要的文件路径,越狱后会生成对应的一些软链接。可以使用C语言的lstat函数获取文件属性,检测是否存在软链接。函数原型:

int lstat(const char *path, struct stat *buf);

参数说明:

  • path:指定文件的路径。
  • buf:用于存储文件属性信息的结构体指针。

lstat函数的返回值:

  • 成功返回0,失败返回-1。
  • lstat函数与stat函数类似,但lstat函数在获取符号链接文件的属性信息时,会返回符号链接本身的属性信息,而不是符号链接指向的文件的属性信息。以下是一个使用lstat函数获取文件属性信息的示例:
+ (Boolean)isLstatAtLnk{
    Boolean result = FALSE;
    NSArray* jbPaths = @[
        @"/mnt",
        @"/tmp",
        @"/User",
        @"/etc",
        @"/var",
        @"/var/stash/Library/Ringtones",
        @"/var/stash/Library/Wallpaper",
        @"/var/stash/usr/include",
        @"/var/stash/usr/libexec",
        @"/var/stash/usr/share",
        @"/var/stash/usr/arm-apple-darwin9",
    ];
    struct stat stat;
    for(NSString* jbPath in jbPaths){
        char jbPathChar[jbPath.length];
        memcpy(jbPathChar, [jbPath cStringUsingEncoding:NSUTF8StringEncoding], jbPath.length);
        NSLog(@"stat_info.st_mode: %hu, S_IFLNK: %d, %d", stat.st_mode, S_IFLNK, stat.st_mode & S_IFLNK);
        if (lstat(jbPathChar, &stat)){
            // 打印错误信息
            // perror("lstat");
        }
        //判断文件类型是否为软链接
        if(stat.st_mode & S_IFLNK){
            result = TRUE;
            NSLog(@"软链接的路径:%@", jbPath);
        } else {
            NSLog(@"路径: %s", jbPathChar);
        }
    return result;
}

检测越狱后的系统调用

有些系统函数或API在未越狱状态下,是不能使用的。比如说,未越狱的设备是不能用fork函数创建子进程的,那么我们就可以用它来进行检测,其他类似的函数:posix_spawn,kill,popen等等。fork函数原型:

pid_t fork(void);

返回两个值:

  • 如果成功,在父进程中返回子进程的 PID,在子进程中返回 0。
  • 如果失败,返回 -1。

以下是一个使用 fork() 系统调用创建子进程的示例:

+ (Boolean)isForkSub{
    pid_t pid;  // 创建一个变量来保存子进程的 PID
    pid = fork();   // 创建子进程
    Boolean result = FALSE;
    if (pid == -1) {
        NSLog(@"进程创建失败!");
    } else if (pid == 0) {
      NSLog(@"我是子进程");
      result = True;
    } else {
      NSLog(@"我是父进程,子进程的 PID 为 %d", pid);
    }
    return result;
}

检测越狱后的环境变量

在 iOS 中,动态链接器 (dyld) 负责加载应用程序所需的动态库。环境变量 DYLD_INSERT_LIBRARIES 用于指定 dyld 在加载应用程序时要加载的动态库。越狱工具可以通过设置环境变量 DYLD_INSERT_LIBRARIES 来注入自己的动态库到应用程序中。这些动态库可以用于修改应用程序的行为,例如绕过沙盒限制、访问系统文件等。因此,如果应用程序的环境变量 DYLD_INSERT_LIBRARIES 不为空,则可以认为该应用程序已被越狱。可以使用 getenv() 函数用于获取环境变量的值。getenv() 函数的函数原型如下:

char *getenv(const char *name);

参数说明:

  • name:环境变量的名称。

返回值:

  • 如果成功,返回指向环境变量值的指针。
  • 如果失败,返回 NULL。

检测代码:

+ (Boolean)isCheekEnv{
    Boolean result = FALSE;
    NSLog(@"环境变量>> %s", getenv("DYLD_INSERT_LIBRARIES"));
    if (NULL != getenv("DYLD_INSERT_LIBRARIES")){
        result = TRUE;
    }
    return result;
}

检测进程是否被调试

sysctl()函数可以获取当前进程的相关信息,从而确实是否在进行pTraced调试。sysctl 函数原型:

int sysctl(int *name, size_t namelen, void *oldp, size_t *oldlenp, void *newp, size_t newlen);

参数:

  • name: 指向一个数组,用于指定要获取或设置的系统配置信息。
  • namelen: 指定name数组的长度。
  • oldp: 指向一个缓冲区,用于存储旧的系统配置信息。
  • oldlenp: 指向一个变量,用于存储oldp缓冲区的大小。
  • newp: 指向一个缓冲区,用于存储新的系统配置信息。
  • newlen: 指定newp缓冲区的大小。

返回值:

  • 0: 成功。
  • -1: 失败。

sysctl()函数用于获取或设置系统的配置信息。name数组中的每个元素都是一个MIB名。MIB名是一个由两部分组成的字符串,第一部分表示MIB族,第二部分表示MIB项。如果oldpnewp均为NULL,则sysctl()函数将获取指定的系统配置信息。如果oldp不为NULL,则sysctl()函数将获取指定的系统配置信息,并将旧的配置信息存储到oldp缓冲区中。如果newp不为NULL,则sysctl()函数将设置指定的系统配置信息,并将新的配置信息存储到newp缓冲区中。检测代码:

+ (Boolean)isDebugged{
    int junk;
    int mib[4];
    struct kinfo_proc info;
    size_t size;
    info.kp_proc.p_flag = 0;
    mib[0] = CTL_KERN;
    mib[1] = KERN_PROC;
    mib[2] = KERN_PROC_PID;
    mib[3] = getpid();
    size = sizeof(info);
    junk = sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL0);
    assert(junk == 0);
    NSLog(@"flag >> %d", info.kp_proc.p_flag);
    NSLog(@"P_TRACED >> %d", P_TRACED);
    return ( (info.kp_proc.p_flag & P_TRACED) != 0 );
}

检测越狱的异常类

使用越狱插件的话会在进程中加载对应的类,可以通过检测是否存在这些类,从而进行检测。可以使用 NSClassFromString() 方法尝试获取越狱类的类对象。如果类对象存在,则说明设备越狱。方法的原型如下:

Class NSClassFromString(NSString *className);
  • 参数:
    • className:类的名称,为字符串类型。
  • 返回值:
    • 成功返回类对象,失败返回 NULL

检测代码:

// HBPreferences 是 Cydia 的首选项类
NSArray *checksClass = [[NSArray alloc] initWithObjects:@"HBPreferences",nil];
for(NSString *className in checksClass)
{
  if (NSClassFromString(className) != NULL) {
 return YES;
  }
}

fishhook注入防护

fishhook是通过交换函数地址来实现hook,假设它hook了stat函数,那么stat的来源将指向攻击者注入的动态库中。因此可以在注入检测的时候加个判断,如果检测使用的函数来自非系统库,说明正在被注入。这里使用dladdr函数获取动态库的信息,检测例子:

+ (Boolean) isStatPath{
    // 敏感路径
    char *JailbrokenPathArr[] = {"/Applications/Cydia.app","/usr/sbin/sshd","/bin/bash","/etc/apt","/Library/MobileSubstrate","/User/Applications/"};

    // stat 判断文件路径是否存在
    for (int i = 0;i < sizeof(JailbrokenPathArr) / sizeof(char *);i++) {
        struct stat stat_info;
        if (0 == stat(JailbrokenPathArr[i], &stat_info)) {
            return TRUE;
        }
    }

    // 检测stat是否来自系统库
    int ret;
    Dl_info dylib_info;
    int (*func_stat)(const char *, struct stat *) = stat;
    if ((ret = dladdr(func_stat, &dylib_info))) {
        NSString *fName = [NSString stringWithUTF8String:dylib_info.dli_fname];
        if(![fName isEqualToString:@"/usr/lib/system/libsystem_kernel.dylib"]){
            NSLog(@"检测到替换了stat库,fname--%@", fName);
            return TRUE;
        }
    }

    return FALSE;
}

参考链接

iOS的越狱检测和反越狱检测剖析 - 简书 (jianshu.com)

iOS越狱检测app及frida过检测 - 『移动安全区』 - 吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

系列文章

IOS安全 测试环境搭建

IOS安全 webview hook 全局调试

IOS安全 SSL Pinning 单向证书检验

IOS安全 APP TLS 数据强制抓包与解包

作者

IOS安全 APP 越狱检测技术

Jammny

假如你坚持一个月做同一件事情

那么它很快会成为你的习惯,学习亦是如此。

原文始发于微信公众号(WgpSec狼组安全团队):IOS安全 APP 越狱检测技术

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年1月21日16:58:18
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   IOS安全 APP 越狱检测技术https://cn-sec.com/archives/2414445.html

发表评论

匿名网友 填写信息