当你通过 SSH 连接时删除了 Linux 上的 /lib 目录

我们先不讨论为什么会发生这种情况,但删除 /lib/usr/lib 或其他一些重要的运行时文件的情况确实经常发生(你可以看看这里这里这里这里). 在这篇文章中,我将只讨论在 Linux 上删除 /lib 时会发生什么,以及如何从中恢复。

对于所有问题,最简单的解决方案是替换缺失的文件,但如果 /lib 被删除,这可能会很困难,因为我们将没有 ld-linux,这是运行任何动态可执行文件所必需的。当你删除了 /lib 之后,所有非静态可执行文件 (例如 lscatetc) 将输出:

| | |---| | No such file or directory |

你也将无法使用 SSH 打开任何新连接,或者如果你正在使用 tmux,则无法打开新的 tmux 窗口/窗格。所以你只能依赖于当前 shell 的内置功能,以及系统上的一些静态可执行文件。

如果你安装了静态的 busybox,那么它就可以成为你的救星。你可以使用 busybox 中的 wget 从一个干净的系统下载库。供你参考:Debian 默认安装了 busybox,但默认版本不是静态版本。

图片

最小化的 Debian 安装

如果你担心将来会发生这种问题:安装 busybox 二进制文件的静态版本,并确认它是正确的版本。

图片

安装静态 busybox

Bash 来救援

我现在假设你没有静态的 busybox,甚至没有任何静态可执行文件(这在很多情况下都是如此,比如在最小化 Debian 的默认安装中)。我的解决方案是从另一台机器下载静态的 busybox

我还假设你安装了 bash(这是大多数系统的默认设置)。bash 有很多默认的内置函数,我们可以使用。这里有一个解决方案,可以使用内置的 bash 函数下载文件。这个线程上的其他解决方案依赖于外部命令 (例如 cat)。请注意,你需要将环境变量 LANG 设置为 C;否则,此脚本将错误地处理 Unicode 字节。

当然,我们不能 chmod 目标文件使其可执行,所以我们需要覆盖一个现有的可执行文件。如果你安装了 busybox(即使它不是静态版本),你可以覆盖这个文件。此时,你可以开始救援任务:例如,使用 wget 从另一台系统下载新的 /lib

请注意,busybox 无法使用非 busybox 程序名称的名称来运行。所以如果你用 busybox 覆盖了例如 fmt 二进制文件,它将无法工作(它会说:applet not found)。如果你没有 busybox,我建议覆盖 cp,然后你可以使用 cp 创建一个 cp 的副本作为 busybox(这将是可执行的)。

图片

cp to busybox

没有 bash?printf 可以帮忙

如果你有一个更高级的 shell(例如:zsh),它已经内置了 TCP 模块。你可以很容易地使用另一台机器上的 nc 将文件发送到目标机器。现在,让我们假设你有一个非常基本的 shell,例如:dash。大多数 shell(包括 dash)都有内置的 printf,我们可以用它来构造二进制文件。

大多数(全部?)shell 内置的 printf 实现都支持 \ooo,其中 ooo 是 3 位八进制数。第一个方法是简单地转换 busybox,但这个文件非常大(2 兆字节)。复制粘贴大型 printf 命令既繁琐又容易出错。我们需要一个小型的静态二进制文件来帮助我们。

如果可以为其他操作系统创建一个小的二进制文件,那么这个 printf 技巧也适用于其他操作系统。

为 Linux 创建一个小的 ELF 文件

如果你直接使用汇编,你可以创建一个非常小的可执行文件,但让我们尝试使用 C 来实现,以便它可以跨不同的架构移植。我能想到的最小的有用程序就是从 stdin 复制到 stdout,所以我们可以在一台机器上准备 netcat

cat busybox | nc -v -l -p 10000

然后我们可以从损坏的机器上这样做:

fdio < /dev/tcp/192.168.1.168/10000 > busybox

源代码可以像这样:

| | |---| | #include "unistd.h" int main() { char x; while (1) { int c = read(0, &x, 1); if (c!=0) break``; c = write(1, &x, 1); if (c!=0) break``; } return 0; } |

如果我们尝试使用标准 C 库(在 AMD64 机器上)编译它,结果是 776KB。

$ gcc -Os -static fd.c
$ du -hs a.out
768K  a.out

Linux 内核源代码包含我们可以使用的 nolibc 实现。使用此编译选项:

gcc -Os -Wl,--build-id=none -fno-asynchronous-unwind-tables -fno-ident -s -nostdlib -nodefaultlibs -static -include nolibc.h fd.c -lgcc -o fd

我们得到一个 4536 字节的二进制文件。相当不错。如果我们添加 -z max-page-size=0x04,我们甚至可以得到更小的尺寸。

gcc -Os -Wl,--build-id=none -z max-page-size=0x04 -fno-asynchronous-unwind-tables -fno-ident -s -nostdlib -nodefaultlibs -static -include nolibc.h fd.c -lgcc -o fd

现在是 672 字节。足够小可以传输。我们可以使用 Python 转换它。

| | |---| | import sys with open (sys.argv[ 1 ], "rb"``) as f: data = f.read() start = 0 width = 20 targetname = sys.argv[ 2 ] while True : part = data[start:start + width] if part = = '': break a = ''.join(['\\'+(oct(ord(i)).zfill( 3 ))[ -3 :] for i in part]) dest = '>' if start> 0 : dest += '>' dest += ' ' + targetname print ("printf '{}' {} "``.format(a, dest)) start += width |

然后我们可以将它复制粘贴到我们的 SSH 会话中,然后进行 /dev/tcp 重定向技巧。

图片

输出示例

当然,我们也可以编写一个完整的程序来建立 TCP 连接,而不是依赖 bash 重定向。

我希望你永远不需要这个知识

几天前,当我更新我的 Solar Powered Pi Zero 时,我遇到了这个问题,不知何故 /lib 被删除了(不确定是什么原因造成的)。这不是一台非常重要的机器,我可以重新刷写 MicroSD 卡来解决这个问题,但我很好奇我是否可以从错误中恢复。

我希望你永远不会在你的生产/重要机器上遇到这个错误,但如果你将来遇到这个问题,我希望这篇文章能帮助你摆脱困境。