summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorWe-unite <3205135446@qq.com>2024-07-18 14:28:52 +0800
committerWe-unite <3205135446@qq.com>2024-07-18 18:59:19 +0800
commit58b1af7139bd2c4a2682382261fcc545b86d8685 (patch)
treeabf0092fc69aa899935dcc08bf886dac5deb12cb
parentf055b3940f999c2e26448812e67b68da363dcbad (diff)
downloadgodo-58b1af7139bd2c4a2682382261fcc545b86d8685.tar.gz
godo-58b1af7139bd2c4a2682382261fcc545b86d8685.zip
Mainly finish the second coroutine, organize event
As is planed, the first coroutine throw rae event infomation to the second, and it organizes all info for the same event accroding to event id, which is unique without shutdown of this computer. There's several defficuties I've encountered, so I list their solution here to remeber: - raw info from 1st coroutine is correct, but wrong when 2nd gets it; or it's correct while recieved, then regular expr goes to match it, the first match is inline with expectations, but the next match goes totally wrong, and the info is different from what is received. Look into the src of go-libaudit, we'll find out that when heard from netlink socket, the read buffer is always the same slice, it first received a long data, then **pass the origin slice to rawEvent.Data**, and then received a shorter data. rawEvent.Data is passed to 2nd coruntine as **a pointer to rawEvent**, which means all this 3 process use the same part of memory. Then, when a shorter info comes from socket, the slice won't be moved, otherwise it write aigin to this part of mem, then coroutine 2 will get a dirty data. To deal with it, we change the type of channel from pointer to interface, and make a deep copy of rawEvent before passing down. As a result, the 2nd coroutine gets a copy of message but not origin, it finally comes right. - While designing a regular expr, it's thought correct but miss matched from the right string. There maybe sth wrong that can't be discovered by people's eye, you can try to rewrite the expr, then it may be fixed. Also, there's some hidden dangers: - 2nd coroutine comes with no error checks althouth err variable is set and catched ubder the rules of compiler. we **shall** make it later. - Is it reasonable to pass cooked event info immediately to 3rd coroutine without waiting some time? Info from network is out of order after all. Fight! Fight! Fight!
-rw-r--r--.gitignore3
-rw-r--r--go.mod1
-rw-r--r--go.sum2
-rw-r--r--godo.go261
4 files changed, 262 insertions, 5 deletions
diff --git a/.gitignore b/.gitignore
index 6cff91d..8bbe3e4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
1.vscode/* 1.vscode/*
2godo \ No newline at end of file 2godo
3
diff --git a/go.mod b/go.mod
index c73fc4f..2969b32 100644
--- a/go.mod
+++ b/go.mod
@@ -4,6 +4,7 @@ go 1.21.5
4 4
5require ( 5require (
6 github.com/elastic/go-libaudit/v2 v2.5.0 6 github.com/elastic/go-libaudit/v2 v2.5.0
7 github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826
7 gopkg.in/yaml.v3 v3.0.1 8 gopkg.in/yaml.v3 v3.0.1
8) 9)
9 10
diff --git a/go.sum b/go.sum
index 6880c39..7ce498a 100644
--- a/go.sum
+++ b/go.sum
@@ -7,6 +7,8 @@ github.com/elastic/go-licenser v0.4.1 h1:1xDURsc8pL5zYT9R29425J3vkHdt4RT5TNEMeRN
7github.com/elastic/go-licenser v0.4.1/go.mod h1:V56wHMpmdURfibNBggaSBfqgPxyT1Tldns1i87iTEvU= 7github.com/elastic/go-licenser v0.4.1/go.mod h1:V56wHMpmdURfibNBggaSBfqgPxyT1Tldns1i87iTEvU=
8github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= 8github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
9github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= 9github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
10github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
11github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
10github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 12github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
11github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 13github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
12github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 14github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
diff --git a/godo.go b/godo.go
index 6b6f48f..2ba97d1 100644
--- a/godo.go
+++ b/godo.go
@@ -9,12 +9,15 @@ import (
9 "os" 9 "os"
10 "os/exec" 10 "os/exec"
11 "path/filepath" 11 "path/filepath"
12 "regexp"
12 "strconv" 13 "strconv"
13 "strings" 14 "strings"
14 "sync" 15 "sync"
16 "time"
15 17
16 "github.com/elastic/go-libaudit/v2" 18 "github.com/elastic/go-libaudit/v2"
17 "github.com/elastic/go-libaudit/v2/auparse" 19 "github.com/elastic/go-libaudit/v2/auparse"
20 "github.com/mohae/deepcopy"
18) 21)
19 22
20var ( 23var (
@@ -26,8 +29,19 @@ var (
26 receiveOnly = fs.Bool("ro", false, "receive only using multicast, requires kernel 3.16+") 29 receiveOnly = fs.Bool("ro", false, "receive only using multicast, requires kernel 3.16+")
27) 30)
28 31
32type Event struct {
33 timestamp time.Time
34 pid, ppid int
35 syscall int
36 argc int
37 args []string
38 cwd string
39}
40
29type process struct { 41type process struct {
30 cmdline string 42 cmdline string
43 argv []string
44 cwd string
31 rootfs string 45 rootfs string
32 children []int 46 children []int
33} 47}
@@ -35,6 +49,10 @@ type process struct {
35var pids map[int]*process //古希腊掌管进程的神 49var pids map[int]*process //古希腊掌管进程的神
36var containers map[string]int // 古希腊掌管容器的神 50var containers map[string]int // 古希腊掌管容器的神
37var wg sync.WaitGroup // 掌管协程 51var wg sync.WaitGroup // 掌管协程
52var rawChan chan interface{} // 从接收到整理的管道,这里不是原始数据类型,下文解释
53var cookedChan chan Event // 整理好的信息的管道
54
55var syscallTable [500]string //记录一下系统调用
38 56
39func main() { 57func main() {
40 // 检查用户身份,并添加auditd规则,监听所有syscall 58 // 检查用户身份,并添加auditd规则,监听所有syscall
@@ -42,7 +60,14 @@ func main() {
42 fmt.Printf("Err: Please run me as root, %d!\n", os.Getegid()) 60 fmt.Printf("Err: Please run me as root, %d!\n", os.Getegid())
43 return 61 return
44 } 62 }
45 syscall := [5]string{"fork", "vfork", "execve", "exit", "exit_group"} 63
64 // 所有的系统调用号与名称的关系
65 err := figureOutSyscalls()
66 if err != nil {
67 fmt.Printf("Error figuring out syscall numbers: %v\n", err)
68 }
69
70 syscall := [6]string{"fork", "vfork", "clone", "execve", "exit", "exit_group"}
46 var auditCmd *exec.Cmd 71 var auditCmd *exec.Cmd
47 auditCmd = exec.Command("auditctl", "-D") // 清空所有规则 72 auditCmd = exec.Command("auditctl", "-D") // 清空所有规则
48 auditCmd.Run() 73 auditCmd.Run()
@@ -58,9 +83,14 @@ func main() {
58 fmt.Printf("Error finding containerd: %v\n", err) 83 fmt.Printf("Error finding containerd: %v\n", err)
59 return 84 return
60 } 85 }
61 86 // 数据结构初始化
62 pids = make(map[int]*process) 87 pids = make(map[int]*process)
63 containers = make(map[string]int) 88 containers = make(map[string]int)
89
90 // 创世之神,1号进程
91 pids[1] = &process{rootfs: "/", children: make([]int, 0)}
92 pids[1].children = append(pids[1].children, containerdPid)
93 // /usr/bin/containerd,也就是我们最关注的进程
64 pids[containerdPid] = &process{cmdline: "/usr/bin/cmdline", rootfs: "/", children: make([]int, 0)} 94 pids[containerdPid] = &process{cmdline: "/usr/bin/cmdline", rootfs: "/", children: make([]int, 0)}
65 95
66 // 开始运行,解析命令行参数后监听 96 // 开始运行,解析命令行参数后监听
@@ -73,6 +103,29 @@ func main() {
73 } 103 }
74} 104}
75 105
106func figureOutSyscalls() error {
107 NRRegex := regexp.MustCompile(`#define __NR_(.*?) (\d+)$`)
108 file, err := os.Open("/usr/include/asm/unistd_64.h")
109 if err != nil {
110 return err
111 }
112 defer file.Close()
113
114 scanner := bufio.NewScanner(file)
115 for scanner.Scan() {
116 line := scanner.Text()
117 if NRRegex.MatchString(line) {
118 match := NRRegex.FindStringSubmatch(line)
119 num, err := strconv.Atoi(match[2])
120 if err != nil {
121 return err
122 }
123 syscallTable[num] = match[1]
124 }
125 }
126 return nil
127}
128
76func getPid() (int, error) { 129func getPid() (int, error) {
77 // 指定要搜索的关键词 130 // 指定要搜索的关键词
78 keyword := "/usr/bin/containerd" 131 keyword := "/usr/bin/containerd"
@@ -122,6 +175,37 @@ func containsKeyword(pid int, keyword string) bool {
122 return false 175 return false
123} 176}
124 177
178func getTimeFromStr(timeStr string) (time.Time, error) {
179 timestampFloat, err := strconv.ParseFloat(timeStr, 64)
180 if err != nil {
181 return time.Unix(0, 0), err
182 }
183 secs := int64(timestampFloat)
184 nsecs := int64((timestampFloat - float64(secs)) * 1e9)
185
186 // 只精确到毫秒就够了
187 t := time.Unix(secs, nsecs).Truncate(time.Millisecond)
188 return t, nil
189}
190
191func hexToAscii(hexString string) string {
192 bytes := []byte{}
193 for i := 0; i < len(hexString); i += 2 {
194 hexPair := hexString[i : i+2]
195 // 将十六进制数转换为十进制数
196 decimal, err := strconv.ParseInt(hexPair, 16, 8)
197 if err != nil {
198 return "Invalid hex string"
199 }
200 char := byte(decimal)
201 bytes = append(bytes, char)
202 }
203
204 asciiString := strings.ReplaceAll(string(bytes), "\000", " ")
205
206 return asciiString
207}
208
125func read() error { 209func read() error {
126 // Write netlink response to a file for further analysis or for writing 210 // Write netlink response to a file for further analysis or for writing
127 // tests cases. 211 // tests cases.
@@ -192,10 +276,25 @@ func read() error {
192 } 276 }
193 } 277 }
194 278
195 return receive(client) 279 // 各协程至此开始
280 // return receive(client)
281 rawChan = make(chan interface{})
282 cookedChan = make(chan Event)
283 wg.Add(1)
284 go receive(client)
285 wg.Add(1)
286 go orgnaze()
287 wg.Add(1)
288 go deal()
289
290 wg.Wait()
291 time.Sleep(2 * time.Second)
292 return nil
196} 293}
197 294
198func receive(r *libaudit.AuditClient) error { 295func receive(r *libaudit.AuditClient) error {
296 defer wg.Done()
297 defer close(rawChan)
199 for { 298 for {
200 rawEvent, err := r.Receive(false) 299 rawEvent, err := r.Receive(false)
201 if err != nil { 300 if err != nil {
@@ -208,6 +307,160 @@ func receive(r *libaudit.AuditClient) error {
208 continue 307 continue
209 } 308 }
210 309
211 fmt.Printf("type=%v msg=%s\n", rawEvent.Type, rawEvent.Data) 310 rawEventMessage := deepcopy.Copy(*rawEvent)
311 rawChan <- rawEventMessage
312 }
313}
314
315func orgnaze() {
316 defer wg.Done()
317 defer close(cookedChan)
318 // 接收信息
319 var raw interface{}
320 var ok bool
321 var rawEvent libaudit.RawAuditMessage
322 // 事件信息
323 var eventId, argc int
324 var err [6]error
325 var event, cooked Event
326 // 为每个事务id存储其信息,事务id在操作系统运行期间是唯一的
327 eventTable := make(map[int]*Event)
328 // 要用的正则匹配列表
329 syscallRegex := regexp.MustCompile(`audit\((\d+\.\d+):(\d+)\).*?syscall=(\d+).*?ppid=(\d+) pid=(\d+).*?$`)
330 execveRegex := regexp.MustCompile(`audit\(\d+\.\d+:(\d+)\): argc=(\d+)`)
331 argsRegex := regexp.MustCompile(`a\d+=("(.*?)"|([0-9a-fA-F]+))`)
332 cwdRegex := regexp.MustCompile(`audit\(\d+\.\d+:(\d+)\): cwd="(.*?)"`)
333 proctitleRegex := regexp.MustCompile(`audit\(\d+\.\d+:(\d+)\): proctitle=("(.*?)"|([0-9a-fA-F]+))$`)
334 eoeRegex := regexp.MustCompile(`audit\(\d+\.\d+:(\d+)\)`)
335 for {
336 raw, ok = <-rawChan
337 if !ok {
338 break
339 }
340 rawEvent = raw.(libaudit.RawAuditMessage)
341
342 // type Event struct {
343 // timestamp time.Time
344 // pid, ppid int
345 // syscall int
346 // argc int
347 // args []string
348 // cwd string
349 // }
350 switch rawEvent.Type {
351 case auparse.AUDIT_SYSCALL:
352 if syscallRegex.Match(rawEvent.Data) {
353 match := syscallRegex.FindSubmatch(rawEvent.Data)
354 event.timestamp, err[0] = getTimeFromStr(string(match[1]))
355 eventId, err[1] = strconv.Atoi(string(match[2]))
356 event.syscall, err[2] = strconv.Atoi(string(match[3]))
357 event.ppid, err[3] = strconv.Atoi(string(match[4]))
358 event.pid, err[4] = strconv.Atoi(string(match[5]))
359 eventTable[eventId] = &Event{
360 timestamp: event.timestamp,
361 syscall: event.syscall,
362 ppid: event.ppid,
363 pid: event.pid,
364 argc: 0,
365 args: make([]string, 0),
366 cwd: "",
367 }
368 }
369 case auparse.AUDIT_EXECVE:
370 if execveRegex.Match(rawEvent.Data) {
371 match := execveRegex.FindSubmatch(rawEvent.Data)
372 eventId, err[0] = strconv.Atoi(string(match[1]))
373 argc, err[1] = strconv.Atoi(string(match[2]))
374 if err[0] == nil && err[1] == nil && argsRegex.Match(rawEvent.Data) {
375 match := argsRegex.FindAllSubmatch(rawEvent.Data, -1)
376 for i := 0; i < argc; i++ {
377 if len(match[i][2]) == 0 {
378 // 代表着匹配到的是十六进制数
379 str := hexToAscii(string(match[i][3]))
380 eventTable[eventId].args = append(eventTable[eventId].args, str)
381 fmt.Printf("Origin: \"%s\", Res: \"%s\"\n", match[i][3], str)
382 } else {
383 eventTable[eventId].args = append(eventTable[eventId].args, string(match[i][2]))
384 }
385 }
386 eventTable[eventId].argc = argc
387 }
388 }
389 // case auparse.AUDIT_PATH:
390 case auparse.AUDIT_CWD:
391 if cwdRegex.Match(rawEvent.Data) {
392 match := cwdRegex.FindSubmatch(rawEvent.Data)
393 eventId, err[0] = strconv.Atoi(string(match[1]))
394 eventTable[eventId].cwd = string(match[2])
395 }
396 case auparse.AUDIT_PROCTITLE:
397 if proctitleRegex.Match(rawEvent.Data) {
398 var cmdline string
399 var pEvent *Event
400 match := proctitleRegex.FindSubmatch(rawEvent.Data)
401 eventId, err[0] = strconv.Atoi(string(match[1]))
402 pEvent = eventTable[eventId]
403 if pEvent.argc == 0 {
404 // 只有等于0,才证明没经过EXECVE提取参数,才允许使用PROCTITLE提取参数
405 if match[3] == nil {
406 // PROCTITLE写的是十六进制,转换为字符串
407 cmdline = hexToAscii(string(match[4]))
408 } else {
409 cmdline = string(match[3])
410 }
411 pEvent.args = strings.Split(cmdline, " ")
412 pEvent.argc = len(eventTable[eventId].args)
413 }
414 // 当读到proctitle的时候,而且是个新进程最好检查一下cwd,如果还为空,找proc
415 if pEvent.cwd == "" && (pEvent.syscall == 57 || pEvent.syscall == 58 || pEvent.syscall == 59) {
416 cwdFilePath := fmt.Sprintf("/proc/%d/cwd", pEvent.pid)
417 pEvent.cwd, err[1] = os.Readlink(cwdFilePath)
418 if err[1] != nil {
419 pEvent.cwd = ""
420 break
421 }
422 }
423 }
424 case auparse.AUDIT_EOE:
425 if eoeRegex.Match(rawEvent.Data) {
426 match := eoeRegex.FindSubmatch(rawEvent.Data)
427 eventId, err[0] = strconv.Atoi(string(match[1]))
428 // TODO: 事件整理完毕,即刻发出,是否合理呢?
429 cooked = *eventTable[eventId] // 应当采用深拷贝吗?有待实验
430 cookedChan <- cooked
431 delete(eventTable, eventId) //发出之后就从信息表扔掉,死人别占地
432 }
433 default:
434 // TODO: 这里也需要做防护
435 }
436 }
437}
438
439func deal() {
440 defer wg.Done()
441 var cooked Event
442 var ok bool
443 for {
444 cooked, ok = <-cookedChan
445 if !ok {
446 break
447 }
448 // type Event struct {
449 // timestamp time.Time
450 // pid, ppid int
451 // syscall int
452 // argc int
453 // args []string
454 // cwd string
455 // }
456 fmt.Printf("recv: %v syscall=%d, ppid=%d, pid=%d, cwd=\"%s\", argc=%d, ", cooked.timestamp, cooked.syscall, cooked.ppid, cooked.pid, cooked.cwd, cooked.argc)
457 if len(cooked.args) != cooked.argc {
458 fmt.Printf("Fuck!\n")
459 continue
460 }
461 for i := 0; i < cooked.argc; i++ {
462 fmt.Printf("arg[%d]=\"%s\", ", i, cooked.args[i])
463 }
464 fmt.Printf("\n")
212 } 465 }
213} 466}