前面曾简单介绍过基于Kauth或者EndpointSecurity框架可以监视系统的各类文件、进程事件,在审计后阻断或放通事件的执行。其中基于内核拓展的方案除了可以阻断执行,还可以修改函数调用参数,进行诸如文件保护、网络隔离等操作。
编写内核拓展较为复杂且可能导致系统崩溃等严重后果。Apple提供了在用户态hook函数调用的机制,使用较为方便,称为动态库注入。动态库注入是dyld加载器提供的功能,通过修改环境变量DYLD_INSERT_LIBRARIES可向二进制注入动态库。注入的动态库需实现函数替换,Apple提供了dyld-interposing方法,使用如下。
#ifndef DYLD_INTERPOSE
#define DYLD_INTERPOSE(_replacement,_replacee) \
__attribute__((used)) static struct{ const void* replacement; const void* replacee; } _interpose_##_replacee \
__attribute__ ((section ("__DATA,__interpose"))) = { (const void*)(unsigned long)&_replacement, (const void*)(unsigned long)&_replacee };
#endif
该段宏定义的目的是修改MachO文件的DATA区的interpose字段,目的是将原始函数的地址替换为自定义函数地址,dyld在加载动态库时对该地址进行替换。如这里对进程执行的系统调用execve和posix_spawn进行替换,将映像更换为echo,读者可自行查看是否完成替换。注意,调用printf函数不一定可以打印出来,具体原因不太明白,猜测是因为printf属于懒加载函数,注入动态库时该函数地址没有更新,无法调用。
static const char *s_repalce_path = "/bin/echo";
int fh_execve(const char *file, char *const *argv, char *const *envp) {
printf("[FishHook - execve] pid: %d, process path: %s.", getpid(), file);
return execve(s_repalce_path, argv, envp);
}
int fh_posix_spawn(pid_t *pid, const char *path, const posix_spawn_file_actions_t *actions, const posix_spawnattr_t *attr, char *const *argv, char *const *envp) {
printf("[FishHook - posix_spawn] pid: %d, process path: %s.", *pid, path);
return posix_spawn(pid, s_repalce_path, actions, attr, argv, envp);
}
DYLD_INTERPOSE(fh_execve, execve)
DYLD_INTERPOSE(fh_posix_spawn, posix_spawn)
需要注意的是,在开启SIP (System Integrity Protection) 机制的机器上,签名的应用程序可能无法继承该环境变量,原因是内核会进行防止可执行文件被修改的检查,可参考Apple官方的SIP指南。