NKCTF 2024 1z_F0r3ns1c5 Writeup
写在前面
整体出题思路是前两个为常规取证点,最后一个稍微套了一下,上上强度(赛方要求,不过后期上了 hint
)。原本打算该题弄成三个 flag
来着,后来考虑到一血有定制礼品不好计算只好作罢,最后就变成一个 flag
了
另外今年貌似参赛选手不太热衷取证,取证题型三道题均攻克率不高
HackMyCQL - 2解
1z_F0r3ns1c5 - 2解
cain_is_hacker - 3 解
1z_F0r3ns1c5
本鼠鼠正在Coding,突然一声OPEN THE DOOR!本鼠鼠直接鼠躯一颤就双手抱头蹲下了,果然本鼠鼠只适合生活在阴暗的下水道 被黑猫警长抓走的时候本鼠鼠还想辩解一下,但是他们拿出你的照片的时候,本鼠鼠认罪了 昨晚和其他鼠鼠聊天的时候其他鼠鼠问本鼠鼠:“你到底喜欢她什么啊?” “喜欢一个人不需要理由” 本鼠鼠很快敲完了键盘,刚要按下回车的时候突然愣住了。 真的不需要理由吗? 请找到鼠鼠的答案吧。
下载附件后,总共会得到三个东西:内存镜像、描述文件和压缩包 (加密容器、马赛克图片)
本鼠鼠的flag总共分为三段捏,flag为nkctf{uuid}形式,另外鼠鼠最喜欢等宽字体了,快快去找吧。
flag01
.\volatility.exe -f .\1.raw --profile=Win7SP1x64 envars | Select-String 'n0wayback'
可以发现一个键名为 n0wayback
的环境变量
n0wayback HPahXR4NvAnZXB16tNK6hAaNVNU++
直接 XXencode 或 随波逐流
一键解码即可
HPahXR4NvAnZXB16tNK6hAaNVNU
// nkctf{39c429eb-2faf
flag02
通过查询进程(pslist
)或获取屏幕截图(screenshot
,但是比较抽象)可以看到当前正在运行 mspaint
、cmd
和 code
.\volatility.exe -f .\1.raw --profile=Win7SP1x64 pslist
0xfffffa8001a022a0 mspaint.exe 2052 1028 6 120 1 0 2024-03-04 05:50:22 UTC+0000
0xfffffa8003c68a80 cmd.exe 4188 1028 3 111 1 0 2024-03-04 05:50:26 UTC+0000
0xfffffa800418c060 Code.exe 888 1028 31 696 1 0 2024-03-04 05:52:52 UTC+0000
既然存在 mspaint
进程,我们直接通过 memdump
导出后,使用 Gimp
进行还原即可
.\volatility.exe -f .\1.raw --profile=Win7SP1x64 memdump -p 2052 -D ./
宽高可以尝试使用常见的显示屏分辨率,诸如 1920*1024
、1024*768
之类的,这里没有标准的值,大差不差即可。
随后就是不断地进行偏移量的调整即可
-49a0-bd24-
flag03
.\volatility.exe -f .\1.raw --profile=Win7SP1x64 consoles
通过 consoles
发现通过 git clone
下载了一份源码
git clone https://github.com/5ime/Secret_Generator.git
我们直接访问该地址,首先写明需要 Docker
启动,同时让我们找到源码代码在哪
Compilable with Dockerfile or Python 3.7.2 only.
Hey, hold on a second... Where's my source code?
我们在 Commits 中看到了源代码,直接手动 git clone
后,通过 git reset
即可得到源代码
根据 README
中要求,我们通过 Docker
对其进行部署
访问 8080
端口,得到一个 Secret Generator
,要求我们输入 加密文字
和 上传字体
结合最初 描述文件
中提到 鼠鼠最喜欢等宽字体了
,以及题目描述开头提到 本鼠鼠正在Coding
,联想到进程中的 Vscode
直接百度搜索其配置文件默认路径即可得到 Vscode
中所使用的等宽字体
// C:\Users\moe\AppData\Roaming\Code\User\Settings.json
{
"editor.fontFamily": "'Fira Code', Consolas, 'Comrier New', monospace",
"window.zoomLevel": 1,
"security.workspace.trust.untrustedFiles": "open"
}
我们直接下载 Fira Code 即可,注意下载后会存在多个粗细的字体,默认情况下直接使用 Regular
即可
剩下的解题方法基本和 CISCN 2023 国粹
一题类似,唯一的区别就是我们需要手动生成一张表(这也是前面需要找到字体的原因)
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789
我们发现通过指定字母表进行生成图片时,后端代码对我们进行了字符串拼接,多出来了 pass[空格]
这五个字符
secret = 'pass ' + secret
for i in range(5, len(secret)-1):
mosaic_img(canvas, W*i, 0, W*i+W, H//2)
mosaic_img(canvas, W*i, H//2, W*i+W, H)
这里提供两种方法,字母表前面加个 AAAAA
字符,以及在末尾多加一个 9
,原因在于前五位和最后一位不加密,且前五位为固定的 pass[空格]
,当我们添加 AAAAA
时在切割的时候,前五个字符会直接被覆盖(文件命名时不允许存在同名文件)
另外一种方法,直接稍微改一下代码逻辑即可
# 可改为如下形式
# 注释掉 secret 变量的字符串拼接,以及 range 范围从 0 开始
# secret = 'pass ' + secret
for i in range(len(secret)):
mosaic_img(canvas, W*i, 0, W*i+W, H//2)
mosaic_img(canvas, W*i, H//2, W*i+W, H)
下面,我们直接通过 Python 的 PIL
库对每个字符进行裁剪,所以需要知道每个字的宽高,代码中也写明了宽高
另外,你也可以通过 图片宽度 / 字符数量
计算得到每个字符所占宽度
H = 60
W = 30
下面直接人工写代码或 PUA AI
帮你写代码即可,话术如下
我有一张
xxx.png
图片,宽高为1086*60
,请通过 Python 的PIL
库将它裁剪为30*60
的图片,切割出来的图片保存到dict
文件夹中,另外切割出的图片命名规则根据如下命名表ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789
最后,如果文件名为大写字母则
文件名-大写.png
这里我加了一个判断,如果是大写字母则 文件名-大写.png
,原因在于 Windows
下 A.png
和 a.png
不能共存,Linux
无此烦恼。
下面的代码来源于 ChatGPT 3.5
(当然,自己写也行),分别对我们自行生成 dict.png
和附件提供的 pass.png
进行切割
from PIL import Image
import os
# 打开图片
img = Image.open("dict.png")
# img = Image.open("pass.png")
# 获取图片尺寸
width, height = img.size
# 定义切割尺寸
tile_width = 30
tile_height = 60
# 确保切割后的图片尺寸可以被整除
assert width % tile_width == 0 and height % tile_height == 0
# 创建 dict 文件夹
if not os.path.exists("dict"):
os.makedirs("dict")
# if not os.path.exists("pass"):
# os.makedirs("pass")
# 定义命名规则
char_set = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
# 切割图片并保存
for i in range(width // tile_width):
for j in range(height // tile_height):
tile = img.crop((i * tile_width, j * tile_height, (i + 1) * tile_width, (j + 1) * tile_height))
index = i * (height // tile_height) + j
filename = f"dict/{char_set[index]}"
# filename = f"pass/{char_set[index]}"
# 如果文件名为大写字母,则添加 '-大写' 后缀
if char_set[index].isupper():
filename += "-大写"
filename += ".png"
tile.save(filename)
print("切割完成")
切完图后,我们进行比对一下即可得到最终内容,这里可以用 np.sum
、sha256
或 md5
之类的均可,只要能代表唯一。防止有人不会写代码,接着用 AI
吧
通过 Python 读取
dict
文件夹中的所有文件,获取该文件的md5
,输出形式为字典,格式为文件名:md5值
,将其存到dict
字典中,pass
文件夹进行相同的操作,将其存到pass
字典中将
pass
字典中的value
与dict
字典中的value
进行比对,如果两个value
相等,则输出dict
的key
这里生成的代码就有些许误差了,但是大部分逻辑是没问题的,自己手动改改即可
import os
import hashlib
def calculate_md5(filename):
"""计算文件的 MD5 值"""
hasher = hashlib.md5()
with open(filename, 'rb') as f:
for chunk in iter(lambda: f.read(4096), b""):
hasher.update(chunk)
return hasher.hexdigest()
def get_file_md5(folder):
"""获取文件夹中所有文件的 MD5 值"""
file_md5_dict = {}
for file_name in os.listdir(folder):
file_path = os.path.join(folder, file_name)
if os.path.isfile(file_path):
file_md5_dict[file_name] = calculate_md5(file_path)
return file_md5_dict
# 读取 dict 文件夹中的所有文件的 MD5 值并存储到字典中
dict_folder = "dict"
dict_md5 = get_file_md5(dict_folder)
# 读取 pass 文件夹中的所有文件的 MD5 值并存储到字典中
pass_folder = "pass"
pass_md5 = get_file_md5(pass_folder)
# 找到 pass 文件夹中与 dict 文件夹中 MD5 值相同的文件名对应的 dict 中的 key
common_keys = [key for key, value in pass_md5.items() if value in dict_md5.values()]
print("Pass 文件夹中与 Dict 文件夹中 MD5 值相同的文件名:")
flag = ''
for key in common_keys:
matching_keys = [k for k, v in dict_md5.items() if v == pass_md5[key]]
for matching_key in matching_keys:
flag += matching_key.split('.png')[0]
print(flag + '3')
运行后即可得到马赛克隐藏的 VeraCrypt
容器密码
b143a6268e2a233
VeraCrypt
挂载后即可得到 flag3
,最终 flag
如下
nkctf{39c429eb-2faf-49a0-bd24-c4f222879312}