逆向工程:將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映象瀏覽工具。使用的基本流程如下。

使用 Dive

Dive 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.584

kB

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

374e0127

c1bc

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的輸出。
Unabletofindimage'wagoodman/dive:latest'locally
latest:Pullingfromwagoodman/dive
89d9c30c1d48:Pullcomplete
5ac8ae86f99b:Pullcomplete
f10575f61141:Pullcomplete
Digest:sha256:2d3be9e9362ecdcb04bf3afdd402a785b877e3bcca3d2fc6e10a83d99ce0955f
Status:Downloadednewerimageforwagoodman/dive:latest
Image Source:docker://example-image
Fetchingimage...(thiscantakeawhileforlargeimages)
Analyzingimage...
Buildingcache...
在列表中上下選擇映象的三個圖層,在右側顯示的目錄樹中找到三個檔案。
我們可以看到右側的內容隨著選擇每一層而變化。當每個檔案被複制到一個空白的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… 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 Engine API。

使用 Python Docker Engine API

Docker釋出了一個針對Docker Engine API的Python庫,允許在Python中管理Docker。在下面的示例中,我們可以透過執行下面的Python 3程式碼來恢復與docker history類似的資訊:
#!/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指令中有一些被雜湊(Hash)過的內容。如前所述,這裡的被雜湊過的內容代表從層外使用的檔名。這些資訊無法直接恢復。然而,正如我們在 Dive 中看到的,當我們搜尋對該映象層所做的更改時,我們可以推斷出這些名稱。有時,在原始複製指令將目標檔名作為目標的情況下,也可以推斷出這些檔名。在其他情況下,檔名可能並不重要,允許我們使用任意檔名。而在其他情況下,雖然更難評估,但我們可以推斷出在系統中其他地方被反向引用的檔名,例如在指令碼或配置檔案等支援依賴中。但無論如何,搜尋層之間的所有變化是最可靠的。

Dedockify

讓我們再深入幾步。為了更好地逆向該映象轉換為Dockerfile,我們需要解析所有內容並將其重新格式化為可讀的形式。為了簡化我們的實驗, 以下程式碼已經可以從GitHub上的Dedockify倉庫獲取。感謝 LanikSJ 所有基礎工作和編碼。
from

sys

import

argv

import

docker

classImageNotFound

(

Exception

):

pass

classMainObj

:

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

andnot

rec:

break

first_tag =

True

self._insert_step(i[

'CreatedBy'

])

ifnot

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.6

MB

在我們使用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_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 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.584

kB

Step

1

/

7

: FROM ubuntu:latest

---> 72300a873c2c

Step

2

/

7

: RUN mkdir testdir1

---> Using cache

--->

41

10037ae26d

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.2

MB

example1 latest 374e0127c1bc

1

hour ago 0B

ubuntu latest 72300a873c2c

9

days ago

64.2

MB

wagoodman/dive latest 4d9ce0be7689

3

weeks ago

83.6

MB

執行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

-

H4sICMicXV4AA2V4YW1wbGUzLnRhcgDtXVtvG8cVVnp56UN/QJ/YDQokgETN

zJkrgTykjgsbDSzDURMkshDM5YzFhiJVkkpiCELzH/pP+tYfkf/UsxRNXdxI

spe7lqv5IJF7PTM7Z87MmY9nZxgL2qG1DkN2nkXmtTecQwYrMMfsBHgXgFuV

eXLCI5c26sxdNHQsie2Nm8GYZEYp+l7g6vdim4MWBrgyBjaY4MbIjZ66hezG

OJ7N/ZSyMp1M5tddd9P5qw/3noA11f+XD5998XjnybVpcMa0lNfoH67oHxiI

jV4nhXjP9c/7701WC1pAY/v/+2wyvimNG+zfgLpi/0KbYv+d4KQapmpQNa0G

1WZ15Kc4npMsr7JUjGGMyLUzPEXJc1RCWqFkTtoEL5TgEaLSKlmOiXHnUSlK

jYsQSFacop9jnTHuDNtinP52GRss/r6pL5iM5344xum3tJWHL6rBSfVoMpuP

/SHSXXTFZ5NDuuB8/28znJ5tfTqf+3jwxTwNx9Ug+9EMLxybHM9fP4jT6erg

7vzlanvnCMeX5Sz2dsYRV0cejr+vBuPj0WizenCYXm0+PvQvlhn7cjI6PsTZ

qzNfTabfDccvPhsuc/twPJ++PJoM66I9u2Jn/Ofj4Wgl6nMfcLS8/XSzmtBm

NRqOj3+sTm+h/8b2P/IvcdqvbeiX07je/uXr/h9oZor9dwF/dHQbF74R3sz/

F1RfuKbLi//fAWr993846C/+J0f/aCONRR9/g/4vbXNQShb7LygoKGgTTDkP

1tiEUvkgJajgnTZRA0pAplFqZ1m02hqXjc4+aaVTztx4pzywfvPxH7X1V/

0

/

oZgu7X8XOKn8NB4M5xjnx9N6ROIPk5ZnI6y7P67aq55+uvvok+3j2XR7NIl+

tD0Lw/Hgwv5q9/zEYuNslz6q/f85MJsd0CBVD4SHEDhGkM4LDIqHQN4JjU5s

5NqiJguRAr3nKpgICoRhyLNiCgHQOLxhfLdN7teVMd7e4uD2AY5Gkzpv14/

2

VuPgegyvDA3aOATr5cJkJUs0tMsRacytE6MsGZnp/piCEMaiijHmJJOIihvN

bh5WX0zh/7kqkA5od3t2QI+yFenjw4/Gk6OPe7Wqnuw++/rpzuMnu7295xdU

9bzar29/X6rPyenpRZZFMMG2GGwxsStgoPhAQF8KrZ1grqZb0iR+R5Xie5zO

hpPxgpbpM+hrOnUwnM0nU1LY3sm1AnnfOXCgDDPfnDM834aX9XOclXZvK/aW

Jf3VzrO/fvb4WW97jjPS95RXp5vXyxd9Qd0MSCnsLeQ/2Hn6dS8PRzgAIbLL

TiBEJ9Ej8hRZEmCURW6CcTaD8+h18BhtdsrJIFDGmBigS6w3HPfqTNbCeO8W

2SQjVBK4lW9RDOIW8hUZv+PG8XdWDOI2xWA4mYGkKvYWxQC3kO+YdMYxod5Z

McDNxQB9Zpkg38iJNymG2uxvFi005+RJwRsVQAAIkmURmaF+gwUXvAZjApis

tWaSJyWFoAaKOS1DFJrKR0omQSSNVvO6ABaNz20e/mITc0MOe9c1vJsVHh7N

X3674CKrwXx6jKf7l6jQzar233Ld8lXzl0d1E724eFY3bsOcvx2mWd14Lttt

riUXQXJAZFJn5MLriMHJTA6yT44zHZjHQJ2qZM5watCpBeeevFdMdANJXUkC

7ZMxjCq7TI6cbW+zUkEpqj/RMrBeWInCUuFqj3QZTxays9RrBNT+XBJDh2Qw

wQkfqXcAqjZK5RA5SNIJzywKSTKTMGiYjkoknwJjzgVg1vBwLilYbbwhFzsz

LYTTwqNgRiMEZhMYK7zztZ9gmHUZap8/0WOROCXRenUhT1Q8IlhltBbOihhR

ZhOtoucgwVRolD3DkqprS6bHZzlbmxMNOBJGRYVxLsmDcSGZJBkTjkdlEtW3

IKj4nA0oUJHDYgP5MZQ3qjHKZCrDqJSylvwZhAtPF5PRMfJEt4I0kjvms/Qq

U4VVnp6O6jD3PJpaujKcLqJqaNF5TlrR5lxS5jQ2IfVRv22MAhbInjAaiNIp

zkSGCAacVmAj0IPWDVoKPFCjQMXi0VX7p7eh4N8phI70kElxeg7vhJIKZYpa

qOy9J9Vpck3Qkx2I2qOMIUrSQ46GKrzkqHRb8R+cF/63CzTWfyvxH8LI8vtP

JyjxH/cbje1/DfEfWvDX4j8YL/bfBZbxH02rQYnZ6DBmY51obP/txH8oUX7/

7QSv+LU2g0DezP/nVF+EBij+fxdY6b/FIJC6PN4s/kOqWv/F/gsKCgrag2Jg

EIxFl3RyIWQB2bjkDIiYFeTIUuLJy6SNNUG4wKPl3GeXEgSfWuL/BNTxv6X/

bx+N9d/O+18gVen/u0Dh/+43Gtt/K+

9

/gWDF/jvBkv9rWg0uvf/lBSZQEqIi

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==

====

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.608

kB

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 -

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"

]

我們可以看到,雖然我們不能完美地重建它,但我們能夠大致重建它。像這樣使用多階段(stage)構建的Dockerfile是無法重建的。這些資訊根本就不存在。我們唯一的選擇是重建我們實際擁有的映象的Dockerfile。如果我們有早期構建階段的映象,我們可以為每個階段重構一個Dockerfile,但在這種情況下,我們只有最終構建階段的映象。但不管怎樣,我們還是成功地從Docker映象中重現了一個有用的Dockerfile。

後記

透過使用與Dive類似的方法,我們應該能夠更新Dedockify的原始碼,使其能夠自動分析每一層,以恢復所有有用的檔案資訊。此外,該程式還可以更新為能夠自動從容器中恢復檔案並將其儲存到本地,同時還能自動對Dockerfile進行適當的更新。最後,還可以對程式進行更新,使其能夠輕鬆推斷出基礎層是否使用了Scratch或其他基礎映象。透過對恢復的Dockerfile語法進行一些額外的修改,Dedockify有可能被更新為在大多數情況下完全自動地將Docker映象逆向工程為一個功能性的Dockerfile。
連結:https://www.cnblogs.com/Stephen/p/17561440.html
(版權歸原作者所有,侵刪)


相關文章