NKCTF 2024 1z_F0r3ns1c5 Writeup

10 min read

写在前面

整体出题思路是前两个为常规取证点,最后一个稍微套了一下,上上强度(赛方要求,不过后期上了 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'

image-20240324154714864

可以发现一个键名为 n0wayback 的环境变量

n0wayback	HPahXR4NvAnZXB16tNK6hAaNVNU++

直接 XXencode随波逐流 一键解码即可

HPahXR4NvAnZXB16tNK6hAaNVNU
// nkctf{39c429eb-2faf

flag02

通过查询进程(pslist)或获取屏幕截图(screenshot,但是比较抽象)可以看到当前正在运行 mspaintcmdcode

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

image-20240324154726984

既然存在 mspaint 进程,我们直接通过 memdump 导出后,使用 Gimp 进行还原即可

.\volatility.exe -f .\1.raw --profile=Win7SP1x64 memdump -p 2052 -D ./

宽高可以尝试使用常见的显示屏分辨率,诸如 1920*10241024*768 之类的,这里没有标准的值,大差不差即可。

随后就是不断地进行偏移量的调整即可

-49a0-bd24-

image-20240324154449926

flag03

.\volatility.exe -f .\1.raw --profile=Win7SP1x64 consoles

image-20240324154739326

通过 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 即可得到源代码

image-20240324154749015

根据 README 中要求,我们通过 Docker 对其进行部署

image-20240324154953798

访问 8080 端口,得到一个 Secret Generator,要求我们输入 加密文字上传字体

image-20240324143415899

结合最初 描述文件 中提到 鼠鼠最喜欢等宽字体了,以及题目描述开头提到 本鼠鼠正在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 即可

image-20240315122909273

剩下的解题方法基本和 CISCN 2023 国粹 一题类似,唯一的区别就是我们需要手动生成一张表(这也是前面需要找到字体的原因)

ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789

image-20240324145041345

我们发现通过指定字母表进行生成图片时,后端代码对我们进行了字符串拼接,多出来了 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)

image-20240324143801054

下面,我们直接通过 Python 的 PIL 库对每个字符进行裁剪,所以需要知道每个字的宽高,代码中也写明了宽高

另外,你也可以通过 图片宽度 / 字符数量 计算得到每个字符所占宽度

H = 60
W = 30

下面直接人工写代码或 PUA AI 帮你写代码即可,话术如下

我有一张 xxx.png 图片,宽高为 1086*60 ,请通过 Python 的 PIL 库将它裁剪为 30*60 的图片,切割出来的图片保存到 dict 文件夹中,另外切割出的图片命名规则根据如下命名表

ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789

最后,如果文件名为大写字母则 文件名-大写.png

这里我加了一个判断,如果是大写字母则 文件名-大写.png,原因在于 WindowsA.pnga.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.sumsha256md5 之类的均可,只要能代表唯一。防止有人不会写代码,接着用 AI

通过 Python 读取 dict 文件夹中的所有文件,获取该文件的 md5,输出形式为字典,格式为 文件名:md5值,将其存到 dict 字典中,pass 文件夹进行相同的操作,将其存到 pass 字典中

pass 字典中的 valuedict 字典中的 value 进行比对,如果两个 value 相等,则输出dictkey

这里生成的代码就有些许误差了,但是大部分逻辑是没问题的,自己手动改改即可

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}