首页/新闻资讯/正文详情

逆向工程: 将docker镜像”反编译”为Dockerfile

发布时间:2026/9/23 10:58:54 来源:云帆数科 栏目:资讯中心
逆向工程: 将docker镜像”反编译”为Dockerfile
逆向工程: 将docker镜像”反编译”为Dockerfile通过研究Docker镜像的内部结构对Docker镜像进行逆向工程。在本文中 我们将通过理解Docker镜像如何存储数据 以及如何使用工具查看镜像方方面面的信息来逆向工程一个Docker镜像; 以及如何使用Python的Docker API来构建一个类似Dedockify的工具来创建Dockerfile。简介随着Docker Hub和TreeScale等公共Docker Registry变得越来越流行管理员和开发人员下载不明来源的Docker镜像变得越来越常见。在大多数情况下便利性胜过可预知的风险。在通常情况下当一个Docker镜像被发布后它会直接出现在列表中、git仓库中或通过相关链接提供。有时候镜像并没有提供Dockerfile。即使提供了Dockerfile我们也很难保证预构建的镜像就是由给出的Dockerfile构建的这些镜像对于我们而言 就是一个黑盒子我们甚至无法保证其使用的安全性。也许您并不关心安全漏洞, 您可能只是想更新平时用的比较多的镜像 例如nginx使其能够运行在最新版本的Ubuntu上。又或者你会想要发布一个更优化的镜像因为另一个发行版的编译器更适合在编译时生成二进制文件。不管什么原因我们都需要将镜像恢复成Dockerfile的选项。Docker镜像并不是一个黑盒子。重建Dockerfile所需的大部分信息都可以被检索到。通过观察Docker镜像内部并检查其内部结构我们将能够从一个任意的预编译容器中重建一个Dockerfile。在本文中我们将展示如何使用两个工具从镜像中重建Dockerfile 前面提及的Dedockify是一个的Python脚本Dive是一个Docker镜像浏览工具。使用的基本流程如下。使用 DiveDive demo为了快速了解镜像是如何组成的我们将使用Dive学习一些高级的、可能对我们来说不熟悉的Docker概念。Dive工具可以检查Docker镜像的每一层(Layer)。让我们创建一个简单的_Dockerfile_用于测试。把这个代码段直接贴到装有Docker的linux主机命令行中mkdir $HOME/test1 cd $HOME/test1 cat Dockerfile EOF ; touch testfile1 testfile2 testfile3 FROM scratch COPY testfile1 / COPY testfile2 / COPY testfile3 / EOF输入以上内容并回车我们就创建了一个新的Dockerfile并在同一目录下填充了3个零字节的测试文件。$ ls Dockerfile testfile1 testfile2 testfile3现在让我们使用这个Dockerfile构建一个镜像并标记为example1。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镜像中查看存储层(Layer)。我们可以从镜像的大小看出这里没有源镜像。我们使用scratch来代替源镜像它让Docker使用一个零字节的空白镜像作为源镜像。然后我们通过复制三个额外的零字节测试文件来修改空白镜像并将修改标记为example1。现在让我们用Dive来查看这个新镜像。docker run --rm -it \ -v /var/run/docker.sock:/var/run/docker.sock \ wagoodman/dive:latest example1执行上述命令将自动从Docker Hub拉取wagoodman/dive镜像并产生Dive的输出。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...在列表中上下选择镜像的三个图层在右侧显示的目录树中找到三个文件。我们可以看到右侧的内容随着选择每一层而变化。当每个文件被复制到一个空白的Docker从头镜像时它被存储为一个新的层。如果您注意到的话 我们还可以看到生成每个层所使用的命令。我们还可以看到源文件和更新文件的哈希值。如果我们注意到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… 0BCREATED 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 Engine API。使用 Python Docker Engine APIDocker发布了一个针对Docker Engine API的Python库允许在Python中管理Docker。在下面的示例中我们可以通过运行下面的Python 3代码来恢复与docker history类似的信息#!/usr/bin/python3 import docker cli docker.APIClient(base_urlunix://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指令中有一些被哈希(Hash)过的内容。如前所述这里的被哈希过的内容代表从层外使用的文件名。这些信息无法直接恢复。然而正如我们在 Dive 中看到的当我们搜索对该镜像层所做的更改时我们可以推断出这些名称。有时在原始复制指令将目标文件名作为目标的情况下也可以推断出这些文件名。在其他情况下文件名可能并不重要允许我们使用任意文件名。而在其他情况下虽然更难评估但我们可以推断出在系统中其他地方被反向引用的文件名例如在脚本或配置文件等支持依赖中。但无论如何搜索层之间的所有变化是最可靠的。Dedockify让我们再深入几步。为了更好地逆向该镜像转换为Dockerfile我们需要解析所有内容并将其重新格式化为可读的形式。为了简化我们的实验 以下代码已经可以从GitHub上的Dedockify仓库获取。感谢 LanikSJ 所有基础工作和编码。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_urlunix://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, recFalse): 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在我们使用dedockify对example1镜像中运行此命令最终将产生以下结果$ python3 dedockify.py 374e0127c1bc FROM example1:latest COPY file:e3c862873fa89cbf2870e2afb7f411d5367d37a4aea01f2620f7314d3370edcc in / COPY file:2a949ad55eee33f6191c82c4554fe83e069d84e9d9d8802f5584c34e79e5622c in / COPY file:aa717ff85b39d3ed034eed42bc1186230cfca081010d9dde956468decdf8bf20 in /我们提取到的信息与之前使用 Dive 解析镜像时看到的几乎一致。注意FROM指令显示的是example1:late而不是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_VERSION19.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-component1 \ mv docker /usr/local/bin COPY file:8385774b036879eb290175cc42a388877142f8abf1342382c4d0496b6a659034 in /usr/local/bin/ ENTRYPOINT [/usr/local/bin/dive]与example1相比这个镜像显示了更多的错误。我们看到ADD指令就在FROM指令之前。我们的代码再次做出了错误的假设。我们不知道ADD指令添加了什么。然而我们可以直观地做出假设即我们不确定基础镜像是什么。ADD指令可能是用来提取本地的tar文件到根目录。也有可能是用这种方法加载另一个基础镜像。Dedockify limitation testing让我们通过创建一个示例Dockerfile来进行实验在这个示例中我们明确定义了基础镜像。和我们之前做的一样在一个空目录下直接从命令行运行下面的代码。mkdir $HOME/test2 cd $HOME/test2 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然后build镜像将我们的新镜像标记为example2。这将创建一个与之前类似的镜像只不过不使用scratch而是使用ubuntu:latest作为基础镜像。$ 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由于我们现在有了一个稍微复杂一些的Dockerfile来重建而且我们也有了生成这个镜像所使用的Dockerfile因此我们可以做一个对比。$ 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运行dedockify脚本$ 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。通过添加一个小的二进制可执行文件该镜像已具备运行的功能。在Dedockify的GitHub仓库中可以找到源代码。由于这个镜像非常小我们不需要构建或拉取它。我们可以通过下面的代码段将整个容器复制粘贴到我们的Docker环境中来展示我们的命令行技巧 uudecode EOF | zcat | docker load begin-base64 600 - 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 3v/sBK8Cy4O/yfOL/y/lcnWOn/rvF/hf8vKCgoaBXGQgwYvEKWwNmY0euo As/cks/mFGMgBIBfD7jaJtrHaMnhx1ZMAF9a/M/6dL/d4HGm9r/qfS/3eC wv/dbzS2/7bmfyr23wmW/F/TanCR/7PKBZAyeI7SuGDqd0Y9ahVYZj6nJFFq pZS2UieBDAC05HRxNBKE06nwfx3yf43tv6X5n3iJ/0Er96ovHP8X/H/O8FK /3eN/yu//xUUFBS0iqbOekv8nzSs9P9doLH23n/V4ky/1cnKPzf/UZj2H /wNd7L8TLPm/NXB2K/5vDbGEhf/riv9rbP/t8H/Ayvt/naDE/xX9L/S/mrxv /Wnc7P9f1PC/5OyxP8WFBQUtIqmizW1Ff9Xv9u0Fj/Lc3/Z4r/3wkK/3e/ 0djW4r/K/P/dIMl/7eGNRtX/N8aYgkL/9cV/9fY/lua/0xYv9doMT/Ff0v 9L9atWL9abwV/1fi/wsKCgpaRdPJetqK/6vX/y39f/torPW4v94f2vExT 736jsf23FPniv13glf8X/M51b8X1lL5H3i/5rafzv8n9Bl/a9OcPfW/1jy f8X/7wQr/a/WMF5/Gm/F/xXv6CgoKBVJPK8tUkQIUjIXCXMBoJj0oPO3Btu jJc86SyRWaFFDOhQ8QxKJ8uya2n9D6bKhdoLH25n/j/My/0cnKPzf/UZj 2/O/wmQr63/a0r8TzdY8n9Nq8FF/oxoB1a6zBk51lkXpMADhmswByzEBd AG5V5skJj1zaSKm4aOhYEpf4P8EE22KwxcSugIHiAwF9KbR2grlLRGCdsKrf XuUQrJe25hYlS5SRHDFpoxNDACOzDxhToCpmUcUYc5JJRMWNZjfzihdTuGsc 41719NPdR59sH8m26NJ9KPtWRiOBxf2V7vnJxYbZ7v0Ue0vScq9qt7dnh3Q o2xFvjwo/Hk6ONezUc2X329dOdx092e3vPq21/dLR9gKPR5Hm1X9/itic HXih9EB4CIFjBOm8wKB4CIxMnRkbubaokRoGgd5zFUwEBcIw5FkxRbpC4/AG frRO/gpHunchT3WGfoEtPTk93azSJH5Hqv4ep7PhZLxgm/sMvqsItx9Wnmd Kr/MS9919S3JbjNB8M5xvnxtM61P0y6tst3uJ724bG/X9z/l9Pv4DXd7/ 6Qa1sbWdxhuP/znXqsT/doJVY9tiGrX9X6t/Y67w/9SPlPF/J/jnw8//8sFq 78Fvf7Xx640/0Hf9/xs68h9OG///c8//am38cHGv376VFdWTZ7P0ymo/TH 3727jBcUFBQUNMKhHw8zzub9W/B4b4sb/H8O0lzyBemIEuX9n06wd1I9WFId FVPOgzU2oVQSAkqeKdN1IASaKiPUrsFr2aNy0Znn8hNTzlz453ywBZ1iIbf z/BosutfzOrRPP7oD49GCIORryNM6iH95/WIcXGyaaTgeCTUl1fJBsJW9y uCRsfXPskbD1LdhRl9na3v4lYU1530vC1sdLVPun8VP/SVMyVZnw/lkOsRZ W2nc9PsvB7ja/pNBlPa/C5ysWuia61420mv4Pej0tNhcQUFBwV3GfwHMszUX AMIAAA EOF直接从命令行运行后会加载一个新的镜像 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如果您向下选择到最后一层您将能够看到所有丢失的数据填充到右侧的目录树中。每个目录都复制了名为testfile1、testfile2和testfile3的零字节文件。在最后一层中一个名为hello的63字节文件被复制到了/app目录中。让我们恢复这些文件由于无法直接从镜像中复制文件因此我们需要先创建一个容器。$ docker run -td --name example3 example3:latest 6fdca182a128df7a76e618931c85a67e14a73adc69ad23782bc9a5dc29420a27现在让我们使用下面从Dive恢复的路径和文件名将我们需要的文件从容器复制到主机。mkdir $HOME/test3 cd $HOME/test3 docker cp example3:/testdir1/testfile1 . docker cp example3:/testdir2/testfile2 . docker cp example3:/testdir3/testfile3 . docker cp example3:/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!程序。实际上在这种情况下Hello, world!程序并不是被设计成始终运行的。在19.03.6版本的Docker中可能存在一个错误导致程序无法正常终止。目前这是可以接受的。容器可以活动或停止应用程序不需要持久化来恢复我们需要的任何数据。处于任何状态的容器都只需要从我们正在提取数据的源镜像中生成。通过运行恢复的可执行文件来验证其行为我们应该看到以下内容$ ./hello Hello, world!使用我们之前生成的Dockerfile我们可以更新它以包含所有新的细节。这包括将FROM指令更新为从头开始以及我们在使用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此镜像显示的文件与原镜像相同。将两个镜像并排比较它们都完全匹配。两者显示的文件大小相同。两者的功能完全相同。以下是用于生成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 - from0 /app/testfile testfile1 WORKDIR /testdir2 COPY - from0 /app/testfile testfile2 WORKDIR /testdir3 COPY - from0 /app/testfile testfile3 WORKDIR /app COPY - from0 /app/hello hello ENTRYPOINT [/app/hello]我们可以看到虽然我们不能完美地重建它但我们能够大致重建它。像这样使用多阶段(stage)构建的Dockerfile是无法重建的。这些信息根本就不存在。我们唯一的选择是重建我们实际拥有的镜像的Dockerfile。如果我们有早期构建阶段的镜像我们可以为每个阶段重构一个Dockerfile但在这种情况下我们只有最终构建阶段的镜像。但不管怎样我们还是成功地从Docker镜像中重现了一个有用的Dockerfile。后记通过使用与Dive类似的方法我们应该能够更新Dedockify的源代码使其能够自动分析每一层以恢复所有有用的文件信息。此外该程序还可以更新为能够自动从容器中恢复文件并将其存储到本地同时还能自动对Dockerfile进行适当的更新。最后还可以对程序进行更新使其能够轻松推断出基础层是否使用了Scratch或其他基础镜像。通过对恢复的Dockerfile语法进行一些额外的修改Dedockify有可能被更新为在大多数情况下完全自动地将Docker镜像逆向工程为一个功能性的Dockerfile。如何系统学习网络安全网络安全作为数字时代的核心技术基石已成为保障各行业安全稳定运行的坚实屏障。筑牢网络安全的防线掌握先进的防护策略和技能正上升为国家与企业发展的战略优先级。构建稳固的网络安全能力是一个体系的工程需要从夯实基础理论出发持续深化到攻防对抗与应急响应的实战层面。如果你是准备学习网络安全黑客或者正在学习我可以把我自用的360独家内部资料分享给你包含以下内容你应该能用得上①网络安全学习路线②20份渗透测试电子书③安全攻防357页笔记④50份安全攻防面试指南⑤安全红队渗透工具包⑥网络安全必备书籍⑦100个漏洞实战案例⑧安全大厂内部视频资源⑨历年CTF夺旗赛题解析一、网络安全黑客学习路线网络安全黑客学习路线形成网络安全领域所有的知识点汇总它的用处就在于你可以按照上面的知识点去找对应的学习资源保证自己学得较为全面。二、网络安全教程视频我们在看视频学习的时候不能光动眼动脑不动手比较科学的学习方法是在理解之后运用它们这时候练手项目就很适合了。三、网络安全CTF实战案例光学理论是没用的要学会跟着一起敲要动手实操才能将自己的所学运用到实际当中去这里带来的是CTFSRC资料HW资料毕竟实战是检验真理的唯一标准嘛~四、网络安全面试题最后我们所有的作为都是为就业服务的所以关键的临门一脚就是咱们的面试题内容所以面试题板块是咱们不可或缺的部分这里我给大家准备的就是我在面试期间准备的资料。网安其实不难难的是坚持和相信自己我的经验是既然已经选定网安你就要相信它相信它能成为你日后进阶的高效渠道这样自己才会更有信念去学习才能在碰到困难的时候坚持下去。机会属于有准备的人这是一个实力的时代。人和人之间的差距不在于智商而在于如何利用业余时间只要你想学习什么时候开始都不晚不要担心这担心那你只需努力剩下的交给时间这份完整版的网络安全学习资料已经上传CSDN朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费】

相关推荐

如何设置目录源码解析从入门到精通
如何设置目录源码解析从入门到精通

如何设置目录源码解析从入门到精通 报错一堆看不懂 StackTrace?别慌,这通常是你在处理文件路径时踩了坑。很多开发者在编写工具脚本或构建系统时,总卡在“如何设置目录”这一步,以为只是简单的 os.mkdir… · 2026/9/23 10:58:54

电火花加工相变与COMSOL多物理场建模解析
电火花加工相变与COMSOL多物理场建模解析

1. 电火花加工中的相变现象解析电火花加工(EDM)本质上是通过脉冲放电产生的瞬时高温使金属材料发生相变蚀除的过程。当电极与工件之间的间隙达到击穿电压时,会产生温度高达8000-12000K的等离子体通道,这个微观尺度下的能量集中现象… · 2026/9/23 10:58:47

YOLO打火机检测:X光安检小目标识别实战指南
YOLO打火机检测:X光安检小目标识别实战指南

简介:本资源是面向计算机视觉与安防检测领域的YOLO目标检测实践数据集,专为机场X光安检场景中打火机识别任务设计,适用于深度学习初学者、算法工程师及安检系统研发人员。数据集包含2119个真实安检场景图像样本,其中706张JPG格式原… · 2026/9/23 10:58:47

Python实现手机操作日志采集与分析实战
Python实现手机操作日志采集与分析实战

1. 项目背景与核心价值手机操作日志采集与分析是移动应用开发、用户体验优化以及质量保障领域的基础性工作。传统的手动测试和基础埋点往往存在两个痛点:一是测试覆盖率有限,难以捕捉真实用户场景中的异常情况;二是日志数据分散,缺… · 2026/9/23 12:12:27

Krill-based Algorithm(KBA):面向高维非凸工程优化的鲁棒群智能算法
Krill-based Algorithm(KBA):面向高维非凸工程优化的鲁棒群智能算法

1. 这不是又一个“仿生算法”噱头:Krill-based Algorithm(KBA)到底在解决什么真问题?你可能已经刷到过“鲸鱼优化”“蜻蜓算法”“海豚回声定位”这类名字听着像海洋纪录片片名的算法——它们被统称为“群智能优化算法”&#xff… · 2026/9/23 12:12:27

电压增益与dB值换算全解析:从20log到放大电路增益计算
电压增益与dB值换算全解析:从20log到放大电路增益计算

搞懂电压增益和dB值换算,调电路心里就有底了。这些年测试放大器、调音频设备,经常碰到有人拿着万用表测完输出电压,却算不清增益到底是多少dB。说实话这玩意儿不难,但20log和10log老有人搞混,分压电阻对增益的影响也容… · 2026/9/23 12:12:27

rdseed 5.3.1 Linux编译与SEED/SAC格式转换实战指南
rdseed 5.3.1 Linux编译与SEED/SAC格式转换实战指南

简介:rdseedv5.3.1 是一款运行于 Linux 环境的地震数据处理工具,核心功能是将 SEED 格式的地震观测数据转换为 SAC 可识别的格式,面向地震学研究者、台站数据处理人员及具备一定 Linux 命令行基础的科学计算用户。压缩包共 454 个文件&#x… · 2026/9/23 12:12:27

Dart SDK版本发布机制揭秘:实验特性从Flag引入到退役的完整生命周期
Dart SDK版本发布机制揭秘:实验特性从Flag引入到退役的完整生命周期

Dart SDK版本发布机制揭秘:实验特性从Flag引入到退役的完整生命周期 【免费下载链接】sdk The Dart SDK, including the VM, JS and Wasm compilers, analysis, core libraries, and more. 项目地址: https://gitcode.com/gh_mirrors/sdk1/sdk Dart SDK 是 D… · 2026/9/23 12:12:21

京东云大促底色:高并发电商系统的确定性工程实践
京东云大促底色:高并发电商系统的确定性工程实践

1. 项目概述:一场大促背后的云基建真相“双11背后,再看京东云的「底色」”——这个标题乍看像一篇媒体评论,但对做过电商系统运维、参与过大促保障、或者亲手搭过高并发订单链路的人来说,它根本不是修辞,而是一道实打实… · 2026/9/23 12:12:21

3招搞定手机怎么下载微信面试难题实战项目解析
3招搞定手机怎么下载微信面试难题实战项目解析

3招搞定手机怎么下载微信面试难题实战项目解析 面试被问“手机怎么下载微信”背后的原理,90%的人答不上来。别笑,这看似弱智的问题,实则是考察你对移动应用分发机制、安全校验及网络协议理解的试金石。我带过不少校招新人,他们背了八股文,却连一个A… · 2026/9/23 0:00:03

你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型
你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型

你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型 面试被问“高并发下如何保证消息不丢失”,你张口就是“用Redis”,结果面试官追问“如果Redis宕机了怎么办”,你瞬间卡壳。这种场景太常见了,很多新手在背八股文时,只记住了技术名词… · 2026/9/23 0:00:29

Win7无线热点配置工具源码解析:解决API失效的3个实战技巧
Win7无线热点配置工具源码解析:解决API失效的3个实战技巧

Win7无线热点配置工具源码解析:解决API失效的3个实战技巧 Win7无线热点配置工具在Win10/11上跑不动?不是你的问题,是版本升级后 API 全变了。很多老项目里的 netsh wlan… · 2026/9/23 0:00:36

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码