活动与内存
按 Alt-M 或选择 命令 → 进程监视器 打开进程监视器。它由内置的“系统”插件提供,该插件默认启用,可在 选项 → 插件 中关闭。进程列表占据当前窗格,另一个窗格显示所选进程的内存概览。按 Enter 或双击,会在监视器上方的窗口中打开该进程,一侧是内存映射,另一侧是所选内存区域的字节预览。Esc 关闭该窗口,Tab 在两侧之间切换。Alt-W 或 [+] 按钮最大化当前焦点所在的窗格,[=] 恢复分栏,[-] 隐藏监视器。监视器打开期间每秒采样一次,关闭后即停止采样。进行内存检查时采样会暂停,结果返回后再恢复。
列表显示 CPU、驻留内存、内存增长以及磁盘读写速率。CPU 100% 表示一个核心满负荷运行。多个进程共享的驻留页面会分别计入每个进程,因此把所有进程的 RAM 加起来并不等于系统总量。CPU 和磁盘速率从第二次采样开始显示。
CPU 是最近几秒的平均值,较早的占用会逐渐淡出,与 top 的做法相同,因此即使进程以短时突发的方式运行,按 CPU 排序的列表也能保持稳定。概览和监视器顶部的系统 CPU 也按同样方式平均。H 会隐藏这段时间内没有使用 CPU 的进程,筛选行会显示隐藏了多少个。进程空闲后仍会留在列表中,直到其平均值降到 0.0。在进程树中,繁忙进程的各级父进程会与它一起保留。再次按 H 可重新显示全部进程。
| 按键 | 操作 |
|---|---|
| Enter | 打开进程的内存映射 / 移到内存区域预览 |
/ |
按名称或 PID 筛选;↑↓ 仍可移动,Enter 保留筛选条件,Esc 清除筛选条件 |
| C / M / G / N / P | 按 CPU / RAM / 增长 / 名称 / PID 排序;再按一次则反向排序 |
| I / O | 按读取 / 写入速率排序 |
| T | 显示或隐藏进程树 |
| H | 隐藏或显示最近几秒内未使用 CPU 的进程 |
| Left / Right | 在进程树中折叠或展开所选进程的分支 |
| K | 发送信号或更改优先级,在进程窗口中也可以 |
| E | 扫描所有进程中的高熵字节 |
| Alt-N | 在网络屏幕上显示所选进程的流量 |
| Alt-O | 在列表旁打开所选进程的程序所在的文件夹 |
| Space | 暂停或恢复采样 |
| R | 刷新当前视图 |
| Esc | 返回;在进程列表中则关闭监视器或回到网络屏幕 |
Alt-O 会在另一个窗格中打开所选进程的程序所在的文件夹,把光标放在该程序上,并让文件获得按键。列表会保留筛选条件、排序和所选进程。Tab 在列表和文件之间切换按键,在另一行上按 Alt-O 会把文件移到那里。在这些文件中可以查看、编辑、删除、压缩和解压,以及新建文件夹。复制和移动需要另一个面板,因此要等监视器关闭后才能进行。在列表中按 Esc 或按 Alt-M 会恢复概览,再按一次 Alt-M 关闭监视器。在打开的文件和内存映射中,Alt-O 会显示所选的打开文件或该区域映射的文件。如果程序在运行期间被删除,会打开它所在的文件夹并给出提示。如果文件不在此计算机上它的文件夹中(在容器内运行的程序就是这样),也会给出提示。警报、启动项和 Docker 视图的用法相同。
每个屏幕关闭时都会保留筛选条件,再次打开时会重新显示。要清除筛选条件,请先按 / 再按 Esc。
单击列标题可排序,双击进程可打开其内存映射。概览显示当前驻留内存、观测到的最高值、自开始监视以来的变化、CPU、虚拟地址空间,以及最近 60 秒驻留内存的图表。读取内存映射后,如果系统提供相应数据,还会显示内存占用、交换、私有和共享的总量。在内存窗格中按 Enter 可打开内存映射。内存映射只在你请求时读取;进程更改其映射后,按 R 可重新读取。
图表下方的 进程树 列出所选进程的各级父进程、该进程本身以及它启动的所有进程,每个进程缩进在启动它的进程之下,并显示其驻留内存。放不下时,最后一行会说明还有多少个进程未显示。
在按 T 显示的进程树中,每个进程列在启动它的进程之下,▾ 表示其下还有其他进程。Left 折叠所选进程的分支;如果所选进程之下没有进程,Left 会折叠它所在的分支,并选中启动它的进程。Right 展开已折叠的分支;在已展开的分支上,则移到该分支的第一个进程。折叠的进程显示 ▸ 以及它隐藏了多少个进程。分支会保持折叠,直到其进程结束。应用筛选时,列表不分层显示。
信号和优先级
Section titled “信号和优先级”按 K 可为所选进程打开 信号或优先级,在进程列表和显示内存映射或打开的文件的进程窗口中均可。按某个选项的字母,或移到该选项后按 Enter。Esc 关闭对话框,不发送任何内容。
| 按键 | 选项 | 作用 |
|---|---|---|
| T | 终止 | 发送 TERM,请求进程退出 |
| I | 中断 | 发送 INT,与在其终端中按 Ctrl-C 相同 |
| H | 挂断 | 发送 HUP;许多服务会重新读取设置 |
| K | 强制结束 | 发送 KILL,立即结束进程,不给它清理的机会 |
| S | 暂停 | 发送 STOP,让进程暂停 |
| C | 继续 | 发送 CONT,让已暂停的进程继续运行 |
| L | 降低优先级 | 将其 nice 值提高 5,最高到 19,系统繁忙时它会更晚获得 CPU |
| R | 提高优先级 | 将其 nice 值降低 5,最低到 -20 |
发送之前,Borg 会确认该 PID 仍属于你选择的进程,且启动时间相同,因此复用该 PID 的新进程不会受到影响。状态行会确认发送了什么,列表也会立即重新采样。进程暂停时,概览会注明这一点;nice 值不为 0 时,概览会显示其 Nice 值。
你可以向自己的进程发送信号,也可以降低它们的优先级。其余操作会被系统拒绝:其他用户的进程,以及只有管理员才能执行的提高优先级。此时 Borg 会提供 A 管理员,通过管理员权限中介绍的辅助程序发送同一选项。Borg 不会向自身发送信号;按 F10 可退出它。
内存映射有三列:
- 内存占用:仅计入该进程自身的内存,即它写入过的页面,加上被换出或被压缩的页面。排查内存泄漏时应关注这个数值;在 macOS 上,它与“活动监视器”中的 内存 列一致。
- 驻留:当前位于 RAM 中的页面,包括与其他进程共享的页面。
- 映射:地址空间,包括预留的部分。它与 RAM 用量无关。
一个进程可能映射了数 GB 的空间(大多是与其他所有进程共享的系统库),而它的内存占用只有几 MB。内存区域起初按用途分组:堆与动态分配、线程栈、程序代码和数据、共享库、映射文件、共享内存、系统及其他分配,以及保留区与保护页。各组及组内的内存区域按内存占用排序,预留部分排在最后。库和映射文件每个文件占一行;展开文件即可查看其内存区域。组名是根据操作系统提供的标签推断出来的。选中某个组或文件时,会说明它是什么,并显示它在内存占用中所占的份额及其中最大的成员。Enter 或 Right 展开组或文件;Left 将其折叠,或折叠包含所选行的那一项。G 在分组视图和原始列表之间切换。
1 内存 和 2 打开的文件 用于在检查标签页之间切换,也可以直接点击。打开的文件列表会显示每个描述符及其访问模式、类型,以及路径或网络端点;另一个窗格会在可获取时显示完整详细信息、文件大小和当前偏移量。在 macOS 上,列表还包括 cwd(当前目录)和 txt(映射的可执行文件)等条目。R 刷新列表。切换标签页时,每个标签页都会保留各自在列表中的位置。访问受限时,Borg 会提议以管理员身份重试。对于普通文件,详细信息中还会像快速预览一样预览其内容:按 Tab 进入预览即可滚动,按 F3 在查看器中打开该文件。Borg 以你自己的权限按路径打开文件,不会动用该进程的描述符。
内存区域预览
Section titled “内存区域预览”选中一个可读的内存区域时,Borg 会在另一个窗格中捕获该区域最多 64 KiB 的内容。如果区域开头全是零,捕获会跳到第一个含有数据的页面,字节上方的一行会说明窗口位于区域中的哪个位置。每行开头是它相对于区域起始处的偏移量,窗格较宽时每行显示更多字节。快速移动时,Borg 会稍等片刻再读取;选中另一个区域会清除旧的预览,并取消其尚未完成的读取。无法读取的区域会改为显示一条消息。按 Tab 或 Enter 可滚动预览。在预览中按 Esc 返回列表,在内存映射中按 Esc 则返回进程列表,光标和筛选条件保持原样。
连续的全零行会折叠成一行,例如“· 60K 个零字节 ·”。] 和 [ 跳到下一个和上一个数据块,> 和 < 捕获该区域中下一个和上一个 64 KiB。H 在十六进制和文本之间切换,R 重新捕获当前显示的窗口,D 高亮显示与同一进程、同一地址上一次捕获相比发生变化的内容。文本视图显示可打印的 ASCII 字符,其他字节以点号代替。捕获是在不暂停进程的情况下从运行中的进程获取的,因此一次捕获无法保证与整个进程保持一致。除非导出,否则捕获只保存在内存中。E 会将原始字节保存为当前文件面板所在文件夹中的新文件,只有你能读取。绝不会覆盖已有文件。
M 在字节视图和该区域的页面图之间切换预览:每个 4 KiB 页面对应一个单元格(大区域中则是每组连续页面对应一个单元格),并按内容类型标注:指向进程自身内存的指针、文本、UTF-16 文本、代码、压缩或加密数据、其他数据、填充、零,以及不在内存中的页面。图例显示每种类型在该区域中所占的比例。页面图获得焦点时,方向键、PgUp、PgDn、Home 和 End 可移动光标,Enter 以字节形式打开光标所在的页面。超过 16,384 个页面的区域会均匀采样。在 Linux 上,页面图会向系统查询哪些页面在内存中,不读取其余页面,因此既不会把已换出的页面换回,也不会触碰新页面;在 macOS 上,从未被访问过的页面读出来都是零。类型仅根据字节内容推测,请将其视为提示。
熵达到每字节 7.2 位的页面会以粗体红色绘制(在红色难以辨认的地方只用粗体),无论其类型如何。这样,加壳或加密的代码在可执行区域中就会显现出来,因为 Borg 会把这类区域中除零和填充以外的每个页面都算作代码。图例以 高熵 显示这类页面的比例,光标所在行会加上光标下页面的熵,并在行尾显示该区域的最高熵。图例下方的说明会解释高熵和低熵通常意味着什么。压缩缓存、图像、压缩包和密钥的熵也很高,因此在怀疑某个区域之前,请先看看它存放的是什么。
在内存窗格中按 /,或在任一窗格中按 Ctrl-F,即可搜索内存。可以选择搜索模式、是否区分大小写,以及搜索进程的可读内存还是当前捕获。表单打开时会保留上次关闭时的内容。文本 搜索 UTF-8、UTF-16(两种字节序)和 UTF-32(两种字节序)。Unicode 还会对兼容字符和组合序列进行规范化:例如,𝐁𝐨𝐫𝐠 可以匹配 Borg。但它不会仅仅因为字母外形相似就将其视为相同。十六进制 接受 42 6F 72 67 这样的字节对。
字符串 和 指标 只读取进程自己填充的内存,即没有文件支撑、且进程可能写入的内存;在 macOS 上,还会跳过进程从未写入过的区域。字符串 会把每段至少六个可打印 ASCII 或 UTF-16LE 字符的连续字符串列出一次,并注明它在内存中的副本数,同时跳过重复字符或标点之类的填充内容;如果输入了查询,则只保留包含该查询的字符串。指标 会保留看起来像 URL、IP 地址、电子邮件地址、文件路径、命令行、机密信息(私钥、访问令牌或密码赋值)或长段 base64 的字符串,并为每一项加上标签。
Borg 还会读取进程所映射文件中的字符串(最多 1 GiB),并用 • 标出只在内存中找到的字符串:即进程自己构建、接收或解码、且不在其任何文件中的文本。排序时,指标排在最前,其次是只在内存中找到的字符串,然后是文件中也存在的字符串;看起来像单词的字符串排在其他字符串之前。列表最多保留最相关的 2,000 条。其他匹配项显示的是所在区域,而这里每行显示类型和副本数,预览中会显示该字符串所在的位置;按 C 可逐一查看其副本。请把这些结果当作值得进一步查看的线索。普通程序中本来也有大量 URL、路径和密钥。
匹配项会占据列表窗格,并显示其地址、编码和所在区域。选中匹配项后,另一个窗格会预览其周围的字节,并高亮显示匹配内容。Enter 进入预览,Esc 返回匹配列表。在匹配列表中按 Esc 会回到你之前所在的内存映射或进程列表。搜索已保存的捕获时,使用的是捕获中的字节,不会读取正在运行的进程。搜索进程时,会分块读取内存,报告无法读取的字节,并在找到 2,000 个匹配项或达到 60 秒后停止。N 从搜索停止处继续,Ctrl-C 停止搜索并保留结果。覆盖范围报告会区分已搜索的字节和无法读取的字节。进程在被搜索期间可能会更改其内存。
代码完整性检查
Section titled “代码完整性检查”在内存映射或搜索匹配项中按 I,可检查进程运行的是否是其文件中的代码。它会将内存中的代码与其映射来源的文件进行比较,报告不一致的页面;在没有文件支撑的内存中搜索可执行映像(ELF、Mach-O 和 PE)的文件头;并列出没有文件支撑的可执行内存、同时可写又可执行的内存,以及来自已删除、无名称或临时文件的代码。它还会从整体上检查进程:通过 LD_PRELOAD、LD_AUDIT、DYLD_INSERT_LIBRARIES 或 /etc/ld.so.preload 注入的库、库搜索路径、Linux 上的跟踪器(tracer)、文件已被删除或替换的程序,以及与其程序名称不一致的进程名。
检查结果会占据列表窗格,按警报、警告、提示的顺序排列。选中一项后会预览其相关的字节并高亮显示证据,列表下方的几行会说明还有哪些其他可能的解释。在运行时编译代码的程序(例如浏览器以及 Java 和 JavaScript 引擎)总是会持有没有文件支撑的代码,因此对它们来说,这类结果只会作为提示。R 重新检查,Ctrl-C 停止检查并保留已有结果,Esc 返回。一次检查最多读取 512 MiB 代码和 1 GiB 其他内存,耗时不超过 45 秒;如果提前停止,会予以说明。
在 Linux 上,页表会显示进程修改过哪些代码页,因此只读取这些页面;在 macOS 上,同样不会读取没有修改页面的区域。macOS 共享缓存中的代码没有单独的文件,因此不做比较。检查无法读取的内存会被计数,Borg 会提供 A 管理员 选项。请把检查结果当作线索,因为调试器、性能分析器、更新程序和安全工具都会留下同样的痕迹。
用规则扫描内存
Section titled “用规则扫描内存”在内存映射或搜索匹配项中按 Y,会用与文件扫描相同的规则扫描进程自己填充的内存:Borg 自带的规则和你规则文件夹中的规则(参见 用规则扫描文件)。与 字符串 一样,它读取没有文件支撑的内存和进程可能写入的内存;在 macOS 上,还会跳过进程从未写入过的区域。没有任何页面在内存或交换区中的区域不会被读取;在 Linux 上,页表会显示进程从未访问过的页面,这些页面同样不会被读取。已换出到交换区的页面会被读取,因为载荷可能藏在那里。内存按 16 MiB 分块读取,相邻块重叠 4 KiB,因此跨越两块的匹配也能被找到。每条规则在每个区域中只列出一次,位置为它的首次匹配处。
匹配结果会占据列表窗格,警报排在最前。每一项会给出规则及其来源(Borg 自带规则 或你的文件),然后是它匹配到的字节和该规则查找的内容;列表下方的几行会说明还有哪些其他可能的解释。选中一项会预览其周围的字节,并高亮显示匹配部分。被排除的规则文件排在最后,并附有编译器信息。R 重新扫描,Ctrl-C 停止扫描并保留已找到的结果,Esc 返回。一次扫描最多读取 1 GiB,耗时不超过 45 秒,最多列出 2,000 个匹配;如果提前停止,会予以说明。无法读取的内存会被计数,Borg 会提供 A 管理员,在辅助程序中使用你的规则文件夹运行扫描。
规则扫描是一种初步排查:针对某个进程或文件夹,快速查找已有人描述过的模式。它不会监视系统、拦截任何东西或进行清理,这些是终端防护产品的工作。有些内容它无法触及:
- macOS 会保护其自带程序以及采用 Hardened Runtime 构建的程序的内存,不让其他任何程序读取,管理员也不例外(参见 管理员权限)。
- 在 Linux 上,
kernel.yama.ptrace_scope决定你可以读取谁的内存。设为 1 时(Ubuntu 即如此),你只能读取自己的子进程,因此对其余进程 Borg 会提供 A 管理员。设为 3 时,任何人都不能读取其他进程,管理员也不例外。 - 内核 rootkit 可以对包括 Borg 在内的所有程序隐藏内存、文件和进程,因此扫描结果干净并不能说明多少问题。
- 加壳或加密的程序只有在运行时才会显露真正的代码。此时扫描它的内存,能看到扫描其文件时看不到的内容。
Borg 读取内存时不会以调试器身份附加,在 Linux 上使用 process_vm_readv,在 macOS 上使用 mach_vm_read,因此进程看不到跟踪器,而这正是大多数反调试检查所关注的。以管理员身份运行的监控工具,例如 auditd、eBPF 工具和 macOS Endpoint Security 客户端,仍然可以记录这些读取。文件扫描会打开它读取的每个文件,inotify 和 fanotify 会报告这些操作。在 Linux 上,扫描不会改变你拥有的文件的访问时间。
对所有进程进行熵扫描
Section titled “对所有进程进行熵扫描”在进程列表中按 E 会打开 熵扫描,它抽样读取每个进程的内存页,查找与加壳代码一样密集的字节。按选项的字母,或移到该选项后按 Enter。Esc 关闭对话框。
| 按键 | 选项 | 读取的内容 |
|---|---|---|
| C | 所有进程的代码 | 可执行区域,加壳或加密的代码会在这里显现 |
| M | 所有进程的全部内存 | 所有可读区域,因此也包括密钥和压缩文件等数据 |
| L | 上次结果 | 不读取新内容,再次显示上次扫描的命中项 |
Borg 从每个区域最多读取 1,024 个 4 KiB 页面,从每个进程最多读取 32,768 个,均匀分布。区域较多的进程每个区域分到的页面更少,但不少于 16 个。区域中只要有一个页面达到每字节 7.2 位或更高,就是一个命中项。命中项占据列表窗格:代码排在最前,然后是进程写入的内存,再然后是按磁盘原样映射的文件;同一类中,高熵页面比例较大的区域排在前面。高熵页面占 70% 或以上的代码以粗体突出显示,规则与 查找加壳程序 相同。每一行给出类型(代码、已写入 或 文件)、该比例和最高熵,以及进程名称、PID 和区域。另一个窗格显示区域的地址、大小和访问权限、抽样页面中有多少是高熵页面、第一个高熵页面从哪里开始,以及这类字节通常是什么。只保留最强的 2,000 个命中项,摘要会统计舍去的较弱命中项。
在命中项上按 Enter 会在该区域打开进程的内存映射并打开页面图,光标位于抽样到的第一个高熵页面,方便你查看周围的字节。按 Esc 回到内存映射,再按一次回到命中项。
Borg 同时读取 4 个进程。没有任何页面在内存或交换区中的区域不会被读取;在 Linux 上,页表会显示哪些页面在内存中,因此扫描既不会触碰新页面,也不会把已换出的页面换回。每个进程都以相同方式映射的缓存会被排除:macOS 上的 dyld 共享缓存,以及 Linux 上内核映射到每个进程中的页面。未经修改而映射的文件(例如共享库)只在第一个映射它的进程中读取。
一次扫描运行 90 秒后停止,剩下的交给 N,它会继续处理尚未读到的进程。Ctrl-C 可提前停止并保留命中项,R 从头重新扫描。命中项上方的第一行统计已扫描的进程和命中项,下一行统计被排除的进程:无法读取、受保护和未读到。在 macOS 上,vmmap 列出一个进程的区域需要几秒钟,因此扫描所有进程可能需要按几轮 N。
内存受 macOS 保护的进程会被计数并跳过,因为任何人都无法读取它们。对于 Borg 无法读取的进程,它会提供 A 管理员,在辅助程序中扫描这些进程,最多 60 秒,这样即使密码提示耗时较长,辅助程序也能在其 2 分钟的时限内完成。高熵字节很常见:压缩缓存、图像、字体和密钥天生就是高熵,因此请把命中项当作线索,在怀疑某个进程之前先看看详细信息。
当读取内存映射或捕获受到限制、搜索无法读取部分内存、熵扫描无法读取某些进程,或者系统拒绝信号或优先级更改时,Borg 会提供 A 管理员 选项。在 macOS 上,这会打开系统的授权提示;在 Linux 上,它使用 pkexec,并且需要有授权代理正在运行。只有一个短时运行的辅助程序会以提升的权限运行。Borg 本身仍以普通权限运行,辅助程序只返回映射数据、有限数量的字节或扫描的命中项,或者发送你选择的那一个信号或优先级。
macOS 会保护其自身程序(系统完整性保护,System Integrity Protection)以及使用强化运行时(Hardened Runtime)构建的程序的内存,使其他任何程序(包括管理员)都无法访问。Borg 会从程序的签名中读取这一信息:进程详细信息中会显示 内存受保护,被拒绝的读取会说明原因,而且不会提供 A 管理员,因为它也无济于事。这些程序的内存映射和打开的文件仍会显示。如果 Borg 已经以管理员身份运行,也不会提供该选项。在 Linux 上,安全策略仍可能拒绝访问;Borg 会显示错误,进程监视器则照常工作。
进程由其 PID 与启动时间共同标识,因此复用某个 PID 的新进程不会继承另一个进程的历史。内存映射、检查结果、捕获和历史都有大小限制。Borg Commander 的名称始终显示在右下角,包括控制台、查看器、监视器、对话框和屏保中。
Borg 可以监视机器上启动的程序,并在出现可疑情况时告诉你。在 F9 → 选项 → 插件中打开 监视:异常的进程启动。监视器就住在 机器上所有 Borg 共用的后台服务里,因此每个 Borg 读到的都是同一批警报。只要有一个 Borg 开着,这个服务就在运行。要在不开 终端时也一直监视,请按照命名会话与后台服务中的说明让服务开机启动。
在你指定发送位置之前,警报只留在这台机器上,页面会在此期间提醒你(见把警报发到机器之外)。
Alt-L 或 命令 → 警报 打开该页面。列表占据活动窗格,最新的警报在最前面,另一个窗格完整显示所选警报。每行显示 时间、触发的规则、程序、启动它的父进程、它运行时所属的用户,以及该进程现在的状态。列表上方的一行统计警报总数以及 还没有人读过的数量。
详情先用一句话说明规则为何触发,然后是命令、程序、父进程、用户、标准流的去向,以及进程目前的状态。在 进程树 中, 每个父进程都在其子进程之上,于是你可以从一个服务一路读到让你担心的那条命令。连接 列出该进程当时持有的套接字, 程序哈希 是其程序的 SHA-256,规则 显示 Borg 的规则和你的规则在该程序中匹配到了什么(见 用规则扫描文件)。
超过 2 KiB 的命令会保留开头和结尾,合计 2 KiB,中间用 … 隔开。父进程的命令和预加载变量的值也按同样的方式截断。
这样无论命令有多长,记录都能容纳最新的 200 条警报,而且很长的第一个参数也无法把命令的结尾挤出视线。Ctrl-C 复制的是
记录中保留的命令。
| 按键 | 操作 |
|---|---|
| Enter | 在进程监视器中显示该进程 |
| C | 让被冻结的进程继续运行 |
| K | 结束该进程,会先询问 |
| A | 从现在起允许这样的启动 |
| Alt-O | 在列表旁打开警报中程序所在的文件夹 |
/ |
筛选行;↑↓ 仍可移动,Enter 保留筛选,Esc 清除 |
| R | 重新读取记录 |
| Ctrl-C | 复制该进程运行的命令 |
| Alt-W | 最大化列表 |
| Esc | 关闭页面,保留筛选 |
筛选会保留包含你输入的每个词的警报,可匹配规则、规则短名、原因、程序、父进程、用户、命令以及 Borg 的处置。在词语前
加 ! 可排除它匹配到的内容:shell-on-socket !root 会保留除 root 之外每个用户的远程 shell。
警报保存在状态文件夹中的 alerts.json 里,仅所有者可读,保留最新的 200 条。关于仍被冻结的进程的警报会在此之外继续保留,因此你始终可以让它继续运行或结束它。Borg 写入的文件不超过 8 MiB,即它能
读回的上限,放不下时先舍弃最旧的警报。Borg 的文件 窗口把它们计入 状态(见 Borg 的文件)。
打开页面即把其中的警报标记为已读,而未读警报的数量会在 Borg 打开期间一直跟随记录变化,无论该页面是否打开。
如果 Borg 发现一个无法读取的 alerts.json,例如旧版本让它超过了 8 MiB,它会先把该文件改名为
alerts-unreadable.json,再写入新的记录,因此其中的警报仍留在磁盘上。之后再出现这种文件时会得到带编号的名称,例如
alerts-unreadable (2).json。只要该文件还在,警报页面就会在列表下方写出它的位置,看完之后可以把它删掉。
监视器关注什么
Section titled “监视器关注什么”每条规则都指出刚启动的进程有哪一点不寻常。级别说明它的含义:警报 很少是无害的,警告 往往有普通的原因, 提示 只是为了让整幅图景完整。
| 规则 | 什么会触发它 | 级别 |
|---|---|---|
| 连接网络套接字的 shell | 输入、输出或错误输出通向网络套接字的 shell,远程 shell 正是这个样子。留在本机上的套接字不算,例如 systemd 用来把服务的输出交给日志的那种 |
警报 |
| 终端上的远程 shell | 运行在某个程序为它打开的终端上的 shell,而这个程序自己的输入、输出或错误输出通向网络套接字,或者它是 socat 或 nc 这样的中继。这是被升级为完整终端的远程 shell,Python 的 pty.spawn 造出的就是这种 |
警报 |
| 运行中被删除的程序 | 程序文件已不存在,磁盘上没有任何东西能说明该进程是什么 | 警报 |
| 没有文件的程序 | Borg 完全找不到该进程的程序文件 | 警报 |
| 被强行加载到程序中的库 | 其环境中设置了 LD_PRELOAD、LD_AUDIT 或 DYLD_INSERT_LIBRARIES |
警报 |
| 服务启动的 shell | Web 服务器、应用运行时或数据库启动了一个 shell | 警告 |
| 无终端的 shell | 两端都没有终端的 shell,因此不是有人敲出来的 | 警告 |
| 程序所开终端上的 shell | 运行在某个程序为自己打开、且没有人登录过的终端上的 shell,例如 expect 或 docker exec 打开的终端 |
警告 |
| 正在探查机器 | 同一个程序在一小时内运行了四条不同的命令,例如 whoami、id、uname 和 ss,且没有人登录它们所在的终端。它通过一个又一个 shell 运行的命令合在一起计算,因此为每条命令都启动一次 sh -c 的 web shell 也会被发现 |
警告 |
| 临时文件夹中的程序 | 程序位于 /tmp、/var/tmp、/dev/shm 或 /run/shm 之下 |
警告 |
| 名称与程序不符 | 进程自称一个名字,运行的却是另一个程序 | 警告 |
| 父进程此前从未启动过它 | 这个父进程此前从未在这台机器上启动过这个程序 | 提示 |
对每个进程,Borg 都会读取它运行的程序以及该文件是否已被删除、命令、用户、它所属的终端以及是谁打开了它、标准输入、输出和错误输出的去向、其环境中的预加载变量,以及它之上的父进程链。当进程所在的网络把某个套接字列为 TCP 或 UDP 时,它才算网络套接字。
是否有人登录了某个终端,判断方式与冻结时相同(见 冻结一个进程):通过 SSH、在控制台、在 tmux 或 screen 中、通过 sudo 或 su,或者在终端窗口中。程序为自己打开的终端不算,无论打开它的是编辑器的远程服务器、docker exec 还是反向 shell。
Borg 会先学习这台机器,然后才动手。最初 7 天它记录这里哪个父进程启动哪个程序,并且不冻结任何东西,处置列会在本该冻结的地方写 本会冻结。此后,对于可以学习的规则,它见过的组合会保持安静:三条 shell 规则、名称不符,以及父进程此前从未启动过它。成串的命令按它来自的程序学习:如果那个程序在学习的那一周里运行过其中的每一条命令,它就保持安静,所以整天运行 df 和 ps 的监控代理保持安静,而从未运行过 id 的 Web 服务器则不会。五条警报级别的规则每次都会触发,因为连接套接字的 shell 或被强行加载的库,不会因为经常发生就变得正常。这一周里运行的一切都会被当作正常,所以请在你信任这台机器的时候打开监视器。触发了学习无法消除的规则的启动,不会进入 Borg 学到的内容。
在某条警报上按 A,从那时起就按其程序和父进程允许这样的启动。这些允许写入状态文件夹中的
watch-allowed.json,监视器会在一分钟内读取并采纳。配置中的 [watch] 小节可以手工写下同样的内容
(见 配置)。Borg 每分钟最多记录 20 个进程、最多冻结 5 个,当同时触发的更多时,列表上方的一行
会说明它省略了多少条发现。Borg 冻结的进程无论之前已有多少条警报都一定会被记录下来,这样你才能让它继续运行。提示单独计数,因此一连串提示永远不会占用警告的名额;超出每分钟 5 个的冻结上限、Borg 本会冻结的进程,每分钟也会记录最多 20 个。
冻结一个进程
Section titled “冻结一个进程”学习期结束后,Borg 会冻结触发了警报级或警告级规则的进程。冻结会向该进程及其下的一切发送停止信号。被冻结的进程并未
退出,因此它所属的服务继续运行,systemd 和 Docker 没有什么需要重启,而它正要做的事都停在原处,等你来看。处置列
会写 已冻结,C 让它继续,K 结束它。终止无法挽回,所以 Borg 会先询问。
只有在你看过学习周里所有写着 本会冻结 的警报之后,或者学习周结束 3 天之后(以先到者为准),Borg 才会开始冻结。打开警报页面会把它们标为已读,监视器会在一分钟内注意到。这样,你可以在任何东西被停下之前检查这一周发现了什么,并先用 A 允许无害的程序,而没人查看的机器仍然受到保护。在此之前,页面底部会写明冻结要等到什么时候,新警报的详情也会说明冻结正在等待。
Borg 永远不会冻结系统的第一个进程、它自己或由它启动的东西、永不冻结名单上的程序(init、systemd、sshd、sudo、su、login 之类,再加上你在 keep-running 下添加的任何名称)、有人登录的终端上的进程,或者你已允许的启动。提示级别永远不会让程序停下。详情会说明是其中哪一条让进程继续运行。
内置名单上的程序只有位于系统存放程序的地方(例如 /usr 和 /bin)时才会被放过,所以放在其他文件夹里、名为 sshd 的副本会像其他程序一样被冻结。你在 keep-running 下添加的名称,不论程序位于何处都会匹配。有人登录过的终端才算数:通过 SSH、在控制台、在 tmux 或 screen 中、通过 sudo 或 su,或者在终端窗口中。终端复用器只对 root 和个人账户(编号从 UID_MIN 开始,其值写在 /etc/login.defs 中,大多数系统上是 1000)算数,因为服务账户也能启动它。程序自己打开的终端不算,这样远程 shell 就无法躲在终端后面。编辑器的远程服务器或 docker exec 打开的终端也不算,所以如果 Borg 冻结了你在那里运行的东西,C 让它继续,A 允许它。
Borg 在发信号之前会再次核对进程的身份,也就是它的编号连同它启动的那一刻,因此与此同时被别的进程占用的编号不会 受到影响。
以 root 身份运行的监视器
Section titled “以 root 身份运行的监视器”没有更多权限时,Borg 只能通过每秒四次查看进程列表,知道自己这个用户的进程,页面会在列表下方说明这一点。短暂的命令 可能在下一次查看之前就已消失。
要在每个进程启动的那一刻就知道它,请安装以 root 身份运行的那一部分:
borg service watch install这会询问一次你的密码,并安装一个单独的单元 borg-commander-watch.service,它运行 borg --watch-root。
这一部分把 Borg 自己的小程序加载进内核,它们在新程序运行之前读取每一次程序启动:程序本身、原样传入的命令行、启动它的进程以及用户。立即结束的命令仍会被报告,事后改写自己命令行的进程也一样。root 那一部分从 /proc 读取其余信息,并在 Borg 请求时停止、继续或结束一个进程。它不做别的事:该单元放弃了
所有不需要的权能,而 Borg 自己的界面仍以你的身份运行。两边通过状态文件夹中一个仅所有者可访问的套接字通信,Borg 只
接受以 root 身份运行的进程的连接,而 root 那一部分只连接属于它被安装给的那个用户的套接字。borg service watch uninstall
会移除它,borg service watch status 则打印事件的来源以及已经发现的内容。只要警报还没有发送位置,安装和状态这两个命令都会发出警告(见把警报发到机器之外)。
这些程序需要 Linux 5.8 或更高版本。在更旧的内核上,或者 root 那一部分无法加载它们时,它会改为接收内核的进程事件并从 /proc 读取全部信息,页面会在列表下方说明这一点。更新 Borg 之后,请再次运行 borg service watch install,让该单元获得新版本需要的权限。
两边的警报是同样的警报,记在同一份记录里。属于其他用户的进程只有 root 那一部分能冻结,因此从页面继续或终止这样的 进程会经由服务中的监视器。当没有任何途径能做到时,页面会说明只有所有者或管理员才能执行此操作。
目前监视器只是 Linux 的功能。在 macOS 上页面会打开并读取它的记录,来源则显示没有任何监视。
把警报发到机器之外
Section titled “把警报发到机器之外”警报页面位于它所监视的机器上,因此接管那台机器的人可以删除记录。在每条警报出现时就把它发送到别处:已经送达的副本不在他们的掌控之中,而心跳会在监视器沉默时告诉你。在服务器的配置中添加 [watch.notify],填写下面任意一个或几个位置。所有地址都使用 https://,发往服务器本机时也可以用 http://:
[watch.notify]name = "web-01" # how messages name this machine, its host name otherwiselevel = "warning" # the least an alert needs to be sent: alert, warning or notice
[watch.notify.telegram]token = "123456:ABC-your-bot-token"chat = "123456789"
[watch.notify.ntfy]url = "https://ntfy.sh/a-long-random-topic"token = "tk_your_token" # where the topic needs one
[watch.notify.webhook]url = "https://example.com/borg"secret = "a long random string"
[watch.notify.heartbeat]url = "https://hc-ping.com/your-check-id"minutes = 5然后运行 borg service watch notify。它会向每个位置发送一条测试消息,并打印每个位置的回应。
- Telegram:用 @BotFather 创建一个机器人,它会给你令牌。给机器人发一条消息,然后打开
https://api.telegram.org/bot<token>/getUpdates,从回应中取出chat.id。 - ntfy:在 ntfy 应用中订阅一个名字很长、无人能猜到的主题,或者订阅你自己的 ntfy 服务器上的主题。
- Webhook:Borg 把每条消息以 JSON 形式发送到该地址。设置了
secret时,每个请求都带有X-Borg-Signature: t=<time>,v1=<signature>:用该密钥对时间、一个点和消息正文计算的 HMAC-SHA256,这样接收方就能分辨 Borg 的消息和伪造的消息。X-Borg-Event说明消息的类型:alert、evidence(程序的 SHA-256 和规则匹配,在 Borg 读取程序之后紧随警报发出)、started、armed或test。 - 心跳:监视器运行期间,Borg 每隔
minutes分钟调用一次该地址,出现问题时会在地址后加上/fail:计算程序哈希的线程停止了,某个位置 30 分钟没有接收消息,Borg 放弃了某条消息,或者 Borg 无法读取它的发件箱。healthchecks.io 之类的服务会在调用停止时通知你,有人杀掉监视器时就会出现这种情况。
只要没有任何位置接收警报,警报就只留在机器上的日志里,Borg 会在你接触监视器的每个地方说明这一点。警报页面会在所选警报的详情上方显示 警报只留在这台机器上。,列表最大化时则显示在列表的最后一行。在 选项 → 插件 中打开监视器时,运行 borg service watch install 和 borg service watch status 时,以及监视器启动时服务的输出里,都会出现同一行。只设置心跳并不会让这条警告消失。心跳只告诉你监视器在运行,不携带任何警报,所以还要添加 Telegram、ntfy 或 Webhook。
Borg 快要采取行动时,警告会更加醒目。从学习期结束前一天起,它显示 Borg 即将开始冻结这里的进程,而没有人会收到通知。,冻结开始之后则显示 Borg 正在冻结这里的进程,而没有人收到通知。 没有发送位置时,Borg 照样监视、学习和冻结,所以对于不是每天都查看的机器,请在学习期结束之前设置好发送位置。
服务写下警报后几秒内就会发送它,还会在监视器启动和开始冻结时告诉你。除非 level 设为 notice,提示级警报只留在页面上。某个位置拒收的消息会在状态文件夹中的 watch-outbox.json 里等待并重试,第一次在 5 秒后,之后最长每 30 分钟一次。Borg 最多保留 500 条等待中的消息,消息较长、文件会超过 16 MiB 时保留得更少,每条尝试 48 小时。下一条消息会说明它放弃了多少条。
一条消息最多带上命令行、程序路径和警报原因各 1,000 字节。更长的内容会保留开头和结尾,中间用省略号代替,所以在命令开头填充内容也藏不住它要做的事。
Borg 发现无法读取的 watch-outbox.json 时,会把它重命名为 watch-outbox-unreadable.json,再新建一个发件箱。之后再出现的会使用带编号的名称,例如 watch-outbox-unreadable (2).json。原先等待的消息留在该文件中,永远不会发送,因此心跳会失败一次并说明原因,每个位置的编号也从 1 重新开始。查看完该文件后请将其删除。
每个位置的消息从 1 开始编号,每条消息的 JSON 在 previous 中带有上一条消息的 SHA-256。编号出现空缺,或者 previous 对不上,就说明有消息在途中丢失或被改动。编号是在服务器上生成的,所以它发现不了控制着服务器的人。
令牌保存在配置中,服务器上以你的用户身份运行的任何程序都能读取。拿到令牌的人可以发送看起来像 Borg 发出的消息,并且接着编号发下去而不留空缺,而 Telegram 机器人可以删除它在过去 48 小时内发送的消息。接管服务器的人还可以删除仍在等待发送的消息。在此之前送达的内容保持原样,发往你自己控制的机器的 Webhook 会按送达时的样子保存它。监视器沉默时,是心跳告诉你。
监视器看不到什么
Section titled “监视器看不到什么”监视器在每个程序启动时对它作出判断,这让一些事情落在它的视线之外。在依赖它之前,请先了解这些。
- 在已经运行的程序内部执行的代码不会启动任何新东西,所以没有规则会看它。自身解释器被人接管的 Web 应用,名称和父进程都和平常一样。
- 监视器启动之前就在运行的进程永远不会被检查,而学习周内运行的一切都会被当作正常。
- 没有以 root 身份运行的监视器时,Borg 只能看到你自己的进程,而在四分之一秒内结束的命令可能在 Borg 查看之前就已消失。以其他用户身份运行的服务,例如
www-data或postgres,需要以 root 身份运行的监视器。以 root 身份运行的监视器停止后,Borg 会重新只监视你自己的进程,直到它再次运行。 - 在 Linux 5.8 之前,或者 root 那一部分无法加载它的程序时,Borg 会在内核报告之后到
/proc中读取每个新进程。立即结束的命令到那时可能已经消失,进程也可能先改掉自己的命令行。 - 有些内核不发送进程事件,例如某些虚拟机和容器主机。这时以 root 身份运行的 Borg 和以 root 身份运行的监视器会每秒查看所有进程四次,短命令可能在它们查看之前就已结束。
- 大量进程同时启动时,内核与 Borg 之间的队列可能被填满,内核随后会丢弃事件,于是有些启动永远不会被检查。
- 在 Borg 读取
/proc之前就已结束的命令只带有这些程序读到的内容:它的程序、命令行、父进程和用户。它的终端、标准流和预加载变量随它一起消失,因此与它们相关的规则无法触发。只有当它的文件在启动时就已不存在(例如从内存运行的程序)时,运行中被删除的程序 才会对它触发。 - 这些程序保留命令行的前 4 KiB,更长的命令行,Borg 会在进程仍在时从
/proc读取。它们借助内核在/sys/kernel/btf/vmlinux发布的类型信息找到命令行和程序的文件。如果内核编译时没有包含这些信息,命令行同样从/proc读取,而提前结束的命令则以启动它时所用的路径命名。这个路径可能相对于它的工作文件夹,也可能经过符号链接,因此 临时文件夹中的程序 可能会漏掉它。 - 以监视器所用用户身份运行的任何东西,都可以停止监视器、修改它在状态文件夹中的文件,或者用
kill -CONT让被冻结的进程继续运行。冻结为你争取查看的时间,而进程的所有者仍然可以撤销它。 - 有人登录的终端上的进程永远不会被冻结,即使登录用的是被盗的密码或密钥。规则仍会触发并写下警报。
- 正在探查机器 只统计同一个程序在一小时内运行的命令。在命令之间等得更久,或者每条命令都从不同程序运行的人,达不到它的门槛。
- 在 Borg 冻结父进程之前就脱离父进程的进程会继续运行。
- 没有
[watch.notify]时,警报要等你打开页面时才会看到。有了它,在警报出现后几秒内接管机器的人仍可能在消息离开之前拦下它,这时心跳的沉默会告诉你出了事。
本指南跟随 main 分支更新 · Borg 0.11.0 · 1f6bce6
最新发布版本可能尚未包含此处介绍的全部功能。 发布说明
译自英文指南,欢迎指正。命令和键盘快捷键不作翻译。