Linux系统/lib目录误删恢复指南:从灾难中拯救你的系统

本文详细探讨了在Linux系统中误删/lib目录后的严重后果及多种恢复方法,包括使用静态busybox、bash内置功能、printf技巧和小型ELF程序等专业技术手段,帮助系统管理员在关键时刻拯救受损系统。

当你在SSH连接状态下误删Linux的/lib目录

首先我们不讨论这种情况为何会发生,但删除/lib、/usr/lib或其他重要运行时文件的情况相当常见(如你所见:这里这里这里这里)。本文仅讨论在Linux上删除/lib后会发生什么,以及如何从中恢复。

问题的影响

最简单的解决方案是替换丢失的文件,但如果/lib被删除,这将变得困难,因为我们将缺少运行任何动态可执行文件所需的ld-linux。当你删除/lib后,所有非静态可执行文件(如ls、cat等)都会输出:

1
No such file or directory

你也将无法使用ssh建立新连接,或者如果你在使用tmux,将无法打开新的tmux窗口/窗格。因此你只能依赖当前的shell内置命令,以及系统上已有的某些静态可执行文件。

恢复方案

使用静态busybox

如果你安装了静态busybox,它可能成为你的救星。你可以使用busybox中的wget从干净系统下载库文件。需要注意的是:Debian默认安装了busybox,但默认版本不是静态版本。

最小化Debian安装建议:如果你担心将来可能遇到此类问题,请安装静态版本的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(这将可执行)。

没有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:

1
cat busybox | nc -v -l -p 10000

然后我们可以从受损机器执行:

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

源代码可以如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#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。

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

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

1
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,甚至可以获得更小的尺寸。

1
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转换此文件。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
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重定向。

最后的思考

我希望你永远不需要这些知识。几天前,当我在更新我的太阳能供电Pi Zero时遇到了这个问题,不知何故/lib被删除了(不确定是什么原因导致的)。这不是一个非常重要的机器,我本可以重新映像MicroSD卡就完事了,但我很好奇是否可以从错误中恢复。

我希望你永远不会在生产/重要机器上遇到此错误,但如果你将来遇到此问题,我希望本文能帮助你从这种情况中恢复。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计