GoogleCTF 2022 - Appnote


Write-Up: Misc - Appnote.txt Google CTF 2022

题目

You are given an archive file that contains the flag somehow, but conventionally unpacking it does not lead to anything useful.

直接用二进制编辑器打开文件,发现:

  • hello.txt 里面是 “There’s more to it than meets the eye…”
  • `hi.txt 写着 “Find a needle in the haystack…” (大海捞针)然后是一些形如 flagXX 的文件。XX 在 00 和 18 之间,每个文件重复36次。提取全部txt内部的字符后,得到字符串:“abcdefghijklmnopqrstuvwxyz{CTF0137}_” .

这个字符串跟flag并无太大关联,但给了我们一些线索。我们注意到ZIP文件中还存在着其他信息。

EOCDs 的 hexdump:

0000eecc: 504b 0506 0000 0000 0100 0100 00ee 0000  PK..............
0000eedc: cc00 0000 b801 504b 0506 0000 0000 0100  ......PK........
0000eeec: 0100 5ae4 0000 880a 0000 a201 504b 0506  ..Z.........PK..
0000eefc: 0000 0000 0100 0100 93d7 0000 6517 0000  ............e...
0000ef0c: 8c01 504b 0506 0000 0000 0100 0100 ccca  ..PK............
0000ef1c: 0000 4224 0000 7601 504b 0506 0000 0000  ..B$..v.PK......
0000ef2c: 0100 0100 69bf 0000 bb2f 0000 6001 504b  ....i..../..`.PK
0000ef3c: 0506 0000 0000 0100 0100 ceb6 0000 6c38  ..............l8
0000ef4c: 0000 4a01 504b 0506 0000 0000 0100 0100  ..J.PK..........
0000ef5c: 29a5 0000 274a 0000 3401 504b 0506 0000  )...'J..4.PK....
0000ef6c: 0000 0100 0100 e79c 0000 7f52 0000 1e01  ...........R....
0000ef7c: 504b 0506 0000 0000 0100 0100 428b 0000  PK..........B...
0000ef8c: 3a64 0000 0801 504b 0506 0000 0000 0100  :d....PK........
0000ef9c: 0100 2186 0000 7169 0000 f200 504b 0506  ..!...qi....PK..
0000efac: 0000 0000 0100 0100 7173 0000 377c 0000  ........qs..7|..
0000efbc: dc00 504b 0506 0000 0000 0100 0100 6670  ..PK..........fp
0000efcc: 0000 587f 0000 c600 504b 0506 0000 0000  ..X.....PK......
0000efdc: 0100 0100 e359 0000 f195 0000 b000 504b  .....Y........PK
0000efec: 0506 0000 0000 0100 0100 ac52 0000 3e9d  ...........R..>.
0000effc: 0000 9a00 504b 0506 0000 0000 0100 0100  ....PK..........
0000f00c: a247 0000 5ea8 0000 8400 504b 0506 0000  .G..^.....PK....
0000f01c: 0000 0100 0100 8e33 0000 88bc 0000 6e00  .......3......n.
0000f02c: 504b 0506 0000 0000 0100 0100 9a2a 0000  PK...........*..
0000f03c: 92c5 0000 5800 504b 0506 0000 0000 0100  ....X.PK........
0000f04c: 0100 161c 0000 2cd4 0000 4200 504b 0506  ......,...B.PK..
0000f05c: 0000 0000 0100 0100 3815 0000 20db 0000  ........8... ...
0000f06c: 2c00 504b 0506 0000 0000 0100 0100 2f02  ,.PK........../.
0000f07c: 0000 3fee 0000 1600 504b 0506 0000 0000  ..?.....PK......
0000f08c: 0100 0100 34f0 0000 5000 0000 0000       ....4...P.....

每个分区都以 “PK” 开始,然后是8个无用的字节,4个字节表示大小,4字节表示偏移(offset)。

然后,将其“合法编码”为适当的 ZIP文件的方式是,它声明所有的 EOCD 都在前一个文件的注释字段中。

除其他字段外,ECDR 包含中央目录的大小(以字节为单位)和从文件开头开始的偏移字节,两者都是 4 字节的小端整数。所以从偏移字节开始会找到对应的CDH。CDH有很多字段,包括本地头的相对偏移量,它再次给出了从文件开头到相应LFH的偏移量。此外,CDH 和 LFH 都将压缩方法字段设置为“”,即不压缩。LFH 之后是我们的数据。

中央目录记录结束 => 中央目录头 => 本地文件头 => 数据

如果使用我们找到的大小和偏移量,并从文件中的“分段”中提取文本,得到了一系列字符串:

abcdefghijklmnopqrstuvwxyz{CTF0137}_
TF0137}_
F0137}_
0137}_
abcdefghijklmnopqrstuvwxyz{CTF0137}_
CTF0137}_
qrstuvwxyz{CTF0137}_
137}_
tuvwxyz{CTF0137}_
}_
nopqrstuvwxyz{CTF0137}_
137}_
efghijklmnopqrstuvwxyz{CTF0137}_
7}_
stuvwxyz{CTF0137}_
opqrstuvwxyz{CTF0137}_

{CTF0137}_
37}_
qrstuvwxyz{CTF0137}_
_

现在我们可以肯定这就是flag了。 如果假设flag格式是 CTF{****} 的话,那么就有如下结论:

  • 第一行是某种类似“字典”的转换规则
  • 所有后面的行都代表标志的一个字符,这里是截断位置前的那一个字符

编写脚本来提取flag:

import re

rex = b"flag[0-9][0-9](.)PK\x01\x02"
rexf = b"PK\x05\x06[\x00-\xFF]{8}([\x00-\xFF]{4})([\x00-\xFF]{4})"
rexm = b"(.)"
patt = b""
first = True

def collect(data):
    global first, patt
    if first:
        for datum in re.findall(rex, data):
            patt += datum
        patt = bytes(patt[-1]) + patt
        first = False
        return ""
    datum = re.findall(rex, data)[0]
    return patt[patt.rfind(datum)-1:patt.rfind(datum)].decode("ASCII")

with open("dump.zip", "rb") as dump:
    data = dump.read()
    for size_b, offset_b in re.findall(rexf, data):
        size = int.from_bytes(size_b, byteorder="little")
        offset = int.from_bytes(offset_b, byteorder="little")
        c = collect(data[offset:offset+size])
        print(c, end="")
        if c == "}":
            break
    print()
import re
content = open('dump.zip','rb').read()

def parse_int(b):
  return int.from_bytes(b,'little') # 注意:此处为小端序!

# 搜索EOCD记录
for m in re.finditer(b"PK\5\6", content):
  i = m.start()

  # 解析EOCD记录
  cent_dir_offset= parse_int(content[i+16:i+20])
  print(hex(i))
  # 解析CDH
  local_header_offset= parse_int(content[cent_dir_offset+42:cent_dir_offset+42+4])

  # 解析LFH
  compressed_size = parse_int(content[local_header_offset+18:local_header_offset+22])
  file_name_length = parse_int(content[local_header_offset+26:local_header_offset+28])
  content_start = local_header_offset+30+file_name_length
  comp_content = content[content_start:content_start+compressed_size]
  print(comp_content.decode("ascii"),end="")

最终的Flag是: CTF{p0s7m0d3rn_z1p}


文章作者: sfc9982
版权声明: 本博客所有文章除特別声明外,均采用 CC BY-NC-ND 4.0 许可协议。转载请注明来源 sfc9982 !
  目录