diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..eff61f4 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,111 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +stackplz is an eBPF-based dynamic tracing tool for Android. It supports syscall tracing, uprobe hooking, hardware breakpoints, and stack unwinding on ARM64/ARM32 devices running kernel 5.10+. + +The codebase consists of two parts: +- **eBPF C programs** (`src/`) — kernel-space probes compiled to BPF bytecode +- **Go user-space code** (`user/`, `cli/`, `main.go`) — loads eBPF programs, processes events, provides CLI + +## Build Commands + +**Prerequisites:** The project requires three sibling repos in the same parent directory: +```bash +# Must be cloned alongside stackplz at the same level +git clone https://github.com/SeeFlowerX/ebpf # modified cilium/ebpf +git clone https://github.com/SeeFlowerX/ebpfmanager # modified ehids/ebpfmanager +``` +Also requires: `libbpf`, `bpftool v7.2.0`, Android NDK r25c, Go 1.18. Run `./build_env.sh` to download libbpf, bpftool, and BTF files. + +**Build (ARM64):** +```bash +make clean && make +# Output: bin/stackplz_arm64 +``` + +**Build (ARM32):** +```bash +make clean && BUILD_TAGS=forarm make +# Output: bin/stackplz_arm +``` + +**Build with debug prints:** +```bash +make clean && DEBUG=1 make +``` + +**Deploy to device:** +```bash +adb push bin/stackplz_arm64 /data/local/tmp +``` + +The `Makefile` pipeline: compile eBPF C → generate min BTF → pack assets via go-bindata → cross-compile Go binary for Android. + +## Architecture + +### Data Flow + +``` +CLI (cobra) → Module Manager → eBPF Programs (kernel) + ↓ + Perf Ring Buffer + ↓ + Event Processor (worker pool) → Output (stdout/file/json) +``` + +### Module System (`user/module/`) + +All modules implement the `IModule` interface (`imodule.go`). Each module manages its own eBPF program loading, map configuration, and event parsing. + +| Module | eBPF Source | Purpose | +|--------|-------------|---------| +| Stack (`stack.go`) | `src/stack.c` | uprobe/uretprobe hooking | +| Syscall (`syscall.go`) | `src/syscall.c` | raw_tracepoint sys_enter/sys_exit | +| Brk (`brk.go`) | — | Hardware breakpoints (via perf_event) | +| PerfMmap (`perf_mmap.go`) | `src/perf_mmap.c` | Process memory mapping tracking | + +### Key Packages + +- **`cli/cmd/`** — Cobra CLI entry point, all flag definitions in `root.go` +- **`user/config/`** — Configuration management: global config, module configs, JSON config file parsing, filter rules +- **`user/event/`** — Event types implementing `IEventStruct` interface (uprobe, syscall, brk, mmap, fork, exit, comm) +- **`user/argtype/`** — Argument type definitions and parsing (struct, string, sockaddr, buffer, register expressions) +- **`user/event_processor/`** — Concurrent worker pool for event processing +- **`user/event_parser/`** — Event binary data parsing +- **`user/rpc/`** — TCP RPC server (port 41718) for Frida integration to set hardware breakpoints +- **`user/util/`** — Android-specific helpers, BPF utilities + +### eBPF Side (`src/`) + +- `src/common/` — Shared headers for filtering, argument capture, buffer management +- `src/types.h` — Shared type definitions between kernel and user space +- `src/maps.h` — eBPF map definitions +- `src/vmlinux_510.h` — Auto-generated kernel struct definitions (110k lines, do not edit) +- Conditional compilation via `-D__MODULE_STACK` / `-D__MODULE_SYSCALL` and `-D__TARGET_ARCH_arm64` / `-D__TARGET_ARCH_arm` + +### Asset Embedding + +eBPF object files (`.o`), BTF files, preload libraries (`preload_libs/*.so`), and syscall config JSONs are packed into `assets/ebpf_probe.go` via `go-bindata` during build. This file is auto-generated — do not edit. + +## go.mod `replace` Directives + +The project uses `replace` directives pointing to `../ebpf` and `../ebpfmanager`. These are modified forks that add perf event options (stack sampling, register sampling). Standard cilium/ebpf will not work. + +## Testing + +There are no unit tests. The `tests/` directory contains JSON config files for manual functional testing on Android devices: +```bash +# Example: run with a test config on device +./stackplz -n com.example.app -c tests/config_uprobe_test.json +``` + +## CI + +GitHub Actions (`.github/workflows/build.yml`) builds both ARM64 and ARM32 binaries on push/PR to `master` or `dev` branches. + +## Language + +All documentation, comments, and commit messages are in Chinese (Simplified). diff --git a/Makefile b/Makefile index 05bd611..f465d02 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,7 @@ TARGET_ARCH = arm endif .PHONY: all -all: ebpf_stack ebpf_syscall ebpf_perf_mmap genbtf assets build +all: ebpf_stack ebpf_syscall ebpf_perf_mmap ebpf_binder genbtf assets build @echo $(shell date) @@ -79,10 +79,27 @@ ebpf_perf_mmap: -o user/assets/perf_mmap.o \ src/perf_mmap.c +.PHONY: ebpf_binder +ebpf_binder: + clang \ + -D__TARGET_ARCH_$(TARGET_ARCH) \ + -D__MODULE_BINDER \ + --target=bpf \ + -c \ + -nostdlibinc \ + -no-canonical-prefixes \ + -O2 \ + $(DEBUG_PRINT) \ + -I libbpf/src \ + -I src \ + -g \ + -o user/assets/binder.o \ + src/binder.c + .PHONY: genbtf genbtf: - cd ${ASSETS_PATH} && ./$(CMD_BPFTOOL) gen min_core_btf rock5b-5.10-f9d1b1529-arm64.btf rock5b-5.10-arm64_min.btf stack.o syscall.o - cd ${ASSETS_PATH} && ./$(CMD_BPFTOOL) gen min_core_btf a12-5.10-arm64.btf a12-5.10-arm64_min.btf stack.o syscall.o + cd ${ASSETS_PATH} && ./$(CMD_BPFTOOL) gen min_core_btf rock5b-5.10-f9d1b1529-arm64.btf rock5b-5.10-arm64_min.btf stack.o syscall.o binder.o + cd ${ASSETS_PATH} && ./$(CMD_BPFTOOL) gen min_core_btf a12-5.10-arm64.btf a12-5.10-arm64_min.btf stack.o syscall.o binder.o .PHONY: assets assets: @@ -90,4 +107,4 @@ assets: .PHONY: build build: - GOARCH=arm64 GOOS=android CGO_ENABLED=1 CC=aarch64-linux-android29-clang $(CMD_GO) build $(BUILD_TAGS) -ldflags "-w -s -extldflags '-Wl,--hash-style=sysv'" -o bin/stackplz_$(TARGET_ARCH) . \ No newline at end of file + GOARCH=arm64 GOOS=android CGO_ENABLED=1 CC=aarch64-linux-android29-clang $(CMD_GO) build $(BUILD_TAGS) -ldflags "-w -s -extldflags '-Wl,--hash-style=sysv'" -o bin/stackplz_$(TARGET_ARCH) . diff --git a/cli/cmd/root.go b/cli/cmd/root.go index 7f4aaa3..5ef47c9 100644 --- a/cli/cmd/root.go +++ b/cli/cmd/root.go @@ -335,6 +335,22 @@ func persistentPreRunEFunc(command *cobra.Command, args []string) error { mconfig.BrkAddr = brk_base + addr } + // 5. binder trace + if gconfig.BinderTrace { + binderSconfig := &config.StackUprobeConfig{} + err = gconfig.Parse_Libinfo(gconfig.BinderLib, binderSconfig) + if err != nil { + return fmt.Errorf("parse binder library failed: %v", err) + } + mconfig.BinderConf.Enable = true + mconfig.BinderConf.LibPath = binderSconfig.LibPath + mconfig.BinderConf.RealFilePath = binderSconfig.RealFilePath + mconfig.BinderConf.Filter = gconfig.BinderFilter + mconfig.BinderConf.NoReply = gconfig.BinderNoReply + mconfig.BinderConf.DumpHex = gconfig.DumpHex + logger.Printf("binder trace enabled, lib:%s", binderSconfig.LibPath) + } + // 检查hook设定 enable_hook := false if len(mconfig.StackUprobeConf.Points) > 0 { @@ -357,8 +373,12 @@ func persistentPreRunEFunc(command *cobra.Command, args []string) error { } logger.Printf("set breakpoint at kernel:%t, addr:0x%x, type:%d", mconfig.BrkKernel, mconfig.BrkAddr, mconfig.BrkType) } + if mconfig.BinderConf != nil && mconfig.BinderConf.Enable { + enable_hook = true + logger.Printf("binder trace enabled") + } if !enable_hook { - logger.Fatal("hook nothing, plz set -w/--point or -s/--syscall or --brk") + logger.Fatal("hook nothing, plz set -w/--point or -s/--syscall or --brk or --binder") } if gconfig.ParseFile != "" { parser := event_parser.NewEventParser() @@ -384,16 +404,29 @@ func runFunc(command *cobra.Command, args []string) { var wg sync.WaitGroup var modNames []string + hasPerf := false if mconfig.BrkAddr != 0 { modNames = append(modNames, module.MODULE_NAME_BRK) - } else if mconfig.SysCallConf.Enable { - modNames = append(modNames, module.MODULE_NAME_PERF) + } + if mconfig.SysCallConf.Enable { + if !hasPerf { + modNames = append(modNames, module.MODULE_NAME_PERF) + hasPerf = true + } modNames = append(modNames, module.MODULE_NAME_SYSCALL) - } else if len(mconfig.StackUprobeConf.Points) > 0 { - modNames = append(modNames, module.MODULE_NAME_PERF) + } + if len(mconfig.StackUprobeConf.Points) > 0 { + if !hasPerf { + modNames = append(modNames, module.MODULE_NAME_PERF) + hasPerf = true + } modNames = append(modNames, module.MODULE_NAME_STACK) - } else { - Logger.Fatal("hook nothing, plz set -w/--point or -s/--syscall or --brk") + } + if mconfig.BinderConf != nil && mconfig.BinderConf.Enable { + modNames = append(modNames, module.MODULE_NAME_BINDER) + } + if len(modNames) == 0 { + Logger.Fatal("hook nothing, plz set -w/--point or -s/--syscall or --brk or --binder") } for _, modName := range modNames { // 现在合并成只有一个模块了 所以直接通过名字获取 @@ -675,4 +708,9 @@ func init() { rootCmd.PersistentFlags().StringVar(&gconfig.NoSysCall, "no-syscall", "", "syscall black list, max 20") // config hook rootCmd.PersistentFlags().StringArrayVarP(&gconfig.ConfigFiles, "config", "c", []string{}, "hook config file") + // binder trace + rootCmd.PersistentFlags().BoolVar(&gconfig.BinderTrace, "binder", false, "enable binder transact tracing") + rootCmd.PersistentFlags().StringVar(&gconfig.BinderLib, "binder-lib", "libbinder.so", "path to libbinder.so") + rootCmd.PersistentFlags().StringVar(&gconfig.BinderFilter, "binder-filter", "", "filter by interface descriptor") + rootCmd.PersistentFlags().BoolVar(&gconfig.BinderNoReply, "binder-no-reply", false, "skip reply events") } diff --git a/docs/BINDER_DUMP_FORMAT.md b/docs/BINDER_DUMP_FORMAT.md new file mode 100644 index 0000000..784b985 --- /dev/null +++ b/docs/BINDER_DUMP_FORMAT.md @@ -0,0 +1,367 @@ +# Binder Dump 二进制格式文档 + +本文档描述 `--dump` 导出的 binder 事件二进制数据格式,供 host 端 binder-trace Python 项目解析。 + +## 导出方式 + +```bash +./stackplz -n com.example.app --binder --dump binder_raw.bin +``` + +## 文件整体结构 + +dump 文件由连续的记录组成,每条记录格式如下: + +``` +[total_len: u32][event_index: u8][rec_type: u32][rec_len: u32][rec_raw: bytes] +``` + +| 字段 | 类型 | 说明 | +|------|------|------| +| total_len | u32 LE | 从 event_index 到末尾的总字节数(不含 total_len 本身的4字节) | +| event_index | u8 | 事件类型索引,binder 事件为 `5`(BINDER_EVENT) | +| rec_type | u32 LE | perf record 类型,正常事件为 `9`(PERF_RECORD_SAMPLE) | +| rec_len | u32 LE | rec_raw 的字节数 | +| rec_raw | bytes | perf buffer 的 RawSample,包含 event_context_t + args | + +Python 读取循环: + +```python +import struct + +def read_records(filepath): + with open(filepath, 'rb') as f: + while True: + buf = f.read(4) + if len(buf) < 4: + break + total_len = struct.unpack(' 0 else b'' + off += max(parcel_len, 0) + + return { + 'handle': handle, + 'code': code, + 'flags': flags, + 'oneway': bool(flags & 0x1), + 'data_size': data_size, + 'parcel_data': parcel_data, + 'truncated': data_size > len(parcel_data), + }, off +``` + +## BINDER_REPLY 事件(eventid=461) + +args 区域包含 8 个参数: + +``` +参数 索引 格式 说明 +arg0 0 [0x00][handle: u32] Binder 句柄 +arg1 1 [0x01][code: u32] Transaction code +arg2 2 [0x02][flags: u32] 标志位 +arg3 3 [0x03][data_size: u32] TRANSACT Parcel 完整大小 +arg4 4 [0x04][size: i32][data: bytes] TRANSACT Parcel 原始数据 +arg5 5 [0x05][ret_value: u32] transact() 返回值(0=成功) +arg6 6 [0x06][reply_size: u32] Reply Parcel 完整大小 +arg7 7 [0x07][rsize: i32][rdata: bytes] Reply Parcel 原始数据 +``` + +**注意**: +- arg4 的 data 是原始 TRANSACT 的 Parcel 数据(包含 Interface Token),与 TRANSACT 事件的 arg4 内容相同 +- arg7 的 rdata 是 reply Parcel 数据(不包含 Interface Token,仅有返回数据) +- 当 flags & 0x1 == 1(oneway 调用)时,reply_size=0 且 rdata 为空 + +Python 解析: + +```python +def parse_reply_args(buf): + off = 0 + # arg0-arg4 与 TRANSACT 相同 + assert buf[off] == 0; off += 1 + handle = struct.unpack(' 0 else b'' + off += max(parcel_len, 0) + # arg5: ret_value + assert buf[off] == 5; off += 1 + ret_value = struct.unpack(' 0 else b'' + off += max(reply_len, 0) + + return { + 'handle': handle, + 'code': code, + 'flags': flags, + 'oneway': bool(flags & 0x1), + 'data_size': data_size, + 'parcel_data': parcel_data, + 'ret_value': ret_value, + 'reply_size': reply_size, + 'reply_data': reply_data, + }, off +``` + +## Interface Token 在 Parcel 数据中的布局 + +TRANSACT 事件的 `parcel_data`(arg4)和 REPLY 事件的 `parcel_data`(arg4)均以 Interface Token 开头。布局因 Android 版本不同而异: + +### Android 9(sdk_int = 28) + +``` +偏移 字段 类型 大小 ++0x00 strict_mode_policy u32 LE 4 ++0x04 descriptor_length u32 LE 4 UTF-16 字符数 ++0x08 descriptor UTF-16LE descriptor_length * 2 字节 ++N null_terminator u16 2 ++N+2 align4 padding 0-2 字节 对齐到 4 字节边界 +``` + +### Android 10(sdk_int = 29) + +``` +偏移 字段 类型 大小 ++0x00 strict_mode_policy u32 LE 4 ++0x04 work_source_uid u32 LE 4 ++0x08 descriptor_length u32 LE 4 ++0x0C descriptor UTF-16LE descriptor_length * 2 字节 ++N null_terminator u16 2 ++N+2 align4 padding 0-2 字节 +``` + +### Android 11+(sdk_int >= 30) + +``` +偏移 字段 类型 大小 ++0x00 strict_mode_policy u32 LE 4 ++0x04 work_source_uid u32 LE 4 ++0x08 version_header u32 LE 4 ++0x0C descriptor_length u32 LE 4 ++0x10 descriptor UTF-16LE descriptor_length * 2 字节 ++N null_terminator u16 2 ++N+2 align4 padding 0-2 字节 +``` + +Python 解析示例: + +```python +def parse_interface_token(data, sdk_int): + """从 Parcel 原始数据解析 Interface Token descriptor""" + if sdk_int <= 28: + desc_len_off = 4 + elif sdk_int <= 29: + desc_len_off = 8 + else: + desc_len_off = 12 + + if len(data) < desc_len_off + 4: + return None + + desc_len = struct.unpack(' 2048 or byte_len < 0: + return None + if len(data) < off + byte_len: + return None + try: + return data[off:off+byte_len].decode('utf-16-le') + except: + return None + +def hexdump(data, max_bytes=128): + lines = [] + for i in range(0, min(len(data), max_bytes), 16): + chunk = data[i:i+16] + hex_str = ' '.join(f'{b:02x}' for b in chunk) + ascii_str = ''.join(chr(b) if 32 <= b < 127 else '.' for b in chunk) + lines.append(f' {i:04x}: {hex_str:<48s} {ascii_str}') + if len(data) > max_bytes: + lines.append(f' ... ({len(data)} bytes total)') + return '\n'.join(lines) + +def read_fixed_arg(buf, off, expect_idx=None): + """读取 save_to_submit_buf 格式: [index: u8][value: u32]""" + if off + 5 > len(buf): + return None, None, off + idx = buf[off] + if expect_idx is not None and idx != expect_idx: + return None, None, off + val = struct.unpack(' len(buf): + return None, None, off + idx = buf[off] + if expect_idx is not None and idx != expect_idx: + return None, None, off + size = struct.unpack(' len(buf): + data = buf[off:] + return idx, data, len(buf) + return idx, buf[off:off+size], off + size + +def main(): + filepath = sys.argv[1] if len(sys.argv) > 1 else 'binder.bin' + with open(filepath, 'rb') as f: + raw = f.read() + + off = 0 + record_num = 0 + errors = [] + transact_count = 0 + reply_count = 0 + transact_has_parcel = 0 + reply_has_data_parcel = 0 + reply_has_reply_parcel = 0 + descriptors = {} + comms = {} + pids = set() + parcel_size_hist = {} + ret_value_counts = {} + + print(f"文件大小: {len(raw)} bytes\n") + + while off < len(raw): + if off + 4 > len(raw): + break + total_len = struct.unpack(' len(raw): + errors.append(f"record#{record_num+1}: total_len={total_len} 超出文件末尾") + break + + payload = raw[off:off+total_len] + off += total_len + record_num += 1 + + if len(payload) < 9: + errors.append(f"record#{record_num}: payload 太短") + continue + + event_index = payload[0] + rec_type = struct.unpack('args[0]; + struct task_struct *child = (struct task_struct *) ctx->args[1]; + + u32 parent_ns_pid = get_task_ns_pid(parent); + u32 child_ns_pid = get_task_ns_pid(child); + + u32* pid = bpf_map_lookup_elem(&child_parent_map, &parent_ns_pid); + if (unlikely(pid == NULL)) return 0; + + if (*pid == parent_ns_pid) { + bpf_map_update_elem(&child_parent_map, &child_ns_pid, &parent_ns_pid, BPF_ANY); + } + return 0; +} + +SEC("uprobe/binder_transact") +int probe_binder_transact(struct pt_regs* ctx) { + program_data_t p = {}; + if (!init_program_data(&p, ctx)) + return 0; + + if (!should_trace(&p)) + return 0; + + // 读取函数参数 + u32 handle, code, flags; + u64 data_ptr, reply_ptr; + +#if defined(__TARGET_ARCH_arm) + handle = (u32)READ_KERN(ctx->regs[1]); + code = (u32)READ_KERN(ctx->regs[2]); + data_ptr = (u64)(u32)READ_KERN(ctx->regs[3]); + // reply 和 flags 在栈上 + u32 sp_val = (u32)READ_KERN(ctx->regs[13]); + u32 reply_32 = 0, flags_32 = 0; + bpf_probe_read_user(&reply_32, sizeof(reply_32), (void*)(u64)sp_val); + bpf_probe_read_user(&flags_32, sizeof(flags_32), (void*)(u64)(sp_val + 4)); + reply_ptr = (u64)reply_32; + flags = flags_32; +#else + handle = (u32)READ_KERN(ctx->regs[1]); + code = (u32)READ_KERN(ctx->regs[2]); + data_ptr = READ_KERN(ctx->regs[3]); + reply_ptr = READ_KERN(ctx->regs[4]); + flags = (u32)READ_KERN(ctx->regs[5]); +#endif + + // 从 Parcel 结构体读取 mData 和 mDataSize + u64 m_data = 0; + u32 m_data_size = 0; + +#if defined(__TARGET_ARCH_arm) + // ARM32: mData at +0x04 (4 bytes ptr), mDataSize at +0x08 (4 bytes) + u32 m_data_32 = 0; + bpf_probe_read_user(&m_data_32, sizeof(m_data_32), (void*)(data_ptr + 4)); + m_data = (u64)m_data_32; + bpf_probe_read_user(&m_data_size, sizeof(m_data_size), (void*)(data_ptr + 8)); +#else + // ARM64: mData at +0x08 (8 bytes ptr), mDataSize at +0x10 (8 bytes, but size_t) + bpf_probe_read_user(&m_data, sizeof(m_data), (void*)(data_ptr + 8)); + u64 m_data_size_64 = 0; + bpf_probe_read_user(&m_data_size_64, sizeof(m_data_size_64), (void*)(data_ptr + 0x10)); + m_data_size = (u32)m_data_size_64; +#endif + + // 保存 per-thread 状态到 binder_state_map(用于 uretprobe) + u32 tid = bpf_get_current_pid_tgid(); + binder_thread_state_t state = {}; + state.handle = handle; + state.code = code; + state.flags = flags; + state.data_ptr = data_ptr; + state.data_size = m_data_size; + state.reply_ptr = reply_ptr; + bpf_map_update_elem(&binder_state_map, &tid, &state, BPF_ANY); + + // 构建 BINDER_TRANSACT 事件 + // args: [0]handle [1]code [2]flags [3]data_size [4]parcel_data + save_to_submit_buf(p.event, (void *)&handle, sizeof(handle), 0); + save_to_submit_buf(p.event, (void *)&code, sizeof(code), 1); + save_to_submit_buf(p.event, (void *)&flags, sizeof(flags), 2); + save_to_submit_buf(p.event, (void *)&m_data_size, sizeof(m_data_size), 3); + + // 读取 Parcel 原始数据 + // 去除 ARM64 MTA (Memory Tagging) 高位 tag,否则 bpf_probe_read 会失败 + m_data = m_data & 0x00ffffffffffffff; + u32 read_size = m_data_size; + if (read_size > MAX_PARCEL_SIZE) { + read_size = MAX_PARCEL_SIZE; + } + if (m_data != 0 && read_size > 0) { + save_bytes_to_buf(p.event, (void *)m_data, read_size, 4); + } else { + save_bytes_to_buf(p.event, 0, 0, 4); + } + + events_perf_submit(&p, BINDER_TRANSACT); + return 0; +} + +SEC("uretprobe/binder_transact") +int probe_binder_transact_ret(struct pt_regs* ctx) { + program_data_t p = {}; + if (!init_program_data(&p, ctx)) + return 0; + + if (!should_trace(&p)) + return 0; + + // 从 binder_state_map 恢复 per-thread 状态 + u32 tid = bpf_get_current_pid_tgid(); + binder_thread_state_t *state = bpf_map_lookup_elem(&binder_state_map, &tid); + if (state == NULL) + return 0; + + u32 handle = state->handle; + u32 code = state->code; + u32 flags = state->flags; + u32 data_size = state->data_size; + u64 data_ptr = state->data_ptr; + u64 reply_ptr = state->reply_ptr; + + // 清理 per-thread 状态 + bpf_map_delete_elem(&binder_state_map, &tid); + + // 获取返回值 +#if defined(__TARGET_ARCH_arm) + u32 ret_value = (u32)READ_KERN(ctx->regs[0]); +#else + u32 ret_value = (u32)READ_KERN(ctx->regs[0]); +#endif + + // 构建 BINDER_REPLY 事件 + // args: [0]handle [1]code [2]flags [3]data_size [4]data_parcel [5]ret_value [6]reply_size [7]reply_data + save_to_submit_buf(p.event, (void *)&handle, sizeof(handle), 0); + save_to_submit_buf(p.event, (void *)&code, sizeof(code), 1); + save_to_submit_buf(p.event, (void *)&flags, sizeof(flags), 2); + save_to_submit_buf(p.event, (void *)&data_size, sizeof(data_size), 3); + + // 重新读取原始 TRANSACT Parcel 数据(包含 Interface Token) + u64 m_data = 0; + u32 m_data_size = 0; + +#if defined(__TARGET_ARCH_arm) + u32 m_data_32 = 0; + bpf_probe_read_user(&m_data_32, sizeof(m_data_32), (void*)(data_ptr + 4)); + m_data = (u64)m_data_32; + bpf_probe_read_user(&m_data_size, sizeof(m_data_size), (void*)(data_ptr + 8)); +#else + bpf_probe_read_user(&m_data, sizeof(m_data), (void*)(data_ptr + 8)); + u64 m_data_size_64 = 0; + bpf_probe_read_user(&m_data_size_64, sizeof(m_data_size_64), (void*)(data_ptr + 0x10)); + m_data_size = (u32)m_data_size_64; +#endif + + // 去除 ARM64 MTA tag + m_data = m_data & 0x00ffffffffffffff; + u32 data_read_size = m_data_size; + if (data_read_size > MAX_PARCEL_SIZE) { + data_read_size = MAX_PARCEL_SIZE; + } + if (m_data != 0 && data_read_size > 0) { + save_bytes_to_buf(p.event, (void *)m_data, data_read_size, 4); + } else { + save_bytes_to_buf(p.event, 0, 0, 4); + } + + save_to_submit_buf(p.event, (void *)&ret_value, sizeof(ret_value), 5); + + // 读取 reply Parcel 数据(仅非 oneway 调用) + if ((flags & 0x1) == 0 && reply_ptr != 0) { + u64 reply_m_data = 0; + u32 reply_m_data_size = 0; + +#if defined(__TARGET_ARCH_arm) + u32 reply_m_data_32 = 0; + bpf_probe_read_user(&reply_m_data_32, sizeof(reply_m_data_32), (void*)(reply_ptr + 4)); + reply_m_data = (u64)reply_m_data_32; + bpf_probe_read_user(&reply_m_data_size, sizeof(reply_m_data_size), (void*)(reply_ptr + 8)); +#else + bpf_probe_read_user(&reply_m_data, sizeof(reply_m_data), (void*)(reply_ptr + 8)); + u64 reply_size_64 = 0; + bpf_probe_read_user(&reply_size_64, sizeof(reply_size_64), (void*)(reply_ptr + 0x10)); + reply_m_data_size = (u32)reply_size_64; +#endif + + save_to_submit_buf(p.event, (void *)&reply_m_data_size, sizeof(reply_m_data_size), 6); + + // 去除 ARM64 MTA tag + reply_m_data = reply_m_data & 0x00ffffffffffffff; + u32 reply_read_size = reply_m_data_size; + if (reply_read_size > MAX_PARCEL_SIZE) { + reply_read_size = MAX_PARCEL_SIZE; + } + if (reply_m_data != 0 && reply_read_size > 0) { + save_bytes_to_buf(p.event, (void *)reply_m_data, reply_read_size, 7); + } else { + save_bytes_to_buf(p.event, 0, 0, 7); + } + } else { + u32 zero_size = 0; + save_to_submit_buf(p.event, (void *)&zero_size, sizeof(zero_size), 6); + save_bytes_to_buf(p.event, 0, 0, 7); + } + + events_perf_submit(&p, BINDER_REPLY); + return 0; +} + +char __license[] SEC("license") = "GPL"; +__u32 _version SEC("version") = 0xFFFFFFFE; diff --git a/src/common/consts.h b/src/common/consts.h index 05bfb4f..7cd7b19 100644 --- a/src/common/consts.h +++ b/src/common/consts.h @@ -30,8 +30,13 @@ #define MAX_PERCPU_BUFSIZE (1 << 15) // set by the kernel as an upper bound #define PATH_MAX 4096 #define MAX_STRING_SIZE 16384 +#ifdef __MODULE_BINDER +#define MAX_BYTES_ARR_SIZE 16384 +#define MAX_BUF_READ_SIZE 16384 +#else #define MAX_BYTES_ARR_SIZE 4096 // same as PATH_MAX -#define MAX_BUF_READ_SIZE 4096 +#define MAX_BUF_READ_SIZE 4096 +#endif #define ARGS_BUF_SIZE 32000 // 配合 common_list 使用的 它们的间隔范围都是 0x400 diff --git a/src/maps.h b/src/maps.h index 53d1f3d..53389b6 100644 --- a/src/maps.h +++ b/src/maps.h @@ -51,4 +51,8 @@ BPF_HASH(sysenter_point_args, u32, point_args_t, 512); BPF_HASH(sysexit_point_args, u32, point_args_t, 512); BPF_ARRAY(base_config, config_entry_t, 1); +#ifdef __MODULE_BINDER +BPF_LRU_HASH(binder_state_map, u32, binder_thread_state_t, 2048); +#endif + #endif /* __MAPS_H__ */ \ No newline at end of file diff --git a/src/types.h b/src/types.h index 654b871..72cb5c3 100644 --- a/src/types.h +++ b/src/types.h @@ -65,7 +65,10 @@ enum event_id_e { SYSCALL_ENTER = 456, SYSCALL_EXIT, - UPROBE_ENTER + UPROBE_ENTER, + HW_BREAKPOINT, + BINDER_TRANSACT, + BINDER_REPLY }; enum op_code_e @@ -280,6 +283,17 @@ typedef u32 __kernel_dev_t; typedef __kernel_dev_t dev_t; +#ifdef __MODULE_BINDER +typedef struct binder_thread_state { + u32 handle; + u32 code; + u32 flags; + u32 data_size; + u64 data_ptr; + u64 reply_ptr; +} binder_thread_state_t; +#endif + typedef struct file_info { union { char pathname[MAX_CACHED_PATH_SIZE]; diff --git a/user/binder/parcel_parser.go b/user/binder/parcel_parser.go new file mode 100644 index 0000000..a0b81c9 --- /dev/null +++ b/user/binder/parcel_parser.go @@ -0,0 +1,79 @@ +package binder + +import ( + "encoding/binary" + "errors" + "fmt" + "unicode/utf16" +) + +// ParseInterfaceToken 从 Parcel 原始数据中解析 Interface Token(descriptor) +// 适配 Android 9 (sdk_int 28) / Android 10 (sdk_int 29) / Android 11+ (sdk_int >= 30) +// +// Android 9: +// +0x00 strict_mode_policy u32 +// +0x04 descriptor_length u32 (UTF-16 字符数) +// +0x08 descriptor UTF-16LE +// +// Android 10: +// +0x00 strict_mode_policy u32 +// +0x04 work_source_uid u32 +// +0x08 descriptor_length u32 +// +0x0C descriptor UTF-16LE +// +// Android 11+: +// +0x00 strict_mode_policy u32 +// +0x04 work_source_uid u32 +// +0x08 version_header u32 +// +0x0C descriptor_length u32 +// +0x10 descriptor UTF-16LE +func ParseInterfaceToken(data []byte, sdkInt uint32) (descriptor string, headerSize int, err error) { + if len(data) < 8 { + return "", 0, errors.New("parcel data too short") + } + + var offset int + switch { + case sdkInt <= 28: + // Android 9: strict_mode_policy(4) + descriptor_length(4) + offset = 4 + case sdkInt <= 29: + // Android 10: strict_mode_policy(4) + work_source_uid(4) + descriptor_length(4) + offset = 8 + default: + // Android 11+: strict_mode_policy(4) + work_source_uid(4) + version_header(4) + descriptor_length(4) + offset = 12 + } + + if len(data) < offset+4 { + return "", 0, errors.New("parcel data too short for descriptor length") + } + + descLen := binary.LittleEndian.Uint32(data[offset : offset+4]) + offset += 4 + + // descriptor 是 UTF-16LE 编码,每个字符 2 字节,末尾有一个 null terminator (2 字节) + byteLen := int(descLen) * 2 + if byteLen < 0 || byteLen > 4096 { + return "", 0, fmt.Errorf("descriptor length %d out of range", descLen) + } + if len(data) < offset+byteLen { + return "", 0, errors.New("parcel data too short for descriptor content") + } + + // 解码 UTF-16LE + utf16Codes := make([]uint16, descLen) + for i := 0; i < int(descLen); i++ { + utf16Codes[i] = binary.LittleEndian.Uint16(data[offset+i*2 : offset+i*2+2]) + } + descriptor = string(utf16.Decode(utf16Codes)) + + // 计算头部总大小(含 null terminator 和 4 字节对齐) + headerSize = offset + byteLen + 2 // +2 for null terminator + // 4 字节对齐 + if headerSize%4 != 0 { + headerSize += 4 - (headerSize % 4) + } + + return descriptor, headerSize, nil +} diff --git a/user/common/const.go b/user/common/const.go index 0805883..57178c1 100644 --- a/user/common/const.go +++ b/user/common/const.go @@ -243,4 +243,5 @@ const ( BRK_EVENT UPROBE_EVENT SYSCALL_EVENT + BINDER_EVENT ) diff --git a/user/config/config_binder.go b/user/config/config_binder.go new file mode 100644 index 0000000..f943909 --- /dev/null +++ b/user/config/config_binder.go @@ -0,0 +1,14 @@ +package config + +type BinderConfig struct { + Enable bool + LibPath string + RealFilePath string + Filter string + NoReply bool + DumpHex bool +} + +func (this *BinderConfig) IsEnable() bool { + return this.Enable +} diff --git a/user/config/config_file.go b/user/config/config_file.go index c1771d0..baeff05 100644 --- a/user/config/config_file.go +++ b/user/config/config_file.go @@ -219,3 +219,10 @@ type SyscallFileConfig struct { FileConfig Points []SyscallPointConfig `json:"points"` } + +type BinderFileConfig struct { + FileConfig + Library string `json:"library"` + Filter string `json:"filter"` + NoReply bool `json:"no_reply"` +} diff --git a/user/config/config_global.go b/user/config/config_global.go index 0a439db..870480b 100644 --- a/user/config/config_global.go +++ b/user/config/config_global.go @@ -64,9 +64,13 @@ type GlobalConfig struct { NoCheck bool Btf bool ExternalBTF string - SysCall string - NoSysCall string - ConfigFiles []string + SysCall string + NoSysCall string + ConfigFiles []string + BinderTrace bool + BinderLib string + BinderFilter string + BinderNoReply bool } func NewGlobalConfig() *GlobalConfig { diff --git a/user/config/config_module.go b/user/config/config_module.go index b98d2d1..e609054 100644 --- a/user/config/config_module.go +++ b/user/config/config_module.go @@ -785,6 +785,7 @@ type ModuleConfig struct { BrkLen uint64 BrkType uint32 BrkKernel bool + SdkInt uint32 Color bool DumpHandle *os.File FmtJson bool @@ -796,6 +797,7 @@ type ModuleConfig struct { Name string StackUprobeConf *StackUprobeConfig SysCallConf *SyscallConfig + BinderConf *BinderConfig } func NewModuleConfig() *ModuleConfig { @@ -837,6 +839,7 @@ func (this *ModuleConfig) DefaultThreadBlacklist() []string { func (this *ModuleConfig) InitCommonConfig(gconfig *GlobalConfig) { + this.SdkInt = gconfig.SdkInt this.MaxOp = gconfig.MaxOp this.Buffer = gconfig.Buffer this.UnwindStack = gconfig.UnwindStack @@ -871,6 +874,9 @@ func (this *ModuleConfig) InitCommonConfig(gconfig *GlobalConfig) { this.SysCallConf.SetLogger(this.logger) this.SysCallConf.SetDumpHex(this.DumpHex) this.SysCallConf.SetColor(this.Color) + + this.BinderConf = &BinderConfig{} + this.BinderConf.DumpHex = this.DumpHex } func (this *ModuleConfig) LoadConfig(gconfig *GlobalConfig) { @@ -915,6 +921,23 @@ func (this *ModuleConfig) LoadConfig(gconfig *GlobalConfig) { if err != nil { panic(err) } + case "binder": + config := &BinderFileConfig{} + err = json.Unmarshal(content, config) + if err != nil { + panic(err) + } + this.BinderConf.Enable = true + if config.Library != "" { + err = gconfig.Parse_Libinfo(config.Library, &StackUprobeConfig{}) + if err == nil { + this.BinderConf.LibPath = config.Library + } + } + if config.Filter != "" { + this.BinderConf.Filter = config.Filter + } + this.BinderConf.NoReply = config.NoReply default: panic(fmt.Sprintf("unsupported config type %s", base_config.Type)) } diff --git a/user/event/event_binder.go b/user/event/event_binder.go new file mode 100644 index 0000000..6b21231 --- /dev/null +++ b/user/event/event_binder.go @@ -0,0 +1,231 @@ +package event + +import ( + "encoding/binary" + "encoding/json" + "fmt" + "stackplz/user/binder" + "stackplz/user/common" + "stackplz/user/util" + "strings" +) + +type BinderEvent struct { + ContextEvent + UUID string + Handle uint32 + Code uint32 + Flags uint32 + DataSize uint32 // Parcel 完整大小 + ParcelData []byte // 实际读取的数据(可能截断) + Truncated bool // DataSize > len(ParcelData) + // REPLY 专用 + RetValue uint32 + ReplySize uint32 + ReplyData []byte + // 解析结果 + Descriptor string // 从 Parcel 解析的 interface descriptor + IsOneway bool // flags & 0x1 +} + +func (this *BinderEvent) DumpRecord() bool { + return this.mconf.DumpRecord(common.BINDER_EVENT, &this.rec) +} + +func (this *BinderEvent) ParseEvent() (IEventStruct, error) { + // 先解析基础 context 信息 + data_e, err := this.ContextEvent.ParseEvent() + if err != nil { + panic(fmt.Sprintf("BinderEvent ContextEvent.ParseEvent() err:%v", err)) + } + if data_e == nil { + // PERF_RECORD_SAMPLE 且 EventId 被 ContextEvent 解析完了 + if err := this.ParseContext(); err != nil { + panic(fmt.Sprintf("BinderEvent.ParseContext() err:%v", err)) + } + return this, nil + } + return data_e, nil +} + +func (this *BinderEvent) peekArgIndex() (uint8, bool) { + if this.buf.Len() < 1 { + return 0, false + } + return this.buf.Bytes()[0], true +} + +func (this *BinderEvent) readBytesArg() []byte { + var index uint8 + if err := binary.Read(this.buf, binary.LittleEndian, &index); err != nil { + panic(err) + } + var size int32 + if err := binary.Read(this.buf, binary.LittleEndian, &size); err != nil { + panic(err) + } + if size <= 0 { + return nil + } + data := make([]byte, size) + if err := binary.Read(this.buf, binary.LittleEndian, &data); err != nil { + panic(err) + } + return data +} + +func (this *BinderEvent) ParseContext() (err error) { + if this.EventId != BINDER_TRANSACT && this.EventId != BINDER_REPLY { + return fmt.Errorf("BinderEvent.ParseContext() unexpected EventId:%d", this.EventId) + } + + // 读取公共参数: handle, code, flags, data_size + this.ReadArg(&this.Handle) + this.ReadArg(&this.Code) + this.ReadArg(&this.Flags) + this.ReadArg(&this.DataSize) + this.IsOneway = (this.Flags & 0x1) != 0 + + // 读取 Parcel 原始数据(可能因 MTA 地址失败而缺失,需检查 arg index) + if idx, ok := this.peekArgIndex(); ok && idx == 4 { + this.ParcelData = this.readBytesArg() + } + this.Truncated = this.DataSize > uint32(len(this.ParcelData)) + + // 从 Parcel 数据解析 Interface Token + if len(this.ParcelData) > 0 { + sdkInt := this.mconf.SdkInt + if sdkInt == 0 { + sdkInt = 30 // 默认 Android 11+ + } + desc, _, parseErr := binder.ParseInterfaceToken(this.ParcelData, sdkInt) + if parseErr == nil { + this.Descriptor = desc + } + } + + if this.EventId == BINDER_REPLY { + // REPLY 事件额外字段: ret_value, reply_size, reply_data + this.ReadArg(&this.RetValue) + this.ReadArg(&this.ReplySize) + if idx, ok := this.peekArgIndex(); ok && idx == 7 { + this.ReplyData = this.readBytesArg() + } + } + + // 解析 padding + this.ParsePadding() + + // 解析堆栈(如果启用) + err = this.ParseContextStack() + if err != nil { + panic(fmt.Sprintf("BinderEvent ParseContextStack err:%v", err)) + } + + return nil +} + +func (this *BinderEvent) Clone() IEventStruct { + event := new(BinderEvent) + return event +} + +func (this *BinderEvent) GetUUID() string { + s := fmt.Sprintf("%d|%d|%s", this.Pid, this.Tid, util.B2STrim(this.Comm[:])) + if this.mconf.ShowTime { + s = fmt.Sprintf("%d|%s", this.Ts, s) + } + if this.mconf.ShowUid { + s = fmt.Sprintf("%d|%s", this.Uid, s) + } + return s +} + +func (this *BinderEvent) MarshalJSON() ([]byte, error) { + eventType := "TRANSACT" + if this.EventId == BINDER_REPLY { + eventType = "REPLY" + } + + result := map[string]interface{}{ + "type": eventType, + "ts": this.Ts, + "pid": this.Pid, + "tid": this.Tid, + "uid": this.Uid, + "comm": util.B2STrim(this.Comm[:]), + "handle": this.Handle, + "code": this.Code, + "flags": this.Flags, + "oneway": this.IsOneway, + "descriptor": this.Descriptor, + "data_size": this.DataSize, + "truncated": this.Truncated, + } + + if this.mconf.BinderConf != nil && this.mconf.BinderConf.DumpHex && len(this.ParcelData) > 0 { + result["parcel_hex"] = fmt.Sprintf("%x", this.ParcelData) + } + + if this.EventId == BINDER_REPLY { + result["ret_value"] = this.RetValue + result["reply_size"] = this.ReplySize + if this.mconf.BinderConf != nil && this.mconf.BinderConf.DumpHex && len(this.ReplyData) > 0 { + result["reply_hex"] = fmt.Sprintf("%x", this.ReplyData) + } + } + + if this.Stackinfo != "" { + result["stack"] = this.Stackinfo + } + + return json.Marshal(result) +} + +func (this *BinderEvent) String() string { + // descriptor 过滤 + if this.mconf.BinderConf != nil && this.mconf.BinderConf.Filter != "" { + if !strings.Contains(this.Descriptor, this.mconf.BinderConf.Filter) { + return "" + } + } + + if this.mconf.FmtJson { + data, err := json.Marshal(this) + if err != nil { + panic(err) + } + return string(data) + } + + var s string + if this.EventId == BINDER_TRANSACT { + s = fmt.Sprintf("[%s] TRANSACT handle=%d code=%d flags=0x%x descriptor=%s size=%d", + this.GetUUID(), this.Handle, this.Code, this.Flags, this.Descriptor, this.DataSize) + } else { + s = fmt.Sprintf("[%s] REPLY handle=%d code=%d flags=0x%x descriptor=%s ret=%d size=%d", + this.GetUUID(), this.Handle, this.Code, this.Flags, this.Descriptor, this.RetValue, this.ReplySize) + } + + if this.Truncated { + s += " [truncated]" + } + + // hex dump + if this.mconf.BinderConf != nil && this.mconf.BinderConf.DumpHex { + if len(this.ParcelData) > 0 { + s += "\n Data Parcel:\n" + util.HexDumpPure(this.ParcelData) + } + if this.EventId == BINDER_REPLY && len(this.ReplyData) > 0 { + s += "\n Reply Parcel:\n" + util.HexDumpPure(this.ReplyData) + } + } + + // 堆栈信息 + stackStr := this.GetStackTrace("") + if stackStr != "" { + s += stackStr + } + + return s +} diff --git a/user/event/event_context.go b/user/event/event_context.go index 0ef4119..2fe0db3 100644 --- a/user/event/event_context.go +++ b/user/event/event_context.go @@ -144,6 +144,8 @@ func (this *ContextEvent) ParseEvent() (IEventStruct, error) { return nil, nil case UPROBE_ENTER: return nil, nil + case BINDER_TRANSACT, BINDER_REPLY: + return nil, nil default: this.logger.Printf("ContextEvent.ParseEvent() unsupported EventId:%d\n", EventId) this.logger.Printf("ContextEvent.ParseEvent() PERF_RECORD_SAMPLE RawSample:\n" + util.HexDump(this.rec.RawSample, util.COLORRED)) diff --git a/user/event/ievent.go b/user/event/ievent.go index 365df20..bb38825 100644 --- a/user/event/ievent.go +++ b/user/event/ievent.go @@ -23,6 +23,8 @@ const ( SYSCALL_EXIT UPROBE_ENTER HW_BREAKPOINT + BINDER_TRANSACT + BINDER_REPLY ) type ArgFormatter interface { diff --git a/user/module/binder.go b/user/module/binder.go new file mode 100644 index 0000000..167ac89 --- /dev/null +++ b/user/module/binder.go @@ -0,0 +1,334 @@ +package module + +import ( + "bytes" + "context" + "errors" + "fmt" + "log" + "math" + "path/filepath" + "stackplz/assets" + "stackplz/user/config" + "stackplz/user/event" + "stackplz/user/util" + "strings" + "unsafe" + + "github.com/cilium/ebpf" + "github.com/cilium/ebpf/btf" + manager "github.com/ehids/ebpfmanager" + "golang.org/x/sys/unix" +) + +type MBinder struct { + Module + bpfManager *manager.Manager + bpfManagerOptions manager.Options + eventFuncMaps map[*ebpf.Map]event.IEventStruct + eventMaps []*ebpf.Map + + hookBpfFile string +} + +func (this *MBinder) Init(ctx context.Context, logger *log.Logger, conf config.IConfig) error { + this.Module.Init(ctx, logger, conf) + this.Module.SetChild(this) + this.eventMaps = make([]*ebpf.Map, 0, 2) + this.eventFuncMaps = make(map[*ebpf.Map]event.IEventStruct) + this.hookBpfFile = "binder.o" + return nil +} + +func (this *MBinder) GetConf() config.IConfig { + return this.mconf +} + +func (this *MBinder) setupManager() error { + maps := []*manager.Map{} + probes := []*manager.Probe{} + + events_map := &manager.Map{ + Name: "events", + } + maps = append(maps, events_map) + + fork_probe := &manager.Probe{ + Section: "raw_tracepoint/sched_process_fork", + EbpfFuncName: "tracepoint__sched__sched_process_fork_binder", + } + probes = append(probes, fork_probe) + + // transact 符号 + binderConf := this.mconf.BinderConf + transactSymbol := "_ZN7android14IPCThreadState8transactEijRKNS_6ParcelEPS1_j" + + // uprobe: transact 入口 + uprobe := &manager.Probe{ + Section: "uprobe/binder_transact", + EbpfFuncName: "probe_binder_transact", + AttachToFuncName: transactSymbol, + BinaryPath: binderConf.LibPath, + RealFilePath: binderConf.RealFilePath, + } + probes = append(probes, uprobe) + + // uretprobe: transact 返回 + if !binderConf.NoReply { + uretprobe := &manager.Probe{ + Section: "uretprobe/binder_transact", + EbpfFuncName: "probe_binder_transact_ret", + AttachToFuncName: transactSymbol, + BinaryPath: binderConf.LibPath, + RealFilePath: binderConf.RealFilePath, + } + probes = append(probes, uretprobe) + } + + this.bpfManager = &manager.Manager{ + Probes: probes, + Maps: maps, + } + return nil +} + +func (this *MBinder) setupManagerOptions() { + if this.mconf.ExternalBTF != "" { + byteBuf, err := assets.Asset("user/assets/" + this.mconf.ExternalBTF) + if err != nil { + this.logger.Fatalf("[setupManagerOptions] failed, err:%v", err) + return + } + spec, err := btf.LoadSpecFromReader(bytes.NewReader(byteBuf)) + if err != nil { + this.logger.Fatalf("[setupManagerOptions] LoadSpecFromReader failed, err:%v", err) + return + } + + this.bpfManagerOptions = manager.Options{ + DefaultKProbeMaxActive: 512, + VerifierOptions: ebpf.CollectionOptions{ + Programs: ebpf.ProgramOptions{ + LogSize: 2097152, + KernelTypes: spec, + }, + }, + RLimit: &unix.Rlimit{ + Cur: math.MaxUint64, + Max: math.MaxUint64, + }, + } + } else { + this.bpfManagerOptions = manager.Options{ + DefaultKProbeMaxActive: 512, + VerifierOptions: ebpf.CollectionOptions{ + Programs: ebpf.ProgramOptions{ + LogSize: 2097152, + }, + }, + RLimit: &unix.Rlimit{ + Cur: math.MaxUint64, + Max: math.MaxUint64, + }, + } + } +} + +func (this *MBinder) Start() error { + return this.start() +} + +func (this *MBinder) Clone() IModule { + mod := new(MBinder) + mod.name = this.name + mod.mType = this.mType + return mod +} + +func (this *MBinder) start() error { + err := this.setupManager() + if err != nil { + return err + } + this.setupManagerOptions() + + var bpfFileName = filepath.Join("user/assets", this.hookBpfFile) + byteBuf, err := assets.Asset(bpfFileName) + if err != nil { + return fmt.Errorf("%s\tcouldn't find asset %v .", this.Name(), err) + } + + if err = this.bpfManager.InitWithOptions(bytes.NewReader(byteBuf), this.bpfManagerOptions); err != nil { + return fmt.Errorf("couldn't init manager %v", err) + } + + if err = this.bpfManager.Start(); err != nil { + return fmt.Errorf("couldn't start bootstrap manager %v .", err) + } + + err = this.updateFilter() + if err != nil { + return err + } + + err = this.initDecodeFun() + if err != nil { + return err + } + + return nil +} + +func (this *MBinder) update_map(map_name string, filter_key uint32, filter_value interface{}) { + bpf_map, err := this.FindMap(map_name) + if err != nil { + panic(fmt.Sprintf("find [%s] failed, err:%v", map_name, err)) + } + err = bpf_map.Update(unsafe.Pointer(&filter_key), filter_value, ebpf.UpdateAny) + if err != nil { + panic(fmt.Sprintf("update [%s] failed, err:%v", map_name, err)) + } +} + +func (this *MBinder) update_base_config() { + var filter_key uint32 = 0 + map_name := "base_config" + filter_value := this.mconf.GetConfigMap() + this.update_map(map_name, filter_key, unsafe.Pointer(&filter_value)) +} + +func (this *MBinder) update_common_list(items []uint32, offset uint32) { + map_name := "common_list" + bpf_map, err := this.FindMap(map_name) + if err != nil { + panic(fmt.Sprintf("find [%s] failed, err:%v", map_name, err)) + } + for _, v := range items { + v += offset + err := bpf_map.Update(unsafe.Pointer(&v), unsafe.Pointer(&v), ebpf.UpdateAny) + if err != nil { + panic(fmt.Sprintf("update [%s] failed, err:%v", map_name, err)) + } + } +} + +func (this *MBinder) list2string(items []uint32) string { + var results []string + for _, v := range items { + results = append(results, fmt.Sprintf("%d", v)) + } + return strings.Join(results, ",") +} + +func (this *MBinder) update_common_filter() { + this.update_common_list(this.mconf.UidWhitelist, util.UID_WHITELIST_START) + this.update_common_list(this.mconf.UidBlacklist, util.UID_BLACKLIST_START) + this.update_common_list(this.mconf.PidWhitelist, util.PID_WHITELIST_START) + this.update_common_list(this.mconf.PidBlacklist, util.PID_BLACKLIST_START) + this.update_common_list(this.mconf.TidWhitelist, util.TID_WHITELIST_START) + this.update_common_list(this.mconf.TidBlacklist, util.TID_BLACKLIST_START) + this.logger.Printf("uid => whitelist:[%s];blacklist:[%s]", this.list2string(this.mconf.UidWhitelist), this.list2string(this.mconf.UidBlacklist)) + this.logger.Printf("pid => whitelist:[%s];blacklist:[%s]", this.list2string(this.mconf.PidWhitelist), this.list2string(this.mconf.PidBlacklist)) + var filter_key uint32 = 0 + map_name := "common_filter" + filter_value := this.mconf.GetCommonFilter() + this.update_map(map_name, filter_key, unsafe.Pointer(&filter_value)) +} + +func (this *MBinder) update_child_parent() { + map_name := "child_parent_map" + for _, v := range this.mconf.PidWhitelist { + this.update_map(map_name, v, unsafe.Pointer(&v)) + } +} + +func (this *MBinder) update_thread_filter() { + map_name := "thread_filter" + bpf_map, err := this.FindMap(map_name) + if err != nil { + panic(fmt.Sprintf("find [%s] failed, err:%v", map_name, err)) + } + + for _, v := range this.mconf.DefaultThreadBlacklist() { + if len(v) > 16 { + panic(fmt.Sprintf("[%s] thread name max len is 16", v)) + } + filter_value := THREAD_NAME_BLACKLIST + filter_key := config.ThreadFilter{} + copy(filter_key.ThreadName[:], v) + err = bpf_map.Update(unsafe.Pointer(&filter_key), unsafe.Pointer(&filter_value), ebpf.UpdateAny) + if err != nil { + panic(fmt.Sprintf("update [%s] failed, err:%v", map_name, err)) + } + } + for _, v := range this.mconf.TNameBlacklist { + if len(v) > 16 { + panic(fmt.Sprintf("[%s] thread name max len is 16", v)) + } + filter_value := THREAD_NAME_BLACKLIST + filter_key := config.ThreadFilter{} + copy(filter_key.ThreadName[:], v) + err = bpf_map.Update(unsafe.Pointer(&filter_key), unsafe.Pointer(&filter_value), ebpf.UpdateAny) + if err != nil { + panic(fmt.Sprintf("update [%s] failed, err:%v", map_name, err)) + } + } + for _, v := range this.mconf.TNameWhitelist { + if len(v) > 16 { + panic(fmt.Sprintf("[%s] thread name max len is 16", v)) + } + filter_value := THREAD_NAME_WHITELIST + filter_key := config.ThreadFilter{} + copy(filter_key.ThreadName[:], v) + err = bpf_map.Update(unsafe.Pointer(&filter_key), unsafe.Pointer(&filter_value), ebpf.UpdateAny) + if err != nil { + panic(fmt.Sprintf("update [%s] failed, err:%v", map_name, err)) + } + } +} + +func (this *MBinder) updateFilter() (err error) { + this.update_base_config() + this.update_common_filter() + this.update_child_parent() + this.update_thread_filter() + return nil +} + +func (this *MBinder) initDecodeFun() error { + EventsMap, err := this.FindMap("events") + if err != nil { + return err + } + this.eventMaps = append(this.eventMaps, EventsMap) + binderEvent := &event.BinderEvent{} + this.eventFuncMaps[EventsMap] = binderEvent + return nil +} + +func (this *MBinder) FindMap(map_name string) (*ebpf.Map, error) { + em, found, err := this.bpfManager.GetMap(map_name) + if err != nil { + return em, err + } + if !found { + return em, errors.New(fmt.Sprintf("cannot find map:%s", map_name)) + } + return em, err +} + +func (this *MBinder) Events() []*ebpf.Map { + return this.eventMaps +} + +func (this *MBinder) DecodeFun(em *ebpf.Map) (event.IEventStruct, bool) { + fun, found := this.eventFuncMaps[em] + return fun, found +} + +func init() { + mod := &MBinder{} + mod.name = MODULE_NAME_BINDER + mod.mType = PROBE_TYPE_UPROBE + Register(mod) +} diff --git a/user/module/const.go b/user/module/const.go index 6d20409..e114b58 100644 --- a/user/module/const.go +++ b/user/module/const.go @@ -13,6 +13,7 @@ const ( MODULE_NAME_BRK = "BrkMod" MODULE_NAME_STACK = "StackMod" MODULE_NAME_SYSCALL = "SyscallMod" + MODULE_NAME_BINDER = "BinderMod" ) const (