编程开源技术交流,分享技术与知识

网站首页 > 开源技术 正文

如何将Docker镜像逆向工程为Dockerfile

wxchong 2024-10-19 15:27:50 开源技术 10 ℃ 0 评论

由于某些原因,我们可能需要从镜像中恢复 Dockerfile。Docker镜像逆向工程为Dockerfile可以帮助我们理解Docker镜像的内部结构,并尝试重建其对应的Dockerfile。本文重点介绍了如何使用 Dedockify和 Dive 这两个工具从镜像中重建Dockerfile的近似版本。基本流程如下

  • 使用Dedockify初始化Dockerfile文件
  • 使用Dive恢复测试文件名
  • 使用docker cp 恢复测试文件
  • 手动修改Dokerfile

使用 dive

img

Dive 是一个用于查看 Docker 镜像内部结构的工具。它可以检查每一层 (Layer) 并展示镜像的目录树、命令序列等信息。通过 Dive,我们可以快速了解镜像的组成。

首先,创建一个 Dockerfile,在命令行中输入以下代码段:

cat > Dockerfile << EOF ; touch testfile1 testfile2 testfile3

FROM scratch

COPY testfile1 /

COPY testfile2 /

COPY testfile3 /

EOF

输入上述内容并按回车键后,我们创建了一个 Dockerfile,并在同一目录下创建了三个测试文件

$ ls

Dockerfile  testfile1  testfile2  testfile3

基于这个Dockerfile创建一个镜像

docker build . -t example1

example1 镜像构建过程输出如下

Sending build context to Docker daemon  3.584kB

Step 1/4 : FROM scratch

 --->

Step 2/4 : COPY testfile1 /

 ---> a9cc49948e40

Step 3/4 : COPY testfile2 /

 ---> 84acff3a5554

Step 4/4 : COPY testfile3 /

 ---> 374e0127c1bc

Successfully built 374e0127c1bc

Successfully tagged example1:latest

查看构建好的example1 镜像

$ docker images

REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE

example1            latest              374e0127c1bc        31 seconds ago      0B

由于没有二进制数据,此镜像是无法运行的。我们只是用它作为一个示例,说明如何在 Docker 镜像中查看层。

我们使用了 scratch作为源镜像,scratch 是一个空白镜像,大小为零字节。当我们不需要任何现有层时,可以用它作为基础镜像。然后,我们在空白镜像上复制了三个额外的零字节测试文件,对其进行了修改,并将修改标记为 example1。

现在,我们使用 Dive 查看example1镜像。

docker run --rm -it \

    -v /var/run/docker.sock:/var/run/docker.sock \

    wagoodman/dive:latest example1

执行上面的命令会从Dockerhub拉取镜像,输出如下

Unable to find image 'wagoodman/dive:latest' locally

latest: Pulling from wagoodman/dive

89d9c30c1d48: Pull complete

5ac8ae86f99b: Pull complete

f10575f61141: Pull complete

Digest: sha256:2d3be9e9362ecdcb04bf3afdd402a785b877e3bcca3d2fc6e10a83d99ce0955f

Status: Downloaded newer image **for** wagoodman/dive:latest

Image Source: docker://example-image

Fetching image... (**this** can take a **while** **for** large images)

Analyzing image...

Building cache...

img

滚动镜像层列表,在右侧显示的树中找到三个文件。

img

滚动浏览每一层时,可以看到右侧的内容发生了变化。当每个文件被复制到一个空白的 scratch 镜像时,它就被记录为一个新层。

img

我们还可以看到制作每个镜像层所使用的命令、源文件和更新文件的哈希值。

查看 Command 这一栏,可以看到以下内容:

\#(nop) COPY file:e3c862873fa89cbf2870e2afb7f411d5367d37a4aea01f2620f7314d3370edcc in /

\#(nop) COPY file:2a949ad55eee33f6191c82c4554fe83e069d84e9d9d8802f5584c34e79e5622c in /

\#(nop) COPY file:aa717ff85b39d3ed034eed42bc1186230cfca081010d9dde956468decdf8bf20 in /

每条命令都可以了解 Dockerfile 中用于生成镜像的原始命令。但是,原始文件名却丢失了。恢复这一信息的唯一方法就是观察目标文件系统的变化,或者根据其他细节进行推断。

docker history

除了 dive ,我们还可以使用 docker history。如果我们在 example1 镜像上使用 docker history 命令,可以查看创建该镜像时在 Dockerfile 中使用的条目。

docker history example1

结果如下:

IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT

374e0127c1bc        25 minutes ago      /bin/sh -c #(nop) COPY file:aa717ff85b39d3ed…   0B

84acff3a5554        25 minutes ago      /bin/sh -c #(nop) COPY file:2a949ad55eee33f6…   0B

a9cc49948e40        25 minutes ago      /bin/sh -c #(nop) COPY file:e3c862873fa89cbf…   0B

CREATED BY 一列中的所有内容都被截断了。这些是通过 Bourne shell 传递的 Dockerfile 指令。这些信息对于重新创建我们的 Dockerfile 很有用,我们可以使用--no-trunc选项来查看所有信息:

$ docker history example1 --no-trunc

IMAGE                                                                     CREATED             CREATED BY                                                                                           SIZE                COMMENT

sha256:374e0127c1bc51bca9330c01a9956be163850162f3c9f3be0340bb142bc57d81   29 minutes ago      /bin/sh -c #(nop) COPY file:aa717ff85b39d3ed034eed42bc1186230cfca081010d9dde956468decdf8bf20 in /    0B

sha256:84acff3a5554aea9a3a98549286347dd466d46db6aa7c2e13bb77f0012490cef   29 minutes ago      /bin/sh -c #(nop) COPY file:2a949ad55eee33f6191c82c4554fe83e069d84e9d9d8802f5584c34e79e5622c in /    0B

sha256:a9cc49948e40d15166b06dab42ea0e388f9905dfdddee7092f9f291d481467fc   29 minutes ago      /bin/sh -c #(nop) COPY file:e3c862873fa89cbf2870e2afb7f411d5367d37a4aea01f2620f7314d3370edcc in /    0B

虽然这其中有一些有用的数据,但从命令行中进行解析可能是个挑战。我们也可以使用 docker inspect。不过,在本文中,我们将重点介绍如何使用适用于 Python 的 Docker 引擎 API。

使用基于Python 的 Docker 引擎 API

Docker 为 Docker 引擎 API 发布了一个 Python 库,允许在 Python 中完全控制 Docker。在示例中,我们可以通过运行下面的 Python 代码,恢复与使用 docker 历史记录类似的信息:

https://github.com/mrhavens/Dedockify/blob/master/examples/histExample.py

\#!/usr/bin/python3

import docker

cli = docker.APIClient(base_url='unix://var/run/docker.sock')

print (cli.history('example1'))

输出如下

[{'Comment': '', 'Created': 1583008507, 'CreatedBy': '/bin/sh -c #(nop) COPY file:aa717ff85b39d3ed034eed42bc1186230cfca081010d9dde956468decdf8bf20 in / ', 'Id': 'sha256:374e0127c1bc51bca9330c01a9956be163850162f3c9f3be0340bb142bc57d81', 'Size': 0, 'Tags': ['example:latest']}, {'Comment': '', 'Created': 1583008507, 'CreatedBy': '/bin/sh -c #(nop) COPY file:2a949ad55eee33f6191c82c4554fe83e069d84e9d9d8802f5584c34e79e5622c in / ', 'Id': 'sha256:84acff3a5554aea9a3a98549286347dd466d46db6aa7c2e13bb77f0012490cef', 'Size': 0, 'Tags': None}, {'Comment': '', 'Created': 1583008507, 'CreatedBy': '/bin/sh -c #(nop) COPY file:e3c862873fa89cbf2870e2afb7f411d5367d37a4aea01f2620f7314d3370edcc in / ', 'Id': 'sha256:a9cc49948e40d15166b06dab42ea0e388f9905dfdddee7092f9f291d481467fc', 'Size': 0, 'Tags': None}]

从输出,我们可以看到,重建 Dockerfile 的大部分内容只需解析所有相关数据并反转条目即可。

然而,我们注意到 COPY 指令中有一些散列条目,这里的散列条目代表从层外使用的文件名。这些信息无法直接恢复。不过,正如我们在Dive中看到的,当我们搜索对图层所做的更改时,我们可以推断出这些名称。如果原始复制指令将目标文件名作为目的文件名,有时也可以推断出这些文件名。在其他情况下,文件名可能并不重要,我们可以使用任意文件名。

Dedockify

img

Dedockify是一个 Python 脚本,可以帮助重建创建镜像所使用的近似 Dockerfile。它利用存储在每个镜像层旁边的元数据,沿着层级树向后遍历,收集与每个层相关联的命令,从而重建在镜像构建过程中执行的命令序列。

对于使用 COPY ADD 指令的情况,Dedockify 生成的输出可能不会完全匹配原始的 Dockerfile,因为它无法访问构建上下文。

以下的 Python 代码可以从 GitHub Dedockify 代码库中获取。https://github.com/mrhavens/Dedockify/tree/master

from sys import argv
import docker

class ImageNotFound(Exception):
    pass

class MainObj:
    def __init__(self):
        super(MainObj, self).__init__()
        self.commands = []
        self.cli = docker.APIClient(base_url='unix://var/run/docker.sock')
        self._get_image(argv[-1])
        self.hist = self.cli.history(self.img['RepoTags'][0])
        self._parse_history()
        self.commands.reverse()
        self._print_commands()

    def _print_commands(self):
        for i in self.commands:
            print(i)

    def _get_image(self, img_hash):
        images = self.cli.images()
        for i in images:
            if img_hash in i['Id']:
                self.img = i
                return
        raise ImageNotFound("Image {} not found\n".format(img_hash))

    def _insert_step(self, step):
        if "#(nop)" in step:
            to_add = step.split("#(nop) ")[1]
        else:
            to_add = ("RUN {}".format(step))
        to_add = to_add.replace("&&", "\\\n    &&")
        self.commands.append(to_add.strip(' '))

    def _parse_history(self, rec=False):
        first_tag = False
        actual_tag = False
        for i in self.hist:
            if i['Tags']:
                actual_tag = i['Tags'][0]
                if first_tag and not rec:
                    break
                first_tag = True
            self._insert_step(i['CreatedBy'])
        if not rec:
            self.commands.append("FROM {}".format(actual_tag))

__main__ = MainObj()

生成初始 Dockerfile

到这一步,我们有两个镜像:wagoodman/dive 和自定义的 example1 镜像。

$ docker images

REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE

example1            latest              374e0127c1bc        42 minutes ago      0B

wagoodman/dive      latest              4d9ce0be7689        2 weeks ago         83.6MB

Running this code against our `example1` image will finally produce the following:

$ python3 dedockify.py 374e0127c1bc

FROM example1:latest

COPY file:e3c862873fa89cbf2870e2afb7f411d5367d37a4aea01f2620f7314d3370edcc **in** /

COPY file:2a949ad55eee33f6191c82c4554fe83e069d84e9d9d8802f5584c34e79e5622c **in** /

COPY file:aa717ff85b39d3ed034eed42bc1186230cfca081010d9dde956468decdf8bf20 **in** /

我们提取到的镜像信息与使用Dive提取的几乎相同。 FROM 指令显示的是 example1:latest 而不是 scratch。在这种情况下,代码对基础镜像做出了不正确的假设。

作为对比,我们对 wagoodman/dive 镜像做同样的操作

$ python3 dedockify.py 4d9ce0be7689

FROM wagoodman/dive:latest

ADD file:fe1f09249227e2da2089afb4d07e16cbf832eeb804120074acd2b8192876cd28 **in** /

CMD ["/bin/sh"]

ARG DOCKER_CLI_VERSION=

RUN |1 DOCKER_CLI_VERSION=19.03.1 /bin/sh -c wget -O- https://download.docker.com/linux/static/stable/x86_64/docker-${DOCKER_CLI_VERSION}.tgz |     tar -xzf - docker/docker --strip-component=1 \

    &&     mv docker /usr/local/bin

COPY file:8385774b036879eb290175cc42a388877142f8abf1342382c4d0496b6a659034 **in** /usr/local/bin/

ENTRYPOINT ["/usr/local/bin/dive"]

与example1 的镜像相比,这里显示了更大的差异。我们注意到 ADD 指令在 FROM 指令之前。我们的代码再次做出了错误的假设。我们不知道 ADD 指令要添加什么。不过,可以直观地假设,我们并不确定基础镜像是什么。ADD 指令可能是用来提取本地 tar 文件到根目录。也有可能是使用这种方法加载另一个基础镜像。

Dedockify测试

创建一个 Dockerfile

cat > Dockerfile << EOF ; touch testfile1 testfile2 testfile3

FROM ubuntu:latest

RUN mkdir testdir1

COPY testfile1 /testdir1

RUN mkdir testdir2

COPY testfile2 /testdir2

RUN mkdir testdir3

COPY testfile3 /testdir3

EOF

执行构建,这里使用 ubuntu:latest 作为基础镜像,并将镜像标记为 example2

$ docker build . -t example2

Sending build context to Docker daemon  3.584kB

Step 1/7 : FROM ubuntu:latest

 ---> 72300a873c2c

Step 2/7 : RUN mkdir testdir1

 ---> Using cache

 ---> 4110037ae26d

Step 3/7 : COPY testfile1 /testdir1

 ---> Using cache

 ---> e4adf6dc5677

Step 4/7 : RUN mkdir testdir2

 ---> Using cache

 ---> 22d301b39a57

Step 5/7 : COPY testfile2 /testdir2

 ---> Using cache

 ---> f60e5f378e13

Step 6/7 : RUN mkdir testdir3

 ---> Using cache

 ---> cec486378382

Step 7/7 : COPY testfile3 /testdir3

 ---> Using cache

 ---> 05651f084d67

Successfully built 05651f084d67

Successfully tagged example2:latest

查看镜像

$ docker images

REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE

example2            latest              05651f084d67        2 minutes ago       64.2MB

example1            latest              374e0127c1bc        1 hour ago          0B

ubuntu              latest              72300a873c2c        9 days ago          64.2MB

wagoodman/dive      latest              4d9ce0be7689        3 weeks ago         83.6MB

逆向example2镜像

$ python3 dedockify.py 05651f084d67

FROM ubuntu:latest

RUN /bin/sh -c mkdir testdir1

COPY file:cc4f6e89a1bc3e3c361a1c6de5acc64d3bac297f0b99aa75af737981a19bc9d6 **in** /testdir1

RUN /bin/sh -c mkdir testdir2

COPY file:a04cdcdf5fd077a994fe5427a04f6b9a52288af02dad44bb1f8025ecf209b339 **in** /testdir2

RUN /bin/sh -c mkdir testdir3

COPY file:2ed8ccde7cd97bc95ca15f0ec24ec447484a8761fa901df6032742e8f1a2a191 **in** /testdir3

这与最初的 Dockerfile 非常吻合。这次没有 ADD 指令,而 FROM 指令是正确的。只要我们的基础镜像是在原始 Dockerfile 中定义的,并且避免使用 scratch 或使用 ADD 指令从 tar 文件创建基础镜像,我们就能比较准确地重建 Dockerfile。不过,我们仍然不知道被复制的原始文件的名称。

Dockerfile 重建

现在,使用之前的工具逆向工程一个 Docker 容器。我们修改了先前的 Dockerfile,创建了 example3。通过添加一个二进制文件,该镜像已具备功能。这些代码可以从 GitHub Dedockify代码库获取。由于这个镜像非常小,我们不需要构建或拉取它。我们只需通过下面的代码段,将整个容器复制粘贴到Docker 环境中即可。

https://github.com/mrhavens/Dedockify/blob/master/examples/example3/hello.tar.gz.uu

uudecode << EOF | zcat | docker load

begin-base64 600 -

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

YUoB50E7iU4JFD4ESbcok5Kq6SWbIWjFpbFSCOmUYaxwiR1yiY3tvxX+T3JR

3v/sBK8Cy+4O/yfO+L/y/lcnWOn/rvF/hf8vKCgoaBXGQgwYvEKWwNmY0euo

As/cks/mFGMgBI+BfD7jaJtrHaMnhx1ZMAF9a/M/6dL/d4HG+m9r/qfS/3eC

wv/dbzS2/7bmfyr23wmW/F/TanCR/7PKBZAyeI7SuGDqd0Y9ahVYZj6nJFFq

pZS2UieBDAC05HRxNBKE06nwfx3yf43tv6X5n3iJ/+0Er96ovHP8X/H/O8FK

/3eN/yu//xUUFBS0iqbOekv8nzSs9P9doLH+23n/V4ky/1cnKPzf/UZj+2+H

/wNd7L8TLPm/NXB2K/5vDbGEhf/riv9rbP/t8H/Ayvt/naDE/xX9L/S/mrxv

/Wnc7P9f1P+C/5OyxP8WFBQUtIqmizW1Ff9X+v9u0Fj/Lc3/Z4r/3wkK/3e/

0dj+W4r/K/P/dIMl/7eGNRtX/N8aYgkL/9cV/9fY/lua/0+xYv9doMT/Ff0v

9L9atWL9abwV/1fi/wsKCgpaRdPJetqK/6vX/y39f/torP+W4v94+f2vExT+

736jsf23FP+niv13glf8X/M5+1b8X1lL5H3i/5rafzv8n9Bl/a9OcPfW/1jy

f8X/7wQr/a/WMF5/Gm/F/xX+v6CgoKBVJPK8tUkQIUjIXCXMBoJj0oPO3Btu

jJc86SyRWaFFDOhQ8QxKJ8uya2n9D6bK+h+doLH+25n/j/My/0cnKPzf/UZj

+2/O/wmQr63/a0r8TzdY8n9Nq8FF/o+xoB1a6zBk51lkXpMADhmswByzE+Bd

AG5V5skJj1zaSKm4aOhYEpf4P8EE22KwxcSugIHiAwF9KbR2grlLRGCdsKrf

XuUQrJe25hYlS5SRHDFpoxNDACOzDxhToCpmUcUYc5JJRMWNZjfzihdTuGsc

41719NPdR59sH8+m26NJ9KPtWRiOBxf2V7vnJxYbZ7v0Ue0vScq9qt7dnh3Q

o2xF+vjwo/Hk6ONezUc+2X329dOdx092e3vPq21/dLR9gKPR5Hm1X9/+itic

HXih9EB4CIFjBOm8wKB4CIxMnRkbubaokRoGgd5zFUwEBcIw5FkxRbpC4/AG

frRO/gpHunchT3WGfoEtPTk93azSJH5Hqv4ep7PhZLxgm/sM+vqsItx9Wnmd

Kr/MS9919S3Jbj+NB8M5xvnxtM61P0y6tst3uJ72+4bG/X9z/l+9Pv4DXd7/

6Qa1sbWdxhuP/znXqsT/doJVY9tiGrX9X6t/Y67w/9SPlPF/J/jnw8//8sFq

78Fvf7Xx640/0Hf9/xs68h9OG//+/c8//am38cHGv37+6VFdWTZ7P0ymo/TH

3727jBcUFBQUNMKhHw8zzub9W/B4b4sb/H8O0lzyBemIEuX9n06wd1I9WFId

FVPOgzU2oVQ+SAkqeKdN1IASaKiPUrsFr2aNy0Znn8hNTzlz453ywBZ1iIbf

z/BosutfzOrRPP7oD49GCIORryNM6iH95/WIcXGyaaTg+eCTUl1fJBsJW9+y

uCRsfXPskbD1LdhRl9na3v4lYU1530vC1sdLVPun+8VP/SVMyVZnw/lkOsRZ

W2nc9PsvB7ja/pNBlPa/C5ysWuia61420mv4Pej0tNhcQUFBwV3GfwHMszUX

AMIAAA==

====

这段代码将经过 uuencode 编码和 gzip压缩的数据解码、解压缩,并将其加载到example3:latest镜像

$ docker images

REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE

example3            latest              059a3878de45        5 minutes ago       63B

现在,让我们尝试重新创建Dockerfile。

$ python3 dedockify.py 059a3878de45

FROM example3:latest

WORKDIR /testdir1

COPY file:322f9f92e3c94eaee1dc0d23758e17b798f39aea6baec8f9594b2e4ccd03e9d0 **in** testfile1

WORKDIR /testdir2

COPY file:322f9f92e3c94eaee1dc0d23758e17b798f39aea6baec8f9594b2e4ccd03e9d0 **in** testfile2

WORKDIR /testdir3

COPY file:322f9f92e3c94eaee1dc0d23758e17b798f39aea6baec8f9594b2e4ccd03e9d0 **in** testfile3

WORKDIR /app

COPY file:b33b40f2c07ced0b9ba6377b37f666041d542205e0964bc26dc0440432d6e861 **in** hello

ENTRYPOINT ["/app/hello"]

这给了我们一个基本的Dockerfile。由于example3:latest是该镜像的名称,因此我们可以从上下文中假设它正在使用scratch。现在,我们需要查看哪些文件被复制到/testdir1、/testdir2、/testdir3和/app中。

我们在dive上运行这个镜像,看看我们将如何恢复丢失的数据。

docker run --rm -it \

    -v /var/run/docker.sock:/var/run/docker.sock \

    wagoodman/dive:latest example3:latest

img

向下滚动到最后一层,你能看到所有丢失的数据都填充到了右侧的树中。每个目录都复制了名为 testfile1、testfile2 和 testfile3 的零字节文件。在最后一个目录中,一个名为 hello 的 63 字节文件被复制到了 /app 目录中。

现在,让我们恢复这些文件。由于无法直接从镜像中复制文件,我们需要先创建一个容器。

$ docker run -td example3:latest

6fdca182a128df7a76e618931c85a67e14a73adc69ad23782bc9a5dc29420a27

现在,我们使用从 Dive 恢复的路径和文件名,将所需文件从容器复制到主机。

/testdir1/testfile1

/testdir2/testfile2

/testdir3/testfile3

/app/hello

查看容器是否还在运行

$ docker ps

CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

6fdca182a128        example3:latest     "/app/hello"        2 minutes ago       Up 2 minutes                            wizardly_lamport

如果容器已经停止了,使用下面的命令查看

$ docker container ls -a

查看日志

$ docker logs 6fdca182a128

Hello, world!

当前容器日志输出了Hello, world

执行docker cp将容器文件复制到主机

docker cp 6fdca182a128:/testdir1/testfile1 .

docker cp 6fdca182a128:/testdir2/testfile2 .

docker cp 6fdca182a128:/testdir3/testfile3 .

docker cp 6fdca182a128:/app/hello .

运行已恢复的可执行文件hello,我们应该会看到以下内容:

$ ./hello

Hello, world!

更新之前的 Dockerfile,包括修改为 FROM scratch,以及使用 Dive 获取的文件名。

FROM scratch

WORKDIR /testdir1

COPY testfile1 .

WORKDIR /testdir2

COPY testfile2 .

WORKDIR /testdir3

COPY testfile3 .

WORKDIR /app

COPY hello .

ENTRYPOINT ["/app/hello"]

使用逆向工程的Dockerfile构建镜像

$ docker build . -t example3:recovered

Sending build context to Docker daemon  4.608kB

Step 1/10 : FROM scratch

 --->

Step 2/10 : WORKDIR /testdir1

 ---> Running **in** 5e8e47505ca6

Removing intermediate container 5e8e47505ca6

 ---> d30a2f002626

Step 3/10 : COPY testfile1 .

 ---> 4ac46077a588

Step 4/10 : WORKDIR /testdir2

 ---> Running **in** 8c48189da985

Removing intermediate container 8c48189da985

 ---> 7c7d90bc2219

Step 5/10 : COPY testfile2 .

 ---> 5b40d33100e1

Step 6/10 : WORKDIR /testdir3

 ---> Running **in** 4ccd634a04db

Removing intermediate container 4ccd634a04db

 ---> f89fdda8f059

Step 7/10 : COPY testfile3 .

 ---> 9542f614200d

Step 8/10 : WORKDIR /app

 ---> Running **in** 7614b0fdba42

Removing intermediate container 7614b0fdba42

 ---> 6d686935a791

Step 9/10 : COPY hello .

 ---> cd4baca758dd

Step 10/10 : ENTRYPOINT ["/app/hello"]

 ---> Running **in** 28a1ca58b27f

Removing intermediate container 28a1ca58b27f

 ---> 35dfd9240a2e

Successfully built 35dfd9240a2e

Successfully tagged example3:recovered

基于上面的镜像启动容器

$ docker run --name recovered -dt example3:recovered

0f696bf500267a996339b522cf584e010434103fe82497df2c1fa58a9c548f20

$ docker logs recovered

Hello, world!

使用 dive 查看镜像层

docker run --rm -it \

    -v /var/run/docker.sock:/var/run/docker.sock \

    wagoodman/dive:latest example3:recovered

img

这次显示的各层与原来的Dockerfile相同。并排比较这两张图片,可以看出它们是匹配的,包括两者的文件大小和功能

下面是用来生成原始 example3 镜像的Dockerfile。

FROM alpine:3.9.2

RUN apk add --no-cache nasm

WORKDIR /app

COPY hello.s /app/hello.s

RUN touch testfile && nasm -f bin -o hello hello.s && chmod +x hello

FROM scratch

WORKDIR /testdir1

COPY --from=0 /app/testfile testfile1

WORKDIR /testdir2

COPY --from=0 /app/testfile testfile2

WORKDIR /testdir3

COPY --from=0 /app/testfile testfile3

WORKDIR /app

COPY --from=0 /app/hello hello

ENTRYPOINT ["/app/hello"]

虽然然我们无法完美地重建 Docker 镜像,但我们可以大致重建。使用多阶段构建的 Dockerfile 是无法重建的,因为中间层的信息不存在。我们的唯一选择是基于我们拥有的最终构建阶段的镜像来重建 Dockerfile

结论

通过使用与dive类似的方法,我们可以更新 Dedockify 源代码,使其自动穿越每一层,以恢复所有有用的文件信息。此外,我们还可以更新程序,使其能够自动从容器中恢复文件并将其存储到本地,同时自动对 Dockerfile 进行适当的更新。最后,还可以对程序进行更新,使其能够推断出基础镜像是否为空镜像。通过对恢复的 Dockerfile 语法进行一些额外的修改,Dedockify可以在大多数情况下完全自动地将Docker镜像逆向为Dockerfile。

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表