├── .gitignore ├── LICENSE ├── README.md ├── bpf_go_tracer ├── README.md ├── head_first_bpf_v3_go.pdf ├── imgs │ ├── gdb_int3.gif │ ├── go_tracer.gif │ └── go_uprobe_arch.png ├── tracee │ ├── Makefile │ └── main.go └── tracer │ ├── Makefile │ ├── go.mod │ ├── go.sum │ └── main.go └── hidden_file ├── Makefile ├── README.md ├── obpf ├── obpf.bpf.c ├── obpf.c └── obpf.h /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 DavadDi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bpf_demo 2 | 3 | * [bpf_go_tracer](bpf_go_tracer/README.md) 展示了如何使用 eBPF 跟踪 Go 程序的函数参数和修改函数参数的样例。 4 | * [hidden_file](hidden_file/README.md) 展示了如何使用 eBPF 隐藏文件的功能,主要展示 bpf_probe_write_user 函数的使用方式。 5 | -------------------------------------------------------------------------------- /bpf_go_tracer/README.md: -------------------------------------------------------------------------------- 1 | # 使用 eBPF 跟踪 Go 2 | 3 | [TOC] 4 | 5 | 该仓库用于演示 eBPF 跟踪 Go 语言函数参数并进行修改,环境搭建参见“环境准备”。 6 | 7 | ## Go 函数参数跟踪和修改 8 | 9 | 效果演示: 10 | 11 | ![](imgs/go_tracer.gif) 12 | 13 | ### 样例代码 14 | 15 | 被跟踪的 Go 语言 tracee.go 文件内容如下: 16 | 17 | ```go 18 | package main 19 | 20 | import "fmt" 21 | 22 | //go:noinline 23 | func ebpfDemo(a1 int, a2 bool, a3 float32) (r1 int64, r2 int32, r3 string) { 24 | fmt.Printf("ebpfDemo:: a1=%d, a2=%t a3=%t.2f\n", a1, a2, a3) 25 | return 100, 200, "test for ebpf" 26 | } 27 | 28 | func main() { 29 | r1, r2, r3 := ebpfDemo(100, true, 66.88) 30 | fmt.Printf("main:: r1=%d, r2=%t 43=%t.2f\n", r1, r2, r3) 31 | 32 | return; 33 | } 34 | ``` 35 | 36 | 其中 `//go:noinline` 明确告诉编译器不适用内联优化,相关内容可参考:[Golang bcc/BPF Function Tracing](https://www.brendangregg.com/blog/2017-01-31/golang-bcc-bpf-function-tracing.html) 或者[中文翻译](https://colobu.com/2017/09/22/golang-bcc-bpf-function-tracing/)。 37 | 38 | 39 | 40 | ### 跟踪程序代码 41 | 42 | ```bash 43 | $ go mod init 44 | ``` 45 | 46 | 跟踪和修改 ebpfDemo 入参的 BPF 程序代码如下: 47 | 48 | ```c 49 | #include 50 | 51 | BPF_PERF_OUTPUT(events); 52 | 53 | typedef struct { 54 | u64 arg1; 55 | char arg2; 56 | char pad[3]; 57 | float arg3; 58 | } args_event_t; 59 | 60 | inline int get_arguments(struct pt_regs *ctx) { 61 | void* stackAddr = (void*)ctx->sp; 62 | args_event_t event = {}; 63 | 64 | bpf_probe_read(&event.arg1, sizeof(event.arg1), stackAddr+8); 65 | bpf_probe_read(&event.arg2, sizeof(event.arg2), stackAddr+16); 66 | bpf_probe_read(&event.arg3, sizeof(event.arg3), stackAddr+20); 67 | 68 | long tmp = 2021; 69 | bpf_probe_write_user(stackAddr+8, &tmp, sizeof(tmp)); 70 | 71 | events.perf_submit(ctx, &event, sizeof(event)); 72 | 73 | return 0; 74 | } 75 | ``` 76 | 77 | 78 | 79 | 其中 `bpf_probe_write_user ` 用于修改 `ebpfDemo` 的第一个入参。 80 | 81 | 单次运行 tracee 结果如下: 82 | 83 | ```bash 84 | bpf_demo/bpf_go_tracer/tracee$ make build && make run 85 | go build -o tracee main.go 86 | ./tracee 87 | ebpfDemo:: a1=100, a2=true a3=66.88 88 | main:: r1=100, r2=200 r3=test for ebpf 89 | ``` 90 | 91 | 92 | 93 | 启动 tracer 程序(make build && make run),然后再次运行 tracee (make run),tracee 和 tracer 的输出如下: 94 | 95 | ```bash 96 | bpf_demo/bpf_go_tracer/tracer$ make build && make run 97 | go build -o tracer main.go 98 | sudo ./tracer --binary ../tracee/tracee --func main.ebpfDemo 99 | Trace ../tracee/tracee on func [main.ebpfDemo] 100 | [100 0 0 0 0 0 0 0 1 0 0 0 143 194 133 66 0 0 0 0] 101 | ebpfDemo:: a1=100, a2=1 a3=66.8./tracee/tracee on func [main.ebpfDemo] # 输出 102 | 103 | ``` 104 | 105 | 106 | 107 | tracee 的输出结果如下: 108 | 109 | ```bash 110 | bpf_demo/bpf_go_tracer/tracee$ make run 111 | ./tracee 112 | ebpfDemo:: a1=2021, a2=true a3=66.88 # 入参已经从原来的 a1=100 => a1=2021 113 | main:: r1=100, r2=200 r3=test for ebpf 114 | ``` 115 | 116 | 117 | 118 | 通过运行结果我们发现通过 BPF 程序完成了 `ebpfDemo` 的参数跟踪和参数修改。 119 | 120 | 121 | 122 | ### uprobe 原理 123 | 124 | 以上跟踪使用 uprobe 方式进行跟踪,被跟踪的函数入口处会被 “int 3 ” 中断指令替换,在函数执行到此处时,会进入到中断,中断处理程序会查找到前期注册的 eBPF 程序并运行。 125 | 126 | Uprobe 原理演示图如下: 127 | 128 | ![arch](imgs/go_uprobe_arch.png) 129 | 130 | 完整演示版本参考如下: 131 | 132 | ![](imgs/gdb_int3.gif) 133 | 134 | 135 | 136 | 137 | 138 | > **此种方式的局限性如下:** 139 | > 140 | > * eBPF 在分析 Go 语言 ABI 的时候局限于简单类型,更加复杂的类型(用户自定义结构体/指针/引用/通道接口等),比较适用的场景是函数的调用次数,函数延迟,函数返回值等; 141 | > 142 | > * 基于 uprobe 需要被跟踪的程序带有符号表(not stripped) 143 | > 144 | > * eBPF 需要特权用户,一般情况下会限制适用范围; 145 | 146 | 147 | 148 | 149 | 150 | ## 环境准备 151 | 152 | 本文我们测试的环境如下: 153 | 154 | * Ubuntu 20.04 LTS , amd64 155 | * bcc v0.23.0(源码安装) 156 | * iovisor/gobpf v0.2.0 157 | * Go1.15 linux/amd64 (最新 1.17 版本调用方式可能有变化) 158 | 159 | 需要注意 iovisor/gobpf 需要与 bcc 版本相对应,否则可能会报接口函数签名不一致,本质上 gobpf 库只是 bcc 底层开发库的一层 cgo 包装。 160 | 161 | 在 Ubuntu 20.04 LTS 系统中,仓库中对应的包存在一些问题,所以必须采用源码方式进行安装,报错信息如下: 162 | 163 | ```bash 164 | vendor/github.com/iovisor/gobpf/bcc/module.go:32:10: fatal error: bcc/bcc_common.h: No such file or directory 165 | ``` 166 | 167 | 详情可参见 Github [Issue 214](https://github.com/iovisor/gobpf/issues/214)。 168 | 169 | 170 | 171 | ### Ubuntu 20.04 环境安装 172 | 173 | 这里我们使用 [multipass](https://multipass.run/) 进行环境安装: 174 | 175 | ```bash 176 | $ multipass find # 该命令可以显示当前全部的版本 177 | $ multipass launch -n ubuntu -c 4 -m 4G -d 40G 20.04 178 | ``` 179 | 180 | 直接使用 `multipass launch -n ubuntu -c 4 -m 4G -d 40G 20.04` 命令进行安装,详细参数介绍如下: 181 | 182 | * -n ubuntu 创建虚拟机的名字,后续登录需要基于该名字; 183 | * -c 虚拟机使用的 CPU 函数可以根据自己的情况进行调整; 184 | * -m 虚拟机占用的内存大小; 185 | * -d 虚拟机占用的磁盘,默认只有 5G,涉及编译可以根据自己情况调整; 186 | * 20.04 为我们要安装的系统镜像名字,也可以使用别名,比如 focal(20.04); 187 | 188 | 系统安装成功后,使用 `multipass shell ubuntu` 即可进行登录。 189 | 190 | 191 | 192 | BPF 程序运行需要依赖 linux-header文件,我们提前进行安装: 193 | 194 | ```bash 195 | $ sudo apt-get install bpfcc-tools linux-headers-$(uname -r) 196 | ``` 197 | 198 | 199 | 200 | ### BCC 源码安装 201 | 202 | 安装流程参考 [BCC 安装文档](https://github.com/iovisor/bcc/blob/master/INSTALL.md#ubuntu---source) 203 | 204 | ```bash 205 | # 安装编译相关工具 206 | $ sudo apt install -y bison build-essential cmake flex git libedit-dev \ 207 | libllvm7 llvm-7-dev libclang-7-dev python zlib1g-dev libelf-dev libfl-dev python3-distutils 208 | 209 | $ git clone https://github.com/iovisor/bcc.git 210 | $ git checkout v0.23.0 -b branch_v0.23.0 211 | $ git submodule update 212 | $ mkdir bcc/build; cd bcc/build 213 | $ cmake .. 214 | $ make 215 | 216 | # 需要特权模式 217 | $ sudo make install 218 | ``` 219 | 220 | 221 | ### Go 安装 222 | 223 | ```bash 224 | $ wget https://dl.google.com/go/go1.15.linux-amd64.tar.gz 225 | $ sudo rm -rf /usr/local/go && sudo tar -C /usr/local -xzf go1.15.linux-amd64.tar.gz 226 | $ export PATH=$PATH:/usr/local/go/bin 227 | $ go version 228 | ``` 229 | 230 | ## 参考文档 231 | 232 | * [Introducing gobpf - Using eBPF from Go](https://kinvolk.io/blog/2016/11/introducing-gobpf-using-ebpf-from-go/) 主要介绍了 gobpf 库的使用姿势,文章比较老,可能有最新的使用优化。 233 | * [ebpf 修改golang 函数参数](http://hushi55.github.io/2021/04/22/ebpf-modify-golang-args) 和 [Go internal ABI specification](http://hushi55.github.io/2021/04/19/Go-internal-ABI-specification) => [原文](https://go.googlesource.com/go/+/refs/heads/dev.regabi/src/cmd/compile/internal-abi.md) 234 | * [Debugging with eBPF Part 1: Tracing Go function arguments in prod](https://www.cncf.io/blog/2021/11/17/debugging-with-ebpf-part-1-tracing-go-function-arguments-in-prod/) 作者 为 Pixie 的*Zain Asgar*,从原理到最终使用都有比较详细的介绍 235 | * [YouTube 分享的视频](https://youtu.be/0mxUU_--dDM) No-Instrumenttation Golang Logging with eBPF Golang Online Meetup 57 236 | * 样例仓库参见 [trace_example](https://github.com/pixie-io/pixie-demos/tree/main/simple-gotracing/trace_example)。 237 | * Pixie 产品追踪 Go 功能参见 [Dynamic Logging In Go (Alpha)](https://docs.px.dev/tutorials/custom-data/dynamic-go-logging/) 238 | * 系列文章 [Debugging with eBPF Part 2: Tracing full body HTTP request/responses](https://blog.px.dev/ebpf-http-tracing/) 介绍了追踪 HTTP 使用 kprobe 和 urprobe 的各种利弊和性能对比 239 | * 站点中 eBPF 相关文档 https://blog.px.dev/ebpf 240 | * [Tracing Go Functions with eBPF Part 1](https://www.grant.pizza/blog/tracing-go-functions-with-ebpf-part-1/) 作者为 grantseltze,2021 年的 Gopher 分享参见这里 [Tracing Go Programs with eBPF!](https://www.grant.pizza/talks/gopherconeu2021/), [YouTube 地址](https://www.youtube.com/watch?v=JxZL6ZEBBEg) GopherCon Europe 2021: Grant Seltzer Richman - Unlocking eBPF from Go 241 | * [Tracing Go Functions with eBPF Part 2](http://hushi55.github.io/2021/04/22/ebpf-modify-golang-args) 作者的开源工具 [weaver](https://github.com/grantseltzer/weaver),博客有不少 eBPF 文章,参见 https://www.grant.pizza/ 242 | * [Youtube: GopherCon 2020: Grant Seltzer Richman - Tracing Go Programs with eBPF!](https://www.youtube.com/watch?v=OxLmd7szevI) 243 | * YouTube [Tracing Go with eBPF / Florian Lehner @ GDG Berlin Golang 06/2021](https://www.youtube.com/watch?v=f1jr9nR25EA) ✅ 有 eBPF 背景介绍和 Go 跟踪的各个方面包括函数堆栈等 [GitHub Slides](https://github.com/florianl/talks/blob/master/2021-ber-tracingGoWithEbpf.pdf); 244 | * 其他样例代码参见这里 [modify-arguments-on-the-fly](https://github.com/chenhengqi/bpf-examples/tree/main/modify-arguments-on-the-fly) 该样例代码不能稳定重现。 245 | -------------------------------------------------------------------------------- /bpf_go_tracer/head_first_bpf_v3_go.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DavadDi/bpf_demo/90a0d2aaeff0a9de96c7045e82340b62219019c2/bpf_go_tracer/head_first_bpf_v3_go.pdf -------------------------------------------------------------------------------- /bpf_go_tracer/imgs/gdb_int3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DavadDi/bpf_demo/90a0d2aaeff0a9de96c7045e82340b62219019c2/bpf_go_tracer/imgs/gdb_int3.gif -------------------------------------------------------------------------------- /bpf_go_tracer/imgs/go_tracer.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DavadDi/bpf_demo/90a0d2aaeff0a9de96c7045e82340b62219019c2/bpf_go_tracer/imgs/go_tracer.gif -------------------------------------------------------------------------------- /bpf_go_tracer/imgs/go_uprobe_arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DavadDi/bpf_demo/90a0d2aaeff0a9de96c7045e82340b62219019c2/bpf_go_tracer/imgs/go_uprobe_arch.png -------------------------------------------------------------------------------- /bpf_go_tracer/tracee/Makefile: -------------------------------------------------------------------------------- 1 | build: 2 | go build -o tracee main.go 3 | 4 | run: 5 | ./tracee 6 | 7 | clean: 8 | rm -rf tracee 9 | -------------------------------------------------------------------------------- /bpf_go_tracer/tracee/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | //go:noinline 6 | func ebpfDemo(a1 int, a2 bool, a3 float32) (r1 int64, r2 int32, r3 string) { 7 | 8 | fmt.Printf("ebpfDemo:: a1=%d, a2=%t a3=%.2f\n", a1, a2, a3) 9 | 10 | return 100, 200, "test for ebpf" 11 | } 12 | 13 | func main() { 14 | r1, r2, r3 := ebpfDemo(100, true, 66.88) 15 | fmt.Printf("main:: r1=%d, r2=%d r3=%s\n", r1, r2, r3) 16 | 17 | return; 18 | } 19 | 20 | -------------------------------------------------------------------------------- /bpf_go_tracer/tracer/Makefile: -------------------------------------------------------------------------------- 1 | build: 2 | go build -o tracer main.go 3 | 4 | 5 | run: 6 | sudo ./tracer --binary ../tracee/tracee --func main.ebpfDemo 7 | 8 | 9 | clean: 10 | rm -rf tracer 11 | -------------------------------------------------------------------------------- /bpf_go_tracer/tracer/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/DavadDi/bpf_demo/bpf_go_tracer/tracer 2 | 3 | go 1.15 4 | 5 | require github.com/iovisor/gobpf v0.2.0 // indirect 6 | -------------------------------------------------------------------------------- /bpf_go_tracer/tracer/go.sum: -------------------------------------------------------------------------------- 1 | github.com/iovisor/gobpf v0.2.0 h1:34xkQxft+35GagXBk3n23eqhm0v7q0ejeVirb8sqEOQ= 2 | github.com/iovisor/gobpf v0.2.0/go.mod h1:WSY9Jj5RhdgC3ci1QaacvbFdQ8cbrEjrpiZbLHLt2s4= 3 | -------------------------------------------------------------------------------- /bpf_go_tracer/tracer/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "flag" 6 | "encoding/binary" 7 | "fmt" 8 | "log" 9 | "os" 10 | "os/signal" 11 | "github.com/iovisor/gobpf/bcc" 12 | ) 13 | 14 | const ebpf_prog = ` 15 | #include 16 | 17 | BPF_PERF_OUTPUT(events); 18 | 19 | typedef struct { 20 | u64 arg1; 21 | char arg2; 22 | char pad[3]; 23 | float arg3; 24 | } args_event_t; 25 | 26 | inline int get_arguments(struct pt_regs *ctx) { 27 | void* stackAddr = (void*)ctx->sp; 28 | args_event_t event = {}; 29 | 30 | bpf_probe_read(&event.arg1, sizeof(event.arg1), stackAddr+8); 31 | bpf_probe_read(&event.arg2, sizeof(event.arg2), stackAddr+16); 32 | bpf_probe_read(&event.arg3, sizeof(event.arg3), stackAddr+20); 33 | 34 | long tmp = 2021; 35 | bpf_probe_write_user(stackAddr+8, &tmp, sizeof(tmp)); 36 | 37 | events.perf_submit(ctx, &event, sizeof(event)); 38 | 39 | return 0; 40 | } 41 | ` 42 | 43 | type argsEvent struct { 44 | Arg1 int64 45 | Arg2 byte 46 | Pad [3]byte 47 | Arg3 float32 48 | } 49 | 50 | var tracePrg string 51 | var traceFun string 52 | 53 | func init() { 54 | flag.StringVar(&tracePrg, "binary", "", "The binary to probe") 55 | flag.StringVar(&traceFun, "func", "", "The function to probe") 56 | } 57 | 58 | func main() { 59 | flag.Parse() 60 | 61 | if len(tracePrg) == 0 || len(traceFun) == 0 { 62 | panic("Argument --binary and --func needs to be specified") 63 | } 64 | 65 | fmt.Printf("Trace %s on func [%s]\n", tracePrg, traceFun); 66 | 67 | bpfModule := bcc.NewModule(ebpf_prog, []string{}) 68 | uprobeFd, err := bpfModule.LoadUprobe("get_arguments") 69 | 70 | if err != nil { 71 | log.Fatal(err) 72 | } 73 | 74 | err = bpfModule.AttachUprobe(tracePrg, traceFun, uprobeFd, -1) 75 | if err != nil { 76 | log.Fatal(err) 77 | } 78 | 79 | table := bcc.NewTable(bpfModule.TableId("events"), bpfModule) 80 | evtCh := make(chan []byte) 81 | lost := make(chan uint64) 82 | 83 | perfMap, err := bcc.InitPerfMap(table, evtCh, lost) 84 | if err != nil { 85 | log.Fatal(err) 86 | } 87 | 88 | sigCh := make(chan os.Signal, 1) 89 | signal.Notify(sigCh, os.Interrupt) 90 | 91 | go func() { 92 | var evt argsEvent 93 | 94 | for { 95 | data := <-evtCh 96 | fmt.Println(data) 97 | err := binary.Read(bytes.NewBuffer(data), binary.LittleEndian, &evt) 98 | if err != nil { 99 | fmt.Fprintf(os.Stderr, "Failed to decode received chroot event data: %s\n", err) 100 | continue 101 | } 102 | 103 | fmt.Printf("ebpfDemo:: a1=%d, a2=%d a3=%.2f\n", evt.Arg1, evt.Arg2, evt.Arg3) 104 | } 105 | }() 106 | 107 | perfMap.Start() 108 | <-sigCh 109 | perfMap.Stop() 110 | } 111 | -------------------------------------------------------------------------------- /hidden_file/Makefile: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) 2 | OUTPUT := .output 3 | CLANG ?= clang 4 | LLVM_STRIP ?= llvm-strip 5 | BPFTOOL ?= $(abspath ../../tools/bpftool) 6 | LIBBPF_SRC := $(abspath ../../libbpf/src) 7 | LIBBPF_OBJ := $(abspath $(OUTPUT)/libbpf.a) 8 | VMLINUX := ../../vmlinux/vmlinux.h 9 | # Use our own libbpf API headers and Linux UAPI headers distributed with 10 | # libbpf to avoid dependency on system-wide headers, which could be missing or 11 | # outdated 12 | INCLUDES := -I$(OUTPUT) -I../../libbpf/include/uapi -I$(dir $(VMLINUX)) 13 | CFLAGS := -g -Wall 14 | ARCH := $(shell uname -m | sed 's/x86_64/x86/' | sed 's/aarch64/arm64/' | sed 's/ppc64le/powerpc/' | sed 's/mips.*/mips/') 15 | 16 | APPS = minimal bootstrap obpf uprobe kprobe fentry 17 | 18 | # Get Clang's default includes on this system. We'll explicitly add these dirs 19 | # to the includes list when compiling with `-target bpf` because otherwise some 20 | # architecture-specific dirs will be "missing" on some architectures/distros - 21 | # headers such as asm/types.h, asm/byteorder.h, asm/socket.h, asm/sockios.h, 22 | # sys/cdefs.h etc. might be missing. 23 | # 24 | # Use '-idirafter': Don't interfere with include mechanics except where the 25 | # build would have failed anyways. 26 | CLANG_BPF_SYS_INCLUDES = $(shell $(CLANG) -v -E - &1 \ 27 | | sed -n '/<...> search starts here:/,/End of search list./{ s| \(/.*\)|-idirafter \1|p }') 28 | 29 | ifeq ($(V),1) 30 | Q = 31 | msg = 32 | else 33 | Q = @ 34 | msg = @printf ' %-8s %s%s\n' \ 35 | "$(1)" \ 36 | "$(patsubst $(abspath $(OUTPUT))/%,%,$(2))" \ 37 | "$(if $(3), $(3))"; 38 | MAKEFLAGS += --no-print-directory 39 | endif 40 | 41 | .PHONY: all 42 | all: $(APPS) 43 | 44 | .PHONY: clean 45 | clean: 46 | $(call msg,CLEAN) 47 | $(Q)rm -rf $(OUTPUT) $(APPS) 48 | 49 | $(OUTPUT) $(OUTPUT)/libbpf: 50 | $(call msg,MKDIR,$@) 51 | $(Q)mkdir -p $@ 52 | 53 | # Build libbpf 54 | $(LIBBPF_OBJ): $(wildcard $(LIBBPF_SRC)/*.[ch] $(LIBBPF_SRC)/Makefile) | $(OUTPUT)/libbpf 55 | $(call msg,LIB,$@) 56 | $(Q)$(MAKE) -C $(LIBBPF_SRC) BUILD_STATIC_ONLY=1 \ 57 | OBJDIR=$(dir $@)/libbpf DESTDIR=$(dir $@) \ 58 | INCLUDEDIR= LIBDIR= UAPIDIR= \ 59 | install 60 | 61 | # Build BPF code 62 | $(OUTPUT)/%.bpf.o: %.bpf.c $(LIBBPF_OBJ) $(wildcard %.h) $(VMLINUX) | $(OUTPUT) 63 | $(call msg,BPF,$@) 64 | $(Q)$(CLANG) -g -O2 -target bpf -D__TARGET_ARCH_$(ARCH) $(INCLUDES) $(CLANG_BPF_SYS_INCLUDES) -c $(filter %.c,$^) -o $@ 65 | $(Q)$(LLVM_STRIP) -g $@ # strip useless DWARF info 66 | 67 | # Generate BPF skeletons 68 | $(OUTPUT)/%.skel.h: $(OUTPUT)/%.bpf.o | $(OUTPUT) 69 | $(call msg,GEN-SKEL,$@) 70 | $(Q)$(BPFTOOL) gen skeleton $< > $@ 71 | 72 | # Build user-space code 73 | $(patsubst %,$(OUTPUT)/%.o,$(APPS)): %.o: %.skel.h 74 | 75 | $(OUTPUT)/%.o: %.c $(wildcard %.h) | $(OUTPUT) 76 | $(call msg,CC,$@) 77 | $(Q)$(CC) $(CFLAGS) $(INCLUDES) -c $(filter %.c,$^) -o $@ 78 | 79 | # Build application binary 80 | $(APPS): %: $(OUTPUT)/%.o $(LIBBPF_OBJ) | $(OUTPUT) 81 | $(call msg,BINARY,$@) 82 | $(Q)$(CC) $(CFLAGS) $^ -lelf -lz -o $@ 83 | 84 | # delete failed targets 85 | .DELETE_ON_ERROR: 86 | 87 | # keep intermediate (.skel.h, .bpf.o, etc) targets 88 | .SECONDARY: 89 | 90 | -------------------------------------------------------------------------------- /hidden_file/README.md: -------------------------------------------------------------------------------- 1 | # Offensive BPF: Understanding and using bpf_probe_write_user 2 | 3 | 编译依赖于 [libbp-bootstrap](https://github.com/libbpf/libbpf-bootstrap),编译和运行的机器需要支持 CO-RE,即内核编译选项默认内置支持了CONFIG_DEBUG_INFO_BTF=y,可以通过参看编译的 config 文件或者 /sys/kernel/btf/vmlinux 文件确认。 4 | 5 | 推荐 Ubuntu 21.10 已经内置支持了BTF,即 CO-RE 的底层依赖。 6 | 7 | 8 | ## 安装基本编译依赖 9 | 10 | ```bash 11 | $ sudo apt-get update 12 | $ sudo apt install -y bison build-essential cmake flex git libedit-dev libllvm11 llvm-11-dev libclang-11-dev python zlib1g-dev libelf-dev libfl-dev python3-distutils 13 | 14 | 15 | # 安装 llvm 和 clang, Ubuntu 21.10 默认 llvm13 版本 16 | $ sudo apt-get install clang llvm 17 | ``` 18 | 19 | ## 编译程序 20 | 21 | 22 | ```bash 23 | $ git clone https://github.com/libbpf/libbpf-bootstrap.git 24 | $ cd libbpf-bootstrap 25 | $ git submodule update --init --recursive # check out libbpf 26 | 27 | $ cd examples/c 28 | $ make 29 | $ sudo ./bootstrap 30 | ``` 31 | 32 | 以上编译完成后,将当前目录的所有文件 cp 到 example/c 目录中,重新进行编译即可。 33 | 34 | Makefile 的差异如下: 35 | 36 | ```bash 37 | $ git diff 38 | diff --git a/examples/c/Makefile b/examples/c/Makefile 39 | index 85a9fbe..1271172 100644 40 | --- a/examples/c/Makefile 41 | +++ b/examples/c/Makefile 42 | @@ -13,7 +13,7 @@ INCLUDES := -I$(OUTPUT) -I../../libbpf/include/uapi -I$(dir $(VMLINUX)) 43 | CFLAGS := -g -Wall 44 | ARCH := $(shell uname -m | sed 's/x86_64/x86/' | sed 's/aarch64/arm64/' | sed 's/ppc64le/powerpc/' | sed 's/mips.*/mips/') 45 | 46 | -APPS = minimal bootstrap uprobe kprobe fentry 47 | +APPS = minimal bootstrap obpf uprobe kprobe fentry 48 | 49 | # Get Clang's default includes on this system. We'll explicitly add these dirs 50 | # to the includes list when compiling with `-target bpf` because otherwise some 51 | ``` 52 | 53 | 编译完成后会生成 obpf 可执行程序,运行 obpf 程序后,通过 ls 查看文件将得到错误的展示。 54 | 55 | ```bash 56 | $ sudo ./obpf 57 | libbpf: elf: skipping unrecognized data section(7) .rodata.str1.1 58 | TIME EVENT COMM PID PPID FILENAME/EXIT COD 59 | ... 60 | ``` 61 | 62 | 使用 ls 查看文件: 63 | 64 | ```bash 65 | ubuntu@ubuntu21-10:~$ ls -hl 66 | ls: cannot access 'xxxxxxas_admin_successful': No such file or directory 67 | ls: cannot access 'xxxxxxle': No such file or directory 68 | ls: cannot access 'xxxxxxfo': No such file or directory 69 | ls: cannot access 'xxxxxxlogout': No such file or directory 70 | ls: cannot access 'xxxxxxc': No such file or directory 71 | total 4.0K 72 | drwxrwxr-x 3 ubuntu ubuntu 4.0K Dec 3 22:26 bpf_demo 73 | -????????? ? ? ? ? ? xxxxxxas_admin_successful 74 | -????????? ? ? ? ? ? xxxxxxc 75 | -????????? ? ? ? ? ? xxxxxxfo 76 | -????????? ? ? ? ? ? xxxxxxle 77 | -????????? ? ? ? ? ? xxxxxxlogout 78 | ``` 79 | 80 | 上述结果中的 xxxxxx 即为 bpf 程序写入。 81 | 82 | 通过 demsg -T 进行查看,可以得到以下提示信息: 83 | 84 | ```bash 85 | $ sudo dmsg -T 86 | ... 87 | [Fri Dec 3 23:12:16 2021] obpf[25207] is installing a program with bpf_probe_write_user helper that may corrupt user memory! 88 | [Fri Dec 3 23:12:16 2021] obpf[25207] is installing a program with bpf_probe_write_user helper that may corrupt user memory! 89 | [Fri Dec 3 23:12:16 2021] obpf[25207] is installing a program with bpf_probe_write_user helper that may corrupt user memory! 90 | [Fri Dec 3 23:12:16 2021] obpf[25207] is installing a program with bpf_probe_write_user helper that may corrupt user memory! 91 | 92 | ``` 93 | 94 | bpf 程序打印的日志: 95 | 96 | ```bash 97 | $ sudo cat /sys/kernel/debug/tracing/trace_pipe 98 | ... 99 | <...>-25282 [000] d... 41502.606920: bpf_trace_printk: ** sys_enter_getdents64 ** OVERWRITING 100 | <...>-25282 [000] d... 41502.606920: bpf_trace_printk: ** RESULT 0 101 | <...>-25282 [000] d... 41502.606921: bpf_trace_printk: Loop 3: offset: 96, total len: 96 102 | ... 103 | ``` 104 | 105 | 程序对应的博客为:[Offensive BPF: Understanding and using bpf_probe_write_user](https://embracethered.com/blog/posts/2021/offensive-bpf-libbpf-bpf_probe_write_user/) 106 | -------------------------------------------------------------------------------- /hidden_file/obpf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DavadDi/bpf_demo/90a0d2aaeff0a9de96c7045e82340b62219019c2/hidden_file/obpf -------------------------------------------------------------------------------- /hidden_file/obpf.bpf.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause 2 | 3 | #include "vmlinux.h" 4 | #include 5 | #include 6 | #include 7 | #include "obpf.h" 8 | 9 | char LICENSE[] SEC("license") = "Dual BSD/GPL"; 10 | const volatile unsigned long long min_duration_ns = 0; 11 | 12 | struct 13 | { 14 | __uint(type, BPF_MAP_TYPE_RINGBUF); 15 | __uint(max_entries, 256 * 1024); 16 | } rb SEC(".maps"); 17 | 18 | struct 19 | { 20 | __uint(type, BPF_MAP_TYPE_HASH); 21 | __uint(max_entries, 8192); 22 | __type(key, tid_t); 23 | __type(value, u64); 24 | } dir_entries SEC(".maps"); 25 | 26 | const int MAX_D_NAME_LEN = 128; 27 | const volatile char* PATTERN; 28 | const char DT_DIR = 4; 29 | //const char DT_REG = 8; 30 | 31 | SEC("tracepoint/syscalls/sys_enter_getdents64") 32 | int handle_enter_getdents64(struct trace_event_raw_sys_enter *ctx) 33 | { 34 | tid_t tid = bpf_get_current_pid_tgid(); 35 | 36 | struct linux_dirent64 *d_entry = (struct linux_dirent64 *) ctx->args[1]; 37 | if (d_entry == NULL) 38 | { 39 | return 0; 40 | } 41 | 42 | bpf_map_update_elem(&dir_entries, &tid, &d_entry, BPF_ANY); 43 | 44 | return 0; 45 | } 46 | 47 | SEC("tracepoint/syscalls/sys_exit_getdents64") 48 | int handle_exit_getdents64(struct trace_event_raw_sys_exit *ctx) 49 | { 50 | struct task_struct *task; 51 | struct event *e; 52 | 53 | task = (struct task_struct *)bpf_get_current_task(); 54 | tid_t tid = bpf_get_current_pid_tgid(); 55 | 56 | long unsigned int * dir_addr = bpf_map_lookup_elem(&dir_entries, &tid); 57 | if (dir_addr == NULL) 58 | { 59 | return 0; 60 | } 61 | 62 | bpf_map_delete_elem(&dir_entries, &tid); 63 | 64 | /* Loop over directory entries */ 65 | struct linux_dirent64 * d_entry; 66 | unsigned short int d_reclen; 67 | unsigned short int d_name_len; 68 | long offset = 0; 69 | 70 | long unsigned int d_entry_base_addr = *dir_addr; 71 | long ret = ctx->ret; 72 | 73 | //MAX_D_NAME_LEN == 128 - const isn't working with allocation here... 74 | char d_name[128]; 75 | int count = 16; 76 | char d_type; 77 | 78 | int i=0; 79 | while (i < 256) // limitation for now, only examine the first 256 entries 80 | { 81 | bpf_printk("Loop %d: offset: %d, total len: %d", i, offset, ret); 82 | 83 | if (offset >= ret) 84 | { 85 | break; 86 | } 87 | 88 | d_entry = (struct linux_dirent64 *) (d_entry_base_addr + offset); 89 | 90 | // read d_reclen 91 | bpf_probe_read_user(&d_reclen, sizeof(d_reclen), &d_entry->d_reclen); 92 | 93 | // skip if it's a directory entry 94 | bpf_probe_read_user(&d_type, sizeof(d_type), &d_entry->d_type); 95 | if (d_type == DT_DIR) 96 | { 97 | offset += d_reclen; 98 | i++; 99 | continue; 100 | } 101 | 102 | //read d_name 103 | d_name_len = d_reclen - 2 - (offsetof(struct linux_dirent64, d_name)); 104 | long success = bpf_probe_read_user(&d_name, MAX_D_NAME_LEN, d_entry->d_name); 105 | if ( success != 0 ) 106 | { 107 | offset += d_reclen; 108 | i++; 109 | continue; 110 | } 111 | 112 | bpf_printk("d_reclen: %d, d_name_len: %d, %s", d_reclen, d_name_len, d_name); 113 | 114 | /* match and overwrite */ 115 | if ( d_name_len > 6 ) 116 | { 117 | bpf_printk("** sys_enter_getdents64 ** OVERWRITING"); 118 | 119 | char replace[] = "xxxxxx"; 120 | 121 | // overwrite the user space d_name buffer 122 | long success = bpf_probe_write_user((char *) &d_entry->d_name, (char *) replace, sizeof(char) * 6); 123 | bpf_printk("** RESULT %d", success); 124 | } 125 | 126 | offset += d_reclen; 127 | i++; 128 | } 129 | 130 | return 0; 131 | } 132 | -------------------------------------------------------------------------------- /hidden_file/obpf.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) 2 | /* Copyright (c) 2020 Facebook */ 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "obpf.h" 10 | #include "obpf.skel.h" 11 | 12 | static struct env { 13 | bool verbose; 14 | long min_duration_ms; 15 | } env; 16 | 17 | const char *argp_program_version = "obpf 0.0"; 18 | const char *argp_program_bug_address = ""; 19 | const char argp_program_doc[] = 20 | "BPF obpf demo application.\n" 21 | "\n" 22 | "It traces process start and exits and shows associated \n" 23 | "information (filename, process duration, PID and PPID, etc).\n" 24 | "\n" 25 | "USAGE: ./obpf [-d ] [-v]\n"; 26 | 27 | static const struct argp_option opts[] = { 28 | { "verbose", 'v', NULL, 0, "Verbose debug output" }, 29 | { "duration", 'd', "DURATION-MS", 0, "Minimum process duration (ms) to report" }, 30 | {}, 31 | }; 32 | 33 | static error_t parse_arg(int key, char *arg, struct argp_state *state) 34 | { 35 | switch (key) { 36 | case 'v': 37 | env.verbose = true; 38 | break; 39 | case 'd': 40 | errno = 0; 41 | env.min_duration_ms = strtol(arg, NULL, 10); 42 | if (errno || env.min_duration_ms <= 0) { 43 | fprintf(stderr, "Invalid duration: %s\n", arg); 44 | argp_usage(state); 45 | } 46 | break; 47 | case ARGP_KEY_ARG: 48 | argp_usage(state); 49 | break; 50 | default: 51 | return ARGP_ERR_UNKNOWN; 52 | } 53 | return 0; 54 | } 55 | 56 | static const struct argp argp = { 57 | .options = opts, 58 | .parser = parse_arg, 59 | .doc = argp_program_doc, 60 | }; 61 | 62 | static int libbpf_print_fn(enum libbpf_print_level level, const char *format, va_list args) 63 | { 64 | if (level == LIBBPF_DEBUG && !env.verbose) 65 | return 0; 66 | return vfprintf(stderr, format, args); 67 | } 68 | 69 | static void bump_memlock_rlimit(void) 70 | { 71 | struct rlimit rlim_new = { 72 | .rlim_cur = RLIM_INFINITY, 73 | .rlim_max = RLIM_INFINITY, 74 | }; 75 | 76 | if (setrlimit(RLIMIT_MEMLOCK, &rlim_new)) { 77 | fprintf(stderr, "Failed to increase RLIMIT_MEMLOCK limit!\n"); 78 | exit(1); 79 | } 80 | } 81 | 82 | static volatile bool exiting = false; 83 | 84 | static void sig_handler(int sig) 85 | { 86 | exiting = true; 87 | } 88 | 89 | static int handle_event(void *ctx, void *data, size_t data_sz) 90 | { 91 | const struct event *e = data; 92 | struct tm *tm; 93 | char ts[32]; 94 | time_t t; 95 | 96 | time(&t); 97 | tm = localtime(&t); 98 | strftime(ts, sizeof(ts), "%H:%M:%S", tm); 99 | 100 | if (e->exit_event) { 101 | printf("%-8s %-5s %-16s %-7d %-7d [%u]", 102 | ts, "EXIT", e->comm, e->pid, e->ppid, e->exit_code); 103 | if (e->duration_ns) 104 | printf(" (%llums)", e->duration_ns / 1000000); 105 | printf("\n"); 106 | } else { 107 | printf("%-8s %-5s %-16s %-7d %-7d %s\n", 108 | ts, "EXEC", e->comm, e->pid, e->ppid, e->filename); 109 | } 110 | 111 | return 0; 112 | } 113 | 114 | int main(int argc, char **argv) 115 | { 116 | struct ring_buffer *rb = NULL; 117 | struct obpf_bpf *skel; 118 | int err; 119 | 120 | /* Parse command line arguments */ 121 | err = argp_parse(&argp, argc, argv, 0, NULL, NULL); 122 | if (err) 123 | return err; 124 | 125 | /* Set up libbpf errors and debug info callback */ 126 | libbpf_set_print(libbpf_print_fn); 127 | 128 | /* Bump RLIMIT_MEMLOCK to create BPF maps */ 129 | bump_memlock_rlimit(); 130 | 131 | /* Cleaner handling of Ctrl-C */ 132 | signal(SIGINT, sig_handler); 133 | signal(SIGTERM, sig_handler); 134 | 135 | /* Load and verify BPF application */ 136 | skel = obpf_bpf__open(); 137 | if (!skel) { 138 | fprintf(stderr, "Failed to open and load BPF skeleton\n"); 139 | return 1; 140 | } 141 | 142 | /* Parameterize BPF code with minimum duration parameter */ 143 | skel->rodata->min_duration_ns = env.min_duration_ms * 1000000ULL; 144 | 145 | /* Load & verify BPF programs */ 146 | err = obpf_bpf__load(skel); 147 | if (err) { 148 | fprintf(stderr, "Failed to load and verify BPF skeleton\n"); 149 | goto cleanup; 150 | } 151 | 152 | /* Attach tracepoints */ 153 | err = obpf_bpf__attach(skel); 154 | if (err) { 155 | fprintf(stderr, "Failed to attach BPF skeleton\n"); 156 | goto cleanup; 157 | } 158 | 159 | /* Set up ring buffer polling */ 160 | rb = ring_buffer__new(bpf_map__fd(skel->maps.rb), handle_event, NULL, NULL); 161 | if (!rb) { 162 | err = -1; 163 | fprintf(stderr, "Failed to create ring buffer\n"); 164 | goto cleanup; 165 | } 166 | 167 | /* Process events */ 168 | printf("%-8s %-5s %-16s %-7s %-7s %s\n", 169 | "TIME", "EVENT", "COMM", "PID", "PPID", "FILENAME/EXIT CODE"); 170 | while (!exiting) { 171 | err = ring_buffer__poll(rb, 100 /* timeout, ms */); 172 | /* Ctrl-C will cause -EINTR */ 173 | if (err == -EINTR) { 174 | err = 0; 175 | break; 176 | } 177 | if (err < 0) { 178 | printf("Error polling perf buffer: %d\n", err); 179 | break; 180 | } 181 | } 182 | 183 | cleanup: 184 | /* Clean up */ 185 | ring_buffer__free(rb); 186 | obpf_bpf__destroy(skel); 187 | 188 | return err < 0 ? -err : 0; 189 | } 190 | -------------------------------------------------------------------------------- /hidden_file/obpf.h: -------------------------------------------------------------------------------- 1 | #ifndef __OBPF_H 2 | #define __OBPF_H 3 | 4 | #define TASK_COMM_LEN 16 5 | #define MAX_FILENAME_LEN 127 6 | 7 | struct event { 8 | int pid; 9 | int ppid; 10 | unsigned exit_code; 11 | unsigned long long duration_ns; 12 | char comm[TASK_COMM_LEN]; 13 | char filename[MAX_FILENAME_LEN]; 14 | bool exit_event; 15 | }; 16 | 17 | #endif /* __OBPF_H */ 18 | --------------------------------------------------------------------------------