macOS xnu execsw 符号或将废弃

孙康

macOS 每次发布新的操作系统版本,其内核 xnu 代码都会有所变化,但execsw符号已经多年没变了。用户态可以通过调用fork+execve或者posix_spawn的方式实现进程创建和执行,对应到内核态实现 Mach-o 文件执行的关键函数为exec_activate_image,相关源码请参考 kern_exec.c

可执行文件加载分析

exec_activate_image函数部分代码摘抄如下,xnu 版本为[8792.81.2],最新版本情况可查看 opensource-xnu 。函数起始部分有一些有关加锁的操作,因为在执行映像替换时,要保证当前进程不能被杀死。函数结尾处有调用kauth_authorize_fileop,作用是通知进程执行事件的发生。Kauth 框架包含通知事件 FileOP 域以及授权事件 Vnode 域,这里便是创建通知事件,授权事件的创建在exec_check_permissions函数内,可以看出授权事件创建的时机非常早。encapsulated_binary 标签位置,有一处循环,循环体是execsw结构体数组里的函数地址调用。这里便是 xnu 内核唯一一处使用execsw符号的位置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
static int exec_activate_image(struct image_params *imgp) {
/*
* For exec, the translock needs to be taken on old proc and not
* on new shadow proc.
*/
if (imgp->ip_flags & IMGPF_EXEC) {
p = current_proc();
}

error = execargs_alloc(imgp);
if (error) {
goto bad_notrans;
}

error = exec_save_path(imgp, imgp->ip_user_fname, imgp->ip_seg, &excpath);
if (error) {
goto bad_notrans;
}

/*
* Before we start the transition from binary A to binary B, make
* sure another thread hasn't started exiting the process. We grab
* the proc lock to check p_lflag initially, and the transition
* mechanism ensures that the value doesn't change after we release
* the lock.
*/
proc_lock(p);
if (p->p_lflag & P_LEXIT) {
error = EDEADLK;
proc_unlock(p);
goto bad_notrans;
}
error = proc_transstart(p, 1, 0);
proc_unlock(p);
if (error) {
goto bad_notrans;
}

error = exec_check_permissions(imgp);
if (error) {
goto bad;
}

encapsulated_binary:
/* Limit the number of iterations we will attempt on each binary */
if (++itercount > EAI_ITERLIMIT) {
error = EBADEXEC;
goto bad;
}
error = -1;
for (i = 0; error == -1 && execsw[i].ex_imgact != NULL; i++) {
error = (*execsw[i].ex_imgact)(imgp);

switch (error) {
/* case -1: not claimed: continue */
case -2: /* Encapsulated binary, imgp->ip_XXX set for next iteration */
goto encapsulated_binary;

case -3: /* Interpreter */
......
default:
break;
}
}

if (error == -1) {
error = ENOEXEC;
} else if (error == 0) {
if (imgp->ip_flags & IMGPF_INTERPRET && ndp->ni_vp) {
AUDIT_ARG(vnpath, ndp->ni_vp, ARG_VNODE2);
}

/*
* Call out to allow 3rd party notification of exec.
* Ignore result of kauth_authorize_fileop call.
*/
if (kauth_authorize_fileop_has_listeners()) {
kauth_authorize_fileop(vfs_context_ucred(imgp->ip_vfs_context),
KAUTH_FILEOP_EXEC,
(uintptr_t)ndp->ni_vp, 0);
}
}

return error;
}

execsw 符号含义

execsw符号可在开源代码中找到定义,如下。execsw是结构体常量数组,内部存放了函数地址和函数描述对,因此exec_activate_image函数可以使用循环的方式遍历该数组并调用其存放的函数。这里 Apple 开发者有一句注释,表示当前对该数组是硬编码的,后期会采用链接器集的方式实现,但这么多年来,该数组没有发生过变化,即便是 os14.0 Beta 版本的发布也没有对此进行更新。值得注意的是数组存放的第一个函数地址exec_mach_imgact,这是 Mach-o 格式的可执行文件解析、加载、执行所必经的函数。该函数相当复杂,代码接近千行,本文不对此讨论。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*
* Our image activator table; this is the table of the image types we are
* capable of loading. We list them in order of preference to ensure the
* fastest image load speed.
*
* XXX hardcoded, for now; should use linker sets
*/
struct execsw {
int(*const ex_imgact)(struct image_params *);
const char *ex_name;
} const execsw[] = {
{ exec_mach_imgact, "Mach-o Binary" },
{ exec_fat_imgact, "Fat Binary" },
{ exec_shell_imgact, "Interpreter Script" },
{ NULL, NULL}
};

execsw 改动

虽然多年来execsw一直是结构体数组,即便 os14 发布也没有改变这一点,但 os14 内核中却不再使用这一看起来至关重要的符号。我们可以使用 ida 逆向内核文件看一下改动。内核文件存放路径为/System/Library/Kernels/kernel,为便于分析,读者可安装 Kernel Debug Kit 内核调试工具,从中获取导出符号更多的kernel.development文件进行分析。

下面贴了 os13.4 kernel.development部分逆向汇编代码。这里使用lea指令将execsw符号地址赋值给r12,后面使用call指令调用r12偏移后的地址表示的函数,偏移大小存储在rbx寄存器。在call指令之上,有一处xor指令,将ebx,即rbx低字节进行了清零处理,相当于偏移大小设置为 0,这便表示调用了execsw存储的第一个函数地址:exec_mach_imgact

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
; 13.4 kernel
__text:FFFFFF8000949243 loc_FFFFFF8000949243: ; CODE XREF: _exec_activate_image+45D↑j
__text:FFFFFF8000949243 mov r13d, 55h
__text:FFFFFF8000949249 mov r15, [rbp+var_60]
__text:FFFFFF800094924D cmp r15d, 2
__text:FFFFFF8000949251 lea r12, _execsw
__text:FFFFFF8000949258 jg loc_FFFFFF8000949459
__text:FFFFFF800094925E
__text:FFFFFF800094925E loc_FFFFFF800094925E: ; CODE XREF: _exec_activate_image+4E0↓j
__text:FFFFFF800094925E inc r15d
__text:FFFFFF8000949261 xor ebx, ebx
__text:FFFFFF8000949263 db 66h, 66h, 66h, 66h, 2Eh
__text:FFFFFF8000949263 nop word ptr [rax+rax+00000000h]
__text:FFFFFF8000949270
__text:FFFFFF8000949270 loc_FFFFFF8000949270: ; CODE XREF: _exec_activate_image+4D5↓j
__text:FFFFFF8000949270 mov rdi, r14
__text:FFFFFF8000949273 call qword ptr [rbx+r12]
......

os14.0 的kernel.development相关位置的汇编代码则不同了,这里直接使用call指令调用了exec_mach_imgact函数,查看execsw符号的交叉引用结果为空。表示虽然execsw符号依然存在,但已经不再使用了,可能不久的将来execsw符号便会成为历史。

1
2
3
4
5
6
7
8
9
10
11
12
; 14.0 kernel
__text:FFFFFF8000933E3D loc_FFFFFF8000933E3D: ; CODE XREF: _exec_activate_image+4FD↑j
__text:FFFFFF8000933E3D mov rdi, rbx
__text:FFFFFF8000933E40 call _exec_mach_imgact
__text:FFFFFF8000933E45 mov r13d, eax
__text:FFFFFF8000933E48 lea eax, [r13+3]
__text:FFFFFF8000933E4C cmp eax, 3 ; switch 4 cases
__text:FFFFFF8000933E4F ja loc_FFFFFF8000934118 ; jumptable FFFFFF8000933E5C default case
__text:FFFFFF8000933E55 movsxd rax, dword ptr [r15+rax*4]
__text:FFFFFF8000933E59 add rax, r15
__text:FFFFFF8000933E5C jmp rax ; switch jump
......
  • Title: macOS xnu execsw 符号或将废弃
  • Author: 孙康
  • Created at : 2023-07-22 12:00:00
  • Updated at : 2023-08-15 09:57:19
  • Link: https://conradsun.github.io/2023/075ef6acf1.html
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments
On this page
macOS xnu execsw 符号或将废弃