2025第八届“强网”拟态防御国际精英挑战赛 - Misc - Ciallo_Encrypt - WriteUp


出题人碎碎念

很荣幸能给这强网拟态线上出了一道中等难度的Misc题(估计做过这题的师傅也能知道是我出的),尽管还是有很多瑕疵,但个人感觉是目前我出过的题里出的最好的(最大的问题是没做数据脱敏,希望大家轻骂),这次的考点主要是以往比较常见的github网络取证,并加上了一个读取私人仓库提交数据的操作(详细的后面会说)。
在整体的过程中,引入了我前一段时间自己研究的Ciallo编码( https://2hi5hu.cn/archives/Ciallo ) ,并经过些许调整变为了Ciallo加密,最终这道Ciallo_Encrypt应运而生。

比较有意思的是,很多选手在做我这道题的时候,中途拿到qq后都来加我小号了,还有人把我当大模型直接问我flag的哈哈哈()

希望以后还有更多的机会出一些更好的题目吧,如果师傅们觉得出的不好,给你带来了不好的体验,我表示十分抱歉,能不能轻点骂球球了ORZ

解题思路

题目无附件,只提供一个靶机,进去后如下,可以看到靶机简单实现了一个自定义加密,但并不清楚加密细节,而上面还有其他页面,分别为未开发完的解密器,日志以及管理端

image-20251017133408768

解密器点进去显示还没上线,但是提示了可以去看项目仓库

image-20251026005351601

查看日志端,也有提示项目代码传至仓库内

image-20251026005403494

其中的base64编码解码后得到信息,核心代码在fork的私人仓库中(后面会考)

image-20251026005407999

而管理端则需要账号密码,显然是需要想办法登进去的

image-20251026005410509

根据信息,我们要找到项目仓库,可以看到网页下标显示了Crafted by Yu2ul0ver,所以我们去github里搜一下这个id,可以看到有且只有一个用户

image-20251017134007748

查看其仓库,发现就一个项目仓库,正好就是靶机的项目

image-20251026005413564

在commit里面可以找到密码的信息,知道了密码是仓库名的md5值,而账号是邮箱

image-20251026005416642

在关闭的issue里看到一条数据,点进去可以找到qq,由此得到了邮箱就是对应的qq邮箱(这里偷懒了,直接用我自己的账号去聊了,下次一定脱敏)

image-20251026005419679

image-20251026005422205

根据信息登录进管理端,可以找到一条历史的加密记录,但是不知道加密逻辑

image-20251026005425701

回到前面的信息,在项目仓库里翻找是找不到对应的加密逻辑的,但是日志中提到的核心代码存放到了fork的私人仓库中,正常情况下,私人仓库肯定是没有办法访问的,但是这题可以,这是因为这个私人仓库是在原项目仓库里fork的,得益于github的仓库网络结构,我们有办法获取到私人仓库的信息,文章参考:https://trufflesecurity.com/blog/anyone-can-access-deleted-and-private-repo-data-github

我们只需要知道私人仓库中,某个提交的hash,即可直接访问查看这个提交的内容,从而暴露私人仓库里的信息

这里可以自己写脚本爆破,也可以利用工具Trufflehog进行爆破(效率高),自己写脚本爆破不用爆整个commit hash,爆短hash就行,也就是前4位,总共65536种可能

image-20251026005429177

爆破过程中会动态更新一个txt文档,用来记录是否有隐藏的hash可以访问,于是乎顺利得到一个hash(实际上你爆破的时候可能会得到不止一条hash,这大概率是因为我重复删除并创建了若干个仓库,这是为了让hash值的开头尽可能小,以保证脚本的爆破速度)

image-20251026005431790

只需要在原项目仓库地址的后面加上/commit/[hash]即可访问到这个提交的内容,不难发现就是加密的逻辑

image-20251026005434769

基于加密逻辑写解密脚本即可得到flag(ai都能梭哈)

from datetime import datetime
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
import hashlib
import base64


def ciallo_decrypt(encrypted_input, ts_str):
    ciallo_blocks = encrypted_input.strip().split(' ')
    binary_string = ''

    for block in ciallo_blocks:
        if not block:
            continue

        bits = [''] * 8

        # bit 0
        bits[0] = '1' if block[1] == '1' else '0'
        # bit 1
        bits[1] = '0' if block[2] == '@' else '1'
        # bit 2
        bits[2] = '1' if block[3] == '1' else '0'
        # bit 3
        bits[3] = '1' if block[4] == '1' else '0'
        # bit 4
        bits[4] = '0' if block[5] == '0' else '1'
        # bit 5
        bits[5] = '1' if block[6] == '一' else '0'
        # bit 6, 7
        if '°' in block:
            bits[6], bits[7] = '0', '0'
        elif '2' in block:
            bits[6], bits[7] = '1', '0'
        elif 'w' in block:
            bits[6], bits[7] = '1', '1'
        else:  # 默认情况
            bits[6], bits[7] = '0', '1'

        binary_string += "".join(bits)

    # 步骤2:将二进制字符串转换回字节
    utf8_bytes = int(binary_string, 2).to_bytes((len(binary_string) + 7) // 8, byteorder='big')

    # 步骤3:UTF-8 解码,得到 Base64 字符串
    enc_b64 = utf8_bytes.decode("utf-8")

    # 步骤4:Base64 解码,得到原始 AES 密文
    ciphertext = base64.b64decode(enc_b64)

    # 步骤5:进行 AES 解密
    # 使用与加密时相同的方式从时间字符串派生密钥
    key = hashlib.md5(ts_str.encode()).digest()

    # 初始化解密器
    cipher = AES.new(key, AES.MODE_ECB)

    try:
        # 解密并移除填充
        decrypted_padded_bytes = cipher.decrypt(ciphertext)
        original_data_bytes = unpad(decrypted_padded_bytes, AES.block_size)

        # 将解密后的字节解码为最终的明文字符串
        decrypted_text = original_data_bytes.decode('utf-8')

        return decrypted_text
    except (ValueError, KeyError) as e:
        print(f"解密失败!请检查你的密钥(ts_str)或密文是否正确。错误: {e}")
        return None


if __name__ == '__main__':
    encrypted_text = input("请输入密文: ")
    time_str = input("请输入时间戳: ")
    dt = datetime.strptime(time_str, "%Y-%m-%d %H:%M:%S")
    key_string = str(int(dt.timestamp()))

    print("密文:")
    print(encrypted_text)
    print("-" * 20)
    print(f"使用的Key: {key_string}")
    print("-" * 20)

    decrypted_result = ciallo_decrypt(encrypted_text, key_string)

    if decrypted_result:
        print("解密成功! 原文是:")
        print(decrypted_result)

PS:
出于某些妙妙原因,flag其实是静态的)

qwq