iOS26.4如何限制系统进程使用JavaScriptCore

admin 2026-06-10 05:00:07 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: iOS26.4引入非公开entitlementcom.apple.security.script-restrictions限制系统进程动态加载JavaScriptCore框架,旨在防御无文件代码执行攻击。该机制通过进程签名验证实现,当高风险进程尝试创建JS执行上下文时触发崩溃。文章通过macOS环境实测验证防御有效性,并解析底层ossecurityconfig_getAPI的检测逻辑。 综合评分: 85 文章分类: 移动安全,漏洞分析,应用安全,安全建设,技术标准


cover_image

iOS 26.4 如何限制系统进程使用 JavaScriptCore

原创

0xcc 0xcc

非尝咸鱼贩

2026年6月7日 09:04 瑞士

在小说阅读器读本章

去阅读

背景

JavaScriptCore 是 Apple 系统自带的 js 引擎。它不只服务于 Safari 和 WebKit,也可以被原生程序通过链接 JavaScriptCore.framework 直接调用执行 js。

原本正常的系统框架,攻击者却可以把 JavaScriptCore 当成系统内置解释器,通过代码注入技术把后利用逻辑寄生在已有进程里。通过脚本访问原生接口,不需要下载可执行文件,也不会产生新的进程。

这种借助系统自带脚本解释器运行代码的思路并不是新鲜事。哪怕只看 iOS 平台的公开研究,Google Project Zero 在 2019 年演示 iMessage 零点击利用的时候就已经用过了。

DarkSword 是比较近的现实案例。2026 年 3 月,iVerify、Google TAG 和 Lookout 联合披露了 DarkSword 工具包针对 iOS 用户大批量发起攻击的事件。它在完成沙箱逃逸和系统提权之后,并不启动独立 payload 进程,而是借用系统中已经存在的服务,在其中运行基于 JavaScriptCore 的代码来实现 C2 逻辑和提取系统敏感信息。甚至提权利用本身也是用 js 实现的。

所谓的无文件代码执行大体步骤如下:

  • 使用 dlopen 动态链接 JavaScriptCore.framework
  • 调用 JSContext 创建 js 执行环境
  • 绑定系统 native API(bridge)和内存读写能力到上下文
  • 运行 js

上面的步骤在浏览器、混合式应用或者需要脚本能力的系统组件里并不奇怪。但对某些关键系统进程(比如用户态核心的 launchd)来说,如果它们开始运行 js,那肯定是非预期行为。苹果的思路就是在预设的系统进程列表当中彻底禁用 JavaScriptCore。

根据 WebKit 的历史记录来看,在 2025 年 8 月的提交 d5e7d2a3eeeeab55e93553b2fc91fc61327a6ffb 就引入了针对性的防御,应该是早于 DarkSword 活动的时间。而它恰好是 Apple 对这种滥用解释器的利用技术的防御,应该是早已设计好的加固能力的一部分,只是到 26.4 才正式发布。

本文就来分析一下这个防御机制的具体实现。

新的 Entitlement

从 iOS 26.4 开始,launchd 以及若干高风险进程的 entitlement 就多了一个 com.apple.security.script-restrictions

注:entitlement 是苹果操作系统的机制。将一段 property list——键值对数据,与代码签名绑定后保存在可执行文件中,用来为操作系统标记特殊的权限

这些进程多与缩略图生成和 BlastDoor 有关

在苹果官方文档当中已经提到了如下 entitlement 可以启用包括 MTE 在内的多种加固措施,甚至对第三方开发者也开放了:

  • com.apple.security.hardened-process
  • com.apple.security.hardened-process.enhanced-security-version-string
  • com.apple.security.hardened-process.checked-allocations
  • com.apple.security.hardened-process.platform-restrictions-string
  • com.apple.security.hardened-process.dyld-ro

它们可以单独展开写各自的文章,篇幅限制就不深入了。

这个 script-restrictions 并没有出现在文档中。尝试在 Xcode iOS 工程中添加,构建之后会被悄悄移除;如果手工调用 codesign 命令签名 app 加上,安装过程会被真机拒绝。

所以目前这个机制不仅文档没有写,也不对第三方 app 开发者开放。不开放就算了,这个加固对第三方应用确实没任何作用,反而不少开发者都喜欢业务逻辑用 js 写,一套代码跑多端。禁了不是给自己找不快。

来实测验证一下。虽然 iOS 不让测试 app 用这个 entitlement,但是 macOS 26.5 没问题。

test_jsc.c

#include&nbsp;<stdio.h>#include&nbsp;<stdint.h>#include&nbsp;<dlfcn.h>
int&nbsp;main(void)&nbsp;{&nbsp; &nbsp;&nbsp;uint64_t&nbsp;(*os_security_config_get)(void) =&nbsp; &nbsp; &nbsp; &nbsp; (uint64_t&nbsp;(*)(void))dlsym(RTLD_DEFAULT,&nbsp;"os_security_config_get");
&nbsp; &nbsp;&nbsp;if&nbsp;(!os_security_config_get) {&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;fprintf(stderr,&nbsp;"system too old\n");&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;1;&nbsp; &nbsp; }
&nbsp; &nbsp;&nbsp;uint64_t&nbsp;cfg =&nbsp;os_security_config_get();&nbsp; &nbsp;&nbsp;printf("os_security_config_get() = 0x%llx &nbsp;| SCRIPT_RESTRICTIONS(0x40)=%s"&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;" &nbsp;HARDENED_HEAP(0x1)=%s TPRO(0x2)=%s GUARD_OBJECTS(0x100)=%s\n",&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (unsigned&nbsp;long&nbsp;long)cfg,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (cfg &&nbsp;0x40) &nbsp;?&nbsp;"ON"&nbsp; :&nbsp;"off",&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (cfg &&nbsp;0x1) &nbsp; ?&nbsp;"on"&nbsp; :&nbsp;"off",&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (cfg &&nbsp;0x2) &nbsp; ?&nbsp;"on"&nbsp; :&nbsp;"off",&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (cfg &&nbsp;0x100) ?&nbsp;"on"&nbsp; :&nbsp;"off");
&nbsp; &nbsp;&nbsp;void&nbsp;*jsc =&nbsp;dlopen("/System/Library/Frameworks/JavaScriptCore.framework/JavaScriptCore", RTLD_NOW);&nbsp; &nbsp;&nbsp;if&nbsp;(!jsc) {&nbsp;printf("dlopen(JavaScriptCore) FAILED: %s\n",&nbsp;dlerror());&nbsp;return&nbsp;2; }
&nbsp; &nbsp;&nbsp;void* (*JSGlobalContextCreate)(void*) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; =&nbsp;dlsym(jsc,&nbsp;"JSGlobalContextCreate");&nbsp; &nbsp;&nbsp;void* (*JSStringCreateWithUTF8CString)(const&nbsp;char*) =&nbsp;dlsym(jsc,&nbsp;"JSStringCreateWithUTF8CString");&nbsp; &nbsp;&nbsp;void* (*JSEvaluateScript)(void*,void*,void*,void*,int,void**) =&nbsp;dlsym(jsc,&nbsp;"JSEvaluateScript");&nbsp; &nbsp;&nbsp;double&nbsp;(*JSValueToNumber)(void*,void*,void**) &nbsp; &nbsp; &nbsp; =&nbsp;dlsym(jsc,&nbsp;"JSValueToNumber");&nbsp; &nbsp;&nbsp;printf("dlopen JSC=%p &nbsp;JSGlobalContextCreate=%p\n", jsc, (void*)JSGlobalContextCreate);
&nbsp; &nbsp;&nbsp;void&nbsp;*ctx =&nbsp;JSGlobalContextCreate(NULL); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;// <- traps here under restrictions&nbsp; &nbsp;&nbsp;printf("JSGlobalContextCreate -> %p\n", ctx);&nbsp; &nbsp;&nbsp;void&nbsp;*src =&nbsp;JSStringCreateWithUTF8CString("40 + 2");&nbsp; &nbsp;&nbsp;void&nbsp;*exc =&nbsp;NULL;&nbsp; &nbsp;&nbsp;void&nbsp;*res =&nbsp;JSEvaluateScript(ctx, src,&nbsp;NULL,&nbsp;NULL,&nbsp;0, &exc);&nbsp; &nbsp;&nbsp;double&nbsp;out =&nbsp;JSValueToNumber(ctx, res,&nbsp;NULL);&nbsp; &nbsp;&nbsp;printf("40 + 2 = %g\n", out);&nbsp; &nbsp;&nbsp;return&nbsp;0;}

ent.plist

<?xml version="1.0"&nbsp;encoding="UTF-8"?><!DOCTYPE&nbsp;plist&nbsp;PUBLIC&nbsp;"-//Apple//DTD PLIST 1.0//EN"&nbsp;"http://www.apple.com/DTDs/PropertyList-1.0.dtd"><plist&nbsp;version="1.0"><dict>&nbsp; &nbsp;&nbsp;<key>com.apple.security.script-restrictions</key>&nbsp; &nbsp;&nbsp;<true/></dict></plist>

编译后加上自签名的 entitlement:

cc test_jsc.c&nbsp;-o test_jsccodesign -s -&nbsp;--entitlements&nbsp;ent.plist&nbsp;--force&nbsp;test_jsc

预期的行为是进程会抛异常,所以挂 lldb 跑:

➜ &nbsp;jsc_test lldb test_jsc(lldb) target create&nbsp;"test_jsc"Current executable&nbsp;set&nbsp;to&nbsp;'/Users/cc/Projects/phrack/jsc_test/test_jsc'&nbsp;(arm64).(lldb)&nbsp;rProcess&nbsp;49313&nbsp;launched:&nbsp;'/Users/cc/Projects/phrack/jsc_test/test_jsc'&nbsp;(arm64)os_security_config_get() =&nbsp;0x40 &nbsp;| SCRIPT_RESTRICTIONS(0x40)=ON &nbsp;HARDENED_HEAP(0x1)=off TPRO(0x2)=off GUARD_OBJECTS(0x100)=offdlopen JSC=0x365fa85a0 &nbsp;JSGlobalContextCreate=0x1a786a9bcProcess&nbsp;49313&nbsp;stopped* thread&nbsp;#1, queue = 'com.apple.main-thread', stop reason = EXC_BREAKPOINT (code=1, subcode=0x1a7ac6854)&nbsp; &nbsp; frame&nbsp;#0: 0x00000001a7ac6854 JavaScriptCore`WTF::makePagesFreezable(void*, unsigned long) + 304JavaScriptCore`WTF::makePagesFreezable:-> &nbsp;0x1a7ac6854 <+304>: brk &nbsp; &nbsp;#0xc471&nbsp; &nbsp;&nbsp;0x1a7ac6858 <+308>: brk &nbsp; &nbsp;#0x1&nbsp; &nbsp;&nbsp;0x1a7ac685c <+312>: adrp &nbsp; x1,&nbsp;5970&nbsp; &nbsp;&nbsp;0x1a7ac6860 <+316>: add &nbsp; &nbsp;x1, x1,&nbsp;#0xb44 ; "/AppleInternal/Library/BuildRoots/4~CN9QugCEa5fya5kkZXSbadtTe9oVa3sO3gsEwzc/Library/Caches/com.apple.xbs/TemporaryDirectory.sbZNe3/Sources/WTF/Source/WTF/wtf/PageBlock.cpp"Target&nbsp;0: (test_jsc) stopped.

程序没有打印 42,而是触发崩溃。去掉 entitlement 之后则一切正常。

到这里这个演示就结束了。也就是如果进程签名里有这个非公开的 com.apple.security.script-restrictions,JavaScriptCore 库可以被动态链接,但无法创建可用的执行上下文。

如果读者想知道是如何实现的,可以继续看下去。

实现

状态查询 API

上面的测试代码出现了一个 iOS 26 新加入的 API os_security_config_get

实际上一共有三个 API:

  • os_security_config_t os_security_config_get();:获取当前进程信息
  • int os_security_config_get_for_proc(pid_t pid, os_security_config_t *config);:获取 pid 对应进程,可能需要 root 权限
  • int os_security_config_get_for_task(task_t task, os_security_config_t *config);:获取 task 对应进程,需要先 task_for_pid,权限要求更高

它们都属于苹果官网文档一个很不起眼的模块 os,一共就没几个 API。

这个 API 返回一个 os_security_config_t,也就是一个 64 位的无符号整数:

/*!&nbsp;*&nbsp;@enum&nbsp;os_security_config_t&nbsp;*&nbsp;*&nbsp;@discussion&nbsp;* Supported security configurations that a process/task can have.&nbsp;* This is a bitmask type, allowing multiple configurations to be active.&nbsp;*&nbsp;*&nbsp;@constant&nbsp;OS_SECURITY_CONFIG_NONE&nbsp;* No security config flags set.&nbsp;*&nbsp;*&nbsp;@constant&nbsp;OS_SECURITY_CONFIG_HARDENED_HEAP&nbsp;* Indicates that the Hardened Heap configuration is enabled for the process/task.&nbsp;* This implies security-critical settings for the system memory allocator.&nbsp;*&nbsp;*&nbsp;@constant&nbsp;OS_SECURITY_CONFIG_TPRO&nbsp;* Indicates that Trusted Path Read-Only (TPRO) is enabled for the process/task.&nbsp;*&nbsp;*&nbsp;@constant&nbsp;OS_SECURITY_CONFIG_SCRIPT_RESTRICTIONS&nbsp;* Indicates Script Restrictions are enabled for the process/task.&nbsp;*&nbsp;*&nbsp;@constant&nbsp;OS_SECURITY_CONFIG_GUARD_OBJECTS&nbsp;* Indicates that the Guard Objects configuration is enabled for the process/task.&nbsp;*/__API_AVAILABLE(macos(26.0),&nbsp;ios(26.0),&nbsp;tvos(26.0),&nbsp;watchos(26.0),&nbsp;visionos(26.0),&nbsp;driverkit(25.0))OS_OPTIONS(os_security_config, uint64_t,&nbsp; OS_SECURITY_CONFIG_NONE =&nbsp;0x0,&nbsp; OS_SECURITY_CONFIG_HARDENED_HEAP&nbsp;OS_SWIFT_NAME(hardenedHeap) =&nbsp;0x1,&nbsp; OS_SECURITY_CONFIG_TPRO&nbsp;OS_SWIFT_NAME(trustedPathReadOnly) =&nbsp;0x2,&nbsp; OS_SECURITY_CONFIG_MTE&nbsp;OS_SWIFT_NAME(memoryTaggingExtension) =&nbsp;0x4,
&nbsp; OS_SECURITY_CONFIG_SCRIPT_RESTRICTIONS&nbsp;OS_SWIFT_NAME(scriptRestrictions) =&nbsp;0x40,&nbsp; OS_SECURITY_CONFIG_GUARD_OBJECTS&nbsp;OS_SWIFT_NAME(guardObjects) =&nbsp;0x100,);

在 SDK 里的注释写得挺清楚。眼尖的读者应该发现这几个 bit 正好对应了前文提到的各种 entitlement,甚至还有 MTE 的配置。这也是我为什么没法展开的原因,真写起来可太长了。

这组枚举对应了 XNU 内核的 task_security_config(对应版本 xnu-12377):

struct&nbsp;task_security_config&nbsp;{union&nbsp;{struct&nbsp;{uint16_t&nbsp;hardened_heap:&nbsp;1,             &nbsp; &nbsp;tpro:&nbsp;1,#if&nbsp;HAS_MTE || HAS_MTE_EMULATION_SHIMS           &nbsp; &nbsp;sec:&nbsp;1,#else&nbsp;/* HAS_MTE || HAS_MTE_EMULATION_SHIMS */           reserved:&nbsp;1,#endif         platform_restrictions_version:&nbsp;3,           &nbsp; &nbsp;script_restrictions:&nbsp;1,           &nbsp; &nbsp;ipc_containment_vessel:&nbsp;1,            &nbsp; &nbsp;guard_objects:&nbsp;1;uint8_t&nbsp;hardened_process_version;      };uint32_t&nbsp;value;  };};

在 XNU 开源代码里并没有找到这结构初始化的部分,只能去 kernelcache 里反编译。伪代码太长就不贴了,直接交叉引用字符串 com.apple.security.hardened-process.dyld-ro 就能定位到。

这个函数除了以上提到的加固 entitlement,还检测如下几个键,并针对性地调整防御等级:

  • 第三方浏览器引擎相关(感谢欧盟):com.apple.developer.web-browser-engine.{host,rendering,networking,webcontent}
  • driverkit:com.apple.developer.driverkit

标志位传递到用户态有三种途径:

  • 当前进程:启动时由 applev[] 传递,一次性初始化,缓存在全局变量
  • 获取 pid:通过 proc_pidinfo 调用
  • 获取 task:通过 task_info 调用

反编译 libsystem_platform.dylib!os_security_config_get 仅有一行代码:

uint64_t&nbsp;os_security_config_get(){&nbsp;&nbsp;return&nbsp;__restrictions_config &&nbsp;0x40&nbsp;| __security_config &&nbsp;0x107;}

这两个全局变量(注:其实有一个不可写)由 __os_security_config_init 负责初始化。

内核参数传递

XNU 内核在启动进程时,除了常用的 argv[] 和 envp[] 之外还会通过栈传递一个 apple[] 字符串数组。如果当前进程有安全相关的配置,就会以 security_config=0x??? 的格式传递给用户态:

{#define&nbsp;SECURITY_CONFIG_KEY&nbsp;"security_config="char&nbsp;security_config_str[strlen(SECURITY_CONFIG_KEY) + HEX_STR_LEN +&nbsp;1];
    snprintf(security_config_str,&nbsp;sizeof(security_config_str),  &nbsp; &nbsp;SECURITY_CONFIG_KEY&nbsp;"0x%x", task_get_security_config(task));
    error = exec_add_user_string(imgp,&nbsp;CAST_USER_ADDR_T(security_config_str),&nbsp;UIO_SYSSPACE,&nbsp;FALSE);  imgp->ip_applec++;}

#if&nbsp;HAS_MTE || HAS_MTE_EMULATION_SHIMSif&nbsp;(task_has_sec(task)) {const&nbsp;char&nbsp;*sec_transition_shims =&nbsp;"has_sec_transition=1";        error = exec_add_user_string(imgp,&nbsp;CAST_USER_ADDR_T(sec_transition_shims),&nbsp;UIO_SYSSPACE,&nbsp;FALSE);if&nbsp;(error) {            printf("Failed to add security translation shims notification\n");goto&nbsp;bad;        }
        imgp->ip_applec++;
/* Push down MTE-specific configuration options that allocators may be interested into. */#define&nbsp;SEC_TRANSITION_POLICY_KEY&nbsp;"sec_transition_policy="
char&nbsp;sec_transition_policy[strlen(SEC_TRANSITION_POLICY_KEY) + HEX_STR_LEN +&nbsp;1];        snprintf(sec_transition_policy,&nbsp;sizeof(sec_transition_policy),      &nbsp; &nbsp;SEC_TRANSITION_POLICY_KEY&nbsp;"0x%x", task_get_sec_policy(task));
        error = exec_add_user_string(imgp,&nbsp;CAST_USER_ADDR_T(sec_transition_policy),&nbsp;UIO_SYSSPACE,&nbsp;FALSE);        imgp->ip_applec++;  }#endif&nbsp;/* HAS_MTE || HAS_MTE_EMULATION_SHIMS */

针对 MTE 还专门有一个 has_sec_transition=1 的配置。

dyld 在启动进程时负责初始化栈和启动参数(argc、argv、envp 和 apple 等),并传递给主程序的入口点,以及在每次载入 dylib 框架的时候调用其 _mod_init_func 注册的初始化函数。

apple[] 字符串数组最终传递顺序:

  • dyld
  • libSystem.B.dylib!libSystem_initializer
  • libsystem_platform.dylib!__libplatform_init
  • libsystem_platform.dylib!__os_security_config_init

也就是每个进程全局初始化一次。

进程状态初始化

有趣的点在 __os_security_config_init 函数内部的实现。其使用 simple_getenv 提取先前提到来自内核的字符串参数,然后解析十六进制字符串作为初始的配置值,也就是我们之前提到最终会存入的 os_security_config_t

这里有一个有意思的分支:

if&nbsp;(v7 & OS_SECURITY_CONFIG_GUARD_OBJECTS)&nbsp; &nbsp; &nbsp; __security_config |= OS_SECURITY_CONFIG_GUARD_OBJECTS;
if&nbsp;(v7 & OS_SECURITY_CONFIG_SCRIPT_RESTRICTIONS ){&nbsp; &nbsp; &nbsp; address = (mach_vm_address_t)&os_script_config_storage;&nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(&nbsp;mach_vm_map(mach_task_self_, &address,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;0x4000,&nbsp;//&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;0, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// mask&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;VM_FLAGS_OVERWRITE | VM_FLAGS_PERMANENT,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;MEMORY_OBJECT_NULL,&nbsp;0, FALSE, &nbsp; &nbsp; &nbsp;// object / offset / copy&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;VM_PROT_NONE, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;// cur_protection&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;VM_PROT_NONE, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;// max_protection&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;VM_INHERIT_COPY) ) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// inheritance (== VM_INHERIT_DEFAULT)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; __os_security_config_init_cold_2();
&nbsp; &nbsp; &nbsp; address = (mach_vm_address_t)&__restrictions_config;&nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(&nbsp;mach_vm_map(mach_task_self_, &address,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;0x4000,&nbsp;0,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;VM_FLAGS_OVERWRITE | VM_FLAGS_PERMANENT,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;MEMORY_OBJECT_NULL,&nbsp;0, FALSE,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;VM_PROT_READ | VM_PROT_WRITE, &nbsp; &nbsp; &nbsp;// cur_protection&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;VM_PROT_READ | VM_PROT_WRITE, &nbsp; &nbsp; &nbsp;// max_protection&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;VM_INHERIT_COPY) )&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; __os_security_config_init_cold_3();
&nbsp; &nbsp; &nbsp; __restrictions_config = OS_SECURITY_CONFIG_SCRIPT_RESTRICTIONS;
&nbsp; &nbsp; &nbsp; result =&nbsp;mach_vm_protect(mach_task_self_, address,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;0x4000,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;/*set_maximum=*/&nbsp;TRUE,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;VM_PROT_READ);&nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;( (kern_return_t)result )&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; __os_security_config_init_cold_4();&nbsp; }

代码没有简单地把值存到全局变量,而是使用位运算拆成了两个部分:__security_config 和 __restrictions_config

目前 __restrictions_config 仅仅用来保存 OS_SECURITY_CONFIG_SCRIPT_RESTRICTIONS 状态,而一旦检测到这个状态启用,libsystem_platform 会锁定其所在的内存页面的访问权限为只读。不仅如此,还有另一个名为 os_script_config_storage 的符号,直接连读权限都取消了。什么鬼?

WebKit 的处理

我们刚才绕了一大圈从内核到链接器到系统库,主要为了演示如何禁用 JavaScriptCore。所以这个符号当然是 WebKit 在用。直接上源码:

static&nbsp;bool&nbsp;scriptingIsForbidden()&nbsp;{&nbsp; &nbsp;&nbsp;return&nbsp;processHasEntitlement("com.apple.security.script-restrictions"_s);}
void&nbsp;initialize()&nbsp;{&nbsp; &nbsp; ...&nbsp; &nbsp; WTF::makePagesFreezable(&os_script_config_storage, OpcodeConfigSizeToProtect);
&nbsp; &nbsp;&nbsp;if&nbsp;(g_jscConfig.vmEntryDisallowed ||&nbsp;scriptingIsForbidden()) [[unlikely]] {&nbsp; &nbsp; &nbsp; &nbsp; g_jscConfig.vmEntryDisallowed =&nbsp;true;&nbsp; &nbsp; &nbsp; &nbsp; WTF::permanentlyFreezePages(&os_script_config_storage, OpcodeConfigSizeToProtect,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; WTF::FreezePagePermission::None);&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return;&nbsp; &nbsp; }&nbsp; &nbsp; WTF::compilerFence();&nbsp; &nbsp; ...}

乍一看 WebKit 也是检测标准的 entitlement 来拒绝初始化,但实际上压根走不到那个分支。

早在上一行 WTF::makePagesFreezable 调用,如果当前进程禁止 JavaScriptCore,libsystem_platform.dylib 就会把 os_script_config_storage 所在内存标记为不可访问。

// Works together with permanentlyFreezePages().void&nbsp;makePagesFreezable(void* base,&nbsp;size_t&nbsp;size){&nbsp; &nbsp;&nbsp;RELEASE_ASSERT(roundUpToMultipleOf(pageSize(), size) == size);
#if&nbsp;PLATFORM(COCOA)&nbsp; &nbsp;&nbsp;mach_vm_address_t&nbsp;addr = std::bit_cast<uintptr_t>(base);&nbsp; &nbsp;&nbsp;auto&nbsp;flags = VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE | VM_FLAGS_PERMANENT;
&nbsp; &nbsp;&nbsp;auto&nbsp;attemptVMMapping = [&] {&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;auto&nbsp;result =&nbsp;mach_vm_map(mach_task_self(), &addr, size,&nbsp;pageSize() -&nbsp;1, flags, MEMORY_OBJECT_NULL,&nbsp;0,&nbsp;false, VM_PROT_READ | VM_PROT_WRITE, VM_PROT_READ | VM_PROT_WRITE, VM_INHERIT_DEFAULT);&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;result;&nbsp; &nbsp; };
&nbsp; &nbsp;&nbsp;auto&nbsp;result =&nbsp;attemptVMMapping();#if&nbsp;PLATFORM(IOS_FAMILY_SIMULATOR)&nbsp; &nbsp;&nbsp;if&nbsp;(result != KERN_SUCCESS) {&nbsp; &nbsp; &nbsp; &nbsp; flags &= ~VM_FLAGS_PERMANENT;&nbsp;// See rdar://75747788.&nbsp; &nbsp; &nbsp; &nbsp; result =&nbsp;attemptVMMapping();&nbsp; &nbsp; }#endif&nbsp; &nbsp;&nbsp;RELEASE_ASSERT(result == KERN_SUCCESS);#else&nbsp; &nbsp;&nbsp;UNUSED_PARAM(base);&nbsp; &nbsp;&nbsp;UNUSED_PARAM(size);#endif}

因此 makePagesFreezable 直接就抛异常(RELEASE_ASSERT)了。

以上的内存权限也是为了防止漏洞利用在拥有读写原语之后修改 __restrictions_config。照这样看来 Apple 认为剩余的几个 flag 并不需要只读保护。可能是因为读取时机的区别,其余的 bit 主要影响内存分配器(对应开源代码的 libmalloc)的行为,而它们早在进程初始化阶段就处理完了。

小结

一个一句话能说清的需求:禁止某些系统进程使用 JavaScriptCore,牵扯到了一堆组件来实现。

内核将 entitlement 的设定转换成 task_security_config,再通过 apple[] 数组传递给用户态初始化逻辑,而用户态的状态消费者则有被动的内存访问失败和显式检查 processHasEntitlement 两层把关,让 JavaScriptCore 初始化失败。

参考资料

  • https://developer.apple.com/documentation/Xcode/enabling-enhanced-security-for-your-app
  • https://developer.apple.com/documentation/javascriptcore/jscontext
  • https://developer.apple.com/documentation/os/os_security_config_get
  • https://github.com/apple-oss-distributions/xnu
  • https://github.com/apple-oss-distributions/dyld
  • https://github.com/apple-oss-distributions/libsystem

免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:非尝咸鱼贩 0xcc 0xcc《iOS 26.4 如何限制系统进程使用 JavaScriptCore》

评论:0   参与:  0