package logic import ( "encoding/json" "errors" "fmt" "log" "slices" "strconv" "strings" "sync" "time" "github.com/elastic/go-libaudit/v2" "github.com/elastic/go-libaudit/v2/aucoalesce" "github.com/elastic/go-libaudit/v2/auparse" "github.com/shirou/gopsutil/v4/process" ) var ( // 接收审计事件的管道 EventChan = make(chan *aucoalesce.Event, 8192) // 记录需要注意的系统调用 Syscalls = map[string]FileAction{ "open": FA_Open, "openat": FA_Open, "close": FA_Close, "rename": FA_Rename, "renameat": FA_Rename, "renameat2": FA_Rename, "link": FA_Rename, "unlink": FA_Rename, "linkat": FA_Rename, "unlinkat": FA_Rename, } SCP = "/usr/bin/scp" EventMap = make(map[string]*EventSet) EventMapLock = sync.RWMutex{} // 保护EventMap的锁 ErrArgNil = errors.New("error arg is nil") ) type FileAction uint8 const ( FA_Open FileAction = iota FA_Close FA_Rename FA_Delete ) func CleanSCP() { EventMapLock.Lock() defer EventMapLock.Unlock() toDelete := make([]string, len(EventMap)) for k, v := range EventMap { alive, _ := v.IsAlive() if !alive { toDelete = append(toDelete, k) } } for _, v := range toDelete { delete(EventMap, v) } } type EventHandler struct{} func (h *EventHandler) ReassemblyComplete(msgs []*auparse.AuditMessage) { event, err := aucoalesce.CoalesceMessages(msgs) if err != nil { log.Printf("coalesce messages error: %v \n", err) } EventChan <- event } func (h *EventHandler) EventsLost(count int) { log.Printf("event lost: %d \n", count) } func StartAuditMonitor(wg *sync.WaitGroup) { defer wg.Done() cli, err := libaudit.NewMulticastAuditClient(nil) if err != nil { log.Printf("failed to create audit client: %v \n", err) return } defer cli.Close() handler := &EventHandler{} rea, err := libaudit.NewReassembler(8192, time.Second*180, handler) if err != nil { log.Printf("%v", err) return } defer rea.Close() // 定期维护事件池 go func() { ticker := time.NewTicker(time.Second * 15) defer ticker.Stop() for range ticker.C { if rea.Maintain() != nil { break } } }() go FiltAuditMsg() for { rawMsg, err := cli.Receive(false) if err != nil { break } err = rea.Push(rawMsg.Type, slices.Clone(rawMsg.Data)) if err != nil { log.Printf("push audit event error: %v\n", err) } } close(EventChan) } // EventSet 事件集,一个事件集记录了同一个进程的审计事件 type EventSet struct { Pid, PPID, Uid int32 // pid、ppid和Uid Events []*aucoalesce.Event // 记录所有事件 Fds map[int64][]*aucoalesce.Event // 记录可以获取文件描述符的事件 Lock sync.RWMutex // 锁,用于保护Fds和Events的读写 } // PushEvent 向事件集中插入事件,并判断是否需要触发文件扫描 func (es *EventSet) PushEvent(event *aucoalesce.Event, syscall string) { if event == nil { return } switch syscall { case "open", "openat": fd, err := getExitFd(event) if err != nil { log.Println("error: ", err) return } if fd == nil { log.Println("error: open(at) syscall, but can't get fd") return } es.Lock.Lock() el, have := es.Fds[*fd] if have && len(el) > 0 { // 已经用了这个fd,有问题,要么是没有删除旧fd,要么是有bug bb, _ := json.Marshal(el) cc, _ := json.Marshal(event) log.Println("error: fd repeat, old: ", string(bb), "new: ", string(cc)) clear(el) el = append(el, event) es.Events = append(es.Events, event) es.Lock.Unlock() } else { // ok,正常的 el = make([]*aucoalesce.Event, 0, 16) el = append(el, event) es.Fds[*fd] = el es.Events = append(es.Events, event) es.Lock.Unlock() } case "close": // 获取close的第一个参数,那就是fd if event.Data == nil { return } fdStr, have := event.Data["a0"] if !have { log.Println("error: syscall close, but can't find the first arg") return } fd, err := ParseInt(fdStr) if err != nil { log.Println("error: ", err) return } es.Lock.Lock() // 判断fd是否存在,存在就扫描文件 es.Events = append(es.Events, event) el, have := es.Fds[fd] if have { delete(es.Fds, fd) es.Lock.Unlock() l := len(el) if l == 0 { // bug log.Println("error: fds slices's length is 0") return } filePath, err := getFilePath(el[l-1]) if err != nil { log.Println("error: ", err) return } if filePath == nil { log.Println("error: find file path is nil") return } go es.ScanFile(*filePath) } else { // 不存在就返回 es.Events = append(es.Events, event) es.Lock.Unlock() } } } func (es *EventSet) ScanFile(path string) { log.Printf("user UID(%d) upload file: %s, scanning...\n", es.Uid, path) v, _ := ScanFile(path) if v { log.Printf("user UID(%d) upload file %s containing viruses \n", es.Uid, path) } else { log.Printf("user UID(%d) upload file %s not find virus \n", es.Uid, path) } } // NewEventSet 从open、openat系统调用创建事件集 func NewEventSet(event *aucoalesce.Event, _ string) (*EventSet, error) { info, err := getBaseInfo(event) if err != nil { return nil, err } result := EventSet{ Events: make([]*aucoalesce.Event, 0, 16), Fds: make(map[int64][]*aucoalesce.Event), Lock: sync.RWMutex{}, } result.Events = append(result.Events, event) if info[0] != nil { result.Pid = *info[0] } if info[1] != nil { result.PPID = *info[1] } if info[2] != nil { result.Uid = *info[2] } if event.Data != nil { exit, have := event.Data["exit"] if !have { return nil, errors.New("create file syscall, but not find exit code") } fd, err := ParseInt(exit) if err != nil { return nil, err } el := make([]*aucoalesce.Event, 0, 16) el = append(el, event) result.Fds[fd] = el } return &result, nil } // CheckAlive 检查进程是否存活 func (es *EventSet) IsAlive() (bool, error) { p, err := process.NewProcess(es.Pid) if err != nil { // 进程已经结束 return false, nil } ppid, err := p.Ppid() if err != nil { return false, nil } if ppid == es.PPID { return true, nil } return false, nil } // PushEvent 向EventMap中放入事件 func PushEvent(syscall string, event *aucoalesce.Event) { if event == nil { return } key := genKey(event) switch syscall { case "open", "openat": // 新建文件 EventMapLock.Lock() es, have := EventMap[key] if !have { ies, err := NewEventSet(event, syscall) if err != nil { log.Println(err) EventMapLock.Unlock() return } es = ies EventMap[key] = es } EventMapLock.Unlock() case "close": // 关闭文件 EventMapLock.Lock() es, have := EventMap[key] if !have { EventMapLock.Unlock() return } EventMapLock.Unlock() es.PushEvent(event, syscall) } } // FiltAuditMsg 过滤出有用的事件,存放到map中,并触发处理程序 func FiltAuditMsg() { for i := range EventChan { if i.Process.Exe != SCP { continue } if i.Category != aucoalesce.EventTypeAuditRule { // 不是由审计规则触发的事件跳过 continue } if i.Type != auparse.AUDIT_SYSCALL { // 不是系统调用,跳过 continue } sc, have := i.Data["syscall"] if !have { continue } // 检查是不是需要关注的系统调用 _, have = Syscalls[sc] if !have { continue } // 失败 if i.Result != "success" { continue } // printEvent(i) PushEvent(sc, i) } } func printEvent(e *aucoalesce.Event) { if e == nil { return } switch e.Data["syscall"] { case "open", "openat": fd, _ := getExitFd(e) fp, _ := getFilePath(e) log.Printf("open(at): pid: %s ,fd: %d, file: %s \n", e.Process.PID, *fd, *fp) case "close": log.Printf("close: pid: %s ,fd: %s \n", e.Process.PID, e.Data["a0"]) default: log.Println("unknown syscall: ", e.Data["syscall"]) } // bb, err := json.MarshalIndent(e, "", " ") // if err == nil { // fmt.Println(string(bb)) // } } func ParseInt(str string) (int64, error) { if strings.ContainsAny(str, "abcdefABCDEFxX") { // 是16进制数 s := strings.ToLower(str) if strings.HasPrefix(s, "0x") { return strconv.ParseInt(s, 0, 64) } else { return strconv.ParseInt(fmt.Sprintf("0x%s", s), 0, 64) } } else { // 不是16进制数 n, err := strconv.ParseInt(str, 10, 64) if err != nil { return 0, err } return n, err } } // genKey 为事件生成字符串id,格式为 pid-ppid func genKey(event *aucoalesce.Event) string { return fmt.Sprintf("%s-%s", event.Process.PID, event.Process.PPID) } // getBaseInfo 获取事件的基本信息,pid、ppid、uid func getBaseInfo(event *aucoalesce.Event) ([3]*int32, error) { result := [3]*int32{nil, nil, nil} if event == nil { return result, ErrArgNil } var pid, ppid, uid int32 if event.User.IDs != nil { u, have := event.User.IDs["uid"] if have { uu, err := ParseInt(u) if err == nil { uid = int32(uu) result[2] = &uid } } } p, err := ParseInt(event.Process.PID) if err == nil { pid = int32(p) result[0] = &pid } p, err = ParseInt(event.Process.PPID) if err == nil { ppid = int32(p) result[0] = &ppid } return result, nil } func getFilePath(event *aucoalesce.Event) (*string, error) { if event == nil { return nil, ErrArgNil } if event.File == nil { return nil, nil } return &event.File.Path, nil } // getExitFd 获取系统调用的返回值 func getExitFd(event *aucoalesce.Event) (*int64, error) { if event == nil { return nil, ErrArgNil } if event.Data != nil { exit, have := event.Data["exit"] if have { e, err := ParseInt(exit) if err != nil { return nil, err } return &e, nil } } return nil, nil }