黑苹果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验证一个应用程序时,它实际上在验证一个签名链:
- 应用程序签名:开发者证书对App Bundle的签名
- 资源签名:Bundle内每个可执行文件、框架、库的独立签名
- 公证票据:Apple Notary Service签发的公证票据(从macOS Catalina开始)
- 系统完整性:SIP(System Integrity Protection)对系统文件的保护
关键守护进程
| 守护进程 | 职责 | 配置文件 |
| amfid | Apple Mobile File Integrity,运行时签名验证 | /System/Library/LaunchDaemons/com.apple.amfid.plist |
| syspolicyd | Gatekeeper策略评估和执行 | /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 IDinfo[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安全开发的问题,欢迎留言交流!


评论(0)