2025第八届“强网”拟态防御国际精英挑战赛 - Misc - Ciallo_Encrypt - WriteUp
出题人碎碎念
很荣幸能给这强网拟态线上出了一道中等难度的Misc题(估计做过这题的师傅也能知道是我出的),尽管还是有很多瑕疵,但个人感觉是目前我出过的题里出的最好的(最大的问题是没做数据脱敏,希望大家轻骂),这次的考点主要是以往比较常见的github网络取证,并加上了一个读取私人仓库提交数据的操作(详细的后面会说)。
在整体的过程中,引入了我前一段时间自己研究的Ciallo编码( https://2hi5hu.cn/archives/Ciallo ) ,并经过些许调整变为了Ciallo加密,最终这道Ciallo_Encrypt应运而生。
比较有意思的是,很多选手在做我这道题的时候,中途拿到qq后都来加我小号了,还有人把我当大模型直接问我flag的哈哈哈()
希望以后还有更多的机会出一些更好的题目吧,如果师傅们觉得出的不好,给你带来了不好的体验,我表示十分抱歉,能不能轻点骂球球了ORZ
解题思路
题目无附件,只提供一个靶机,进去后如下,可以看到靶机简单实现了一个自定义加密,但并不清楚加密细节,而上面还有其他页面,分别为未开发完的解密器,日志以及管理端

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

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

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

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

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

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

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

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


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

回到前面的信息,在项目仓库里翻找是找不到对应的加密逻辑的,但是日志中提到的核心代码存放到了fork的私人仓库中,正常情况下,私人仓库肯定是没有办法访问的,但是这题可以,这是因为这个私人仓库是在原项目仓库里fork的,得益于github的仓库网络结构,我们有办法获取到私人仓库的信息,文章参考:https://trufflesecurity.com/blog/anyone-can-access-deleted-and-private-repo-data-github
我们只需要知道私人仓库中,某个提交的hash,即可直接访问查看这个提交的内容,从而暴露私人仓库里的信息
这里可以自己写脚本爆破,也可以利用工具Trufflehog进行爆破(效率高),自己写脚本爆破不用爆整个commit hash,爆短hash就行,也就是前4位,总共65536种可能

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

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

基于加密逻辑写解密脚本即可得到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其实是静态的)