当你通过 SSH 连接时删除了 Linux 上的 /lib 目录
当你通过 SSH 连接时删除了 Linux 上的 /lib 目录
我们先不讨论为什么会发生这种情况,但删除 /lib
、/usr/lib
或其他一些重要的运行时文件的情况确实经常发生(你可以看看这里、这里、这里 和这里). 在这篇文章中,我将只讨论在 Linux 上删除 /lib
时会发生什么,以及如何从中恢复。
对于所有问题,最简单的解决方案是替换缺失的文件,但如果 /lib
被删除,这可能会很困难,因为我们将没有 ld-linux
,这是运行任何动态可执行文件所必需的。当你删除了 /lib
之后,所有非静态可执行文件 (例如 ls
、cat
、etc
) 将输出:
| |
|---|
| 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 卡来解决这个问题,但我很好奇我是否可以从错误中恢复。
我希望你永远不会在你的生产/重要机器上遇到这个错误,但如果你将来遇到这个问题,我希望这篇文章能帮助你摆脱困境。