审计(audit)

简介

audit是macOS操作系统上一个安全审计功能,最初是从OpenBSM移植到OS X系统的。

由于审计是一个和安全密切的相关的操作,所以它是由内核在系统级别执行的,但是当出现安全性敏感的操作或情况时,用户态应用程序可以请求显示地记录日志,但在大多数情况下,它通过外部定义的审计策略记录用户和进程的操作。这些审计策略决定了哪些事件或情况值得系统关注。因此,系统管理员可以定义和实施审计策略,收集数据,进行安全分析。但是对于系统性能开销比较大,所以在iOS上是没有的。

注意:在macOS 14被弃用了,被EndpointSecurity替代,但是也可以通过一下命令重启audit

bash 复制代码
sudo launchctl enable system/com.apple.auditd && sudo reboot

从用户态的角度看审计

1.1 auditd守护进程

审计是OS X中自包含的一个子系统,在用户态主要的组件是auditd,它是由launchd根据需要启动的后台服务进程,这个后台服务进程不负责实际的审计日志记录,而是内核本身通过vnode直接写入日志的。但是这个后台服务进程可以控制内核组件,因此如果要控制审计,则需要控制auditd进程。管理员可以通过audit命令去控制auditd进程。

参数 描述
-e 删除过期的日志。(过期标准是啥??)
-i 初始化审计
-n 关闭当前审计文件,开启新的日志,并且删除过期的审计日志
-s 指定审计系统应该从审计控制文件/etc/security/audit_control中同步其配置并创建一个新的日志文件。来自audit_control(5)配置flags参数是在登录时设置的,不与此标志同步。
-t 终止审计

1.2 审计日志

1.2.1 日志文件格式

审计日志存放在/var/audit目录中,日志文件的命名格式是(起始时间戳.终止时间戳),时间精度是秒,由于日志是持续生成的,所以除非系统崩溃或重启,一个文件的stop_time就是下一个日志文件的start_time,最后一个日志的stop_time为not_terminated.可以通过一下命令查看日志:

bash 复制代码
sudo ls -l /var/audit

-r--r-----  1 root  wheel     2375  1 17 23:12 20240117150345.20240117151217
-r--r-----  1 root  wheel      196  1 17 23:22 20240117151217.not_terminated

1.2.2 查看日志

日志文件是以二进制格式保存,可以通过praudit命令进行解码。这个命令可以将日志输出位人类可读的格式,例如默认输出为CSV,还可以输出为XML格式。具体命令参数解释如下表

参数 描述
-d , 指定分隔符。默认的分隔符是逗号。
-l 在同一行打印整个记录。如果未指定此选项,则每个令牌将显示在不同的行上。
-n 不要将用户和组id转换为其名称,而是保留其数字形式。
-p 如果要从tail(1)实用程序通过管道输入praudit,请指定此选项。这将导致praudit同步到下一条记录的开始。
-r 以原始形式打印记录。以数字形式(也称为原始形式)显示记录和事件类型。该选项与-s不兼容。
-s 打印短格式的记录。以简短的文本形式显示记录和事件。该选项与-r不兼容。
-x 将日志输出为XML格式

由于日志循环得太频繁了,所有还有一个特殊的字节设备/dev/auditpipe,用户态程序可以通过这个设备实时访问审计记录。praudit命令也可以直接操作这个设备查看实时日志。具体先执行以下命令

bash 复制代码
sudo praudit -s /dev/auditpipe

然后睡眠屏幕,然后在认证解锁系统,可以看到以下类似的输出

go 复制代码
header,196,11,AUE_auth_user,0,Wed Jan 17 23:42:40 2024, + 305 msec
subject,wzf,wzf,staff,wzf,staff,506,100003,1238,0.0.0.0
text,Verify password for record type Users 'wzf' node '/Local/Default'
return,failure: Unknown error: 255,5000
identity,1,com.apple.opendirectoryd,complete,,complete,0xe7c3aea310e4e4c45cf6640ca9931e35805c68f3
trailer,196
...........

1.3 审计控制策略

由于审计操作都是在行为发生时执行的,所以对于性能的损耗比较严重,管理员可以通过控制审计策略来控制审计日志记录。这些策略都是放在/etc/security目录下的文件中。其文件作用描述如下表:

审计控制文件 作用描述
audit_control 设置审计策略以及日志相关的管理数据,其中flags值定义了审计的事件类别
audit_class 定义了事件类别,比如文件、进程、网络等类别
audit_event 定义了事件标识符映射为类别助记符和人类可读的名称,比如AUE_OPEN事件,表示仅仅访问了文件属性fa(文件属性访问)3:AUE_OPEN:open(2) - attr only:fa
audit_user 提供了额外针对每个用户的审计策略,与audit_control中的审计策略组合使用
audit_warn 对auditd后台服务程序产生的警告信息(例如:'audit space low (< 5% free) on audit log file-system')进行处理的shell脚本。这个脚本通常将消息传递给logger

几个文件详解如下:

1.3.1 audit_control

javascript 复制代码
dir:/var/audit //审计日志存储目录
flags:lo,aa //指定为所有用户审计哪些事件类别,其中lo表示记录登录/注销事件,aa表示记录授权和拒绝访问的事件。
minfree:5//审计日志目录大小至少需要磁盘空间的百分之5%,当可用磁盘空间低于此值时将发出限制警告
naflags:lo,aa //定义这些事件类别不能仅仅用于某个特定的用户
policy:cnt,argv//指定各种行为的全局审计策略标志列表,例如失败停止、路径和参数审计等。
filesz:2M//设置单个审计日志文件的最大大小为2M。当达到此大小时,系统可能会轮转日志文件
expire-after:10M//设置审计日志文件的过期时间为10分钟。过期后的日志文件可能会被系统清理。如果不设置就不会过期

1.3.2 audit_class

audit_class文件包含系统上可审计事件类别的描述,每个可审计事件是一个类的成员,每行将一个审计事件掩码(位图)映射到一个类描述。内容的格式如下:

eventmask:eventclass:description

ruby 复制代码
0x00000000:no:invalid class//无效的类别,通常不会使用
0x00000001:fr:file read//文件读取操作
0x00000002:fw:file write//文件写入操作
0x00000004:fa:file attribute access//访问文件属性
0x00000008:fm:file attribute modify//修改文件属性
0x00000010:fc:file create//创建文件
0x00000020:fd:file delete//删除文件
0x00000040:cl:file close//关闭文件
0x00000080:pc:process//进程操作
0x00000100:nt:network// 网络操作
0x00000200:ip:ipc//进程间通信
0x00000400:na:non attributable
0x00000800:ad:administrative
0x00001000:lo:login_logout//登录和注销操作
0x00002000:aa:authentication and authorization//认证和授权操作
0x00004000:ap:application//应用程序操作
0x10000000:res://保留供内部使用
0x20000000:io:ioctl//输入/输出控制操作
0x40000000:ex:exec 
0x80000000:ot:miscellaneous//其他杂项操作
0xffffffff:all:all flags set

1.3.3 audit_event

audit_event文件包含系统中可审计事件的描述。每行将审计事件号映射到名称、描述和类,格式如下:

eventnum:eventname:description:eventclass

ruby 复制代码
1:AUE_EXIT:exit(2):pc//进程AUE_EXIT事件
2:AUE_FORK:fork(2):pc//进程AUE_FORK事件
3:AUE_OPEN:open(2) - attr only:fa//访问文件属性对应的AUE_OPEN事件
4:AUE_CREAT:creat(2):fc//文件创建事件
5:AUE_LINK:link(2):fc//文件link事件
6:AUE_UNLINK:unlink(2):fd//文件删除类别中的unlink事件
.....
 

1.3.4 audit_user

管理员可以针对不同的用户,设定不同的审计级别,内容格式

username:alwaysaudit:neveraudit

perl 复制代码
root:lo:no //root用户执行登录和注销操作审计,不执行no(无效的类别,通常不会使用)

1.3.5 audit_warn

管理员可以设定当审计警告出现时运行的脚本程序。

ini 复制代码
argument=""
willsleep=0
type=$1
shift

while [ $# -ge 1 ]; do
	case $1 in
	--will-sleep) willsleep=1 ;;
	*) argument=$1 ;;
	esac
	shift
done

# Don't log audit warning events when the system is about to sleep.
if [ $willsleep -eq 0 ]; then
	logger -p security.warning "audit warning: $type $argument"
fi

从内核态看审计

从内核的角度看,审计只不过是在系统调用的逻辑中穿插了一些宏的调用过程,下载darwin-xnu源码发现

如/bsd/dev/arm/systemcalls.c文件代码所示:

scss 复制代码
void unix_syscall(struct arm_saved_state * state,__unused thread_t thread_act,
	struct uthread * uthread,struct proc * proc)
{
	....
	AUDIT_SYSCALL_ENTER(code, proc, uthread);
	error = (*(callp->sy_call))(proc, &uthread->uu_arg[0], &(uthread->uu_rval[0]));
	AUDIT_SYSCALL_EXIT(code, proc, uthread, error);
  ...
}
 

在这段代码中调用了宏AUDIT_SYSCALL_ENTER和AUDIT_SYSCALL_EXIT,这些宏定义在/bsd/security/audit/audit.h文件中,当用的时候会调用AUDIT_ENABLED宏检查全局变量audit_enabled的值,以免在禁用审计的情况下产生任何开销。管理员可以通过 auditon系统调用(指定 A_SETCOND 命令)的方式修改这个变量的值。

scss 复制代码
#define AUDIT_SYSCALL_ENTER(args...)    do {                            \
	if (AUDIT_ENABLED()) {                                  \
	    audit_syscall_enter(args);                              \
	}                                                               \
} while (0)

/*
 * Wrap the audit_syscall_exit() function so that it is called only when
 * we have a audit record on the thread.  Audit records can persist after
 * auditing is disabled, so we don't just check audit_enabled here.
 */
#define AUDIT_SYSCALL_EXIT(code, proc, uthread, error)  do {            \
	  if (AUDIT_AUDITING(uthread->uu_ar))                     \
	        audit_syscall_exit(code, error, proc, uthread); \
} while (0)
  • AUDIT_SYSCALL_ENTER:调用 sysent 表中的一条 UNIX 系统调用之前调用这个宏。这个宏接受3个参数:系统调用代码(编号)、BSD 进程以及负责这个调用的线程对象。
  • AUDIT_ARG:在系统调用的实现内部调用。这个宏接受一个表示操作的参数,以及其他可变参数,其他参数具体取决于对应的系统调用。
  • AUDIT_SYSCALL_EXIT:在系统调用的实现之后立即被调用。参数和 ENTER 的参数一致,还接受一个系统调用的返回值

还有一些宏专门用于 Mach 陷阱的审计,但是仅限于 BSD 调用导致 Mach 调用的情况,而且只有部分Mach 陷阱才支持。

如果确实启用了审计,那么这些宏要么创建一个新的 kaudit_record(最终调用 audit_new),要么使用一条已有的审计记录(如果在 BSD 线程的 uu_ar 字段中能找到的话。在audit_syscall_exit函数中通过调用 audit_commit 将审计记录最终确定下来,然后 audit_commit 会将这条审计记录转移到一个 audit_q 队列中。一旦这条记录进入了队列,线程的 uu_ar 字段就会被重置。除了将记录放在 audit_q 中之外,audit_commit 还会向一个条件变量 audit_worker_cv 发信号。

scss 复制代码
void
audit_commit(struct kaudit_record *ar, int error, int retval)
{
  ...
  TAILQ_INSERT_TAIL(&audit_q, ar, k_q);
	...
	cv_signal(&audit_worker_cv);
}

这样可以唤醒一个专用的审计工作线程,这个线程会调用 audit_worker_process_record 对审计记录进行处理,而这个函数会调用 kaudit_to_bsm,将审计记录转换为 OpenBSM 兼容的格式。这种记录可以直接写入(通过内核)审计文件,提交到审计管道,而且从 Lion 开始,还可以写入审计会话设备(通过 audit_session.c 文件中定义的audit_sdev_submit 函数.然后这条记录被释放,如下方示例代码所示:

scss 复制代码
/*
* 给定一条内核审计记录,根据要求对其进行处理。根据是否还存在一条用户审计记录,
* 内核审计记录被转换为一条或两条BSM记录。内核记录必须被转换为BSD之后才能被写出
*到其他地方。两种类型都会被写入磁盘和审计管道
*/
static void audit _worker_process_record(struct kaudit_record *ar)
{
  //转为OpenBSM兼容的格式
  error = kaudit_to_bsm(ar, &bsm);
  switch (error){
    ///基本所有的错误都会跳到out
  }
 
  //直接写入文件。audit_vp 是审计文件的vnode
  if (ar->k_ar_commit & AR_PRESELECT_TRAIL) {
		AUDIT_WORKER_SX_ASSERT();   
		audit_record_write(audit_vp, &audit_ctx, bsm->data, bsm->len);
	}
  
  //发送到任何/dev/auditpipe 实例
  if (ar->k_ar_commit & AR_PRESELECT_PIPE) {
		audit_pipe_submit(auid, event, class, sorf,
		    ar->k_ar_commit & AR_PRESELECT_TRAIL, bsm->data,
		    bsm->len);
	}

  //发送到任何/dev/auditsessions 设备实例(Lion 新引入)
  if (ar->k_ar_commit & AR_PRESELECT_FILTER) {
		/*
		 *  XXXss - 需要一般化,以便可以方便地插入新的过滤器
		 */
		audit_sdev_submit(auid, ar->k_ar.ar_subj_asid, bsm->data,
		    bsm->len);
	} 
  kau_free(bsm) ;
out:
  if (trail_locked) {
		AUDIT_WORKER_SX_XUNLOCK();
	}
}

audit_vp是内核代码直接写入文件的有趣实例,不需要用户态的干预。这是一个必要的捷径,因为审计具有安全敏感的本质。

在用户态如何使用审计

在用户态我们可以通过ioctl函数控制/dev/auditpipe管道的行为,然后通过au_read_rec读取管道里面的审计纪录进行安全分析。

  • 获取/dev/auditpipe文件描述符
  • 通过ioctl控制管道行为
  • 读取分析审计日志

打印进程类别事件示例代码如下:

ini 复制代码
//
//  main.m
//  auditDemo
//
//  Created by xxx on 2024/1/18.
//

#import <Foundation/Foundation.h>
#import <bsm/libbsm.h>
#import <sys/ioctl.h>
#import <security/audit/audit_ioctl.h>

//配置audit pipe
int configAuditPipe(FILE *auditFile) {
    if (auditFile == NULL) {
        return -1;
    }
    int auditFileDescriptor = fileno(auditFile);
    //1.设置审计策略flags
    u_int auditFlags = 0x00000080;
    int ioctlResult;
    ioctlResult = ioctl(auditFileDescriptor,AUDITPIPE_SET_PRESELECT_FLAGS, &auditFlags);
    if (ioctlResult == -1) {
        return -1;
    }    
    //2.设置审计操作模式
    int auditModel = AUDITPIPE_PRESELECT_MODE_LOCAL;
    ioctlResult = ioctl(auditFileDescriptor, AUDITPIPE_SET_PRESELECT_MODE,&auditModel);
    
    //3.设置审计管道队列限制
    int max_queue_count;
    ioctlResult = ioctl(auditFileDescriptor, AUDITPIPE_GET_QLIMIT_MAX,&max_queue_count);
    if (ioctlResult == -1) {
        return -1;
    }
    NSLog(@"max_queue_count:%d",max_queue_count);
    ioctlResult = ioctl(auditFileDescriptor, AUDITPIPE_SET_QLIMIT,&max_queue_count);
    return 0;
}
int readPipe(FILE *auditFile) {
    if (auditFile == NULL) {
        return -1;
    }
    //1.读取审计纪录
    u_char *buffer = NULL;
    int recordLen = 0;
    int bytesread = 0;
    tokenstr_t token = {0};
    while ((recordLen = au_read_rec(auditFile, &buffer)) != -1) {
        bytesread = 0;
        while (bytesread < recordLen) {
            if (-1 == au_fetch_tok(&token, buffer + bytesread, recordLen - bytesread)){
                break;
            }
            bytesread += token.len;
             //2.打印审计日志
            au_print_tok_xml(stdout, &token, ",", 0, 0);
            printf("\n");
        }
        free(buffer);
        fflush(stdout);
    }
    return 0;
}
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        NSLog(@"Hello, World!");
    
        //1.获取审计管道文件句柄
        char *auditPipe = "/dev/auditpipe";
        FILE *auditFile = fopen(auditPipe, "r");
        if (auditFile != NULL) {
            //2.配置审计管道
            int result = configAuditPipe(auditFile);
            if (result == -1) {
                return -1;
            }
           
            //3.读取管道审计日记
            readPipe(auditFile);
             
        }
    }
    CFRunLoopRun();
    return 0;
}

代码输出如下:

ini 复制代码
max_queue_count:1024
<record version="11" event="pid_for_task()" modifier="0" time="Thu Jan 18 22:28:30 2024" msec=" + 723 msec" >
<argument arg-num="1" value="0x203" desc="port" />
<argument arg-num="2" value="0xefd9" desc="pid" />
<subject audit-uid="wzf" uid="wzf" gid="staff" ruid="wzf" rgid="staff" pid="61401" sid="100003" tid="50331650 0.0.0.0" />
<return errval="success" retval="0" />
</record>
<record version="11" event="pid_for_task()" modifier="0" time="Thu Jan 18 22:28:30 2024" msec=" + 723 msec" >
<argument arg-num="1" value="0x203" desc="port" />
<argument arg-num="2" value="0xefd9" desc="pid" />
<subject audit-uid="wzf" uid="wzf" gid="staff" ruid="wzf" rgid="staff" pid="61401" sid="100003" tid="50331650 0.0.0.0" />
<return errval="success" retval="0" />
</record>
<record version="11" event="pid_for_task()" modifier="0" time="Thu Jan 18 22:28:30 2024" msec=" + 723 msec" >
<argument arg-num="1" value="0x203" desc="port" />
<argument arg-num="2" value="0xefd9" desc="pid" />
<subject audit-uid="wzf" uid="wzf" gid="staff" ruid="wzf" rgid="staff" pid="61401" sid="100003" tid="50331650 0.0.0.0" />
<return errval="success" retval="0" />
</record>
相关推荐
原住民的自修室6 小时前
Mac 3大好用的复制粘贴管理工具对比
macos·paste·pastenow·maccy
原住民的自修室13 小时前
mac 电脑如何打开剪切板
macos·历史记录·复制粘贴·剪切板
闲人一小枚13 小时前
mac u盘重装mac10.15Catalina系统
macos
fenglllle19 小时前
macOS 15.4.1 Chrome不能访问本地网络
chrome·macos
学渣676561 天前
venv和pyenv在mac上
macos
SZ1701102312 天前
介质访问控制(MAC)
网络·macos
蓉妹妹2 天前
Mac电脑,idea突然文件都展示成了文本格式,导致ts,tsx文件都不能正常加载或提示异常,解决方案详细说明如下
macos·intellij-idea
Tassel_YUE2 天前
VMware Fusion安装win11 arm;使用Mac远程连接到Win
arm开发·macos
@PHARAOH2 天前
HOW - 在 Mac 上的 Chrome 浏览器中调试 Windows 场景下的前端页面
前端·chrome·macos
海尔辛3 天前
学习黑客 MAC 地址深入了解
学习·macos·php