黑苹果macOS Security.framework代码签名深度验证完全指南:从SecCode到代码注入检测的完整安全体系

发布时间:2026年06月11日 | 分类:黑苹果 | 关键词:代码签名, SecCode, 安全验证

前言:代码签名的信任基石

macOS的安全性建立在多层防护体系之上,其中代码签名是最基础也最重要的一环。从Gatekeeper的启动检查,到XProtect的恶意软件扫描,再到AMFI(Apple Mobile File Integrity)的运行时验证,代码签名贯穿了macOS应用的整个生命周期。

对于黑苹果用户和开发者来说,深入理解代码签名机制尤为重要。一方面,黑苹果环境下需要安装各种补丁和驱动程序,理解签名有助于排查安全相关的兼容性问题;另一方面,开发者可以利用代码签名验证API构建更安全的应用程序。

本文聚焦于Security.framework中的代码签名验证API(SecCode、SecRequirement、SecStaticCode),系统讲解如何对应用程序进行静态和动态签名验证,并实战演示代码注入检测的实现。

macOS代码签名体系架构

签名层次结构

macOS的代码签名是一个多层嵌套的结构。当Gatekeeper验证一个应用程序时,它实际上在验证一个签名链:

  1. 应用程序签名:开发者证书对App Bundle的签名
  2. 资源签名:Bundle内每个可执行文件、框架、库的独立签名
  3. 公证票据:Apple Notary Service签发的公证票据(从macOS Catalina开始)
  4. 系统完整性:SIP(System Integrity Protection)对系统文件的保护

关键守护进程

守护进程职责配置文件
amfidApple Mobile File Integrity,运行时签名验证/System/Library/LaunchDaemons/com.apple.amfid.plist
syspolicydGatekeeper策略评估和执行/System/Library/LaunchDaemons/com.apple.syspolicyd.plist
trustd证书信任评估/usr/libexec/trustd
taskgated任务访问控制,ptrace和task_for_pid权限/System/Library/LaunchDaemons/com.apple.taskgated.plist

SecStaticCode:静态签名验证

基础验证流程

SecStaticCode用于对磁盘上的代码进行签名验证,不涉及进程运行时状态:

#include <Security/Security.h>
#include <Security/SecCode.h>
#include <Security/SecStaticCode.h>

// 1. 创建静态代码对象
SecStaticCodeRef staticCode = NULL;
OSStatus status = SecStaticCodeCreateWithPath(
    (__bridge CFURLRef)[NSURL fileURLWithPath:@"/Applications/Safari.app"],
    kSecCSDefaultFlags,
    &staticCode
);

if (status != errSecSuccess) {
    NSLog(@"无法创建静态代码对象: %d", status);
    return;
}

// 2. 检查签名是否有效
status = SecStaticCodeCheckValidity(
    staticCode,
    kSecCSDefaultFlags,
    NULL  // 不指定额外的验证条件
);

switch (status) {
    case errSecSuccess:
        NSLog(@"签名验证通过 ✓");
        break;
    case errSecCSUnsigned:
        NSLog(@"代码未签名 ✗");
        break;
    case errSecCSSignatureFailed:
        NSLog(@"签名验证失败 ✗");
        break;
    default:
        NSLog(@"验证错误: %d", status);
}

CFRelease(staticCode);

获取签名详细信息

// 获取签名证书信息
CFDictionaryRef signingInfo = NULL;
status = SecCodeCopySigningInformation(
    (SecCodeRef)staticCode,  // 可以传递SecStaticCodeRef(桥接)
    kSecCSDefaultFlags,
    &signingInfo
);

if (status == errSecSuccess) {
    NSDictionary *info = (__bridge NSDictionary *)signingInfo;
    
    // 签名证书链
    NSArray *certChain = info[(__bridge id)kSecCodeInfoCertificates];
    for (id cert in certChain) {
        SecCertificateRef certRef = (__bridge SecCertificateRef)cert;
        CFStringRef commonName = NULL;
        SecCertificateCopyCommonName(certRef, &commonName);
        NSLog(@"证书: %@", commonName);
        CFRelease(commonName);
    }
    
    // 代码标识符
    NSString *identifier = info[(__bridge id)kSecCodeInfoIdentifier];
    NSLog(@"Bundle ID: %@", identifier);
    
    // 签名团队标识
    NSString *teamID = info[(__bridge id)kSecCodeInfoTeamIdentifier];
    NSLog(@"Team ID: %@", teamID);
    
    // 签名标志
    NSNumber *flags = info[(__bridge id)kSecCodeInfoFlags];
    NSLog(@"签名标志: 0x%x", flags.unsignedIntValue);
    
    CFRelease(signingInfo);
}

使用SecRequirement进行精准验证

SecRequirement允许你定义精确的验证条件,这是Apple公证服务内部使用的机制:

// 创建验证需求:要求团队标识为 "ABCDE12345" 的签名
NSString *reqStr = @"anchor apple generic and "
    "certificate leaf[subject.OU] = "ABCDE12345"";
    
SecRequirementRef requirement = NULL;
status = SecRequirementCreateWithString(
    (__bridge CFStringRef)reqStr,
    kSecCSDefaultFlags,
    &requirement
);

if (status == errSecSuccess) {
    // 使用需求进行验证
    status = SecStaticCodeCheckValidity(
        (SecStaticCodeRef)staticCode,
        kSecCSDefaultFlags,
        requirement
    );
    
    if (status == errSecSuccess) {
        NSLog(@"满足指定需求 ✓");
    }
    
    CFRelease(requirement);
}

常用的SecRequirement语法:

  • anchor apple generic — 要求Apple签名的代码(适用于系统应用)
  • anchor trusted — 要求信任链中的有效签名
  • certificate leaf[subject.OU] = "TEAMID" — 验证团队标识
  • identifier "com.example.app" — 验证Bundle ID
  • info[CFBundleShortVersionString] >= "2.0" — 验证版本号

SecCode:运行时动态验证

获取运行进程的代码对象

SecCode提供了对运行中进程的签名验证能力,这是代码注入检测的基础:

// 通过PID获取运行中进程的SecCode
pid_t targetPID = 12345;  // 目标进程PID
SecCodeRef runningCode = NULL;
NSDictionary *attributes = @{
    (__bridge id)kSecGuestAttributePid: @(targetPID)
};

status = SecCodeCopyGuestWithAttributes(
    NULL,  // host,NULL表示自身进程
    (__bridge CFDictionaryRef)attributes,
    kSecCSDefaultFlags,
    &runningCode
);

if (status == errSecSuccess) {
    // 验证运行时签名
    status = SecCodeCheckValidity(
        runningCode,
        kSecCSDefaultFlags,
        NULL
    );
    
    NSLog(@"运行时签名状态: %@", 
        status == errSecSuccess ? @"有效 ✓" : @"无效 ✗");
    
    CFRelease(runningCode);
}

检测代码注入

代码注入是macOS恶意软件的常见技术。利用SecCode的运行时验证能力,可以检测进程是否被注入了未签名的动态库:

- (BOOL)hasUnsignedCodeInjection:(pid_t)pid {
    SecCodeRef guestCode = NULL;
    NSDictionary *attrs = @{
        (__bridge id)kSecGuestAttributePid: @(pid)
    };
    
    OSStatus status = SecCodeCopyGuestWithAttributes(
        NULL,
        (__bridge CFDictionaryRef)attrs,
        kSecCSDefaultFlags,
        &guestCode
    );
    
    if (status != errSecSuccess) return YES; // 可疑
    
    // 验证完整性
    CFDictionaryRef signingInfo = NULL;
    SecCodeCopySigningInformation(
        guestCode,
        kSecCSDynamicInformation,  // 关键:获取动态信息
        &signingInfo
    );
    
    if (signingInfo) {
        NSDictionary *info = (__bridge NSDictionary *)signingInfo;
        
        // 检查是否有未签名的dylib被加载
        NSNumber *internalReq = info[(__bridge id)kSecCodeInfoInternalRequirement];
        if (!internalReq) {
            NSLog(@"警告: 进程 %d 可能包含未签名的代码注入!", pid);
            CFRelease(signingInfo);
            CFRelease(guestCode);
            return YES;
        }
        
        CFRelease(signingInfo);
    }
    
    CFRelease(guestCode);
    return NO;
}

黑苹果环境特有问题

SIP与代码签名

SIP(System Integrity Protection)与代码签名紧密相连。在黑苹果上:

  • SIP完全启用:系统文件签名验证最高级别,非Apple签名的内核扩展无法加载。
  • SIP部分关闭(csr-active-config=0x67):允许加载未签名的kext,但用户空间签名验证仍然有效。
  • SIP完全关闭(csr-active-config=0xFF):所有签名验证被禁用,生产环境不推荐。
# 检查SIP状态
csrutil status

# 查看当前csr-active-config值
nvram csr-active-config

# 检查特定kext的签名状态
codesign -dvvv /Library/Extensions/Lilu.kext

自定义Kext签名

在SIP部分启用的黑苹果环境中,可以使用自签名证书为自定义kext签名:

# 1. 创建自签名证书(在钥匙串访问中创建"代码签名"类型的证书)
# 2. 对kext进行签名
codesign --force --sign "Your Certificate Name" /Library/Extensions/YourKext.kext

# 3. 验证签名
codesign --verify --verbose /Library/Extensions/YourKext.kext

# 4. 查看签名详细信息
codesign -dvvv --extract-certificates /Library/Extensions/YourKext.kext

实用工具开发:代码完整性检查器

以下是一个完整的命令行工具,可以扫描指定目录中所有可执行文件的签名状态:

// codescanner.m - 代码签名扫描器
#import <Foundation/Foundation.h>
#import <Security/Security.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSString *scanPath = @"/Applications";
        if (argc > 1) scanPath = [NSString stringWithUTF8String:argv[1]];
        
        NSFileManager *fm = [NSFileManager defaultManager];
        NSDirectoryEnumerator *enumerator = [fm enumeratorAtPath:scanPath];
        
        int totalCount = 0, signedCount = 0, unsignedCount = 0, invalidCount = 0;
        
        for (NSString *filename in enumerator) {
            if (![filename hasSuffix:@".app"]) continue;
            
            NSString *fullPath = [scanPath stringByAppendingPathComponent:filename];
            SecStaticCodeRef staticCode = NULL;
            
            OSStatus status = SecStaticCodeCreateWithPath(
                (__bridge CFURLRef)[NSURL fileURLWithPath:fullPath],
                kSecCSDefaultFlags, &staticCode);
            
            if (status != errSecSuccess) continue;
            
            totalCount++;
            status = SecStaticCodeCheckValidity(staticCode, kSecCSDefaultFlags, NULL);
            
            switch (status) {
                case errSecSuccess: signedCount++; break;
                case errSecCSUnsigned: 
                    unsignedCount++;
                    printf("[未签名] %s
", [filename UTF8String]);
                    break;
                default:
                    invalidCount++;
                    printf("[签名无效] %s (error: %d)
", [filename UTF8String], (int)status);
            }
            
            CFRelease(staticCode);
        }
        
        printf("
扫描结果: 总计 %d, 已签名 %d, 未签名 %d, 无效 %d
",
               totalCount, signedCount, unsignedCount, invalidCount);
    }
    return 0;
}

代码注入防护实战

检测常见注入技术

macOS上常见的代码注入技术包括DYLD_INSERT_LIBRARIES、thread injection、mach_inject和代码签名绕过。以下是针对这些技术的检测方法:

// 1. 检测DYLD环境变量注入
- (BOOL)isDyldInjectionDetected:(pid_t)pid {
    task_t task;
    if (task_for_pid(mach_task_self(), pid, &task) != KERN_SUCCESS) {
        return NO;
    }
    
    // 读取目标进程的环境变量
    // 检查DYLD_INSERT_LIBRARIES是否被设置
    // ...(实现细节需要使用mach_vm_read)
    
    return NO;
}

// 2. 检测未授权动态库加载
- (NSArray*)unexpectedLibrariesForPID:(pid_t)pid {
    NSMutableArray *unexpected = [NSMutableArray array];
    
    // 通过vmmap或mach_vm_region遍历加载的动态库
    // 对每个动态库执行签名验证
    // ... (实现细节)
    
    return unexpected;
}

// 3. 使用AMFI检查进程完整性
- (BOOL)isProcessIntegrityValid:(pid_t)pid {
    // 通过CS_OPS_STATUS检查进程的代码签名状态
    uint32_t flags = 0;
    csops(pid, CS_OPS_STATUS, &flags, sizeof(flags));
    
    // 检查关键标志位
    BOOL hasHardenedRuntime = (flags & CS_HARD) != 0;     // Hardened Runtime
    BOOL hasLibraryValidation = (flags & CS_KILL) != 0;   // Library Validation
    BOOL hasDebugRestriction = (flags & CS_DEBUGGED) == 0; // 未被调试
    
    return hasHardenedRuntime && hasLibraryValidation && hasDebugRestriction;
}

性能优化与最佳实践

缓存验证结果

频繁的签名验证可能带来性能开销。建议实现一个基于文件修改时间的缓存:

@interface CodeSignCache : NSObject
@property (nonatomic, strong) NSMutableDictionary *cache;
@end

@implementation CodeSignCache

- (BOOL)isValid:(NSString *)path {
    NSDictionary *attrs = [[NSFileManager defaultManager] 
        attributesOfItemAtPath:path error:nil];
    NSDate *modDate = attrs[NSFileModificationDate];
    
    NSString *key = path;
    NSDictionary *cached = self.cache[key];
    
    if (cached && [cached[@"date"] isEqualToDate:modDate]) {
        return [cached[@"valid"] boolValue];
    }
    
    // 执行实际的签名验证
    SecStaticCodeRef code = NULL;
    SecStaticCodeCreateWithPath((__bridge CFURLRef)[NSURL fileURLWithPath:path],
        kSecCSDefaultFlags, &code);
    
    OSStatus status = errSecSuccess;
    if (code) {
        status = SecStaticCodeCheckValidity(code, kSecCSDefaultFlags, NULL);
        CFRelease(code);
    }
    
    BOOL valid = (status == errSecSuccess);
    self.cache[key] = @{@"date": modDate, @"valid": @(valid)};
    return valid;
}

@end

总结

Security.framework中的代码签名验证API为macOS应用提供了一道强大的安全防线。对于黑苹果开发和运维场景,掌握这些API可以:

  • 快速排查因签名问题导致的启动失败或权限异常
  • 检测系统中潜在的恶意代码注入行为
  • 验证关键系统组件的完整性
  • 构建更安全的应用程序沙箱环境

代码签名不仅仅是Apple的要求,更是保障系统安全的重要基石。希望本文能帮助大家更深入地理解macOS的安全机制!

有任何关于代码签名或macOS安全开发的问题,欢迎留言交流!

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。