l3hctf - misc - WriteUp


碎碎念

首先先说一句:SU牛逼! 这次狠狠夺冠了,队里的大哥们太厉害了,这把也是给俺蹭到了)总的来说这次l3h的题目难度其实不算特别高,反而还有些特别简单 (以及特别恶心的)(没有针对出题人的意思) 但结果来说还是能做的,不过还是要继续学习啊qwq

欢迎各方向师傅大佬加入SU

有意愿加入SU的小伙伴请联系:suers_xctf@126.com 或者直接联系baozongwi师傅QQ:2405758945

量子双生影

知识点省流

ntfs数据流隐写+ai二维码变换+双图合并

WP

附件给了一个压缩包,打开后里面是一个webp图片,打开后不难看出是一个通过ai处理后的二维码

image-20250714200622508

这时候掏出我们的lovelyqrscanner扫描一下,发现提示我们这不是flag,那就需要再找找藏了什么,这里给了一个提示quantum但不知道是什么意思

image-20250714200702615

用7z打开压缩包,发现压缩包里其实还藏了一张图片,实际上这是ntfs数据流隐写(可参考这个文章https://joner11234.github.io/article/85357d8d.html)

用7z打开可以直接看到藏起来的文件,也可以用winrar解压后,用NtfsStreamsEditor去扫描目录

image-20250714200740283

提取出来后,可以发现第二张图片乱乱的,隐约中既有第一张的内容,又混杂了其他东西,所以用stegsolve去合并处理一下,最后可以得到另一张二维码

image-20250714201103395

接着扫描即可得到flag

image-20250714201157142

LearnRag

知识点省流

论文题

WP

https://github.com/vec2text/vec2text

https://arxiv.org/html/2401.12192v4

还在学习中)

Why not read it out?

知识点省流

魔改密文破译

WP

非常有意思的一道题,非常有意思

附件给了一个README文件,010看看发现是jpg图片,并且在末尾看到藏了一串倒转的base64,厨子处理一下得到提示:IGN Review

修改文件后缀打开内容如下

image-20250714203845016

可以看出图片中有一大段内容,而且是通过某种文字表达出来,所以我们对文字简单社工一下,确定这些文字来自于tunic这个游戏,这是一种由游戏作者自创的音标文字

f8d09996-732a-4a00-b4dd-3d458bc62746

随后,又找到了在b站上的一个视频,https://www.bilibili.com/video/BV1n541117Pi/,里面详细介绍了这种文字如何翻译成英文单词,这种文字将单词的音标划分为元音和辅音后,然后通过外圆内辅的构造方式拼凑出英文单词(红线为外,蓝线为内)

image-20250714205435689

然而,到这里本以为可以通过直接的对照去破译图片里的文字,结果发现完全不行,题目中的文字跟原版的规则完全对不上,不难猜到作者对文字进行了魔改。

这时候就需要用到前面给的提示:IGN Review,简单搜一下可以发现ign中对tunic这个游戏的官方测评只有一个,链接如下

https://www.ign.com/articles/tunic-review-xbox-pc-steam

进去后,如果有留意题目给的内容,会发现第一段文字其实有一些奇妙的熟悉感

image-20250714205733098

简单比对一下,可以明显的发现两边的符号是能很好的对上的,也就是说——图片中的密文所对应的明文的内容就是ign评测的第一段

d704c0a2-8a8e-4380-817f-3b36d7437627

至此有了重大的突破,既然有了明确的明文和对应密文,那我们就可以去进行逐一的比对后,从而破译别的密文(图片下面还有五行密文)

开始破译:

首先考虑的是出题人在魔改的时候是否带有某些规律,比如说对文字进行了反转,异或等处理,通过比对后发现并没有什么可以便于我们破译的规律,唯独只有一个规律,那就是作者把原本外圆内辅的构造方式改成了外辅内圆(byd破译的时候还得把外改内 内改外 工作量又大了)

接着开始进行手搓,借由https://lunar.exchange/tunic-decoder/ 这个web进行辅助,最终成功对新的码表在基于已知密明文的情况下,完成了最大程度的破译(如果有出错的部分可以及时跟我反馈):

首先是辅音部分:

image-20250714232343975

其次是元音部分:

image-20250714234601291

要注意给出的原文中有个别的音标好像存在丢失,但不影响我们进行后面的破译工作

码表建立后,转化成对应的单词即可,过程省略了,想尝试的朋友可以自己试试)

最终破译出来的内容大致如下,简单处理一下即可得到正确的flag

the content of flag is: come on little brave fox
replace letter o with number zero, letter l with number one
replace letter a with symbol at
make every letter e uppercase
use underline to link each word

Please Sign In

知识点省流

很简单的ai向量反演

WP

附件给了一个dockerfile、一个json还有一个服务端源码

源码如下,简单分析一下可以发现当用户传入的图片与预存的图片的均方误差低于0.000005时,就检测通过并回复flag

import uvicorn
import torch
import json
import os
from fastapi import FastAPI, File, UploadFile
from PIL import Image
from torchvision import transforms
from torchvision.models import shufflenet_v2_x1_0, ShuffleNet_V2_X1_0_Weights

feature_extractor = shufflenet_v2_x1_0(weights=ShuffleNet_V2_X1_0_Weights.IMAGENET1K_V1)
feature_extractor.fc = torch.nn.Identity()
feature_extractor.eval()

weights = ShuffleNet_V2_X1_0_Weights.IMAGENET1K_V1
transform = transforms.Compose([
    transforms.ToTensor(),
])

if not os.path.exists("embedding.json"):
    user_image = Image.open("user_image.jpg").convert("RGB")
    user_image = transform(user_image).unsqueeze(0)
    with torch.no_grad():
        user_embedding = feature_extractor(user_image)[0]

    with open("embedding.json", "w") as f:
        json.dump(user_embedding.tolist(), f)
    
user_embedding = json.load(open("embedding.json", "r"))
user_embedding = torch.tensor(user_embedding, dtype=torch.float32)
user_embedding = user_embedding.unsqueeze(0)
    
app = FastAPI()

@app.post("/signin/")
async def signin(file: UploadFile = File(...)):
    submit_image = Image.open(file.file).convert("RGB")
    submit_image = transform(submit_image).unsqueeze(0)
    with torch.no_grad():
        submit_embedding = feature_extractor(submit_image)[0]
    diff = torch.mean((user_embedding - submit_embedding) ** 2)
    result = {
        "status": "L3HCTF{test_flag}" if diff.item() < 5e-6 else "failure"
    }
    return result

@app.get("/")
async def root():
    return {"message": "Welcome to the Face Recognition API!"}

if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8000)

因为条件不复杂,直接让gpt写个脚本就出了

import torch
from torchvision import transforms
from torchvision.models import shufflenet_v2_x1_0, ShuffleNet_V2_X1_0_Weights
from PIL import Image
import json
import requests
import os

def invert_embedding_to_image(embedding_path, output_path, steps=500, lr=0.1):
    # Load model
    model = shufflenet_v2_x1_0(weights=ShuffleNet_V2_X1_0_Weights.IMAGENET1K_V1)
    model.fc = torch.nn.Identity()
    model.eval()

    # Load target embedding
    with open(embedding_path, 'r') as f:
        target_emb = torch.tensor(json.load(f), dtype=torch.float32)

    # Initialize trainable image tensor (noise)
    img = torch.randn(1, 3, 224, 224, requires_grad=True)
    optimizer = torch.optim.Adam([img], lr=lr)

    for step in range(steps):
        optimizer.zero_grad()
        # Sigmoid to bound pixels between 0 and 1
        clipped = img.sigmoid()
        emb = model(clipped)[0]
        loss = torch.nn.functional.mse_loss(emb, target_emb)
        loss.backward()
        optimizer.step()
        if step % 50 == 0:
            print(f'Step {step}, Loss {loss.item():.6e}')

    # Convert to image and save
    result = (clipped.detach().squeeze().permute(1, 2, 0).cpu().numpy() * 255).astype('uint8')
    inv_image = Image.fromarray(result)
    inv_image.save(output_path)
    print(f"Inverted image saved to '{output_path}'")

def upload_and_print_response(image_path, server_url):
    if not os.path.exists(image_path):
        print(f"File '{image_path}' not found.")
        return
    with open(image_path, 'rb') as f:
        files = {'file': f}
        response = requests.post(server_url, files=files)
    try:
        print("Server response:", response.json())
    except ValueError:
        print("Server response (text):", response.text)

if __name__ == "__main__":
    EMB_PATH = 'embedding.json'
    OUT_IMG = 'inverted_image.png'
    SIGNIN_URL = 'http://1.95.8.146:50001/signin/'

    # Step 1: Invert embedding to image
    invert_embedding_to_image(EMB_PATH, OUT_IMG, steps=500, lr=0.1)

    # Step 2: Upload image and print the flag or failure
    upload_and_print_response(OUT_IMG, SIGNIN_URL)

a022ad00-bdbf-40f2-9d4d-de896d218357

PaperBack

知识点省流

基本算是个小众工具题

WP

下载附件得到一张奇怪的bmp图

image-20250714235117685

但事实上救赎之道就在题目中,有这么一个与题目同名的工具:https://ollydbg.de/Paperbak/

用这个工具打开这个bmp图片后,他会导出一个ws文件

image-20250714235253080

打开文件会发现里面有奇怪的缩进 空格

image-20250714235323743

将所有数据复制到厨子中,对其进行一些简单的处理:将缩进空格转为hex值后,再将20替换成0,09替换成1,再删掉没用的内容(090a)即可得到如下结果

image-20250714235553992

不难看出每段里藏了一个8位的二进制字符串,前面正好能跟L3H的二进制值对应,所以提取出来转换成ascii值即可得到完整flag

image-20250714235723350

qwq