充分利用你的下一次技术会议的 7 个技巧
会议是科技行业的福利之一。以下是如何充分利用你所参加的会议的方法。我在 2023 年 2 月参加了两个针对开源软件的技术会议。我是比利时根特举办的 Config Management Camp 的演讲者，也是比利时布鲁塞尔 FOSDEM 的与会者。本文旨在着重讲述我在会议上的经验，并为你提供一些如何充分利用这种机会的建议。保持目的性不同的人参加会议有不同的原因。有些人是某个主题、知识或兴趣领域的演讲者。另外一些人是希望从这些讲座中获得知识并与其他志同道合的人建立联系的与会者。也有一些代表他们公司的与会者。你很可能属于其中的一类。明确你希望从会议中获得什么，这是进行一次成功会议访问的第一步。如果你是一个演讲者，这意味着你要熟练掌握你所演讲的东西。如果你是一个与会者，你应该对你想从会议中获得什么有一个认识。了解场地和会议日程FOSDEM 是一个非常大的会议，在两天的时间里至少有六千人参加。对于一个参加这样一个大型的会议的与会者来说，由于许多演讲会在同一时间举行，无法参加所有你感兴趣的会议并不奇怪。通常，这样的大型会议在大学或会议中心等宽敞的场地举行。由于面积非常大，会议会根据特定的主题分散在整个会场。这些会议会有一个固定的日程，所以有时你可能必须迅速地从场地的一边跑到另一边。场馆地图可以在场馆网站上轻松获取。在第一天早点到达会场并熟悉它是非常有用的。这有助于节省你在一个演讲结束时匆忙赶往另一个演讲的时间。记些笔记在现场集中注意力和专注于演讲当然很好。然而，你的头脑只能记住这么多。当然，有些人会试着尽可能地利用他们的手机拍摄正在演示的幻灯片（顺着演讲者的演讲节奏）。如果你想在社交媒体上快速更新你正在参加的会议信息，这是很好的。但是它不是很有效的笔记。通常幻灯片上的有效信息是最少的。但是如果演讲者在台上深入地解释了一些东西，你可能会错过这些解释。我建议你随身携带一个记事本和一支笔。你甚至可以带上笔记本电脑做笔记。这个做法的目的是在于通过在演讲中对有趣的花絮做一个简短的笔记，以便你可以在以后重温它们。而且它让你总是能在演讲快结束时回想起要向演讲者提问的问题。建立关系和促成协作会议可能是与志同道合的人彼此交往的最佳场所。他们和你一样对同样的话题感兴趣。最好利用这段时间来了解在感兴趣的话题上正在发生什么，看看人们如何解决令人感兴趣的问题，他们如何处理事情，并掌握整个行业的脉搏。你在会议上的时间有限，所以一定要把你介绍给那些从事与你有关的工作的人。这是一个收集信息的好机会，以便以后与他们沟通。你可以通过电子邮件、Mastodon、领英等方式交换个人信息。腾出时间看展位和礼品大多数技术会议都有来自各种公司或上游项目的展位，希望推销他们的产品和服务。为了吸引更多参展的人，展位上经常会有各种各样的免费的礼品（在大多数情况下）。这些礼品通常是贴纸、凉水瓶、有趣的小玩意、毛绒玩具、笔之类。一定要把它们收集起来，这样你就有东西送给你的同事和朋友了。参观展位不应该只是为了礼品。你应该利用这个机会与来自不同公司的人交谈（即使他们是竞争对手），以了解他们能提供什么。谁没准你就会得到以后的项目知识！放松参加会议不应该只是为了工作。这也让你你从平时忙碌的日程中休息一下，放松一下。很有可能你正在前往一个你还没有访问过的不同的国家或城市。会议、会谈和技术讨论都很重要。但这只是整个体验的一部分。另一半的经验是旅行，它可以使人了解另一个国家、它的文化、它的人民、食物、语言和不同的生活方式。退一步讲，享受所有这些经历，留下终生的回忆。我建议你在住宿的地方找一些著名的地标兼做。你也应该尝试当地的美食，或者你可以和当地人聊天。最后，你会发现你认为从来没有存在过的自己的另一面。写下你的经历一旦你从会议回来，不要只是忘记它然后回到你的日常生活，就好像什么都没发生一样。利用这个机会写下你的经历，并分享你认为最好的演讲以及原因。会议和旅行的主要收获是什么？你应该记录你学到的东西。你应该主动联系你在会议上遇到的人。你也可以在社交媒体上关注你可能错过的事情。总结会议是科技行业的福利之一。我建议每个人在职业生涯中的某个时候都去一次。我希望这篇文章能帮助你了解如何在参加技术会议时最大限度地利用会议。（题图：MJ/conference illustration talk meetup in high resolution, very detailed, 8k）via: https://opensource.com/article/23/4/tips-tech-conference作者：Gaurav Kamathe 选题：lkxed 译者：Taivas Jumala 校对：wxy本文由 LCTT 原创编译，Linux中国 荣誉推出
Rustlings 是由 Rust 团队维护的开源项目，旨在帮助你通过调试代码的方式来学习 Rust。在我上一篇 关于 Rustup 的文章 中，我向你们展示了如何安装 Rust 工具链。但是，如果不能上手操作一下 Rust 的话下载工具链又有什么用？学习任何语言都包括阅读现有的代码和写很多的示例程序，这是精通一门语言的好方法。然而，我们还可以走第三条路：调试代码。通过调试来学习牵扯到尝试去编译一个已经写好的（满是漏洞的）示例程序，理解编译器生成的错误信息，修复示例代码，然后再重新编译。重复这个过程直到代码能够成功被编译并运行。Rustlings 是一个由 Rust 团队维护的开源项目，旨在帮助你通过调试代码来学习 Rust。它也会一路为你提供提示。如果你是一名 Rust 初学者，并且刚开始阅读或已经读完了 Rust 书籍，那么 Rustlings 就是理想的下一步。Rustllings 帮助你将运用书中所学，并转向开发更大的项目。安装 Rustlings我使用（并推荐）Fedora 电脑来体验 Rustlings，但是任何 Linux 发行版都可以。要安装 Rustlings，你必须下载并运行它的安装脚本。通常建议你以不具备任何特别权限的普通用户（非 root 用户）来运行脚本。记住，你需要 Rust 工具链来使用 Rustlings。如果你还没有这些工具链，请参考我 关于 Rustup 的文章。当你准备好时，下载这个安装脚本：$ curl -L https://raw.githubusercontent.com/rust-lang/rustlings/main/install.sh > rustlings_install.sh $ file rustlings_install.sh rustlings_install.sh: Bourne-Again shell script, ASCII text executable 阅读脚本以了解它会做什么：$ less rustlings_install.sh 然后运行安装：$ bash rustlings_install.sh [...] Installing /home/tux/.cargo/bin/rustlings Installed package `rustlings v4.8.0 (/home/tux/rustlings)` (executable `rustlings`) All done! 运行 rustlings 以开始。Rustlings 练习你现在可以使用命令 rustlings。与标志 --help 一起执行来查看可选的选项。$ rustlings --help 这个安装脚本也克隆了 Rustlings 的 Git 仓库，并安装了运行示例程序所需的依赖。你可以在 ruslings 下的 exercises 目录查阅这些示例程序。$ cd rustlings $ pwd /home/tux/rustlings $ ls AUTHORS.md Cargo.toml CONTRIBUTING.md info.toml install.sh README.md target Cargo.lock CHANGELOG.md exercises install.ps1 LICENSE src tests $ ls -m exercises/ advanced_errors, clippy, collections, conversions, enums, error_handling, functions, generics, if, intro, macros, mod.rs, modules, move_semantics, option, primitive_types, quiz1.rs, quiz2.rs, quiz3.rs, quiz4.rs, README.md, standard_library_types, strings, structs, tests, threads, traits, variables 从命令行列出所有练习命令 ruslings 提供给你一个 list 命令用以展示每个示例程序，它的完整路径，以及状态 （默认为 “待定”）。$ rustlings list Name Path Status intro1 exercises/intro/intro1.rs Pending intro2 exercises/intro/intro2.rs Pending variables1 exercises/variables/variables1.rs Pending variables2 exercises/variables/variables2.rs Pending variables3 exercises/variables/variables3.rs Pending [...] 在显示结尾处，你会有一个进度报告用来追踪进度。Progress: You completed 0 / 84 exercises (0.00 %). 查看示例程序命令 rustlings list 向你展示了现有的程序，所以你可以在任何时候查看这些程序的代码，你只需要将完整路径复制到你的终端作为命令 cat 或者 less 的参数：$ cat exercises/intro/intro1.rs 验证你的程序现在你可以开始调试程序了。你可以使用命令 verify 来做这件事。注意 Rustlings 选择了列表里的第一个程序（intro1.rs）并尝试去编译它，最后编译成功：$ rustlings verify Progress: [-----------------------------------] 0/84 ✅ Successfully ran exercises/intro/intro1.rs! You can keep working on this exercise, or jump into the next one by removing the `I AM NOT DONE` comment: 6 | // Execute the command `rustlings hint intro1` for a hint. 7 | 8 | // I AM NOT DONE 9 | 正如你从结果中所见，尽管示例代码成功编译了，你依然需要做一些工作。每个示例程序的源文件中都带有以下注释：$ grep "NOT DONE" exercises/intro/intro1.rs // I AM NOT DONE 虽然第一个程序的编译没有问题，除非你去掉注释 I AM NOT DONE，Rustlings 不会移到下一个程序。来到下一个练习一旦你从 intro1.rs 中去掉这些注释，你就可以通过再一次运行命令 rustlings verify 来到下一个练习。这一次，你会发现 Rustlings 尝试去编译这个系列中的下一个程序（intro2.rs），但是遇到了一个错误。你应该调试并修复这个问题，并前进。这是你理解为什么 Rust 说程序有漏洞的至关重要的一步。$ rustlings verify Progress: [>------------------------] 1/84 ⚠️ Compiling of exercises/intro/intro2.rs failed! Please try again. Here's the output: error: 1 positional argument in format string, but no arguments were given --> exercises/intro/intro2.rs:8:21 | 8 | println!("Hello {}!"); | ^^ error: aborting due to previous error 来点提示Rustlings 有一个非常好用的 hint 参数，这个参数会告诉你示例程序中哪里出错了，以及如何去修复它。你可以认为这是在编译错误信息基础之上，一个额外的帮助选项。$ rustlings hint intro2 Add an argument after the format string. 基于以上提示，修复这个程序就很简单了。你只需要在语句 println 中加一个额外的参数。这个 diff 对比应该能帮你理解发生的变化：< println!("Hello {}!", "world"); --- > println!("Hello {}!"); 一旦你做出了修改，并从源代码中去掉了注释 NOT DONE，你可以再一次运行 rustlings verify 来编译并运行代码。$ rustlings verify Progress: [>-------------------------------------] 1/84 ✅ Successfully ran exercises/intro/intro2.rs! 追踪进度你无法在一天之内做完所有的练习，忘记练到哪也很常见。你可以执行命令 list 来查看你的练习状态。$ rustlings list Name Path Status intro1 exercises/intro/intro1.rs Done intro2 exercises/intro/intro2.rs Done variables1 exercises/variables/variables1.rs Pending variables2 exercises/variables/variables2.rs Pending variables3 exercises/variables/variables3.rs Pending [...] 运行特定的练习如果你不想从头开始并且想要跳过一些练习，Rustlings 允许你使用命令 rustlings run 来专注特定的练习。如此可以运行指定的程序而不需要验证之前的课程。例如：$ rustlings run intro2 Hello world! ✅ Successfully ran exercises/intro/intro2.rs $ rustlings run variables1 敲入练习名字可能会变得乏味，但 Rustlings 为你准备了便利的命令 next 用来移向系列中的下一个练习。$ rustlings run next 替代命令 watch如果你不想在每次修改后还要敲一次 verify，你可以在终端窗口中运行命令 watch，然后再继续修改源代码以解决问题。命令 watch 会检测到这些修改，然后重新编译以查看这些问题是否被解决。$ rustlings watch 通过调试学习Rust 编译器以提供非常有意义的错误信息而被熟知，这些错误信息会帮助你理解在你代码中的问题。这通常意味着更快的调试。Rustlings 是练习 Rust，学会阅读错误信息，并理解 Rust 语言的优秀途径。来看看 GitHub 上 Rustlings 5.0.0 的最新功能吧。下载 Rust 速查表via: https://opensource.com/article/22/7/learn-rust-rustlings作者：Gaurav Kamathe 选题：lkxed 译者：yzuowei 校对：wxy本文由 LCTT 原创编译，Linux中国 荣誉推出
这篇文章能让你了解如何使用 Checksec ，来识别一个可执行文件的安全属性，了解安全属性的含义，并知道如何使用它们。编译源代码会生成一个二进制文件（LCTT 译注：即 .o 文件）。在编译期间，你可以向 gcc 编译器提供 标志 flags ，以启用或禁用二进制文件的某些属性，这些属性与安全性相关。Checksec 是一个漂亮的小工具，同时它也是一个 shell 脚本。Checksec 可以识别编译时构建到二进制文件中的安全属性。编译器可能会默认启用一些安全属性，你也可以提供特定的标志，来启用其他的安全属性。本文将介绍如何使用 Checksec ，来识别二进制文件的安全属性，包括：Checksec 在查找有关安全属性的信息时，使用了什么底层的命令在将源代码编译成二进制文件时，如何使用 GNU 编译器套件 GNU Compiler Collection （即 GCC）来启用安全属性。安装 checksec要在 Fedora 和其他基于 RPM 的 Linux 系统上，安装 Checksec，请使用以下命令：$ sudo dnf install checksec 对于基于 Debian 的 Linux 发行版，使用对应的 apt 命令，来安装 Checksec。$ sudo apt install checksec shell 脚本在安装完 Checksec 后，能够发现 Checksec 是一个单文件的 shell 脚本，它位于 /usr/bin/checksec，并且这个文件挺大的。Checksec 的一个优点是你可以通过快速通读这个 shell 脚本，从而了解 Checksec 的执行原理、明白所有能查找有关二进制文件或可执行文件的安全属性的系统命令：$ file /usr/bin/checksec /usr/bin/checksec: Bourne-Again shell script, ASCII text executable, with very long lines $ wc -l /usr/bin/checksec 2111 /usr/bin/checksec 以下的命令展示了如何对你每天都会使用的：ls 命令的二进制文件运行 Checksec。Checksec 命令的格式是：checksec --file=，后面再跟上二进制文件的绝对路径：$ checksec --file=/usr/bin/ls RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE Full RELRO Canary found NX enabled PIE enabled No RPATH No RUNPATH No Symbols Yes 5 17 /usr/bin/ls 当你在终端中对某个二进制文件运行 Checksec 时，你会看到安全属性有颜色上的区分，显示什么是好的安全属性（绿色），什么可能不是好的安全属性（红色）。我在这里说 “可能” 是因为即使有些安全属性是红色的，也不一定意味着这个二进制文件很糟糕，它可能只是表明发行版供应商在编译二进制文件时做了一些权衡，从而舍弃了部分安全属性。Checksec 输出的第一行提供了二进制文件的各种安全属性，例如 RELRO、STACK CANARY、NX 等（我将在后文进行详细解释）。第二行打印出给定二进制文件（本例中为 ls）在这些安全属性的状态（例如，NX enabled 表示为堆栈中的数据没有执行权限）。示例二进制文件在本文中，我将使用以下的 “hello world” 程序作为示例二进制文件。#include <stdio.h> int main() { printf("Hello World\n"); return 0; } 请注意，在编译源文件 hello.c 的时候，我没有给 gcc 提供任何额外的标志：$ gcc hello.c -o hello $ file hello hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=014b8966ba43e3ae47fab5acae051e208ec9074c, for GNU/Linux 3.2.0, not stripped $ ./hello Hello World 使用 Checksec 运行二进制文件 hello，打印的某些安全属性的状态，与上面的 ls 二进制文件的结果不同（在你的屏幕上，某些属性可能显示为红色）：$ checksec --file=./hello RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE Partial RELRO No canary found NX enabled No PIE No RPATH No RUNPATH 85) Symbols No 0 0./hello $ （LCTT 译注：在我的 Ubuntu 22.04 虚拟机，使用 11.3.0 版本的 gcc，结果与上述不太相同，利用默认参数进行编译，会得到 RELRO、PIE、NX 保护是全开的情况。）更改 Checksec 的输出格式Checksec 允许自定义各种输出格式，你可以使用 --output 来自定义输出格式。我将选择的输出格式是 JSON 格式，并将输出结果通过管道传输到 jq 实用程序，来得到漂亮的打印。接下来，确保你已安装好了 jq，因为本教程会使用 jq 从 Checksec 的输出结果中，用 grep 来快速得到某一特定的安全属性状态，并报告该安全属性是否启动（启动为 yes，未启动为 no）：$ checksec --file=./hello --output=json | jq { "hello": { "relro": "partial", "canary": "no", "nx": "yes", "pie": "no", "rpath": "no", "runpath": "no", "symbols": "yes", "fortify_source": "no", "fortified": "0", "fortify-able": "0" } } 看一看所有的安全属性上面的二进制文件 hello 包括几个安全属性。我将该二进制文件与 ls 的二进制文件进行比较，以检查启用的安全属性有何不同，并解释 Checksec 是如何找到此信息。1、符号（Symbol）我先从简单的讲起。在编译期间，某些 符号 symbols 包含在二进制文件中，这些符号主要用作于调试。开发软件时，需要用到这些符号，来调试和修复错误。这些符号通常会从供用户普遍使用的最终二进制文件中删除。删除这些符号不会影响到二进制文件的执行。删除符号通常是为了节省空间，因为一旦符号被删除了，二进制文件就会稍微小一些。在闭源或专有软件中，符号通常都会被删除，因为把这些符号放在二进制文件中，可以很容易地推断出软件的内部工作原理。根据 Checksec 的结果，在二进制文件 hello 中有符号，但在 ls 的二进制文件中不会有符号。同样地，你还可以用 file 命令，来找到符号的信息，在二进制文件 hello 的输出结果的最后，看到 not stripped，表明二进制文件 hello 有符号：$ checksec --file=/bin/ls --output=json | jq | grep symbols "symbols": "no", $ checksec --file=./hello --output=json | jq | grep symbols "symbols": "yes", $ file hello hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=014b8966ba43e3ae47fab5acae051e208ec9074c, for GNU/Linux 3.2.0, not stripped Checksec 是如何找到符号的信息呢？Checksec 提供了一个方便的 --debug 选项，来显示运行了哪些函数。因此，运行以下的命令，会显示在 shell 脚本中运行了哪些函数：$ checksec --debug --file=./hello 在本教程中，我试图寻找 Checksec 查找安全属性信息时，使用了什么底层命令。由于 Checksec 是一个 shell 脚本，因此你始终可以使用 Bash 功能。以下的命令将输出从 shell 脚本中运行的每个命令：$ bash -x /usr/bin/checksec --file=./hello 如果你滚动浏览上述的输出结果的话，你会看到 echo_message 后面有各个安全属性的类别。以下显示了 Checksec 检测二进制文件是否包含符号时，运行的底层命令：+ readelf -W --symbols ./hello + grep -q '\\.symtab' + echo_message '\033[31m96) Symbols\t\033[m ' Symbols, ' symbols="yes"' '"symbols":"yes",' 上面的输出显示，Checksec 利用 readelf，来读取二进制文件，并提供一个特殊 --symbols 标志，来列出二进制文件中的所有符号。然后它会查找一个特殊值：.symtab，它提供了所能找到的条目的计数（即符号的个数）。你可以在上面编译的测试二进制文件 hello 上，尝试以下命令，得到与 Checksec 查看二进制文件类似的符号信息：$ readelf -W --symbols ./hello $ readelf -W --symbols ./hello | grep -i symtab （LCTT 译注：也可以通过直接查看 /usr/bin/checksec 下的 Checksec 源文件。）如何删除符号你可以在编译后或编译时删除符号。编译后： 在编译后，你可以使用 strip，手动地来删除二进制文件的符号。删除后，使用 file 命令，来检验是否还有符号，现在显示 stripped，表明二进制文件 hello 无符号了：$ gcc hello.c -o hello $ $ file hello hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=322037496cf6a2029dcdcf68649a4ebc63780138, for GNU/Linux 3.2.0, not stripped $ $ strip hello $ $ file hello hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=322037496cf6a2029dcdcf68649a4ebc63780138, for GNU/Linux 3.2.0, stripped $ 编译时： 你也可以在编译时，用 -s 参数让 gcc 编译器帮你自动地删除符号：$ gcc -s hello.c -o hello $ $ file hello hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=247de82a8ad84e7d8f20751ce79ea9e0cf4bd263, for GNU/Linux 3.2.0, stripped $ 重新运行 Checksec，你可以看到现在二进制文件 hello 的 symbols 这一属性的值是no：$ checksec --file=./hello --output=json | jq | grep symbols "symbols": "no", $ 2、Canary（堆栈溢出哨兵）Canary 是放置在缓冲区和 栈 stack 上的控制数据之间的已知值，它用于监视缓冲区是否溢出。当应用程序执行时，会为其分配两种内存，其中之一就是 栈。栈是一个具有两个操作的数据结构：第一个操作 push，将数据压入堆栈；第二个操作 pop，以后进先出的顺序从栈中弹出数据。恶意的输入可能会导致栈溢出，或使用特制的输入破坏栈，并导致程序崩溃：$ checksec --file=/bin/ls --output=json | jq | grep canary "canary": "yes", $ $ checksec --file=./hello --output=json | jq | grep canary "canary": "no", $ Checksec 是如何确定二进制文件是否启用了 Canary 的呢？使用上述同样的方法，得到 Checksec 在检测二进制文件是否启用 Canary 时，运行的底层命令：$ readelf -W -s ./hello | grep -E '__stack_chk_fail|__intel_security_cookie' 启用 Canary为了防止栈溢出等情况，编译器提供了 -stack-protector-all 标志，它向二进制文件添加了额外的代码，来检查缓冲区是否溢出：$ gcc -fstack-protector-all hello.c -o hello $ checksec --file=./hello --output=json | jq | grep canary "canary": "yes", Checksec 显示 Canary 属性现已启用。你还可以通过以下方式，来验证这一点：$ readelf -W -s ./hello | grep -E '__stack_chk_fail|__intel_security_cookie' 2: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __stack_chk_fail@GLIBC_2.4 (3) 83: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __stack_chk_fail@@GLIBC_2.4 $ 3、位置无关可执行文件（PIE） 位置无关可执行文件 Position-Independent Executable （PIE），顾名思义，它指的是放置在内存中某处执行的代码，不管其绝对地址的位置，即代码段、数据段地址随机化（ASLR）：$ checksec --file=/bin/ls --output=json | jq | grep pie "pie": "yes", $ checksec --file=./hello --output=json | jq | grep pie "pie": "no", 通常，PIE 仅对 库 libraries 启用，并不对独立命令行程序启用 PIE。在下面的输出中，hello 显示为 LSB executable，而 libc 标准库（.so） 文件被标记为 LSB shared object：$ file hello hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=014b8966ba43e3ae47fab5acae051e208ec9074c, for GNU/Linux 3.2.0, not stripped $ file /lib64/libc-2.32.so /lib64/libc-2.32.so: ELF 64-bit LSB shared object, x86-64, version 1 (GNU/Linux), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=4a7fb374097fb927fb93d35ef98ba89262d0c4a4, for GNU/Linux 3.2.0, not stripped Checksec 查找是否启用 PIE 的底层命令如下：$ readelf -W -h ./hello | grep EXEC Type: EXEC (Executable file) 如果你在共享库上尝试相同的命令，你将看到 DYN，而不是 EXEC：$ readelf -W -h /lib64/libc-2.32.so | grep DYN Type: DYN (Shared object file) 启用 PIE要在测试程序 hello.c 上启用 PIE，请在编译时，使用以下命令：$ gcc -pie -fpie hello.c -o hello` 你可以使用 Checksec，来验证 PIE 是否已启用：$ checksec --file=./hello --output=json | jq | grep pie "pie": "yes", $ 现在，应该会显示为 “ PIE 可执行 pie executable ”，其类型从 EXEC 更改为 DYN：$ file hello hello: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=bb039adf2530d97e02f534a94f0f668cd540f940, for GNU/Linux 3.2.0, not stripped $ readelf -W -h ./hello | grep DYN Type: DYN (Shared object file) 4、NX（堆栈禁止执行）NX 代表 不可执行 non-executable 。它通常在 CPU 层面上启用，因此启用 NX 的操作系统可以将某些内存区域标记为不可执行。通常，缓冲区溢出漏洞将恶意代码放在堆栈上，然后尝试执行它。但是，让堆栈这些可写区域变得不可执行，可以防止这种攻击。在使用 gcc 对源程序进行编译时，默认启用此安全属性：$ checksec --file=/bin/ls --output=json | jq | grep nx "nx": "yes", $ checksec --file=./hello --output=json | jq | grep nx "nx": "yes", Checksec 使用以下底层命令，来确定是否启用了 NX。在尾部的 RW 表示堆栈是可读可写的；因为没有 E，所以堆栈是不可执行的：$ readelf -W -l ./hello | grep GNU_STACK GNU_STACK 0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW 0x10 演示如何禁用 NX我们不建议禁用 NX，但你可以在编译程序时，使用 -z execstack 参数，来禁用 NX：$ gcc -z execstack hello.c -o hello $ checksec --file=./hello --output=json | jq | grep nx "nx": "no", 编译后，堆栈会变为可读可写可执行（RWE），允许在堆栈上的恶意代码执行：$ readelf -W -l ./hello | grep GNU_STACK GNU_STACK 0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RWE 0x10 5、RELRO（GOT 写保护）RELRO 代表 “ 重定位只读 Relocation Read-Only ”。可执行链接格式（ELF）二进制文件使用全局偏移表（GOT）来动态地解析函数。启用 RELRO 后，会设置二进制文件中的 GOT 表为只读，从而防止重定位攻击：$ checksec --file=/bin/ls --output=json | jq | grep relro "relro": "full", $ checksec --file=./hello --output=json | jq | grep relro "relro": "partial", Checksec 使用以下底层命令，来查找是否启用 RELRO。在二进制文件 hello 仅启用了 RELRO 属性中的一个属性，因此，在 Checksec 验证时，显示 partial：$ readelf -W -l ./hello | grep GNU_RELRO GNU_RELRO 0x002e10 0x0000000000403e10 0x0000000000403e10 0x0001f0 0x0001f0 R 0x1 $ readelf -W -d ./hello | grep BIND_NOW 启用全 RELRO要启用全 RELRO，请在 gcc 编译时，使用以下命令行参数：$ gcc -Wl,-z,relro,-z,now hello.c -o hello $ checksec --file=./hello --output=json | jq | grep relro "relro": "full", 现在， RELRO 中的第二个属性也被启用，使程序变成全 RELRO：$ readelf -W -l ./hello | grep GNU_RELRO GNU_RELRO 0x002dd0 0x0000000000403dd0 0x0000000000403dd0 0x000230 0x000230 R 0x1 $ readelf -W -d ./hello | grep BIND_NOW 0x0000000000000018 (BIND_NOW) 6、FortifyFortify 是另一个安全属性，但它超出了本文的范围。Checksec 是如何在二进制文件中验证 Fortify，以及如何在 gcc 编译时启用 Fortify，作为你需要解决的课后练习。$ checksec --file=/bin/ls --output=json | jq | grep -i forti "fortify_source": "yes", "fortified": "5", "fortify-able": "17" $ checksec --file=./hello --output=json | jq | grep -i forti "fortify_source": "no", "fortified": "0", "fortify-able": "0" 其他的 Checksec 功能关于安全性的话题是永无止境的，不可能在本文涵盖所有关于安全性的内容，但我还想提一下 Checksec 命令的一些其他功能，这些功能也很好用。对多个二进制文件运行 Checksec你不必对每个二进制文件都进行一次 Checksec。相反，你可以提供多个二进制文件所在的目录路径，Checksec 将一次性为你验证所有文件：$ checksec --dir=/usr 对进程运行 ChecksecChecksec 除了能检查二进制文件的安全属性，Checksec 还能对程序起作用。以下的命令用于查找你系统上所有正在运行的程序的安全属性。如果你希望 Checksec 检查所有正在运行的进程，可以使用 --proc-all，或者你也可以使用进程名称，选择特定的进程进行检查：$ checksec --proc-all $ checksec --proc=bash 对内核运行 Checksec除了本文介绍的用 Checksec 检查用户态应用程序的安全属性之外，你还可以使用它来检查系统内置的 内核属性 kernel properties ：$ checksec --kernel 快来试一试 Checksec 吧Checksec 是一个能了解哪些用户空间和内核的安全属性被启用的好方法。现在，你就可以开始使用 Checksec，来了解每个安全属性是什么，并明白启用每个安全属性的原因，以及它能阻止的攻击类型。via: https://opensource.com/article/21/6/linux-checksec作者：Gaurav Kamathe 选题：lujun9972 译者：chai001125 校对：wxy本文由 LCTT 原创编译，Linux中国 荣誉推出
rustup 可用于 Rust 安装与更新。它还能够在稳定版、测试版和每日更新版之间无缝切换 Rust 编译器及其工具。Rust 编程语言 如今变得越来越流行，受到爱好者和公司的一致好评。它受欢迎的原因之一是 Rust 提供的令人惊叹的工具，使其成为开发人员使用的乐趣。rustup 是管理 Rust 工具的官方工具。它不仅可以安装和更新 Rust ，它还能够在稳定版、测试版和每日更新版之间无缝切换 Rust 编译器及其工具。本文将向你介绍 rustup 及其一些常用命令。默认 Rust 安装方式如果你想在 Linux 上安装 Rust，你可以使用你的包管理器。在 Fedora 或 CentOS Stream 上，你可以这样：$ sudo dnf install rust cargo 这提供了一个稳定版的 Rust 工具链，如果你是 Rust 的初学者，并想尝试编译和运行简单的程序，它会非常有用。但是，由于 Rust 是一种新的编程语言，它变化很快，并且经常添加许多新功能。这些功能是 Rust 工具链的每日更新版和之后测试版的一部分。要试用这些功能，你需要安装这些较新版本的工具链，而不会影响系统上的稳定版本。不幸的是，你的发行版的包管理器在这里无法做到。使用 rustup 安装 Rust 工具链要解决上述问题，你可以下载安装脚本：$ curl --proto '=https' --tlsv1.2 \ -sSf https://sh.rustup.rs > sh.rustup.rs 检查它，然后运行它。它不需要 root 权限，并根据你的本地用户权限安装 Rust：$ file sh.rustup.rs sh.rustup.rs: POSIX shell script, ASCII text executable $ less sh.rustup.rs $ bash sh.rustup.rs 出现提示时选择选项 1：1) Proceed with installation (default) 2) Customize installation 3) Cancel installation > 1 安装后，你必须获取环境变量以确保 rustup 命令立即可供你运行：$ source $HOME/.cargo/env 验证是否安装了 Rust 编译器（rustc）和 Rust 包管理器（cargo）：$ rustc --version $ cargo --version 查看已安装和可用的工具链你可以使用以下命令查看已安装的不同工具链以及哪个工具链是可用的：$ rustup show 在工具链之间切换你可以查看默认工具链并根据需要进行更改。如果你当前使用的是稳定版工具链，并希望尝试每日更新版中提供的新功能，你可以轻松切换到每日更新版工具链：$ rustup default $ rustup default nightly 要查看 Rust 的编译器和包管理器的完整路径：$ rustup which rustc $ rustup which cargo 检查和更新工具链要检查是否有新的 Rust 工具链可用：$ rustup check 假设一个新版本的 Rust 发布了，其中包含一些有趣的特性，并且你想要获取最新版本的 Rust。你可以使用 update 子命令来做到这一点：$ rustup update 帮助和文档以上命令对于日常使用来说绰绰有余。尽管如此，rustup 有多种命令，你可以参考帮助部分了解更多详细信息：$ rustup --help rustup 在 GitHub 上有完整的 参考手册，你可以用作参考。所有 Rust 文档都安装在你的本地系统上，不需要你连接到互联网。你可以访问包括书籍、标准库等在内的本地文档：$ rustup doc $ rustup doc --book $ rustup doc --std $ rustup doc --cargo Rust 是一种正在积极开发中的令人兴奋的语言。如果你对编程的发展方向感兴趣，请关注 Rust！via: https://opensource.com/article/22/6/rust-toolchain-rustup作者：Gaurav Kamathe 选题：lkxed 译者：geekpi 校对：turbokernel本文由 LCTT 原创编译，Linux中国 荣誉推出
这些方便的 Go 构建选项可以帮助你更好地理解 Go 的编译过程。学习一门新的编程语言最令人欣慰的部分之一，就是最终运行了一个可执行文件，并获得预期的输出。当我开始学习 Go 这门编程语言时，我先是阅读一些示例程序来熟悉语法，然后是尝试写一些小的测试程序。随着时间的推移，这种方法帮助我熟悉了编译和构建程序的过程。Go 的构建选项提供了更好地控制构建过程的方法。它们还可以提供额外的信息，帮助把这个过程分成更小的部分。在这篇文章中，我将演示我所使用的一些选项。注意：我使用的“ 构建 build ”和“ 编译 compile ”这两个词是同一个意思。开始使用 Go我使用的 Go 版本是 1.16.7。但是，这里给出的命令应该也能在最新的版本上运行。如果你没有安装 Go，你可以从 Go 官网 上下载它，并按照说明进行安装。你可以通过打开一个命令提示符，并键入下面的命令来验证你所安装的版本：$ go version 你应该会得到类似下面这样的输出，具体取决于你安装的版本：go version go1.16.7 linux/amd64 基本的 Go 程序的编译和执行方法我将从一个在屏幕上简单打印 “Hello World” 的 Go 程序示例开始，就像下面这样：$ cat hello.go package main import "fmt" func main() { fmt.Println("Hello World") } 在讨论更高级的选项之前，我将解释如何编译这个 Go 示例程序。我使用了 build 命令，后面跟着 Go 程序的源文件名，本例中是 hello.go，就像下面这样：$ go build hello.go 如果一切工作正常，你应该看到在你的当前目录下创建了一个名为 hello 的可执行文件。你可以通过使用 file 命令验证它是 ELF 二进制可执行格式（在 Linux 平台上）。你也可以直接执行它，你会看到它输出 “Hello World”。$ ls hello hello.go $ file ./hello ./hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, not stripped $ ./hello Hello World Go 提供了一个方便的 run 命令，以便你只是想看看程序是否能正常工作，并获得预期的输出，而不想生成一个最终的二进制文件。请记住，即使你在当前目录中没有看到可执行文件，Go 仍然会在某个地方编译并生成可执行文件并运行它，然后把它从系统中删除。我将在本文后面的章节中解释。$ go run hello.go Hello World $ ls hello.go 更多细节上面的命令就像一阵风一样，一下子就运行完了我的程序。然而，如果你想知道 Go 在编译这些程序的过程中做了什么，Go 提供了一个 -x 选项，它可以打印出 Go 为产生这个可执行文件所做的一切。简单看一下你就会发现，Go 在 /tmp 内创建了一个临时工作目录，并生成了可执行文件，然后把它移到了 Go 源程序所在的当前目录。$ go build -x hello.go WORK=/tmp/go-build1944767317 mkdir -p $WORK/b001/ << snip >> mkdir -p $WORK/b001/exe/ cd . /usr/lib/golang/pkg/tool/linux_amd64/link -o $WORK \ /b001/exe/a.out -importcfg $WORK/b001 \ /importcfg.link -buildmode=exe -buildid=K26hEYzgDkqJjx2Hf-wz/\ nDueg0kBjIygx25rYwbK/W-eJaGIOdPEWgwC6o546 \ /K26hEYzgDkqJjx2Hf-wz -extld=gcc /root/.cache/go-build /cc \ /cc72cb2f4fbb61229885fc434995964a7a4d6e10692a23cc0ada6707c5d3435b-d /usr/lib/golang/pkg/tool/linux_amd64/buildid -w $WORK \ /b001/exe/a.out # internal mv $WORK/b001/exe/a.out hello rm -r $WORK/b001/ 这有助于解决在程序运行后却在当前目录下没有生成可执行文件的谜团。使用 -x 显示可执行文件确实在 /tmp 工作目录下创建并被执行了。然而，与 build 命令不同的是，可执行文件并没有移动到当前目录，这使得看起来没有可执行文件被创建。$ go run -x hello.go mkdir -p $WORK/b001/exe/ cd . /usr/lib/golang/pkg/tool/linux_amd64/link -o $WORK/b001 \ /exe/hello -importcfg $WORK/b001/importcfg.link -s -w -buildmode=exe -buildid=hK3wnAP20DapUDeuvAAS/E_TzkbzwXz6tM5dEC8Mx \ /7HYBzuaDGVdaZwSMEWAa/hK3wnAP20DapUDeuvAAS -extld=gcc \ /root/.cache/go-build/75/ \ 7531fcf5e48444eed677bfc5cda1276a52b73c62ebac3aa99da3c4094fa57dc3-d $WORK/b001/exe/hello Hello World 模仿编译而不产生可执行文件假设你不想编译程序并产生一个实际的二进制文件，但你确实想看到这个过程中的所有步骤。你可以通过使用 -n 这个构建选项来做到这一点，该选项会打印出通常的执行步骤，而不会实际创建二进制文件。$ go build -n hello.go 保存临时目录很多工作都发生在 /tmp 工作目录中，一旦可执行文件被创建和运行，它就会被删除。但是如果你想看看哪些文件是在编译过程中创建的呢？Go 提供了一个 -work 选项，它可以在编译程序时使用。-work 选项除了运行程序外，还打印了工作目录的路径，但它并不会在这之后删除工作目录，所以你可以切换到该目录，检查在编译过程中创建的所有文件。$ go run -work hello.go WORK=/tmp/go-build3209320645 Hello World $ find /tmp/go-build3209320645 /tmp/go-build3209320645 /tmp/go-build3209320645/b001 /tmp/go-build3209320645/b001/importcfg.link /tmp/go-build3209320645/b001/exe /tmp/go-build3209320645/b001/exe/hello $ /tmp/go-build3209320645/b001/exe/hello Hello World 其他编译选项如果说，你想手动编译程序，而不是使用 Go 的 build 和 run 这两个方便的命令，最后得到一个可以直接由你的操作系统（这里指 Linux）运行的可执行文件。那么，你该怎么做呢？这个过程可以分为两部分：编译和链接。你可以使用 tool 选项来看看它是如何工作的。首先，使用 tool compile 命令产生结果的 ar 归档文件，它包含了 .o 中间文件。接下来，对这个 hello.o 文件执行 tool link 命令，产生最终的可执行文件，然后你就可以运行它了。$ go tool compile hello.go $ file hello.o hello.o: current ar archive $ ar t hello.o __.PKGDEF _go_.o $ go tool link -o hello hello.o $ file hello hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, not stripped $ ./hello Hello World 如果你想进一步查看基于 hello.o 文件产生可执行文件的链接过程，你可以使用 -v 选项，它会搜索每个 Go 可执行文件中包含的 runtime.a 文件。$ go tool link -v -o hello hello.o HEADER = -H5 -T0x401000 -R0x1000 searching for runtime.a in /usr/lib/golang/pkg/linux_amd64/runtime.a 82052 symbols, 18774 reachable 1 package symbols, 1106 hashed symbols, 77185 non-package symbols, 3760 external symbols 81968 liveness data 交叉编译选项现在我已经解释了 Go 程序的编译过程，接下来，我将演示 Go 如何通过在实际的 build 命令之前提供 GOOS 和 GOARCH 这两个环境变量，来允许你构建针对不同硬件架构和操作系统的可执行文件。这有什么用呢？举个例子，你会发现为 ARM（arch64）架构制作的可执行文件不能在英特尔（x86\_64）架构上运行，而且会产生一个 Exec 格式错误。下面的这些选项使得生成跨平台的二进制文件变得小菜一碟：$ GOOS=linux GOARCH=arm64 go build hello.go $ file ./hello ./hello: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), statically linked, not stripped $ ./hello bash: ./hello: cannot execute binary file: Exec format error $ uname -m x86_64 你可以阅读我之前的博文，以更多了解我在 使用 Go 进行交叉编译 方面的经验。查看底层汇编指令源代码并不会直接转换为可执行文件，尽管它生成了一种中间汇编格式，然后最终被组装为可执行文件。在 Go 中，这被映射为一种中间汇编格式，而不是底层硬件汇编指令。要查看这个中间汇编格式，请在使用 build 命令时，提供 -gcflags 选项，后面跟着 -S。这个命令将会显示使用到的汇编指令：$ go build -gcflags="-S" hello.go # command-line-arguments "".main STEXT size=138 args=0x0 locals=0x58 funcid=0x0 0x0000 00000 (/test/hello.go:5) TEXT "".main(SB), ABIInternal, $88-0 0x0000 00000 (/test/hello.go:5) MOVQ (TLS), CX 0x0009 00009 (/test/hello.go:5) CMPQ SP, 16(CX) 0x000d 00013 (/test/hello.go:5) PCDATA $0, $-2 0x000d 00013 (/test/hello.go:5) JLS 128 << snip >> 你也可以使用 objdump -s 选项，来查看已经编译好的可执行程序的汇编指令，就像下面这样：$ ls hello hello.go $ go tool objdump -s main.main hello TEXT main.main(SB) /test/hello.go hello.go:5 0x4975a0 64488b0c25f8ffffff MOVQ FS:0xfffffff8, CX hello.go:5 0x4975a9 483b6110 CMPQ 0x10(CX), SP hello.go:5 0x4975ad 7671 JBE 0x497620 hello.go:5 0x4975af 4883ec58 SUBQ $0x58, SP hello.go:6 0x4975d8 4889442448 MOVQ AX, 0x48(SP) << snip >> 分离二进制文件以减少其大小Go 的二进制文件通常比较大。例如, 一个简单的 “Hello World” 程序将会产生一个 1.9M 大小的二进制文件。$ go build hello.go $ $ du -sh hello 1.9M hello $ $ file hello hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, not stripped $ 为了减少生成的二进制文件的大小，你可以分离执行过程中不需要的信息。使用 -ldflags 和 -s -w 选项可以使生成的二进制文件略微变小为 1.3M。$ go build -ldflags="-s -w" hello.go $ $ du -sh hello 1.3M hello $ $ file hello hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, stripped $ 总结我希望这篇文章向你介绍了一些方便的 Go 编译选项，同时帮助了你更好地理解 Go 编译过程。关于构建过程的其他信息和其他有趣的选项，请参考 Go 命令帮助：$ go help build题图由 Ashraf Chemban 在 Pixabay 上发布。via: https://opensource.com/article/22/4/go-build-options作者：Gaurav Kamathe 选题：lkxed 译者：lkxed 校对：wxy本文由 LCTT 原创编译，Linux中国 荣誉推出
Go 团队接受了新增对模糊测试的支持的提议。Go 的应用越来越广泛。现在它是云原生软件、容器软件、命令行工具和数据库等等的首选语言。Go 很早之前就已经有了内建的 对测试的支持。这使得写测试代码和运行都相当简单。什么是模糊测试？ 模糊测试 fuzz testing （fuzzing）是指向你的软件输入非预期的数据。理想情况下，这种测试会让你的应用程序崩溃或有非预期的表现。抛开最终的结果，从程序对非预期的输入数据的处理结果中你可以得到很多信息，这样你就可以增加一些合适的错误处理。任何一个软件都有对不同来源的输入或数据的接收说明，软件会对这些数据进行处理并返回适当的结果。软件开发后，测试工程师团队对其进行测试，找出软件中的错误，给出测试报告，并（由开发者）修复。通常测试的目的是验证软件的行为是否符合预期。测试又可以细分为不同的类型，如功能测试、集成测试、性能测试等等。每种测试方法关注软件功能的某一个方面，以便发现错误或者提升可靠性或性能。模糊测试在这一测试过程上更进一步，尝试向软件程序输入一些“无效”或“随机”的数据。这种输入是故意的，期望得到的结果就是程序崩溃或输出异常，这样就可以暴露程序中的错误以便由开发者来修复它们。与其他测试类似，很少需要手动进行模糊测试，业界有大量的模糊测试工具可以将这个过程自动化。Go 中的软件测试举个例子，假如你想测试 add.go 中的 Add() 函数，你可以在 add_test.go 中导入 testing 包并把测试体写在以 TestXXX() 开头的函数内。考虑如下代码：func Add(num1, num2 int) int { } 在 add_test.go 文件中，你可能有如下测试代码：import "testing" func TestAdd(t *testing.T) { } 运行测试：$ go test 新增对模糊测试的支持Go 团队已经接受了 新增对模糊测试的支持的提议，以进一步推动这项工作。这涉及到新增一个 testing.F 类型，在 _test.go 文件中新增 FuzzXXX() 函数，在 Go 工具中会新增一个 -fuzz 选项来执行这些测试。在 add_test.go 文件中：func FuzzAdd(f *testing.F) { } 执行以下代码：$ go test -fuzz 在本文编写时，这个 功能还是试验性的，但是应该会在 1.18 发布版本中包含。（LCTT 译注：Go 1.18 刚刚发布，已经包含了对模糊测试的支持）目前很多功能如 -keepfuzzing、-race 等也还没有支持。Go 团队最近发布了一篇 模糊测试教程，值得读一下。安装 gotip 来获取最新的功能如果你极度渴望在正式发布之前尝试这些功能，你可以使用 gotip 来测试即将正式发布的 Go 功能并反馈给他们。你可以使用下面的命令来安装 gotip。安装之后，你可以用 gotip 程序代替以前的 go 程序来编译和运行程序。$ go install golang.org/dl/gotip@latest $ gotip download $ gotip version go version devel go1.18-f009910 Thu Jan 6 16:22:21 2022 +0000 linux/amd64 社区对于模糊测试的观点软件社区中经常会讨论模糊测试，不同的人对模糊测试有不同的看法。有些人认为这是一种有用的技术，可以找到错误，尤其是在安全方面。然而考虑到模糊测试所需要的资源（CPU、内存），有人就认为这是一种浪费，而他们更愿意用其他的测试方法。即使在 Go 团队内部，意见也不统一。我们可以看到 Go 的联合创始人 Rob Pike 对模糊测试的使用和在 Go 中的实现是持轻微的怀疑态度的。...虽然模糊测试有助于发现某类错误，但是它会占用大量的 CPU 和存储资源，并且效益成本比率也不明确。我担心为了写模糊测试浪费精力，或者 git 仓库中充斥大量无用的测试数据~Rob Pike然而，Go 安全团队的另一个成员，Filo Sottile，似乎对 Go 新增支持模糊测试很乐观，举了很多例子来支持，也希望模糊测试能成为开发过程中的一部分。我想说模糊测试可以发现极端情况下的错误。这是我们作为安全团队对其感兴趣的原因：在极端情况下发现的错误可以避免在生产环境中成为弱点。我们希望模糊测试能成为开发的一部分 —— 不只是构建或安全方面 —— 而是整个开发过程：它能提升相关代码的质量...~Filo Sottile现实中的模糊测试对我而言，模糊测试在发现错误以及让系统变得更安全和更有弹性方面似乎非常有效。举个例子，Linux 内核也会使用名为 syzkaller 的工具进行模糊测试，这个工具已经发现了 大量 错误。AFL 也是比较流行的模糊测试工具，用来测试 C/C++ 写的程序。之前也有对 Go 程序进行模糊测试的观点，其中之一就是 Filo 在 GitHub 评论中提到的 go-fuzz。go-fuzz 的记录提供了相当惊人的证据，证明模糊处理能很好地找到人类没有发现的错误。根据我的经验，我们只需要消耗一点点 CPU 的时间就可以得到极端情况下非常高效的测试结果。为什么在 Go 中新增对模糊测试的原生支持如果我们的需求是对 Go 程序进行模糊测试，之前的工具像 go-fuzz 就可以完成，那么为什么要在这种语言中增加原生支持呢？Go 模糊测试设计草案 中说明了这样做的一些根本原因。设计的思路是让开发过程更简单，因为前面说的工具增加了开发者的工作量，还有功能缺失。如果你没有接触过模糊测试，那么我建议你读一下设计草案文档。开发者可以使用诸如 go-fuzz 或 fzgo（基于 go-fuzz）来解决某些需求。然而，已有的每种解决方案都需要在典型的 Go 测试上做更多的事，而且还缺少关键的功能。相比于其他的 Go 测试（如基准测试和单元测试），模糊测试不应该比它们复杂，功能也不应该比它们少。已有的解决方案增加了额外的开销，比如自定义命令行工具。模糊测试工具在大家期望 Go 语言新增功能的列表中，模糊测试是其中很受欢迎的一项。虽然现在还是试验性的，但在将要到来的发布版本中会变得更强大。这给了我们足够的时间去尝试它以及探索它的使用场景。我们不应该把它视为一种开销，如果使用得当它会是一种发现错误非常高效的测试工具。使用 Go 的团队应该推动它的使用，开发者可以写简单的模糊测试，测试团队去慢慢扩展以此来使用它全部的能力。via: https://opensource.com/article/22/1/native-go-fuzz-testing作者：Gaurav Kamathe 选题：lujun9972 译者：lxbwolf 校对：wxy本文由 LCTT 原创编译，Linux中国 荣誉推出
trace-cmd 是一个易于使用，且特性众多、可用来追踪内核函数的命令。在 之前的文章 里，我介绍了如何利用 ftrace 来追踪内核函数。通过写入和读出文件来使用 ftrace 会变得很枯燥，所以我对它做了一个封装来运行带有选项的命令，以启用和禁用追踪、设置过滤器、查看输出、清除输出等等。trace-cmd 命令是一个可以帮助你做到这一点的工具。在这篇文章中，我使用 trace-cmd 来执行我在 ftrace 文章中所做的相同任务。由于会经常参考那篇文章，建议在阅读这篇文章之前先阅读它。安装 trace-cmd本文中所有的命令都运行在 root 用户下。因为 ftrace 机制被内置于内核中，因此你可以使用下面的命令进行验证它是否启用：# mount | grep tracefs none on /sys/kernel/tracing type tracefs (rw,relatime,seclabel) 不过，你需要手动尝试安装 trace-cmd 命令：# dnf install trace-cmd -y 列出可用的追踪器当使用 ftrace 时，你必须查看文件的内容以了解有哪些追踪器可用。但使用 trace-cmd，你可以通过以下方式获得这些信息:# trace-cmd list -t hwlat blk mmiotrace function_graph wakeup_dl wakeup_rt wakeup function nop 启用函数追踪器在我 之前的文章 中，我使用了两个追踪器，在这里我也会这么做。用 function 启用你的第一个追踪器:$ trace-cmd start -p function plugin 'function' 查看追踪输出一旦追踪器被启用，你可以通过使用 show 参数来查看输出。这只显示了前 20 行以保持例子的简短（见我之前的文章对输出的解释）：# trace-cmd show | head -20 ## tracer: function # # entries-in-buffer/entries-written: 410142/3380032 #P:8 # # _-----=> irqs-off # / _----=> need-resched # | / _---=> hardirq/softirq # || / _--=> preempt-depth # ||| / delay # TASK-PID CPU# |||| TIMESTAMP FUNCTION # | | | |||| | | gdbus-2606 [004] ..s. 10520.538759: __msecs_to_jiffies <-rebalance_domains gdbus-2606 [004] ..s. 10520.538760: load_balance <-rebalance_domains gdbus-2606 [004] ..s. 10520.538761: idle_cpu <-load_balance gdbus-2606 [004] ..s. 10520.538762: group_balance_cpu <-load_balance gdbus-2606 [004] ..s. 10520.538762: find_busiest_group <-load_balance gdbus-2606 [004] ..s. 10520.538763: update_group_capacity <-update_sd_lb_stats.constprop.0 gdbus-2606 [004] ..s. 10520.538763: __msecs_to_jiffies <-update_group_capacity gdbus-2606 [004] ..s. 10520.538765: idle_cpu <-update_sd_lb_stats.constprop.0 gdbus-2606 [004] ..s. 10520.538766: __msecs_to_jiffies <-rebalance_domains 停止追踪并清除缓冲区追踪将会在后台继续运行，你可以继续用 show 查看输出。要停止追踪，请运行带有 stop 参数的 trace-cmd 命令：# trace-cmd stop 要清除缓冲区，用 clear 参数运行它：# trace-cmd clear 启用函数调用图追踪器运行第二个追踪器，通过 function_graph 参数来启用它。# trace-cmd start -p function_graph Plugin 'function_graph' 再次使用 show 参数查看输出。正如预期的那样，输出与第一次追踪输出略有不同。这一次，它包括一个函数调用链：# trace-cmd show | head -20 ## tracer: function_graph # # CPU DURATION FUNCTION CALLS # | | | | | | | 4) 0.079 us | } /* rcu_all_qs */ 4) 0.327 us | } /* __cond_resched */ 4) 0.081 us | rcu_read_unlock_strict(); 4) | __cond_resched() { 4) 0.078 us | rcu_all_qs(); 4) 0.243 us | } 4) 0.080 us | rcu_read_unlock_strict(); 4) | __cond_resched() { 4) 0.078 us | rcu_all_qs(); 4) 0.241 us | } 4) 0.080 us | rcu_read_unlock_strict(); 4) | __cond_resched() { 4) 0.079 us | rcu_all_qs(); 4) 0.235 us | } 4) 0.095 us | rcu_read_unlock_strict(); 4) | __cond_resched() { 使用 stop 和 clear 命令来停止追踪和清除缓存区：# trace-cmd stop # trace-cmd clear 调整追踪以增加深度如果你想在函数调用中看到更多的深度，你可以对追踪器进行调整：# trace-cmd start -p function_graph --max-graph-depth 5 plugin 'function_graph' 现在，当你将这个输出与你之前看到的进行比较时，你应该看到更多的嵌套函数调用：# trace-cmd show | head -20 ## tracer: function_graph # # CPU DURATION FUNCTION CALLS # | | | | | | | 6) | __fget_light() { 6) 0.804 us | __fget_files(); 6) 2.708 us | } 6) 3.650 us | } /* __fdget */ 6) 0.547 us | eventfd_poll(); 6) 0.535 us | fput(); 6) | __fdget() { 6) | __fget_light() { 6) 0.946 us | __fget_files(); 6) 1.895 us | } 6) 2.849 us | } 6) | sock_poll() { 6) 0.651 us | unix_poll(); 6) 1.905 us | } 6) 0.475 us | fput(); 6) | __fdget() { 了解可被追踪的函数如果你想只追踪某些函数而忽略其他的，你需要知道确切的函数名称。你可以用 list -f 参数来得到它们。例如搜索常见的内核函数 kmalloc，它被用来在内核中分配内存：# trace-cmd list -f | grep kmalloc bpf_map_kmalloc_node mempool_kmalloc __traceiter_kmalloc __traceiter_kmalloc_node kmalloc_slab kmalloc_order kmalloc_order_trace kmalloc_large_node __kmalloc __kmalloc_track_caller __kmalloc_node __kmalloc_node_track_caller [...] 下面是我的测试系统中可被追踪的函数总数：# trace-cmd list -f | wc -l 63165 追踪内核模块相关的函数你也可以追踪与特定内核模块相关的函数。假设你想追踪 kvm 内核模块相关的功能，你可以通过以下方式来实现。请确保该模块已经加载：# lsmod | grep kvm_intel kvm_intel 335872 0 kvm 987136 1 kvm_intel 再次运行 trace-cmd，使用 list 参数，并从输出结果中，grep 查找以 ] 结尾的行。这将过滤掉内核模块。然后 grep 内核模块 kvm_intel ，你应该看到所有与该内核模块有关的函数。# trace-cmd list -f | grep ]$ | grep kvm_intel vmx_can_emulate_instruction [kvm_intel] vmx_update_emulated_instruction [kvm_intel] vmx_setup_uret_msr [kvm_intel] vmx_set_identity_map_addr [kvm_intel] handle_machine_check [kvm_intel] handle_triple_fault [kvm_intel] vmx_patch_hypercall [kvm_intel] [...] vmx_dump_dtsel [kvm_intel] vmx_dump_sel [kvm_intel] 追踪特定函数现在你知道了如何找到感兴趣的函数，请用一个例子把这些内容用于时间。就像前面的文章一样，试着追踪与文件系统相关的函数。我的测试系统上的文件系统是 ext4。这个过程略有不同；你在运行命令时，不使用 start 参数，而是在 record 参数后面加上你想追踪的函数的“模式”。你还需要指定你想要的追踪器；在这种情况下，就是 function_graph。该命令会继续记录追踪，直到你用 Ctrl+C 停止它。所以几秒钟后，按 Ctrl+C 停止追踪：# trace-cmd list -f | grep ^ext4_ # trace-cmd record -l ext4_* -p function_graph plugin 'function_graph' Hit Ctrl^C to stop recording ^C CPU0 data recorded at offset=0x856000 8192 bytes in size [...] 查看追踪记录要查看你之前的追踪记录，运行带有 report 参数的命令。从输出结果来看，很明显过滤器起作用了，你只看到 ext4 相关的函数追踪：# trace-cmd report | head -20 [...] cpus=8 trace-cmd-12697 [000] 11303.928103: funcgraph_entry: | ext4_show_options() { trace-cmd-12697 [000] 11303.928104: funcgraph_entry: 0.187 us | ext4_get_dummy_policy(); trace-cmd-12697 [000] 11303.928105: funcgraph_exit: 1.583 us | } trace-cmd-12697 [000] 11303.928122: funcgraph_entry: | ext4_create() { trace-cmd-12697 [000] 11303.928122: funcgraph_entry: | ext4_alloc_inode() { trace-cmd-12697 [000] 11303.928123: funcgraph_entry: 0.101 us | ext4_es_init_tree(); trace-cmd-12697 [000] 11303.928123: funcgraph_entry: 0.083 us | ext4_init_pending_tree(); trace-cmd-12697 [000] 11303.928123: funcgraph_entry: 0.141 us | ext4_fc_init_inode(); trace-cmd-12697 [000] 11303.928123: funcgraph_exit: 0.931 us | } trace-cmd-12697 [000] 11303.928124: funcgraph_entry: 0.081 us | ext4_get_dummy_policy(); trace-cmd-12697 [000] 11303.928124: funcgraph_entry: 0.133 us | ext4_get_group_desc(); trace-cmd-12697 [000] 11303.928124: funcgraph_entry: 0.115 us | ext4_free_inodes_count(); trace-cmd-12697 [000] 11303.928124: funcgraph_entry: 0.114 us | ext4_get_group_desc(); 追踪一个特定的 PID假设你想追踪与一个进程（PID）有关的函数。打开另一个终端，注意运行中的 shell 的PID：# echo $$ 10885 再次运行 record 命令，用 -P 选项传递PID。这一次，让终端运行（也就是说，先不要按 Ctrl+C ）：# trace-cmd record -P 10885 -p function_graph Plugin 'function_graph' Hit Ctrl^C to stop recording 在 shell 上运行一些命令移动到另一个终端，在那里你有一个以特定 PID 运行的 shell，并运行任何命令，例如，ls 命令用来列出文件：# ls Temp-9b61f280-fdc1-4512-9211-5c60f764d702 tracker-extract-3-files.1000 v8-compile-cache-1000 [...] 移动到你启用追踪的终端，按 Ctrl+C 停止追踪：# trace-cmd record -P 10885 -p function_graph plugin 'function_graph' Hit Ctrl^C to stop recording ^C CPU1 data recorded at offset=0x856000 618496 bytes in size [...] 在追踪的输出中，你可以看到左边是 PID 和 Bash shell，右边是与之相关的函数调用。这对于缩小你的追踪范围是非常方便的：# trace-cmd report | head -20 cpus=8 <idle>-0 [001] 11555.380581: funcgraph_entry: | switch_mm_irqs_off() { <idle>-0 [001] 11555.380583: funcgraph_entry: 1.703 us | load_new_mm_cr3(); <idle>-0 [001] 11555.380586: funcgraph_entry: 0.493 us | switch_ldt(); <idle>-0 [001] 11555.380587: funcgraph_exit: 7.235 us | } bash-10885 [001] 11555.380589: funcgraph_entry: 1.046 us | finish_task_switch.isra.0(); bash-10885 [001] 11555.380591: funcgraph_entry: | __fdget() { bash-10885 [001] 11555.380592: funcgraph_entry: 2.036 us | __fget_light(); bash-10885 [001] 11555.380594: funcgraph_exit: 3.256 us | } bash-10885 [001] 11555.380595: funcgraph_entry: | tty_poll() { bash-10885 [001] 11555.380597: funcgraph_entry: | tty_ldisc_ref_wait() { bash-10885 [001] 11555.380598: funcgraph_entry: | ldsem_down_read() { bash-10885 [001] 11555.380598: funcgraph_entry: | __cond_resched() { 试一试这些简短的例子显示了使用 trace-cmd 命令而不是底层的 ftrace 机制，是如何实现既容易使用又拥有丰富的功能，许多内容本文并没有涉及。要想了解更多信息并更好地使用它，请查阅它的手册，并尝试使用其他有用的命令。via: https://opensource.com/article/21/7/linux-kernel-trace-cmd作者：Gaurav Kamathe 选题：lujun9972 译者：萌新阿岩 校对：wxy本文由 LCTT 原创编译，Linux中国 荣誉推出
通过 ftrace 来了解 Linux 内核内部工作方式是一个好方法。操作系统的内核是最难以理解的软件之一。自从你的系统启动后，它会一直在后台运行。尽管每个用户都不与内核直接交互，但他们在内核的帮助下完成自己的计算任务。与内核的交互发生在调用系统调用或者用户日常使用的各种库或应用间接调用了系统调用。在之前的文章里我介绍了如何使用 strace 来追踪系统调用。然而，使用 strace 时你的视野是有限的。它允许你查看特定参数的系统调用。并在工作完成后，看到其返回值或状态，以表明是成功还是失败。但是你无法知道内核在这段时间内发生了什么。除了系统调用外，还有很多其他活动内核中发生，而你却视而不见。ftrace 介绍本文的旨在通过使用一个名为 ftrace 的机制来阐明追踪内核函数的一些情况。它使得任何 Linux 用户可以轻松地追踪内核，并且了解更多关于 Linux 内核内部如何工作。ftrace 默认产生的输出往往是巨大的，因为内核总是忙碌的。为了节省空间，很多情况下我会通过截断来给出尽量小的输出。我使用 Fedora 来演示下面的例子，但是它们应该在其他最新的 Linux 发行版上同样可以运行。启用 ftraceftrace 现在已经是内核中的一部分了，你不再需要事先安装它了。也就是说，如果你在使用最近的 Linux 系统，那么 ftrace 是已经启用了的。为了验证 ftrace 是否可用，运行 mount 命令并查找 tracefs。如果你看到类似下面的输出，表示 ftrace 已经启用，你可以轻松地尝试本文中下面的例子。下面有些命令需要在 root 用户下使用（用 sudo 执行是不够的）。# mount | grep tracefs none on /sys/kernel/tracing type tracefs (rw,relatime,seclabel) 要想使用 ftrace，你首先需要进入上面 mount 命令中找到的特定目录中，在那个目录下运行文章中的其他命令。# cd /sys/kernel/tracing 一般的工作流程首先，你需要理解捕捉踪迹和获取输出的一般流程。如果你直接运行 ftrace，不会运行任何特定的 ftrace 命令。相反的，基本操作是通过标准 Linux 命令来写入或读取一些文件。一般的步骤如下：通过写入一些特定文件来启用/结束追踪通过写入一些特定文件来设置/取消追踪时的过滤规则从文件中读取基于第 1 和 2 步的追踪输出从文件中清除早期输出或缓冲区缩小到你的特定用例（你要追踪的内核函数），重复 1、2、3、4 步可用的追踪器类型有多种不同的追踪器可供使用。之前提到，在运行任何命令前，你需要进入一个特定的目录下，因为需要的文件在这些目录下。我在我的例子中使用了相对路径（而不是绝对路径）。你可以查看 available_tracers 文件内容来查看所有可用的追踪器类型。你可以看下面列出了几个。不需要担心这些：$ pwd /sys/kernel/tracing $ sudo cat available_tracers hwlat blk mmiotrace function_graph wakeup_dl wakeup_rt wakeup function nop 在所有输出的追踪器中，我会聚焦于下面三个特殊的：启用追踪的 function 和 function_graph，以及停止追踪的 nop。确认当前的追踪器通常情况默认的追踪器设定为 nop。即在特殊文件中 current_tracer 中的 “无操作”，这意味着追踪目前是关闭的：$ pwd /sys/kernel/tracing $ sudo cat current_tracer nop 查看追踪输出在启用任何追踪功能之前，请你看一下保存追踪输出的文件。你可以用 cat 命令查看名为 trace 的文件的内容：# cat trace # tracer: nop # # entries-in-buffer/entries-written: 0/0 #P:8 # # _-----=> irqs-off # / _----=> need-resched # | / _---=> hardirq/softirq # || / _--=> preempt-depth # ||| / delay # TASK-PID CPU# |||| TIMESTAMP FUNCTION # | | | |||| | | 启用 function 追踪器你可以通过向 current_tracer 文件写入 function 来启用第一个追踪器 function（文件原本内容为 nop，意味着追踪是关闭的）。把这个操作看成是启用追踪的一种方式：$ pwd /sys/kernel/tracing $ sudo cat current_tracer nop $ echo function > current_tracer $ $ cat current_tracer function 查看 function 追踪器的更新追踪输出现在你已启用追踪，是时候查看输出了。如果你查看 trace 文件内容，你将会看到许多被连续写入的内容。我通过管道只展示了文件内容的前 20 行。根据左边输出的标题，你可以看到在某个 CPU 上运行的任务和进程 ID。根据右边输出的内容，你可以看到具体的内核函数和其父函数。中间显示了时间戳信息：# sudo cat trace | head -20 # tracer: function # # entries-in-buffer/entries-written: 409936/4276216 #P:8 # # _-----=> irqs-off # / _----=> need-resched # | / _---=> hardirq/softirq # || / _--=> preempt-depth # ||| / delay # TASK-PID CPU# |||| TIMESTAMP FUNCTION # | | | |||| | | <idle>-0 [000] d... 2088.841739: tsc_verify_tsc_adjust <-arch_cpu_idle_enter <idle>-0 [000] d... 2088.841739: local_touch_nmi <-do_idle <idle>-0 [000] d... 2088.841740: rcu_nocb_flush_deferred_wakeup <-do_idle <idle>-0 [000] d... 2088.841740: tick_check_broadcast_expired <-do_idle <idle>-0 [000] d... 2088.841740: cpuidle_get_cpu_driver <-do_idle <idle>-0 [000] d... 2088.841740: cpuidle_not_available <-do_idle <idle>-0 [000] d... 2088.841741: cpuidle_select <-do_idle <idle>-0 [000] d... 2088.841741: menu_select <-do_idle <idle>-0 [000] d... 2088.841741: cpuidle_governor_latency_req <-menu_select 请记住当追踪打开后，这意味着追踪结果会被一直连续写入直至你关闭追踪。关闭追踪关闭追踪是简单的。你只需要在 current_tracer 文件中用 nop 替换 function 追踪器即可：$ sudo cat current_tracer function $ sudo echo nop > current_tracer $ sudo cat current_tracer nop 启用 function\_graph 追踪器现在尝试第二个名为 function_graph 的追踪器。你可以使用和上面相同的步骤：在 current_tracer 文件中写入 function_graph：$ sudo echo function_graph > current_tracer $ sudo cat current_tracer function_graph function\_tracer 追踪器的追踪输出注意到目前 trace 文件的输出格式已经发生变化。现在，你可以看到 CPU ID 和内核函数的执行时长。接下来，一个花括号表示一个函数的开始，以及它内部调用了哪些其他函数：# cat trace | head -20 # tracer: function_graph # # CPU DURATION FUNCTION CALLS # | | | | | | | 6) | n_tty_write() { 6) | down_read() { 6) | __cond_resched() { 6) 0.341 us | rcu_all_qs(); 6) 1.057 us | } 6) 1.807 us | } 6) 0.402 us | process_echoes(); 6) | add_wait_queue() { 6) 0.391 us | _raw_spin_lock_irqsave(); 6) 0.359 us | _raw_spin_unlock_irqrestore(); 6) 1.757 us | } 6) 0.350 us | tty_hung_up_p(); 6) | mutex_lock() { 6) | __cond_resched() { 6) 0.404 us | rcu_all_qs(); 6) 1.067 us | } 启用追踪的设置来增加追踪的深度你可以使用下面的步骤来调整追踪器以看到更深层次的函数调用。完成之后，你可以查看 trace 文件的内容并发现输出变得更加详细了。为了文章的可读性，这个例子的输出被省略了：# cat max_graph_depth 0 # echo 1 > max_graph_depth ## or: # echo 2 > max_graph_depth # sudo cat trace 查找要追踪的函数上面的步骤足以让你开始追踪。但是它产生的输出内容是巨大的，当你想试图找到自己感兴趣的内容时，往往会很困难。通常你更希望能够只追踪特定的函数，而忽略其他函数。但如果你不知道它们确切的名称，你怎么知道要追踪哪些进程？有一个文件可以帮助你解决这个问题 —— available_filter_functions 文件提供了一个可供追踪的函数列表：$ sudo wc -l available_filter_functions 63165 available_filter_functions 查找一般的内核函数现在试着搜索一个你所知道的简单内核函数。用户空间由 malloc 函数用来分配内存，而内核由 kmalloc 函数，它提供类似的功能。下面是所有与 kmalloc 相关的函数：$ sudo grep kmalloc available_filter_functions debug_kmalloc mempool_kmalloc kmalloc_slab kmalloc_order kmalloc_order_trace kmalloc_fix_flags kmalloc_large_node __kmalloc __kmalloc_track_caller __kmalloc_node __kmalloc_node_track_caller [...] 查找内核模块或者驱动相关函数在 available_filter_functions 文件的输出中，你可以看到一些以括号内文字结尾的行，例如下面的例子中的 [kvm_intel]。这些函数与当前加载的内核模块 kvm_intel 有关。你可以运行 lsmod 命令来验证：$ sudo grep kvm available_filter_functions | tail __pi_post_block [kvm_intel] vmx_vcpu_pi_load [kvm_intel] vmx_vcpu_pi_put [kvm_intel] pi_pre_block [kvm_intel] pi_post_block [kvm_intel] pi_wakeup_handler [kvm_intel] pi_has_pending_interrupt [kvm_intel] pi_update_irte [kvm_intel] vmx_dump_dtsel [kvm_intel] vmx_dump_sel [kvm_intel] $ lsmod | grep -i kvm kvm_intel 335872 0 kvm 987136 1 kvm_intel irqbypass 16384 1 kvm 仅追踪特定的函数为了实现对特定函数或模式的追踪，你可以利用 set_ftrace_filter 文件来指定你要追踪上述输出中的哪些函数。这个文件也接受 * 模式，它可以扩展到包括具有给定模式的其他函数。作为一个例子，我在我的机器上使用 ext4 文件系统。我可以用下面的命令指定 ext4 的特定内核函数来追踪：# mount | grep home /dev/mapper/fedora-home on /home type ext4 (rw,relatime,seclabel) # pwd /sys/kernel/tracing # cat set_ftrace_filter #### all functions enabled #### $ $ echo ext4_* > set_ftrace_filter $ $ cat set_ftrace_filter ext4_has_free_clusters ext4_validate_block_bitmap ext4_get_group_number ext4_get_group_no_and_offset ext4_get_group_desc [...] 现在当你可以看到追踪输出时，你只能看到与内核函数有关的 ext4 函数，而你之前已经为其设置了一个过滤器。所有其他的输出都被忽略了：# cat trace |head -20 ## tracer: function # # entries-in-buffer/entries-written: 3871/3871 #P:8 # # _-----=> irqs-off # / _----=> need-resched # | / _---=> hardirq/softirq # || / _--=> preempt-depth # ||| / delay # TASK-PID CPU# |||| TIMESTAMP FUNCTION # | | | |||| | | cupsd-1066 [004] .... 3308.989545: ext4_file_getattr <-vfs_fstat cupsd-1066 [004] .... 3308.989547: ext4_getattr <-ext4_file_getattr cupsd-1066 [004] .... 3308.989552: ext4_file_getattr <-vfs_fstat cupsd-1066 [004] .... 3308.989553: ext4_getattr <-ext4_file_getattr cupsd-1066 [004] .... 3308.990097: ext4_file_open <-do_dentry_open cupsd-1066 [004] .... 3308.990111: ext4_file_getattr <-vfs_fstat cupsd-1066 [004] .... 3308.990111: ext4_getattr <-ext4_file_getattr cupsd-1066 [004] .... 3308.990122: ext4_llseek <-ksys_lseek cupsd-1066 [004] .... 3308.990130: ext4_file_read_iter <-new_sync_read 排除要被追踪的函数你并不总是知道你想追踪什么，但是，你肯定知道你不想追踪什么。因此，有一个 set_ftrace_notrace —— 请注意其中的 “no”。你可以在这个文件中写下你想要的模式，并启用追踪。这样除了所提到的模式外，任何其他东西都会被追踪到。这通常有助于删除那些使我们的输出变得混乱的普通功能：$ sudo cat set_ftrace_notrace #### no functions disabled #### 具有目标性的追踪到目前为止，你一直在追踪内核中发生的一切。但是，它无法帮助你追踪与某个特定命令有关的事件。为了达到这个目的，你可以按需打开和关闭跟踪，并且在它们之间，运行我们选择的命令，这样你就不会在跟踪输出中得到额外的输出。你可以通过向 tracing_on 写入 1 来启用跟踪，写 0 来关闭跟踪。# cat tracing_on 0 # echo 1 > tracing_on # cat tracing_on 1 ### Run some specific command that we wish to trace here ### # echo 0 > tracing_on # cat tracing_on 0 追踪特定的 PID如果你想追踪与正在运行的特定进程有关的活动，你可以将该 PID 写入一个名为 set_ftrace_pid 的文件，然后启用追踪。这样一来，追踪就只限于这个 PID，这在某些情况下是非常有帮助的。$ sudo echo $PID > set_ftrace_pid 总结ftrace 是一个了解 Linux 内核内部工作的很好方式。通过一些练习，你可以学会对 ftrace 进行调整以缩小搜索范围。要想更详细地了解 ftrace 和它的高级用法，请看 ftrace 的核心作者 Steven Rostedt 写的这些优秀文章。调试 Linux 内核，第一部分调试 Linux 内核，第二部分调试 Linux 内核，第三部分via: https://opensource.com/article/21/7/linux-kernel-ftrace作者：Gaurav Kamathe 选题：lujun9972 译者：萌新阿岩 校对：wxy本文由 LCTT 原创编译，Linux中国 荣誉推出
像查询数据库一样查询你的 Linux 操作系统信息
使用数据库查询操作轻松获取系统信息。Linux 提供了很多帮助用户收集主机操作系统信息的命令：列出文件或者目录的属性信息；查询安装的软件包、正在执行的命令、开机时启动的服务；或者了解系统的硬件。每个命令使用自己的输出格式列出系统的信息。你需要使用 grep、sed、awk 这样的工具过滤命令输出的结果，以便找到特定的信息。此外，很多这样的信息会频繁变动，导致系统状态的改变。将所有的信息格式化为一个数据库的 SQL 查询的输出进行查看将会十分有益。想象一下，你能够像查询具有类似名称的 SQL 数据库表一样查询 ps 和 rpm 命令的输出。幸运的是，有一个工具刚好实现了这个功能，而且功能更多：Osquery 是一个 开源的 “由 SQL 驱动的操作系统仪表、监控和分析框架”。许多处理安全、DevOps、合规性的应用，以及仓储管理管理（仅举几例）在内部依赖 Osquery 提供的核心功能。安装 OsqueryOsquery 适用于 Linux、macOS、Windows、FreeBSD。请按照 指南 为你的操作系统安装最新版本。（我会在下面的例子中使用 4.7.0 版本。）安装完成后，确保 Osquery 可以工作：$ rpm -qa | grep osquery osquery-4.7.0-1.linux.x86_64 $ $ osqueryi --version osqueryi version 4.7.0 $ Osquery 组件Osquery 有两个主要组件：osqueri 是一个交互式的 SQL 查询控制台，可以独立运行，不需要超级用户权限（除非要查询的表格需要访问权限）。osqueryd 像一个安装在主机的监控守护进程，可以定期调度查询操作执行，从底层架构收集信息。可以在不运行 osqueryd 的情况下执行 osqueri。另一个工具，osqueryctl，控制守护进程的启动、停止，并检查其状态。$ rpm -ql osquery-4.8.0-1.linux.x86_64 | grep bin /usr/bin/osqueryctl /usr/bin/osqueryd /usr/bin/osqueryi $ 使用 osqueryi 交互式命令提示符你和 Osquery 的交互与使用 SQL 数据库十分相似。事实上，osqueryi 是 SQList shell 的一个修改版。执行 osqueryi 命令进入交互式命令提示符 ，就可以执行 Osquery 的命令，通常以 . 开始：$ osqueryi Using a virtual database. Need help, type '.help' osquery> 要退出交互式命令提示符，执行 .quit 命令回到操作系统的命令提示符：osquery> osquery> .quit $ 找出可用的表如前所述，Osquery 像 SQL 查询一样输出数据，数据库中的信息通常保存在表中。但是如何在不知道表名的情况下查询这些表呢？你可以运行 .tables 命令列出所有可以查询的表。如果你是一个 Linux 长期用户或者一个系统管理员 ，就会对表名十分熟悉，因为你一直在使用操作系统命令获取同样的信息：osquery> .tables => acpi_tables => apparmor_events => apparmor_profiles => apt_sources <<裁剪>> => arp_cache => user_ssh_keys => users => yara => yara_events => ycloud_instance_metadata => yum_sources osquery> 检查各个表的模式知道表名后，可以查看每个表提供的信息。既然 ps 命令经常用于获取进程信息，就以 processes 为例。执行 .schema 命令加上表名查看表中保存的信息。如果要验证命令返回的结果，可以快速执行 ps -ef 或 ps aux，对比命令的输出和表中的内容：osquery> .schema processes CREATE TABLE processes(`pid` BIGINT, `name` TEXT, `path` TEXT, `cmdline` TEXT, `state` TEXT, `cwd` TEXT, `root` TEXT, `uid` BIGINT, `gid` BIGINT, `euid` BIGINT, `egid` BIGINT, `suid` BIGINT, `sgid` BIGINT, `on_disk` INTEGER, `wired_size` BIGINT, `resident_size` BIGINT, `total_size` BIGINT, `user_time` BIGINT, `system_time` BIGINT, `disk_bytes_read` BIGINT, `disk_bytes_written` BIGINT, `start_time` BIGINT, `parent` BIGINT, `pgroup` BIGINT, `threads` INTEGER, `nice` INTEGER, `is_elevated_token` INTEGER HIDDEN, `elapsed_time` BIGINT HIDDEN, `handle_count` BIGINT HIDDEN, `percent_processor_time` BIGINT HIDDEN, `upid` BIGINT HIDDEN, `uppid` BIGINT HIDDEN, `cpu_type` INTEGER HIDDEN, `cpu_subtype` INTEGER HIDDEN, `phys_footprint` BIGINT HIDDEN, PRIMARY KEY (`pid`)) WITHOUT ROWID; osquery> 要进一步确认，可以使用下面的命令查看 RPM 包的结构信息，然后与操作系统命令 rpm -qa 和 rpm -qi 的输出比较：osquery> osquery> .schema rpm_packages CREATE TABLE rpm_packages(`name` TEXT, `version` TEXT, `release` TEXT, `source` TEXT, `size` BIGINT, `sha1` TEXT, `arch` TEXT, `epoch` INTEGER, `install_time` INTEGER, `vendor` TEXT, `package_group` TEXT, `pid_with_namespace` INTEGER HIDDEN, `mount_namespace_id` TEXT HIDDEN, PRIMARY KEY (`name`, `version`, `release`, `arch`, `epoch`, `pid_with_namespace`)) WITHOUT ROWID; osquery> 从 Osquery 的 表格文档 获取更多信息。使用 PRAGMA 命令或许模式信息对你来说太难看懂，还有另一种途径能够以详细的表格格式打印表中的信息：PRAGMA 命令。例如，我想通过 PRAGMA 用一种易于理解的格式查看 rpm_packages 表的信息：osquery> PRAGMA table_info(rpm_packages); 这种表格式信息的一个好处是你可以关注想要查询的字段，查看命令提供的类型信息：osquery> PRAGMA table_info(users); +-----+-------------+--------+---------+------------+----+ | cid | name | type | notnull | dflt_value | pk | +-----+-------------+--------+---------+------------+----+ | 0 | uid | BIGINT | 1 | | 1 | | 1 | gid | BIGINT | 0 | | 0 | | 2 | uid_signed | BIGINT | 0 | | 0 | | 3 | gid_signed | BIGINT | 0 | | 0 | | 4 | username | TEXT | 1 | | 2 | | 5 | description | TEXT | 0 | | 0 | | 6 | directory | TEXT | 0 | | 0 | | 7 | shell | TEXT | 0 | | 0 | | 8 | uuid | TEXT | 1 | | 3 | +-----+-------------+--------+---------+------------+----+ osquery> 进行你的第一次查询在你从表、模式、条目中获取到所有进行查询所需要的信息后，进行你的第一次 SQL 查询查看其中的信息。下面的查询返回系统中的用户和每个用户的用户 ID、组 ID、主目录和默认的命令行解释器。Linux 用户通过查看 /etc/passwd 文件的内容并执行 grep、sed、awk 命令获取同样的信息。osquery> osquery> select uid,gid,directory,shell,uuid FROM users LIMIT 7; +-----+-----+----------------+----------------+------+ | uid | gid | directory | shell | uuid | +-----+-----+----------------+----------------+------+ | 0 | 0 | /root | /bin/bash | | | 1 | 1 | /bin | /sbin/nologin | | | 2 | 2 | /sbin | /sbin/nologin | | | 3 | 4 | /var/adm | /sbin/nologin | | | 4 | 7 | /var/spool/lpd | /sbin/nologin | | | 5 | 0 | /sbin | /bin/sync | | | 6 | 0 | /sbin | /sbin/shutdown | | +-----+-----+----------------+----------------+------+ osquery> 不进入交互模式的查询如果你想要在不进入 osqueri 交互模式的情况下进行查询，该怎么办？要用查询操作写命令行解释器脚本，这种方式可能十分有用。这种情况下，可以直接从 Bash 解释器 echo SQL 查询，通过管道输出到 osqueri ：$ echo "select uid,gid,directory,shell,uuid FROM users LIMIT 7;" | osqueryi +-----+-----+----------------+----------------+------+ | uid | gid | directory | shell | uuid | +-----+-----+----------------+----------------+------+ | 0 | 0 | /root | /bin/bash | | | 1 | 1 | /bin | /sbin/nologin | | | 2 | 2 | /sbin | /sbin/nologin | | | 3 | 4 | /var/adm | /sbin/nologin | | | 4 | 7 | /var/spool/lpd | /sbin/nologin | | | 5 | 0 | /sbin | /bin/sync | | | 6 | 0 | /sbin | /sbin/shutdown | | +-----+-----+----------------+----------------+------+ $ 获悉系统启动时开始的服务Osquery 还可以列出系统启动时开始的所有服务。例如，可以查询 startup_items 表获取启动时开始的前五项服务的名称、状态和路径：osquery> SELECT name,type,status,path FROM startup_items LIMIT 5; name = README type = Startup Item status = enabled path = /etc/rc.d/init.d/README name = anamon type = Startup Item status = enabled path = /etc/rc.d/init.d/anamon name = functions type = Startup Item status = enabled path = /etc/rc.d/init.d/functions name = osqueryd type = Startup Item status = enabled path = /etc/rc.d/init.d/osqueryd name = AT-SPI D-Bus Bus type = Startup Item status = enabled path = /usr/libexec/at-spi-bus-launcher --launch-immediately osquery> 查阅二进制文件的 ELF 信息假如你想要弄清 ls 二进制文件的更多细节，通常会通过 readelf -h 命令，加上 ls 命令的路径。查询 Osquery 的 elf_info 表你可以得到同样的信息：osquery> SELECT * FROM elf_info WHERE path="/bin/ls"; class = 64 abi = sysv abi_version = 0 type = dyn machine = 62 version = 1 entry = 24064 flags = 0 path = /bin/ls osquery> 现在你应该初步了解如何使用 osqueri 查询自己想要的信息。然而，这些信息保存在数量巨大的表中；我查询过的一个系统中，有 156 个不同的表，这个数字可能是十分惊人的：$ echo ".tables" | osqueryi | wc -l 156 $ 要让事情变得更容易，可以从这些表开始获取你的 Linux 系统的信息：系统信息表：osquery> select * from system_info; 系统限制信息：osquery> select * from ulimit_info; 由各种进程打开的文件：osquery> select * from process_open_files; 系统上开放的端口：osquery> select * from listening_ports; 运行中的进程信息：osquery> select * from processes; 已安装的包信息：osquery> select * from rpm_packages; 用户登录信息：osquery> select * from last; 系统日志信息：osquery> select * from syslog_events; 了解更多Osquery 是一个强大的工具，提供了许多可以用于解决各种使用案例的主机信息。你可以阅读 文档 了解更多 Osquery 的信息。via: https://opensource.com/article/21/6/osquery-linux作者：Gaurav Kamathe 选题：lujun9972 译者：YungeG 校对：wxy本文由 LCTT 原创编译，Linux中国 荣誉推出
利用 Volatility 找出应用程序、网络连接、内核模块、文件等方面的情况。计算机的操作系统和应用使用主内存（RAM）来执行不同的任务。这种易失性内存包含大量关于运行应用、网络连接、内核模块、打开的文件以及几乎所有其他的内容信息，但这些信息每次计算机重启的时候都会被清除。 内存取证 Memory forensics 是一种从内存中找到和抽取这些有价值的信息的方式。Volatility 是一种使用插件来处理这类信息的开源工具。但是，存在一个问题：在你处理这些信息前，必须将物理内存转储到一个文件中，而 Volatility 没有这种能力。因此，这篇文章分为两部分：第一部分是处理获取物理内存并将其转储到一个文件中。第二部分使用 Volatility 从这个内存转储中读取并处理这些信息。我在本教程中使用了以下测试系统，不过它可以在任何 Linux 发行版上工作：$ cat /etc/redhat-release Red Hat Enterprise Linux release 8.3 (Ootpa) $ $ uname -r 4.18.0-240.el8.x86_64 $ 注意事项： 部分 1 涉及到编译和加载一个内核模块。不要担心：它并不像听起来那么困难。一些指南：按照以下的步骤。不要在生产系统或你的主要计算机上尝试任何这些步骤。始终使用测试的虚拟机（VM）来尝试，直到你熟悉使用这些工具并理解它们的工作原理为止。安装需要的包在开始之前安装必要的工具。如果你经常使用基于 Debian 的发行版，可以使用 apt-get 命令。这些包大多数提供了需要的内核信息和工具来编译代码：$ yum install kernel-headers kernel-devel gcc elfutils-libelf-devel make git libdwarf-tools python2-devel.x86_64-y 部分 1：使用 LiME 获取内存并将其转储到一个文件中在开始分析内存之前，你需要一个内存转储供你使用。在实际的取证活动中，这可能来自一个被破坏或者被入侵的系统。这些信息通常会被收集和存储来分析入侵是如何发生的及其影响。由于你可能没有可用的内存转储，你可以获取你的测试 VM 的内存转储，并使用它来执行内存取证。 Linux 内存提取器 Linux Memory Extractor （LiME）是一个在 Linux 系统上获取内存很常用的工具。使用以下命令获得 LiME：$ git clone https://github.com/504ensicsLabs/LiME.git $ $ cd LiME/src/ $ $ ls deflate.c disk.c hash.c lime.h main.c Makefile Makefile.sample tcp.c $ 构建 LiME 内核模块在 src 文件夹下运行 make 命令。这会创建一个以 .ko 为扩展名的内核模块。理想情况下，在 make 结束时，lime.ko 文件会使用格式 lime-<your-kernel-version>.ko 被重命名。$ make make -C /lib/modules/4.18.0-240.el8.x86_64/build M="/root/LiME/src" modules make[1]: Entering directory '/usr/src/kernels/4.18.0-240.el8.x86_64' << 删节 >> make[1]: Leaving directory '/usr/src/kernels/4.18.0-240.el8.x86_64' strip --strip-unneeded lime.ko mv lime.ko lime-4.18.0-240.el8.x86_64.ko $ $ $ ls -l lime-4.18.0-240.el8.x86_64.ko -rw-r--r--. 1 root root 25696 Apr 17 14:45 lime-4.18.0-240.el8.x86_64.ko $ $ file lime-4.18.0-240.el8.x86_64.ko lime-4.18.0-240.el8.x86_64.ko: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), BuildID[sha1]=1d0b5cf932389000d960a7e6b57c428b8e46c9cf, not stripped $ 加载LiME 内核模块现在是时候加载内核模块来获取系统内存了。insmod 命令会帮助加载内核模块；模块一旦被加载，会在你的系统上读取主内存（RAM）并且将内存的内容转储到命令行所提供的 path 目录下的文件中。另一个重要的参数是 format；保持 lime 的格式，如下所示。在插入内核模块之后，使用 lsmod 命令验证它是否真的被加载。$ lsmod | grep lime $ $ insmod ./lime-4.18.0-240.el8.x86_64.ko "path=../RHEL8.3_64bit.mem format=lime" $ $ lsmod | grep lime lime 16384 0 $ 你应该看到给 path 命令的文件已经创建好了，而且文件大小与你系统的物理内存（RAM）大小相同（并不奇怪）。一旦你有了内存转储，你就可以使用 rmmod 命令删除该内核模块：$ $ ls -l ~/LiME/RHEL8.3_64bit.mem -r--r--r--. 1 root root 4294544480 Apr 17 14:47 /root/LiME/RHEL8.3_64bit.mem $ $ du -sh ~/LiME/RHEL8.3_64bit.mem 4.0G /root/LiME/RHEL8.3_64bit.mem $ $ free -m total used free shared buff/cache available Mem: 3736 220 366 8 3149 3259 Swap: 4059 8 4051 $ $ rmmod lime $ $ lsmod | grep lime $ 内存转储中是什么？这个内存转储文件只是原始数据，就像使用 file 命令可以看到的一样。你不可能通过手动去理解它；是的，在这里边有一些 ASCII 字符，但是你无法用编辑器打开这个文件并把它读出来。hexdump 的输出显示，最初的几个字节是 EmiL；这是因为你的请求格式在上面的命令行中是 lime：$ file ~/LiME/RHEL8.3_64bit.mem /root/LiME/RHEL8.3_64bit.mem: data $ $ hexdump -C ~/LiME/RHEL8.3_64bit.mem | head 00000000 45 4d 69 4c 01 00 00 00 00 10 00 00 00 00 00 00 |EMiL............| 00000010 ff fb 09 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000020 b8 fe 4c cd 21 44 00 32 20 00 00 2a 2a 2a 2a 2a |..L.!D.2 ..*****| 00000030 2a 2a 2a 2a 2a 2a 2a 2a 2a 2a 2a 2a 2a 2a 2a 2a |****************| 00000040 2a 2a 2a 2a 2a 2a 2a 2a 2a 2a 2a 2a 2a 20 00 20 |************* . | 00000050 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| * 00000080 00 00 00 00 00 00 00 00 00 00 00 00 70 78 65 6c |............pxel| 00000090 69 6e 75 78 2e 30 00 00 00 00 00 00 00 00 00 00 |inux.0..........| 000000a0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| $ 部分 2：获得 Volatility 并使用它来分析你的内存转储现在你有了要分析的示例内存转储，使用下面的命令获取 Volatility 软件。Volatility 已经用 Python 3 重写了，但是本教程使用的是用 Python 2 写的原始的 Volatility 包。如果你想用 Volatility 3 进行实验，可以从合适的 Git 仓库下载它，并在以下命令中使用 Python 3 而不是 Python 2：$ git clone https://github.com/volatilityfoundation/volatility.git $ $ cd volatility/ $ $ ls AUTHORS.txt contrib LEGAL.txt Makefile PKG-INFO pyinstaller.spec resources tools vol.py CHANGELOG.txt CREDITS.txt LICENSE.txt MANIFEST.in pyinstaller README.txt setup.py volatility $ Volatility 使用两个 Python 库来实现某些功能，所以使用以下命令来安装它们。否则，在你运行 Volatility 工具时，你可能看到一些导入错误；你可以忽略它们，除非你正在运行的插件需要这些库；这种情况下，工具将会报错：$ pip2 install pycrypto $ pip2 install distorm3 列出 Volatility 的 Linux 配置文件你将要运行的第一个 Volatility 命令列出了可用的 Linux 配置文件，运行 Volatility 命令的主要入口点是 vol.py 脚本。使用 Python 2 解释器调用它并提供 --info 选项。为了缩小输出，查找以 Linux 开头的字符串。正如你所看到的，并没有很多 Linux 配置文件被列出：$ python2 vol.py --info | grep ^Linux Volatility Foundation Volatility Framework 2.6.1 LinuxAMD64PagedMemory - Linux-specific AMD 64-bit address space. $ 构建你自己的 Linux 配置文件Linux 发行版是多种多样的，并且是为不同架构而构建的。这就是为什么配置文件是必要的 —— Volatility 在提取信息前必须知道内存转储是从哪个系统和架构获得的。有一些 Volatility 命令可以找到这些信息；但是这个方法很费时。为了加快速度，可以使用以下命令构建一个自定义的 Linux 配置文件：移动到 Volatility 仓库的 tools/linux目录下，运行 make 命令：$ cd tools/linux/ $ $ pwd /root/volatility/tools/linux $ $ ls kcore Makefile Makefile.enterprise module.c $ $ make make -C //lib/modules/4.18.0-240.el8.x86_64/build CONFIG_DEBUG_INFO=y M="/root/volatility/tools/linux" modules make[1]: Entering directory '/usr/src/kernels/4.18.0-240.el8.x86_64' << 删节 >> make[1]: Leaving directory '/usr/src/kernels/4.18.0-240.el8.x86_64' $ 你应该看到一个新的 module.dwarf 文件。你也需要 /boot 目录下的 System.map 文件，因为它包含了所有与当前运行的内核相关的符号：$ ls kcore Makefile Makefile.enterprise module.c module.dwarf $ $ ls -l module.dwarf -rw-r--r--. 1 root root 3987904 Apr 17 15:17 module.dwarf $ $ ls -l /boot/System.map-4.18.0-240.el8.x86_64 -rw-------. 1 root root 4032815 Sep 23 2020 /boot/System.map-4.18.0-240.el8.x86_64 $ $ 要创建一个自定义配置文件，移动回到 Volatility 目录并且运行下面的命令。第一个参数提供了一个自定义 .zip 文件，文件名是你自己命名的。我经常使用操作系统和内核版本来命名。下一个参数是前边创建的 module.dwarf 文件，最后一个参数是 /boot 目录下的 System.map 文件：$ $ cd volatility/ $ $ zip volatility/plugins/overlays/linux/Redhat8.3_4.18.0-240.zip tools/linux/module.dwarf /boot/System.map-4.18.0-240.el8.x86_64 adding: tools/linux/module.dwarf (deflated 91%) adding: boot/System.map-4.18.0-240.el8.x86_64 (deflated 79%) $ 现在自定义配置文件就准备好了，所以在前边给出的位置检查一下 .zip 文件是否被创建好。如果你想知道 Volatility 是否检测到这个自定义配置文件，再一次运行 --info 命令。现在，你应该可以在下边的列出的内容中看到新的配置文件：$ $ ls -l volatility/plugins/overlays/linux/Redhat8.3_4.18.0-240.zip -rw-r--r--. 1 root root 1190360 Apr 17 15:20 volatility/plugins/overlays/linux/Redhat8.3_4.18.0-240.zip $ $ $ python2 vol.py --info | grep Redhat Volatility Foundation Volatility Framework 2.6.1 LinuxRedhat8_3_4_18_0-240x64 - A Profile for Linux Redhat8.3_4.18.0-240 x64 $ $ 开始使用 Volatility现在你已经准备好去做一些真正的内存取证了。记住，Volatility 是由自定义的插件组成的，你可以针对内存转储来获得信息。命令的通用格式是：python2 vol.py -f <memory-dump-file-taken-by-Lime> <plugin-name> --profile=<name-of-our-custom-profile> 有了这些信息，运行 linux_banner 插件来看看你是否可从内存转储中识别正确的发行版信息：$ python2 vol.py -f ~/LiME/RHEL8.3_64bit.mem linux_banner --profile=LinuxRedhat8_3_4_18_0-240x64 Volatility Foundation Volatility Framework 2.6.1 Linux version 4.18.0-240.el8.x86_64 ([mockbuild@vm09.test.com][4]) (gcc version 8.3.1 20191121 (Red Hat 8.3.1-5) (GCC)) #1 SMP Wed Sep 23 05:13:10 EDT 2020 $ 找到 Linux 插件到现在都很顺利，所以现在你可能对如何找到所有 Linux 插件的名字比较好奇。有一个简单的技巧：运行 --info 命令并抓取 linux_ 字符串。有各种各样的插件可用于不同的用途。这里列出一部分：$ python2 vol.py --info | grep linux_ Volatility Foundation Volatility Framework 2.6.1 linux_apihooks - Checks for userland apihooks linux_arp - Print the ARP table linux_aslr_shift - Automatically detect the Linux ASLR shift << 删节 >> linux_banner - Prints the Linux banner information linux_vma_cache - Gather VMAs from the vm_area_struct cache linux_volshell - Shell in the memory image linux_yarascan - A shell in the Linux memory image $ 使用 linux_psaux 插件检查内存转储时系统上正在运行哪些进程。注意列表中的最后一个命令：它是你在转储之前运行的 insmod 命令。$ python2 vol.py -f ~/LiME/RHEL8.3_64bit.mem linux_psaux --profile=LinuxRedhat8_3_4_18_0-240x64 Volatility Foundation Volatility Framework 2.6.1 Pid Uid Gid Arguments 1 0 0 /usr/lib/systemd/systemd --switched-root --system --deserialize 18 2 0 0 [kthreadd] 3 0 0 [rcu_gp] 4 0 0 [rcu_par_gp] 861 0 0 /usr/libexec/platform-python -Es /usr/sbin/tuned -l -P 869 0 0 /usr/bin/rhsmcertd 875 0 0 /usr/libexec/sssd/sssd_be --domain implicit_files --uid 0 --gid 0 --logger=files 878 0 0 /usr/libexec/sssd/sssd_nss --uid 0 --gid 0 --logger=files << 删节 >> 11064 89 89 qmgr -l -t unix -u 227148 0 0 [kworker/0:0] 227298 0 0 -bash 227374 0 0 [kworker/u2:1] 227375 0 0 [kworker/0:2] 227884 0 0 [kworker/0:3] 228573 0 0 insmod ./lime-4.18.0-240.el8.x86_64.ko path=../RHEL8.3_64bit.mem format=lime 228576 0 0 $ 想要知道系统的网络状态吗？运行 linux_netstat 插件来找到在内存转储期间网络连接的状态：$ python2 vol.py -f ~/LiME/RHEL8.3_64bit.mem linux_netstat --profile=LinuxRedhat8_3_4_18_0-240x64 Volatility Foundation Volatility Framework 2.6.1 UNIX 18113 systemd/1 /run/systemd/private UNIX 11411 systemd/1 /run/systemd/notify UNIX 11413 systemd/1 /run/systemd/cgroups-agent UNIX 11415 systemd/1 UNIX 11416 systemd/1 << 删节 >> $ 接下来，使用 linux_mount 插件来看在内存转储期间哪些文件系统被挂载：$ python2 vol.py -f ~/LiME/RHEL8.3_64bit.mem linux_mount --profile=LinuxRedhat8_3_4_18_0-240x64 Volatility Foundation Volatility Framework 2.6.1 tmpfs /sys/fs/cgroup tmpfs ro,nosuid,nodev,noexec cgroup /sys/fs/cgroup/pids cgroup rw,relatime,nosuid,nodev,noexec systemd-1 /proc/sys/fs/binfmt_misc autofs rw,relatime sunrpc /var/lib/nfs/rpc_pipefs rpc_pipefs rw,relatime /dev/mapper/rhel_kvm--03--guest11-root / xfs rw,relatime tmpfs /dev/shm tmpfs rw,nosuid,nodev selinuxfs /sys/fs/selinux selinuxfs rw,relatime << 删节 >> cgroup /sys/fs/cgroup/net_cls,net_prio cgroup rw,relatime,nosuid,nodev,noexec cgroup /sys/fs/cgroup/cpu,cpuacct cgroup rw,relatime,nosuid,nodev,noexec bpf /sys/fs/bpf bpf rw,relatime,nosuid,nodev,noexec cgroup /sys/fs/cgroup/memory cgroup ro,relatime,nosuid,nodev,noexec cgroup /sys/fs/cgroup/cpuset cgroup rw,relatime,nosuid,nodev,noexec mqueue /dev/mqueue mqueue rw,relatime $ 好奇哪些内核模块被加载了吗？Volatility 也为这个提供了一个插件 linux_lsmod：$ python2 vol.py -f ~/LiME/RHEL8.3_64bit.mem linux_lsmod --profile=LinuxRedhat8_3_4_18_0-240x64 Volatility Foundation Volatility Framework 2.6.1 ffffffffc0535040 lime 20480 ffffffffc0530540 binfmt_misc 20480 ffffffffc05e8040 sunrpc 479232 << 删节 >> ffffffffc04f9540 nfit 65536 ffffffffc0266280 dm_mirror 28672 ffffffffc025e040 dm_region_hash 20480 ffffffffc0258180 dm_log 20480 ffffffffc024bbc0 dm_mod 151552 $ 想知道哪些文件被哪些进程打开了吗？使用 linux_bash 插件可以列出这些信息：$ python2 vol.py -f ~/LiME/RHEL8.3_64bit.mem linux_bash --profile=LinuxRedhat8_3_4_18_0-240x64 -v Volatility Foundation Volatility Framework 2.6.1 Pid Name Command Time Command -------- -------------------- ------------------------------ ------- 227221 bash 2021-04-17 18:38:24 UTC+0000 lsmod 227221 bash 2021-04-17 18:38:24 UTC+0000 rm -f .log 227221 bash 2021-04-17 18:38:24 UTC+0000 ls -l /etc/zzz 227221 bash 2021-04-17 18:38:24 UTC+0000 cat ~/.vimrc 227221 bash 2021-04-17 18:38:24 UTC+0000 ls 227221 bash 2021-04-17 18:38:24 UTC+0000 cat /proc/817/cwd 227221 bash 2021-04-17 18:38:24 UTC+0000 ls -l /proc/817/cwd 227221 bash 2021-04-17 18:38:24 UTC+0000 ls /proc/817/ << 删节 >> 227298 bash 2021-04-17 18:40:30 UTC+0000 gcc prt.c 227298 bash 2021-04-17 18:40:30 UTC+0000 ls 227298 bash 2021-04-17 18:40:30 UTC+0000 ./a.out 227298 bash 2021-04-17 18:40:30 UTC+0000 vim prt.c 227298 bash 2021-04-17 18:40:30 UTC+0000 gcc prt.c 227298 bash 2021-04-17 18:40:30 UTC+0000 ./a.out 227298 bash 2021-04-17 18:40:30 UTC+0000 ls $ 想知道哪些文件被哪些进程打开了吗？使用 linux_lsof 插件可以列出这些信息：$ python2 vol.py -f ~/LiME/RHEL8.3_64bit.mem linux_lsof --profile=LinuxRedhat8_3_4_18_0-240x64 Volatility Foundation Volatility Framework 2.6.1 Offset Name Pid FD Path ------------------ ------------------------------ -------- -------- ---- 0xffff9c83fb1e9f40 rsyslogd 71194 0 /dev/null 0xffff9c83fb1e9f40 rsyslogd 71194 1 /dev/null 0xffff9c83fb1e9f40 rsyslogd 71194 2 /dev/null 0xffff9c83fb1e9f40 rsyslogd 71194 3 /dev/urandom 0xffff9c83fb1e9f40 rsyslogd 71194 4 socket:[83565] 0xffff9c83fb1e9f40 rsyslogd 71194 5 /var/log/messages 0xffff9c83fb1e9f40 rsyslogd 71194 6 anon_inode:[9063] 0xffff9c83fb1e9f40 rsyslogd 71194 7 /var/log/secure << 删节 >> 0xffff9c8365761f40 insmod 228573 0 /dev/pts/0 0xffff9c8365761f40 insmod 228573 1 /dev/pts/0 0xffff9c8365761f40 insmod 228573 2 /dev/pts/0 0xffff9c8365761f40 insmod 228573 3 /root/LiME/src/lime-4.18.0-240.el8.x86_64.ko $ 访问 Linux 插件脚本位置通过读取内存转储和处理这些信息，你可以获得更多的信息。如果你会 Python，并且好奇这些信息是如何被处理的，可以到存储所有插件的目录，选择一个你感兴趣的，并看看 Volatility 是如何获得这些信息的：$ ls volatility/plugins/linux/ apihooks.py common.py kernel_opened_files.py malfind.py psaux.py apihooks.pyc common.pyc kernel_opened_files.pyc malfind.pyc psaux.pyc arp.py cpuinfo.py keyboard_notifiers.py mount_cache.py psenv.py arp.pyc cpuinfo.pyc keyboard_notifiers.pyc mount_cache.pyc psenv.pyc aslr_shift.py dentry_cache.py ld_env.py mount.py pslist_cache.py aslr_shift.pyc dentry_cache.pyc ld_env.pyc mount.pyc pslist_cache.pyc << 删节 >> check_syscall_arm.py __init__.py lsmod.py proc_maps.py tty_check.py check_syscall_arm.pyc __init__.pyc lsmod.pyc proc_maps.pyc tty_check.pyc check_syscall.py iomem.py lsof.py proc_maps_rb.py vma_cache.py check_syscall.pyc iomem.pyc lsof.pyc proc_maps_rb.pyc vma_cache.pyc $ $ 我喜欢 Volatility 的理由是他提供了许多安全插件。这些信息很难手动获取：linux_hidden_modules - Carves memory to find hidden kernel modules linux_malfind - Looks for suspicious process mappings linux_truecrypt_passphrase - Recovers cached Truecrypt passphrases Volatility 也允许你在内存转储中打开一个 shell，所以你可以运行 shell 命令来代替上面所有命令，并获得相同的信息：$ python2 vol.py -f ~/LiME/RHEL8.3_64bit.mem linux_volshell --profile=LinuxRedhat8_3_4_18_0-240x64 -v Volatility Foundation Volatility Framework 2.6.1 Current context: process systemd, pid=1 DTB=0x1042dc000 Welcome to volshell! Current memory image is: file:///root/LiME/RHEL8.3_64bit.mem To get help, type 'hh()' >>> >>> sc() Current context: process systemd, pid=1 DTB=0x1042dc000 >>> 接下来的步骤内存转储是了解 Linux 内部情况的好方法。试一试 Volatility 的所有插件，并详细研究它们的输出。然后思考这些信息如何能够帮助你识别入侵或安全问题。深入了解这些插件的工作原理，甚至尝试改进它们。如果你没有找到你想做的事情的插件，那就写一个并提交给 Volatility，这样其他人也可以使用它。via: https://opensource.com/article/21/4/linux-memory-forensics作者：Gaurav Kamathe 选题：lujun9972 译者：RiaXu 校对：wxy本文由 LCTT 原创编译，Linux中国 荣誉推出