├── .gitignore ├── .header-asterisk ├── .header-hashsign ├── LICENSE ├── Makefile ├── README.md ├── aur ├── .gitignore ├── PKGBUILD.libxpid └── PKGBUILD.xpid ├── bpf ├── .gitignore ├── Makefile ├── README.md ├── probe.tracepoints.c ├── xpid-bpf.c └── xpid-bpf.h ├── cmd ├── main.go └── runtime.go ├── go.mod ├── go.sum ├── libxpid ├── .gitignore ├── CMakeLists.txt ├── README.md ├── configure ├── include │ └── xpid.h └── src │ ├── bpf.c │ ├── proc.c │ ├── proc.h │ └── proc_dir.c ├── meta.go ├── pkg ├── api │ └── v1 │ │ ├── interface.go │ │ ├── module.go │ │ ├── module_container.go │ │ ├── module_ebpf.go │ │ ├── module_namespace.go │ │ ├── module_proc.go │ │ └── process.go ├── encoders │ ├── encoder.go │ ├── json │ │ └── encoder.go │ ├── raw │ │ ├── encoder-color.go │ │ └── encoder.go │ └── table │ │ └── encoder.go ├── filters │ ├── filter.go │ ├── filter_containers.go │ ├── filter_ebpf.go │ ├── filter_empty.go │ ├── filter_hidden.go │ ├── filter_namespace_cgroup.go │ ├── filter_namespace_ipc.go │ ├── filter_namespace_mount.go │ ├── filter_namespace_net.go │ ├── filter_namespace_pid.go │ ├── filter_namespace_time.go │ ├── filter_namespace_uts.go │ └── filter_thread.go ├── libxpid │ ├── libxpid_bpf.go │ └── libxpid_proc_dir.go ├── procfs │ └── procfs.go └── procx │ ├── procx.go │ ├── procx_test.go │ ├── query.go │ ├── query_test.go │ └── system_linux.go └── release └── .gitkeep /.gitignore: -------------------------------------------------------------------------------- 1 | .idea* 2 | .tar.gz 3 | .o 4 | .ll 5 | nova 6 | xpid 7 | aur/* -------------------------------------------------------------------------------- /.header-asterisk: -------------------------------------------------------------------------------- 1 | /* ==========================================================================*\ 2 | * MIT License Copyright (c) 2022 Kris Nóva * 3 | * * 4 | * ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ * 5 | * ┃ ███╗ ██╗ ██████╗ ██╗ ██╗ █████╗ ┃ * 6 | * ┃ ████╗ ██║██╔═████╗██║ ██║██╔══██╗ ┃ * 7 | * ┃ ██╔██╗ ██║██║██╔██║██║ ██║███████║ ┃ * 8 | * ┃ ██║╚██╗██║████╔╝██║╚██╗ ██╔╝██╔══██║ ┃ * 9 | * ┃ ██║ ╚████║╚██████╔╝ ╚████╔╝ ██║ ██║ ┃ * 10 | * ┃ ╚═╝ ╚═══╝ ╚═════╝ ╚═══╝ ╚═╝ ╚═╝ ┃ * 11 | * ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ * 12 | * * 13 | * This machine kills fascists. * 14 | * * 15 | \*===========================================================================*/ 16 | 17 | -------------------------------------------------------------------------------- /.header-hashsign: -------------------------------------------------------------------------------- 1 | # =========================================================================== # 2 | # MIT License Copyright (c) 2022 Kris Nóva # 3 | # # 4 | # ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ # 5 | # ┃ ███╗ ██╗ ██████╗ ██╗ ██╗ █████╗ ┃ # 6 | # ┃ ████╗ ██║██╔═████╗██║ ██║██╔══██╗ ┃ # 7 | # ┃ ██╔██╗ ██║██║██╔██║██║ ██║███████║ ┃ # 8 | # ┃ ██║╚██╗██║████╔╝██║╚██╗ ██╔╝██╔══██║ ┃ # 9 | # ┃ ██║ ╚████║╚██████╔╝ ╚████╔╝ ██║ ██║ ┃ # 10 | # ┃ ╚═╝ ╚═══╝ ╚═════╝ ╚═══╝ ╚═╝ ╚═╝ ┃ # 11 | # ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ # 12 | # # 13 | # This machine kills fascists. # 14 | # # 15 | # =========================================================================== # -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | /* ==========================================================================*\ 2 | * MIT License Copyright (c) 2022 Kris Nóva 3 | * 4 | * ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ 5 | * ┃ ███╗ ██╗ ██████╗ ██╗ ██╗ █████╗ ┃ 6 | * ┃ ████╗ ██║██╔═████╗██║ ██║██╔══██╗ ┃ 7 | * ┃ ██╔██╗ ██║██║██╔██║██║ ██║███████║ ┃ 8 | * ┃ ██║╚██╗██║████╔╝██║╚██╗ ██╔╝██╔══██║ ┃ 9 | * ┃ ██║ ╚████║╚██████╔╝ ╚████╔╝ ██║ ██║ ┃ 10 | * ┃ ╚═╝ ╚═══╝ ╚═════╝ ╚═══╝ ╚═╝ ╚═╝ ┃ 11 | * ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ 12 | * 13 | * This machine kills fascists. 14 | * 15 | \*===========================================================================*/ 16 | 17 | MIT License Copyright (c) 2022 Kris Nóva 18 | 19 | Permission is hereby granted, free of charge, to any person obtaining a copy 20 | of this software and associated documentation files (the "Software"), to deal 21 | in the Software without restriction, including without limitation the rights 22 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 23 | copies of the Software, and to permit persons to whom the Software is 24 | furnished to do so, subject to the following conditions: 25 | 26 | The above copyright notice and this permission notice shall be included in all 27 | copies or substantial portions of the Software. 28 | 29 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 30 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 31 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 32 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 33 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 34 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 35 | SOFTWARE. 36 | 37 | 38 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # =========================================================================== # 2 | # MIT License Copyright (c) 2022 Kris Nóva # 3 | # # 4 | # ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ # 5 | # ┃ ███╗ ██╗ ██████╗ ██╗ ██╗ █████╗ ┃ # 6 | # ┃ ████╗ ██║██╔═████╗██║ ██║██╔══██╗ ┃ # 7 | # ┃ ██╔██╗ ██║██║██╔██║██║ ██║███████║ ┃ # 8 | # ┃ ██║╚██╗██║████╔╝██║╚██╗ ██╔╝██╔══██║ ┃ # 9 | # ┃ ██║ ╚████║╚██████╔╝ ╚████╔╝ ██║ ██║ ┃ # 10 | # ┃ ╚═╝ ╚═══╝ ╚═════╝ ╚═══╝ ╚═╝ ╚═╝ ┃ # 11 | # ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ # 12 | # # 13 | # This machine kills fascists. # 14 | # # 15 | # =========================================================================== # 16 | 17 | default: compile 18 | 19 | version = 1.3.2 20 | target = xpid 21 | org = kris-nova 22 | authorname = Kris Nóva 23 | authoremail = kris@nivenly.com 24 | license = MIT 25 | year = 2022 26 | copyright = Copyright (c) $(year) 27 | cstyle = Google 28 | 29 | compile: ## Compile for the local architecture ⚙ 30 | @echo "Compiling..." 31 | go build -ldflags "\ 32 | -X 'github.com/$(org)/$(target).Version=$(version)' \ 33 | -X 'github.com/$(org)/$(target).AuthorName=$(authorname)' \ 34 | -X 'github.com/$(org)/$(target).AuthorEmail=$(authoremail)' \ 35 | -X 'github.com/$(org)/$(target).Copyright=$(copyright)' \ 36 | -X 'github.com/$(org)/$(target).License=$(license)' \ 37 | -X 'github.com/$(org)/$(target).Name=$(target)'" \ 38 | -o $(target) cmd/*.go 39 | 40 | install: ## Install the program to /usr/bin 🎉 41 | @echo "Installing..." 42 | sudo cp $(target) /usr/bin/$(target) 43 | 44 | test: clean compile install ## 🤓 Run go tests 45 | @echo "Testing..." 46 | go test -v ./... 47 | 48 | clean: ## Clean your artifacts 🧼 49 | @echo "Cleaning..." 50 | rm -rf libxpid/build/* 51 | rm -rvf release/* 52 | 53 | format: ## Format the code 54 | @echo " -> Formatting code" 55 | clang-format -i -style=$(cstyle) libxpid/include/*.h 56 | clang-format -i -style=$(cstyle) libxpid/src/*.h 57 | clang-format -i -style=$(cstyle) libxpid/src/*.c 58 | 59 | all: libxpid libxpid-install compile install ## Complete development build and install of xpid from source 60 | 61 | .PHONY: libxpid 62 | libxpid: libxpid-clean ## Compile and install libxpid 63 | @echo "Building libxpid..." 64 | mkdir -p libxpid/build 65 | cd libxpid && ./configure 66 | cd libxpid/build && make 67 | 68 | .PHONY: aur 69 | aur: ## Publish to AUR using my SSH key 70 | @echo "Publishing to AUR using Kris Nóva's key (if exists)..." 71 | cd aur && ./aur_build && ./aur_push 72 | 73 | .PHONY: libxpid-clean 74 | libxpid-clean: ## Clean libxpid 75 | @echo "Clean libxpid..." 76 | rm -rf libxpid/build/* 77 | 78 | .PHONY: purge 79 | purge: ## WARNING This is a dangerous command that will purge all potential xpid artifacts from your system (As root!) 80 | rm -vf /usr/include/*xpid* 81 | rm -vf /usr/lib/*xpid* 82 | rm -vf /usr/local/include/*xpid* 83 | rm -vf /usr/local/lib/*xpid* 84 | 85 | .PHONY: libxpid-install 86 | libxpid-install: ## Install libxpid 87 | @echo "Installing libxpid..." 88 | cd libxpid/build && make install 89 | 90 | .PHONY: release 91 | release: ## Make the binaries for a GitHub release 📦 92 | mkdir -p release 93 | GOOS="linux" GOARCH="amd64" go build -ldflags "-X 'github.com/$(org)/$(target).Version=$(version)'" -o release/$(target)-linux-amd64 cmd/*.go 94 | GOOS="linux" GOARCH="arm" go build -ldflags "-X 'github.com/$(org)/$(target).Version=$(version)'" -o release/$(target)-linux-arm cmd/*.go 95 | GOOS="linux" GOARCH="arm64" go build -ldflags "-X 'github.com/$(org)/$(target).Version=$(version)'" -o release/$(target)-linux-arm64 cmd/*.go 96 | GOOS="linux" GOARCH="386" go build -ldflags "-X 'github.com/$(org)/$(target).Version=$(version)'" -o release/$(target)-linux-386 cmd/*.go 97 | GOOS="darwin" GOARCH="amd64" go build -ldflags "-X 'github.com/$(org)/$(target).Version=$(version)'" -o release/$(target)-darwin-amd64 cmd/*.go 98 | 99 | .PHONY: help 100 | help: ## 🤔 Show help messages for make targets 101 | @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[32m%-30s\033[0m %s\n", $$1, $$2}' 102 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # xpid 2 | 3 | It's [`nmap`](https://nmap.org/) but for pids. 🤓 4 | 5 | --- 6 | 7 | Please help me become an independent programmer by donating directly below. 8 | 9 | [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/D1D8CXLHZ) 10 | 11 | --- 12 | 13 | `xpid` gives a user the ability to "investigate" for process details on a Linux system. 14 | 15 | For example a sleeping thread will have a directory `/proc/[pid]` that can be navigated to, but not listed. 16 | 17 | `xpid` will check many different places in the kernel for details about a pid. 18 | By searching subsets of possible pids `xpid` will be able to check for pid details in many places in the kernel. 19 | 20 | ``` 21 | xpid [flags] -o [output] 22 | 23 | Investigate all pids 24 | xpid 25 | 26 | Investigate pid 1 27 | xpid 1 28 | 29 | Find all container processes on a system 30 | xpid -c 31 | 32 | Find all processes in the same namespace(s) as pid 1 33 | xpid --ns-in [mnt, net, pid, ipc, cgroup] 34 | 35 | Find all processes not in the same namespace(s) as current user 36 | xpid --ns-out-user [mnt, net, pid, ipc, cgroup] 37 | 38 | Find all processes running with eBPF programs as JSON 39 | xpid --ebpf -o json 40 | 41 | Find all processes running with eBPF programs, in a container, in /proc 42 | xpid -b -c -p 43 | 44 | Find all processes between specific values (Query syntax) 45 | xpid +100 # Search pids up to 100 46 | xpid 100-2000 # Search pids between 100-2000 47 | xpid 65000+ # Search pids 65000 or above 48 | 49 | Find all hidden processes on a system (slow) 50 | xpid -x 51 | 52 | ``` 53 | 54 | ## Container pids (xpid -c) 📦 55 | 56 | `xpid` will lookup container processes at runtime. 🎉 57 | 58 | This works by reading the link in [`/proc/[pid]/ns/@cgroup`](https://man7.org/linux/man-pages/man7/namespaces.7.html#:~:text=/proc/%5Bpid%5D/ns/cgroup) and correlating it back to the value in `/proc/1/[pid]/ns/@cgroup`. 59 | 60 | Regardless of the pid namespace context, if there is a "container" that is unique from the current pid 1, `xpid` will find it. 61 | 62 | ## eBPF pids (xpid -b) 🐝 63 | 64 | `xpid` will find pids that have eBPF programs loaded at runtime. 65 | 66 | This works by correlating the file descriptor info from [`/proc/[pid]/fdinfo/*`](https://man7.org/linux/man-pages/man5/proc.5.html#:~:text=file%20descriptor%200.-,/proc/%5Bpid%5D/fdinfo/,-(since%20Linux%202.6.22)) back to `/sys/fs/bpf/progs.debug`. 67 | If a pid has an eBPF program loaded, `xpid` will find it. 68 | 69 | ## Hidden pids (xpid -x) 🙈 70 | 71 | Because of the flexibility with kernel modules and eBPF in the kernel, it can be possible to prevent the [`proc(5)`](https://man7.org/linux/man-pages/man5/proc.5.html) filesystem from listing pid details in traditional ways. 72 | 73 | `xpid` uses a variety of tactics to search for pids in the same way `nmap` will use different tactics to port scan a target. 74 | 75 | ## Go runtime 76 | 77 | `xpid` is a Go runtime utility that depends on `libxpid`. 78 | Install `libxpid` first (below), and then compile the Go runtime. 79 | 80 | ```bash 81 | git clone https://github.com/kris-nova/xpid.git 82 | cd xpid 83 | make 84 | sudo make install 85 | ``` 86 | 87 | ## Xpid C library (libxpid) 88 | 89 | `libxpid` is written in C, as it will leverage [`ptrace(2)`](https://man7.org/linux/man-pages/man2/ptrace.2.html) and eBPF code directly. 90 | This means that the `xpid` executable is NOT entirely statically linked. 91 | You must first have `libxpid` installed on your system, before the `xpid` Go program will run. 92 | 93 | ```bash 94 | git clone https://github.com/kris-nova/xpid.git 95 | cd xpid/libxpid 96 | ./configure 97 | cd build 98 | make 99 | sudo make install 100 | ``` 101 | 102 | -------------------------------------------------------------------------------- /aur/.gitignore: -------------------------------------------------------------------------------- 1 | xpid* 2 | libxpid* 3 | src* 4 | pkg* -------------------------------------------------------------------------------- /aur/PKGBUILD.libxpid: -------------------------------------------------------------------------------- 1 | # Maintainer: Kris Nóva 2 | 3 | pkgbase=libxpid 4 | pkgname=(libxpid) 5 | pkgver=v1.3.2 6 | pkgrel=1 7 | pkgdesc="Linux Process Scanning. (C Library). Find eBPF programs, containers, hidden pids. Like nmap for pids in the kernel." 8 | arch=(x86_64) 9 | url="https://github.com/kris-nova/xpid" 10 | license=(MIT) 11 | makedepends=(make clang cmake) 12 | checkdepends=() 13 | optdepends=() 14 | backup=() 15 | options=() 16 | source=("git+https://github.com/kris-nova/xpid.git#tag=$pkgver") 17 | sha256sums=('SKIP') 18 | 19 | build() { 20 | cd xpid 21 | make DESTDIR="$pkgdir" purge 22 | cd libxpid 23 | ./configure 24 | cd build 25 | make 26 | } 27 | 28 | package() { 29 | depends=(libxpid) 30 | cd xpid 31 | cd libxpid/build 32 | make DESTDIR="$pkgdir" install 33 | } 34 | -------------------------------------------------------------------------------- /aur/PKGBUILD.xpid: -------------------------------------------------------------------------------- 1 | # Maintainer: Kris Nóva 2 | 3 | pkgbase=xpid 4 | pkgname=(xpid) 5 | pkgver=v1.3.2 6 | pkgrel=1 7 | pkgdesc="Linux Process Scanning. (CLI Runtime). Find eBPF programs, containers, hidden pids. Like nmap for pids in the kernel." 8 | arch=(x86_64) 9 | url="https://github.com/kris-nova/xpid" 10 | license=(MIT) 11 | makedepends=(libxpid go make) 12 | checkdepends=() 13 | optdepends=() 14 | backup=() 15 | options=() 16 | source=("git+https://github.com/kris-nova/xpid.git#tag=$pkgver") 17 | sha256sums=('SKIP') 18 | 19 | build() { 20 | cd $pkgname 21 | GO111MODULE=on 22 | go mod vendor 23 | go mod download 24 | make compile 25 | } 26 | 27 | package() { 28 | depends=(libxpid) 29 | cd $pkgname 30 | make DESTDIR="$pkgdir" install 31 | } 32 | -------------------------------------------------------------------------------- /bpf/.gitignore: -------------------------------------------------------------------------------- 1 | *.ll 2 | *.o 3 | vmlinux.h 4 | xpid-bpf -------------------------------------------------------------------------------- /bpf/Makefile: -------------------------------------------------------------------------------- 1 | # =========================================================================== # 2 | # MIT License Copyright (c) 2022 Kris Nóva # 3 | # # 4 | # ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ # 5 | # ┃ ███╗ ██╗ ██████╗ ██╗ ██╗ █████╗ ┃ # 6 | # ┃ ████╗ ██║██╔═████╗██║ ██║██╔══██╗ ┃ # 7 | # ┃ ██╔██╗ ██║██║██╔██║██║ ██║███████║ ┃ # 8 | # ┃ ██║╚██╗██║████╔╝██║╚██╗ ██╔╝██╔══██║ ┃ # 9 | # ┃ ██║ ╚████║╚██████╔╝ ╚████╔╝ ██║ ██║ ┃ # 10 | # ┃ ╚═╝ ╚═══╝ ╚═════╝ ╚═══╝ ╚═╝ ╚═╝ ┃ # 11 | # ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ # 12 | # # 13 | # This machine kills fascists. # 14 | # # 15 | # =========================================================================== # 16 | 17 | TARGET := xpid-bpf 18 | CFLAGS ?= -I /usr/include 19 | LDFLAGS ?= 20 | LIBS = -l bpf 21 | STYLE = Google 22 | 23 | all: autogen probes build ## Build everything 24 | 25 | .PHONY: clean 26 | clean: ## Clean objects 27 | rm -vf $(TARGET) 28 | rm -vf *.o 29 | rm -vf *.ll 30 | rm -vf $(PROBE) 31 | 32 | format: ## Format the code 33 | @echo " -> Formatting code" 34 | @clang-format -i -style=$(STYLE) *.c *.h 35 | 36 | build: ## Build the userspace program 37 | @echo " -> Building" 38 | clang $(CFLAGS) $(LDFLAGS) $(LIBS) -o $(TARGET) xpid-bpf.c -Wl, 39 | 40 | install: ## Install the target, and probe 41 | mkdir -p /usr/share/xpid 42 | cp $(TARGET) /usr/bin/$(TARGET) 43 | cp -v probe.*.o /usr/share/xpid/ 44 | 45 | .PHONY: probes 46 | probes: probe-tracepoints ## Compile eBPF probes 47 | @echo " -> Building probe" 48 | 49 | autogen: 50 | bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h 51 | 52 | probe-tracepoints: probe = probe.tracepoints 53 | probe-tracepoints: 54 | clang -S \ 55 | -target bpf \ 56 | -D __BPF_TRACING__ \ 57 | $(CFLAGS) \ 58 | -Wall \ 59 | -Werror \ 60 | -O2 -emit-llvm -c -g $(probe).c 61 | llc -march=bpf -filetype=obj -o $(probe).o $(probe).ll 62 | 63 | .PHONY: help 64 | help: ## Show help messages for make targets 65 | @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(firstword $(MAKEFILE_LIST)) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[32m%-30s\033[0m %s\n", $$1, $$2}' -------------------------------------------------------------------------------- /bpf/README.md: -------------------------------------------------------------------------------- 1 | # BPF Programs 2 | 3 | In this directory we have a number of useful eBPF programs that can be built and loaded into the kernel. 4 | 5 | These eBPF programs are useful for testing `xpid`'s ability to find eBPF pids at runtime. 6 | 7 | # Building the programs 8 | 9 | ```bash 10 | make 11 | sudo make install 12 | ``` 13 | 14 | # Running the programs 15 | 16 | ```bash 17 | ./xpid-bpf 18 | ``` -------------------------------------------------------------------------------- /bpf/probe.tracepoints.c: -------------------------------------------------------------------------------- 1 | /* ==========================================================================*\ 2 | * MIT License Copyright (c) 2022 Kris Nóva * 3 | * * 4 | * ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ * 5 | * ┃ ███╗ ██╗ ██████╗ ██╗ ██╗ █████╗ ┃ * 6 | * ┃ ████╗ ██║██╔═████╗██║ ██║██╔══██╗ ┃ * 7 | * ┃ ██╔██╗ ██║██║██╔██║██║ ██║███████║ ┃ * 8 | * ┃ ██║╚██╗██║████╔╝██║╚██╗ ██╔╝██╔══██║ ┃ * 9 | * ┃ ██║ ╚████║╚██████╔╝ ╚████╔╝ ██║ ██║ ┃ * 10 | * ┃ ╚═╝ ╚═══╝ ╚═════╝ ╚═══╝ ╚═╝ ╚═╝ ┃ * 11 | * ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ * 12 | * * 13 | * This machine kills fascists. * 14 | * * 15 | \*===========================================================================*/ 16 | 17 | // clang-format off 18 | #include "vmlinux.h" 19 | // clang-format on 20 | #include 21 | 22 | #include "xpid-bpf.h" 23 | 24 | struct { 25 | __uint(type, BPF_MAP_TYPE_HASH); 26 | __uint(max_entries, PROBE_MAX_ENTRIES); 27 | __type(key, int); 28 | __type(value, int); 29 | } probe_tracepoints_map SEC(".maps"); 30 | 31 | // name: inet_sock_set_state 32 | // ID: 1373 33 | // format: 34 | // field:unsigned short common_type; offset:0; size:2; 35 | // signed:0; field:unsigned char common_flags; offset:2; size:1; 36 | // signed:0; field:unsigned char common_preempt_count; offset:3; 37 | // size:1; signed:0; field:int common_pid; offset:4; size:4; 38 | // signed:1; 39 | // 40 | // field:const void * skaddr; offset:8; size:8; signed:0; 41 | // field:int oldstate; offset:16; size:4; signed:1; 42 | // field:int newstate; offset:20; size:4; signed:1; 43 | // field:__u16 sport; offset:24; size:2; signed:0; 44 | // field:__u16 dport; offset:26; size:2; signed:0; 45 | // field:__u16 family; offset:28; size:2; signed:0; 46 | // field:__u16 protocol; offset:30; size:2; signed:0; 47 | // field:__u8 saddr[4]; offset:32; size:4; signed:0; 48 | // field:__u8 daddr[4]; offset:36; size:4; signed:0; 49 | // field:__u8 saddr_v6[16]; offset:40; size:16; signed:0; 50 | // field:__u8 daddr_v6[16]; offset:56; size:16; signed:0; 51 | // 52 | // print fmt: "family=%s protocol=%s sport=%hu dport=%hu saddr=%pI4 daddr=%pI4 53 | // saddrv6=%pI6c daddrv6=%pI6c oldstate=%s newstate=%s", 54 | // __print_symbolic(REC->family, { 2, "AF_INET" }, { 10, "AF_INET6" }), 55 | // __print_symbolic(REC->protocol, { 6, "IPPROTO_TCP" }, { 33, "IPPROTO_DCCP" }, 56 | // { 132, "IPPROTO_SCTP" }, { 262, "IPPROTO_MPTCP" }), REC->sport, REC->dport, 57 | // REC->saddr, REC->daddr, REC->saddr_v6, REC->daddr_v6, 58 | // __print_symbolic(REC->oldstate, { 1, "TCP_ESTABLISHED" }, { 2, "TCP_SYN_SENT" 59 | // }, { 3, "TCP_SYN_RECV" }, { 4, "TCP_FIN_WAIT1" }, { 5, "TCP_FIN_WAIT2" }, { 60 | // 6, "TCP_TIME_WAIT" }, { 7, "TCP_CLOSE" }, { 8, "TCP_CLOSE_WAIT" }, { 9, 61 | // "TCP_LAST_ACK" }, { 10, "TCP_LISTEN" }, { 11, "TCP_CLOSING" }, { 12, 62 | // "TCP_NEW_SYN_RECV" }), __print_symbolic(REC->newstate, { 1, "TCP_ESTABLISHED" 63 | // }, { 2, "TCP_SYN_SENT" }, { 3, "TCP_SYN_RECV" }, { 4, "TCP_FIN_WAIT1" }, { 5, 64 | // "TCP_FIN_WAIT2" }, { 6, "TCP_TIME_WAIT" }, { 7, "TCP_CLOSE" }, { 8, 65 | // "TCP_CLOSE_WAIT" }, { 9, "TCP_LAST_ACK" }, { 10, "TCP_LISTEN" }, { 11, 66 | // "TCP_CLOSING" }, { 12, "TCP_NEW_SYN_RECV" }) 67 | SEC("tracepoint/sock/inet_sock_set_state") 68 | int inet_sock_set_state(void *args) { return 0; } 69 | 70 | // name: cgroup_attach_task 71 | // ID: 432 72 | // format: 73 | // field:unsigned short common_type; offset:0; size:2; 74 | // signed:0; field:unsigned char common_flags; offset:2; size:1; 75 | // signed:0; field:unsigned char common_preempt_count; offset:3; 76 | // size:1; signed:0; field:int common_pid; offset:4; size:4; 77 | // signed:1; 78 | // 79 | // field:int dst_root; offset:8; size:4; signed:1; 80 | // field:int dst_level; offset:12; size:4; signed:1; 81 | // field:u64 dst_id; offset:16; size:8; signed:0; 82 | // field:int pid; offset:24; size:4; signed:1; 83 | // field:__data_loc char[] dst_path; offset:28; size:4; 84 | // signed:1; field:__data_loc char[] comm; offset:32; size:4; 85 | // signed:1; 86 | // 87 | // print fmt: "dst_root=%d dst_id=%llu dst_level=%d dst_path=%s pid=%d comm=%s", 88 | // REC->dst_root, REC->dst_id, REC->dst_level, __get_str(dst_path), REC->pid, 89 | // __get_str(comm) 90 | SEC("tracepoint/cgroup/cgroup_attach_task") 91 | int cgroup_attach_task(void *args) { return 0; } 92 | -------------------------------------------------------------------------------- /bpf/xpid-bpf.c: -------------------------------------------------------------------------------- 1 | /* ==========================================================================*\ 2 | * MIT License Copyright (c) 2022 Kris Nóva * 3 | * * 4 | * ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ * 5 | * ┃ ███╗ ██╗ ██████╗ ██╗ ██╗ █████╗ ┃ * 6 | * ┃ ████╗ ██║██╔═████╗██║ ██║██╔══██╗ ┃ * 7 | * ┃ ██╔██╗ ██║██║██╔██║██║ ██║███████║ ┃ * 8 | * ┃ ██║╚██╗██║████╔╝██║╚██╗ ██╔╝██╔══██║ ┃ * 9 | * ┃ ██║ ╚████║╚██████╔╝ ╚████╔╝ ██║ ██║ ┃ * 10 | * ┃ ╚═╝ ╚═══╝ ╚═════╝ ╚═══╝ ╚═╝ ╚═╝ ┃ * 11 | * ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ * 12 | * * 13 | * This machine kills fascists. * 14 | * * 15 | \*===========================================================================*/ 16 | 17 | #include "xpid-bpf.h" 18 | 19 | #include 20 | #include 21 | #include 22 | 23 | void usage() { 24 | printf("xpid-bpf\n"); 25 | printf("\n"); 26 | printf("eBPF programs used for exercising xpid.\n"); 27 | printf("\n"); 28 | exit(0); 29 | } 30 | 31 | struct config { 32 | char probe_tracepoints[PATH_MAX]; 33 | } cfg; 34 | 35 | void cmdsetup(int argc, char **argv) { 36 | // Set defaults here 37 | sprintf(cfg.probe_tracepoints, PROBE_DEFAULT_FMT, PROBE_TRACEPOINT); 38 | for (int i = 0; i < argc; i++) { 39 | if (argv[i][0] == '-') { 40 | switch (argv[i][1]) { 41 | case 'h': 42 | usage(); 43 | break; 44 | } 45 | } 46 | } 47 | } 48 | 49 | int main(int argc, char **argv) { 50 | cmdsetup(argc, argv); 51 | 52 | // Probe Tracepoints 53 | struct bpf_object *probetracepoints_obj; 54 | struct bpf_program *probetracepoints_prog = NULL; 55 | 56 | int loaded; 57 | printf(" -> eBPF Open %s\n", cfg.probe_tracepoints); 58 | probetracepoints_obj = bpf_object__open(cfg.probe_tracepoints); 59 | if (!probetracepoints_obj) { 60 | printf(" XX Unable to open %s\n", cfg.probe_tracepoints); 61 | return 1; 62 | } 63 | bpf_object__load(probetracepoints_obj); 64 | bpf_object__next_map(probetracepoints_obj, NULL); 65 | bpf_object__for_each_program(probetracepoints_prog, probetracepoints_obj) { 66 | const char *progname = bpf_program__name(probetracepoints_prog); 67 | const char *progsecname = bpf_program__section_name(probetracepoints_prog); 68 | struct bpf_link *link = bpf_program__attach(probetracepoints_prog); 69 | if (!link) { 70 | return 1; 71 | } 72 | printf(" -> eBPF Program Attached (Name) : %s\n", progname); 73 | printf(" -> eBPF Program Attached (Sec) : %s\n", progsecname); 74 | } 75 | printf(" -> xpid map : BPF_MAP_TYPE_HASH\n"); 76 | printf(" -> xpid prog : perf\n"); 77 | 78 | 79 | printf("Hanging...\n"); 80 | while(1){} 81 | 82 | return 0; 83 | } -------------------------------------------------------------------------------- /bpf/xpid-bpf.h: -------------------------------------------------------------------------------- 1 | /* ==========================================================================*\ 2 | * MIT License Copyright (c) 2022 Kris Nóva * 3 | * * 4 | * ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ * 5 | * ┃ ███╗ ██╗ ██████╗ ██╗ ██╗ █████╗ ┃ * 6 | * ┃ ████╗ ██║██╔═████╗██║ ██║██╔══██╗ ┃ * 7 | * ┃ ██╔██╗ ██║██║██╔██║██║ ██║███████║ ┃ * 8 | * ┃ ██║╚██╗██║████╔╝██║╚██╗ ██╔╝██╔══██║ ┃ * 9 | * ┃ ██║ ╚████║╚██████╔╝ ╚████╔╝ ██║ ██║ ┃ * 10 | * ┃ ╚═╝ ╚═══╝ ╚═════╝ ╚═══╝ ╚═╝ ╚═╝ ┃ * 11 | * ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ * 12 | * * 13 | * This machine kills fascists. * 14 | * * 15 | \*===========================================================================*/ 16 | 17 | #ifndef XPID_BPF_H 18 | #define XPID_BPF_H 19 | 20 | #define PATH_MAX 1024 21 | 22 | #define PROBE_DEFAULT_DIR "/usr/share/xpid" 23 | #define PROBE_DEFAULT_FMT "/usr/share/xpid/%s" 24 | 25 | #define PROBE_MAX_ENTRIES 1024 26 | 27 | #define PROBE_TRACEPOINT "probe.tracepoints.o" // This MUST match the Makefile 28 | 29 | #endif // XPID_BPF_H 30 | -------------------------------------------------------------------------------- /cmd/main.go: -------------------------------------------------------------------------------- 1 | /*===========================================================================*\ 2 | * MIT License Copyright (c) 2022 Kris Nóva * 3 | * * 4 | * ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ * 5 | * ┃ ███╗ ██╗ ██████╗ ██╗ ██╗ █████╗ ┃ * 6 | * ┃ ████╗ ██║██╔═████╗██║ ██║██╔══██╗ ┃ * 7 | * ┃ ██╔██╗ ██║██║██╔██║██║ ██║███████║ ┃ * 8 | * ┃ ██║╚██╗██║████╔╝██║╚██╗ ██╔╝██╔══██║ ┃ * 9 | * ┃ ██║ ╚████║╚██████╔╝ ╚████╔╝ ██║ ██║ ┃ * 10 | * ┃ ╚═╝ ╚═══╝ ╚═════╝ ╚═══╝ ╚═╝ ╚═╝ ┃ * 11 | * ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ * 12 | * * 13 | * This machine kills fascists. * 14 | * * 15 | \*===========================================================================*/ 16 | 17 | package main 18 | 19 | import ( 20 | "fmt" 21 | "os" 22 | "time" 23 | 24 | "github.com/kris-nova/xpid/pkg/encoders/table" 25 | 26 | "github.com/kris-nova/xpid/pkg/encoders/raw" 27 | 28 | encoder "github.com/kris-nova/xpid/pkg/encoders" 29 | 30 | filter "github.com/kris-nova/xpid/pkg/filters" 31 | 32 | "github.com/kris-nova/xpid/pkg/encoders/json" 33 | 34 | v1 "github.com/kris-nova/xpid/pkg/api/v1" 35 | 36 | "github.com/kris-nova/xpid/pkg/procx" 37 | 38 | "github.com/kris-nova/xpid" 39 | "github.com/sirupsen/logrus" 40 | "github.com/urfave/cli/v2" 41 | ) 42 | 43 | var cfg = &AppOptions{} 44 | 45 | type AppOptions struct { 46 | 47 | // Verbose will toggle verbosity (breaks encoding) 48 | Verbose bool 49 | 50 | // Concurrent will toggle the concurrent pool buffer for the process explorer. 51 | // If enabled will run concurrently, or "fast". 52 | Concurrent bool 53 | 54 | // Encoders 55 | // 56 | // Support: raw, json, color 57 | Output string 58 | 59 | // Hidden will only show "hidden" pids 60 | Hidden bool 61 | 62 | // Threads will toggle showing threads in a result 63 | Threads bool 64 | 65 | // Ebpf works like containers. Will attempt to show Ebpf programs. 66 | Ebpf bool 67 | 68 | // Containers 69 | Container bool 70 | 71 | // Available namespaces: mnt, pid, net, ipc, cgroup 72 | NamespaceInPid1 cli.StringSlice 73 | NamespaceOutPid1 cli.StringSlice 74 | NamespaceInUser cli.StringSlice 75 | NamespaceOutUser cli.StringSlice 76 | 77 | // TableFormatters 78 | ShowTableNamespaces bool 79 | 80 | ProcListing bool 81 | } 82 | 83 | const ( 84 | ExitCode_PermissionDenied int = 99 85 | ExitCode_InvalidNamespace int = 80 86 | ) 87 | 88 | func main() { 89 | /* Change version to -V */ 90 | cli.VersionFlag = &cli.BoolFlag{ 91 | Name: "version", 92 | Aliases: []string{"V"}, 93 | Usage: "The version of the program.", 94 | } 95 | 96 | // EXAMPLE: Override a template 97 | cli.AppHelpTemplate = fmt.Sprintf(`%s 98 | {{.Usage}} 99 | 100 | {{.UsageText}} 101 | Options 102 | {{range .VisibleFlags}}{{.}} 103 | {{end}} 104 | `, xpid.Banner()) 105 | 106 | app := &cli.App{ 107 | Name: xpid.Name, 108 | Version: xpid.Version, 109 | Compiled: time.Now(), 110 | Authors: []*cli.Author{ 111 | &cli.Author{ 112 | Name: xpid.AuthorName, 113 | Email: xpid.AuthorEmail, 114 | }, 115 | }, 116 | 117 | Copyright: xpid.Copyright, 118 | HelpName: xpid.Copyright, 119 | Usage: "Linux Process Discovery. Like nmap, but for pids.", 120 | UsageText: `xpid [flags] -o [output] 121 | 122 | Investigate all pids 123 | xpid 124 | 125 | Investigate pid 1 126 | xpid 1 127 | 128 | Find all container processes on a system 129 | xpid -c 130 | 131 | Find all processes in the same namespace(s) as pid 1 132 | xpid --ns-in [mnt, net, pid, ipc, cgroup] 133 | 134 | Find all processes not in the same namespace(s) as current user 135 | xpid --ns-out-user [mnt, net, pid, ipc, cgroup] 136 | 137 | Find all processes running with eBPF programs as JSON 138 | xpid --ebpf -o json 139 | 140 | Find all processes running with eBPF programs, in a container, in /proc 141 | xpid -b -c -p 142 | 143 | Find all processes between specific values (Query syntax) 144 | xpid +100 # Search pids up to 100 145 | xpid 100-2000 # Search pids between 100-2000 146 | xpid 65000+ # Search pids 65000 or above 147 | 148 | Find all hidden processes on a system (slow) 149 | xpid -x 150 | `, 151 | Commands: []*cli.Command{}, 152 | Flags: []cli.Flag{ 153 | &cli.BoolFlag{ 154 | Name: "verbose", 155 | Aliases: []string{"v"}, 156 | Destination: &cfg.Verbose, 157 | Usage: "Toggle verbose mode", 158 | DefaultText: "", 159 | }, 160 | &cli.StringFlag{ 161 | Name: "out", 162 | Aliases: []string{"o"}, 163 | Destination: &cfg.Output, 164 | Usage: "Set output encoder (json, table, raw)", 165 | }, 166 | &cli.BoolFlag{ 167 | Name: "proc", 168 | Aliases: []string{"p", "pl"}, 169 | Destination: &cfg.ProcListing, 170 | Usage: "List only pids in /proc (Fast).", 171 | DefaultText: "", 172 | }, 173 | &cli.StringSliceFlag{ 174 | Name: "ns-in", 175 | Aliases: []string{"N"}, 176 | Destination: &cfg.NamespaceInPid1, 177 | Usage: "Show pids in namespace(s) as pid 1.", 178 | DefaultText: "", 179 | }, 180 | &cli.StringSliceFlag{ 181 | Name: "ns-out", 182 | Aliases: []string{"O"}, 183 | Destination: &cfg.NamespaceOutPid1, 184 | Usage: "Reject pids in namespace(s) as pid 1.", 185 | DefaultText: "", 186 | }, 187 | &cli.StringSliceFlag{ 188 | Name: "ns-in-user", 189 | Destination: &cfg.NamespaceInPid1, 190 | Usage: "Show pids in namespace(s) as user.", 191 | DefaultText: "", 192 | }, 193 | &cli.StringSliceFlag{ 194 | Name: "ns-out-user", 195 | Destination: &cfg.NamespaceOutPid1, 196 | Usage: "Reject pids in namespace(s) as user.", 197 | DefaultText: "", 198 | }, 199 | &cli.BoolFlag{ 200 | Name: "concurrent", 201 | Aliases: []string{"C", "fast"}, 202 | Destination: &cfg.Concurrent, 203 | Value: true, 204 | Usage: "Run concurrently (heavy CPU).", 205 | // Leave default text alone 206 | }, 207 | &cli.BoolFlag{ 208 | Name: "ebpf", 209 | Aliases: []string{"bpf", "b"}, // The "B" stands for Berkeley, Bitches. 210 | Destination: &cfg.Ebpf, 211 | Value: false, 212 | Usage: "Show pids with BPF programs attached.", 213 | DefaultText: "", 214 | }, 215 | &cli.BoolFlag{ 216 | Name: "hidden", 217 | Aliases: []string{"x"}, 218 | Destination: &cfg.Hidden, 219 | Value: false, 220 | Usage: "Useful for finding rootkits.", 221 | DefaultText: "", 222 | }, 223 | &cli.BoolFlag{ 224 | Name: "threads", 225 | Aliases: []string{"t", "thread"}, 226 | Destination: &cfg.Threads, 227 | Value: false, 228 | Usage: "Show threads and parent pids", 229 | DefaultText: "", 230 | }, 231 | &cli.BoolFlag{ 232 | Name: "container", 233 | Aliases: []string{"c"}, 234 | Destination: &cfg.Container, 235 | Value: false, 236 | Usage: "Show pids with unique cgroup namespace.", 237 | DefaultText: "", 238 | }, 239 | &cli.BoolFlag{ 240 | Name: "n", 241 | Aliases: []string{"namespaces"}, 242 | Destination: &cfg.ShowTableNamespaces, 243 | Value: false, 244 | Usage: "Display system namespaces.", 245 | DefaultText: "", 246 | }, 247 | }, 248 | EnableBashCompletion: false, 249 | HideHelp: false, 250 | HideVersion: true, 251 | Before: func(c *cli.Context) error { 252 | Preloader() 253 | return nil 254 | }, 255 | After: func(c *cli.Context) error { 256 | // Destruct 257 | return nil 258 | }, 259 | Action: func(c *cli.Context) error { 260 | var pids []*v1.Process 261 | 262 | query := c.Args().Get(0) 263 | if query == "" { 264 | max := procx.MaxPid() 265 | if max == -1 { 266 | return fmt.Errorf("unable to read from /proc") 267 | } 268 | query = fmt.Sprintf("1-%d", max) 269 | } 270 | 271 | // Validation 272 | if cfg.Hidden && cfg.ProcListing { 273 | return fmt.Errorf("unable to find hidden pids by only looking in procfs") 274 | } 275 | if cfg.Ebpf { 276 | table.TableFmtBPF = true 277 | } 278 | 279 | // Initialize the explorer based on flags 280 | if cfg.ProcListing { 281 | pids = procx.ProcListingQuery(query) 282 | } else { 283 | pids = procx.PIDQuery(query) 284 | } 285 | 286 | if pids == nil { 287 | return fmt.Errorf("invalid pid query: %s", query) 288 | } 289 | logrus.Infof("Query : %s\n", query) 290 | x := procx.NewProcessExplorer(pids) 291 | 292 | // Fast 293 | x.SetFast(cfg.Concurrent) 294 | 295 | // Encoder 296 | var encoder encoder.ProcessExplorerEncoder 297 | switch cfg.Output { 298 | case "json": 299 | encoder = json.NewJSONEncoder() 300 | break 301 | case "table": 302 | encoder = table.NewTableEncoder() 303 | break 304 | case "raw": 305 | encoder = raw.NewRawEncoder() 306 | break 307 | case "color": 308 | rawcolor := raw.NewRawEncoder() 309 | rawcolor.SetFormat(raw.ColorFormatter) 310 | encoder = rawcolor 311 | default: 312 | table.TableFmtNS = cfg.ShowTableNamespaces 313 | encoder = table.NewTableEncoder() 314 | } 315 | 316 | // First the current user 317 | bytes, err := encoder.EncodeUser(currentUser()) 318 | if err != nil { 319 | return fmt.Errorf("unable to find current user: %v", err) 320 | } 321 | fmt.Print(string(bytes)) 322 | 323 | // Next pid one 324 | pid1 := v1.ProcessPID(1) 325 | pid1.ShowHeader = true 326 | v1.NewProcModule().Execute(pid1) 327 | v1.NewNamespaceModule().Execute(pid1) 328 | bytes, err = encoder.Encode(pid1) 329 | if err != nil { 330 | return fmt.Errorf("unable to encode current upid: %v", err) 331 | } 332 | fmt.Print(string(bytes)) 333 | 334 | // Finally our pid 335 | upid := v1.ProcessPID(int64(os.Getpid())) 336 | v1.NewProcModule().Execute(upid) 337 | v1.NewNamespaceModule().Execute(upid) 338 | 339 | upid.DrawLineAfter = true 340 | bytes, err = encoder.Encode(upid) 341 | if err != nil { 342 | return fmt.Errorf("unable to encode current upid: %v", err) 343 | } 344 | fmt.Print(string(bytes)) 345 | 346 | // Filters 347 | filter.PidOne = pid1 348 | filter.CurrentUser = currentUser() 349 | 350 | encoder.AddFilter(filter.RetainOnlyNamed) 351 | if cfg.Hidden { 352 | encoder.AddFilter(filter.RetainOnlyHidden) 353 | } 354 | if !cfg.Threads { 355 | encoder.AddFilter(filter.RejectThreads) 356 | } 357 | 358 | // Always load "proc" module 359 | x.AddModule(v1.NewProcModule()) 360 | 361 | // Always load "namespace" module 362 | x.AddModule(v1.NewNamespaceModule()) 363 | 364 | // Check for EBPF 365 | if cfg.Ebpf { 366 | x.AddModule(v1.NewEBPFModule()) 367 | encoder.AddFilter(filter.RetainOnlyEBPF) 368 | } 369 | 370 | // Check for container 371 | if cfg.Container { 372 | x.AddModule(v1.NewContainerModule()) 373 | encoder.AddFilter(filter.RetainOnlyContainers) 374 | } 375 | 376 | // Namespace in Pid 1 377 | nsIns := cfg.NamespaceInPid1.Value() 378 | if len(nsIns) > 0 { 379 | for _, nsIn := range nsIns { 380 | switch nsIn { 381 | case v1.NamespaceMount: 382 | filter.NamespaceFilterSet_Mount = pid1.NamespaceModule.Mount 383 | encoder.AddFilter(filter.RetainNamespaceIn_Mount) 384 | break 385 | case v1.NamespaceIPC: 386 | filter.NamespaceFilterSet_IPC = pid1.NamespaceModule.IPC 387 | encoder.AddFilter(filter.RetainNamespaceIn_IPC) 388 | break 389 | case v1.NamespaceNet: 390 | filter.NamespaceFilterSet_Net = pid1.NamespaceModule.Net 391 | encoder.AddFilter(filter.RetainNamespaceIn_Net) 392 | break 393 | case v1.NamespaceCgroup: 394 | filter.NamespaceFilterSet_Cgroup = pid1.NamespaceModule.Cgroup 395 | encoder.AddFilter(filter.RetainNamespaceIn_Cgroup) 396 | break 397 | case v1.NamespacePid: 398 | filter.NamespaceFilterSet_PID = pid1.NamespaceModule.PID 399 | encoder.AddFilter(filter.RetainNamespaceIn_PID) 400 | break 401 | default: 402 | logrus.Errorf("invalid namespace-in: %s", nsIn) 403 | os.Exit(ExitCode_InvalidNamespace) 404 | } 405 | } 406 | } 407 | 408 | // Namespace in User 409 | nsInsU := cfg.NamespaceInUser.Value() 410 | if len(nsInsU) > 0 { 411 | for _, nsIn := range nsInsU { 412 | switch nsIn { 413 | case v1.NamespaceMount: 414 | filter.NamespaceFilterSet_Mount = upid.NamespaceModule.Mount 415 | encoder.AddFilter(filter.RetainNamespaceIn_Mount) 416 | break 417 | case v1.NamespaceIPC: 418 | filter.NamespaceFilterSet_IPC = upid.NamespaceModule.IPC 419 | encoder.AddFilter(filter.RetainNamespaceIn_IPC) 420 | break 421 | case v1.NamespaceNet: 422 | filter.NamespaceFilterSet_Net = upid.NamespaceModule.Net 423 | encoder.AddFilter(filter.RetainNamespaceIn_Net) 424 | break 425 | case v1.NamespaceCgroup: 426 | filter.NamespaceFilterSet_Cgroup = upid.NamespaceModule.Cgroup 427 | encoder.AddFilter(filter.RetainNamespaceIn_Cgroup) 428 | break 429 | case v1.NamespacePid: 430 | filter.NamespaceFilterSet_PID = upid.NamespaceModule.PID 431 | encoder.AddFilter(filter.RetainNamespaceIn_PID) 432 | break 433 | default: 434 | logrus.Errorf("invalid namespace-in-user: %s", nsIn) 435 | os.Exit(ExitCode_InvalidNamespace) 436 | } 437 | } 438 | } 439 | 440 | // Namespace out Pid 1 441 | nsOuts := cfg.NamespaceOutPid1.Value() 442 | if len(nsOuts) > 0 { 443 | for _, nsOut := range nsOuts { 444 | switch nsOut { 445 | 446 | case v1.NamespaceMount: 447 | filter.NamespaceFilterSet_Mount = pid1.NamespaceModule.Mount 448 | encoder.AddFilter(filter.RetainNamespaceOut_Mount) 449 | break 450 | case v1.NamespaceIPC: 451 | filter.NamespaceFilterSet_IPC = pid1.NamespaceModule.IPC 452 | encoder.AddFilter(filter.RetainNamespaceOut_IPC) 453 | break 454 | case v1.NamespaceNet: 455 | filter.NamespaceFilterSet_Net = pid1.NamespaceModule.Net 456 | encoder.AddFilter(filter.RetainNamespaceOut_Net) 457 | break 458 | case v1.NamespaceCgroup: 459 | filter.NamespaceFilterSet_Cgroup = pid1.NamespaceModule.Cgroup 460 | encoder.AddFilter(filter.RetainNamespaceOut_Cgroup) 461 | break 462 | case v1.NamespacePid: 463 | filter.NamespaceFilterSet_PID = pid1.NamespaceModule.PID 464 | encoder.AddFilter(filter.RetainNamespaceOut_PID) 465 | break 466 | default: 467 | logrus.Errorf("invalid namespace-out: %s", nsOut) 468 | os.Exit(ExitCode_InvalidNamespace) 469 | } 470 | } 471 | } 472 | 473 | // Namespace out User 474 | nsOutsU := cfg.NamespaceOutUser.Value() 475 | if len(nsOutsU) > 0 { 476 | for _, nsOut := range nsOutsU { 477 | switch nsOut { 478 | 479 | case v1.NamespaceMount: 480 | filter.NamespaceFilterSet_Mount = upid.NamespaceModule.Mount 481 | encoder.AddFilter(filter.RetainNamespaceOut_Mount) 482 | break 483 | case v1.NamespaceIPC: 484 | filter.NamespaceFilterSet_IPC = upid.NamespaceModule.IPC 485 | encoder.AddFilter(filter.RetainNamespaceOut_IPC) 486 | break 487 | case v1.NamespaceNet: 488 | filter.NamespaceFilterSet_Net = upid.NamespaceModule.Net 489 | encoder.AddFilter(filter.RetainNamespaceOut_Net) 490 | break 491 | case v1.NamespaceCgroup: 492 | filter.NamespaceFilterSet_Cgroup = upid.NamespaceModule.Cgroup 493 | encoder.AddFilter(filter.RetainNamespaceOut_Cgroup) 494 | break 495 | case v1.NamespacePid: 496 | filter.NamespaceFilterSet_PID = upid.NamespaceModule.PID 497 | encoder.AddFilter(filter.RetainNamespaceOut_PID) 498 | break 499 | default: 500 | logrus.Errorf("invalid namespace-out-user: %s", nsOut) 501 | os.Exit(ExitCode_InvalidNamespace) 502 | } 503 | } 504 | } 505 | 506 | // Execute 507 | x.SetEncoder(encoder) 508 | x.SetWriter(os.Stdout) 509 | return x.Execute() 510 | }, 511 | } 512 | 513 | err := app.Run(os.Args) 514 | if err != nil { 515 | logrus.Errorf("execution error: %v", err) 516 | os.Exit(1) 517 | } 518 | os.Exit(0) 519 | } 520 | 521 | // Preloader will run for ALL commands, and is used 522 | // to initalize the runtime environments of the program. 523 | func Preloader() { 524 | /* Flag parsing */ 525 | if cfg.Verbose { 526 | logrus.SetLevel(logrus.DebugLevel) 527 | } else { 528 | logrus.SetLevel(logrus.WarnLevel) 529 | } 530 | 531 | if cfg.Container { 532 | if !isuid(0) { 533 | logrus.Errorf("Permission denied. UID=0 Required for [containers].") 534 | os.Exit(ExitCode_PermissionDenied) 535 | } 536 | } 537 | 538 | if cfg.Ebpf { 539 | if !isuid(0) { 540 | logrus.Errorf("Permission denied. UID=0 Required for [ebpf].") 541 | os.Exit(ExitCode_PermissionDenied) 542 | } 543 | } 544 | } 545 | -------------------------------------------------------------------------------- /cmd/runtime.go: -------------------------------------------------------------------------------- 1 | /*===========================================================================*\ 2 | * MIT License Copyright (c) 2022 Kris Nóva * 3 | * * 4 | * ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ * 5 | * ┃ ███╗ ██╗ ██████╗ ██╗ ██╗ █████╗ ┃ * 6 | * ┃ ████╗ ██║██╔═████╗██║ ██║██╔══██╗ ┃ * 7 | * ┃ ██╔██╗ ██║██║██╔██║██║ ██║███████║ ┃ * 8 | * ┃ ██║╚██╗██║████╔╝██║╚██╗ ██╔╝██╔══██║ ┃ * 9 | * ┃ ██║ ╚████║╚██████╔╝ ╚████╔╝ ██║ ██║ ┃ * 10 | * ┃ ╚═╝ ╚═══╝ ╚═════╝ ╚═══╝ ╚═╝ ╚═╝ ┃ * 11 | * ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ * 12 | * * 13 | * This machine kills fascists. * 14 | * * 15 | \*===========================================================================*/ 16 | 17 | package main 18 | 19 | import ( 20 | "os/user" 21 | "strconv" 22 | 23 | api "github.com/kris-nova/xpid/pkg/api/v1" 24 | ) 25 | 26 | func isuid(check int) bool { 27 | u, _ := user.Current() 28 | if u == nil { 29 | return false 30 | } 31 | i, _ := strconv.Atoi(u.Uid) 32 | return check == i 33 | } 34 | 35 | func currentUser() *api.User { 36 | gouser, _ := user.Current() 37 | gogroup, _ := user.LookupGroupId(gouser.Gid) 38 | uid, _ := strconv.Atoi(gouser.Uid) 39 | gid, _ := strconv.Atoi(gouser.Gid) 40 | u := &api.User{ 41 | Group: api.Group{ 42 | ID: gid, 43 | Name: gogroup.Name, 44 | }, 45 | User: *gouser, 46 | ID: uid, 47 | Name: gouser.Username, 48 | } 49 | return u 50 | } 51 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/kris-nova/xpid 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/kris-nova/go-nova v0.0.0-20220421215251-a2e7dcdd690b 7 | github.com/sirupsen/logrus v1.8.1 8 | github.com/urfave/cli/v2 v2.4.1 9 | ) 10 | 11 | require ( 12 | github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect 13 | github.com/fatih/color v1.13.0 // indirect 14 | github.com/jedib0t/go-pretty v4.3.0+incompatible // indirect 15 | github.com/mattn/go-colorable v0.1.9 // indirect 16 | github.com/mattn/go-isatty v0.0.14 // indirect 17 | github.com/russross/blackfriday/v2 v2.1.0 // indirect 18 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect 19 | golang.org/x/term v0.0.0-20220411215600-e5f449aeb171 // indirect 20 | ) 21 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 2 | github.com/cpuguy83/go-md2man/v2 v2.0.1 h1:r/myEWzV9lfsM1tFLgDyu0atFtJ1fXn261LKYj/3DxU= 3 | github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 4 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 5 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 6 | github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= 7 | github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= 8 | github.com/jedib0t/go-pretty v4.3.0+incompatible h1:CGs8AVhEKg/n9YbUenWmNStRW2PHJzaeDodcfvRAbIo= 9 | github.com/jedib0t/go-pretty v4.3.0+incompatible/go.mod h1:XemHduiw8R651AF9Pt4FwCTKeG3oo7hrHJAoznj9nag= 10 | github.com/kris-nova/go-nova v0.0.0-20220421215251-a2e7dcdd690b h1:WgE+h1ID1sQBvqYPCThmQUnkqwYmhf+7lvlnVwzI6Oo= 11 | github.com/kris-nova/go-nova v0.0.0-20220421215251-a2e7dcdd690b/go.mod h1:eCyQUOhUVQA6jspm6q6tV3JsLdzWDM3ZfGXxdlazDK8= 12 | github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U= 13 | github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= 14 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 15 | github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= 16 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 17 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 18 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 19 | github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= 20 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 21 | github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= 22 | github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= 23 | github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= 24 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 25 | github.com/urfave/cli/v2 v2.4.1 h1:iWKDWVGT9QvuhSoeaFD1TRwndLTnBiFYZ4dXeZwGWik= 26 | github.com/urfave/cli/v2 v2.4.1/go.mod h1:NX9W0zmTvedE5oDoOMs2RTC8RvdK98NTYZE5LbaEYPg= 27 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4= 28 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 29 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 30 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 31 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= 32 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 33 | golang.org/x/term v0.0.0-20220411215600-e5f449aeb171 h1:EH1Deb8WZJ0xc0WK//leUHXcX9aLE5SymusoTmMZye8= 34 | golang.org/x/term v0.0.0-20220411215600-e5f449aeb171/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 35 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 36 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 37 | -------------------------------------------------------------------------------- /libxpid/.gitignore: -------------------------------------------------------------------------------- 1 | cmake-build-debug 2 | build -------------------------------------------------------------------------------- /libxpid/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # =========================================================================== # 2 | # MIT License Copyright (c) 2022 Kris Nóva # 3 | # # 4 | # ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ # 5 | # ┃ ███╗ ██╗ ██████╗ ██╗ ██╗ █████╗ ┃ # 6 | # ┃ ████╗ ██║██╔═████╗██║ ██║██╔══██╗ ┃ # 7 | # ┃ ██╔██╗ ██║██║██╔██║██║ ██║███████║ ┃ # 8 | # ┃ ██║╚██╗██║████╔╝██║╚██╗ ██╔╝██╔══██║ ┃ # 9 | # ┃ ██║ ╚████║╚██████╔╝ ╚████╔╝ ██║ ██║ ┃ # 10 | # ┃ ╚═╝ ╚═══╝ ╚═════╝ ╚═══╝ ╚═╝ ╚═╝ ┃ # 11 | # ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ # 12 | # # 13 | # This machine kills fascists. # 14 | # # 15 | # =========================================================================== # 16 | 17 | 18 | ###################################################################### 19 | set(SO_VERSION "1.3.1") 20 | ###################################################################### 21 | 22 | cmake_minimum_required(VERSION 3.9) 23 | project(xpid VERSION ${SO_VERSION} DESCRIPTION "Linux Process Discovery") 24 | 25 | # 26 | # Define all internal files 27 | # 28 | add_library(xpid SHARED 29 | src/proc.c 30 | src/proc_dir.c 31 | src/bpf.c 32 | ) 33 | 34 | target_link_libraries(xpid bpf) 35 | 36 | # 37 | # Version of the library properties 38 | # 39 | set_target_properties(xpid PROPERTIES VERSION ${SO_VERSION}) 40 | # 41 | # This is where the magic symlinking in /usr/include comes to play 42 | set_target_properties(xpid PROPERTIES SOVERSION ${SO_VERSION}) 43 | 44 | set_target_properties(xpid PROPERTIES SOVERSION ${SO_VERSION}) 45 | 46 | 47 | # 48 | # Everything in /include will be what we expose as API 49 | # 50 | set_target_properties(xpid PROPERTIES PUBLIC_HEADER include/xpid.h) 51 | 52 | # Add the top level directory to make our #include statements cleaner 53 | target_include_directories(xpid PRIVATE .) 54 | # Add include (public API) 55 | target_include_directories(xpid PRIVATE include) 56 | # Add src (private implementation) 57 | target_include_directories(xpid PRIVATE src) 58 | 59 | # make install 60 | # Here is the install rule 61 | include(GNUInstallDirs) 62 | # This is where we put the shared object on the filesystem 63 | install(TARGETS xpid 64 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} 65 | PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) -------------------------------------------------------------------------------- /libxpid/README.md: -------------------------------------------------------------------------------- 1 | # libxpid 2 | 3 | The internal C library for `xpid` 4 | 5 | The separation between `xpid` and `libxpid` is not as clean as it could be. 6 | 7 | The long term goal of this library is to serve as a C implementation of all the `xpid` internal sourcing which will include: 8 | 9 | - ptrace support for pids at runtime 10 | - eBPF support for pids at runtime 11 | - glibc support for procfs 12 | - proc parsing 13 | - kernel module for interfacing with the pid table at runtime 14 | 15 | For now this serves as a reliable hook for ad-hoc `xpid` functionality. 16 | As the needs of `xpid` mature, I will slowly begin to migrate from Go directly into C. 17 | In other words, I am prototyping in Go until its obvious that the feature needs to be backported to C. -------------------------------------------------------------------------------- /libxpid/configure: -------------------------------------------------------------------------------- 1 | # =========================================================================== # 2 | # MIT License Copyright (c) 2022 Kris Nóva # 3 | # # 4 | # ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ # 5 | # ┃ ███╗ ██╗ ██████╗ ██╗ ██╗ █████╗ ┃ # 6 | # ┃ ████╗ ██║██╔═████╗██║ ██║██╔══██╗ ┃ # 7 | # ┃ ██╔██╗ ██║██║██╔██║██║ ██║███████║ ┃ # 8 | # ┃ ██║╚██╗██║████╔╝██║╚██╗ ██╔╝██╔══██║ ┃ # 9 | # ┃ ██║ ╚████║╚██████╔╝ ╚████╔╝ ██║ ██║ ┃ # 10 | # ┃ ╚═╝ ╚═══╝ ╚═════╝ ╚═══╝ ╚═╝ ╚═╝ ┃ # 11 | # ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ # 12 | # # 13 | # This machine kills fascists. # 14 | # # 15 | # =========================================================================== # 16 | 17 | 18 | mkdir -p build 19 | cd build 20 | cmake \ 21 | -DCMAKE_INSTALL_PREFIX:PATH=/usr \ 22 | ../ -------------------------------------------------------------------------------- /libxpid/include/xpid.h: -------------------------------------------------------------------------------- 1 | /* ==========================================================================*\ 2 | * MIT License Copyright (c) 2022 Kris Nóva * 3 | * * 4 | * ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ * 5 | * ┃ ███╗ ██╗ ██████╗ ██╗ ██╗ █████╗ ┃ * 6 | * ┃ ████╗ ██║██╔═████╗██║ ██║██╔══██╗ ┃ * 7 | * ┃ ██╔██╗ ██║██║██╔██║██║ ██║███████║ ┃ * 8 | * ┃ ██║╚██╗██║████╔╝██║╚██╗ ██╔╝██╔══██║ ┃ * 9 | * ┃ ██║ ╚████║╚██████╔╝ ╚████╔╝ ██║ ██║ ┃ * 10 | * ┃ ╚═╝ ╚═══╝ ╚═════╝ ╚═══╝ ╚═╝ ╚═╝ ┃ * 11 | * ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ * 12 | * * 13 | * This machine kills fascists. * 14 | * * 15 | \*===========================================================================*/ 16 | 17 | #ifndef LIB_XPID_H 18 | #define LIB_XPID_H 19 | 20 | #include 21 | #include 22 | 23 | // proc_dir.c 24 | extern int proc_dir_opendir(pid_t pid); 25 | extern int proc_dir_chdir(pid_t pid); 26 | extern int proc_dir_dent(pid_t pid); 27 | 28 | // bpf.c 29 | void bpf_map_type_enum(int i, char *name); 30 | void bpf_program_details(__u32 id, char *sec); 31 | 32 | #endif // end LIB_XPID_H -------------------------------------------------------------------------------- /libxpid/src/bpf.c: -------------------------------------------------------------------------------- 1 | /* ==========================================================================*\ 2 | * MIT License Copyright (c) 2022 Kris Nóva * 3 | * * 4 | * ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ * 5 | * ┃ ███╗ ██╗ ██████╗ ██╗ ██╗ █████╗ ┃ * 6 | * ┃ ████╗ ██║██╔═████╗██║ ██║██╔══██╗ ┃ * 7 | * ┃ ██╔██╗ ██║██║██╔██║██║ ██║███████║ ┃ * 8 | * ┃ ██║╚██╗██║████╔╝██║╚██╗ ██╔╝██╔══██║ ┃ * 9 | * ┃ ██║ ╚████║╚██████╔╝ ╚████╔╝ ██║ ██║ ┃ * 10 | * ┃ ╚═╝ ╚═══╝ ╚═════╝ ╚═══╝ ╚═╝ ╚═╝ ┃ * 11 | * ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ * 12 | * * 13 | * This machine kills fascists. * 14 | * * 15 | \*===========================================================================*/ 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #include "xpid.h" 23 | 24 | void bpf_program_details(__u32 id, char *sec) { 25 | int prog_fd; 26 | prog_fd = bpf_prog_get_fd_by_id(id); 27 | struct bpf_prog_info info = {}; 28 | __u32 info_len = sizeof(info); 29 | bpf_obj_get_info_by_fd(prog_fd,&info, &info_len); 30 | char infostr[1024]; 31 | sprintf(infostr, "%s:%llu", info.name, info.netns_ino); 32 | strncpy(sec, infostr, 1024); 33 | } 34 | 35 | void bpf_map_type_enum_linux_5_17(int i, char *name) { 36 | switch (i) { 37 | case 0: 38 | strncpy(name, "BPF_MAP_TYPE_UNSPEC", 256); 39 | return; 40 | case 1: 41 | strncpy(name, "BPF_MAP_TYPE_HASH", 256); 42 | return; 43 | case 2: 44 | strncpy(name, "BPF_MAP_TYPE_ARRAY", 256); 45 | return; 46 | case 3: 47 | strncpy(name, "BPF_MAP_TYPE_PROG_ARRAY", 256); 48 | return; 49 | case 4: 50 | strncpy(name, "BPF_MAP_TYPE_PERF_EVENT_ARRAY", 256); 51 | return; 52 | case 5: 53 | strncpy(name, "BPF_MAP_TYPE_PERCPU_HASH", 256); 54 | return; 55 | case 6: 56 | strncpy(name, "BPF_MAP_TYPE_PERCPU_ARRAY", 256); 57 | return; 58 | case 7: 59 | strncpy(name, "BPF_MAP_TYPE_STACK_TRACE", 256); 60 | return; 61 | case 8: 62 | strncpy(name, "BPF_MAP_TYPE_CGROUP_ARRAY", 256); 63 | return; 64 | case 9: 65 | strncpy(name, "BPF_MAP_TYPE_LRU_HASH", 256); 66 | return; 67 | case 10: 68 | strncpy(name, "BPF_MAP_TYPE_LRU_PERCPU_HASH", 256); 69 | return; 70 | case 11: 71 | strncpy(name, "BPF_MAP_TYPE_LPM_TRIE", 256); 72 | return; 73 | case 12: 74 | strncpy(name, "BPF_MAP_TYPE_ARRAY_OF_MAPS", 256); 75 | return; 76 | case 13: 77 | strncpy(name, "BPF_MAP_TYPE_HASH_OF_MAPS", 256); 78 | return; 79 | case 14: 80 | strncpy(name, "BPF_MAP_TYPE_DEVMAP", 256); 81 | return; 82 | case 15: 83 | strncpy(name, "BPF_MAP_TYPE_SOCKMAP", 256); 84 | return; 85 | case 16: 86 | strncpy(name, "BPF_MAP_TYPE_CPUMAP", 256); 87 | return; 88 | case 17: 89 | strncpy(name, "BPF_MAP_TYPE_XSKMAP", 256); 90 | return; 91 | case 18: 92 | strncpy(name, "BPF_MAP_TYPE_SOCKHASH", 256); 93 | return; 94 | case 19: 95 | strncpy(name, "BPF_MAP_TYPE_CGROUP_STORAGE", 256); 96 | return; 97 | case 20: 98 | strncpy(name, "BPF_MAP_TYPE_REUSEPORT_SOCKARRAY", 256); 99 | return; 100 | case 21: 101 | strncpy(name, "BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE", 256); 102 | return; 103 | case 22: 104 | strncpy(name, "BPF_MAP_TYPE_QUEUE", 256); 105 | return; 106 | case 23: 107 | strncpy(name, "BPF_MAP_TYPE_STACK", 256); 108 | return; 109 | case 24: 110 | strncpy(name, "BPF_MAP_TYPE_SK_STORAGE", 256); 111 | return; 112 | case 25: 113 | strncpy(name, "BPF_MAP_TYPE_DEVMAP_HASH", 256); 114 | return; 115 | case 26: 116 | strncpy(name, "BPF_MAP_TYPE_STRUCT_OPS", 256); 117 | return; 118 | case 27: 119 | strncpy(name, "BPF_MAP_TYPE_RINGBUF", 256); 120 | return; 121 | case 28: 122 | strncpy(name, "BPF_MAP_TYPE_INODE_STORAGE", 256); 123 | return; 124 | case 29: 125 | strncpy(name, "BPF_MAP_TYPE_TASK_STORAGE", 256); 126 | return; 127 | case 30: 128 | strncpy(name, "BPF_MAP_TYPE_BLOOM_FILTER", 256); 129 | return; 130 | case 31: 131 | strncpy(name, "BPF_MAP_TYPE_STRUCT_OPS", 256); 132 | return; 133 | default: 134 | strncpy(name, "UNKNOWN", 256); 135 | return; 136 | } 137 | } 138 | 139 | void bpf_map_type_enum(int i, char *name) { 140 | bpf_map_type_enum_linux_5_17(i, name); 141 | } 142 | 143 | -------------------------------------------------------------------------------- /libxpid/src/proc.c: -------------------------------------------------------------------------------- 1 | /* ==========================================================================*\ 2 | * MIT License Copyright (c) 2022 Kris Nóva * 3 | * * 4 | * ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ * 5 | * ┃ ███╗ ██╗ ██████╗ ██╗ ██╗ █████╗ ┃ * 6 | * ┃ ████╗ ██║██╔═████╗██║ ██║██╔══██╗ ┃ * 7 | * ┃ ██╔██╗ ██║██║██╔██║██║ ██║███████║ ┃ * 8 | * ┃ ██║╚██╗██║████╔╝██║╚██╗ ██╔╝██╔══██║ ┃ * 9 | * ┃ ██║ ╚████║╚██████╔╝ ╚████╔╝ ██║ ██║ ┃ * 10 | * ┃ ╚═╝ ╚═══╝ ╚═════╝ ╚═══╝ ╚═╝ ╚═╝ ┃ * 11 | * ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ * 12 | * * 13 | * This machine kills fascists. * 14 | * * 15 | \*===========================================================================*/ 16 | 17 | #include "proc.h" 18 | 19 | #include 20 | 21 | void procfs(char *p) { 22 | // TODO We need to actually look up where proc is mounted! 23 | sprintf(p, "%s", PROCFS_DEFAULT); 24 | } 25 | 26 | void procfs_pid(char *p, int pid) { 27 | // TODO We need to actually look up where proc is mounted! 28 | sprintf(p, "%s/%d", PROCFS_DEFAULT, pid); 29 | } 30 | 31 | void procfs_pid_file(char *p, int pid, char *file) { 32 | // TODO We need to actually look up where proc is mounted! 33 | sprintf(p, "%s/%d/%s", PROCFS_DEFAULT, pid, file); 34 | } -------------------------------------------------------------------------------- /libxpid/src/proc.h: -------------------------------------------------------------------------------- 1 | /* ==========================================================================*\ 2 | * MIT License Copyright (c) 2022 Kris Nóva * 3 | * * 4 | * ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ * 5 | * ┃ ███╗ ██╗ ██████╗ ██╗ ██╗ █████╗ ┃ * 6 | * ┃ ████╗ ██║██╔═████╗██║ ██║██╔══██╗ ┃ * 7 | * ┃ ██╔██╗ ██║██║██╔██║██║ ██║███████║ ┃ * 8 | * ┃ ██║╚██╗██║████╔╝██║╚██╗ ██╔╝██╔══██║ ┃ * 9 | * ┃ ██║ ╚████║╚██████╔╝ ╚████╔╝ ██║ ██║ ┃ * 10 | * ┃ ╚═╝ ╚═══╝ ╚═════╝ ╚═══╝ ╚═╝ ╚═╝ ┃ * 11 | * ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ * 12 | * * 13 | * This machine kills fascists. * 14 | * * 15 | \*===========================================================================*/ 16 | #ifndef XPID_PROC_H 17 | #define XPID_PROC_H 18 | 19 | #define PROCFS_PATH_MAX 256 20 | #define PROCFS_DEFAULT "/proc" 21 | 22 | void procfs(char *p); 23 | void procfs_pid(char *p, int pid); 24 | void procfs_pid_file(char *p, int pid, char *file); 25 | 26 | #endif // end XPID_PROC_H -------------------------------------------------------------------------------- /libxpid/src/proc_dir.c: -------------------------------------------------------------------------------- 1 | /* ==========================================================================*\ 2 | * MIT License Copyright (c) 2022 Kris Nóva * 3 | * * 4 | * ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ * 5 | * ┃ ███╗ ██╗ ██████╗ ██╗ ██╗ █████╗ ┃ * 6 | * ┃ ████╗ ██║██╔═████╗██║ ██║██╔══██╗ ┃ * 7 | * ┃ ██╔██╗ ██║██║██╔██║██║ ██║███████║ ┃ * 8 | * ┃ ██║╚██╗██║████╔╝██║╚██╗ ██╔╝██╔══██║ ┃ * 9 | * ┃ ██║ ╚████║╚██████╔╝ ╚████╔╝ ██║ ██║ ┃ * 10 | * ┃ ╚═╝ ╚═══╝ ╚═════╝ ╚═══╝ ╚═╝ ╚═╝ ┃ * 11 | * ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ * 12 | * * 13 | * This machine kills fascists. * 14 | * * 15 | \*===========================================================================*/ 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #include "proc.h" 24 | #include "xpid.h" 25 | 26 | /** 27 | * proc_dir_opendir will attempt to opendir(/proc/[pid]) 28 | * and report. 29 | * 30 | * @param pid 31 | * @return 32 | */ 33 | int proc_dir_opendir(pid_t pid) { 34 | struct dirent *dent; 35 | char *p = malloc(PROCFS_PATH_MAX); 36 | procfs_pid(p, pid); 37 | DIR *dir = opendir(p); 38 | free(p); 39 | if (dir == NULL) { 40 | closedir(dir); 41 | return 0; 42 | } 43 | dent = readdir(dir); 44 | if (dent) { 45 | closedir(dir); 46 | return 1; 47 | } 48 | closedir(dir); 49 | return 2; 50 | } 51 | 52 | /** 53 | * proc_dir_chdir will attempt to chdir(/proc/[pid]) 54 | * and report. 55 | * 56 | * @param pid 57 | * @return 58 | */ 59 | 60 | int proc_dir_chdir(pid_t pid) { 61 | int ret; 62 | char *p = malloc(PROCFS_PATH_MAX); 63 | procfs_pid(p, pid); 64 | ret = chdir(p); 65 | free(p); 66 | if (ret == -1) { 67 | return 0; 68 | } 69 | if (ret == 0) { 70 | return 1; 71 | } 72 | return 2; 73 | } 74 | 75 | /** 76 | * proc_dir_dent will attempt to opendir(/proc/[pid]) 77 | * and list (dent) files and report. 78 | * 79 | * @param pid 80 | * @return 81 | */ 82 | int proc_dir_dent(pid_t pid) { 83 | char *pproc = malloc(PROCFS_PATH_MAX); 84 | char *pprocpid = malloc(PROCFS_PATH_MAX); 85 | procfs(pproc); 86 | procfs_pid(pprocpid, pid); 87 | struct dirent *dent; 88 | DIR *dir = opendir(pproc); 89 | if (dir == NULL) { 90 | closedir(dir); 91 | free(pproc); 92 | free(pprocpid); 93 | return 0; 94 | } 95 | while ((dent = readdir(dir)) != NULL) { 96 | char pidstr[PROCFS_PATH_MAX]; 97 | sprintf(pidstr, "%d", pid); 98 | if (strncmp(dent->d_name, pidstr, sizeof dent->d_name) == 0) { 99 | closedir(dir); 100 | free(pproc); 101 | free(pprocpid); 102 | return 1; 103 | } 104 | } 105 | closedir(dir); 106 | free(pproc); 107 | free(pprocpid); 108 | return 0; 109 | } 110 | -------------------------------------------------------------------------------- /meta.go: -------------------------------------------------------------------------------- 1 | /*===========================================================================*\ 2 | * MIT License Copyright (c) 2022 Kris Nóva * 3 | * * 4 | * ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ * 5 | * ┃ ███╗ ██╗ ██████╗ ██╗ ██╗ █████╗ ┃ * 6 | * ┃ ████╗ ██║██╔═████╗██║ ██║██╔══██╗ ┃ * 7 | * ┃ ██╔██╗ ██║██║██╔██║██║ ██║███████║ ┃ * 8 | * ┃ ██║╚██╗██║████╔╝██║╚██╗ ██╔╝██╔══██║ ┃ * 9 | * ┃ ██║ ╚████║╚██████╔╝ ╚████╔╝ ██║ ██║ ┃ * 10 | * ┃ ╚═╝ ╚═══╝ ╚═════╝ ╚═══╝ ╚═╝ ╚═╝ ┃ * 11 | * ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ * 12 | * * 13 | * This machine kills fascists. * 14 | * * 15 | \*===========================================================================*/ 16 | 17 | package xpid 18 | 19 | import "fmt" 20 | 21 | var ( 22 | Name string 23 | Version string 24 | Copyright string 25 | License string 26 | AuthorName string 27 | AuthorEmail string 28 | ) 29 | 30 | func Banner() string { 31 | var banner string 32 | banner += fmt.Sprintf("\n") 33 | banner += fmt.Sprintf(" ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓\n") 34 | banner += fmt.Sprintf(" ┃ ██╗ ██╗██████╗ ██╗██████╗ ┃\n") 35 | banner += fmt.Sprintf(" ┃ ╚██╗██╔╝██╔══██╗██║██╔══██╗ ┃\n") 36 | banner += fmt.Sprintf(" ┃ ╚███╔╝ ██████╔╝██║██║ ██║ ┃\n") 37 | banner += fmt.Sprintf(" ┃ ██╔██╗ ██╔═══╝ ██║██║ ██║ ┃\n") 38 | banner += fmt.Sprintf(" ┃ ██╔╝ ██╗██║ ██║██████╔╝ ┃\n") 39 | banner += fmt.Sprintf(" ┃ ╚═╝ ╚═╝╚═╝ ╚═╝╚═════╝ ┃\n") 40 | banner += fmt.Sprintf(" ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛\n") 41 | banner += fmt.Sprintf(" %s <%s>\n", AuthorName, AuthorEmail) 42 | return banner 43 | } 44 | -------------------------------------------------------------------------------- /pkg/api/v1/interface.go: -------------------------------------------------------------------------------- 1 | /*===========================================================================*\ 2 | * MIT License Copyright (c) 2022 Kris Nóva * 3 | * * 4 | * ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ * 5 | * ┃ ███╗ ██╗ ██████╗ ██╗ ██╗ █████╗ ┃ * 6 | * ┃ ████╗ ██║██╔═████╗██║ ██║██╔══██╗ ┃ * 7 | * ┃ ██╔██╗ ██║██║██╔██║██║ ██║███████║ ┃ * 8 | * ┃ ██║╚██╗██║████╔╝██║╚██╗ ██╔╝██╔══██║ ┃ * 9 | * ┃ ██║ ╚████║╚██████╔╝ ╚████╔╝ ██║ ██║ ┃ * 10 | * ┃ ╚═╝ ╚═══╝ ╚═════╝ ╚═══╝ ╚═╝ ╚═╝ ┃ * 11 | * ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ * 12 | * * 13 | * This machine kills fascists. * 14 | * * 15 | \*===========================================================================*/ 16 | 17 | package v1 18 | 19 | type ProcessExecutable func(p *Process) (ProcessExplorerModule, error) 20 | 21 | type ProcessExplorerModule interface { 22 | Meta() *Meta 23 | Execute(p *Process) error 24 | } 25 | -------------------------------------------------------------------------------- /pkg/api/v1/module.go: -------------------------------------------------------------------------------- 1 | /*===========================================================================*\ 2 | * MIT License Copyright (c) 2022 Kris Nóva * 3 | * * 4 | * ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ * 5 | * ┃ ███╗ ██╗ ██████╗ ██╗ ██╗ █████╗ ┃ * 6 | * ┃ ████╗ ██║██╔═████╗██║ ██║██╔══██╗ ┃ * 7 | * ┃ ██╔██╗ ██║██║██╔██║██║ ██║███████║ ┃ * 8 | * ┃ ██║╚██╗██║████╔╝██║╚██╗ ██╔╝██╔══██║ ┃ * 9 | * ┃ ██║ ╚████║╚██████╔╝ ╚████╔╝ ██║ ██║ ┃ * 10 | * ┃ ╚═╝ ╚═══╝ ╚═════╝ ╚═══╝ ╚═╝ ╚═╝ ┃ * 11 | * ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ * 12 | * * 13 | * This machine kills fascists. * 14 | * * 15 | \*===========================================================================*/ 16 | 17 | package v1 18 | 19 | type Meta struct { 20 | Name string 21 | Description string 22 | Authors []string 23 | } 24 | -------------------------------------------------------------------------------- /pkg/api/v1/module_container.go: -------------------------------------------------------------------------------- 1 | /*===========================================================================*\ 2 | * MIT License Copyright (c) 2022 Kris Nóva * 3 | * * 4 | * ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ * 5 | * ┃ ███╗ ██╗ ██████╗ ██╗ ██╗ █████╗ ┃ * 6 | * ┃ ████╗ ██║██╔═████╗██║ ██║██╔══██╗ ┃ * 7 | * ┃ ██╔██╗ ██║██║██╔██║██║ ██║███████║ ┃ * 8 | * ┃ ██║╚██╗██║████╔╝██║╚██╗ ██╔╝██╔══██║ ┃ * 9 | * ┃ ██║ ╚████║╚██████╔╝ ╚████╔╝ ██║ ██║ ┃ * 10 | * ┃ ╚═╝ ╚═══╝ ╚═════╝ ╚═══╝ ╚═╝ ╚═╝ ┃ * 11 | * ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ * 12 | * * 13 | * This machine kills fascists. * 14 | * * 15 | \*===========================================================================*/ 16 | 17 | package v1 18 | 19 | import ( 20 | "fmt" 21 | 22 | "github.com/kris-nova/xpid/pkg/procfs" 23 | ) 24 | 25 | var _ ProcessExplorerModule = &ContainerModule{} 26 | 27 | type ContainerModule struct { 28 | cgroupNamespace string 29 | CgroupNamespaceUnique bool `json:"isCgroupNamespaceUnique,omitempty"` 30 | } 31 | 32 | func NewContainerModule() *ContainerModule { 33 | m := &ContainerModule{} 34 | // We always need a pid 1 35 | m.Execute(ProcessPID(1)) 36 | return m 37 | } 38 | 39 | func (m *ContainerModule) Meta() *Meta { 40 | return &Meta{ 41 | Name: "Container module", 42 | Description: "Find container meta information at runtime.", 43 | Authors: []string{ 44 | "Kris Nóva ", 45 | }, 46 | } 47 | } 48 | 49 | var pidone *ContainerModule 50 | 51 | func (m *ContainerModule) Execute(p *Process) error { 52 | // Module specific (correlated) 53 | result := &ContainerModule{} 54 | 55 | procfshandle := procfs.NewProcFileSystem(procfs.Proc()) 56 | nscgroup, _ := procfshandle.ReadlinkPID(p.PID, "ns/cgroup") 57 | m.cgroupNamespace = nscgroup 58 | 59 | // If it's pid 1 we can just move on, there is nothing to compare 60 | if p.PID == 1 { 61 | p.Container = false 62 | pidone = result 63 | pidone.cgroupNamespace = nscgroup 64 | return nil 65 | } 66 | if pidone == nil { 67 | return fmt.Errorf("pid one not initialized") 68 | } 69 | if pidone.cgroupNamespace == "" { 70 | return fmt.Errorf("pid one empty cgroup namespace") 71 | } 72 | 73 | // Research: 74 | // 75 | // As far as I can tell the majority of container environments 76 | // can be identified by their system.slice mounts in /sys/fs/cgroup 77 | // or by the ns/cgroup mapping in /proc 78 | // 79 | // For us to call something "a container" it basically needs to have 80 | // a unique ns/cgroup link that is different from the pid 1 in the 81 | // current pid namespace. 82 | if m.cgroupNamespace != "" && m.cgroupNamespace != pidone.cgroupNamespace { 83 | // We found a container 84 | p.Container = true 85 | } else { 86 | p.Container = false 87 | } 88 | return nil 89 | } 90 | -------------------------------------------------------------------------------- /pkg/api/v1/module_ebpf.go: -------------------------------------------------------------------------------- 1 | /*===========================================================================*\ 2 | * MIT License Copyright (c) 2022 Kris Nóva * 3 | * * 4 | * ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ * 5 | * ┃ ███╗ ██╗ ██████╗ ██╗ ██╗ █████╗ ┃ * 6 | * ┃ ████╗ ██║██╔═████╗██║ ██║██╔══██╗ ┃ * 7 | * ┃ ██╔██╗ ██║██║██╔██║██║ ██║███████║ ┃ * 8 | * ┃ ██║╚██╗██║████╔╝██║╚██╗ ██╔╝██╔══██║ ┃ * 9 | * ┃ ██║ ╚████║╚██████╔╝ ╚████╔╝ ██║ ██║ ┃ * 10 | * ┃ ╚═╝ ╚═══╝ ╚═════╝ ╚═══╝ ╚═╝ ╚═╝ ┃ * 11 | * ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ * 12 | * * 13 | * This machine kills fascists. * 14 | * * 15 | \*===========================================================================*/ 16 | 17 | package v1 18 | 19 | import ( 20 | "fmt" 21 | "io/ioutil" 22 | "path/filepath" 23 | "strconv" 24 | "strings" 25 | 26 | "github.com/kris-nova/xpid/pkg/libxpid" 27 | 28 | "github.com/kris-nova/xpid/pkg/procfs" 29 | ) 30 | 31 | var _ ProcessExplorerModule = &EBPFModule{} 32 | 33 | type EBPFModule struct { 34 | Mounts string `json:"mount,omitempty"` 35 | Progs []string `json:"progs,omitempty"` 36 | Maps []string `json:"maps,omitempty"` 37 | } 38 | 39 | func NewEBPFModule() *EBPFModule { 40 | return &EBPFModule{} 41 | } 42 | 43 | const ( 44 | // Taken from 45 | // https://github.com/torvalds/linux/blob/master/include/uapi/linux/bpf.h 46 | 47 | FileDescriptorMapIDKey = "map_id" 48 | FileDescriptorProgIDKey = "prog_id" 49 | ) 50 | 51 | func (m *EBPFModule) Meta() *Meta { 52 | return &Meta{ 53 | Name: "eBPF module", 54 | Description: "Search proc(5) filesystems for eBPF programs. Will do an in depth scan and search for obfuscated directories.", 55 | Authors: []string{ 56 | "Kris Nóva ", 57 | }, 58 | } 59 | } 60 | 61 | func (m *EBPFModule) Execute(p *Process) error { 62 | // Module specific (correlated) 63 | 64 | procfshandle := procfs.NewProcFileSystem(procfs.Proc()) 65 | mounts, _ := procfshandle.ContentsPID(p.PID, "mounts") 66 | p.Mounts = mounts 67 | 68 | bpfDebug, err := NewEBPFFileSystemData() 69 | if err != nil { 70 | return fmt.Errorf("unable to read /sys/fs/bpf: %v", err) 71 | } 72 | 73 | // Compare with file descriptors in fdinfo 74 | fds, err := procfshandle.DirPID(p.PID, "fdinfo") 75 | 76 | if err != nil { 77 | return fmt.Errorf("unable to read /proc/%d/fdinfo: %v", p.PID, err) 78 | } 79 | 80 | // File descriptor scanning 81 | // 82 | // Here we try to map the file descriptor keys (map_id, prog_id) 83 | // back to the established values found in the progs.debug and maps.debug 84 | // sys filesystem 85 | // 86 | for _, fd := range fds { 87 | fddata, err := procfshandle.ContentsPID(p.PID, filepath.Join("fdinfo", fd.Name())) 88 | if err != nil { 89 | continue 90 | } 91 | fdProgID := procfs.FileKeyValue(fddata, FileDescriptorProgIDKey) 92 | fdMapID := procfs.FileKeyValue(fddata, FileDescriptorMapIDKey) 93 | 94 | // Map back to /sys/fs/bpf/progs.debug 95 | for id, _ := range bpfDebug.Progs { 96 | if id == "" { 97 | continue 98 | } 99 | if id == fdProgID { 100 | // We have mapped an eBPF program to a PID! 101 | p.EBPF = true 102 | progDetails := programDetails(p, fddata) 103 | if progDetails != "" && !strings.Contains(strings.Join(p.EBPFModule.Progs, ""), progDetails) { 104 | p.EBPFModule.Progs = append(p.EBPFModule.Progs, progDetails) 105 | } 106 | } 107 | } 108 | 109 | // Map back to /sys/fs/bpf/maps.debug 110 | for id, _ := range bpfDebug.Maps { 111 | if id == "" { 112 | continue 113 | } 114 | if id == fdMapID { 115 | // We have mapped an eBPF program to a PID! 116 | p.EBPF = true 117 | mapDetails := mapDetails(p, fddata) 118 | if mapDetails != "" && !strings.Contains(strings.Join(p.EBPFModule.Maps, ""), mapDetails) { 119 | p.EBPFModule.Maps = append(p.EBPFModule.Maps, mapDetails) 120 | } 121 | } 122 | } 123 | } 124 | return nil 125 | } 126 | 127 | // EBPFFileSystemData is structured data from /sys/fs/bpf/* 128 | type EBPFFileSystemData struct { 129 | Maps map[string]*Map 130 | Progs map[string]*Prog 131 | } 132 | 133 | type Map struct { 134 | ID string 135 | Name string 136 | MaxEntries string 137 | } 138 | type Prog struct { 139 | ID string 140 | Name string 141 | Attached string 142 | } 143 | 144 | const ( 145 | DefaultEBPFFileSystemDataDir = "/sys/fs/bpf" 146 | ) 147 | 148 | // NewEBPFFileSystemData will read from /sys/fs/bpf/[maps.debug, progs.debug] 149 | func NewEBPFFileSystemData() (*EBPFFileSystemData, error) { 150 | e := &EBPFFileSystemData{ 151 | Progs: make(map[string]*Prog), 152 | Maps: make(map[string]*Map), 153 | } 154 | mapbytes, err := ioutil.ReadFile(filepath.Join(DefaultEBPFFileSystemDataDir, "maps.debug")) 155 | if err != nil { 156 | return nil, fmt.Errorf("map read: %v", err) 157 | } 158 | mapstr := string(mapbytes) 159 | lines := strings.Split(mapstr, "\n") 160 | for _, line := range lines { 161 | line = strings.TrimSpace(line) 162 | if line == "" { 163 | continue 164 | } 165 | 166 | // Parse the file 167 | spl := strings.Split(line, " ") 168 | var name, id string 169 | if len(spl) < 2 { 170 | name = "" 171 | } else { 172 | name = strings.TrimSpace(spl[1]) 173 | } 174 | id = strings.TrimSpace(spl[0]) 175 | 176 | // Ignore headers 177 | if id == "id" { 178 | continue 179 | } 180 | 181 | mp := &Map{ 182 | ID: id, 183 | Name: name, 184 | } 185 | e.Maps[id] = mp 186 | } 187 | 188 | progbytes, err := ioutil.ReadFile(filepath.Join(DefaultEBPFFileSystemDataDir, "progs.debug")) 189 | if err != nil { 190 | return nil, fmt.Errorf("prog read: %v", err) 191 | } 192 | progstr := string(progbytes) 193 | lines = strings.Split(progstr, "\n") 194 | for _, line := range lines { 195 | line = strings.TrimSpace(line) 196 | if line == "" { 197 | continue 198 | } 199 | 200 | // Parse the file 201 | spl := strings.Split(line, " ") 202 | var name, id string 203 | if len(spl) < 2 { 204 | name = "" 205 | } else { 206 | name = strings.TrimSpace(spl[1]) 207 | } 208 | id = strings.TrimSpace(spl[0]) 209 | 210 | // Ignore headers 211 | if id == "id" { 212 | continue 213 | } 214 | p := &Prog{ 215 | ID: id, 216 | Name: name, 217 | } 218 | e.Progs[id] = p 219 | } 220 | return e, nil 221 | } 222 | 223 | // fddata is the filedescriptor data 224 | // 225 | // Example BPF Program File Descriptor: 226 | // 227 | // [root@emily]: /proc/141735/fdinfo># cat 17 228 | // pos: 0 229 | // flags: 02000000 230 | // mnt_id: 15 231 | // ino: 10586 232 | // link_type: perf 233 | // link_id: 19 234 | // prog_tag: 40bd9646d9b53ff8 235 | // prog_id: 106 236 | // pos: 0 237 | // 238 | // flags: 02000000 239 | // mnt_id: 15 240 | // ino: 11861 241 | // link_type: raw_tracepoint 242 | // link_id: 28 243 | // prog_tag: 1b9b934ffae90cca 244 | // prog_id: 16097 245 | // tp_name: sys_enter 246 | // 247 | func programDetails(p *Process, fddata string) string { 248 | linkType := procfs.FileKeyValue(fddata, "link_type") 249 | progId := procfs.FileKeyValue(fddata, "prog_id") 250 | tpName := procfs.FileKeyValue(fddata, "tp_name") 251 | var progDetails string 252 | if tpName == "" { 253 | // Ignore programs with a tracepoint name 254 | return "" 255 | } 256 | progDetails = tpName 257 | if linkType != "" { 258 | progDetails = fmt.Sprintf("%s %s", progDetails, linkType) 259 | } 260 | if progId != "" { 261 | progDetails = fmt.Sprintf("%s %s", progDetails, progId) 262 | } 263 | return strings.TrimSpace(progDetails) 264 | } 265 | 266 | // fddata is the filedescriptor data 267 | // 268 | // Example BPF Map File Descriptor: 269 | // 270 | // [root@alice]: /proc/2582229># cat fdinfo/11 271 | // pos: 0 272 | // flags: 02000002 273 | // mnt_id: 15 274 | // ino: 11861 275 | // map_type: 1 276 | // key_size: 20 277 | // value_size: 48 278 | // max_entries: 65535 279 | // map_flags: 0x1 280 | // map_extra: 0x0 281 | // memlock: 4718592 282 | // map_id: 79 283 | // frozen: 0 284 | func mapDetails(p *Process, fddata string) string { 285 | var mapDetails string 286 | mapType := procfs.FileKeyValue(fddata, "map_type") 287 | i, err := strconv.Atoi(mapType) 288 | if err == nil { 289 | mapDetails = libxpid.BPFMapType(i) 290 | } else { 291 | mapDetails = mapType 292 | } 293 | return mapDetails 294 | } 295 | -------------------------------------------------------------------------------- /pkg/api/v1/module_namespace.go: -------------------------------------------------------------------------------- 1 | /*===========================================================================*\ 2 | * MIT License Copyright (c) 2022 Kris Nóva * 3 | * * 4 | * ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ * 5 | * ┃ ███╗ ██╗ ██████╗ ██╗ ██╗ █████╗ ┃ * 6 | * ┃ ████╗ ██║██╔═████╗██║ ██║██╔══██╗ ┃ * 7 | * ┃ ██╔██╗ ██║██║██╔██║██║ ██║███████║ ┃ * 8 | * ┃ ██║╚██╗██║████╔╝██║╚██╗ ██╔╝██╔══██║ ┃ * 9 | * ┃ ██║ ╚████║╚██████╔╝ ╚████╔╝ ██║ ██║ ┃ * 10 | * ┃ ╚═╝ ╚═══╝ ╚═════╝ ╚═══╝ ╚═╝ ╚═╝ ┃ * 11 | * ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ * 12 | * * 13 | * This machine kills fascists. * 14 | * * 15 | \*===========================================================================*/ 16 | 17 | package v1 18 | 19 | import ( 20 | "fmt" 21 | "strings" 22 | 23 | "github.com/kris-nova/xpid/pkg/procfs" 24 | ) 25 | 26 | var _ ProcessExplorerModule = &NamespaceModule{} 27 | 28 | const ( 29 | 30 | // Only support a few namespaces for now 31 | // We can easily plumb more namespaces through 32 | // as needed 33 | 34 | NamespaceMount string = "mnt" 35 | NamespaceIPC string = "ipc" 36 | NamespaceCgroup string = "cgroup" 37 | NamespacePid string = "pid" 38 | NamespaceNet string = "net" 39 | NamespaceUTS string = "uts" 40 | NamespaceTime string = "time" 41 | ) 42 | 43 | // [root@alice]: /proc/331236/ns># ls 44 | // cgroup@ mnt@ pid@ time@ user@ 45 | // ipc@ net@ pid_for_children@ time_for_children@ uts@ 46 | 47 | func NewNamespaceModule() *NamespaceModule { 48 | return &NamespaceModule{} 49 | } 50 | 51 | func (m *NamespaceModule) Meta() *Meta { 52 | return &Meta{ 53 | Name: "Namespace module", 54 | Description: "Search proc(5) filesystems for namespace meta.", 55 | Authors: []string{ 56 | "Kris Nóva ", 57 | }, 58 | } 59 | } 60 | 61 | type NamespaceModule struct { 62 | Net string `json:"net,omitempty"` 63 | PID string `json:"pid,omitempty"` 64 | Cgroup string `json:"cgroup,omitempty"` 65 | IPC string `json:"ipc,omitempty"` 66 | Mount string `json:"mnt,omitempty"` 67 | UTS string `json:"uts,omitempty"` 68 | Time string `json:"time,omitempty"` 69 | } 70 | 71 | func (m *NamespaceModule) Execute(p *Process) error { 72 | // Module specific (correlated) 73 | p.NamespaceModule.Net = nsString(p.PID, NamespaceNet) 74 | p.NamespaceModule.IPC = nsString(p.PID, NamespaceIPC) 75 | p.NamespaceModule.PID = nsString(p.PID, NamespacePid) 76 | p.NamespaceModule.Cgroup = nsString(p.PID, NamespaceCgroup) 77 | p.NamespaceModule.Mount = nsString(p.PID, NamespaceMount) 78 | p.NamespaceModule.UTS = nsString(p.PID, NamespaceUTS) 79 | p.NamespaceModule.Time = nsString(p.PID, NamespaceTime) 80 | return nil 81 | } 82 | 83 | func nsString(pid int64, ns string) string { 84 | procfshandle := procfs.NewProcFileSystem(procfs.Proc()) 85 | content, _ := procfshandle.ReadlinkPID(pid, fmt.Sprintf("ns/%s", ns)) 86 | if content == "" { 87 | return content 88 | } 89 | spl := strings.Split(content, ":") 90 | if len(spl) < 2 { 91 | return content 92 | } 93 | nsBracket := spl[1] 94 | nsBracket = strings.Replace(nsBracket, "[", "", 1) 95 | nsBracket = strings.Replace(nsBracket, "]", "", 1) 96 | return strings.TrimSpace(nsBracket) 97 | } 98 | -------------------------------------------------------------------------------- /pkg/api/v1/module_proc.go: -------------------------------------------------------------------------------- 1 | /*===========================================================================*\ 2 | * MIT License Copyright (c) 2022 Kris Nóva * 3 | * * 4 | * ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ * 5 | * ┃ ███╗ ██╗ ██████╗ ██╗ ██╗ █████╗ ┃ * 6 | * ┃ ████╗ ██║██╔═████╗██║ ██║██╔══██╗ ┃ * 7 | * ┃ ██╔██╗ ██║██║██╔██║██║ ██║███████║ ┃ * 8 | * ┃ ██║╚██╗██║████╔╝██║╚██╗ ██╔╝██╔══██║ ┃ * 9 | * ┃ ██║ ╚████║╚██████╔╝ ╚████╔╝ ██║ ██║ ┃ * 10 | * ┃ ╚═╝ ╚═══╝ ╚═════╝ ╚═══╝ ╚═╝ ╚═╝ ┃ * 11 | * ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ * 12 | * * 13 | * This machine kills fascists. * 14 | * * 15 | \*===========================================================================*/ 16 | 17 | package v1 18 | 19 | import ( 20 | "os/user" 21 | "strconv" 22 | "strings" 23 | 24 | "github.com/kris-nova/xpid/pkg/procfs" 25 | 26 | "github.com/kris-nova/xpid/pkg/libxpid" 27 | ) 28 | 29 | var _ ProcessExplorerModule = &ProcModule{} 30 | 31 | type ProcModule struct { 32 | Opendir int `json:"opendir,omitempty"` 33 | Chdir int `json:"chdir,omitempty"` 34 | Dent int `json:"dents,omitempty"` 35 | Comm string `json:"comm,omitempty"` 36 | Cmdline string `json:"cmdline,omitempty"` 37 | Status string `json:"status,omitempty"` 38 | } 39 | 40 | func NewProcModule() *ProcModule { 41 | return &ProcModule{} 42 | } 43 | 44 | func (m *ProcModule) Meta() *Meta { 45 | return &Meta{ 46 | Name: "Proc module", 47 | Description: "Search proc(5) filesystems for pid information. Will do an in depth scan and search for obfuscated directories.", 48 | Authors: []string{ 49 | "Kris Nóva ", 50 | }, 51 | } 52 | } 53 | 54 | func (m *ProcModule) Execute(p *Process) error { 55 | // Module specific (correlated) 56 | 57 | // Process visibility 58 | p.ProcessVisible.Opendir = libxpid.ProcDirOpendir(p.PID) 59 | p.ProcessVisible.Chdir = libxpid.ProcDirChdir(p.PID) 60 | p.ProcessVisible.Dent = libxpid.ProcDirDent(p.PID) 61 | 62 | // Meta (comm. cmdline, status) 63 | procfshandle := procfs.NewProcFileSystem(procfs.Proc()) 64 | comm, _ := procfshandle.ContentsPID(p.PID, "comm") 65 | p.ProcModule.Comm = strings.TrimSpace(comm) 66 | p.Name = p.ProcModule.Comm 67 | cmdline, _ := procfshandle.ContentsPID(p.PID, "cmdline") 68 | p.ProcModule.Cmdline = strings.TrimSpace(cmdline) 69 | p.CommandLine = p.ProcModule.Cmdline 70 | status, _ := procfshandle.ContentsPID(p.PID, "status") 71 | p.Status = strings.TrimSpace(status) 72 | 73 | // User 74 | uidMap, _ := procfshandle.ContentsPID(p.PID, "uid_map") 75 | p.User.ID = IDFromMap(uidMap) 76 | u, _ := user.LookupId(IDFromMapString(uidMap)) 77 | if u != nil { 78 | p.User.User = *u 79 | p.User.Name = u.Username 80 | p.User.Name = u.Username 81 | } 82 | 83 | // Group 84 | gidMap, _ := procfshandle.ContentsPID(p.PID, "gid_map") 85 | p.User.Group.ID = IDFromMap(gidMap) 86 | g, _ := user.LookupGroupId(IDFromMapString(gidMap)) 87 | if g != nil { 88 | p.User.Group.Name = g.Name 89 | } 90 | 91 | // Higher level process (blind) 92 | // Map to higher abstraction here 93 | p.Thread = StatusFileIsThread(p.Status) 94 | 95 | return nil 96 | } 97 | 98 | // IDFromMap returns the first value in uid_map and gid_map in /proc 99 | func IDFromMap(mp string) int { 100 | retstr := "" 101 | mp = strings.TrimSpace(mp) 102 | spl := strings.Split(mp, " ") 103 | retstr = spl[0] // Should always be the first value 104 | reti, err := strconv.Atoi(retstr) 105 | if err != nil { 106 | return -1 107 | } 108 | return reti 109 | } 110 | 111 | func IDFromMapString(mp string) string { 112 | mp = strings.TrimSpace(mp) 113 | spl := strings.Split(mp, " ") 114 | return spl[0] 115 | } 116 | 117 | func StatusFileIsThread(status string) bool { 118 | tgid := procfs.FileKeyValue(status, "Tgid") 119 | pid := procfs.FileKeyValue(status, "Pid") 120 | if tgid != "" && pid != "" { 121 | if tgid != pid { 122 | return true 123 | } 124 | } 125 | return false 126 | } 127 | -------------------------------------------------------------------------------- /pkg/api/v1/process.go: -------------------------------------------------------------------------------- 1 | /*===========================================================================*\ 2 | * MIT License Copyright (c) 2022 Kris Nóva * 3 | * * 4 | * ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ * 5 | * ┃ ███╗ ██╗ ██████╗ ██╗ ██╗ █████╗ ┃ * 6 | * ┃ ████╗ ██║██╔═████╗██║ ██║██╔══██╗ ┃ * 7 | * ┃ ██╔██╗ ██║██║██╔██║██║ ██║███████║ ┃ * 8 | * ┃ ██║╚██╗██║████╔╝██║╚██╗ ██╔╝██╔══██║ ┃ * 9 | * ┃ ██║ ╚████║╚██████╔╝ ╚████╔╝ ██║ ██║ ┃ * 10 | * ┃ ╚═╝ ╚═══╝ ╚═════╝ ╚═══╝ ╚═╝ ╚═╝ ┃ * 11 | * ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ * 12 | * * 13 | * This machine kills fascists. * 14 | * * 15 | \*===========================================================================*/ 16 | 17 | package v1 18 | 19 | import "os/user" 20 | 21 | // arch_status cpu_resctrl_groups latency net/ root@ statm 22 | // attr/ cpuset limits ns/ sched status 23 | // autogroup cwd@ loginuid numa_maps schedstat syscall 24 | // auxv environ map_files/ oom_adj sessionid task/ 25 | // cgroup exe@ maps oom_score setgroups timens_offsets 26 | // clear_refs fd/ mem oom_score_adj smaps timers 27 | // cmdline fdinfo/ mountinfo pagemap smaps_rollup timerslack_ns 28 | // comm gid_map mounts personality stack uid_map 29 | // coredump_filter io mountstats projid_map stat wchan 30 | 31 | // Process is a Linux process abstraction. 32 | // By design this will have fields that are available in /proc and much more. 33 | // 34 | // Some of these fields will be calculated at runtime based on certain situations 35 | // in the system. 36 | // 37 | // This data structure and the logic that populates it will be a substantial part 38 | // of the xpid library, and the xpid API. 39 | type Process struct { 40 | 41 | // The process unique ID. 42 | PID int64 `json:"pid,omitempty"` 43 | 44 | // showHeader is used to also encode the header for runtime encoders. 45 | // This should never be used by formatting encoders such as JSON or similar. 46 | ShowHeader bool `json:"-"` 47 | DrawLineAfter bool `json:"-"` 48 | 49 | // ProcessVisible is a combination of the values 50 | // we get from libxpid that will determine if a process 51 | // running in Linux is visible or not 52 | ProcessVisible `json:"processVisible,omitempty"` 53 | 54 | // User is the user associated with the process 55 | // 56 | // Group is a subset of User 57 | User `json:"user,omitempty"` 58 | 59 | // Name (proc/[pid]/comm) 60 | // This file exposes the process's comm value—that is, the 61 | // command name associated with the process. 62 | Name string `json:"name,omitempty"` 63 | 64 | // CommandLine (/proc/[pid]/cmdline) 65 | // This read-only file holds the complete command line for 66 | // the process, unless the process is a zombie. In the 67 | // latter case, there is nothing in this file: that is, a 68 | // read on this file will return 0 characters 69 | CommandLine string `json:"commandLine,omitempty"` 70 | 71 | // EBPF is an xpid specific field that attempts to detect 72 | // if a specific PID is running any eBPF related probes or maps. 73 | // 74 | // Will be set to true is eBPF is detected. 75 | EBPF bool `json:"ebpf,omitempty"` 76 | 77 | // Container is an xpid specific field that attempts to detect 78 | // if a specific PID is running as a container 79 | // 80 | // Will be set to true if we suspect this is a container PID 81 | Container bool `json:"container,omitempty"` 82 | 83 | // Thread is a bool that will be set if the process is part of 84 | // a thread pool, or has a TGID that is unique from PID 85 | // 86 | // Reading from /proc/[pid]/status 87 | Thread bool `json:"thread,omitempty"` 88 | 89 | // Modules are embedded directly here 90 | 91 | EBPFModule `json:"ebpfModule,omitempty"` 92 | ProcModule `json:"procModule,omitempty"` 93 | ContainerModule `json:"containerModule,omitempty"` 94 | NamespaceModule `json:"namespaceModule,omitempty"` 95 | } 96 | 97 | // User is a user from the filesystem 98 | // This can be populated from process details, or from 99 | // the current user context. 100 | type User struct { 101 | user.User `json:"-"` 102 | Group `json:"group,omitempty"` 103 | ID int `json:"uid,omitempty"` 104 | Name string `json:"name,omitempty"` 105 | } 106 | 107 | type Group struct { 108 | ID int `json:"gid,omitempty"` 109 | Name string `json:"name,omitempty"` 110 | } 111 | 112 | type ProcessVisible struct { 113 | 114 | // Opendir is if the /proc/[pid] directory can be "opened" or "listed". 115 | // Failing Opendir is a sign that the process may be attempted to being 116 | // obfuscated to the user at runtime. 117 | Opendir int `json:"opendir,omitempty"` 118 | 119 | // Chdir is if the /proc/[pid] directory can be "navigated" or "changed to". 120 | // Failing chdir is a sign that the current user has invalid permission, 121 | // or that something in the kernel is preventing the user from open the directory. 122 | Chdir int `json:"chdir,omitempty"` 123 | 124 | // After opendir we see if we can "list" files inside of the directory. 125 | // This call happens at a higher level and will see if a directory 126 | // within proc can be found by opening it's parent directory for listing. 127 | // 128 | // In this case /proc is typically opened, and then the pid directories are 129 | // matched against! 130 | Dent int `json:"dents,omitempty"` 131 | } 132 | 133 | func ProcessPID(pid int64) *Process { 134 | return &Process{ 135 | ProcessVisible: ProcessVisible{}, 136 | User: User{}, 137 | PID: pid, 138 | EBPFModule: EBPFModule{}, 139 | ProcModule: ProcModule{}, 140 | ContainerModule: ContainerModule{}, 141 | NamespaceModule: NamespaceModule{}, 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /pkg/encoders/encoder.go: -------------------------------------------------------------------------------- 1 | package encoder 2 | 3 | import ( 4 | api "github.com/kris-nova/xpid/pkg/api/v1" 5 | filter "github.com/kris-nova/xpid/pkg/filters" 6 | ) 7 | 8 | type ProcessExplorerEncoder interface { 9 | AddFilter(f filter.ProcessFilter) 10 | Encode(p *api.Process) ([]byte, error) 11 | EncodeAll(p *api.Process) ([]byte, error) 12 | EncodeUser(u *api.User) ([]byte, error) 13 | } 14 | -------------------------------------------------------------------------------- /pkg/encoders/json/encoder.go: -------------------------------------------------------------------------------- 1 | /*===========================================================================*\ 2 | * MIT License Copyright (c) 2022 Kris Nóva * 3 | * * 4 | * ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ * 5 | * ┃ ███╗ ██╗ ██████╗ ██╗ ██╗ █████╗ ┃ * 6 | * ┃ ████╗ ██║██╔═████╗██║ ██║██╔══██╗ ┃ * 7 | * ┃ ██╔██╗ ██║██║██╔██║██║ ██║███████║ ┃ * 8 | * ┃ ██║╚██╗██║████╔╝██║╚██╗ ██╔╝██╔══██║ ┃ * 9 | * ┃ ██║ ╚████║╚██████╔╝ ╚████╔╝ ██║ ██║ ┃ * 10 | * ┃ ╚═╝ ╚═══╝ ╚═════╝ ╚═══╝ ╚═╝ ╚═╝ ┃ * 11 | * ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ * 12 | * * 13 | * This machine kills fascists. * 14 | * * 15 | \*===========================================================================*/ 16 | 17 | package json 18 | 19 | import ( 20 | "encoding/json" 21 | "fmt" 22 | 23 | encoder "github.com/kris-nova/xpid/pkg/encoders" 24 | 25 | filter "github.com/kris-nova/xpid/pkg/filters" 26 | 27 | api "github.com/kris-nova/xpid/pkg/api/v1" 28 | ) 29 | 30 | var _ encoder.ProcessExplorerEncoder = &JSONEncoder{} 31 | 32 | type JSONEncoder struct { 33 | filters []filter.ProcessFilter 34 | } 35 | 36 | func (j *JSONEncoder) EncodeAll(p *api.Process) ([]byte, error) { 37 | return j.Encode(p) 38 | } 39 | 40 | func (j *JSONEncoder) EncodeUser(u *api.User) ([]byte, error) { 41 | return json.Marshal(u) 42 | } 43 | 44 | func (j *JSONEncoder) Encode(p *api.Process) ([]byte, error) { 45 | for _, f := range j.filters { 46 | if !f(p) { 47 | return []byte(""), fmt.Errorf("filtered") 48 | } 49 | } 50 | return json.Marshal(p) 51 | } 52 | 53 | func (j *JSONEncoder) AddFilter(f filter.ProcessFilter) { 54 | j.filters = append(j.filters, f) 55 | } 56 | 57 | func NewJSONEncoder() *JSONEncoder { 58 | return &JSONEncoder{} 59 | } 60 | -------------------------------------------------------------------------------- /pkg/encoders/raw/encoder-color.go: -------------------------------------------------------------------------------- 1 | /*===========================================================================*\ 2 | * MIT License Copyright (c) 2022 Kris Nóva * 3 | * * 4 | * ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ * 5 | * ┃ ███╗ ██╗ ██████╗ ██╗ ██╗ █████╗ ┃ * 6 | * ┃ ████╗ ██║██╔═████╗██║ ██║██╔══██╗ ┃ * 7 | * ┃ ██╔██╗ ██║██║██╔██║██║ ██║███████║ ┃ * 8 | * ┃ ██║╚██╗██║████╔╝██║╚██╗ ██╔╝██╔══██║ ┃ * 9 | * ┃ ██║ ╚████║╚██████╔╝ ╚████╔╝ ██║ ██║ ┃ * 10 | * ┃ ╚═╝ ╚═══╝ ╚═════╝ ╚═══╝ ╚═╝ ╚═╝ ┃ * 11 | * ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ * 12 | * * 13 | * This machine kills fascists. * 14 | * * 15 | \*===========================================================================*/ 16 | 17 | package raw 18 | 19 | import ( 20 | "fmt" 21 | 22 | "github.com/fatih/color" 23 | 24 | api "github.com/kris-nova/xpid/pkg/api/v1" 25 | ) 26 | 27 | var _ Formatter = ColorFormatter 28 | 29 | func ColorFormatter(p *api.Process) string { 30 | pidColor := color.New(color.FgBlue, color.Bold) 31 | cliColor := color.New(color.FgYellow, color.Italic) 32 | ugColor := color.New(color.FgCyan, color.Italic) 33 | 34 | // Default color formatter 35 | return fmt.Sprintf("[%s] %s(%s):%s(%s) %s (%s)\n", 36 | pidColor.Sprintf("%d", p.PID), 37 | ugColor.Sprintf("%s", p.User.Name), 38 | ugColor.Sprintf("%d", p.User.ID), 39 | ugColor.Sprintf("%s", p.User.Group.Name), 40 | ugColor.Sprintf("%d", p.User.Group.ID), 41 | color.GreenString("%s", p.Name), 42 | cliColor.Sprintf("%s", p.CommandLine)) 43 | } 44 | -------------------------------------------------------------------------------- /pkg/encoders/raw/encoder.go: -------------------------------------------------------------------------------- 1 | /*===========================================================================*\ 2 | * MIT License Copyright (c) 2022 Kris Nóva * 3 | * * 4 | * ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ * 5 | * ┃ ███╗ ██╗ ██████╗ ██╗ ██╗ █████╗ ┃ * 6 | * ┃ ████╗ ██║██╔═████╗██║ ██║██╔══██╗ ┃ * 7 | * ┃ ██╔██╗ ██║██║██╔██║██║ ██║███████║ ┃ * 8 | * ┃ ██║╚██╗██║████╔╝██║╚██╗ ██╔╝██╔══██║ ┃ * 9 | * ┃ ██║ ╚████║╚██████╔╝ ╚████╔╝ ██║ ██║ ┃ * 10 | * ┃ ╚═╝ ╚═══╝ ╚═════╝ ╚═══╝ ╚═╝ ╚═╝ ┃ * 11 | * ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ * 12 | * * 13 | * This machine kills fascists. * 14 | * * 15 | \*===========================================================================*/ 16 | 17 | package raw 18 | 19 | import ( 20 | "fmt" 21 | 22 | encoder "github.com/kris-nova/xpid/pkg/encoders" 23 | 24 | filter "github.com/kris-nova/xpid/pkg/filters" 25 | 26 | api "github.com/kris-nova/xpid/pkg/api/v1" 27 | ) 28 | 29 | var _ encoder.ProcessExplorerEncoder = &RawEncoder{} 30 | 31 | type RawEncoder struct { 32 | filters []filter.ProcessFilter 33 | format Formatter 34 | } 35 | 36 | func (r *RawEncoder) EncodeAll(p *api.Process) ([]byte, error) { 37 | return r.Encode(p) 38 | } 39 | 40 | func (r *RawEncoder) EncodeUser(u *api.User) ([]byte, error) { 41 | str := fmt.Sprintf("\n[%s] %d [%s] %d\n\n", 42 | u.Name, 43 | u.ID, 44 | u.Group.Name, 45 | u.Group.ID) 46 | return []byte(str), nil 47 | } 48 | 49 | type Formatter func(p *api.Process) string 50 | 51 | var _ Formatter = DefaultFormatter 52 | 53 | func DefaultFormatter(p *api.Process) string { 54 | return fmt.Sprintf("[%d] %s(%d):%s(%d) %s (%s)\n", 55 | p.PID, 56 | p.User.Name, 57 | p.User.ID, 58 | p.User.Group.Name, 59 | p.User.Group.ID, 60 | p.Name, 61 | p.CommandLine) 62 | } 63 | 64 | func (r *RawEncoder) SetFormat(f Formatter) { 65 | r.format = f 66 | } 67 | 68 | func (r *RawEncoder) Encode(p *api.Process) ([]byte, error) { 69 | for _, f := range r.filters { 70 | x := f(p) 71 | if !x { 72 | return []byte(""), fmt.Errorf("filtered") 73 | } 74 | } 75 | return []byte(r.format(p)), nil 76 | } 77 | 78 | func (r *RawEncoder) AddFilter(f filter.ProcessFilter) { 79 | r.filters = append(r.filters, f) 80 | } 81 | 82 | func NewRawEncoder() *RawEncoder { 83 | return &RawEncoder{ 84 | format: DefaultFormatter, 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /pkg/encoders/table/encoder.go: -------------------------------------------------------------------------------- 1 | /*===========================================================================*\ 2 | * MIT License Copyright (c) 2022 Kris Nóva * 3 | * * 4 | * ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ * 5 | * ┃ ███╗ ██╗ ██████╗ ██╗ ██╗ █████╗ ┃ * 6 | * ┃ ████╗ ██║██╔═████╗██║ ██║██╔══██╗ ┃ * 7 | * ┃ ██╔██╗ ██║██║██╔██║██║ ██║███████║ ┃ * 8 | * ┃ ██║╚██╗██║████╔╝██║╚██╗ ██╔╝██╔══██║ ┃ * 9 | * ┃ ██║ ╚████║╚██████╔╝ ╚████╔╝ ██║ ██║ ┃ * 10 | * ┃ ╚═╝ ╚═══╝ ╚═════╝ ╚═══╝ ╚═╝ ╚═╝ ┃ * 11 | * ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ * 12 | * * 13 | * This machine kills fascists. * 14 | * * 15 | \*===========================================================================*/ 16 | 17 | package table 18 | 19 | import ( 20 | "fmt" 21 | 22 | "github.com/fatih/color" 23 | encoder "github.com/kris-nova/xpid/pkg/encoders" 24 | "golang.org/x/term" 25 | 26 | filter "github.com/kris-nova/xpid/pkg/filters" 27 | 28 | api "github.com/kris-nova/xpid/pkg/api/v1" 29 | ) 30 | 31 | var _ encoder.ProcessExplorerEncoder = &TableEncoder{} 32 | 33 | type TableEncoder struct { 34 | filters []filter.ProcessFilter 35 | } 36 | 37 | func (j *TableEncoder) EncodeAll(p *api.Process) ([]byte, error) { 38 | return j.Encode(p) 39 | } 40 | 41 | func (j *TableEncoder) EncodeUser(u *api.User) ([]byte, error) { 42 | var str string 43 | 44 | // Header 45 | var hdr string 46 | hdr += fmt.Sprintf("%-*s", len(u.Name)+3, "USER") 47 | hdr += fmt.Sprintf("%-*s", 5, "UID") 48 | hdr += fmt.Sprintf("%-*s", len(u.Group.Name)+3, "GROUP") 49 | hdr += fmt.Sprintf("%-*s", 5, "GID") 50 | hdr += fmt.Sprintf("\n") 51 | str += drawLine("─") 52 | str += color.GreenString(hdr) 53 | 54 | // First line 55 | str += fmt.Sprintf("%-*s", len(u.Name)+3, u.Name) 56 | str += fmt.Sprintf("%-*d", 5, u.ID) 57 | str += fmt.Sprintf("%-*s", len(u.Group.Name)+3, u.Group.Name) 58 | str += fmt.Sprintf("%-*d", 5, u.Group.ID) 59 | str += fmt.Sprintf("\n") 60 | str += drawLine("─") 61 | 62 | return []byte(str), nil 63 | } 64 | 65 | var ( 66 | TableFmtNS bool = false 67 | TableFmtBPF bool = false 68 | ) 69 | 70 | func (j *TableEncoder) Encode(p *api.Process) ([]byte, error) { 71 | for _, f := range j.filters { 72 | if !f(p) { 73 | return []byte(""), fmt.Errorf(filter.Filtered) 74 | } 75 | } 76 | 77 | var str string 78 | var hdr string 79 | if p.ShowHeader { 80 | // Header 81 | hdr += fmt.Sprintf("%-9s", "PID") 82 | hdr += fmt.Sprintf("%-9s", "USER") 83 | hdr += fmt.Sprintf("%-9s", "GROUP") 84 | hdr += fmt.Sprintf("%-24s", "CMD") 85 | 86 | if TableFmtNS { 87 | hdr += fmt.Sprintf("%-12s", "NS-PID") // Compute 88 | hdr += fmt.Sprintf("%-12s", "NS-CGROUP") // Compute 89 | hdr += fmt.Sprintf("%-12s", "NS-NET") // Network 90 | hdr += fmt.Sprintf("%-12s", "NS-MNT") // Storage 91 | } 92 | if TableFmtBPF { 93 | hdr += fmt.Sprintf("%-36s", "BPF-MAP") 94 | hdr += fmt.Sprintf("%-36s", "BPF-PROG") 95 | } 96 | hdr += fmt.Sprintf("\n") 97 | hdrColor := color.New(color.FgGreen) 98 | hdr = hdrColor.Sprintf(hdr) 99 | str += hdr 100 | } 101 | 102 | // Lines 103 | x := 0 104 | str += color.YellowString(fmt.Sprintf("%-9d", p.PID)) 105 | str += fmt.Sprintf("%-9s", p.User.Name) 106 | str += fmt.Sprintf("%-9s", p.User.Group.Name) 107 | str += color.CyanString(fmt.Sprintf("%-24s", p.ProcModule.Comm)) 108 | x = x + 51 109 | if TableFmtNS { 110 | str += fmt.Sprintf("%-12s", p.NamespaceModule.PID) 111 | str += fmt.Sprintf("%-12s", p.NamespaceModule.Cgroup) 112 | str += fmt.Sprintf("%-12s", p.NamespaceModule.Net) 113 | str += fmt.Sprintf("%-12s", p.NamespaceModule.Mount) 114 | x = x + 48 115 | } 116 | var div bool 117 | if TableFmtBPF { 118 | var l, lm, lp int 119 | lm = len(p.EBPFModule.Maps) 120 | lp = len(p.EBPFModule.Progs) 121 | if lp > lm { 122 | l = lp 123 | } else { 124 | l = lm 125 | } 126 | n := false 127 | for i := 0; i < l; i++ { 128 | if n { 129 | str += fmt.Sprintf("%-*s", x, "") 130 | } 131 | if lm >= i+1 { 132 | str += fmt.Sprintf("%-36s", p.EBPFModule.Maps[i]) 133 | } else { 134 | str += fmt.Sprintf("%-36s", "╌╌╌╌") 135 | } 136 | if lp >= i+1 { 137 | str += fmt.Sprintf("%-36s", p.EBPFModule.Progs[i]) 138 | } else { 139 | str += fmt.Sprintf("%-36s", "╌╌╌╌") 140 | } 141 | if i+1 != l { 142 | str += "\n" 143 | } 144 | n = true 145 | } 146 | if l > 0 { 147 | div = true 148 | } 149 | } 150 | str += fmt.Sprintf("\n") 151 | if div { 152 | str += drawLine("╌") 153 | } 154 | if p.DrawLineAfter { 155 | str += drawLine("━") 156 | } 157 | 158 | return []byte(str), nil 159 | 160 | } 161 | 162 | func (j *TableEncoder) AddFilter(f filter.ProcessFilter) { 163 | j.filters = append(j.filters, f) 164 | } 165 | 166 | func NewTableEncoder() *TableEncoder { 167 | return &TableEncoder{} 168 | } 169 | 170 | func drawLine(ch string) string { 171 | y, _, _ := term.GetSize(0) 172 | if y == 0 { 173 | return "" 174 | } 175 | lc := color.New(color.Bold, color.FgGreen) 176 | var str string 177 | for i := 0; i < y; i++ { 178 | str += lc.Sprintf("%s", ch) 179 | } 180 | str += "\n" 181 | return str 182 | } 183 | -------------------------------------------------------------------------------- /pkg/filters/filter.go: -------------------------------------------------------------------------------- 1 | /*===========================================================================*\ 2 | * MIT License Copyright (c) 2022 Kris Nóva * 3 | * * 4 | * ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ * 5 | * ┃ ███╗ ██╗ ██████╗ ██╗ ██╗ █████╗ ┃ * 6 | * ┃ ████╗ ██║██╔═████╗██║ ██║██╔══██╗ ┃ * 7 | * ┃ ██╔██╗ ██║██║██╔██║██║ ██║███████║ ┃ * 8 | * ┃ ██║╚██╗██║████╔╝██║╚██╗ ██╔╝██╔══██║ ┃ * 9 | * ┃ ██║ ╚████║╚██████╔╝ ╚████╔╝ ██║ ██║ ┃ * 10 | * ┃ ╚═╝ ╚═══╝ ╚═════╝ ╚═══╝ ╚═╝ ╚═╝ ┃ * 11 | * ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ * 12 | * * 13 | * This machine kills fascists. * 14 | * * 15 | \*===========================================================================*/ 16 | 17 | package filter 18 | 19 | import api "github.com/kris-nova/xpid/pkg/api/v1" 20 | 21 | const ( 22 | Filtered string = "filtered" 23 | ) 24 | 25 | // Various filters will require different assertions 26 | // based on the runtime context which can be set below 27 | 28 | var PidOne *api.Process 29 | var CurrentPid *api.Process 30 | var CurrentUser *api.User 31 | 32 | // ProcessFilter will be an arbitrary filter that can be used to filter 33 | // PID data from xpid. 34 | // 35 | // TRUE (Retain the process) 36 | // FALSE (Ignore the process) 37 | type ProcessFilter func(p *api.Process) bool 38 | -------------------------------------------------------------------------------- /pkg/filters/filter_containers.go: -------------------------------------------------------------------------------- 1 | /*===========================================================================*\ 2 | * MIT License Copyright (c) 2022 Kris Nóva * 3 | * * 4 | * ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ * 5 | * ┃ ███╗ ██╗ ██████╗ ██╗ ██╗ █████╗ ┃ * 6 | * ┃ ████╗ ██║██╔═████╗██║ ██║██╔══██╗ ┃ * 7 | * ┃ ██╔██╗ ██║██║██╔██║██║ ██║███████║ ┃ * 8 | * ┃ ██║╚██╗██║████╔╝██║╚██╗ ██╔╝██╔══██║ ┃ * 9 | * ┃ ██║ ╚████║╚██████╔╝ ╚████╔╝ ██║ ██║ ┃ * 10 | * ┃ ╚═╝ ╚═══╝ ╚═════╝ ╚═══╝ ╚═╝ ╚═╝ ┃ * 11 | * ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ * 12 | * * 13 | * This machine kills fascists. * 14 | * * 15 | \*===========================================================================*/ 16 | 17 | package filter 18 | 19 | import ( 20 | api "github.com/kris-nova/xpid/pkg/api/v1" 21 | ) 22 | 23 | var _ ProcessFilter = RetainOnlyEBPF 24 | 25 | func RetainOnlyContainers(p *api.Process) bool { 26 | if p.Container { 27 | return true 28 | } 29 | return false 30 | } 31 | -------------------------------------------------------------------------------- /pkg/filters/filter_ebpf.go: -------------------------------------------------------------------------------- 1 | /*===========================================================================*\ 2 | * MIT License Copyright (c) 2022 Kris Nóva * 3 | * * 4 | * ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ * 5 | * ┃ ███╗ ██╗ ██████╗ ██╗ ██╗ █████╗ ┃ * 6 | * ┃ ████╗ ██║██╔═████╗██║ ██║██╔══██╗ ┃ * 7 | * ┃ ██╔██╗ ██║██║██╔██║██║ ██║███████║ ┃ * 8 | * ┃ ██║╚██╗██║████╔╝██║╚██╗ ██╔╝██╔══██║ ┃ * 9 | * ┃ ██║ ╚████║╚██████╔╝ ╚████╔╝ ██║ ██║ ┃ * 10 | * ┃ ╚═╝ ╚═══╝ ╚═════╝ ╚═══╝ ╚═╝ ╚═╝ ┃ * 11 | * ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ * 12 | * * 13 | * This machine kills fascists. * 14 | * * 15 | \*===========================================================================*/ 16 | 17 | package filter 18 | 19 | import ( 20 | api "github.com/kris-nova/xpid/pkg/api/v1" 21 | ) 22 | 23 | var _ ProcessFilter = RetainOnlyEBPF 24 | 25 | func RetainOnlyEBPF(p *api.Process) bool { 26 | if p.EBPF { 27 | return true 28 | } 29 | return false 30 | } 31 | -------------------------------------------------------------------------------- /pkg/filters/filter_empty.go: -------------------------------------------------------------------------------- 1 | /*===========================================================================*\ 2 | * MIT License Copyright (c) 2022 Kris Nóva * 3 | * * 4 | * ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ * 5 | * ┃ ███╗ ██╗ ██████╗ ██╗ ██╗ █████╗ ┃ * 6 | * ┃ ████╗ ██║██╔═████╗██║ ██║██╔══██╗ ┃ * 7 | * ┃ ██╔██╗ ██║██║██╔██║██║ ██║███████║ ┃ * 8 | * ┃ ██║╚██╗██║████╔╝██║╚██╗ ██╔╝██╔══██║ ┃ * 9 | * ┃ ██║ ╚████║╚██████╔╝ ╚████╔╝ ██║ ██║ ┃ * 10 | * ┃ ╚═╝ ╚═══╝ ╚═════╝ ╚═══╝ ╚═╝ ╚═╝ ┃ * 11 | * ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ * 12 | * * 13 | * This machine kills fascists. * 14 | * * 15 | \*===========================================================================*/ 16 | 17 | package filter 18 | 19 | import api "github.com/kris-nova/xpid/pkg/api/v1" 20 | 21 | var _ ProcessFilter = RetainOnlyNamed 22 | 23 | // RetainOnlyNamed will only keep "named" PIDs 24 | func RetainOnlyNamed(p *api.Process) bool { 25 | if p.Name != "" || p.Name == "\n" { 26 | return true 27 | } 28 | return false 29 | } 30 | -------------------------------------------------------------------------------- /pkg/filters/filter_hidden.go: -------------------------------------------------------------------------------- 1 | /*===========================================================================*\ 2 | * MIT License Copyright (c) 2022 Kris Nóva * 3 | * * 4 | * ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ * 5 | * ┃ ███╗ ██╗ ██████╗ ██╗ ██╗ █████╗ ┃ * 6 | * ┃ ████╗ ██║██╔═████╗██║ ██║██╔══██╗ ┃ * 7 | * ┃ ██╔██╗ ██║██║██╔██║██║ ██║███████║ ┃ * 8 | * ┃ ██║╚██╗██║████╔╝██║╚██╗ ██╔╝██╔══██║ ┃ * 9 | * ┃ ██║ ╚████║╚██████╔╝ ╚████╔╝ ██║ ██║ ┃ * 10 | * ┃ ╚═╝ ╚═══╝ ╚═════╝ ╚═══╝ ╚═╝ ╚═╝ ┃ * 11 | * ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ * 12 | * * 13 | * This machine kills fascists. * 14 | * * 15 | \*===========================================================================*/ 16 | 17 | package filter 18 | 19 | import ( 20 | api "github.com/kris-nova/xpid/pkg/api/v1" 21 | ) 22 | 23 | var _ ProcessFilter = RetainOnlyHidden 24 | 25 | // RetainOnlyHidden will only keep "hidden" PIDs 26 | func RetainOnlyHidden(p *api.Process) bool { 27 | if p.ProcessVisible.Opendir != p.ProcessVisible.Dent { 28 | return true 29 | } 30 | if p.ProcessVisible.Chdir != p.ProcessVisible.Dent { 31 | return true 32 | } 33 | return false 34 | } 35 | -------------------------------------------------------------------------------- /pkg/filters/filter_namespace_cgroup.go: -------------------------------------------------------------------------------- 1 | /*===========================================================================*\ 2 | * MIT License Copyright (c) 2022 Kris Nóva * 3 | * * 4 | * ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ * 5 | * ┃ ███╗ ██╗ ██████╗ ██╗ ██╗ █████╗ ┃ * 6 | * ┃ ████╗ ██║██╔═████╗██║ ██║██╔══██╗ ┃ * 7 | * ┃ ██╔██╗ ██║██║██╔██║██║ ██║███████║ ┃ * 8 | * ┃ ██║╚██╗██║████╔╝██║╚██╗ ██╔╝██╔══██║ ┃ * 9 | * ┃ ██║ ╚████║╚██████╔╝ ╚████╔╝ ██║ ██║ ┃ * 10 | * ┃ ╚═╝ ╚═══╝ ╚═════╝ ╚═══╝ ╚═╝ ╚═╝ ┃ * 11 | * ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ * 12 | * * 13 | * This machine kills fascists. * 14 | * * 15 | \*===========================================================================*/ 16 | 17 | package filter 18 | 19 | import ( 20 | api "github.com/kris-nova/xpid/pkg/api/v1" 21 | ) 22 | 23 | var NamespaceFilterSet_Cgroup = "" 24 | 25 | func RetainNamespaceIn_Cgroup(p *api.Process) bool { 26 | if p.NamespaceModule.Cgroup == NamespaceFilterSet_Cgroup { 27 | return true 28 | } 29 | return false 30 | } 31 | 32 | func RetainNamespaceOut_Cgroup(p *api.Process) bool { 33 | if p.NamespaceModule.Cgroup == "" { 34 | return false 35 | } 36 | if p.NamespaceModule.Cgroup != NamespaceFilterSet_Cgroup { 37 | return true 38 | } 39 | return false 40 | } 41 | -------------------------------------------------------------------------------- /pkg/filters/filter_namespace_ipc.go: -------------------------------------------------------------------------------- 1 | /*===========================================================================*\ 2 | * MIT License Copyright (c) 2022 Kris Nóva * 3 | * * 4 | * ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ * 5 | * ┃ ███╗ ██╗ ██████╗ ██╗ ██╗ █████╗ ┃ * 6 | * ┃ ████╗ ██║██╔═████╗██║ ██║██╔══██╗ ┃ * 7 | * ┃ ██╔██╗ ██║██║██╔██║██║ ██║███████║ ┃ * 8 | * ┃ ██║╚██╗██║████╔╝██║╚██╗ ██╔╝██╔══██║ ┃ * 9 | * ┃ ██║ ╚████║╚██████╔╝ ╚████╔╝ ██║ ██║ ┃ * 10 | * ┃ ╚═╝ ╚═══╝ ╚═════╝ ╚═══╝ ╚═╝ ╚═╝ ┃ * 11 | * ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ * 12 | * * 13 | * This machine kills fascists. * 14 | * * 15 | \*===========================================================================*/ 16 | 17 | package filter 18 | 19 | import ( 20 | api "github.com/kris-nova/xpid/pkg/api/v1" 21 | ) 22 | 23 | var NamespaceFilterSet_IPC = "" 24 | 25 | func RetainNamespaceIn_IPC(p *api.Process) bool { 26 | if p.NamespaceModule.IPC == NamespaceFilterSet_IPC { 27 | return true 28 | } 29 | return false 30 | } 31 | 32 | func RetainNamespaceOut_IPC(p *api.Process) bool { 33 | if p.NamespaceModule.IPC == "" { 34 | return false 35 | } 36 | if p.NamespaceModule.IPC != NamespaceFilterSet_IPC { 37 | return true 38 | } 39 | return false 40 | } 41 | -------------------------------------------------------------------------------- /pkg/filters/filter_namespace_mount.go: -------------------------------------------------------------------------------- 1 | /*===========================================================================*\ 2 | * MIT License Copyright (c) 2022 Kris Nóva * 3 | * * 4 | * ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ * 5 | * ┃ ███╗ ██╗ ██████╗ ██╗ ██╗ █████╗ ┃ * 6 | * ┃ ████╗ ██║██╔═████╗██║ ██║██╔══██╗ ┃ * 7 | * ┃ ██╔██╗ ██║██║██╔██║██║ ██║███████║ ┃ * 8 | * ┃ ██║╚██╗██║████╔╝██║╚██╗ ██╔╝██╔══██║ ┃ * 9 | * ┃ ██║ ╚████║╚██████╔╝ ╚████╔╝ ██║ ██║ ┃ * 10 | * ┃ ╚═╝ ╚═══╝ ╚═════╝ ╚═══╝ ╚═╝ ╚═╝ ┃ * 11 | * ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ * 12 | * * 13 | * This machine kills fascists. * 14 | * * 15 | \*===========================================================================*/ 16 | 17 | package filter 18 | 19 | import ( 20 | api "github.com/kris-nova/xpid/pkg/api/v1" 21 | ) 22 | 23 | // NamespaceMount string = "mnt" 24 | // NamespaceIPC string = "ipc" 25 | // NamespaceCgroup string = "cgroup" 26 | // NamespacePid string = "pid" 27 | // NamespaceNet string = "net" 28 | // NamespaceUTS string = "uts" 29 | // NamespaceTime string = "time" 30 | 31 | var NamespaceFilterSet_Mount = "" 32 | 33 | func RetainNamespaceIn_Mount(p *api.Process) bool { 34 | if p.NamespaceModule.Mount == NamespaceFilterSet_Mount { 35 | return true 36 | } 37 | return false 38 | } 39 | 40 | func RetainNamespaceOut_Mount(p *api.Process) bool { 41 | if p.NamespaceModule.Mount == "" { 42 | return false 43 | } 44 | if p.NamespaceModule.Mount != NamespaceFilterSet_Mount { 45 | return true 46 | } 47 | return false 48 | } 49 | -------------------------------------------------------------------------------- /pkg/filters/filter_namespace_net.go: -------------------------------------------------------------------------------- 1 | /*===========================================================================*\ 2 | * MIT License Copyright (c) 2022 Kris Nóva * 3 | * * 4 | * ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ * 5 | * ┃ ███╗ ██╗ ██████╗ ██╗ ██╗ █████╗ ┃ * 6 | * ┃ ████╗ ██║██╔═████╗██║ ██║██╔══██╗ ┃ * 7 | * ┃ ██╔██╗ ██║██║██╔██║██║ ██║███████║ ┃ * 8 | * ┃ ██║╚██╗██║████╔╝██║╚██╗ ██╔╝██╔══██║ ┃ * 9 | * ┃ ██║ ╚████║╚██████╔╝ ╚████╔╝ ██║ ██║ ┃ * 10 | * ┃ ╚═╝ ╚═══╝ ╚═════╝ ╚═══╝ ╚═╝ ╚═╝ ┃ * 11 | * ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ * 12 | * * 13 | * This machine kills fascists. * 14 | * * 15 | \*===========================================================================*/ 16 | 17 | package filter 18 | 19 | import ( 20 | api "github.com/kris-nova/xpid/pkg/api/v1" 21 | ) 22 | 23 | var NamespaceFilterSet_Net = "" 24 | 25 | func RetainNamespaceIn_Net(p *api.Process) bool { 26 | if p.NamespaceModule.Net == NamespaceFilterSet_Net { 27 | return true 28 | } 29 | return false 30 | } 31 | 32 | func RetainNamespaceOut_Net(p *api.Process) bool { 33 | if p.NamespaceModule.Net == "" { 34 | return false 35 | } 36 | if p.NamespaceModule.Net != NamespaceFilterSet_Net { 37 | return true 38 | } 39 | return false 40 | } 41 | -------------------------------------------------------------------------------- /pkg/filters/filter_namespace_pid.go: -------------------------------------------------------------------------------- 1 | /*===========================================================================*\ 2 | * MIT License Copyright (c) 2022 Kris Nóva * 3 | * * 4 | * ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ * 5 | * ┃ ███╗ ██╗ ██████╗ ██╗ ██╗ █████╗ ┃ * 6 | * ┃ ████╗ ██║██╔═████╗██║ ██║██╔══██╗ ┃ * 7 | * ┃ ██╔██╗ ██║██║██╔██║██║ ██║███████║ ┃ * 8 | * ┃ ██║╚██╗██║████╔╝██║╚██╗ ██╔╝██╔══██║ ┃ * 9 | * ┃ ██║ ╚████║╚██████╔╝ ╚████╔╝ ██║ ██║ ┃ * 10 | * ┃ ╚═╝ ╚═══╝ ╚═════╝ ╚═══╝ ╚═╝ ╚═╝ ┃ * 11 | * ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ * 12 | * * 13 | * This machine kills fascists. * 14 | * * 15 | \*===========================================================================*/ 16 | 17 | package filter 18 | 19 | import ( 20 | api "github.com/kris-nova/xpid/pkg/api/v1" 21 | ) 22 | 23 | var NamespaceFilterSet_PID = "" 24 | 25 | func RetainNamespaceIn_PID(p *api.Process) bool { 26 | if p.NamespaceModule.PID == NamespaceFilterSet_PID { 27 | return true 28 | } 29 | return false 30 | } 31 | 32 | func RetainNamespaceOut_PID(p *api.Process) bool { 33 | if p.NamespaceModule.PID == "" { 34 | return false 35 | } 36 | if p.NamespaceModule.PID != NamespaceFilterSet_PID { 37 | return true 38 | } 39 | return false 40 | } 41 | -------------------------------------------------------------------------------- /pkg/filters/filter_namespace_time.go: -------------------------------------------------------------------------------- 1 | /*===========================================================================*\ 2 | * MIT License Copyright (c) 2022 Kris Nóva * 3 | * * 4 | * ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ * 5 | * ┃ ███╗ ██╗ ██████╗ ██╗ ██╗ █████╗ ┃ * 6 | * ┃ ████╗ ██║██╔═████╗██║ ██║██╔══██╗ ┃ * 7 | * ┃ ██╔██╗ ██║██║██╔██║██║ ██║███████║ ┃ * 8 | * ┃ ██║╚██╗██║████╔╝██║╚██╗ ██╔╝██╔══██║ ┃ * 9 | * ┃ ██║ ╚████║╚██████╔╝ ╚████╔╝ ██║ ██║ ┃ * 10 | * ┃ ╚═╝ ╚═══╝ ╚═════╝ ╚═══╝ ╚═╝ ╚═╝ ┃ * 11 | * ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ * 12 | * * 13 | * This machine kills fascists. * 14 | * * 15 | \*===========================================================================*/ 16 | 17 | package filter 18 | 19 | import ( 20 | api "github.com/kris-nova/xpid/pkg/api/v1" 21 | ) 22 | 23 | var NamespaceFilterSet_Time = "" 24 | 25 | func RetainNamespaceIn_Time(p *api.Process) bool { 26 | if p.NamespaceModule.Time == NamespaceFilterSet_Time { 27 | return true 28 | } 29 | return false 30 | } 31 | 32 | func RetainNamespaceOut_Time(p *api.Process) bool { 33 | if p.NamespaceModule.Time == "" { 34 | return false 35 | } 36 | if p.NamespaceModule.Time != NamespaceFilterSet_Time { 37 | return true 38 | } 39 | return false 40 | } 41 | -------------------------------------------------------------------------------- /pkg/filters/filter_namespace_uts.go: -------------------------------------------------------------------------------- 1 | /*===========================================================================*\ 2 | * MIT License Copyright (c) 2022 Kris Nóva * 3 | * * 4 | * ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ * 5 | * ┃ ███╗ ██╗ ██████╗ ██╗ ██╗ █████╗ ┃ * 6 | * ┃ ████╗ ██║██╔═████╗██║ ██║██╔══██╗ ┃ * 7 | * ┃ ██╔██╗ ██║██║██╔██║██║ ██║███████║ ┃ * 8 | * ┃ ██║╚██╗██║████╔╝██║╚██╗ ██╔╝██╔══██║ ┃ * 9 | * ┃ ██║ ╚████║╚██████╔╝ ╚████╔╝ ██║ ██║ ┃ * 10 | * ┃ ╚═╝ ╚═══╝ ╚═════╝ ╚═══╝ ╚═╝ ╚═╝ ┃ * 11 | * ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ * 12 | * * 13 | * This machine kills fascists. * 14 | * * 15 | \*===========================================================================*/ 16 | 17 | package filter 18 | 19 | import ( 20 | api "github.com/kris-nova/xpid/pkg/api/v1" 21 | ) 22 | 23 | var NamespaceFilterSet_UTS = "" 24 | 25 | func RetainNamespaceIn_UTS(p *api.Process) bool { 26 | if p.NamespaceModule.UTS == NamespaceFilterSet_UTS { 27 | return true 28 | } 29 | return false 30 | } 31 | 32 | func RetainNamespaceOut_UTS(p *api.Process) bool { 33 | if p.NamespaceModule.Cgroup == "" { 34 | return false 35 | } 36 | if p.NamespaceModule.UTS != NamespaceFilterSet_UTS { 37 | return true 38 | } 39 | return false 40 | } 41 | -------------------------------------------------------------------------------- /pkg/filters/filter_thread.go: -------------------------------------------------------------------------------- 1 | /*===========================================================================*\ 2 | * MIT License Copyright (c) 2022 Kris Nóva * 3 | * * 4 | * ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ * 5 | * ┃ ███╗ ██╗ ██████╗ ██╗ ██╗ █████╗ ┃ * 6 | * ┃ ████╗ ██║██╔═████╗██║ ██║██╔══██╗ ┃ * 7 | * ┃ ██╔██╗ ██║██║██╔██║██║ ██║███████║ ┃ * 8 | * ┃ ██║╚██╗██║████╔╝██║╚██╗ ██╔╝██╔══██║ ┃ * 9 | * ┃ ██║ ╚████║╚██████╔╝ ╚████╔╝ ██║ ██║ ┃ * 10 | * ┃ ╚═╝ ╚═══╝ ╚═════╝ ╚═══╝ ╚═╝ ╚═╝ ┃ * 11 | * ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ * 12 | * * 13 | * This machine kills fascists. * 14 | * * 15 | \*===========================================================================*/ 16 | 17 | package filter 18 | 19 | import ( 20 | api "github.com/kris-nova/xpid/pkg/api/v1" 21 | ) 22 | 23 | var _ ProcessFilter = RejectThreads 24 | 25 | func RejectThreads(p *api.Process) bool { 26 | // Future Logic Here 27 | if p.Thread { 28 | return false 29 | } 30 | return true 31 | } 32 | -------------------------------------------------------------------------------- /pkg/libxpid/libxpid_bpf.go: -------------------------------------------------------------------------------- 1 | /*===========================================================================*\ 2 | * MIT License Copyright (c) 2022 Kris Nóva * 3 | * * 4 | * ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ * 5 | * ┃ ███╗ ██╗ ██████╗ ██╗ ██╗ █████╗ ┃ * 6 | * ┃ ████╗ ██║██╔═████╗██║ ██║██╔══██╗ ┃ * 7 | * ┃ ██╔██╗ ██║██║██╔██║██║ ██║███████║ ┃ * 8 | * ┃ ██║╚██╗██║████╔╝██║╚██╗ ██╔╝██╔══██║ ┃ * 9 | * ┃ ██║ ╚████║╚██████╔╝ ╚████╔╝ ██║ ██║ ┃ * 10 | * ┃ ╚═╝ ╚═══╝ ╚═════╝ ╚═══╝ ╚═╝ ╚═╝ ┃ * 11 | * ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ * 12 | * * 13 | * This machine kills fascists. * 14 | * * 15 | \*===========================================================================*/ 16 | 17 | package libxpid 18 | 19 | // #cgo LDFLAGS: -lxpid 20 | // 21 | // #include "xpid.h" 22 | // #include "stdlib.h" 23 | import "C" 24 | import ( 25 | "strings" 26 | "sync" 27 | "unsafe" 28 | ) 29 | 30 | const ( 31 | 32 | // Memory in C is hard 33 | 34 | Empty1 string = " " 35 | Empty2 string = Empty1 + Empty1 36 | Empty4 string = Empty2 + Empty2 37 | Empty8 string = Empty4 + Empty4 38 | Empty16 string = Empty8 + Empty8 39 | Empty32 string = Empty16 + Empty16 40 | Empty64 string = Empty32 + Empty32 41 | Empty128 string = Empty64 + Empty64 42 | Empty256 string = Empty128 + Empty128 43 | Empty512 string = Empty256 + Empty256 44 | Empty1024 string = Empty512 + Empty512 45 | ) 46 | 47 | var libxpidbpfenummtx sync.Mutex 48 | 49 | func BPFProgramSections(progId int) []string { 50 | // void bpf_program_details(__u32 id, char *sec); 51 | secstr := Empty1024 52 | csecstr := C.CString(secstr) 53 | defer C.free(unsafe.Pointer(csecstr)) 54 | C.bpf_program_details(C.__u32(progId), csecstr) 55 | secstr = C.GoString(csecstr) 56 | return strings.Split(secstr, ":") 57 | } 58 | 59 | func BPFMapType(mapType int) string { 60 | libxpidbpfenummtx.Lock() 61 | defer libxpidbpfenummtx.Unlock() 62 | name := Empty256 63 | cname := C.CString(name) 64 | defer C.free(unsafe.Pointer(cname)) 65 | C.bpf_map_type_enum(C.int(mapType), cname) 66 | return C.GoString(cname) 67 | } 68 | -------------------------------------------------------------------------------- /pkg/libxpid/libxpid_proc_dir.go: -------------------------------------------------------------------------------- 1 | /*===========================================================================*\ 2 | * MIT License Copyright (c) 2022 Kris Nóva * 3 | * * 4 | * ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ * 5 | * ┃ ███╗ ██╗ ██████╗ ██╗ ██╗ █████╗ ┃ * 6 | * ┃ ████╗ ██║██╔═████╗██║ ██║██╔══██╗ ┃ * 7 | * ┃ ██╔██╗ ██║██║██╔██║██║ ██║███████║ ┃ * 8 | * ┃ ██║╚██╗██║████╔╝██║╚██╗ ██╔╝██╔══██║ ┃ * 9 | * ┃ ██║ ╚████║╚██████╔╝ ╚████╔╝ ██║ ██║ ┃ * 10 | * ┃ ╚═╝ ╚═══╝ ╚═════╝ ╚═══╝ ╚═╝ ╚═╝ ┃ * 11 | * ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ * 12 | * * 13 | * This machine kills fascists. * 14 | * * 15 | \*===========================================================================*/ 16 | 17 | package libxpid 18 | 19 | // #cgo LDFLAGS: -lxpid 20 | // 21 | // #include "xpid.h" 22 | import "C" 23 | 24 | func ProcDirOpendir(pid int64) int { 25 | x := C.proc_dir_opendir(C.int(int(pid))) 26 | return int(x) 27 | } 28 | 29 | func ProcDirChdir(pid int64) int { 30 | x := C.proc_dir_chdir(C.int(int(pid))) 31 | return int(x) 32 | } 33 | 34 | func ProcDirDent(pid int64) int { 35 | x := C.proc_dir_dent(C.int(int(pid))) 36 | return int(x) 37 | } 38 | -------------------------------------------------------------------------------- /pkg/procfs/procfs.go: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * MIT License 3 | * Copyright (c) 2022 Kris Nóva 4 | * 5 | * ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ 6 | * ┃ ███╗ ██╗ ██████╗ ██╗ ██╗ █████╗ ┃ 7 | * ┃ ████╗ ██║██╔═████╗██║ ██║██╔══██╗ ┃ 8 | * ┃ ██╔██╗ ██║██║██╔██║██║ ██║███████║ ┃ 9 | * ┃ ██║╚██╗██║████╔╝██║╚██╗ ██╔╝██╔══██║ ┃ 10 | * ┃ ██║ ╚████║╚██████╔╝ ╚████╔╝ ██║ ██║ ┃ 11 | * ┃ ╚═╝ ╚═══╝ ╚═════╝ ╚═══╝ ╚═╝ ╚═╝ ┃ 12 | * ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ 13 | * 14 | *****************************************************************************/ 15 | 16 | package procfs 17 | 18 | import ( 19 | "fmt" 20 | "io/fs" 21 | "io/ioutil" 22 | "os" 23 | "path/filepath" 24 | "strings" 25 | ) 26 | 27 | const ( 28 | DefaultProcRoot = "/proc" 29 | ) 30 | 31 | type ProcFileSystem struct { 32 | rootPath string 33 | } 34 | 35 | var p *ProcFileSystem 36 | 37 | func NewProcFileSystem(root string) *ProcFileSystem { 38 | return &ProcFileSystem{ 39 | rootPath: root, 40 | } 41 | } 42 | 43 | func (p *ProcFileSystem) Contents(file string) (string, error) { 44 | bytes, err := ioutil.ReadFile(filepath.Join(p.rootPath, file)) 45 | return string(bytes), err 46 | } 47 | 48 | func (p *ProcFileSystem) ContentsPID(pid int64, file string) (string, error) { 49 | file = fmt.Sprintf("%d/%s", pid, file) 50 | return p.Contents(file) 51 | } 52 | 53 | func (p *ProcFileSystem) Dir(dir string) ([]fs.FileInfo, error) { 54 | return ioutil.ReadDir(filepath.Join(p.rootPath, dir)) 55 | } 56 | 57 | func (p *ProcFileSystem) DirPID(pid int64, dir string) ([]fs.FileInfo, error) { 58 | dir = fmt.Sprintf("%d/%s", pid, dir) 59 | return p.Dir(dir) 60 | } 61 | 62 | func (p *ProcFileSystem) ReadlinkPID(pid int64, file string) (string, error) { 63 | file = fmt.Sprintf("%d/%s", pid, file) 64 | return p.Readlink(file) 65 | } 66 | 67 | func (p *ProcFileSystem) Readlink(file string) (string, error) { 68 | return os.Readlink(filepath.Join(p.rootPath, file)) 69 | } 70 | 71 | func Proc() string { 72 | // TODO Look up procfs! 73 | return DefaultProcRoot 74 | } 75 | 76 | func FileKeyValue(content, key string) string { 77 | lines := strings.Split(content, "\n") 78 | for _, line := range lines { 79 | spl := strings.Split(line, ":") 80 | if len(spl) != 2 { 81 | continue 82 | } 83 | k := strings.TrimSpace(spl[0]) 84 | v := strings.TrimSpace(spl[1]) 85 | if key == k { 86 | return v 87 | } 88 | } 89 | return "" 90 | } 91 | -------------------------------------------------------------------------------- /pkg/procx/procx.go: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * MIT License 3 | * Copyright (c) 2022 Kris Nóva 4 | * 5 | * ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ 6 | * ┃ ███╗ ██╗ ██████╗ ██╗ ██╗ █████╗ ┃ 7 | * ┃ ████╗ ██║██╔═████╗██║ ██║██╔══██╗ ┃ 8 | * ┃ ██╔██╗ ██║██║██╔██║██║ ██║███████║ ┃ 9 | * ┃ ██║╚██╗██║████╔╝██║╚██╗ ██╔╝██╔══██║ ┃ 10 | * ┃ ██║ ╚████║╚██████╔╝ ╚████╔╝ ██║ ██║ ┃ 11 | * ┃ ╚═╝ ╚═══╝ ╚═════╝ ╚═══╝ ╚═╝ ╚═╝ ┃ 12 | * ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ 13 | * 14 | *****************************************************************************/ 15 | 16 | package procx 17 | 18 | import ( 19 | "fmt" 20 | "io" 21 | "sync" 22 | 23 | encoder "github.com/kris-nova/xpid/pkg/encoders" 24 | 25 | api "github.com/kris-nova/xpid/pkg/api/v1" 26 | ) 27 | 28 | const ( 29 | // PidPoolLimit is the limit of concurrent pids to investigate 30 | // concurrently. Default to 2^19 = 524288 31 | PidPoolLimit int = 524288 32 | ) 33 | 34 | type ProcessExplorer struct { 35 | processes []*api.Process 36 | modules []api.ProcessExplorerModule 37 | encoder encoder.ProcessExplorerEncoder 38 | writer io.Writer 39 | fast bool 40 | ftx PidPool 41 | } 42 | 43 | func NewProcessExplorer(processes []*api.Process) *ProcessExplorer { 44 | return &ProcessExplorer{ 45 | processes: processes, 46 | ftx: NewPidPool(PidPoolLimit), 47 | } 48 | } 49 | 50 | func (x *ProcessExplorer) AddModule(m api.ProcessExplorerModule) { 51 | x.modules = append(x.modules, m) 52 | } 53 | 54 | func (x *ProcessExplorer) SetEncoder(e encoder.ProcessExplorerEncoder) { 55 | x.encoder = e 56 | } 57 | 58 | func (x *ProcessExplorer) SetWriter(w io.Writer) { 59 | x.writer = w 60 | } 61 | 62 | func (x *ProcessExplorer) SetFast(f bool) { 63 | x.fast = f 64 | } 65 | 66 | // Execute will run the process explorer. 67 | // 68 | // The function is O(m*p) where the runtime complexity grows withs the amount 69 | // of modules and pids to execute. 70 | func (x *ProcessExplorer) Execute() error { 71 | 72 | // Validation 73 | if x.processes == nil { 74 | return fmt.Errorf("missing pids in process explorer") 75 | } 76 | if x.encoder == nil { 77 | return fmt.Errorf("missing encoder in process explorer") 78 | } 79 | if x.modules == nil { 80 | return fmt.Errorf("missing modules in process explorer") 81 | } 82 | if len(x.modules) < 1 { 83 | return fmt.Errorf("empty modules in process explorer") 84 | } 85 | 86 | // Main execution loops 87 | 88 | // Safe to do pid 1 again 89 | i := 0 90 | for _, process := range x.processes { 91 | if i == 15 { 92 | process.ShowHeader = true 93 | i = 0 94 | } 95 | for _, module := range x.modules { 96 | x.ftx.Add() 97 | if x.fast { 98 | go x.walk(process, module) 99 | } else { 100 | x.walk(process, module) 101 | } 102 | } 103 | i++ 104 | } 105 | for x.ftx.Cur() != 0 { 106 | } 107 | return nil 108 | } 109 | 110 | // Walk ignores errors and will walk a process and a module 111 | // 112 | // Walk may be ran concurrently if needed 113 | func (x *ProcessExplorer) walk(p *api.Process, module api.ProcessExplorerModule) { 114 | module.Execute(p) // Ignore errors in walk 115 | r, _ := x.encoder.Encode(p) 116 | x.writer.Write(r) 117 | x.ftx.Sub() 118 | } 119 | 120 | type PidPool struct { 121 | sync.Mutex 122 | cur int 123 | limit int 124 | } 125 | 126 | func NewPidPool(limit int) PidPool { 127 | return PidPool{ 128 | limit: limit, 129 | cur: 0, 130 | Mutex: sync.Mutex{}, 131 | } 132 | } 133 | 134 | func (x *PidPool) Add() { 135 | for x.Cur() >= x.limit { 136 | } 137 | x.Lock() 138 | defer x.Unlock() 139 | x.cur++ 140 | } 141 | 142 | func (x *PidPool) Sub() { 143 | x.Lock() 144 | defer x.Unlock() 145 | x.cur-- 146 | } 147 | 148 | func (x *PidPool) Cur() int { 149 | x.Lock() 150 | defer x.Unlock() 151 | return x.cur 152 | } 153 | -------------------------------------------------------------------------------- /pkg/procx/procx_test.go: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * MIT License 3 | * Copyright (c) 2022 Kris Nóva 4 | * 5 | * ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ 6 | * ┃ ███╗ ██╗ ██████╗ ██╗ ██╗ █████╗ ┃ 7 | * ┃ ████╗ ██║██╔═████╗██║ ██║██╔══██╗ ┃ 8 | * ┃ ██╔██╗ ██║██║██╔██║██║ ██║███████║ ┃ 9 | * ┃ ██║╚██╗██║████╔╝██║╚██╗ ██╔╝██╔══██║ ┃ 10 | * ┃ ██║ ╚████║╚██████╔╝ ╚████╔╝ ██║ ██║ ┃ 11 | * ┃ ╚═╝ ╚═══╝ ╚═════╝ ╚═══╝ ╚═╝ ╚═╝ ┃ 12 | * ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ 13 | * 14 | *****************************************************************************/ 15 | 16 | package procx 17 | 18 | import "testing" 19 | 20 | func TestProcessExplorer(t *testing.T) { 21 | // TODO 22 | } 23 | -------------------------------------------------------------------------------- /pkg/procx/query.go: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * MIT License 3 | * Copyright (c) 2022 Kris Nóva 4 | * 5 | * ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ 6 | * ┃ ███╗ ██╗ ██████╗ ██╗ ██╗ █████╗ ┃ 7 | * ┃ ████╗ ██║██╔═████╗██║ ██║██╔══██╗ ┃ 8 | * ┃ ██╔██╗ ██║██║██╔██║██║ ██║███████║ ┃ 9 | * ┃ ██║╚██╗██║████╔╝██║╚██╗ ██╔╝██╔══██║ ┃ 10 | * ┃ ██║ ╚████║╚██████╔╝ ╚████╔╝ ██║ ██║ ┃ 11 | * ┃ ╚═╝ ╚═══╝ ╚═════╝ ╚═══╝ ╚═╝ ╚═╝ ┃ 12 | * ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ 13 | * 14 | *****************************************************************************/ 15 | 16 | package procx 17 | 18 | import ( 19 | "io/ioutil" 20 | "strconv" 21 | "strings" 22 | 23 | api "github.com/kris-nova/xpid/pkg/api/v1" 24 | ) 25 | 26 | func ProcListingQuery(raw string) []*api.Process { 27 | var processes []*api.Process 28 | raw = strings.TrimSpace(raw) 29 | left, right := queryLeftRight(raw) 30 | if left == -1 || right == -1 { 31 | return nil 32 | } 33 | files, err := ioutil.ReadDir(ProcPath()) 34 | if err != nil { 35 | return nil 36 | } 37 | for _, file := range files { 38 | if file.IsDir() { 39 | i, e := strconv.Atoi(file.Name()) 40 | if e == nil { 41 | // We have a dir and its name is a number :) 42 | if i >= left && i <= right { 43 | processes = append(processes, &api.Process{ 44 | PID: int64(i), 45 | }) 46 | } 47 | } 48 | } 49 | } 50 | return processes 51 | } 52 | 53 | // PIDQuery will take an nmap like query for linux pids 54 | // 55 | // PIDQuery will return ALL POSSIBLE PIDS! 56 | // PIDQuery will not pull a list from /proc but rather build a list of potential pids. 57 | func PIDQuery(raw string) []*api.Process { 58 | var processes []*api.Process 59 | raw = strings.TrimSpace(raw) 60 | left, right := queryLeftRight(raw) 61 | if left == -1 || right == -1 { 62 | return nil 63 | } 64 | for i := left; i <= right; i++ { 65 | p := api.ProcessPID(int64(i)) 66 | processes = append(processes, p) 67 | } 68 | return processes 69 | } 70 | 71 | func queryLeftRight(raw string) (left, right int) { 72 | if strings.HasPrefix(raw, "+") { 73 | raw = strings.TrimPrefix(raw, "+") 74 | pid, err := strconv.Atoi(raw) 75 | if err != nil { 76 | return -1, -1 77 | } 78 | left := 0 79 | right := pid 80 | return left, right 81 | } else if strings.Contains(raw, "-") { 82 | spl := strings.Split(raw, "-") 83 | if len(spl) != 2 { 84 | return -1, -1 85 | } 86 | left, err := strconv.Atoi(spl[0]) 87 | if err != nil { 88 | return -1, -1 89 | } 90 | right, err := strconv.Atoi(spl[1]) 91 | if err != nil { 92 | return -1, -1 93 | } 94 | if left > right { 95 | sw := left 96 | left = right 97 | right = sw 98 | } 99 | return left, right 100 | //for i := left; i <= right; i++ { 101 | // p := api.ProcessPID(int64(i)) 102 | // processes = append(processes, p) 103 | //} 104 | } else if strings.HasSuffix(raw, "+") { 105 | raw = strings.TrimSuffix(raw, "+") 106 | pid, err := strconv.Atoi(raw) 107 | if err != nil { 108 | return -1, -1 109 | } 110 | left := pid 111 | right := int(MaxPid()) 112 | return left, right 113 | //for i := left; i <= right; i++ { 114 | // p := api.ProcessPID(int64(i)) 115 | // processes = append(processes, p) 116 | //} 117 | } else { 118 | pid, err := strconv.Atoi(raw) 119 | if err != nil { 120 | return -1, -1 121 | } 122 | return pid, pid 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /pkg/procx/query_test.go: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * MIT License 3 | * Copyright (c) 2022 Kris Nóva 4 | * 5 | * ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ 6 | * ┃ ███╗ ██╗ ██████╗ ██╗ ██╗ █████╗ ┃ 7 | * ┃ ████╗ ██║██╔═████╗██║ ██║██╔══██╗ ┃ 8 | * ┃ ██╔██╗ ██║██║██╔██║██║ ██║███████║ ┃ 9 | * ┃ ██║╚██╗██║████╔╝██║╚██╗ ██╔╝██╔══██║ ┃ 10 | * ┃ ██║ ╚████║╚██████╔╝ ╚████╔╝ ██║ ██║ ┃ 11 | * ┃ ╚═╝ ╚═══╝ ╚═════╝ ╚═══╝ ╚═╝ ╚═╝ ┃ 12 | * ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ 13 | * 14 | *****************************************************************************/ 15 | 16 | package procx 17 | 18 | import ( 19 | "testing" 20 | ) 21 | 22 | func TestPIDQueryDash(t *testing.T) { 23 | test := "1-100" 24 | pids := PIDQuery(test) 25 | if len(pids) != 100 { 26 | t.Errorf("invalid PID query: missing 1-100") 27 | } 28 | for i := 0; i < 100; i++ { 29 | pid := pids[i] 30 | if int(pid.PID) != i+1 { 31 | t.Errorf("invalid PID %d:%d", int(pid.PID), int(i)) 32 | } 33 | } 34 | } 35 | 36 | func TestPIDQuery(t *testing.T) { 37 | test := "100" 38 | pids := PIDQuery(test) 39 | if len(pids) != 1 { 40 | t.Errorf("invalid PID query: missing 100") 41 | } 42 | if pids[0].PID != int64(100) { 43 | t.Errorf("invalid PID: 100") 44 | } 45 | } 46 | 47 | func TestPIDQueryDashSad(t *testing.T) { 48 | test := "11-da-asdf---100--" 49 | pids := PIDQuery(test) 50 | if pids != nil { 51 | t.Errorf("error expected") 52 | } 53 | } 54 | 55 | func TestPIDQueryDashReverse(t *testing.T) { 56 | test := "100-1" 57 | pids := PIDQuery(test) 58 | if len(pids) != 100 { 59 | t.Errorf("invalid PID query: missing 1-100") 60 | } 61 | for i := 0; i < 100; i++ { 62 | pid := pids[i] 63 | if int(pid.PID) != i+1 { 64 | t.Errorf("invalid PID %d:%d", int(pid.PID), int(i)) 65 | } 66 | } 67 | } 68 | 69 | func TestPIDQueryAfter(t *testing.T) { 70 | test := "100+" 71 | pids := PIDQuery(test) 72 | if len(pids) <= 1 { 73 | t.Errorf("invalid plus query") 74 | t.FailNow() 75 | } 76 | if pids[0].PID != 100 { 77 | t.Errorf("invalid plus query starting point") 78 | } 79 | } 80 | 81 | func TestPIDQueryLeading(t *testing.T) { 82 | test := "+100" 83 | pids := PIDQuery(test) 84 | if len(pids) <= 1 { 85 | t.Errorf("invalid minus query") 86 | t.FailNow() 87 | } 88 | if pids[0].PID != 0 { 89 | t.Errorf("invalid minus query starting point") 90 | } 91 | if pids[100].PID != 100 { 92 | t.Errorf("invalid minus query finish point") 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /pkg/procx/system_linux.go: -------------------------------------------------------------------------------- 1 | //go:aur_build linux 2 | 3 | /****************************************************************************** 4 | * MIT License 5 | * Copyright (c) 2022 Kris Nóva 6 | * 7 | * ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ 8 | * ┃ ███╗ ██╗ ██████╗ ██╗ ██╗ █████╗ ┃ 9 | * ┃ ████╗ ██║██╔═████╗██║ ██║██╔══██╗ ┃ 10 | * ┃ ██╔██╗ ██║██║██╔██║██║ ██║███████║ ┃ 11 | * ┃ ██║╚██╗██║████╔╝██║╚██╗ ██╔╝██╔══██║ ┃ 12 | * ┃ ██║ ╚████║╚██████╔╝ ╚████╔╝ ██║ ██║ ┃ 13 | * ┃ ╚═╝ ╚═══╝ ╚═════╝ ╚═══╝ ╚═╝ ╚═╝ ┃ 14 | * ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ 15 | * 16 | *****************************************************************************/ 17 | 18 | package procx 19 | 20 | import ( 21 | "os" 22 | "path/filepath" 23 | "strconv" 24 | "strings" 25 | 26 | "github.com/sirupsen/logrus" 27 | ) 28 | 29 | const ( 30 | DefaultProcLocation = "/proc" 31 | DefaultMaxPid int64 = 65000 32 | ) 33 | 34 | func ProcPath() string { 35 | // TODO Logic to lookup if proc is not /proc 36 | return DefaultProcLocation 37 | } 38 | 39 | // MaxPid will return the system specific maximum PID number 40 | // 41 | // For Linux systems this can be found in proc(5) if it is mounted! 42 | func MaxPid() int64 { 43 | maxPidFile := filepath.Join(ProcPath(), "sys/kernel/pid_max") 44 | bytes, err := os.ReadFile(maxPidFile) 45 | if err != nil { 46 | logrus.Warnf("err reading %s: %v", maxPidFile, err) 47 | return DefaultMaxPid 48 | } 49 | v := string(bytes) 50 | v = strings.Replace(v, "\n", "", -1) 51 | vi, err := strconv.Atoi(v) 52 | if err != nil { 53 | logrus.Warnf("err reading %s: %v", maxPidFile, err) 54 | return -1 55 | } 56 | return int64(vi) 57 | } 58 | -------------------------------------------------------------------------------- /release/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krisnova/xpid/fcb9d507906940dffa3a86be3d43126241f1b2c3/release/.gitkeep --------------------------------------------------------------------------------