scp.go 9.59 KB
Newer Older
1
2
3
4
package logic

import (
	"encoding/json"
liming6's avatar
liming6 committed
5
	"errors"
6
	"fmt"
liming6's avatar
liming6 committed
7
8
	"log"
	"slices"
9
10
11
	"strconv"
	"strings"
	"sync"
liming6's avatar
liming6 committed
12
	"time"
13

liming6's avatar
liming6 committed
14
	"github.com/elastic/go-libaudit/v2"
15
16
	"github.com/elastic/go-libaudit/v2/aucoalesce"
	"github.com/elastic/go-libaudit/v2/auparse"
liming6's avatar
liming6 committed
17
	"github.com/shirou/gopsutil/v4/process"
18
19
20
21
)

var (
	// 接收审计事件的管道
liming6's avatar
liming6 committed
22
	EventChan = make(chan *aucoalesce.Event, 8192)
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

	// 	记录需要注意的系统调用
	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,
	}

liming6's avatar
liming6 committed
38
	SCP = "/usr/bin/scp"
39

liming6's avatar
liming6 committed
40
41
	EventMap     = make(map[string]*EventSet)
	EventMapLock = sync.RWMutex{} // 保护EventMap的锁
42

liming6's avatar
liming6 committed
43
	ErrArgNil = errors.New("error arg is nil")
44
45
46
47
48
49
50
51
52
53
54
)

type FileAction uint8

const (
	FA_Open FileAction = iota
	FA_Close
	FA_Rename
	FA_Delete
)

liming6's avatar
liming6 committed
55
56
57
58
59
60
61
62
63
64
65
66
67
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)
	}
68
69
}

liming6's avatar
liming6 committed
70
71
72
73
74
75
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)
76
	}
liming6's avatar
liming6 committed
77
78
79
80
81
82
83
84
85
86
87
88
89
	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
90
	}
liming6's avatar
liming6 committed
91
	defer cli.Close()
92

liming6's avatar
liming6 committed
93
94
95
96
97
	handler := &EventHandler{}
	rea, err := libaudit.NewReassembler(8192, time.Second*180, handler)
	if err != nil {
		log.Printf("%v", err)
		return
98
	}
liming6's avatar
liming6 committed
99
100
101
102
103
104
105
106
107
108
109
110
111
112
	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()
113

liming6's avatar
liming6 committed
114
115
116
117
118
119
120
121
122
123
124
	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)
125
126
127
128
}

// EventSet 事件集,一个事件集记录了同一个进程的审计事件
type EventSet struct {
liming6's avatar
liming6 committed
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
	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
278
279
}

liming6's avatar
liming6 committed
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
// 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() {
317
	for i := range EventChan {
liming6's avatar
liming6 committed
318
		if i.Process.Exe != SCP {
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
			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
		}
liming6's avatar
liming6 committed
342
343
		// printEvent(i)
		PushEvent(sc, i)
344
345
346
347
348
349
350
351
352
	}
}

func printEvent(e *aucoalesce.Event) {
	if e == nil {
		return
	}
	switch e.Data["syscall"] {
	case "open", "openat":
liming6's avatar
liming6 committed
353
354
355
		fd, _ := getExitFd(e)
		fp, _ := getFilePath(e)
		log.Printf("open(at): pid: %s ,fd: %d, file: %s \n", e.Process.PID, *fd, *fp)
356
	case "close":
liming6's avatar
liming6 committed
357
		log.Printf("close: pid: %s ,fd: %s \n", e.Process.PID, e.Data["a0"])
358
	default:
liming6's avatar
liming6 committed
359
		log.Println("unknown syscall: ", e.Data["syscall"])
360
	}
liming6's avatar
liming6 committed
361
362
363
364
	// bb, err := json.MarshalIndent(e, "", "  ")
	// if err == nil {
	// 	fmt.Println(string(bb))
	// }
365
366
367
368
369
370
371
372
373
374
375
376
377
}

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进制数
liming6's avatar
liming6 committed
378
		n, err := strconv.ParseInt(str, 10, 64)
379
380
381
		if err != nil {
			return 0, err
		}
liming6's avatar
liming6 committed
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
		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
		}
445
	}
liming6's avatar
liming6 committed
446
	return nil, nil
447
}