diff options
author | We-unite <3205135446@qq.com> | 2024-07-18 14:28:52 +0800 |
---|---|---|
committer | We-unite <3205135446@qq.com> | 2024-07-18 18:59:19 +0800 |
commit | 58b1af7139bd2c4a2682382261fcc545b86d8685 (patch) | |
tree | abf0092fc69aa899935dcc08bf886dac5deb12cb | |
parent | f055b3940f999c2e26448812e67b68da363dcbad (diff) | |
download | godo-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-- | .gitignore | 3 | ||||
-rw-r--r-- | go.mod | 1 | ||||
-rw-r--r-- | go.sum | 2 | ||||
-rw-r--r-- | godo.go | 261 |
4 files changed, 262 insertions, 5 deletions
@@ -1,2 +1,3 @@ | |||
1 | .vscode/* | 1 | .vscode/* |
2 | godo \ No newline at end of file | 2 | godo |
3 | |||
@@ -4,6 +4,7 @@ go 1.21.5 | |||
4 | 4 | ||
5 | require ( | 5 | require ( |
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 | ||
@@ -7,6 +7,8 @@ github.com/elastic/go-licenser v0.4.1 h1:1xDURsc8pL5zYT9R29425J3vkHdt4RT5TNEMeRN | |||
7 | github.com/elastic/go-licenser v0.4.1/go.mod h1:V56wHMpmdURfibNBggaSBfqgPxyT1Tldns1i87iTEvU= | 7 | github.com/elastic/go-licenser v0.4.1/go.mod h1:V56wHMpmdURfibNBggaSBfqgPxyT1Tldns1i87iTEvU= |
8 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= | 8 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= |
9 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= | 9 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= |
10 | github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= | ||
11 | github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= | ||
10 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | 12 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= |
11 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | 13 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= |
12 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | 14 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= |
@@ -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 | ||
20 | var ( | 23 | var ( |
@@ -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 | ||
32 | type Event struct { | ||
33 | timestamp time.Time | ||
34 | pid, ppid int | ||
35 | syscall int | ||
36 | argc int | ||
37 | args []string | ||
38 | cwd string | ||
39 | } | ||
40 | |||
29 | type process struct { | 41 | type 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 { | |||
35 | var pids map[int]*process //古希腊掌管进程的神 | 49 | var pids map[int]*process //古希腊掌管进程的神 |
36 | var containers map[string]int // 古希腊掌管容器的神 | 50 | var containers map[string]int // 古希腊掌管容器的神 |
37 | var wg sync.WaitGroup // 掌管协程 | 51 | var wg sync.WaitGroup // 掌管协程 |
52 | var rawChan chan interface{} // 从接收到整理的管道,这里不是原始数据类型,下文解释 | ||
53 | var cookedChan chan Event // 整理好的信息的管道 | ||
54 | |||
55 | var syscallTable [500]string //记录一下系统调用 | ||
38 | 56 | ||
39 | func main() { | 57 | func 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 | ||
106 | func 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 | |||
76 | func getPid() (int, error) { | 129 | func 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 | ||
178 | func 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 | |||
191 | func 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 | |||
125 | func read() error { | 209 | func 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 | ||
198 | func receive(r *libaudit.AuditClient) error { | 295 | func 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 | |||
315 | func 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 | |||
439 | func 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 | } |