``` ps 工具,带有 eBPF 增强和容器上下文 ```
psc: The ps utility, with an eBPF twist and container context

原始链接: https://github.com/loresuso/psc

## psc:基于 eBPF 和 CEL 的现代进程扫描器 `psc` (ps container) 是一个强大的进程扫描器,利用 eBPF 和 Google 的通用表达式语言 (CEL) 实现对系统状态的精确查询,并提供完整的容器上下文。与依赖于解析固定输出的传统工具(如 `ps`、`lsof` 和 `ss`)不同,`psc` 允许使用 CEL 表达式进行灵活的过滤。 `psc` 通过 eBPF 直接读取内核数据,绕过可能被篡改的 `/proc` 文件系统,使其能够抵抗 rootkit 和 LD_PRELOAD 攻击。它提供关于进程、网络连接(socket)和打开文件的洞察,包括容器化(Docker、containerd 等)的详细信息。 **主要特性:** * **基于 CEL 的过滤:** 根据名称、用户、命令行、容器详情、socket 状态等查询进程。 * **内核级可见性:** eBPF 确保数据的准确性,不受用户态篡改的影响。 * **容器优先:** 轻松检查容器内的进程。 * **可定制输出:** 使用预设或单独选择控制显示的字段。 `psc` 需要 root 权限以及 Linux kernel 5.8+ 和必要的开发工具。它为传统的进程监控工具提供了一种更安全、更灵活的替代方案。

黑客新闻 新 | 过去 | 评论 | 提问 | 展示 | 招聘 | 提交 登录 psc: 带 eBPF 技巧和容器上下文的 ps 工具 (github.com/loresuso) 6 分,tanelpoder 1 小时前 | 隐藏 | 过去 | 收藏 | 1 条评论 apopapo 1 分钟前 [–] > psc 使用 eBPF 迭代器直接从内核数据结构读取进程和文件描述符信息。这完全绕过了 /proc 文件系统,提供了用户态 rootkit 或 LD_PRELOAD 技巧无法篡改的可视性。 这里有什么权衡吗?回复 指南 | 常见问题 | 列表 | API | 安全 | 法律 | 申请 YC | 联系 搜索:
相关文章

原文

psc (ps container) is a process scanner that uses eBPF iterators and Google CEL to query system state with precision and full container context.

psc requires root privileges to load eBPF programs.

Traditional Linux tools like ps, lsof, and ss are powerful but inflexible. They output fixed formats that require extensive piping through grep, awk, and sed to extract useful information:

# Find all nginx processes owned by root
ps aux | grep nginx | grep root | grep -v grep

# With psc:
psc 'process.name == "nginx" && process.user == "root"'
# Find processes with established connections on port 443
ss -tnp | grep ESTAB | grep :443 | awk '{print $6}' | cut -d'"' -f2

# With psc:
psc 'socket.state == established && socket.dstPort == uint(443)'
# Find containerized processes
ps aux | xargs -I{} sh -c 'cat /proc/{}/cgroup 2>/dev/null | grep -q docker && echo {}'

# With psc:
psc 'container.runtime == docker'

These tools also read from /proc, a virtual filesystem that can be manipulated by userland rootkits. A compromised library loaded via LD_PRELOAD can intercept system calls and hide processes, network connections, or files from these traditional utilities.

eBPF Iterators for Kernel-Level Visibility

psc uses eBPF iterators to read process and file descriptor information directly from kernel data structures. This bypasses the /proc filesystem entirely, providing visibility that cannot be subverted by userland rootkits or LD_PRELOAD tricks. When an attacker uses LD_PRELOAD to inject a malicious shared library that intercepts calls to readdir() or open(), traditional tools see only what the rootkit allows. psc reads kernel memory directly via eBPF, seeing the true system state.

Google CEL for Flexible Queries

Instead of chaining grep commands, psc uses the Common Expression Language (CEL) to filter processes. CEL is a simple, safe expression language designed for evaluating boolean conditions. It allows you to answer:

  • What is running: Filter by process name, command line, user, or PID
  • Where it is running: Filter by container runtime, container name, image, or labels
  • Why it is running: Inspect open file descriptors, network connections (ports, states, protocols), and socket types to understand what a process is doing and why it exists

Debug Containers from the Host

With psc, you can inspect any container's processes, open files, and network connections directly from the host.

  • Linux kernel 5.8 or later (eBPF iterators were introduced in this version)
  • Go 1.25 or later
  • Clang and LLVM
  • libbpf development headers
  • Linux kernel headers
  • bpftool (for generating vmlinux.h)

On Debian/Ubuntu:

sudo apt-get install clang llvm libbpf-dev linux-headers-$(uname -r) linux-tools-$(uname -r)

On Fedora/RHEL:

sudo dnf install clang llvm libbpf-devel kernel-devel bpftool
# Generate vmlinux.h (required once per kernel version)
make vmlinux

# Build the binary
make build

Or manually:

bpftool btf dump file /sys/kernel/btf/vmlinux format c > bpf/vmlinux.h
go generate ./...
go build -o psc
# List all processes
psc

# List all processes as a tree
psc --tree

Filtering with CEL Expressions

Pass a CEL expression as the first argument to filter processes:

# Filter by process name
psc 'process.name == "nginx"'

# Filter by user
psc 'process.user == "root"'

# Filter by command line content
psc 'process.cmdline.contains("--config")'

# Filter by PID range
psc 'process.pid > 1000 && process.pid < 2000'

# Combine conditions
psc 'process.name == "bash" || process.name == "zsh"'
# Show only containerized processes
psc 'container.id != ""'

# Filter by container runtime (constants: docker, containerd, crio, podman)
psc 'container.runtime == docker'

# Filter by container name
psc 'container.name == "nginx"'

# Filter by container image
psc 'container.image.contains("nginx:latest")'

# Show as tree to see container process hierarchy
psc 'container.runtime == docker' --tree

Socket and File Descriptor Filtering

Understanding why a process exists often requires looking at its open file descriptors and network connections:

# Find processes with listening TCP sockets
psc 'socket.type == tcp && socket.state == listen'

# Find processes with established connections
psc 'socket.state == established'

# Find processes connected to a specific port
psc 'socket.dstPort == uint(443)'

# Find processes using Unix sockets
psc 'socket.family == unix'

# Find processes with files open in /etc
psc 'file.path.startsWith("/etc")'

Process fields (process.X):

  • name - Process name (string)
  • pid - Process ID (int)
  • ppid - Parent process ID (int)
  • tid - Thread ID (int)
  • euid - Effective user ID (int)
  • ruid - Real user ID (int)
  • suid - Saved set-user-ID (int)
  • user - Username (string)
  • cmdline - Full command line (string)
  • state - Process state (uint)

Capability fields (process.capabilities.X):

  • effective - Effective capabilities bitmask (uint)
  • permitted - Permitted capabilities bitmask (uint)
  • inheritable - Inheritable capabilities bitmask (uint)

Namespace fields (process.namespaces.X):

  • net - Network namespace inode (uint)
  • pid - PID namespace inode (uint)
  • mnt - Mount namespace inode (uint)
  • uts - UTS namespace inode (uint)
  • ipc - IPC namespace inode (uint)
  • cgroup - Cgroup namespace inode (uint)

Container fields (container.X):

  • id - Container ID (string)
  • name - Container name (string)
  • image - Container image (string)
  • runtime - Container runtime (string)
  • labels - Container labels (map)

File/Socket fields (file.X or socket.X):

  • path - File path (string)
  • fd - File descriptor number (int)
  • srcPort - Source port (uint, use uint() for comparisons: socket.srcPort == uint(80))
  • dstPort - Destination port (uint, use uint() for comparisons: socket.dstPort == uint(443))
  • type - Socket type (tcp, udp)
  • state - Socket state (for filtering, use constants like listen, established)
  • family - Address family (unix, inet, inet6)
  • unixPath - Unix socket path (string)
  • fdType - FD type (file_type, socket_type)

Use these without quotes in expressions:

  • Runtimes: docker, containerd, crio, podman
  • Socket types: tcp, udp
  • Address families: unix, inet, inet6
  • Socket states (for filtering): established, listen, syn_sent, syn_recv, fin_wait1, fin_wait2, time_wait, close, close_wait, last_ack, closing
  • FD types: file_type, socket_type

Note: Output uses ss-style state names: ESTAB, LISTEN, SYN-SENT, etc. For UDP sockets, only UNCONN (unconnected) or ESTAB (connected) are shown since UDP is connectionless.

CEL provides string manipulation functions:

  • .contains("substr") - Check if string contains substring
  • .startsWith("prefix") - Check if string starts with prefix
  • .endsWith("suffix") - Check if string ends with suffix
  • --tree, -t - Display processes as a tree
  • --no-color - Disable colored output
  • -o, --output - Custom output columns (comma-separated field names)

The -o flag lets you specify exactly which fields to display. You can use presets for common use cases or specify individual fields.

Presets:

  • sockets - Process info + full socket details (family, type, state, addresses, ports)
  • files - Process info + file descriptor details (fd, type, path)
  • containers - Process info + container details (name, image, runtime)
  • network - Compact network view (pid, name, type, state, ports)
# Use a preset
psc 'socket.state == listen' -o sockets
psc 'container.id != ""' -o containers

# Or specify individual fields
psc -o process.pid,process.name,process.user
psc 'socket.state == listen' -o process.pid,process.name,socket.srcPort,socket.state

When the output includes file/socket fields and the filter matches multiple files per process, each match gets its own row:

$ psc 'socket.state == listen' -o network

PID      NAME      TYPE   STATE    SRCPORT   DSTPORT
1234     nginx     tcp    LISTEN   80        0
1234     nginx     tcp    LISTEN   443       0
5678     sshd      tcp    LISTEN   22        0

Use psc fields to list all available fields and presets:

Find all web servers:

psc 'process.name == "nginx" || process.name == "apache2" || process.name == "httpd"'

Find processes listening on privileged ports:

psc 'socket.state == listen && socket.srcPort < uint(1024)'

Find processes in a different network namespace (useful for container/pod inspection):

psc 'process.namespaces.net != uint(4026531840)' -o process.pid,process.name,process.namespaces.net

Show capabilities for privileged processes:

psc 'process.euid == 0' -o process.pid,process.name,process.capabilities.effective,process.capabilities.permitted

Find processes that elevated privileges via SUID binaries (real UID differs from effective UID):

psc 'process.ruid != process.euid'

Find Docker containers running as root:

psc 'container.runtime == docker && process.user == "root"'

Debug a specific container:

psc 'container.name == "my-app"' --tree

Find processes with connections to external services:

psc 'socket.state == established && socket.dstPort == uint(443)'

Show network connections with custom columns:

psc 'socket.state == established' -o process.pid,process.name,socket.srcPort,socket.dstPort,socket.dstAddr

List containerized processes with their container info:

psc 'container.id != ""' -o process.pid,process.name,process.user,container.name,container.image

MIT

联系我们 contact @ memedata.com